From 042f3f884962d64feaa126c59b937e4f67b44956 Mon Sep 17 00:00:00 2001 From: Marc Prud'hommeaux Date: Wed, 11 Mar 2026 20:16:11 -0400 Subject: [PATCH 1/6] Add test for JavaVirtualMachine --- .../JavaVirtualMachineTests.swift | 45 +++++++++++++++++++ 1 file changed, 45 insertions(+) create mode 100644 Tests/SwiftJavaJNICoreTests/JavaVirtualMachineTests.swift diff --git a/Tests/SwiftJavaJNICoreTests/JavaVirtualMachineTests.swift b/Tests/SwiftJavaJNICoreTests/JavaVirtualMachineTests.swift new file mode 100644 index 0000000..0faa574 --- /dev/null +++ b/Tests/SwiftJavaJNICoreTests/JavaVirtualMachineTests.swift @@ -0,0 +1,45 @@ +//===----------------------------------------------------------------------===// +// +// This source file is part of the Swift.org open source project +// +// Copyright (c) 2026 Apple Inc. and the Swift.org project authors +// Licensed under Apache License v2.0 +// +// See LICENSE.txt for license information +// See CONTRIBUTORS.txt for the list of Swift.org project authors +// +// SPDX-License-Identifier: Apache-2.0 +// +//===----------------------------------------------------------------------===// + +import SwiftJavaJNICore +import Testing + +@Suite +struct JavaVirtualMachineTests { + + @Test + func loadJVMAndCallMethod() throws { + // Create a shared JVM instance + let jvm = try JavaVirtualMachine.shared() + + // Get the JNI environment for the current thread + let env = try jvm.environment() + + // Find java.lang.System + let systemClass = env.interface.FindClass(env, "java/lang/System") + #expect(systemClass != nil, "Failed to find java.lang.System") + + // Look up the static method currentTimeMillis()J + let methodID = env.interface.GetStaticMethodID( + env, systemClass, "currentTimeMillis", "()J" + ) + #expect(methodID != nil, "Failed to find System.currentTimeMillis") + + // Call System.currentTimeMillis() — returns jlong + let timeMillis = env.interface.CallStaticLongMethodA( + env, systemClass, methodID, nil + ) + #expect(timeMillis > 0, "Expected a positive timestamp, got \(timeMillis)") + } +} From 0be6f605e2c2ac3689f6ce963d2122cd82a6cf59 Mon Sep 17 00:00:00 2001 From: Marc Prud'hommeaux Date: Wed, 11 Mar 2026 20:18:55 -0400 Subject: [PATCH 2/6] Install Java on CI hosts --- .github/workflows/pull_request.yml | 13 +++++++++++++ 1 file changed, 13 insertions(+) diff --git a/.github/workflows/pull_request.yml b/.github/workflows/pull_request.yml index 282d5ea..35d1392 100644 --- a/.github/workflows/pull_request.yml +++ b/.github/workflows/pull_request.yml @@ -19,6 +19,19 @@ jobs: enable_linux_checks: true linux_os_versions: '["amazonlinux2", "bookworm", "noble", "jammy", "rhel-ubi9"]' linux_exclude_swift_versions: "[{\"swift_version\": \"5.9\"}, {\"swift_version\": \"5.10\"}]" + linux_pre_build_command: | + if command -v apt-get >/dev/null 2>&1 ; then # bookworm, noble, jammy + if command -v sudo &> /dev/null && [ "$EUID" -ne 0 ]; then + sudo apt-get update -y + sudo apt-get install -y default-jdk + else + apt-get update -y + apt-get install -y default-jdk + fi + elif command -v yum >/dev/null 2>&1 ; then # amazonlinux2, rhel-ubi9 + yum update -y + yum install -y java-devel + fi enable_android_sdk_build: true enable_android_sdk_checks: true soundness: From 8f60ce62772c246142dcadffc036a8cb49cd03e8 Mon Sep 17 00:00:00 2001 From: Marc Prud'hommeaux Date: Wed, 11 Mar 2026 20:30:28 -0400 Subject: [PATCH 3/6] Update JVM search path and formatting --- .../VirtualMachine/JavaVirtualMachine.swift | 1 + .../JavaVirtualMachineTests.swift | 23 +++++++++++++++---- 2 files changed, 20 insertions(+), 4 deletions(-) diff --git a/Sources/SwiftJavaJNICore/VirtualMachine/JavaVirtualMachine.swift b/Sources/SwiftJavaJNICore/VirtualMachine/JavaVirtualMachine.swift index 04c2367..fbae254 100644 --- a/Sources/SwiftJavaJNICore/VirtualMachine/JavaVirtualMachine.swift +++ b/Sources/SwiftJavaJNICore/VirtualMachine/JavaVirtualMachine.swift @@ -446,6 +446,7 @@ private func loadLibJava() throws -> DylibType { "/usr/local/opt/java", "/usr/lib/jvm/default-java", // Ubuntu/Debian "/usr/lib/jvm/default", // Arch + "/usr/lib/jvm/java", // rhel-ubi9, amazonlinux2 ].first(where: { FileManager.default.fileExists(atPath: $0) }) diff --git a/Tests/SwiftJavaJNICoreTests/JavaVirtualMachineTests.swift b/Tests/SwiftJavaJNICoreTests/JavaVirtualMachineTests.swift index 0faa574..3a53c45 100644 --- a/Tests/SwiftJavaJNICoreTests/JavaVirtualMachineTests.swift +++ b/Tests/SwiftJavaJNICoreTests/JavaVirtualMachineTests.swift @@ -18,9 +18,18 @@ import Testing @Suite struct JavaVirtualMachineTests { - @Test + static var isSupportedPlatform: Bool { + #if os(Android) + // Android tests are not currently run within an .apk and so do not have any ambient JVM + return false + #else + return true + #endif + } + + @Test(.enabled(if: isSupportedPlatform)) func loadJVMAndCallMethod() throws { - // Create a shared JVM instance + // Load or create a shared JVM instance let jvm = try JavaVirtualMachine.shared() // Get the JNI environment for the current thread @@ -32,13 +41,19 @@ struct JavaVirtualMachineTests { // Look up the static method currentTimeMillis()J let methodID = env.interface.GetStaticMethodID( - env, systemClass, "currentTimeMillis", "()J" + env, + systemClass, + "currentTimeMillis", + "()J" ) #expect(methodID != nil, "Failed to find System.currentTimeMillis") // Call System.currentTimeMillis() — returns jlong let timeMillis = env.interface.CallStaticLongMethodA( - env, systemClass, methodID, nil + env, + systemClass, + methodID, + nil ) #expect(timeMillis > 0, "Expected a positive timestamp, got \(timeMillis)") } From 203858fb33f46de7f21a41b3ce8622edb48419cc Mon Sep 17 00:00:00 2001 From: Marc Prud'hommeaux Date: Wed, 11 Mar 2026 21:02:45 -0400 Subject: [PATCH 4/6] Disable JavaVirtualMachineTests when Java home cannot be found --- .../VirtualMachine/JavaVirtualMachine.swift | 52 +++++++++++++------ .../JavaVirtualMachineTests.swift | 5 +- 2 files changed, 40 insertions(+), 17 deletions(-) diff --git a/Sources/SwiftJavaJNICore/VirtualMachine/JavaVirtualMachine.swift b/Sources/SwiftJavaJNICore/VirtualMachine/JavaVirtualMachine.swift index fbae254..e5fdd3d 100644 --- a/Sources/SwiftJavaJNICore/VirtualMachine/JavaVirtualMachine.swift +++ b/Sources/SwiftJavaJNICore/VirtualMachine/JavaVirtualMachine.swift @@ -427,6 +427,42 @@ private func symbol(_ handle: DylibType, _ name: String) -> T? { } #endif +/// The Java installation folder for the system, found either with `JAVA_HOME` environment or by checking standard locations +func systemJavaHome() -> String? { + // always defer to JAVA_HOME when it is set in the environment + if let javaHome = ProcessInfo.processInfo.environment["JAVA_HOME"] { + return javaHome + } + + // if JAVA_HOME is unset, hunt around some standard locations + var defaultJavaPaths: [String] = [] + + #if os(macOS) + #if arch(x86_64) + defaultJavaPaths += ["/usr/local/opt/java"] // macOS Homebrew x86 + #else + defaultJavaPaths += ["/opt/homebrew/opt/java"] // macOS Homebrew + #endif + defaultJavaPaths += ["/System/Library/Frameworks/JavaVM.framework/Home"] // macOS legacy path + #endif + + #if os(Linux) + defaultJavaPaths += [ + "/usr/lib/jvm/default-java", // Ubuntu/Debian + "/usr/lib/jvm/default", // Arch + "/usr/lib/jvm/java", // rhel-ubi9, amazonlinux2 + ] + #endif + + if let javaHome = defaultJavaPaths.first(where: { + FileManager.default.fileExists(atPath: $0) + }) { + return javaHome + } + + return nil +} + /// Located the shared library that includes the `JNI_GetCreatedJavaVMs` and `JNI_CreateJavaVM` entry points to the `JNINativeInterface` function table private func loadLibJava() throws -> DylibType { #if os(Android) @@ -437,21 +473,7 @@ private func loadLibJava() throws -> DylibType { } #endif - guard - let javaHome = ProcessInfo.processInfo.environment["JAVA_HOME"] - ?? { - // if JAVA_HOME is unset, look in some standard locations - [ - "/opt/homebrew/opt/java", // macOS Homebrew - "/usr/local/opt/java", - "/usr/lib/jvm/default-java", // Ubuntu/Debian - "/usr/lib/jvm/default", // Arch - "/usr/lib/jvm/java", // rhel-ubi9, amazonlinux2 - ].first(where: { - FileManager.default.fileExists(atPath: $0) - }) - }() - else { + guard let javaHome = systemJavaHome() else { throw JavaVirtualMachine.VMError.javaHomeNotFound } diff --git a/Tests/SwiftJavaJNICoreTests/JavaVirtualMachineTests.swift b/Tests/SwiftJavaJNICoreTests/JavaVirtualMachineTests.swift index 3a53c45..c291973 100644 --- a/Tests/SwiftJavaJNICoreTests/JavaVirtualMachineTests.swift +++ b/Tests/SwiftJavaJNICoreTests/JavaVirtualMachineTests.swift @@ -12,8 +12,8 @@ // //===----------------------------------------------------------------------===// -import SwiftJavaJNICore import Testing +@testable import SwiftJavaJNICore @Suite struct JavaVirtualMachineTests { @@ -23,7 +23,8 @@ struct JavaVirtualMachineTests { // Android tests are not currently run within an .apk and so do not have any ambient JVM return false #else - return true + // disable test when we cannot find a system Java + return systemJavaHome() != nil #endif } From 0f7ed9f0253b490017135d4efef521028fc286e7 Mon Sep 17 00:00:00 2001 From: Marc Prud'hommeaux Date: Wed, 11 Mar 2026 21:08:51 -0400 Subject: [PATCH 5/6] Update formatting --- Tests/SwiftJavaJNICoreTests/JavaVirtualMachineTests.swift | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/Tests/SwiftJavaJNICoreTests/JavaVirtualMachineTests.swift b/Tests/SwiftJavaJNICoreTests/JavaVirtualMachineTests.swift index c291973..b24f57a 100644 --- a/Tests/SwiftJavaJNICoreTests/JavaVirtualMachineTests.swift +++ b/Tests/SwiftJavaJNICoreTests/JavaVirtualMachineTests.swift @@ -12,8 +12,8 @@ // //===----------------------------------------------------------------------===// -import Testing @testable import SwiftJavaJNICore +import Testing @Suite struct JavaVirtualMachineTests { From 9c2d7246daa9253547cd1a860cf3944394ea6664 Mon Sep 17 00:00:00 2001 From: Marc Prud'hommeaux Date: Wed, 11 Mar 2026 21:11:50 -0400 Subject: [PATCH 6/6] Fix formatting --- Tests/SwiftJavaJNICoreTests/JavaVirtualMachineTests.swift | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/Tests/SwiftJavaJNICoreTests/JavaVirtualMachineTests.swift b/Tests/SwiftJavaJNICoreTests/JavaVirtualMachineTests.swift index b24f57a..d2dedd5 100644 --- a/Tests/SwiftJavaJNICoreTests/JavaVirtualMachineTests.swift +++ b/Tests/SwiftJavaJNICoreTests/JavaVirtualMachineTests.swift @@ -12,9 +12,10 @@ // //===----------------------------------------------------------------------===// -@testable import SwiftJavaJNICore import Testing +@testable import SwiftJavaJNICore + @Suite struct JavaVirtualMachineTests {