From 5853e8f3bfc82218b766e1e6db1116c888b46e57 Mon Sep 17 00:00:00 2001 From: David Nestorovic Date: Thu, 6 Feb 2025 14:30:43 +0100 Subject: [PATCH 01/28] Re-implement JUnit suppport --- .../junit/platform/JUnitPlatformFeature.java | 80 ++++++++----- .../NativeImageConfigurationImpl.java | 6 - .../platform/NativeImageJUnitLauncher.java | 112 ++++++++++++++---- .../config/core/NativeImageConfiguration.java | 8 +- .../config/core/PluginConfigProvider.java | 4 - .../config/jupiter/JupiterConfigProvider.java | 63 ++-------- .../platform/PlatformConfigProvider.java | 51 +------- .../config/vintage/VintageConfigProvider.java | 27 +---- gradle/libs.versions.toml | 2 +- .../buildtools/gradle/NativeImagePlugin.java | 6 + .../buildtools/maven/NativeTestMojo.java | 9 ++ 11 files changed, 173 insertions(+), 195 deletions(-) diff --git a/common/junit-platform-native/src/main/java/org/graalvm/junit/platform/JUnitPlatformFeature.java b/common/junit-platform-native/src/main/java/org/graalvm/junit/platform/JUnitPlatformFeature.java index e9fc8c155..786773201 100644 --- a/common/junit-platform-native/src/main/java/org/graalvm/junit/platform/JUnitPlatformFeature.java +++ b/common/junit-platform-native/src/main/java/org/graalvm/junit/platform/JUnitPlatformFeature.java @@ -44,7 +44,7 @@ import org.graalvm.junit.platform.config.core.PluginConfigProvider; import org.graalvm.nativeimage.ImageSingletons; import org.graalvm.nativeimage.hosted.Feature; -import org.graalvm.nativeimage.hosted.RuntimeClassInitialization; +import org.graalvm.nativeimage.hosted.RuntimeReflection; import org.junit.platform.engine.DiscoverySelector; import org.junit.platform.engine.discovery.DiscoverySelectors; import org.junit.platform.engine.discovery.UniqueIdSelector; @@ -66,6 +66,7 @@ import java.util.List; import java.util.Optional; import java.util.ServiceLoader; +import java.util.function.BiConsumer; import java.util.function.Consumer; import java.util.stream.Collectors; import java.util.stream.Stream; @@ -74,10 +75,19 @@ public final class JUnitPlatformFeature implements Feature { public final boolean debug = System.getProperty("debug") != null; - private static final NativeImageConfigurationImpl nativeImageConfigImpl = new NativeImageConfigurationImpl(); private final ServiceLoader extensionConfigProviders = ServiceLoader.load(PluginConfigProvider.class); + public static void debug(String format, Object... args) { + if (debug()) { + System.out.printf("[Debug] " + format + "%n", args); + } + } + + public static boolean debug() { + return ImageSingletons.lookup(JUnitPlatformFeature.class).debug; + } + @Override public void duringSetup(DuringSetupAccess access) { forEachProvider(p -> p.onLoad(nativeImageConfigImpl)); @@ -85,29 +95,28 @@ public void duringSetup(DuringSetupAccess access) { @Override public void beforeAnalysis(BeforeAnalysisAccess access) { - RuntimeClassInitialization.initializeAtBuildTime(NativeImageJUnitLauncher.class); - List classpathRoots = access.getApplicationClassPath(); List selectors = getSelectors(classpathRoots); + registerTestClassesForReflection(selectors); - Launcher launcher = LauncherFactory.create(); - TestPlan testplan = discoverTestsAndRegisterTestClassesForReflection(launcher, selectors); - ImageSingletons.add(NativeImageJUnitLauncher.class, new NativeImageJUnitLauncher(launcher, testplan)); + /* support for Vintage */ + registerClassesForHamcrestSupport(access); } private List getSelectors(List classpathRoots) { try { - Path outputDir = Paths.get(System.getProperty(UniqueIdTrackingListener.OUTPUT_DIR_PROPERTY_NAME)); - String prefix = System.getProperty(UniqueIdTrackingListener.OUTPUT_FILE_PREFIX_PROPERTY_NAME, + Path uniqueIdDirectory = Paths.get(System.getProperty(UniqueIdTrackingListener.OUTPUT_DIR_PROPERTY_NAME)); + String uniqueIdFilePrefix = System.getProperty(UniqueIdTrackingListener.OUTPUT_FILE_PREFIX_PROPERTY_NAME, UniqueIdTrackingListener.DEFAULT_OUTPUT_FILE_PREFIX); - List selectors = readAllFiles(outputDir, prefix) + + List selectors = readAllFiles(uniqueIdDirectory, uniqueIdFilePrefix) .map(DiscoverySelectors::selectUniqueId) .collect(Collectors.toList()); if (!selectors.isEmpty()) { System.out.printf( "[junit-platform-native] Running in 'test listener' mode using files matching pattern [%s*] " + "found in folder [%s] and its subfolders.%n", - prefix, outputDir.toAbsolutePath()); + uniqueIdFilePrefix, uniqueIdDirectory.toAbsolutePath()); return selectors; } } catch (Exception ex) { @@ -122,18 +131,15 @@ private List getSelectors(List classpathRoots } /** - * Use the JUnit Platform Launcher to discover tests and register classes - * for reflection. + * Use the JUnit Platform Launcher to register classes for reflection. */ - private TestPlan discoverTestsAndRegisterTestClassesForReflection(Launcher launcher, - List selectors) { - + private void registerTestClassesForReflection(List selectors) { LauncherDiscoveryRequest request = LauncherDiscoveryRequestBuilder.request() .selectors(selectors) .build(); + Launcher launcher = LauncherFactory.create(); TestPlan testPlan = launcher.discover(request); - testPlan.getRoots().stream() .flatMap(rootIdentifier -> testPlan.getDescendants(rootIdentifier).stream()) .map(TestIdentifier::getSource) @@ -143,14 +149,18 @@ private TestPlan discoverTestsAndRegisterTestClassesForReflection(Launcher launc .map(ClassSource.class::cast) .map(ClassSource::getJavaClass) .forEach(this::registerTestClassForReflection); - - return testPlan; } private void registerTestClassForReflection(Class clazz) { debug("Registering test class for reflection: %s", clazz.getName()); nativeImageConfigImpl.registerAllClassMembersForReflection(clazz); forEachProvider(p -> p.onTestClassRegistered(clazz, nativeImageConfigImpl)); + + Class[] interfaces = clazz.getInterfaces(); + for (Class inter : interfaces) { + registerTestClassForReflection(inter); + } + Class superClass = clazz.getSuperclass(); if (superClass != null && superClass != Object.class) { registerTestClassForReflection(superClass); @@ -163,16 +173,6 @@ private void forEachProvider(Consumer consumer) { } } - public static void debug(String format, Object... args) { - if (debug()) { - System.out.printf("[Debug] " + format + "%n", args); - } - } - - public static boolean debug() { - return ImageSingletons.lookup(JUnitPlatformFeature.class).debug; - } - private Stream readAllFiles(Path dir, String prefix) throws IOException { return findFiles(dir, prefix).map(outputFile -> { try { @@ -192,4 +192,26 @@ private static Stream findFiles(Path dir, String prefix) throws IOExceptio && path.getFileName().toString().startsWith(prefix))); } + private static void registerClassesForHamcrestSupport(BeforeAnalysisAccess access) { + ClassLoader applicationLoader = access.getApplicationClassLoader(); + Class typeSafeMatcher = findClassOrNull(applicationLoader, "org.hamcrest.TypeSafeMatcher"); + Class typeSafeDiagnosingMatcher = findClassOrNull(applicationLoader, "org.hamcrest.TypeSafeDiagnosingMatcher"); + if (typeSafeMatcher != null || typeSafeDiagnosingMatcher != null) { + BiConsumer> registerMatcherForReflection = (a, c) -> RuntimeReflection.register(c.getDeclaredMethods()); + if (typeSafeMatcher != null) { + access.registerSubtypeReachabilityHandler(registerMatcherForReflection, typeSafeMatcher); + } + if (typeSafeDiagnosingMatcher != null) { + access.registerSubtypeReachabilityHandler(registerMatcherForReflection, typeSafeDiagnosingMatcher); + } + } + } + + private static Class findClassOrNull(ClassLoader loader, String className) { + try { + return loader.loadClass(className); + } catch (ClassNotFoundException e) { + return null; + } + } } diff --git a/common/junit-platform-native/src/main/java/org/graalvm/junit/platform/NativeImageConfigurationImpl.java b/common/junit-platform-native/src/main/java/org/graalvm/junit/platform/NativeImageConfigurationImpl.java index acda95462..a2d1a91eb 100644 --- a/common/junit-platform-native/src/main/java/org/graalvm/junit/platform/NativeImageConfigurationImpl.java +++ b/common/junit-platform-native/src/main/java/org/graalvm/junit/platform/NativeImageConfigurationImpl.java @@ -64,10 +64,4 @@ public void registerForReflection(Executable... methods) { public void registerForReflection(Field... fields) { RuntimeReflection.register(fields); } - - - @Override - public void initializeAtBuildTime(Class... classes) { - RuntimeClassInitialization.initializeAtBuildTime(classes); - } } diff --git a/common/junit-platform-native/src/main/java/org/graalvm/junit/platform/NativeImageJUnitLauncher.java b/common/junit-platform-native/src/main/java/org/graalvm/junit/platform/NativeImageJUnitLauncher.java index c386fa1c5..4266bbe20 100644 --- a/common/junit-platform-native/src/main/java/org/graalvm/junit/platform/NativeImageJUnitLauncher.java +++ b/common/junit-platform-native/src/main/java/org/graalvm/junit/platform/NativeImageJUnitLauncher.java @@ -42,38 +42,30 @@ package org.graalvm.junit.platform; import org.graalvm.nativeimage.ImageInfo; -import org.graalvm.nativeimage.ImageSingletons; -import org.junit.platform.launcher.Launcher; -import org.junit.platform.launcher.TestExecutionListener; -import org.junit.platform.launcher.TestPlan; +import org.junit.platform.engine.DiscoverySelector; +import org.junit.platform.engine.discovery.DiscoverySelectors; +import org.junit.platform.engine.discovery.UniqueIdSelector; +import org.junit.platform.launcher.*; +import org.junit.platform.launcher.core.LauncherDiscoveryRequestBuilder; +import org.junit.platform.launcher.core.LauncherFactory; import org.junit.platform.launcher.listeners.SummaryGeneratingListener; import org.junit.platform.launcher.listeners.TestExecutionSummary; +import org.junit.platform.launcher.listeners.UniqueIdTrackingListener; import org.junit.platform.reporting.legacy.xml.LegacyXmlReportGeneratingListener; +import java.io.IOException; import java.io.PrintWriter; +import java.io.UncheckedIOException; +import java.nio.file.Files; +import java.nio.file.Path; import java.nio.file.Paths; -import java.util.Arrays; -import java.util.LinkedList; +import java.util.*; +import java.util.stream.Collectors; +import java.util.stream.Stream; public class NativeImageJUnitLauncher { static final String DEFAULT_OUTPUT_FOLDER = Paths.get("test-results-native").resolve("test").toString(); - final Launcher launcher; - final TestPlan testPlan; - - public NativeImageJUnitLauncher(Launcher launcher, TestPlan testPlan) { - this.launcher = launcher; - this.testPlan = testPlan; - } - - public void registerTestExecutionListeners(TestExecutionListener testExecutionListener) { - launcher.registerTestExecutionListeners(testExecutionListener); - } - - public void execute() { - launcher.execute(testPlan); - } - static String stringPad(String input) { return String.format("%1$-20s", input); } @@ -84,7 +76,9 @@ public static void main(String... args) { System.exit(1); } + /* scan runtime arguments */ String xmlOutput = DEFAULT_OUTPUT_FOLDER; + String testIds = null; boolean silent = false; LinkedList arguments = new LinkedList<>(Arrays.asList(args)); @@ -96,6 +90,7 @@ public static void main(String... args) { System.out.println("----------------------------------------\n"); System.out.println("Flags:"); System.out.println(stringPad("--xml-output-dir") + "Selects report xml output directory (default: `" + DEFAULT_OUTPUT_FOLDER + "`)"); + System.out.println(stringPad("--test-ids") + "Provides path to generated testIDs"); System.out.println(stringPad("--silent") + "Only output xml without stdout summary"); System.out.println(stringPad("--help") + "Displays this help screen"); System.exit(0); @@ -103,6 +98,9 @@ public static void main(String... args) { case "--xml-output-dir": xmlOutput = arguments.poll(); break; + case "--test-ids": + testIds = arguments.poll(); + break; case "--silent": silent = true; break; @@ -113,9 +111,18 @@ public static void main(String... args) { } } - PrintWriter out = new PrintWriter(System.out); - NativeImageJUnitLauncher launcher = ImageSingletons.lookup(NativeImageJUnitLauncher.class); + if (xmlOutput == null) { + throw new RuntimeException("xml-output-dir argument passed incorrectly to the launcher class."); + } + + if (testIds == null) { + throw new RuntimeException("Test ids not provided to the launcher class."); + } + + Launcher launcher = LauncherFactory.create(); + TestPlan testPlan = getTestPlan(launcher, testIds); + PrintWriter out = new PrintWriter(System.out); if (!silent) { out.println("JUnit Platform on Native Image - report"); out.println("----------------------------------------\n"); @@ -126,7 +133,7 @@ public static void main(String... args) { SummaryGeneratingListener summaryListener = new SummaryGeneratingListener(); launcher.registerTestExecutionListeners(summaryListener); launcher.registerTestExecutionListeners(new LegacyXmlReportGeneratingListener(Paths.get(xmlOutput), out)); - launcher.execute(); + launcher.execute(testPlan); TestExecutionSummary summary = summaryListener.getSummary(); if (!silent) { @@ -137,4 +144,59 @@ public static void main(String... args) { long failedCount = summary.getTotalFailureCount(); System.exit(failedCount > 0 ? 1 : 0); } + + private static TestPlan getTestPlan(Launcher launcher, String testIDs) { + List selectors = getSelectors(testIDs); + if (selectors == null) { + throw new RuntimeException("Cannot compute test selectors from test ids."); + } + + LauncherDiscoveryRequest request = LauncherDiscoveryRequestBuilder.request() + .selectors(selectors) + .build(); + + return launcher.discover(request); + } + + private static List getSelectors(String testIDs) { + try { + Path outputDir = Paths.get(testIDs); + System.out.println("outputDir = " + outputDir); + String prefix = System.getProperty(UniqueIdTrackingListener.OUTPUT_FILE_PREFIX_PROPERTY_NAME, + UniqueIdTrackingListener.DEFAULT_OUTPUT_FILE_PREFIX); + List selectors = readAllFiles(outputDir, prefix) + .map(DiscoverySelectors::selectUniqueId) + .collect(Collectors.toList()); + if (!selectors.isEmpty()) { + System.out.printf( + "[junit-platform-native] Running in 'test listener' mode using files matching pattern [%s*] " + + "found in folder [%s] and its subfolders.%n", + prefix, outputDir.toAbsolutePath()); + return selectors; + } + } catch (Exception ex) { + throw new RuntimeException("Failed to read UIDs from UniqueIdTrackingListener output files: " + ex.getMessage()); + } + + return null; + } + + private static Stream readAllFiles(Path dir, String prefix) throws IOException { + return findFiles(dir, prefix).map(outputFile -> { + try { + return Files.readAllLines(outputFile); + } catch (IOException ex) { + throw new UncheckedIOException(ex); + } + }).flatMap(List::stream); + } + + private static Stream findFiles(Path dir, String prefix) throws IOException { + if (!Files.exists(dir)) { + return Stream.empty(); + } + return Files.find(dir, Integer.MAX_VALUE, + (path, basicFileAttributes) -> (basicFileAttributes.isRegularFile() + && path.getFileName().toString().startsWith(prefix))); + } } diff --git a/common/junit-platform-native/src/main/java/org/graalvm/junit/platform/config/core/NativeImageConfiguration.java b/common/junit-platform-native/src/main/java/org/graalvm/junit/platform/config/core/NativeImageConfiguration.java index 42e1f736c..96b9ec089 100644 --- a/common/junit-platform-native/src/main/java/org/graalvm/junit/platform/config/core/NativeImageConfiguration.java +++ b/common/junit-platform-native/src/main/java/org/graalvm/junit/platform/config/core/NativeImageConfiguration.java @@ -57,7 +57,7 @@ public interface NativeImageConfiguration { default void registerAllClassMembersForReflection(String... classNames) { registerAllClassMembersForReflection(Utils.toClasses(classNames)); - }; + } default void registerAllClassMembersForReflection(Class... classes) { for (Class clazz : classes) { @@ -68,10 +68,4 @@ default void registerAllClassMembersForReflection(Class... classes) { registerForReflection(clazz.getDeclaredFields()); } } - - default void initializeAtBuildTime(String... classNames) { - initializeAtBuildTime(Utils.toClasses(classNames)); - }; - - void initializeAtBuildTime(Class... classes); } diff --git a/common/junit-platform-native/src/main/java/org/graalvm/junit/platform/config/core/PluginConfigProvider.java b/common/junit-platform-native/src/main/java/org/graalvm/junit/platform/config/core/PluginConfigProvider.java index d55c59e81..8b104c489 100644 --- a/common/junit-platform-native/src/main/java/org/graalvm/junit/platform/config/core/PluginConfigProvider.java +++ b/common/junit-platform-native/src/main/java/org/graalvm/junit/platform/config/core/PluginConfigProvider.java @@ -47,8 +47,4 @@ public interface PluginConfigProvider { void onTestClassRegistered(Class testClass, NativeImageConfiguration registry); - default int getMajorJDKVersion() { - return Runtime.version().feature(); - } - } diff --git a/common/junit-platform-native/src/main/java/org/graalvm/junit/platform/config/jupiter/JupiterConfigProvider.java b/common/junit-platform-native/src/main/java/org/graalvm/junit/platform/config/jupiter/JupiterConfigProvider.java index f8b3f7959..0271db1b4 100644 --- a/common/junit-platform-native/src/main/java/org/graalvm/junit/platform/config/jupiter/JupiterConfigProvider.java +++ b/common/junit-platform-native/src/main/java/org/graalvm/junit/platform/config/jupiter/JupiterConfigProvider.java @@ -46,6 +46,7 @@ import org.graalvm.junit.platform.config.util.AnnotationUtils; import org.junit.jupiter.api.DisplayNameGeneration; import org.junit.jupiter.api.IndicativeSentencesGeneration; +import org.junit.jupiter.api.MethodOrderer; import org.junit.jupiter.api.TestMethodOrder; import org.junit.jupiter.api.condition.DisabledIf; import org.junit.jupiter.api.condition.EnabledIf; @@ -55,12 +56,11 @@ import org.junit.jupiter.params.provider.ArgumentsSource; import org.junit.jupiter.params.provider.EnumSource; import org.junit.jupiter.params.provider.MethodSource; -import org.junit.platform.commons.support.AnnotationSupport; +import org.junit.jupiter.params.provider.FieldSource; import java.lang.reflect.Method; import java.util.ArrayList; import java.util.List; -import java.util.Optional; import static org.graalvm.junit.platform.JUnitPlatformFeature.debug; @@ -68,46 +68,6 @@ public class JupiterConfigProvider implements PluginConfigProvider { @Override public void onLoad(NativeImageConfiguration config) { - config.initializeAtBuildTime( - "org.junit.jupiter.api.condition.OS", - "org.junit.jupiter.engine.config.EnumConfigurationParameterConverter", - "org.junit.jupiter.engine.descriptor.ClassTestDescriptor", - "org.junit.jupiter.engine.descriptor.ClassBasedTestDescriptor", - "org.junit.jupiter.engine.descriptor.TestFactoryTestDescriptor", - "org.junit.jupiter.engine.descriptor.JupiterTestDescriptor", - "org.junit.jupiter.engine.descriptor.JupiterTestDescriptor$1", - "org.junit.jupiter.engine.descriptor.MethodBasedTestDescriptor", - "org.junit.jupiter.engine.descriptor.TestMethodTestDescriptor", - "org.junit.jupiter.engine.descriptor.TestTemplateTestDescriptor", - "org.junit.jupiter.engine.execution.ConditionEvaluator", - "org.junit.jupiter.engine.execution.ExecutableInvoker", - "org.junit.jupiter.params.provider.EnumSource$Mode", - // new in Junit 5.10 - "org.junit.platform.launcher.core.LauncherConfig", - "org.junit.jupiter.engine.config.InstantiatingConfigurationParameterConverter" - ); - - if (getMajorJDKVersion() >= 21) { - /* new with simulated class initialization */ - config.initializeAtBuildTime( - "org.junit.jupiter.api.DisplayNameGenerator$Standard", - "org.junit.jupiter.api.extension.ConditionEvaluationResult", - "org.junit.jupiter.api.TestInstance$Lifecycle", - "org.junit.jupiter.engine.config.CachingJupiterConfiguration", - "org.junit.jupiter.engine.config.DefaultJupiterConfiguration", - "org.junit.jupiter.engine.descriptor.DynamicDescendantFilter", - "org.junit.jupiter.engine.descriptor.JupiterEngineDescriptor", - "org.junit.jupiter.engine.descriptor.NestedClassTestDescriptor", - "org.junit.jupiter.engine.execution.InterceptingExecutableInvoker", - "org.junit.jupiter.engine.execution.InterceptingExecutableInvoker$ReflectiveInterceptorCall", - "org.junit.jupiter.engine.execution.InterceptingExecutableInvoker$ReflectiveInterceptorCall$VoidMethodInterceptorCall", - "org.junit.jupiter.engine.execution.InvocationInterceptorChain", - "org.junit.jupiter.engine.JupiterTestEngine", - "org.junit.jupiter.params.provider.EnumSource$Mode$Validator" - ); - } - - config.registerAllClassMembersForReflection( "org.junit.jupiter.engine.extension.TimeoutExtension$ExecutorResource", "org.junit.jupiter.engine.extension.TimeoutInvocationFactory$SingleThreadExecutorResource" @@ -116,15 +76,16 @@ public void onLoad(NativeImageConfiguration config) { @Override public void onTestClassRegistered(Class testClass, NativeImageConfiguration registry) { + AnnotationUtils.registerClassesFromAnnotationForReflection(testClass, registry, TestMethodOrder.class, TestMethodOrder::value); AnnotationUtils.registerClassesFromAnnotationForReflection(testClass, registry, ArgumentsSource.class, ArgumentsSource::value); AnnotationUtils.registerClassesFromAnnotationForReflection(testClass, registry, ExtendWith.class, ExtendWith::value); - AnnotationUtils.forEachAnnotatedMethod(testClass, EnumSource.class, (m, annotation) -> handleEnumSource(m, annotation, registry)); - handleTestOrderer(testClass, registry); AnnotationUtils.registerClassesFromAnnotationForReflection(testClass, registry, DisplayNameGeneration.class, DisplayNameGeneration::value); AnnotationUtils.registerClassesFromAnnotationForReflection(testClass, registry, IndicativeSentencesGeneration.class, IndicativeSentencesGeneration::generator); AnnotationUtils.forEachAnnotatedMethodParameter(testClass, ConvertWith.class, annotation -> registry.registerAllClassMembersForReflection(annotation.value())); AnnotationUtils.forEachAnnotatedMethodParameter(testClass, AggregateWith.class, annotation -> registry.registerAllClassMembersForReflection(annotation.value())); + AnnotationUtils.forEachAnnotatedMethod(testClass, EnumSource.class, (m, annotation) -> handleEnumSource(m, annotation, registry)); AnnotationUtils.registerClassesFromAnnotationForReflection(testClass, registry, MethodSource.class, JupiterConfigProvider::handleMethodSource); + AnnotationUtils.registerClassesFromAnnotationForReflection(testClass, registry, FieldSource.class, JupiterConfigProvider::handleFieldSource); AnnotationUtils.registerClassesFromAnnotationForReflection(testClass, registry, EnabledIf.class, JupiterConfigProvider::handleEnabledIf); AnnotationUtils.registerClassesFromAnnotationForReflection(testClass, registry, DisabledIf.class, JupiterConfigProvider::handleDisabledIf); } @@ -133,6 +94,10 @@ private static Class[] handleMethodSource(MethodSource annotation) { return handleMethodReference(annotation.value()); } + private static Class[] handleFieldSource(FieldSource annotation) { + return handleMethodReference(annotation.value()); + } + private static Class[] handleEnabledIf(EnabledIf annotation) { return handleMethodReference(annotation.value()); } @@ -181,14 +146,4 @@ public static void handleEnumSource(Method method, EnumSource source, NativeImag debug("Method doesn't have at least 1 parameter - skipping enum registration. Method: %s", method); } } - - private static void handleTestOrderer(Class testClass, NativeImageConfiguration registry) { - Optional annotation = AnnotationSupport.findAnnotation(testClass, TestMethodOrder.class); - if (annotation.isPresent()) { - TestMethodOrder testMethodOrder = annotation.get(); - Class clazz = testMethodOrder.value(); - registry.initializeAtBuildTime(clazz); - } - } - } diff --git a/common/junit-platform-native/src/main/java/org/graalvm/junit/platform/config/platform/PlatformConfigProvider.java b/common/junit-platform-native/src/main/java/org/graalvm/junit/platform/config/platform/PlatformConfigProvider.java index 4b94023b1..509874382 100644 --- a/common/junit-platform-native/src/main/java/org/graalvm/junit/platform/config/platform/PlatformConfigProvider.java +++ b/common/junit-platform-native/src/main/java/org/graalvm/junit/platform/config/platform/PlatformConfigProvider.java @@ -43,58 +43,15 @@ import org.graalvm.junit.platform.config.core.NativeImageConfiguration; import org.graalvm.junit.platform.config.core.PluginConfigProvider; +import org.graalvm.nativeimage.hosted.RuntimeSerialization; +import org.junit.platform.launcher.TestIdentifier; + public class PlatformConfigProvider implements PluginConfigProvider { @Override public void onLoad(NativeImageConfiguration config) { - config.initializeAtBuildTime( - "org.junit.platform.launcher.TestIdentifier", - "org.junit.platform.launcher.core.InternalTestPlan", - "org.junit.platform.commons.util.StringUtils", - "org.junit.platform.launcher.core.TestExecutionListenerRegistry", - "org.junit.platform.commons.logging.LoggerFactory$DelegatingLogger", - "org.junit.platform.launcher.core.EngineDiscoveryOrchestrator", - "org.junit.platform.launcher.core.LauncherConfigurationParameters", - "org.junit.platform.commons.logging.LoggerFactory", - "org.junit.platform.engine.UniqueIdFormat", - "org.junit.platform.commons.util.ReflectionUtils", - // https://github.com/graalvm/native-build-tools/issues/300 - "org.junit.platform.reporting.open.xml.OpenTestReportGeneratingListener", - // https://github.com/graalvm/native-build-tools/issues/602 - "org.junit.platform.commons.util.LruCache" - ); - - if (getMajorJDKVersion() >= 21) { - /* new with --strict-image-heap */ - config.initializeAtBuildTime( - "org.junit.platform.engine.support.descriptor.ClassSource", - "org.junit.platform.engine.support.descriptor.MethodSource", - "org.junit.platform.engine.support.hierarchical.Node$ExecutionMode", - "org.junit.platform.engine.TestDescriptor$Type", - "org.junit.platform.engine.UniqueId", - "org.junit.platform.engine.UniqueId$Segment", - "org.junit.platform.launcher.core.DefaultLauncher", - "org.junit.platform.launcher.core.DefaultLauncherConfig", - "org.junit.platform.launcher.core.EngineExecutionOrchestrator", - "org.junit.platform.launcher.core.LauncherConfigurationParameters$ParameterProvider$1", - "org.junit.platform.launcher.core.LauncherConfigurationParameters$ParameterProvider$2", - "org.junit.platform.launcher.core.LauncherConfigurationParameters$ParameterProvider$3", - "org.junit.platform.launcher.core.LauncherConfigurationParameters$ParameterProvider$4", - "org.junit.platform.launcher.core.LauncherDiscoveryResult", - "org.junit.platform.launcher.core.LauncherListenerRegistry", - "org.junit.platform.launcher.core.ListenerRegistry", - "org.junit.platform.launcher.core.SessionPerRequestLauncher", - "org.junit.platform.launcher.LauncherSessionListener$1", - "org.junit.platform.launcher.listeners.UniqueIdTrackingListener", - "org.junit.platform.reporting.shadow.org.opentest4j.reporting.events.api.DocumentWriter$1", - "org.junit.platform.suite.engine.SuiteEngineDescriptor", - "org.junit.platform.suite.engine.SuiteLauncher", - "org.junit.platform.suite.engine.SuiteTestDescriptor", - "org.junit.platform.suite.engine.SuiteTestEngine" - ); - } - + RuntimeSerialization.register(TestIdentifier.class.getDeclaredClasses()); try { /* Verify if the core JUnit Platform test class is available on the classpath */ Class.forName("org.junit.platform.commons.annotation.Testable"); diff --git a/common/junit-platform-native/src/main/java/org/graalvm/junit/platform/config/vintage/VintageConfigProvider.java b/common/junit-platform-native/src/main/java/org/graalvm/junit/platform/config/vintage/VintageConfigProvider.java index af653cbbe..539d27da5 100644 --- a/common/junit-platform-native/src/main/java/org/graalvm/junit/platform/config/vintage/VintageConfigProvider.java +++ b/common/junit-platform-native/src/main/java/org/graalvm/junit/platform/config/vintage/VintageConfigProvider.java @@ -43,33 +43,16 @@ import org.graalvm.junit.platform.config.core.NativeImageConfiguration; import org.graalvm.junit.platform.config.core.PluginConfigProvider; +import org.graalvm.nativeimage.hosted.RuntimeSerialization; public class VintageConfigProvider implements PluginConfigProvider { @Override public void onLoad(NativeImageConfiguration config) { - config.initializeAtBuildTime( - "org.junit.vintage.engine.descriptor.RunnerTestDescriptor", - "org.junit.vintage.engine.support.UniqueIdReader", - "org.junit.vintage.engine.support.UniqueIdStringifier", - "org.junit.runner.Description", - "org.junit.runners.BlockJUnit4ClassRunner", - "org.junit.runners.JUnit4", - /* Workaround until we can register serializable classes from a native-image feature */ - "org.junit.runner.Result" - ); - - if (getMajorJDKVersion() >= 21) { - /* new with simulated class initialization */ - config.initializeAtBuildTime( - "java.lang.annotation.Annotation", - "org.junit.runners.model.FrameworkMethod", - "org.junit.runners.model.TestClass", - "org.junit.runners.ParentRunner$1", - "org.junit.Test", - "org.junit.vintage.engine.descriptor.VintageEngineDescriptor", - "org.junit.vintage.engine.VintageTestEngine" - ); + try { + RuntimeSerialization.register(Class.forName("org.junit.runner.Result").getDeclaredClasses()); + } catch (ClassNotFoundException e) { + System.out.println("Cannot register declared classes of org.junit.runner.Result for serialization. Vintage JUnit not available."); } } diff --git a/gradle/libs.versions.toml b/gradle/libs.versions.toml index 101943a79..aa952608a 100644 --- a/gradle/libs.versions.toml +++ b/gradle/libs.versions.toml @@ -12,7 +12,7 @@ mavenResolver = "1.9.22" graalvm = "23.0.2" openjson = "1.0.13" junitPlatform = "1.10.0" -junitJupiter = "5.10.0" +junitJupiter = "5.11.0" slf4j = "1.7.9" groovy = "3.0.11" jetty = "11.0.11" diff --git a/native-gradle-plugin/src/main/java/org/graalvm/buildtools/gradle/NativeImagePlugin.java b/native-gradle-plugin/src/main/java/org/graalvm/buildtools/gradle/NativeImagePlugin.java index dfa3b521a..b066fabcf 100644 --- a/native-gradle-plugin/src/main/java/org/graalvm/buildtools/gradle/NativeImagePlugin.java +++ b/native-gradle-plugin/src/main/java/org/graalvm/buildtools/gradle/NativeImagePlugin.java @@ -65,6 +65,7 @@ import org.graalvm.buildtools.gradle.tasks.actions.CleanupAgentFilesAction; import org.graalvm.buildtools.gradle.tasks.actions.MergeAgentFilesAction; import org.graalvm.buildtools.utils.SharedConstants; +import org.graalvm.nativeimage.ImageSingletons; import org.graalvm.reachability.DirectoryConfiguration; import org.gradle.api.Action; import org.gradle.api.NamedDomainObjectContainer; @@ -764,14 +765,19 @@ private static NativeImageOptions createTestOptions(GraalVMExtension graalExtens testExtension.getMainClass().set("org.graalvm.junit.platform.NativeImageJUnitLauncher"); testExtension.getMainClass().finalizeValue(); testExtension.getImageName().convention(mainExtension.getImageName().map(name -> name + SharedConstants.NATIVE_TESTS_SUFFIX)); + ListProperty runtimeArgs = testExtension.getRuntimeArgs(); runtimeArgs.add("--xml-output-dir"); runtimeArgs.add(project.getLayout().getBuildDirectory().dir("test-results/" + binaryName + "-native").map(d -> d.getAsFile().getAbsolutePath())); + runtimeArgs.add("--test-ids"); + runtimeArgs.add(project.getLayout().getBuildDirectory().dir("test-results/" + binaryName + "/testlist").map(d -> d.getAsFile().getAbsolutePath())); + testExtension.buildArgs("--features=org.graalvm.junit.platform.JUnitPlatformFeature"); ConfigurableFileCollection classpath = testExtension.getClasspath(); classpath.from(configs.getImageClasspathConfiguration()); classpath.from(sourceSet.getOutput().getClassesDirs()); classpath.from(sourceSet.getOutput().getResourcesDir()); + return testExtension; } diff --git a/native-maven-plugin/src/main/java/org/graalvm/buildtools/maven/NativeTestMojo.java b/native-maven-plugin/src/main/java/org/graalvm/buildtools/maven/NativeTestMojo.java index a59009a57..f5c3926e9 100644 --- a/native-maven-plugin/src/main/java/org/graalvm/buildtools/maven/NativeTestMojo.java +++ b/native-maven-plugin/src/main/java/org/graalvm/buildtools/maven/NativeTestMojo.java @@ -218,6 +218,12 @@ private void runNativeTests(Path executable) throws MojoExecutionException { if (!xmlLocation.toFile().exists() && !xmlLocation.toFile().mkdirs()) { throw new MojoExecutionException("Failed creating xml output directory"); } + + Path testIdsLocation = outputDirectory.toPath().resolve("test-ids"); + if (!testIdsLocation.toFile().exists()) { + throw new MojoExecutionException("Test-ids not available under target/test-ids"); + } + try { ProcessBuilder processBuilder = new ProcessBuilder(executable.toAbsolutePath().toString()); processBuilder.inheritIO(); @@ -226,7 +232,10 @@ private void runNativeTests(Path executable) throws MojoExecutionException { List command = new ArrayList<>(); command.add("--xml-output-dir"); command.add(xmlLocation.toString()); + command.add("--test-ids"); + command.add(testIdsLocation.toString()); systemProperties.forEach((key, value) -> command.add("-D" + key + "=" + value)); + processBuilder.command().addAll(command); processBuilder.environment().putAll(environment); From d04129422ef09d1689b4e34b263f5725e833b43e Mon Sep 17 00:00:00 2001 From: David Nestorovic Date: Wed, 19 Feb 2025 15:23:50 +0100 Subject: [PATCH 02/28] Make the feature work with previous class initialization strategy --- .../junit/platform/JUnitPlatformFeature.java | 21 +++++++++++-------- .../platform/NativeImageJUnitLauncher.java | 6 +----- .../config/jupiter/JupiterConfigProvider.java | 5 ----- .../platform/PlatformConfigProvider.java | 8 ------- 4 files changed, 13 insertions(+), 27 deletions(-) diff --git a/common/junit-platform-native/src/main/java/org/graalvm/junit/platform/JUnitPlatformFeature.java b/common/junit-platform-native/src/main/java/org/graalvm/junit/platform/JUnitPlatformFeature.java index 786773201..1fdac7d96 100644 --- a/common/junit-platform-native/src/main/java/org/graalvm/junit/platform/JUnitPlatformFeature.java +++ b/common/junit-platform-native/src/main/java/org/graalvm/junit/platform/JUnitPlatformFeature.java @@ -44,7 +44,9 @@ import org.graalvm.junit.platform.config.core.PluginConfigProvider; import org.graalvm.nativeimage.ImageSingletons; import org.graalvm.nativeimage.hosted.Feature; +import org.graalvm.nativeimage.hosted.RuntimeClassInitialization; import org.graalvm.nativeimage.hosted.RuntimeReflection; + import org.junit.platform.engine.DiscoverySelector; import org.junit.platform.engine.discovery.DiscoverySelectors; import org.junit.platform.engine.discovery.UniqueIdSelector; @@ -95,15 +97,20 @@ public void duringSetup(DuringSetupAccess access) { @Override public void beforeAnalysis(BeforeAnalysisAccess access) { - List classpathRoots = access.getApplicationClassPath(); - List selectors = getSelectors(classpathRoots); + /* Before GraalVM version 22 we couldn't have classes initialized at run-time + that are also used at build-time but not added to the image heap */ + if (Runtime.version().feature() <= 21) { + RuntimeClassInitialization.initializeAtBuildTime("org.junit"); + } + + List selectors = getSelectors(); registerTestClassesForReflection(selectors); - /* support for Vintage */ + /* support for JUnit Vintage */ registerClassesForHamcrestSupport(access); } - private List getSelectors(List classpathRoots) { + private List getSelectors() { try { Path uniqueIdDirectory = Paths.get(System.getProperty(UniqueIdTrackingListener.OUTPUT_DIR_PROPERTY_NAME)); String uniqueIdFilePrefix = System.getProperty(UniqueIdTrackingListener.OUTPUT_FILE_PREFIX_PROPERTY_NAME, @@ -123,11 +130,7 @@ private List getSelectors(List classpathRoots debug("Failed to read UIDs from UniqueIdTrackingListener output files: " + ex.getMessage()); } - System.out.println("[junit-platform-native] Running in 'test discovery' mode. Note that this is a fallback mode."); - if (debug) { - classpathRoots.forEach(entry -> debug("Selecting classpath root: " + entry)); - } - return DiscoverySelectors.selectClasspathRoots(new HashSet<>(classpathRoots)); + throw new RuntimeException("Cannot compute test selectors from test ids."); } /** diff --git a/common/junit-platform-native/src/main/java/org/graalvm/junit/platform/NativeImageJUnitLauncher.java b/common/junit-platform-native/src/main/java/org/graalvm/junit/platform/NativeImageJUnitLauncher.java index 4266bbe20..f52288c44 100644 --- a/common/junit-platform-native/src/main/java/org/graalvm/junit/platform/NativeImageJUnitLauncher.java +++ b/common/junit-platform-native/src/main/java/org/graalvm/junit/platform/NativeImageJUnitLauncher.java @@ -147,10 +147,6 @@ public static void main(String... args) { private static TestPlan getTestPlan(Launcher launcher, String testIDs) { List selectors = getSelectors(testIDs); - if (selectors == null) { - throw new RuntimeException("Cannot compute test selectors from test ids."); - } - LauncherDiscoveryRequest request = LauncherDiscoveryRequestBuilder.request() .selectors(selectors) .build(); @@ -178,7 +174,7 @@ private static List getSelectors(String testIDs) { throw new RuntimeException("Failed to read UIDs from UniqueIdTrackingListener output files: " + ex.getMessage()); } - return null; + throw new RuntimeException("Cannot compute test selectors from test ids."); } private static Stream readAllFiles(Path dir, String prefix) throws IOException { diff --git a/common/junit-platform-native/src/main/java/org/graalvm/junit/platform/config/jupiter/JupiterConfigProvider.java b/common/junit-platform-native/src/main/java/org/graalvm/junit/platform/config/jupiter/JupiterConfigProvider.java index 0271db1b4..3ad7aa3b4 100644 --- a/common/junit-platform-native/src/main/java/org/graalvm/junit/platform/config/jupiter/JupiterConfigProvider.java +++ b/common/junit-platform-native/src/main/java/org/graalvm/junit/platform/config/jupiter/JupiterConfigProvider.java @@ -46,7 +46,6 @@ import org.graalvm.junit.platform.config.util.AnnotationUtils; import org.junit.jupiter.api.DisplayNameGeneration; import org.junit.jupiter.api.IndicativeSentencesGeneration; -import org.junit.jupiter.api.MethodOrderer; import org.junit.jupiter.api.TestMethodOrder; import org.junit.jupiter.api.condition.DisabledIf; import org.junit.jupiter.api.condition.EnabledIf; @@ -68,10 +67,6 @@ public class JupiterConfigProvider implements PluginConfigProvider { @Override public void onLoad(NativeImageConfiguration config) { - config.registerAllClassMembersForReflection( - "org.junit.jupiter.engine.extension.TimeoutExtension$ExecutorResource", - "org.junit.jupiter.engine.extension.TimeoutInvocationFactory$SingleThreadExecutorResource" - ); } @Override diff --git a/common/junit-platform-native/src/main/java/org/graalvm/junit/platform/config/platform/PlatformConfigProvider.java b/common/junit-platform-native/src/main/java/org/graalvm/junit/platform/config/platform/PlatformConfigProvider.java index 509874382..ed552d9af 100644 --- a/common/junit-platform-native/src/main/java/org/graalvm/junit/platform/config/platform/PlatformConfigProvider.java +++ b/common/junit-platform-native/src/main/java/org/graalvm/junit/platform/config/platform/PlatformConfigProvider.java @@ -52,14 +52,6 @@ public class PlatformConfigProvider implements PluginConfigProvider { @Override public void onLoad(NativeImageConfiguration config) { RuntimeSerialization.register(TestIdentifier.class.getDeclaredClasses()); - try { - /* Verify if the core JUnit Platform test class is available on the classpath */ - Class.forName("org.junit.platform.commons.annotation.Testable"); - } catch (ClassNotFoundException e) { - throw new RuntimeException("Missing some JUnit Platform classes for runtime reflection configuration. \n" + - "Check if JUnit Platform is on your classpath or if that version is supported. \n" + - "Original error: " + e); - } } @Override From 6ec21ea99cfa87e3434d3b0a8e176ebef0a1211c Mon Sep 17 00:00:00 2001 From: David Nestorovic Date: Thu, 20 Feb 2025 12:42:19 +0100 Subject: [PATCH 03/28] Add maven and gradle functional tests for JUnit --- .../junit/platform/JUnitPlatformFeature.java | 2 +- .../gradle/JUnitFunctionalTests.groovy | 84 +++++++++++ .../maven/JUnitFunctionalTests.groovy | 71 +++++++++ samples/junit-tests/build.gradle | 65 ++++++++ samples/junit-tests/gradle.properties | 1 + samples/junit-tests/pom.xml | 139 ++++++++++++++++++ samples/junit-tests/settings.gradle | 48 ++++++ .../src/test/java/tests/ComplexTest.java | 75 ++++++++++ .../java/tests/JUnitAnnotationsTests.java | 113 ++++++++++++++ .../src/test/java/tests/OrderTests.java | 35 +++++ .../src/test/java/tests/VintageTests.java | 45 ++++++ .../src/test/java/tests/common/Fruits.java | 19 +++ .../test/java/tests/common/TestInterface.java | 10 ++ .../src/test/resources/resource.txt | 1 + 14 files changed, 707 insertions(+), 1 deletion(-) create mode 100644 native-gradle-plugin/src/functionalTest/groovy/org/graalvm/buildtools/gradle/JUnitFunctionalTests.groovy create mode 100644 native-maven-plugin/src/functionalTest/groovy/org/graalvm/buildtools/maven/JUnitFunctionalTests.groovy create mode 100644 samples/junit-tests/build.gradle create mode 100644 samples/junit-tests/gradle.properties create mode 100644 samples/junit-tests/pom.xml create mode 100644 samples/junit-tests/settings.gradle create mode 100644 samples/junit-tests/src/test/java/tests/ComplexTest.java create mode 100644 samples/junit-tests/src/test/java/tests/JUnitAnnotationsTests.java create mode 100644 samples/junit-tests/src/test/java/tests/OrderTests.java create mode 100644 samples/junit-tests/src/test/java/tests/VintageTests.java create mode 100644 samples/junit-tests/src/test/java/tests/common/Fruits.java create mode 100644 samples/junit-tests/src/test/java/tests/common/TestInterface.java create mode 100644 samples/junit-tests/src/test/resources/resource.txt diff --git a/common/junit-platform-native/src/main/java/org/graalvm/junit/platform/JUnitPlatformFeature.java b/common/junit-platform-native/src/main/java/org/graalvm/junit/platform/JUnitPlatformFeature.java index 1fdac7d96..283fd2c78 100644 --- a/common/junit-platform-native/src/main/java/org/graalvm/junit/platform/JUnitPlatformFeature.java +++ b/common/junit-platform-native/src/main/java/org/graalvm/junit/platform/JUnitPlatformFeature.java @@ -64,7 +64,6 @@ import java.nio.file.Files; import java.nio.file.Path; import java.nio.file.Paths; -import java.util.HashSet; import java.util.List; import java.util.Optional; import java.util.ServiceLoader; @@ -101,6 +100,7 @@ public void beforeAnalysis(BeforeAnalysisAccess access) { that are also used at build-time but not added to the image heap */ if (Runtime.version().feature() <= 21) { RuntimeClassInitialization.initializeAtBuildTime("org.junit"); + RuntimeClassInitialization.initializeAtBuildTime("java"); } List selectors = getSelectors(); diff --git a/native-gradle-plugin/src/functionalTest/groovy/org/graalvm/buildtools/gradle/JUnitFunctionalTests.groovy b/native-gradle-plugin/src/functionalTest/groovy/org/graalvm/buildtools/gradle/JUnitFunctionalTests.groovy new file mode 100644 index 000000000..0ba536a3a --- /dev/null +++ b/native-gradle-plugin/src/functionalTest/groovy/org/graalvm/buildtools/gradle/JUnitFunctionalTests.groovy @@ -0,0 +1,84 @@ +/* + * Copyright (c) 2025, Oracle and/or its affiliates. All rights reserved. + * DO NOT ALTER OR REMOVE COPYRIGHT NOTICES OR THIS FILE HEADER. + * + * The Universal Permissive License (UPL), Version 1.0 + * + * Subject to the condition set forth below, permission is hereby granted to any + * person obtaining a copy of this software, associated documentation and/or + * data (collectively the "Software"), free of charge and under any and all + * copyright rights in the Software, and any and all patent rights owned or + * freely licensable by each licensor hereunder covering either (i) the + * unmodified Software as contributed to or provided by such licensor, or (ii) + * the Larger Works (as defined below), to deal in both + * + * (a) the Software, and + * + * (b) any piece of software and/or hardware listed in the lrgrwrks.txt file if + * one is included with the Software each a "Larger Work" to which the Software + * is contributed by such licensors), + * + * without restriction, including without limitation the rights to copy, create + * derivative works of, display, perform, and distribute the Software and make, + * use, sell, offer for sale, import, export, have made, and have sold the + * Software and the Larger Work(s), and to sublicense the foregoing rights on + * either these or other terms. + * + * This license is subject to the following condition: + * + * The above copyright notice and either this complete permission notice or at a + * minimum a reference to the UPL must be included in all copies or substantial + * portions of the Software. + * + * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR + * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, + * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE + * AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER + * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, + * OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE + * SOFTWARE. + */ + +package org.graalvm.buildtools.gradle + +import org.graalvm.buildtools.gradle.fixtures.AbstractFunctionalTest +import spock.lang.Ignore +import spock.lang.IgnoreIf +import spock.lang.Issue +import spock.lang.Requires +import spock.lang.Unroll + +import java.nio.file.Files + +class JUnitFunctionalTests extends AbstractFunctionalTest { + @Unroll("test if JUint support works with various annotations, reflection and resources") + def "run junit tests"() { + + given: + withSample("junit-tests") + + when: + run 'nativeTest' + + then: + tasks { + succeeded ':testClasses', ':nativeTestCompile', ':nativeTest' + } + outputContains "Running in 'test listener' mode using files matching pattern [junit-platform-unique-ids*] found in folder [" + outputContains """ +[ 10 containers found ] +[ 0 containers skipped ] +[ 10 containers started ] +[ 0 containers aborted ] +[ 10 containers successful ] +[ 0 containers failed ] +[ 24 tests found ] +[ 1 tests skipped ] +[ 23 tests started ] +[ 0 tests aborted ] +[ 23 tests successful ] +[ 0 tests failed ] +""".trim() + + } +} diff --git a/native-maven-plugin/src/functionalTest/groovy/org/graalvm/buildtools/maven/JUnitFunctionalTests.groovy b/native-maven-plugin/src/functionalTest/groovy/org/graalvm/buildtools/maven/JUnitFunctionalTests.groovy new file mode 100644 index 000000000..2b84daaec --- /dev/null +++ b/native-maven-plugin/src/functionalTest/groovy/org/graalvm/buildtools/maven/JUnitFunctionalTests.groovy @@ -0,0 +1,71 @@ +/* + * Copyright (c) 2025, 2025, Oracle and/or its affiliates. All rights reserved. + * DO NOT ALTER OR REMOVE COPYRIGHT NOTICES OR THIS FILE HEADER. + * + * The Universal Permissive License (UPL), Version 1.0 + * + * Subject to the condition set forth below, permission is hereby granted to any + * person obtaining a copy of this software, associated documentation and/or + * data (collectively the "Software"), free of charge and under any and all + * copyright rights in the Software, and any and all patent rights owned or + * freely licensable by each licensor hereunder covering either (i) the + * unmodified Software as contributed to or provided by such licensor, or (ii) + * the Larger Works (as defined below), to deal in both + * + * (a) the Software, and + * + * (b) any piece of software and/or hardware listed in the lrgrwrks.txt file if + * one is included with the Software each a "Larger Work" to which the Software + * is contributed by such licensors), + * + * without restriction, including without limitation the rights to copy, create + * derivative works of, display, perform, and distribute the Software and make, + * use, sell, offer for sale, import, export, have made, and have sold the + * Software and the Larger Work(s), and to sublicense the foregoing rights on + * either these or other terms. + * + * This license is subject to the following condition: + * + * The above copyright notice and either this complete permission notice or at a + * minimum a reference to the UPL must be included in all copies or substantial + * portions of the Software. + * + * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR + * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, + * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE + * AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER + * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, + * OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE + * SOFTWARE. + */ + +package org.graalvm.buildtools.maven + +class JUnitFunctionalTests extends AbstractGraalVMMavenFunctionalTest { + + def "run junit tests"() { + withSample("junit-tests") + + when: + mvn '-DquickBuild', '-Pnative', 'test' + + then: + buildSucceeded + outputContains "[junit-platform-native] Running in 'test listener' mode" + outputContains """ +[ 10 containers found ] +[ 0 containers skipped ] +[ 10 containers started ] +[ 0 containers aborted ] +[ 10 containers successful ] +[ 0 containers failed ] +[ 24 tests found ] +[ 1 tests skipped ] +[ 23 tests started ] +[ 0 tests aborted ] +[ 23 tests successful ] +[ 0 tests failed ] +""".trim() + } + +} diff --git a/samples/junit-tests/build.gradle b/samples/junit-tests/build.gradle new file mode 100644 index 000000000..0e5021ca1 --- /dev/null +++ b/samples/junit-tests/build.gradle @@ -0,0 +1,65 @@ +/* + * Copyright (c) 2025, 2025, Oracle and/or its affiliates. All rights reserved. + * DO NOT ALTER OR REMOVE COPYRIGHT NOTICES OR THIS FILE HEADER. + * + * The Universal Permissive License (UPL), Version 1.0 + * + * Subject to the condition set forth below, permission is hereby granted to any + * person obtaining a copy of this software, associated documentation and/or + * data (collectively the "Software"), free of charge and under any and all + * copyright rights in the Software, and any and all patent rights owned or + * freely licensable by each licensor hereunder covering either (i) the + * unmodified Software as contributed to or provided by such licensor, or (ii) + * the Larger Works (as defined below), to deal in both + * + * (a) the Software, and + * + * (b) any piece of software and/or hardware listed in the lrgrwrks.txt file if + * one is included with the Software each a "Larger Work" to which the Software + * is contributed by such licensors), + * + * without restriction, including without limitation the rights to copy, create + * derivative works of, display, perform, and distribute the Software and make, + * use, sell, offer for sale, import, export, have made, and have sold the + * Software and the Larger Work(s), and to sublicense the foregoing rights on + * either these or other terms. + * + * This license is subject to the following condition: + * + * The above copyright notice and either this complete permission notice or at a + * minimum a reference to the UPL must be included in all copies or substantial + * portions of the Software. + * + * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR + * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, + * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE + * AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER + * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, + * OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE + * SOFTWARE. + */ + +plugins { + id 'application' + id 'org.graalvm.buildtools.native' +} + +repositories { + mavenCentral() +} + +dependencies { + testImplementation 'org.junit.jupiter:junit-jupiter:5.11.0' + testImplementation "org.junit.vintage:junit-vintage-engine:5.11.0" +} + +application { + // Define the main class for the application. + mainClass = 'tests.App' +} + +tasks.named('test') { + // Use JUnit Platform for unit tests. + useJUnitPlatform() +} + diff --git a/samples/junit-tests/gradle.properties b/samples/junit-tests/gradle.properties new file mode 100644 index 000000000..1df94c8aa --- /dev/null +++ b/samples/junit-tests/gradle.properties @@ -0,0 +1 @@ +native.gradle.plugin.version = 0.10.6-SNAPSHOT diff --git a/samples/junit-tests/pom.xml b/samples/junit-tests/pom.xml new file mode 100644 index 000000000..b7c04d141 --- /dev/null +++ b/samples/junit-tests/pom.xml @@ -0,0 +1,139 @@ + + + + + 4.0.0 + + org.graalvm.buildtools.examples + maven + 1.0.0-SNAPSHOT + + + 1.8 + UTF-8 + 5.11.0 + 0.10.6-SNAPSHOT + 0.10.6-SNAPSHOT + example-app + + + + + org.junit.jupiter + junit-jupiter + ${junit.jupiter.version} + test + + + org.junit.vintage + junit-vintage-engine + ${junit.jupiter.version} + test + + + + + + native + + + + org.apache.maven.plugins + maven-surefire-plugin + 3.0.0-M5 + + + + org.graalvm.buildtools + native-maven-plugin + ${native.maven.plugin.version} + true + + + + test-native + + test + + test + + + build-native + + compile-no-fork + + package + + + + false + ${imageName} + false + + + + + + + + + + + org.apache.maven.plugins + maven-surefire-plugin + 3.0.0-M5 + + + + org.apache.maven.plugins + maven-compiler-plugin + 3.8.1 + + ${java.version} + 1.8 + + + + + + diff --git a/samples/junit-tests/settings.gradle b/samples/junit-tests/settings.gradle new file mode 100644 index 000000000..11735ca92 --- /dev/null +++ b/samples/junit-tests/settings.gradle @@ -0,0 +1,48 @@ +/* + * Copyright (c) 2025, 2025, Oracle and/or its affiliates. All rights reserved. + * DO NOT ALTER OR REMOVE COPYRIGHT NOTICES OR THIS FILE HEADER. + * + * The Universal Permissive License (UPL), Version 1.0 + * + * Subject to the condition set forth below, permission is hereby granted to any + * person obtaining a copy of this software, associated documentation and/or + * data (collectively the "Software"), free of charge and under any and all + * copyright rights in the Software, and any and all patent rights owned or + * freely licensable by each licensor hereunder covering either (i) the + * unmodified Software as contributed to or provided by such licensor, or (ii) + * the Larger Works (as defined below), to deal in both + * + * (a) the Software, and + * + * (b) any piece of software and/or hardware listed in the lrgrwrks.txt file if + * one is included with the Software each a "Larger Work" to which the Software + * is contributed by such licensors), + * + * without restriction, including without limitation the rights to copy, create + * derivative works of, display, perform, and distribute the Software and make, + * use, sell, offer for sale, import, export, have made, and have sold the + * Software and the Larger Work(s), and to sublicense the foregoing rights on + * either these or other terms. + * + * This license is subject to the following condition: + * + * The above copyright notice and either this complete permission notice or at a + * minimum a reference to the UPL must be included in all copies or substantial + * portions of the Software. + * + * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR + * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, + * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE + * AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER + * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, + * OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE + * SOFTWARE. + */ + +pluginManagement { + plugins { + id 'org.graalvm.buildtools.native' version getProperty('native.gradle.plugin.version') + } +} + +rootProject.name = 'junit-tests' diff --git a/samples/junit-tests/src/test/java/tests/ComplexTest.java b/samples/junit-tests/src/test/java/tests/ComplexTest.java new file mode 100644 index 000000000..7b6c7db78 --- /dev/null +++ b/samples/junit-tests/src/test/java/tests/ComplexTest.java @@ -0,0 +1,75 @@ +package tests; + +import org.junit.jupiter.api.Test; +import tests.common.Fruits; + +import java.io.BufferedReader; +import java.io.IOException; +import java.io.InputStream; +import java.io.InputStreamReader; +import java.lang.reflect.Field; +import java.lang.reflect.InvocationTargetException; +import java.lang.reflect.Method; +import java.util.List; +import java.util.stream.Collectors; + +import static org.junit.jupiter.api.Assertions.*; + + +public class ComplexTest { + + private static final String RESOURCE = "/resource.txt"; + + @Test + public void callMethodFromOtherClass() { + String fruit = Fruits.getSomeFruit(); + assertNotNull(fruit); + assertTrue(fruit.contains("berry")); + } + + @Test + public void accessMethodReflectively() { + try { + String methodName = "get" + "Blackberry"; + Method method = Fruits.class.getDeclaredMethod(methodName, (Class[]) null); + method.setAccessible(true); + + Fruits f = new Fruits(); + String retval = (String) method.invoke(f); + assertTrue(retval.equalsIgnoreCase("blackberry")); + } catch (NoSuchMethodException | IllegalAccessException | InvocationTargetException e) { + throw new RuntimeException(e); + } + } + + + @Test + public void accessFiledReflectively() { + try { + Field fruitsField = Fruits.class.getDeclaredField("fruits"); + fruitsField.setAccessible(true); + + Fruits f = new Fruits(); + List fruits = (List) fruitsField.get(f); + + assertEquals(3, fruits.size()); + assertEquals(3, fruits.stream().filter(fruit -> fruit.contains("berry")).collect(Collectors.toList()).size()); + } catch (NoSuchFieldException | IllegalAccessException e) { + throw new RuntimeException(e); + } + } + + @Test + public void resourceTest() { + try(InputStream is = ComplexTest.class.getResourceAsStream(RESOURCE)) { + assertNotNull(is); + InputStreamReader isr = new InputStreamReader(is); + BufferedReader br = new BufferedReader(isr); + + assertTrue(br.readLine().equalsIgnoreCase("Hello from resource!")); + } catch (IOException e) { + throw new RuntimeException(e); + } + } + +} diff --git a/samples/junit-tests/src/test/java/tests/JUnitAnnotationsTests.java b/samples/junit-tests/src/test/java/tests/JUnitAnnotationsTests.java new file mode 100644 index 000000000..d1ee42731 --- /dev/null +++ b/samples/junit-tests/src/test/java/tests/JUnitAnnotationsTests.java @@ -0,0 +1,113 @@ +/* + * This Java source file was generated by the Gradle 'init' task. + */ +package tests; + +import org.junit.jupiter.api.*; +import org.junit.jupiter.params.ParameterizedTest; +import org.junit.jupiter.params.provider.MethodSource; +import org.junit.jupiter.params.provider.FieldSource; +import tests.common.TestInterface; + + +import java.util.Arrays; +import java.util.List; + +import static org.junit.jupiter.api.Assertions.*; + + +public class JUnitAnnotationsTests implements TestInterface { + + /* test Disabled tests */ + @Test + @Disabled + void skipThisTest() { + throw new RuntimeException("This test should not be executed!"); + } + + /* test MethodSource with Interface */ + @ParameterizedTest + @MethodSource("names") + void test(String name) { + assertEquals(5, name.length()); + assertTrue(name.startsWith("S")); + } + + /* test FieldSource with other class */ + @ParameterizedTest + @FieldSource("tests.common.Fruits#fruits") + void testWithExternalFieldSource(String fruit) { + assertTrue(fruit.contains("berry")); + } + + + /* test FieldSource with field from this class */ + static final List listOfFruits = Arrays.asList("apple", "ananas"); + + @ParameterizedTest + @FieldSource("listOfFruits") + void singleFieldSource(String fruit) { + assertTrue(fruit.startsWith("a")); + } + + + /* test RepeatedTest */ + private static int numberOfRepetitions = -1; + + @BeforeAll + static void initializeNumberOfRepetitions() { + /* if we don't execute this first, repeated tests will fail */ + numberOfRepetitions = 1; + } + + @AfterAll + static void checkIfThisComesLast() { + /* if this comes last, 3 repeated tests should have increased this value to 4 */ + assertEquals(4, numberOfRepetitions); + } + + @RepeatedTest(3) + void repeatedTest(RepetitionInfo repetitionInfo) { + assertEquals(repetitionInfo.getCurrentRepetition(), numberOfRepetitions); + numberOfRepetitions++; + } + + /* test BeforeEach and AfterEach annotations */ + private static int beforeEachTestValue = -1; + private static int afterEachTestValue = -1; + + @BeforeEach + void setBeforeEach() { + beforeEachTestValue = 0; + } + + @AfterEach + void setAfterEach() { + afterEachTestValue = -1; + } + + @Test + void beforeAndAfterEachTest1() { + assertEquals(0, beforeEachTestValue); + assertEquals(-1, afterEachTestValue); + beforeEachTestValue = (int) (Math.random() * 10 + 1); + afterEachTestValue = (int) (Math.random() * 10 + 1); + } + + @Test + void beforeAndAfterEachTest2() { + assertEquals(0, beforeEachTestValue); + assertEquals(-1, afterEachTestValue); + beforeEachTestValue = (int) (Math.random() * 10 + 1); + afterEachTestValue = (int) (Math.random() * 10 + 1); + } + + @Test + void beforeAndAfterEachTest3() { + assertEquals(0, beforeEachTestValue); + assertEquals(-1, afterEachTestValue); + beforeEachTestValue = (int) (Math.random() * 10 + 1); + afterEachTestValue = (int) (Math.random() * 10 + 1); + } + +} diff --git a/samples/junit-tests/src/test/java/tests/OrderTests.java b/samples/junit-tests/src/test/java/tests/OrderTests.java new file mode 100644 index 000000000..d09936ced --- /dev/null +++ b/samples/junit-tests/src/test/java/tests/OrderTests.java @@ -0,0 +1,35 @@ +package tests; + +import org.junit.jupiter.api.MethodOrderer; +import org.junit.jupiter.api.Order; +import org.junit.jupiter.api.Test; +import org.junit.jupiter.api.TestMethodOrder; + +import static org.junit.jupiter.api.Assertions.assertNull; +import static org.junit.jupiter.api.Assertions.assertTrue; + +@TestMethodOrder(MethodOrderer.OrderAnnotation.class) +public class OrderTests { + /* tests execution order should be dictated by the @Order annotation */ + protected static String testOrderChecker = "First test"; + + @Test + @Order(2) + void secondTest() { + assertTrue(testOrderChecker.equalsIgnoreCase("Second test")); + testOrderChecker = null; + } + + @Test + @Order(3) + void thirdTest() { + assertNull(testOrderChecker); + } + + @Test + @Order(1) + void firstTest() { + assertTrue(testOrderChecker.equalsIgnoreCase("First test")); + testOrderChecker = "Second test"; + } +} diff --git a/samples/junit-tests/src/test/java/tests/VintageTests.java b/samples/junit-tests/src/test/java/tests/VintageTests.java new file mode 100644 index 000000000..01c411401 --- /dev/null +++ b/samples/junit-tests/src/test/java/tests/VintageTests.java @@ -0,0 +1,45 @@ +package tests; + +import org.hamcrest.MatcherAssert; +import org.hamcrest.core.Every; +import org.junit.Rule; +import org.junit.Test; +import org.junit.rules.ExpectedException; + +import java.util.Arrays; +import java.util.List; + +import static org.hamcrest.CoreMatchers.instanceOf; +import static org.hamcrest.CoreMatchers.is; + +public class VintageTests { + + /* passes but with some exceptions ("as warning") */ + + @Test + public void testEvery() { + List numbers = Arrays.asList(1, 1, 1, 1); + MatcherAssert.assertThat(numbers, Every.everyItem(is(1))); + } + + @SuppressWarnings("deprecation") + @Rule + public ExpectedException expectedException = ExpectedException.none(); + + @Test + public void testExpectedException() { + expectedException.expect(ArithmeticException.class); + throw new ArithmeticException(); + } + + @Test + public void testExpectedExceptionCause() { + expectedException.expectCause(instanceOf(ArithmeticException.class)); + try { + throw new ArithmeticException(); + } catch (ArithmeticException e) { + throw new RuntimeException(e); + } + } + +} diff --git a/samples/junit-tests/src/test/java/tests/common/Fruits.java b/samples/junit-tests/src/test/java/tests/common/Fruits.java new file mode 100644 index 000000000..c427250fc --- /dev/null +++ b/samples/junit-tests/src/test/java/tests/common/Fruits.java @@ -0,0 +1,19 @@ +package tests.common; + +import java.util.Arrays; +import java.util.List; + +public class Fruits { + + private static final List fruits = Arrays.asList("blackberry", "raspberry", "strawberry"); + + public static String getSomeFruit() { + int index = Math.random() > 0.5 ? 1 : 0; + return fruits.get(index); + } + + private String getBlackberry() { + return fruits.get(0); + } + +} diff --git a/samples/junit-tests/src/test/java/tests/common/TestInterface.java b/samples/junit-tests/src/test/java/tests/common/TestInterface.java new file mode 100644 index 000000000..ab8e1bc2d --- /dev/null +++ b/samples/junit-tests/src/test/java/tests/common/TestInterface.java @@ -0,0 +1,10 @@ +package tests.common; + +import java.util.List; + +public interface TestInterface { + + static List names() { + return List.of("Sarah", "Susan"); + } +} diff --git a/samples/junit-tests/src/test/resources/resource.txt b/samples/junit-tests/src/test/resources/resource.txt new file mode 100644 index 000000000..a07388701 --- /dev/null +++ b/samples/junit-tests/src/test/resources/resource.txt @@ -0,0 +1 @@ +Hello from resource! From f9755ae7bbaee694f66e3e9ae66100b220842952 Mon Sep 17 00:00:00 2001 From: David Nestorovic Date: Thu, 20 Feb 2025 13:23:29 +0100 Subject: [PATCH 04/28] Add default test-ids locations --- .../NativeImageConfigurationImpl.java | 1 - .../platform/NativeImageJUnitLauncher.java | 57 +++++++++++++++++-- .../buildtools/gradle/NativeImagePlugin.java | 1 - 3 files changed, 53 insertions(+), 6 deletions(-) diff --git a/common/junit-platform-native/src/main/java/org/graalvm/junit/platform/NativeImageConfigurationImpl.java b/common/junit-platform-native/src/main/java/org/graalvm/junit/platform/NativeImageConfigurationImpl.java index a2d1a91eb..6ffb424e0 100644 --- a/common/junit-platform-native/src/main/java/org/graalvm/junit/platform/NativeImageConfigurationImpl.java +++ b/common/junit-platform-native/src/main/java/org/graalvm/junit/platform/NativeImageConfigurationImpl.java @@ -42,7 +42,6 @@ package org.graalvm.junit.platform; import org.graalvm.junit.platform.config.core.NativeImageConfiguration; -import org.graalvm.nativeimage.hosted.RuntimeClassInitialization; import org.graalvm.nativeimage.hosted.RuntimeReflection; import java.lang.reflect.Executable; diff --git a/common/junit-platform-native/src/main/java/org/graalvm/junit/platform/NativeImageJUnitLauncher.java b/common/junit-platform-native/src/main/java/org/graalvm/junit/platform/NativeImageJUnitLauncher.java index f52288c44..03303cca0 100644 --- a/common/junit-platform-native/src/main/java/org/graalvm/junit/platform/NativeImageJUnitLauncher.java +++ b/common/junit-platform-native/src/main/java/org/graalvm/junit/platform/NativeImageJUnitLauncher.java @@ -45,7 +45,9 @@ import org.junit.platform.engine.DiscoverySelector; import org.junit.platform.engine.discovery.DiscoverySelectors; import org.junit.platform.engine.discovery.UniqueIdSelector; -import org.junit.platform.launcher.*; +import org.junit.platform.launcher.Launcher; +import org.junit.platform.launcher.LauncherDiscoveryRequest; +import org.junit.platform.launcher.TestPlan; import org.junit.platform.launcher.core.LauncherDiscoveryRequestBuilder; import org.junit.platform.launcher.core.LauncherFactory; import org.junit.platform.launcher.listeners.SummaryGeneratingListener; @@ -59,7 +61,9 @@ import java.nio.file.Files; import java.nio.file.Path; import java.nio.file.Paths; -import java.util.*; +import java.util.Arrays; +import java.util.LinkedList; +import java.util.List; import java.util.stream.Collectors; import java.util.stream.Stream; @@ -116,7 +120,9 @@ public static void main(String... args) { } if (testIds == null) { - throw new RuntimeException("Test ids not provided to the launcher class."); + System.out.println("[junit-platform-native] WARNING: test-ids not provided to the NativeImageJUnitLauncher. " + + "This should only happen if you are running tests binary manually (instead of using 'gradle nativeTest' command)"); + testIds = getTestIDsFromDefaultLocations(); } Launcher launcher = LauncherFactory.create(); @@ -157,7 +163,6 @@ private static TestPlan getTestPlan(Launcher launcher, String testIDs) { private static List getSelectors(String testIDs) { try { Path outputDir = Paths.get(testIDs); - System.out.println("outputDir = " + outputDir); String prefix = System.getProperty(UniqueIdTrackingListener.OUTPUT_FILE_PREFIX_PROPERTY_NAME, UniqueIdTrackingListener.DEFAULT_OUTPUT_FILE_PREFIX); List selectors = readAllFiles(outputDir, prefix) @@ -195,4 +200,48 @@ private static Stream findFiles(Path dir, String prefix) throws IOExceptio (path, basicFileAttributes) -> (basicFileAttributes.isRegularFile() && path.getFileName().toString().startsWith(prefix))); } + + private static String getTestIDsFromDefaultLocations() { + System.out.println("[junit-platform-native] WARNING: Trying to find test-ids on default locations."); + Path defaultGradleTestIDsLocation = getGradleTestIdsDefaultLocation(); + Path defaultMavenTestIDsLocation = getMavenTestIDsDefaultLocation(); + + if (Files.exists(defaultGradleTestIDsLocation) && Files.exists(defaultMavenTestIDsLocation)) { + throw new RuntimeException("[junit-platform-native] test-ids found in both " + defaultGradleTestIDsLocation + " and " + defaultMavenTestIDsLocation + + ". Please specify the test-ids location by passing the '--test-ids ' argument to your tests executable."); + } + + if (Files.exists(defaultGradleTestIDsLocation)) { + System.out.println("[junit-platform-native] WARNING: Using test-ids from default Gradle project location:" + defaultGradleTestIDsLocation); + return defaultGradleTestIDsLocation.toString(); + } + + if (Files.exists(defaultMavenTestIDsLocation)) { + System.out.println("[junit-platform-native] WARNING: Using test-ids from default Maven project location:" + defaultMavenTestIDsLocation); + return defaultMavenTestIDsLocation.toString(); + } + + throw new RuntimeException("[junit-platform-native] test-ids not provided to the NativeImageJUnitLauncher and cannot be found on default locations. " + + "Searched in: " + defaultGradleTestIDsLocation + " and " + defaultMavenTestIDsLocation); + } + + private static Path getGradleTestIdsDefaultLocation() { + return Path.of(getBuildDirectory("/build/")) + .resolve("test-results") + .resolve("test") + .resolve("testlist") + .toAbsolutePath(); + } + + private static Path getMavenTestIDsDefaultLocation() { + return Path.of(getBuildDirectory("/target/")) + .resolve("test-ids") + .toAbsolutePath(); + } + + private static String getBuildDirectory(String buildDir) { + String executableLocation = Path.of(".").toAbsolutePath().toString(); + return executableLocation.substring(0, executableLocation.indexOf(buildDir) + buildDir.length()); + } + } diff --git a/native-gradle-plugin/src/main/java/org/graalvm/buildtools/gradle/NativeImagePlugin.java b/native-gradle-plugin/src/main/java/org/graalvm/buildtools/gradle/NativeImagePlugin.java index b066fabcf..21e3f2c15 100644 --- a/native-gradle-plugin/src/main/java/org/graalvm/buildtools/gradle/NativeImagePlugin.java +++ b/native-gradle-plugin/src/main/java/org/graalvm/buildtools/gradle/NativeImagePlugin.java @@ -65,7 +65,6 @@ import org.graalvm.buildtools.gradle.tasks.actions.CleanupAgentFilesAction; import org.graalvm.buildtools.gradle.tasks.actions.MergeAgentFilesAction; import org.graalvm.buildtools.utils.SharedConstants; -import org.graalvm.nativeimage.ImageSingletons; import org.graalvm.reachability.DirectoryConfiguration; import org.gradle.api.Action; import org.gradle.api.NamedDomainObjectContainer; From 66acdca8653a6f88028c1aba2035d6dc15c06cf4 Mon Sep 17 00:00:00 2001 From: David Nestorovic Date: Thu, 20 Feb 2025 16:03:09 +0100 Subject: [PATCH 05/28] Fix MethodSourceTests for JUnit 5.11.0 --- .../java/org/graalvm/junit/jupiter/MethodSourceTests.java | 8 ++++++++ .../java/org/graalvm/buildtools/maven/NativeTestMojo.java | 2 +- 2 files changed, 9 insertions(+), 1 deletion(-) diff --git a/common/junit-platform-native/src/test/java/org/graalvm/junit/jupiter/MethodSourceTests.java b/common/junit-platform-native/src/test/java/org/graalvm/junit/jupiter/MethodSourceTests.java index 7083a3d01..0970ed69a 100644 --- a/common/junit-platform-native/src/test/java/org/graalvm/junit/jupiter/MethodSourceTests.java +++ b/common/junit-platform-native/src/test/java/org/graalvm/junit/jupiter/MethodSourceTests.java @@ -78,12 +78,17 @@ protected static void addExpectedArgs(int a, int b) { protected static void traceArgs(int a, int b) { actualArgs.add(new int[]{a, b}); } + + protected static void clearPreviousExpectedArgs() { + expectedArgs.clear(); + } } public static class EmptyMethodSourceTests extends ArgumentTestBase { @BeforeAll public static void setup() { + clearPreviousExpectedArgs(); addExpectedArgs(1, 5); addExpectedArgs(7, 12); } @@ -106,6 +111,7 @@ public static class SameClassMethodSourceTests extends ArgumentTestBase { @BeforeAll public static void setup() { + clearPreviousExpectedArgs(); addExpectedArgs(31, 32); addExpectedArgs(1, 3); } @@ -128,6 +134,7 @@ public static class OtherClassMethodSourceTests extends ArgumentTestBase { @BeforeAll public static void setup() { + clearPreviousExpectedArgs(); addExpectedArgs(33, 35); addExpectedArgs(99, 1); } @@ -143,6 +150,7 @@ public static class CombinedMethodSourceTests extends ArgumentTestBase { @BeforeAll public static void setup() { + clearPreviousExpectedArgs(); addExpectedArgs(33, 35); addExpectedArgs(99, 1); addExpectedArgs(31, 32); diff --git a/native-maven-plugin/src/main/java/org/graalvm/buildtools/maven/NativeTestMojo.java b/native-maven-plugin/src/main/java/org/graalvm/buildtools/maven/NativeTestMojo.java index f5c3926e9..ce06d73a8 100644 --- a/native-maven-plugin/src/main/java/org/graalvm/buildtools/maven/NativeTestMojo.java +++ b/native-maven-plugin/src/main/java/org/graalvm/buildtools/maven/NativeTestMojo.java @@ -219,7 +219,7 @@ private void runNativeTests(Path executable) throws MojoExecutionException { throw new MojoExecutionException("Failed creating xml output directory"); } - Path testIdsLocation = outputDirectory.toPath().resolve("test-ids"); + Path testIdsLocation = Path.of(NativeExtension.testIdsDirectory(outputDirectory.getAbsolutePath())); if (!testIdsLocation.toFile().exists()) { throw new MojoExecutionException("Test-ids not available under target/test-ids"); } From bd36fc6f788d2c6d8fe5fc93083a3a0b8cb60512 Mon Sep 17 00:00:00 2001 From: David Nestorovic Date: Thu, 20 Feb 2025 18:53:54 +0100 Subject: [PATCH 06/28] Use system property to calculate test-ids location --- .../gradle/wrapper/gradle-wrapper.properties | 2 +- .../gradle/native-image-testing.gradle | 2 + .../gradle/wrapper/gradle-wrapper.properties | 2 +- .../junit/platform/JUnitPlatformFeature.java | 3 +- .../platform/NativeImageJUnitLauncher.java | 54 +++++++++---------- .../gradle/wrapper/gradle-wrapper.properties | 2 +- .../buildtools/gradle/NativeImagePlugin.java | 5 +- .../gradle/wrapper/gradle-wrapper.properties | 2 +- .../buildtools/maven/NativeTestMojo.java | 7 --- 9 files changed, 35 insertions(+), 44 deletions(-) diff --git a/common/graalvm-reachability-metadata/gradle/wrapper/gradle-wrapper.properties b/common/graalvm-reachability-metadata/gradle/wrapper/gradle-wrapper.properties index 41dfb8790..db9a6b825 100644 --- a/common/graalvm-reachability-metadata/gradle/wrapper/gradle-wrapper.properties +++ b/common/graalvm-reachability-metadata/gradle/wrapper/gradle-wrapper.properties @@ -1,5 +1,5 @@ distributionBase=GRADLE_USER_HOME distributionPath=wrapper/dists -distributionUrl=https\://services.gradle.org/distributions/gradle-7.4-bin.zip +distributionUrl=https\://services.gradle.org/distributions/gradle-8.3-bin.zip zipStoreBase=GRADLE_USER_HOME zipStorePath=wrapper/dists diff --git a/common/junit-platform-native/gradle/native-image-testing.gradle b/common/junit-platform-native/gradle/native-image-testing.gradle index 912f88b7c..cef08233e 100644 --- a/common/junit-platform-native/gradle/native-image-testing.gradle +++ b/common/junit-platform-native/gradle/native-image-testing.gradle @@ -38,6 +38,7 @@ * OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE * SOFTWARE. */ + def agentOutput = layout.buildDirectory.dir("agent") ext { @@ -143,4 +144,5 @@ tasks.register("nativeTest", Exec) { dependsOn nativeTestCompile workingDir = "${buildDir}" executable = "${buildDir}/native-image-tests" + args = ["-Djunit.platform.listeners.uid.tracking.output.dir=${testIdsDir.get().asFile.absolutePath}"] } diff --git a/common/junit-platform-native/gradle/wrapper/gradle-wrapper.properties b/common/junit-platform-native/gradle/wrapper/gradle-wrapper.properties index 41dfb8790..db9a6b825 100644 --- a/common/junit-platform-native/gradle/wrapper/gradle-wrapper.properties +++ b/common/junit-platform-native/gradle/wrapper/gradle-wrapper.properties @@ -1,5 +1,5 @@ distributionBase=GRADLE_USER_HOME distributionPath=wrapper/dists -distributionUrl=https\://services.gradle.org/distributions/gradle-7.4-bin.zip +distributionUrl=https\://services.gradle.org/distributions/gradle-8.3-bin.zip zipStoreBase=GRADLE_USER_HOME zipStorePath=wrapper/dists diff --git a/common/junit-platform-native/src/main/java/org/graalvm/junit/platform/JUnitPlatformFeature.java b/common/junit-platform-native/src/main/java/org/graalvm/junit/platform/JUnitPlatformFeature.java index 283fd2c78..3644e07dc 100644 --- a/common/junit-platform-native/src/main/java/org/graalvm/junit/platform/JUnitPlatformFeature.java +++ b/common/junit-platform-native/src/main/java/org/graalvm/junit/platform/JUnitPlatformFeature.java @@ -63,7 +63,6 @@ import java.io.UncheckedIOException; import java.nio.file.Files; import java.nio.file.Path; -import java.nio.file.Paths; import java.util.List; import java.util.Optional; import java.util.ServiceLoader; @@ -112,7 +111,7 @@ public void beforeAnalysis(BeforeAnalysisAccess access) { private List getSelectors() { try { - Path uniqueIdDirectory = Paths.get(System.getProperty(UniqueIdTrackingListener.OUTPUT_DIR_PROPERTY_NAME)); + Path uniqueIdDirectory = Path.of(System.getProperty(UniqueIdTrackingListener.OUTPUT_DIR_PROPERTY_NAME)); String uniqueIdFilePrefix = System.getProperty(UniqueIdTrackingListener.OUTPUT_FILE_PREFIX_PROPERTY_NAME, UniqueIdTrackingListener.DEFAULT_OUTPUT_FILE_PREFIX); diff --git a/common/junit-platform-native/src/main/java/org/graalvm/junit/platform/NativeImageJUnitLauncher.java b/common/junit-platform-native/src/main/java/org/graalvm/junit/platform/NativeImageJUnitLauncher.java index 03303cca0..e3a2bc8c1 100644 --- a/common/junit-platform-native/src/main/java/org/graalvm/junit/platform/NativeImageJUnitLauncher.java +++ b/common/junit-platform-native/src/main/java/org/graalvm/junit/platform/NativeImageJUnitLauncher.java @@ -55,6 +55,7 @@ import org.junit.platform.launcher.listeners.UniqueIdTrackingListener; import org.junit.platform.reporting.legacy.xml.LegacyXmlReportGeneratingListener; +import java.io.File; import java.io.IOException; import java.io.PrintWriter; import java.io.UncheckedIOException; @@ -82,7 +83,6 @@ public static void main(String... args) { /* scan runtime arguments */ String xmlOutput = DEFAULT_OUTPUT_FOLDER; - String testIds = null; boolean silent = false; LinkedList arguments = new LinkedList<>(Arrays.asList(args)); @@ -94,7 +94,6 @@ public static void main(String... args) { System.out.println("----------------------------------------\n"); System.out.println("Flags:"); System.out.println(stringPad("--xml-output-dir") + "Selects report xml output directory (default: `" + DEFAULT_OUTPUT_FOLDER + "`)"); - System.out.println(stringPad("--test-ids") + "Provides path to generated testIDs"); System.out.println(stringPad("--silent") + "Only output xml without stdout summary"); System.out.println(stringPad("--help") + "Displays this help screen"); System.exit(0); @@ -102,9 +101,6 @@ public static void main(String... args) { case "--xml-output-dir": xmlOutput = arguments.poll(); break; - case "--test-ids": - testIds = arguments.poll(); - break; case "--silent": silent = true; break; @@ -119,14 +115,8 @@ public static void main(String... args) { throw new RuntimeException("xml-output-dir argument passed incorrectly to the launcher class."); } - if (testIds == null) { - System.out.println("[junit-platform-native] WARNING: test-ids not provided to the NativeImageJUnitLauncher. " + - "This should only happen if you are running tests binary manually (instead of using 'gradle nativeTest' command)"); - testIds = getTestIDsFromDefaultLocations(); - } - Launcher launcher = LauncherFactory.create(); - TestPlan testPlan = getTestPlan(launcher, testIds); + TestPlan testPlan = getTestPlan(launcher); PrintWriter out = new PrintWriter(System.out); if (!silent) { @@ -151,8 +141,8 @@ public static void main(String... args) { System.exit(failedCount > 0 ? 1 : 0); } - private static TestPlan getTestPlan(Launcher launcher, String testIDs) { - List selectors = getSelectors(testIDs); + private static TestPlan getTestPlan(Launcher launcher) { + List selectors = getSelectors(); LauncherDiscoveryRequest request = LauncherDiscoveryRequestBuilder.request() .selectors(selectors) .build(); @@ -160,19 +150,21 @@ private static TestPlan getTestPlan(Launcher launcher, String testIDs) { return launcher.discover(request); } - private static List getSelectors(String testIDs) { + private static List getSelectors() { try { - Path outputDir = Paths.get(testIDs); - String prefix = System.getProperty(UniqueIdTrackingListener.OUTPUT_FILE_PREFIX_PROPERTY_NAME, + String systemPropertyBasedLocation = System.getProperty(UniqueIdTrackingListener.OUTPUT_DIR_PROPERTY_NAME); + Path uniqueIdDirectory = systemPropertyBasedLocation != null ? Path.of(systemPropertyBasedLocation) : getTestIDsFromDefaultLocations(); + String uniqueIdFilePrefix = System.getProperty(UniqueIdTrackingListener.OUTPUT_FILE_PREFIX_PROPERTY_NAME, UniqueIdTrackingListener.DEFAULT_OUTPUT_FILE_PREFIX); - List selectors = readAllFiles(outputDir, prefix) + + List selectors = readAllFiles(uniqueIdDirectory, uniqueIdFilePrefix) .map(DiscoverySelectors::selectUniqueId) .collect(Collectors.toList()); if (!selectors.isEmpty()) { System.out.printf( "[junit-platform-native] Running in 'test listener' mode using files matching pattern [%s*] " + "found in folder [%s] and its subfolders.%n", - prefix, outputDir.toAbsolutePath()); + uniqueIdFilePrefix, uniqueIdDirectory.toAbsolutePath()); return selectors; } } catch (Exception ex) { @@ -201,32 +193,33 @@ private static Stream findFiles(Path dir, String prefix) throws IOExceptio && path.getFileName().toString().startsWith(prefix))); } - private static String getTestIDsFromDefaultLocations() { - System.out.println("[junit-platform-native] WARNING: Trying to find test-ids on default locations."); + private static Path getTestIDsFromDefaultLocations() { + System.out.println("[junit-platform-native] WARNING: Trying to find test-ids on default locations. " + + "This should only happen if you are running tests executable manually."); Path defaultGradleTestIDsLocation = getGradleTestIdsDefaultLocation(); Path defaultMavenTestIDsLocation = getMavenTestIDsDefaultLocation(); if (Files.exists(defaultGradleTestIDsLocation) && Files.exists(defaultMavenTestIDsLocation)) { throw new RuntimeException("[junit-platform-native] test-ids found in both " + defaultGradleTestIDsLocation + " and " + defaultMavenTestIDsLocation + - ". Please specify the test-ids location by passing the '--test-ids ' argument to your tests executable."); + ". Please specify the test-ids location by passing the '--test-ids ' argument to your tests executable."); } if (Files.exists(defaultGradleTestIDsLocation)) { System.out.println("[junit-platform-native] WARNING: Using test-ids from default Gradle project location:" + defaultGradleTestIDsLocation); - return defaultGradleTestIDsLocation.toString(); + return defaultGradleTestIDsLocation; } if (Files.exists(defaultMavenTestIDsLocation)) { System.out.println("[junit-platform-native] WARNING: Using test-ids from default Maven project location:" + defaultMavenTestIDsLocation); - return defaultMavenTestIDsLocation.toString(); + return defaultMavenTestIDsLocation; } throw new RuntimeException("[junit-platform-native] test-ids not provided to the NativeImageJUnitLauncher and cannot be found on default locations. " + - "Searched in: " + defaultGradleTestIDsLocation + " and " + defaultMavenTestIDsLocation); + "Searched in: " + defaultGradleTestIDsLocation + " and " + defaultMavenTestIDsLocation); } private static Path getGradleTestIdsDefaultLocation() { - return Path.of(getBuildDirectory("/build/")) + return Path.of(getBuildDirectory(File.separator + "build" + File.separator)) .resolve("test-results") .resolve("test") .resolve("testlist") @@ -234,14 +227,19 @@ private static Path getGradleTestIdsDefaultLocation() { } private static Path getMavenTestIDsDefaultLocation() { - return Path.of(getBuildDirectory("/target/")) + return Path.of(getBuildDirectory(File.separator + "target" + File.separator)) .resolve("test-ids") .toAbsolutePath(); } private static String getBuildDirectory(String buildDir) { String executableLocation = Path.of(".").toAbsolutePath().toString(); - return executableLocation.substring(0, executableLocation.indexOf(buildDir) + buildDir.length()); + int index = executableLocation.indexOf(buildDir); + if (index < 0) { + return buildDir.substring(1); + } + + return executableLocation.substring(0, index + buildDir.length()); } } diff --git a/native-gradle-plugin/gradle/wrapper/gradle-wrapper.properties b/native-gradle-plugin/gradle/wrapper/gradle-wrapper.properties index 41dfb8790..db9a6b825 100644 --- a/native-gradle-plugin/gradle/wrapper/gradle-wrapper.properties +++ b/native-gradle-plugin/gradle/wrapper/gradle-wrapper.properties @@ -1,5 +1,5 @@ distributionBase=GRADLE_USER_HOME distributionPath=wrapper/dists -distributionUrl=https\://services.gradle.org/distributions/gradle-7.4-bin.zip +distributionUrl=https\://services.gradle.org/distributions/gradle-8.3-bin.zip zipStoreBase=GRADLE_USER_HOME zipStorePath=wrapper/dists diff --git a/native-gradle-plugin/src/main/java/org/graalvm/buildtools/gradle/NativeImagePlugin.java b/native-gradle-plugin/src/main/java/org/graalvm/buildtools/gradle/NativeImagePlugin.java index 21e3f2c15..71013e5c1 100644 --- a/native-gradle-plugin/src/main/java/org/graalvm/buildtools/gradle/NativeImagePlugin.java +++ b/native-gradle-plugin/src/main/java/org/graalvm/buildtools/gradle/NativeImagePlugin.java @@ -110,6 +110,7 @@ import org.gradle.api.tasks.bundling.AbstractArchiveTask; import org.gradle.api.tasks.bundling.Jar; import org.gradle.api.tasks.testing.Test; +import org.gradle.internal.impldep.org.junit.platform.launcher.listeners.UniqueIdTrackingListener; import org.gradle.jvm.toolchain.JavaToolchainService; import org.gradle.language.base.plugins.LifecycleBasePlugin; import org.gradle.process.CommandLineArgumentProvider; @@ -650,7 +651,6 @@ public void registerTestBinary(Project project, // Following ensures that required feature jar is on classpath for every project injectTestPluginDependencies(project, graalExtension.getTestSupport()); - TaskProvider testImageBuilder = tasks.named(deriveTaskName(name, "native", "Compile"), BuildNativeImageTask.class, task -> { task.setOnlyIf(t -> graalExtension.getTestSupport().get() && testListDirectory.getAsFile().get().exists()); task.getTestListDirectory().set(testListDirectory); @@ -662,6 +662,7 @@ public void registerTestBinary(Project project, // Later this will be replaced by a dedicated task not requiring execution of tests testList.from(testListDirectory).builtBy(testTask); testOptions.getClasspath().from(testList); + testOptions.getRuntimeArgs().add("-D" + JUNIT_PLATFORM_LISTENERS_UID_TRACKING_OUTPUT_DIR + "=" + testResultsDir.dir(testTask.getName() + "/testlist").get().getAsFile().getAbsolutePath()); }); if (isPrimaryTest) { tasks.register(DEPRECATED_NATIVE_TEST_BUILD_TASK, t -> { @@ -768,8 +769,6 @@ private static NativeImageOptions createTestOptions(GraalVMExtension graalExtens ListProperty runtimeArgs = testExtension.getRuntimeArgs(); runtimeArgs.add("--xml-output-dir"); runtimeArgs.add(project.getLayout().getBuildDirectory().dir("test-results/" + binaryName + "-native").map(d -> d.getAsFile().getAbsolutePath())); - runtimeArgs.add("--test-ids"); - runtimeArgs.add(project.getLayout().getBuildDirectory().dir("test-results/" + binaryName + "/testlist").map(d -> d.getAsFile().getAbsolutePath())); testExtension.buildArgs("--features=org.graalvm.junit.platform.JUnitPlatformFeature"); ConfigurableFileCollection classpath = testExtension.getClasspath(); diff --git a/native-maven-plugin/gradle/wrapper/gradle-wrapper.properties b/native-maven-plugin/gradle/wrapper/gradle-wrapper.properties index 41dfb8790..db9a6b825 100644 --- a/native-maven-plugin/gradle/wrapper/gradle-wrapper.properties +++ b/native-maven-plugin/gradle/wrapper/gradle-wrapper.properties @@ -1,5 +1,5 @@ distributionBase=GRADLE_USER_HOME distributionPath=wrapper/dists -distributionUrl=https\://services.gradle.org/distributions/gradle-7.4-bin.zip +distributionUrl=https\://services.gradle.org/distributions/gradle-8.3-bin.zip zipStoreBase=GRADLE_USER_HOME zipStorePath=wrapper/dists diff --git a/native-maven-plugin/src/main/java/org/graalvm/buildtools/maven/NativeTestMojo.java b/native-maven-plugin/src/main/java/org/graalvm/buildtools/maven/NativeTestMojo.java index ce06d73a8..a201c96f4 100644 --- a/native-maven-plugin/src/main/java/org/graalvm/buildtools/maven/NativeTestMojo.java +++ b/native-maven-plugin/src/main/java/org/graalvm/buildtools/maven/NativeTestMojo.java @@ -219,11 +219,6 @@ private void runNativeTests(Path executable) throws MojoExecutionException { throw new MojoExecutionException("Failed creating xml output directory"); } - Path testIdsLocation = Path.of(NativeExtension.testIdsDirectory(outputDirectory.getAbsolutePath())); - if (!testIdsLocation.toFile().exists()) { - throw new MojoExecutionException("Test-ids not available under target/test-ids"); - } - try { ProcessBuilder processBuilder = new ProcessBuilder(executable.toAbsolutePath().toString()); processBuilder.inheritIO(); @@ -232,8 +227,6 @@ private void runNativeTests(Path executable) throws MojoExecutionException { List command = new ArrayList<>(); command.add("--xml-output-dir"); command.add(xmlLocation.toString()); - command.add("--test-ids"); - command.add(testIdsLocation.toString()); systemProperties.forEach((key, value) -> command.add("-D" + key + "=" + value)); processBuilder.command().addAll(command); From faff29f0edae80d8bc5046022ed4dd6615101691 Mon Sep 17 00:00:00 2001 From: David Nestorovic Date: Fri, 21 Feb 2025 09:54:34 +0100 Subject: [PATCH 07/28] Restore code that provides support for Timeout annotation --- .../platform/config/jupiter/JupiterConfigProvider.java | 6 ++++++ .../org/graalvm/buildtools/gradle/NativeImagePlugin.java | 1 - 2 files changed, 6 insertions(+), 1 deletion(-) diff --git a/common/junit-platform-native/src/main/java/org/graalvm/junit/platform/config/jupiter/JupiterConfigProvider.java b/common/junit-platform-native/src/main/java/org/graalvm/junit/platform/config/jupiter/JupiterConfigProvider.java index 3ad7aa3b4..eaac4112e 100644 --- a/common/junit-platform-native/src/main/java/org/graalvm/junit/platform/config/jupiter/JupiterConfigProvider.java +++ b/common/junit-platform-native/src/main/java/org/graalvm/junit/platform/config/jupiter/JupiterConfigProvider.java @@ -67,10 +67,16 @@ public class JupiterConfigProvider implements PluginConfigProvider { @Override public void onLoad(NativeImageConfiguration config) { + /* Provide support for Timeout annotation */ + config.registerAllClassMembersForReflection( + "org.junit.jupiter.engine.extension.TimeoutExtension$ExecutorResource", + "org.junit.jupiter.engine.extension.TimeoutInvocationFactory$SingleThreadExecutorResource" + ); } @Override public void onTestClassRegistered(Class testClass, NativeImageConfiguration registry) { + /* Provide support for various annotations */ AnnotationUtils.registerClassesFromAnnotationForReflection(testClass, registry, TestMethodOrder.class, TestMethodOrder::value); AnnotationUtils.registerClassesFromAnnotationForReflection(testClass, registry, ArgumentsSource.class, ArgumentsSource::value); AnnotationUtils.registerClassesFromAnnotationForReflection(testClass, registry, ExtendWith.class, ExtendWith::value); diff --git a/native-gradle-plugin/src/main/java/org/graalvm/buildtools/gradle/NativeImagePlugin.java b/native-gradle-plugin/src/main/java/org/graalvm/buildtools/gradle/NativeImagePlugin.java index 71013e5c1..e8d09a54d 100644 --- a/native-gradle-plugin/src/main/java/org/graalvm/buildtools/gradle/NativeImagePlugin.java +++ b/native-gradle-plugin/src/main/java/org/graalvm/buildtools/gradle/NativeImagePlugin.java @@ -110,7 +110,6 @@ import org.gradle.api.tasks.bundling.AbstractArchiveTask; import org.gradle.api.tasks.bundling.Jar; import org.gradle.api.tasks.testing.Test; -import org.gradle.internal.impldep.org.junit.platform.launcher.listeners.UniqueIdTrackingListener; import org.gradle.jvm.toolchain.JavaToolchainService; import org.gradle.language.base.plugins.LifecycleBasePlugin; import org.gradle.process.CommandLineArgumentProvider; From 1e66e703f5601b27cefa923b0c98849a4b2a08f6 Mon Sep 17 00:00:00 2001 From: David Nestorovic Date: Fri, 21 Feb 2025 10:05:43 +0100 Subject: [PATCH 08/28] Add more checks in JUnitFunctionalTests --- .../gradle/wrapper/gradle-wrapper.properties | 2 +- .../gradle/wrapper/gradle-wrapper.properties | 2 +- native-gradle-plugin/gradle/wrapper/gradle-wrapper.properties | 2 +- .../org/graalvm/buildtools/gradle/JUnitFunctionalTests.groovy | 1 + native-maven-plugin/gradle/wrapper/gradle-wrapper.properties | 2 +- .../org/graalvm/buildtools/maven/JUnitFunctionalTests.groovy | 1 + 6 files changed, 6 insertions(+), 4 deletions(-) diff --git a/common/graalvm-reachability-metadata/gradle/wrapper/gradle-wrapper.properties b/common/graalvm-reachability-metadata/gradle/wrapper/gradle-wrapper.properties index db9a6b825..41dfb8790 100644 --- a/common/graalvm-reachability-metadata/gradle/wrapper/gradle-wrapper.properties +++ b/common/graalvm-reachability-metadata/gradle/wrapper/gradle-wrapper.properties @@ -1,5 +1,5 @@ distributionBase=GRADLE_USER_HOME distributionPath=wrapper/dists -distributionUrl=https\://services.gradle.org/distributions/gradle-8.3-bin.zip +distributionUrl=https\://services.gradle.org/distributions/gradle-7.4-bin.zip zipStoreBase=GRADLE_USER_HOME zipStorePath=wrapper/dists diff --git a/common/junit-platform-native/gradle/wrapper/gradle-wrapper.properties b/common/junit-platform-native/gradle/wrapper/gradle-wrapper.properties index db9a6b825..41dfb8790 100644 --- a/common/junit-platform-native/gradle/wrapper/gradle-wrapper.properties +++ b/common/junit-platform-native/gradle/wrapper/gradle-wrapper.properties @@ -1,5 +1,5 @@ distributionBase=GRADLE_USER_HOME distributionPath=wrapper/dists -distributionUrl=https\://services.gradle.org/distributions/gradle-8.3-bin.zip +distributionUrl=https\://services.gradle.org/distributions/gradle-7.4-bin.zip zipStoreBase=GRADLE_USER_HOME zipStorePath=wrapper/dists diff --git a/native-gradle-plugin/gradle/wrapper/gradle-wrapper.properties b/native-gradle-plugin/gradle/wrapper/gradle-wrapper.properties index db9a6b825..41dfb8790 100644 --- a/native-gradle-plugin/gradle/wrapper/gradle-wrapper.properties +++ b/native-gradle-plugin/gradle/wrapper/gradle-wrapper.properties @@ -1,5 +1,5 @@ distributionBase=GRADLE_USER_HOME distributionPath=wrapper/dists -distributionUrl=https\://services.gradle.org/distributions/gradle-8.3-bin.zip +distributionUrl=https\://services.gradle.org/distributions/gradle-7.4-bin.zip zipStoreBase=GRADLE_USER_HOME zipStorePath=wrapper/dists diff --git a/native-gradle-plugin/src/functionalTest/groovy/org/graalvm/buildtools/gradle/JUnitFunctionalTests.groovy b/native-gradle-plugin/src/functionalTest/groovy/org/graalvm/buildtools/gradle/JUnitFunctionalTests.groovy index 0ba536a3a..56f30b92f 100644 --- a/native-gradle-plugin/src/functionalTest/groovy/org/graalvm/buildtools/gradle/JUnitFunctionalTests.groovy +++ b/native-gradle-plugin/src/functionalTest/groovy/org/graalvm/buildtools/gradle/JUnitFunctionalTests.groovy @@ -64,6 +64,7 @@ class JUnitFunctionalTests extends AbstractFunctionalTest { tasks { succeeded ':testClasses', ':nativeTestCompile', ':nativeTest' } + outputDoesNotContain "[junit-platform-native] WARNING: Trying to find test-ids on default locations" outputContains "Running in 'test listener' mode using files matching pattern [junit-platform-unique-ids*] found in folder [" outputContains """ [ 10 containers found ] diff --git a/native-maven-plugin/gradle/wrapper/gradle-wrapper.properties b/native-maven-plugin/gradle/wrapper/gradle-wrapper.properties index db9a6b825..41dfb8790 100644 --- a/native-maven-plugin/gradle/wrapper/gradle-wrapper.properties +++ b/native-maven-plugin/gradle/wrapper/gradle-wrapper.properties @@ -1,5 +1,5 @@ distributionBase=GRADLE_USER_HOME distributionPath=wrapper/dists -distributionUrl=https\://services.gradle.org/distributions/gradle-8.3-bin.zip +distributionUrl=https\://services.gradle.org/distributions/gradle-7.4-bin.zip zipStoreBase=GRADLE_USER_HOME zipStorePath=wrapper/dists diff --git a/native-maven-plugin/src/functionalTest/groovy/org/graalvm/buildtools/maven/JUnitFunctionalTests.groovy b/native-maven-plugin/src/functionalTest/groovy/org/graalvm/buildtools/maven/JUnitFunctionalTests.groovy index 2b84daaec..1bdfa704d 100644 --- a/native-maven-plugin/src/functionalTest/groovy/org/graalvm/buildtools/maven/JUnitFunctionalTests.groovy +++ b/native-maven-plugin/src/functionalTest/groovy/org/graalvm/buildtools/maven/JUnitFunctionalTests.groovy @@ -51,6 +51,7 @@ class JUnitFunctionalTests extends AbstractGraalVMMavenFunctionalTest { then: buildSucceeded + outputDoesNotContain "[junit-platform-native] WARNING: Trying to find test-ids on default locations" outputContains "[junit-platform-native] Running in 'test listener' mode" outputContains """ [ 10 containers found ] From 76ddff139d2e6902df5776e01ed8b7624a667a06 Mon Sep 17 00:00:00 2001 From: David Nestorovic Date: Fri, 28 Feb 2025 13:49:39 +0100 Subject: [PATCH 09/28] Rename JUnit functional tests --- .../buildtools/gradle/JUnitFunctionalTests.groovy | 11 +---------- .../buildtools/maven/JUnitFunctionalTests.groovy | 4 +--- 2 files changed, 2 insertions(+), 13 deletions(-) diff --git a/native-gradle-plugin/src/functionalTest/groovy/org/graalvm/buildtools/gradle/JUnitFunctionalTests.groovy b/native-gradle-plugin/src/functionalTest/groovy/org/graalvm/buildtools/gradle/JUnitFunctionalTests.groovy index 56f30b92f..c630abc4f 100644 --- a/native-gradle-plugin/src/functionalTest/groovy/org/graalvm/buildtools/gradle/JUnitFunctionalTests.groovy +++ b/native-gradle-plugin/src/functionalTest/groovy/org/graalvm/buildtools/gradle/JUnitFunctionalTests.groovy @@ -42,17 +42,9 @@ package org.graalvm.buildtools.gradle import org.graalvm.buildtools.gradle.fixtures.AbstractFunctionalTest -import spock.lang.Ignore -import spock.lang.IgnoreIf -import spock.lang.Issue -import spock.lang.Requires -import spock.lang.Unroll - -import java.nio.file.Files class JUnitFunctionalTests extends AbstractFunctionalTest { - @Unroll("test if JUint support works with various annotations, reflection and resources") - def "run junit tests"() { + def "test if JUint support works with various annotations, reflection and resources"() { given: withSample("junit-tests") @@ -80,6 +72,5 @@ class JUnitFunctionalTests extends AbstractFunctionalTest { [ 23 tests successful ] [ 0 tests failed ] """.trim() - } } diff --git a/native-maven-plugin/src/functionalTest/groovy/org/graalvm/buildtools/maven/JUnitFunctionalTests.groovy b/native-maven-plugin/src/functionalTest/groovy/org/graalvm/buildtools/maven/JUnitFunctionalTests.groovy index 1bdfa704d..5dd7c57d2 100644 --- a/native-maven-plugin/src/functionalTest/groovy/org/graalvm/buildtools/maven/JUnitFunctionalTests.groovy +++ b/native-maven-plugin/src/functionalTest/groovy/org/graalvm/buildtools/maven/JUnitFunctionalTests.groovy @@ -42,8 +42,7 @@ package org.graalvm.buildtools.maven class JUnitFunctionalTests extends AbstractGraalVMMavenFunctionalTest { - - def "run junit tests"() { + def "test if JUint support works with various annotations, reflection and resources"() { withSample("junit-tests") when: @@ -68,5 +67,4 @@ class JUnitFunctionalTests extends AbstractGraalVMMavenFunctionalTest { [ 0 tests failed ] """.trim() } - } From ec2a319e007fd37fe1214345f254129ce5f2fec2 Mon Sep 17 00:00:00 2001 From: David Nestorovic Date: Fri, 28 Feb 2025 15:41:04 +0100 Subject: [PATCH 10/28] Make fallback test-ids search more roubust --- .../platform/NativeImageJUnitLauncher.java | 47 ++++++++++++++----- 1 file changed, 36 insertions(+), 11 deletions(-) diff --git a/common/junit-platform-native/src/main/java/org/graalvm/junit/platform/NativeImageJUnitLauncher.java b/common/junit-platform-native/src/main/java/org/graalvm/junit/platform/NativeImageJUnitLauncher.java index e3a2bc8c1..3b3df6c6b 100644 --- a/common/junit-platform-native/src/main/java/org/graalvm/junit/platform/NativeImageJUnitLauncher.java +++ b/common/junit-platform-native/src/main/java/org/graalvm/junit/platform/NativeImageJUnitLauncher.java @@ -195,21 +195,22 @@ private static Stream findFiles(Path dir, String prefix) throws IOExceptio private static Path getTestIDsFromDefaultLocations() { System.out.println("[junit-platform-native] WARNING: Trying to find test-ids on default locations. " + + "As this is a fallback mode, it could take a while. " + "This should only happen if you are running tests executable manually."); Path defaultGradleTestIDsLocation = getGradleTestIdsDefaultLocation(); Path defaultMavenTestIDsLocation = getMavenTestIDsDefaultLocation(); - if (Files.exists(defaultGradleTestIDsLocation) && Files.exists(defaultMavenTestIDsLocation)) { + if (testIdsDirectoryExists(defaultGradleTestIDsLocation) && testIdsDirectoryExists(defaultMavenTestIDsLocation)) { throw new RuntimeException("[junit-platform-native] test-ids found in both " + defaultGradleTestIDsLocation + " and " + defaultMavenTestIDsLocation + ". Please specify the test-ids location by passing the '--test-ids ' argument to your tests executable."); } - if (Files.exists(defaultGradleTestIDsLocation)) { + if (testIdsDirectoryExists(defaultGradleTestIDsLocation)) { System.out.println("[junit-platform-native] WARNING: Using test-ids from default Gradle project location:" + defaultGradleTestIDsLocation); return defaultGradleTestIDsLocation; } - if (Files.exists(defaultMavenTestIDsLocation)) { + if (testIdsDirectoryExists(defaultMavenTestIDsLocation)) { System.out.println("[junit-platform-native] WARNING: Using test-ids from default Maven project location:" + defaultMavenTestIDsLocation); return defaultMavenTestIDsLocation; } @@ -219,17 +220,13 @@ private static Path getTestIDsFromDefaultLocations() { } private static Path getGradleTestIdsDefaultLocation() { - return Path.of(getBuildDirectory(File.separator + "build" + File.separator)) - .resolve("test-results") - .resolve("test") - .resolve("testlist") - .toAbsolutePath(); + File gradleBuildDirectory = new File(getBuildDirectory(File.separator + "build" + File.separator)); + return searchForDirectory(gradleBuildDirectory, "testlist"); } private static Path getMavenTestIDsDefaultLocation() { - return Path.of(getBuildDirectory(File.separator + "target" + File.separator)) - .resolve("test-ids") - .toAbsolutePath(); + File mavenTargetDirectory = new File(getBuildDirectory(File.separator + "target" + File.separator)); + return searchForDirectory(mavenTargetDirectory, "test-ids"); } private static String getBuildDirectory(String buildDir) { @@ -242,4 +239,32 @@ private static String getBuildDirectory(String buildDir) { return executableLocation.substring(0, index + buildDir.length()); } + private static Path searchForDirectory(File root, String target) { + if (root == null || !root.isDirectory()) { + return null; + } + + if (root.getName().equals(target)) { + return Path.of(root.getAbsolutePath()); + } + + File[] content = root.listFiles(); + if (content == null) { + return null; + } + + for (File file : content) { + Path result = searchForDirectory(file, target); + if (result != null) { + return result; + } + } + + return null; + } + + private static boolean testIdsDirectoryExists(Path directory) { + return directory != null && Files.exists(directory); + } + } From 50a7ac982f41e2feb24d9d7c504dc9ac28a02bf0 Mon Sep 17 00:00:00 2001 From: David Nestorovic Date: Tue, 4 Mar 2025 16:07:26 +0100 Subject: [PATCH 11/28] Throw an error when unique file prefix is not avaialable at runtime --- .../org/graalvm/junit/platform/NativeImageJUnitLauncher.java | 4 ++++ 1 file changed, 4 insertions(+) diff --git a/common/junit-platform-native/src/main/java/org/graalvm/junit/platform/NativeImageJUnitLauncher.java b/common/junit-platform-native/src/main/java/org/graalvm/junit/platform/NativeImageJUnitLauncher.java index 3b3df6c6b..c35871a88 100644 --- a/common/junit-platform-native/src/main/java/org/graalvm/junit/platform/NativeImageJUnitLauncher.java +++ b/common/junit-platform-native/src/main/java/org/graalvm/junit/platform/NativeImageJUnitLauncher.java @@ -157,6 +157,10 @@ private static List getSelectors() { String uniqueIdFilePrefix = System.getProperty(UniqueIdTrackingListener.OUTPUT_FILE_PREFIX_PROPERTY_NAME, UniqueIdTrackingListener.DEFAULT_OUTPUT_FILE_PREFIX); + if (uniqueIdFilePrefix == null) { + throw new RuntimeException("Test-ids unique id file prefix not provided to the NativeImageJUnitLauncher."); + } + List selectors = readAllFiles(uniqueIdDirectory, uniqueIdFilePrefix) .map(DiscoverySelectors::selectUniqueId) .collect(Collectors.toList()); From 9b4c24932850f1c756db95bbc136d24a68f096e1 Mon Sep 17 00:00:00 2001 From: David Nestorovic Date: Tue, 4 Mar 2025 16:25:55 +0100 Subject: [PATCH 12/28] Add buildtime checks for test-ids directory and prefix --- .../graalvm/junit/platform/JUnitPlatformFeature.java | 10 +++++++++- .../junit/platform/NativeImageJUnitLauncher.java | 2 +- 2 files changed, 10 insertions(+), 2 deletions(-) diff --git a/common/junit-platform-native/src/main/java/org/graalvm/junit/platform/JUnitPlatformFeature.java b/common/junit-platform-native/src/main/java/org/graalvm/junit/platform/JUnitPlatformFeature.java index 3644e07dc..87eae81c3 100644 --- a/common/junit-platform-native/src/main/java/org/graalvm/junit/platform/JUnitPlatformFeature.java +++ b/common/junit-platform-native/src/main/java/org/graalvm/junit/platform/JUnitPlatformFeature.java @@ -111,10 +111,18 @@ public void beforeAnalysis(BeforeAnalysisAccess access) { private List getSelectors() { try { - Path uniqueIdDirectory = Path.of(System.getProperty(UniqueIdTrackingListener.OUTPUT_DIR_PROPERTY_NAME)); + String uniqueIdDirectoryProperty = System.getProperty(UniqueIdTrackingListener.OUTPUT_DIR_PROPERTY_NAME); + if (uniqueIdDirectoryProperty == null) { + throw new IllegalStateException("Cannot determine test-ids directory because junit.platform.listeners.uid.tracking.output.dir property is null"); + } + String uniqueIdFilePrefix = System.getProperty(UniqueIdTrackingListener.OUTPUT_FILE_PREFIX_PROPERTY_NAME, UniqueIdTrackingListener.DEFAULT_OUTPUT_FILE_PREFIX); + if (uniqueIdFilePrefix == null) { + throw new IllegalStateException("Cannot determine unique test-ids prefix because junit.platform.listeners.uid.tracking.output.file.prefix property is null"); + } + Path uniqueIdDirectory = Path.of(uniqueIdDirectoryProperty); List selectors = readAllFiles(uniqueIdDirectory, uniqueIdFilePrefix) .map(DiscoverySelectors::selectUniqueId) .collect(Collectors.toList()); diff --git a/common/junit-platform-native/src/main/java/org/graalvm/junit/platform/NativeImageJUnitLauncher.java b/common/junit-platform-native/src/main/java/org/graalvm/junit/platform/NativeImageJUnitLauncher.java index c35871a88..a9adac5bb 100644 --- a/common/junit-platform-native/src/main/java/org/graalvm/junit/platform/NativeImageJUnitLauncher.java +++ b/common/junit-platform-native/src/main/java/org/graalvm/junit/platform/NativeImageJUnitLauncher.java @@ -154,9 +154,9 @@ private static List getSelectors() { try { String systemPropertyBasedLocation = System.getProperty(UniqueIdTrackingListener.OUTPUT_DIR_PROPERTY_NAME); Path uniqueIdDirectory = systemPropertyBasedLocation != null ? Path.of(systemPropertyBasedLocation) : getTestIDsFromDefaultLocations(); + String uniqueIdFilePrefix = System.getProperty(UniqueIdTrackingListener.OUTPUT_FILE_PREFIX_PROPERTY_NAME, UniqueIdTrackingListener.DEFAULT_OUTPUT_FILE_PREFIX); - if (uniqueIdFilePrefix == null) { throw new RuntimeException("Test-ids unique id file prefix not provided to the NativeImageJUnitLauncher."); } From f94a05e524e8745099bfa921e96eae4ffe07a02d Mon Sep 17 00:00:00 2001 From: David Nestorovic Date: Mon, 17 Mar 2025 11:49:26 +0100 Subject: [PATCH 13/28] Register org.junit.runner.Description fields for reflection when using vintage provider --- .../junit/platform/config/vintage/VintageConfigProvider.java | 4 +++- 1 file changed, 3 insertions(+), 1 deletion(-) diff --git a/common/junit-platform-native/src/main/java/org/graalvm/junit/platform/config/vintage/VintageConfigProvider.java b/common/junit-platform-native/src/main/java/org/graalvm/junit/platform/config/vintage/VintageConfigProvider.java index 539d27da5..60f98c273 100644 --- a/common/junit-platform-native/src/main/java/org/graalvm/junit/platform/config/vintage/VintageConfigProvider.java +++ b/common/junit-platform-native/src/main/java/org/graalvm/junit/platform/config/vintage/VintageConfigProvider.java @@ -43,6 +43,7 @@ import org.graalvm.junit.platform.config.core.NativeImageConfiguration; import org.graalvm.junit.platform.config.core.PluginConfigProvider; +import org.graalvm.nativeimage.hosted.RuntimeReflection; import org.graalvm.nativeimage.hosted.RuntimeSerialization; public class VintageConfigProvider implements PluginConfigProvider { @@ -51,8 +52,9 @@ public class VintageConfigProvider implements PluginConfigProvider { public void onLoad(NativeImageConfiguration config) { try { RuntimeSerialization.register(Class.forName("org.junit.runner.Result").getDeclaredClasses()); + RuntimeReflection.register(Class.forName("org.junit.runner.Description").getDeclaredFields()); } catch (ClassNotFoundException e) { - System.out.println("Cannot register declared classes of org.junit.runner.Result for serialization. Vintage JUnit not available."); + System.out.println("Cannot register declared classes of org.junit.runner.Result for serialization or fields of org.junit.runner.Description for reflection. Vintage JUnit not available."); } } From 4ce03e79c219ab36082d59e287631761c1e75996 Mon Sep 17 00:00:00 2001 From: David Nestorovic Date: Mon, 17 Mar 2025 12:02:29 +0100 Subject: [PATCH 14/28] Refactor usages of flatmap --- .../graalvm/junit/platform/NativeImageJUnitLauncher.java | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) diff --git a/common/junit-platform-native/src/main/java/org/graalvm/junit/platform/NativeImageJUnitLauncher.java b/common/junit-platform-native/src/main/java/org/graalvm/junit/platform/NativeImageJUnitLauncher.java index a9adac5bb..6d486312e 100644 --- a/common/junit-platform-native/src/main/java/org/graalvm/junit/platform/NativeImageJUnitLauncher.java +++ b/common/junit-platform-native/src/main/java/org/graalvm/junit/platform/NativeImageJUnitLauncher.java @@ -179,13 +179,13 @@ private static List getSelectors() { } private static Stream readAllFiles(Path dir, String prefix) throws IOException { - return findFiles(dir, prefix).map(outputFile -> { + return findFiles(dir, prefix).flatMap(outputFile -> { try { - return Files.readAllLines(outputFile); + return Files.readAllLines(outputFile).stream(); } catch (IOException ex) { throw new UncheckedIOException(ex); } - }).flatMap(List::stream); + }); } private static Stream findFiles(Path dir, String prefix) throws IOException { From 6519022c1ac33fe050f97a4704880e21941c661f Mon Sep 17 00:00:00 2001 From: David Nestorovic Date: Thu, 20 Mar 2025 18:34:35 +0100 Subject: [PATCH 15/28] Support RunWith annotation for JUnit4 --- .../junit/platform/JUnitPlatformFeature.java | 5 ++++ .../config/core/NativeImageConfiguration.java | 5 ++++ .../config/core/PluginConfigProvider.java | 14 ++++++++--- .../config/jupiter/JupiterConfigProvider.java | 2 +- .../platform/PlatformConfigProvider.java | 2 +- .../config/vintage/VintageConfigProvider.java | 23 ++++++++++++++++++- 6 files changed, 45 insertions(+), 6 deletions(-) diff --git a/common/junit-platform-native/src/main/java/org/graalvm/junit/platform/JUnitPlatformFeature.java b/common/junit-platform-native/src/main/java/org/graalvm/junit/platform/JUnitPlatformFeature.java index 87eae81c3..397499311 100644 --- a/common/junit-platform-native/src/main/java/org/graalvm/junit/platform/JUnitPlatformFeature.java +++ b/common/junit-platform-native/src/main/java/org/graalvm/junit/platform/JUnitPlatformFeature.java @@ -84,6 +84,11 @@ public static void debug(String format, Object... args) { } } + @Override + public void afterRegistration(AfterRegistrationAccess access) { + extensionConfigProviders.forEach(p -> p.initialize(access.getApplicationClassLoader(), nativeImageConfigImpl)); + } + public static boolean debug() { return ImageSingletons.lookup(JUnitPlatformFeature.class).debug; } diff --git a/common/junit-platform-native/src/main/java/org/graalvm/junit/platform/config/core/NativeImageConfiguration.java b/common/junit-platform-native/src/main/java/org/graalvm/junit/platform/config/core/NativeImageConfiguration.java index 96b9ec089..361388f8b 100644 --- a/common/junit-platform-native/src/main/java/org/graalvm/junit/platform/config/core/NativeImageConfiguration.java +++ b/common/junit-platform-native/src/main/java/org/graalvm/junit/platform/config/core/NativeImageConfiguration.java @@ -66,6 +66,11 @@ default void registerAllClassMembersForReflection(Class... classes) { registerForReflection(clazz.getDeclaredConstructors()); registerForReflection(clazz.getDeclaredMethods()); registerForReflection(clazz.getDeclaredFields()); + + Class[] declaredClasses = clazz.getDeclaredClasses(); + for (Class cls : declaredClasses) { + registerAllClassMembersForReflection(cls); + } } } } diff --git a/common/junit-platform-native/src/main/java/org/graalvm/junit/platform/config/core/PluginConfigProvider.java b/common/junit-platform-native/src/main/java/org/graalvm/junit/platform/config/core/PluginConfigProvider.java index 8b104c489..b07a7ddaf 100644 --- a/common/junit-platform-native/src/main/java/org/graalvm/junit/platform/config/core/PluginConfigProvider.java +++ b/common/junit-platform-native/src/main/java/org/graalvm/junit/platform/config/core/PluginConfigProvider.java @@ -41,10 +41,18 @@ package org.graalvm.junit.platform.config.core; -public interface PluginConfigProvider { +public abstract class PluginConfigProvider { - void onLoad(NativeImageConfiguration config); + protected ClassLoader applicationClassLoader; + protected NativeImageConfiguration nativeImageConfigImpl; - void onTestClassRegistered(Class testClass, NativeImageConfiguration registry); + public abstract void onLoad(NativeImageConfiguration config); + + public abstract void onTestClassRegistered(Class testClass, NativeImageConfiguration registry); + + public final void initialize(ClassLoader classLoader, NativeImageConfiguration nic) { + applicationClassLoader = classLoader; + nativeImageConfigImpl = nic; + } } diff --git a/common/junit-platform-native/src/main/java/org/graalvm/junit/platform/config/jupiter/JupiterConfigProvider.java b/common/junit-platform-native/src/main/java/org/graalvm/junit/platform/config/jupiter/JupiterConfigProvider.java index eaac4112e..f666eba56 100644 --- a/common/junit-platform-native/src/main/java/org/graalvm/junit/platform/config/jupiter/JupiterConfigProvider.java +++ b/common/junit-platform-native/src/main/java/org/graalvm/junit/platform/config/jupiter/JupiterConfigProvider.java @@ -63,7 +63,7 @@ import static org.graalvm.junit.platform.JUnitPlatformFeature.debug; -public class JupiterConfigProvider implements PluginConfigProvider { +public class JupiterConfigProvider extends PluginConfigProvider { @Override public void onLoad(NativeImageConfiguration config) { diff --git a/common/junit-platform-native/src/main/java/org/graalvm/junit/platform/config/platform/PlatformConfigProvider.java b/common/junit-platform-native/src/main/java/org/graalvm/junit/platform/config/platform/PlatformConfigProvider.java index ed552d9af..4c763d621 100644 --- a/common/junit-platform-native/src/main/java/org/graalvm/junit/platform/config/platform/PlatformConfigProvider.java +++ b/common/junit-platform-native/src/main/java/org/graalvm/junit/platform/config/platform/PlatformConfigProvider.java @@ -47,7 +47,7 @@ import org.junit.platform.launcher.TestIdentifier; -public class PlatformConfigProvider implements PluginConfigProvider { +public class PlatformConfigProvider extends PluginConfigProvider { @Override public void onLoad(NativeImageConfiguration config) { diff --git a/common/junit-platform-native/src/main/java/org/graalvm/junit/platform/config/vintage/VintageConfigProvider.java b/common/junit-platform-native/src/main/java/org/graalvm/junit/platform/config/vintage/VintageConfigProvider.java index 60f98c273..9efb39410 100644 --- a/common/junit-platform-native/src/main/java/org/graalvm/junit/platform/config/vintage/VintageConfigProvider.java +++ b/common/junit-platform-native/src/main/java/org/graalvm/junit/platform/config/vintage/VintageConfigProvider.java @@ -46,7 +46,10 @@ import org.graalvm.nativeimage.hosted.RuntimeReflection; import org.graalvm.nativeimage.hosted.RuntimeSerialization; -public class VintageConfigProvider implements PluginConfigProvider { +import java.lang.annotation.Annotation; +import java.lang.reflect.Method; + +public class VintageConfigProvider extends PluginConfigProvider { @Override public void onLoad(NativeImageConfiguration config) { @@ -60,5 +63,23 @@ public void onLoad(NativeImageConfiguration config) { @Override public void onTestClassRegistered(Class testClass, NativeImageConfiguration registry) { + registerAnnotationClassesForReflection("org.junit.runner.RunWith", "value", testClass); + registerAnnotationClassesForReflection("org.junit.runners.Parameterized.UseParametersRunnerFactory", "value", testClass); + } + + @SuppressWarnings("unchecked") + private void registerAnnotationClassesForReflection(String annotationClass, String classProviderMethod, Class testClass) { + try { + Class annotation = (Class) applicationClassLoader.loadClass(annotationClass); + Method classProvider = annotation.getDeclaredMethod(classProviderMethod); + + Annotation classAnnotation = testClass.getAnnotation(annotation); + if (classAnnotation != null) { + Class annotationArgument = (Class) classProvider.invoke(classAnnotation); + nativeImageConfigImpl.registerAllClassMembersForReflection(annotationArgument); + } + } catch (ReflectiveOperationException e) { + // intentionally ignored + } } } From f3be7dbdc80db13323ffde638a6b1fb681613340 Mon Sep 17 00:00:00 2001 From: David Nestorovic Date: Thu, 27 Mar 2025 14:58:39 +0100 Subject: [PATCH 16/28] Improve error messages --- .../graalvm/junit/platform/NativeImageJUnitLauncher.java | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) diff --git a/common/junit-platform-native/src/main/java/org/graalvm/junit/platform/NativeImageJUnitLauncher.java b/common/junit-platform-native/src/main/java/org/graalvm/junit/platform/NativeImageJUnitLauncher.java index 6d486312e..a2bcd997d 100644 --- a/common/junit-platform-native/src/main/java/org/graalvm/junit/platform/NativeImageJUnitLauncher.java +++ b/common/junit-platform-native/src/main/java/org/graalvm/junit/platform/NativeImageJUnitLauncher.java @@ -198,7 +198,8 @@ private static Stream findFiles(Path dir, String prefix) throws IOExceptio } private static Path getTestIDsFromDefaultLocations() { - System.out.println("[junit-platform-native] WARNING: Trying to find test-ids on default locations. " + + System.out.println("[junit-platform-native] WARNING: -djunit.platform.listeners.uid.tracking.output.dir not specified, " + + "trying to find test-ids on default Gradle/Maven locations. " + "As this is a fallback mode, it could take a while. " + "This should only happen if you are running tests executable manually."); Path defaultGradleTestIDsLocation = getGradleTestIdsDefaultLocation(); @@ -219,8 +220,7 @@ private static Path getTestIDsFromDefaultLocations() { return defaultMavenTestIDsLocation; } - throw new RuntimeException("[junit-platform-native] test-ids not provided to the NativeImageJUnitLauncher and cannot be found on default locations. " + - "Searched in: " + defaultGradleTestIDsLocation + " and " + defaultMavenTestIDsLocation); + throw new RuntimeException("[junit-platform-native] test-ids not provided to the NativeImageJUnitLauncher and cannot be found on default locations."); } private static Path getGradleTestIdsDefaultLocation() { From dd4f497396044eaa55d14ddbe0482b157d5ee6eb Mon Sep 17 00:00:00 2001 From: David Nestorovic Date: Wed, 2 Apr 2025 10:38:39 +0200 Subject: [PATCH 17/28] Use several specific packages instead of one general when initializing at buildtime --- .../graalvm/junit/platform/JUnitPlatformFeature.java | 8 -------- .../platform/config/core/PluginConfigProvider.java | 10 ++++++++++ .../config/jupiter/JupiterConfigProvider.java | 10 ++++++++++ .../config/platform/PlatformConfigProvider.java | 11 +++++++++++ .../config/vintage/VintageConfigProvider.java | 11 +++++++++++ samples/junit-tests/gradle.properties | 2 +- samples/junit-tests/pom.xml | 4 ++-- 7 files changed, 45 insertions(+), 11 deletions(-) diff --git a/common/junit-platform-native/src/main/java/org/graalvm/junit/platform/JUnitPlatformFeature.java b/common/junit-platform-native/src/main/java/org/graalvm/junit/platform/JUnitPlatformFeature.java index 397499311..4c4c1b387 100644 --- a/common/junit-platform-native/src/main/java/org/graalvm/junit/platform/JUnitPlatformFeature.java +++ b/common/junit-platform-native/src/main/java/org/graalvm/junit/platform/JUnitPlatformFeature.java @@ -44,7 +44,6 @@ import org.graalvm.junit.platform.config.core.PluginConfigProvider; import org.graalvm.nativeimage.ImageSingletons; import org.graalvm.nativeimage.hosted.Feature; -import org.graalvm.nativeimage.hosted.RuntimeClassInitialization; import org.graalvm.nativeimage.hosted.RuntimeReflection; import org.junit.platform.engine.DiscoverySelector; @@ -100,13 +99,6 @@ public void duringSetup(DuringSetupAccess access) { @Override public void beforeAnalysis(BeforeAnalysisAccess access) { - /* Before GraalVM version 22 we couldn't have classes initialized at run-time - that are also used at build-time but not added to the image heap */ - if (Runtime.version().feature() <= 21) { - RuntimeClassInitialization.initializeAtBuildTime("org.junit"); - RuntimeClassInitialization.initializeAtBuildTime("java"); - } - List selectors = getSelectors(); registerTestClassesForReflection(selectors); diff --git a/common/junit-platform-native/src/main/java/org/graalvm/junit/platform/config/core/PluginConfigProvider.java b/common/junit-platform-native/src/main/java/org/graalvm/junit/platform/config/core/PluginConfigProvider.java index b07a7ddaf..938fcda3b 100644 --- a/common/junit-platform-native/src/main/java/org/graalvm/junit/platform/config/core/PluginConfigProvider.java +++ b/common/junit-platform-native/src/main/java/org/graalvm/junit/platform/config/core/PluginConfigProvider.java @@ -41,6 +41,10 @@ package org.graalvm.junit.platform.config.core; +import org.graalvm.nativeimage.hosted.RuntimeClassInitialization; + +import java.util.List; + public abstract class PluginConfigProvider { protected ClassLoader applicationClassLoader; @@ -55,4 +59,10 @@ public final void initialize(ClassLoader classLoader, NativeImageConfiguration n applicationClassLoader = classLoader; nativeImageConfigImpl = nic; } + + protected void initializeClassesForOlderJDKs(List packages) { + if (Runtime.version().feature() <= 21) { + packages.forEach(RuntimeClassInitialization::initializeAtBuildTime); + } + } } diff --git a/common/junit-platform-native/src/main/java/org/graalvm/junit/platform/config/jupiter/JupiterConfigProvider.java b/common/junit-platform-native/src/main/java/org/graalvm/junit/platform/config/jupiter/JupiterConfigProvider.java index f666eba56..af5b4b5a7 100644 --- a/common/junit-platform-native/src/main/java/org/graalvm/junit/platform/config/jupiter/JupiterConfigProvider.java +++ b/common/junit-platform-native/src/main/java/org/graalvm/junit/platform/config/jupiter/JupiterConfigProvider.java @@ -65,8 +65,18 @@ public class JupiterConfigProvider extends PluginConfigProvider { + /* Before GraalVM version 22 we couldn't have classes initialized at run-time + that are also used at build-time but not added to the image heap */ + private final List packagesForOldInitializationStrategy = List.of( + "org.junit.jupiter.engine", + "org.junit.jupiter.api", + "org.junit.jupiter.params", + "java.beans.Introspector"); + @Override public void onLoad(NativeImageConfiguration config) { + initializeClassesForOlderJDKs(packagesForOldInitializationStrategy); + /* Provide support for Timeout annotation */ config.registerAllClassMembersForReflection( "org.junit.jupiter.engine.extension.TimeoutExtension$ExecutorResource", diff --git a/common/junit-platform-native/src/main/java/org/graalvm/junit/platform/config/platform/PlatformConfigProvider.java b/common/junit-platform-native/src/main/java/org/graalvm/junit/platform/config/platform/PlatformConfigProvider.java index 4c763d621..e5facfe04 100644 --- a/common/junit-platform-native/src/main/java/org/graalvm/junit/platform/config/platform/PlatformConfigProvider.java +++ b/common/junit-platform-native/src/main/java/org/graalvm/junit/platform/config/platform/PlatformConfigProvider.java @@ -46,11 +46,22 @@ import org.graalvm.nativeimage.hosted.RuntimeSerialization; import org.junit.platform.launcher.TestIdentifier; +import java.util.List; + public class PlatformConfigProvider extends PluginConfigProvider { + /* Before GraalVM version 22 we couldn't have classes initialized at run-time + that are also used at build-time but not added to the image heap */ + private final List packagesForOldInitializationStrategy = List.of( + "org.junit.platform.engine", + "org.junit.platform.launcher", + "org.junit.platform.commons", + "org.junit.validator.PublicClassValidator"); + @Override public void onLoad(NativeImageConfiguration config) { + initializeClassesForOlderJDKs(packagesForOldInitializationStrategy); RuntimeSerialization.register(TestIdentifier.class.getDeclaredClasses()); } diff --git a/common/junit-platform-native/src/main/java/org/graalvm/junit/platform/config/vintage/VintageConfigProvider.java b/common/junit-platform-native/src/main/java/org/graalvm/junit/platform/config/vintage/VintageConfigProvider.java index 9efb39410..5e9c05b81 100644 --- a/common/junit-platform-native/src/main/java/org/graalvm/junit/platform/config/vintage/VintageConfigProvider.java +++ b/common/junit-platform-native/src/main/java/org/graalvm/junit/platform/config/vintage/VintageConfigProvider.java @@ -48,11 +48,22 @@ import java.lang.annotation.Annotation; import java.lang.reflect.Method; +import java.util.List; public class VintageConfigProvider extends PluginConfigProvider { + /* Before GraalVM version 22 we couldn't have classes initialized at run-time + that are also used at build-time but not added to the image heap */ + private final List packagesForOldInitializationStrategy = List.of( + "org.junit.vintage.engine", + "org.junit.runner", + "org.junit.runners", + "org.junit.internal.runners.rules.RuleMemberValidator"); + @Override public void onLoad(NativeImageConfiguration config) { + initializeClassesForOlderJDKs(packagesForOldInitializationStrategy); + try { RuntimeSerialization.register(Class.forName("org.junit.runner.Result").getDeclaredClasses()); RuntimeReflection.register(Class.forName("org.junit.runner.Description").getDeclaredFields()); diff --git a/samples/junit-tests/gradle.properties b/samples/junit-tests/gradle.properties index 1df94c8aa..543c078cd 100644 --- a/samples/junit-tests/gradle.properties +++ b/samples/junit-tests/gradle.properties @@ -1 +1 @@ -native.gradle.plugin.version = 0.10.6-SNAPSHOT +native.gradle.plugin.version = 0.10.7-SNAPSHOT diff --git a/samples/junit-tests/pom.xml b/samples/junit-tests/pom.xml index b7c04d141..9cdc96369 100644 --- a/samples/junit-tests/pom.xml +++ b/samples/junit-tests/pom.xml @@ -52,8 +52,8 @@ 1.8 UTF-8 5.11.0 - 0.10.6-SNAPSHOT - 0.10.6-SNAPSHOT + 0.10.7-SNAPSHOT + 0.10.7-SNAPSHOT example-app From 82d3fbb70c6da6053a81bbd7c14c2f04a14e2739 Mon Sep 17 00:00:00 2001 From: David Nestorovic Date: Mon, 7 Apr 2025 17:05:50 +0200 Subject: [PATCH 18/28] Store all classes that should be initialized at buildtime for earlier JDKs in a separate file --- .../junit/platform/JUnitPlatformFeature.java | 21 +++++++ .../platform/NativeImageJUnitLauncher.java | 2 +- .../config/core/PluginConfigProvider.java | 10 ---- .../config/jupiter/JupiterConfigProvider.java | 10 ---- .../platform/PlatformConfigProvider.java | 12 ---- .../config/vintage/VintageConfigProvider.java | 11 ---- .../main/resources/initialize-at-buildtime | 55 +++++++++++++++++++ 7 files changed, 77 insertions(+), 44 deletions(-) create mode 100644 common/junit-platform-native/src/main/resources/initialize-at-buildtime diff --git a/common/junit-platform-native/src/main/java/org/graalvm/junit/platform/JUnitPlatformFeature.java b/common/junit-platform-native/src/main/java/org/graalvm/junit/platform/JUnitPlatformFeature.java index 4c4c1b387..6dac74c51 100644 --- a/common/junit-platform-native/src/main/java/org/graalvm/junit/platform/JUnitPlatformFeature.java +++ b/common/junit-platform-native/src/main/java/org/graalvm/junit/platform/JUnitPlatformFeature.java @@ -44,6 +44,7 @@ import org.graalvm.junit.platform.config.core.PluginConfigProvider; import org.graalvm.nativeimage.ImageSingletons; import org.graalvm.nativeimage.hosted.Feature; +import org.graalvm.nativeimage.hosted.RuntimeClassInitialization; import org.graalvm.nativeimage.hosted.RuntimeReflection; import org.junit.platform.engine.DiscoverySelector; @@ -58,7 +59,10 @@ import org.junit.platform.launcher.core.LauncherFactory; import org.junit.platform.launcher.listeners.UniqueIdTrackingListener; +import java.io.BufferedReader; import java.io.IOException; +import java.io.InputStream; +import java.io.InputStreamReader; import java.io.UncheckedIOException; import java.nio.file.Files; import java.nio.file.Path; @@ -99,6 +103,12 @@ public void duringSetup(DuringSetupAccess access) { @Override public void beforeAnalysis(BeforeAnalysisAccess access) { + /* Before GraalVM version 22 we couldn't have classes initialized at run-time + * that are also used at build-time but not added to the image heap */ + if (Runtime.version().feature() <= 21) { + initializeClassesForJDKsBefore21(); + } + List selectors = getSelectors(); registerTestClassesForReflection(selectors); @@ -221,4 +231,15 @@ private static Class findClassOrNull(ClassLoader loader, String className) { return null; } } + + private static void initializeClassesForJDKsBefore21() { + try (InputStream is = JUnitPlatformFeature.class.getResourceAsStream("/initialize-at-buildtime")) { + if (is != null) { + BufferedReader br = new BufferedReader(new InputStreamReader(is)); + br.lines().forEach(RuntimeClassInitialization::initializeAtBuildTime); + } + } catch (IOException e) { + throw new RuntimeException(e); + } + } } diff --git a/common/junit-platform-native/src/main/java/org/graalvm/junit/platform/NativeImageJUnitLauncher.java b/common/junit-platform-native/src/main/java/org/graalvm/junit/platform/NativeImageJUnitLauncher.java index a2bcd997d..5829d6f42 100644 --- a/common/junit-platform-native/src/main/java/org/graalvm/junit/platform/NativeImageJUnitLauncher.java +++ b/common/junit-platform-native/src/main/java/org/graalvm/junit/platform/NativeImageJUnitLauncher.java @@ -201,7 +201,7 @@ private static Path getTestIDsFromDefaultLocations() { System.out.println("[junit-platform-native] WARNING: -djunit.platform.listeners.uid.tracking.output.dir not specified, " + "trying to find test-ids on default Gradle/Maven locations. " + "As this is a fallback mode, it could take a while. " + - "This should only happen if you are running tests executable manually."); + "This should only happen if you are running tests executable manually and you didn't pass uid output directory with -djunit.platform.listeners.uid.tracking.output.dir=."); Path defaultGradleTestIDsLocation = getGradleTestIdsDefaultLocation(); Path defaultMavenTestIDsLocation = getMavenTestIDsDefaultLocation(); diff --git a/common/junit-platform-native/src/main/java/org/graalvm/junit/platform/config/core/PluginConfigProvider.java b/common/junit-platform-native/src/main/java/org/graalvm/junit/platform/config/core/PluginConfigProvider.java index 938fcda3b..b07a7ddaf 100644 --- a/common/junit-platform-native/src/main/java/org/graalvm/junit/platform/config/core/PluginConfigProvider.java +++ b/common/junit-platform-native/src/main/java/org/graalvm/junit/platform/config/core/PluginConfigProvider.java @@ -41,10 +41,6 @@ package org.graalvm.junit.platform.config.core; -import org.graalvm.nativeimage.hosted.RuntimeClassInitialization; - -import java.util.List; - public abstract class PluginConfigProvider { protected ClassLoader applicationClassLoader; @@ -59,10 +55,4 @@ public final void initialize(ClassLoader classLoader, NativeImageConfiguration n applicationClassLoader = classLoader; nativeImageConfigImpl = nic; } - - protected void initializeClassesForOlderJDKs(List packages) { - if (Runtime.version().feature() <= 21) { - packages.forEach(RuntimeClassInitialization::initializeAtBuildTime); - } - } } diff --git a/common/junit-platform-native/src/main/java/org/graalvm/junit/platform/config/jupiter/JupiterConfigProvider.java b/common/junit-platform-native/src/main/java/org/graalvm/junit/platform/config/jupiter/JupiterConfigProvider.java index af5b4b5a7..f666eba56 100644 --- a/common/junit-platform-native/src/main/java/org/graalvm/junit/platform/config/jupiter/JupiterConfigProvider.java +++ b/common/junit-platform-native/src/main/java/org/graalvm/junit/platform/config/jupiter/JupiterConfigProvider.java @@ -65,18 +65,8 @@ public class JupiterConfigProvider extends PluginConfigProvider { - /* Before GraalVM version 22 we couldn't have classes initialized at run-time - that are also used at build-time but not added to the image heap */ - private final List packagesForOldInitializationStrategy = List.of( - "org.junit.jupiter.engine", - "org.junit.jupiter.api", - "org.junit.jupiter.params", - "java.beans.Introspector"); - @Override public void onLoad(NativeImageConfiguration config) { - initializeClassesForOlderJDKs(packagesForOldInitializationStrategy); - /* Provide support for Timeout annotation */ config.registerAllClassMembersForReflection( "org.junit.jupiter.engine.extension.TimeoutExtension$ExecutorResource", diff --git a/common/junit-platform-native/src/main/java/org/graalvm/junit/platform/config/platform/PlatformConfigProvider.java b/common/junit-platform-native/src/main/java/org/graalvm/junit/platform/config/platform/PlatformConfigProvider.java index e5facfe04..9125c5f48 100644 --- a/common/junit-platform-native/src/main/java/org/graalvm/junit/platform/config/platform/PlatformConfigProvider.java +++ b/common/junit-platform-native/src/main/java/org/graalvm/junit/platform/config/platform/PlatformConfigProvider.java @@ -46,22 +46,10 @@ import org.graalvm.nativeimage.hosted.RuntimeSerialization; import org.junit.platform.launcher.TestIdentifier; -import java.util.List; - - public class PlatformConfigProvider extends PluginConfigProvider { - /* Before GraalVM version 22 we couldn't have classes initialized at run-time - that are also used at build-time but not added to the image heap */ - private final List packagesForOldInitializationStrategy = List.of( - "org.junit.platform.engine", - "org.junit.platform.launcher", - "org.junit.platform.commons", - "org.junit.validator.PublicClassValidator"); - @Override public void onLoad(NativeImageConfiguration config) { - initializeClassesForOlderJDKs(packagesForOldInitializationStrategy); RuntimeSerialization.register(TestIdentifier.class.getDeclaredClasses()); } diff --git a/common/junit-platform-native/src/main/java/org/graalvm/junit/platform/config/vintage/VintageConfigProvider.java b/common/junit-platform-native/src/main/java/org/graalvm/junit/platform/config/vintage/VintageConfigProvider.java index 5e9c05b81..9efb39410 100644 --- a/common/junit-platform-native/src/main/java/org/graalvm/junit/platform/config/vintage/VintageConfigProvider.java +++ b/common/junit-platform-native/src/main/java/org/graalvm/junit/platform/config/vintage/VintageConfigProvider.java @@ -48,22 +48,11 @@ import java.lang.annotation.Annotation; import java.lang.reflect.Method; -import java.util.List; public class VintageConfigProvider extends PluginConfigProvider { - /* Before GraalVM version 22 we couldn't have classes initialized at run-time - that are also used at build-time but not added to the image heap */ - private final List packagesForOldInitializationStrategy = List.of( - "org.junit.vintage.engine", - "org.junit.runner", - "org.junit.runners", - "org.junit.internal.runners.rules.RuleMemberValidator"); - @Override public void onLoad(NativeImageConfiguration config) { - initializeClassesForOlderJDKs(packagesForOldInitializationStrategy); - try { RuntimeSerialization.register(Class.forName("org.junit.runner.Result").getDeclaredClasses()); RuntimeReflection.register(Class.forName("org.junit.runner.Description").getDeclaredFields()); diff --git a/common/junit-platform-native/src/main/resources/initialize-at-buildtime b/common/junit-platform-native/src/main/resources/initialize-at-buildtime new file mode 100644 index 000000000..84f8c4254 --- /dev/null +++ b/common/junit-platform-native/src/main/resources/initialize-at-buildtime @@ -0,0 +1,55 @@ +org.junit.jupiter.api.condition.OS +org.junit.jupiter.api.RandomOrdererUtils +org.junit.jupiter.api.MethodOrderer$Random +org.junit.jupiter.api.MethodOrderer$MethodName +org.junit.jupiter.api.MethodOrderer$DisplayName +org.junit.jupiter.params.provider.EnumSource$Mode +org.junit.jupiter.engine.descriptor.DisplayNameUtils +org.junit.jupiter.engine.descriptor.ClassTestDescriptor +org.junit.jupiter.engine.descriptor.JupiterTestDescriptor +org.junit.jupiter.engine.descriptor.JupiterTestDescriptor$1 +org.junit.jupiter.engine.descriptor.MethodBasedTestDescriptor +org.junit.jupiter.engine.descriptor.TestMethodTestDescriptor +org.junit.jupiter.engine.descriptor.ClassBasedTestDescriptor +org.junit.jupiter.engine.descriptor.TestTemplateTestDescriptor +org.junit.jupiter.engine.config.InstantiatingConfigurationParameterConverter +org.junit.jupiter.engine.config.EnumConfigurationParameterConverter +org.junit.jupiter.engine.execution.ConditionEvaluator +org.junit.jupiter.engine.discovery.MethodFinder +org.junit.jupiter.engine.discovery.MethodSelectorResolver +org.junit.jupiter.engine.discovery.MethodSelectorResolver$MethodType +org.junit.jupiter.engine.discovery.MethodSelectorResolver$MethodType$1 +org.junit.jupiter.engine.discovery.MethodSelectorResolver$MethodType$2 +org.junit.jupiter.engine.discovery.MethodSelectorResolver$MethodType$3 +org.junit.jupiter.engine.discovery.DiscoverySelectorResolver +org.junit.jupiter.engine.discovery.AbstractOrderingVisitor +org.junit.jupiter.engine.discovery.ClassOrderingVisitor +org.junit.jupiter.engine.discovery.predicates.IsTestClassWithTests +org.junit.platform.launcher.TestIdentifier +org.junit.platform.launcher.EngineDiscoveryResult +org.junit.platform.launcher.core.EngineFilterer +org.junit.platform.launcher.core.EngineDiscoveryOrchestrator +org.junit.platform.launcher.core.LauncherConfig +org.junit.platform.launcher.core.LauncherConfigurationParameters +org.junit.platform.launcher.listeners.discovery.LauncherDiscoveryListeners$LauncherDiscoveryListenerType +org.junit.platform.engine.UniqueIdFormat +org.junit.platform.engine.SelectorResolutionResult +org.junit.platform.engine.support.discovery.SelectorResolver$Resolution +org.junit.platform.commons.util.ClasspathScanner +org.junit.platform.commons.util.ReflectionUtils +org.junit.platform.commons.util.StringUtils +org.junit.platform.commons.logging.LoggerFactory +org.junit.platform.commons.logging.LoggerFactory$DelegatingLogger +org.junit.validator.PublicClassValidator +org.junit.vintage.engine.discovery.DefensiveAllDefaultPossibilitiesBuilder +org.junit.vintage.engine.discovery.VintageDiscoverer +org.junit.vintage.engine.JUnit4VersionCheck +org.junit.vintage.engine.support.UniqueIdStringifier +org.junit.vintage.engine.support.UniqueIdReader +org.junit.vintage.engine.descriptor.RunnerTestDescriptor +org.junit.runner.Result +org.junit.runner.Description +org.junit.runners.BlockJUnit4ClassRunner +org.junit.internal.runners.rules.RuleMemberValidator +org.junit.runners.JUnit4 +java.beans.Introspector From 3364e05d0277b948e49470f19b88d62da4b92cf9 Mon Sep 17 00:00:00 2001 From: David Nestorovic Date: Thu, 10 Apr 2025 11:58:55 +0200 Subject: [PATCH 19/28] Extract annottaion element fetching into a separate method --- .../config/core/PluginConfigProvider.java | 20 ++++++++++++++++ .../config/vintage/VintageConfigProvider.java | 24 +++++-------------- 2 files changed, 26 insertions(+), 18 deletions(-) diff --git a/common/junit-platform-native/src/main/java/org/graalvm/junit/platform/config/core/PluginConfigProvider.java b/common/junit-platform-native/src/main/java/org/graalvm/junit/platform/config/core/PluginConfigProvider.java index b07a7ddaf..fe183e49a 100644 --- a/common/junit-platform-native/src/main/java/org/graalvm/junit/platform/config/core/PluginConfigProvider.java +++ b/common/junit-platform-native/src/main/java/org/graalvm/junit/platform/config/core/PluginConfigProvider.java @@ -41,6 +41,9 @@ package org.graalvm.junit.platform.config.core; +import java.lang.annotation.Annotation; +import java.lang.reflect.Method; + public abstract class PluginConfigProvider { protected ClassLoader applicationClassLoader; @@ -55,4 +58,21 @@ public final void initialize(ClassLoader classLoader, NativeImageConfiguration n applicationClassLoader = classLoader; nativeImageConfigImpl = nic; } + + @SuppressWarnings("unchecked") + protected final T getAnnotationElementValue(Class annotatedClass, String annotationName, String annotationElementName) { + try { + Class annotation = (Class) applicationClassLoader.loadClass(annotationName); + Method classProvider = annotation.getDeclaredMethod(annotationElementName); + + Annotation classAnnotation = annotatedClass.getAnnotation(annotation); + if (classAnnotation != null) { + return (T) classProvider.invoke(classAnnotation); + } + } catch (ReflectiveOperationException e) { + // intentionally ignored + } + + return null; + } } diff --git a/common/junit-platform-native/src/main/java/org/graalvm/junit/platform/config/vintage/VintageConfigProvider.java b/common/junit-platform-native/src/main/java/org/graalvm/junit/platform/config/vintage/VintageConfigProvider.java index 9efb39410..f4399457f 100644 --- a/common/junit-platform-native/src/main/java/org/graalvm/junit/platform/config/vintage/VintageConfigProvider.java +++ b/common/junit-platform-native/src/main/java/org/graalvm/junit/platform/config/vintage/VintageConfigProvider.java @@ -46,9 +46,6 @@ import org.graalvm.nativeimage.hosted.RuntimeReflection; import org.graalvm.nativeimage.hosted.RuntimeSerialization; -import java.lang.annotation.Annotation; -import java.lang.reflect.Method; - public class VintageConfigProvider extends PluginConfigProvider { @Override @@ -63,23 +60,14 @@ public void onLoad(NativeImageConfiguration config) { @Override public void onTestClassRegistered(Class testClass, NativeImageConfiguration registry) { - registerAnnotationClassesForReflection("org.junit.runner.RunWith", "value", testClass); - registerAnnotationClassesForReflection("org.junit.runners.Parameterized.UseParametersRunnerFactory", "value", testClass); + registerAnnotationClassesForReflection(testClass, "org.junit.runner.RunWith", "value"); + registerAnnotationClassesForReflection(testClass, "org.junit.runners.Parameterized.UseParametersRunnerFactory", "value"); } - @SuppressWarnings("unchecked") - private void registerAnnotationClassesForReflection(String annotationClass, String classProviderMethod, Class testClass) { - try { - Class annotation = (Class) applicationClassLoader.loadClass(annotationClass); - Method classProvider = annotation.getDeclaredMethod(classProviderMethod); - - Annotation classAnnotation = testClass.getAnnotation(annotation); - if (classAnnotation != null) { - Class annotationArgument = (Class) classProvider.invoke(classAnnotation); - nativeImageConfigImpl.registerAllClassMembersForReflection(annotationArgument); - } - } catch (ReflectiveOperationException e) { - // intentionally ignored + private void registerAnnotationClassesForReflection(Class testClass, String annotationName, String annotationElementName) { + Class annotationArgument = getAnnotationElementValue(testClass, annotationName, annotationElementName); + if (annotationArgument != null) { + nativeImageConfigImpl.registerAllClassMembersForReflection(annotationArgument); } } } From c890433a66f9d4ad201efa97e1e452553f1328d0 Mon Sep 17 00:00:00 2001 From: David Nestorovic Date: Thu, 10 Apr 2025 14:13:34 +0200 Subject: [PATCH 20/28] Improve error message and rename function for initializing classes for earlier JDKs --- .../org/graalvm/junit/platform/JUnitPlatformFeature.java | 7 ++++--- 1 file changed, 4 insertions(+), 3 deletions(-) diff --git a/common/junit-platform-native/src/main/java/org/graalvm/junit/platform/JUnitPlatformFeature.java b/common/junit-platform-native/src/main/java/org/graalvm/junit/platform/JUnitPlatformFeature.java index 6dac74c51..8a3ddeecb 100644 --- a/common/junit-platform-native/src/main/java/org/graalvm/junit/platform/JUnitPlatformFeature.java +++ b/common/junit-platform-native/src/main/java/org/graalvm/junit/platform/JUnitPlatformFeature.java @@ -106,7 +106,7 @@ public void beforeAnalysis(BeforeAnalysisAccess access) { /* Before GraalVM version 22 we couldn't have classes initialized at run-time * that are also used at build-time but not added to the image heap */ if (Runtime.version().feature() <= 21) { - initializeClassesForJDKsBefore21(); + initializeClassesForJDK21OrEarlier(); } List selectors = getSelectors(); @@ -232,14 +232,15 @@ private static Class findClassOrNull(ClassLoader loader, String className) { } } - private static void initializeClassesForJDKsBefore21() { + private static void initializeClassesForJDK21OrEarlier() { try (InputStream is = JUnitPlatformFeature.class.getResourceAsStream("/initialize-at-buildtime")) { if (is != null) { BufferedReader br = new BufferedReader(new InputStreamReader(is)); br.lines().forEach(RuntimeClassInitialization::initializeAtBuildTime); + br.close(); } } catch (IOException e) { - throw new RuntimeException(e); + throw new RuntimeException("Failed to process build time initializations for JDK 21 or earlier"); } } } From cae0ced8b0fea25b2c02b42c53444ee2627b0ab5 Mon Sep 17 00:00:00 2001 From: David Nestorovic Date: Thu, 17 Apr 2025 13:29:53 +0200 Subject: [PATCH 21/28] Adjust initialize-at-buildtime list with JUnit list --- .../main/resources/initialize-at-buildtime | 123 +++++++++++++----- 1 file changed, 92 insertions(+), 31 deletions(-) diff --git a/common/junit-platform-native/src/main/resources/initialize-at-buildtime b/common/junit-platform-native/src/main/resources/initialize-at-buildtime index 84f8c4254..aef609c90 100644 --- a/common/junit-platform-native/src/main/resources/initialize-at-buildtime +++ b/common/junit-platform-native/src/main/resources/initialize-at-buildtime @@ -1,55 +1,116 @@ +java.beans.Introspector +org.junit.internal.runners.rules.RuleMemberValidator org.junit.jupiter.api.condition.OS -org.junit.jupiter.api.RandomOrdererUtils -org.junit.jupiter.api.MethodOrderer$Random -org.junit.jupiter.api.MethodOrderer$MethodName +org.junit.jupiter.api.DisplayNameGenerator$IndicativeSentences +org.junit.jupiter.api.DisplayNameGenerator$Standard +org.junit.jupiter.api.extension.ConditionEvaluationResult org.junit.jupiter.api.MethodOrderer$DisplayName -org.junit.jupiter.params.provider.EnumSource$Mode -org.junit.jupiter.engine.descriptor.DisplayNameUtils +org.junit.jupiter.api.MethodOrderer$MethodName +org.junit.jupiter.api.MethodOrderer$Random +org.junit.jupiter.api.RandomOrdererUtils +org.junit.jupiter.api.TestInstance$Lifecycle +org.junit.jupiter.engine.config.CachingJupiterConfiguration +org.junit.jupiter.engine.config.DefaultJupiterConfiguration +org.junit.jupiter.engine.config.EnumConfigurationParameterConverter +org.junit.jupiter.engine.config.InstantiatingConfigurationParameterConverter +org.junit.jupiter.engine.descriptor.ClassBasedTestDescriptor +org.junit.jupiter.engine.descriptor.ClassBasedTestDescriptor$ClassInfo +org.junit.jupiter.engine.descriptor.ClassBasedTestDescriptor$LifecycleMethods +org.junit.jupiter.engine.descriptor.ClassTemplateInvocationTestDescriptor +org.junit.jupiter.engine.descriptor.ClassTemplateTestDescriptor org.junit.jupiter.engine.descriptor.ClassTestDescriptor +org.junit.jupiter.engine.descriptor.DisplayNameUtils +org.junit.jupiter.engine.descriptor.DynamicDescendantFilter +org.junit.jupiter.engine.descriptor.DynamicDescendantFilter$Mode +org.junit.jupiter.engine.descriptor.ExclusiveResourceCollector$1 +org.junit.jupiter.engine.descriptor.JupiterEngineDescriptor org.junit.jupiter.engine.descriptor.JupiterTestDescriptor org.junit.jupiter.engine.descriptor.JupiterTestDescriptor$1 org.junit.jupiter.engine.descriptor.MethodBasedTestDescriptor +org.junit.jupiter.engine.descriptor.MethodBasedTestDescriptor$MethodInfo +org.junit.jupiter.engine.descriptor.NestedClassTestDescriptor +org.junit.jupiter.engine.descriptor.TestFactoryTestDescriptor org.junit.jupiter.engine.descriptor.TestMethodTestDescriptor -org.junit.jupiter.engine.descriptor.ClassBasedTestDescriptor org.junit.jupiter.engine.descriptor.TestTemplateTestDescriptor -org.junit.jupiter.engine.config.InstantiatingConfigurationParameterConverter -org.junit.jupiter.engine.config.EnumConfigurationParameterConverter -org.junit.jupiter.engine.execution.ConditionEvaluator +org.junit.jupiter.engine.discovery.AbstractOrderingVisitor +org.junit.jupiter.engine.discovery.ClassOrderingVisitor +org.junit.jupiter.engine.discovery.ClassSelectorResolver +org.junit.jupiter.engine.discovery.ClassSelectorResolver$DummyClassTemplateInvocationContext +org.junit.jupiter.engine.discovery.DiscoverySelectorResolver org.junit.jupiter.engine.discovery.MethodFinder org.junit.jupiter.engine.discovery.MethodSelectorResolver org.junit.jupiter.engine.discovery.MethodSelectorResolver$MethodType org.junit.jupiter.engine.discovery.MethodSelectorResolver$MethodType$1 org.junit.jupiter.engine.discovery.MethodSelectorResolver$MethodType$2 org.junit.jupiter.engine.discovery.MethodSelectorResolver$MethodType$3 -org.junit.jupiter.engine.discovery.DiscoverySelectorResolver -org.junit.jupiter.engine.discovery.AbstractOrderingVisitor -org.junit.jupiter.engine.discovery.ClassOrderingVisitor org.junit.jupiter.engine.discovery.predicates.IsTestClassWithTests -org.junit.platform.launcher.TestIdentifier -org.junit.platform.launcher.EngineDiscoveryResult -org.junit.platform.launcher.core.EngineFilterer +org.junit.jupiter.engine.execution.ConditionEvaluator +org.junit.jupiter.engine.execution.InterceptingExecutableInvoker +org.junit.jupiter.engine.execution.InterceptingExecutableInvoker$ReflectiveInterceptorCall +org.junit.jupiter.engine.execution.InterceptingExecutableInvoker$ReflectiveInterceptorCall$VoidMethodInterceptorCall +org.junit.jupiter.engine.execution.InvocationInterceptorChain +org.junit.jupiter.engine.JupiterTestEngine +org.junit.jupiter.params.provider.EnumSource$Mode +org.junit.jupiter.params.provider.EnumSource$Mode$Validator +org.junit.platform.commons.logging.LoggerFactory +org.junit.platform.commons.logging.LoggerFactory$DelegatingLogger +org.junit.platform.commons.support.scanning.DefaultClasspathScanner +org.junit.platform.commons.util.ClasspathScanner +org.junit.platform.commons.util.LruCache +org.junit.platform.commons.util.ReflectionUtils +org.junit.platform.commons.util.StringUtils +org.junit.platform.engine.SelectorResolutionResult +org.junit.platform.engine.support.descriptor.ClassSource +org.junit.platform.engine.support.descriptor.MethodSource +org.junit.platform.engine.support.discovery.SelectorResolver$Resolution +org.junit.platform.engine.support.hierarchical.Node$ExecutionMode +org.junit.platform.engine.support.store.NamespacedHierarchicalStore +org.junit.platform.engine.support.store.NamespacedHierarchicalStore$EvaluatedValue +org.junit.platform.engine.TestDescriptor$Type +org.junit.platform.engine.UniqueId +org.junit.platform.engine.UniqueId$Segment +org.junit.platform.engine.UniqueIdFormat +org.junit.platform.launcher.core.DefaultLauncher +org.junit.platform.launcher.core.DefaultLauncherConfig +org.junit.platform.launcher.core.DiscoveryIssueNotifier org.junit.platform.launcher.core.EngineDiscoveryOrchestrator +org.junit.platform.launcher.core.EngineExecutionOrchestrator +org.junit.platform.launcher.core.EngineFilterer +org.junit.platform.launcher.core.HierarchicalOutputDirectoryProvider +org.junit.platform.launcher.core.InternalTestPlan org.junit.platform.launcher.core.LauncherConfig org.junit.platform.launcher.core.LauncherConfigurationParameters +org.junit.platform.launcher.core.LauncherConfigurationParameters$ParameterProvider$1 +org.junit.platform.launcher.core.LauncherConfigurationParameters$ParameterProvider$2 +org.junit.platform.launcher.core.LauncherConfigurationParameters$ParameterProvider$3 +org.junit.platform.launcher.core.LauncherConfigurationParameters$ParameterProvider$4 +org.junit.platform.launcher.core.LauncherDiscoveryResult +org.junit.platform.launcher.core.LauncherDiscoveryResult$EngineResultInfo +org.junit.platform.launcher.core.LauncherListenerRegistry +org.junit.platform.launcher.core.ListenerRegistry +org.junit.platform.launcher.core.SessionPerRequestLauncher +org.junit.platform.launcher.EngineDiscoveryResult +org.junit.platform.launcher.LauncherSessionListener$1 org.junit.platform.launcher.listeners.discovery.LauncherDiscoveryListeners$LauncherDiscoveryListenerType -org.junit.platform.engine.UniqueIdFormat -org.junit.platform.engine.SelectorResolutionResult -org.junit.platform.engine.support.discovery.SelectorResolver$Resolution -org.junit.platform.commons.util.ClasspathScanner -org.junit.platform.commons.util.ReflectionUtils -org.junit.platform.commons.util.StringUtils -org.junit.platform.commons.logging.LoggerFactory -org.junit.platform.commons.logging.LoggerFactory$DelegatingLogger +org.junit.platform.launcher.listeners.UniqueIdTrackingListener +org.junit.platform.launcher.TestIdentifier +org.junit.platform.reporting.open.xml.OpenTestReportGeneratingListener +org.junit.platform.reporting.shadow.org.opentest4j.reporting.events.api.DocumentWriter$1 +org.junit.platform.suite.engine.SuiteEngineDescriptor +org.junit.platform.suite.engine.SuiteLauncher +org.junit.platform.suite.engine.SuiteTestDescriptor +org.junit.platform.suite.engine.SuiteTestDescriptor$LifecycleMethods +org.junit.platform.suite.engine.SuiteTestEngine +org.junit.runner.Description +org.junit.runner.Result +org.junit.runners.BlockJUnit4ClassRunner +org.junit.runners.JUnit4 org.junit.validator.PublicClassValidator +org.junit.vintage.engine.descriptor.RunnerTestDescriptor +org.junit.vintage.engine.descriptor.VintageEngineDescriptor org.junit.vintage.engine.discovery.DefensiveAllDefaultPossibilitiesBuilder org.junit.vintage.engine.discovery.VintageDiscoverer org.junit.vintage.engine.JUnit4VersionCheck -org.junit.vintage.engine.support.UniqueIdStringifier org.junit.vintage.engine.support.UniqueIdReader -org.junit.vintage.engine.descriptor.RunnerTestDescriptor -org.junit.runner.Result -org.junit.runner.Description -org.junit.runners.BlockJUnit4ClassRunner -org.junit.internal.runners.rules.RuleMemberValidator -org.junit.runners.JUnit4 -java.beans.Introspector +org.junit.vintage.engine.support.UniqueIdStringifier +org.junit.vintage.engine.VintageTestEngine From c3979f406c25fc1807eeb0433098f4ae99ab080a Mon Sep 17 00:00:00 2001 From: David Nestorovic Date: Wed, 23 Apr 2025 01:15:10 +0200 Subject: [PATCH 22/28] Add exclude config for JUnit versions that provide initialize-at-buildtime --- .../gradle/native-image-testing.gradle | 15 +++++++++++++++ .../resources/extra-build-args/exclude-config | 1 + .../src/main/resources/initialize-at-buildtime | 3 +++ 3 files changed, 19 insertions(+) create mode 100644 common/junit-platform-native/src/main/resources/extra-build-args/exclude-config diff --git a/common/junit-platform-native/gradle/native-image-testing.gradle b/common/junit-platform-native/gradle/native-image-testing.gradle index cef08233e..3efba2ea6 100644 --- a/common/junit-platform-native/gradle/native-image-testing.gradle +++ b/common/junit-platform-native/gradle/native-image-testing.gradle @@ -91,6 +91,17 @@ abstract class NativeTestArgumentProvider implements CommandLineArgumentProvider @Input abstract Property getDiscovery() + @InputDirectory + abstract DirectoryProperty getExcludeConfigDir() + + List addExcludeConfig() { + String[] jarPatternPair = getExcludeConfigDir().get().file("exclude-config").getAsFile().readLines().get(0).split(":") + var jar = jarPatternPair[0] + var pattern = jarPatternPair[1] + + return ["--exclude-config", jar, pattern] + } + @Override Iterable asArguments() { def args = [ @@ -100,6 +111,9 @@ abstract class NativeTestArgumentProvider implements CommandLineArgumentProvider "-o", "native-image-tests", "-Djunit.platform.listeners.uid.tracking.output.dir=${testIdsDir.get().asFile.absolutePath}" ] + + args.addAll(addExcludeConfig()) + if (agentOutputDir.isPresent()) { def outputDir = agentOutputDir.get().asFile if (!outputDir.exists()) { @@ -135,6 +149,7 @@ tasks.register("nativeTestCompile", Exec) { def argsProvider = objects.newInstance(NativeTestArgumentProvider) argsProvider.classpath.from(test.classpath) argsProvider.testIdsDir.set(testIdsDir) + argsProvider.excludeConfigDir.set(layout.buildDirectory.dir("resources").get().dir("main").dir("extra-build-args")) argsProvider.agentOutputDir.set(agentOutput) argsProvider.discovery.set(providers.systemProperty("testDiscovery").map(v -> Boolean.valueOf(v)).orElse(false)) argumentProviders.add(argsProvider) diff --git a/common/junit-platform-native/src/main/resources/extra-build-args/exclude-config b/common/junit-platform-native/src/main/resources/extra-build-args/exclude-config new file mode 100644 index 000000000..d44922d0c --- /dev/null +++ b/common/junit-platform-native/src/main/resources/extra-build-args/exclude-config @@ -0,0 +1 @@ +.*.jar:META-INF\/native-image\/org.junit.*.properties.* diff --git a/common/junit-platform-native/src/main/resources/initialize-at-buildtime b/common/junit-platform-native/src/main/resources/initialize-at-buildtime index aef609c90..912ada016 100644 --- a/common/junit-platform-native/src/main/resources/initialize-at-buildtime +++ b/common/junit-platform-native/src/main/resources/initialize-at-buildtime @@ -33,6 +33,7 @@ org.junit.jupiter.engine.descriptor.TestFactoryTestDescriptor org.junit.jupiter.engine.descriptor.TestMethodTestDescriptor org.junit.jupiter.engine.descriptor.TestTemplateTestDescriptor org.junit.jupiter.engine.discovery.AbstractOrderingVisitor +org.junit.jupiter.engine.discovery.AbstractOrderingVisitor$DescriptorWrapperOrderer org.junit.jupiter.engine.discovery.ClassOrderingVisitor org.junit.jupiter.engine.discovery.ClassSelectorResolver org.junit.jupiter.engine.discovery.ClassSelectorResolver$DummyClassTemplateInvocationContext @@ -44,6 +45,7 @@ org.junit.jupiter.engine.discovery.MethodSelectorResolver$MethodType$1 org.junit.jupiter.engine.discovery.MethodSelectorResolver$MethodType$2 org.junit.jupiter.engine.discovery.MethodSelectorResolver$MethodType$3 org.junit.jupiter.engine.discovery.predicates.IsTestClassWithTests +org.junit.jupiter.engine.discovery.predicates.IsTestFactoryMethod org.junit.jupiter.engine.execution.ConditionEvaluator org.junit.jupiter.engine.execution.InterceptingExecutableInvoker org.junit.jupiter.engine.execution.InterceptingExecutableInvoker$ReflectiveInterceptorCall @@ -72,6 +74,7 @@ org.junit.platform.engine.UniqueId$Segment org.junit.platform.engine.UniqueIdFormat org.junit.platform.launcher.core.DefaultLauncher org.junit.platform.launcher.core.DefaultLauncherConfig +org.junit.platform.launcher.core.DiscoveryIssueCollector org.junit.platform.launcher.core.DiscoveryIssueNotifier org.junit.platform.launcher.core.EngineDiscoveryOrchestrator org.junit.platform.launcher.core.EngineExecutionOrchestrator From 7f516032369b4d09bdfe6194d92776e5ef33d145 Mon Sep 17 00:00:00 2001 From: David Nestorovic Date: Wed, 23 Apr 2025 01:44:34 +0200 Subject: [PATCH 23/28] Add exclude config in both Gradle and Maven --- .../gradle/native-image-testing.gradle | 2 +- .../resources/extra-build-args/exclude-config | 2 +- .../junit/jupiter/MethodSourceTests.java | 9 +-------- .../graalvm/buildtools/utils/JUnitUtils.java | 17 +++++++++++++++++ gradle/libs.versions.toml | 4 ++-- .../buildtools/gradle/NativeImagePlugin.java | 4 ++++ .../buildtools/maven/NativeTestMojo.java | 6 ++++++ 7 files changed, 32 insertions(+), 12 deletions(-) create mode 100644 common/utils/src/main/java/org/graalvm/buildtools/utils/JUnitUtils.java diff --git a/common/junit-platform-native/gradle/native-image-testing.gradle b/common/junit-platform-native/gradle/native-image-testing.gradle index 3efba2ea6..40391030f 100644 --- a/common/junit-platform-native/gradle/native-image-testing.gradle +++ b/common/junit-platform-native/gradle/native-image-testing.gradle @@ -95,7 +95,7 @@ abstract class NativeTestArgumentProvider implements CommandLineArgumentProvider abstract DirectoryProperty getExcludeConfigDir() List addExcludeConfig() { - String[] jarPatternPair = getExcludeConfigDir().get().file("exclude-config").getAsFile().readLines().get(0).split(":") + String[] jarPatternPair = getExcludeConfigDir().get().file("exclude-config").getAsFile().readLines().get(0).split(",") var jar = jarPatternPair[0] var pattern = jarPatternPair[1] diff --git a/common/junit-platform-native/src/main/resources/extra-build-args/exclude-config b/common/junit-platform-native/src/main/resources/extra-build-args/exclude-config index d44922d0c..ceae5d2e3 100644 --- a/common/junit-platform-native/src/main/resources/extra-build-args/exclude-config +++ b/common/junit-platform-native/src/main/resources/extra-build-args/exclude-config @@ -1 +1 @@ -.*.jar:META-INF\/native-image\/org.junit.*.properties.* +.*.jar,META-INF\/native-image\/org.junit.*.properties.* diff --git a/common/junit-platform-native/src/test/java/org/graalvm/junit/jupiter/MethodSourceTests.java b/common/junit-platform-native/src/test/java/org/graalvm/junit/jupiter/MethodSourceTests.java index 0970ed69a..626bfb617 100644 --- a/common/junit-platform-native/src/test/java/org/graalvm/junit/jupiter/MethodSourceTests.java +++ b/common/junit-platform-native/src/test/java/org/graalvm/junit/jupiter/MethodSourceTests.java @@ -60,6 +60,7 @@ public abstract static class ArgumentTestBase { @BeforeAll public static void setup() { + expectedArgs.clear(); actualArgs.clear(); } @@ -78,17 +79,12 @@ protected static void addExpectedArgs(int a, int b) { protected static void traceArgs(int a, int b) { actualArgs.add(new int[]{a, b}); } - - protected static void clearPreviousExpectedArgs() { - expectedArgs.clear(); - } } public static class EmptyMethodSourceTests extends ArgumentTestBase { @BeforeAll public static void setup() { - clearPreviousExpectedArgs(); addExpectedArgs(1, 5); addExpectedArgs(7, 12); } @@ -111,7 +107,6 @@ public static class SameClassMethodSourceTests extends ArgumentTestBase { @BeforeAll public static void setup() { - clearPreviousExpectedArgs(); addExpectedArgs(31, 32); addExpectedArgs(1, 3); } @@ -134,7 +129,6 @@ public static class OtherClassMethodSourceTests extends ArgumentTestBase { @BeforeAll public static void setup() { - clearPreviousExpectedArgs(); addExpectedArgs(33, 35); addExpectedArgs(99, 1); } @@ -150,7 +144,6 @@ public static class CombinedMethodSourceTests extends ArgumentTestBase { @BeforeAll public static void setup() { - clearPreviousExpectedArgs(); addExpectedArgs(33, 35); addExpectedArgs(99, 1); addExpectedArgs(31, 32); diff --git a/common/utils/src/main/java/org/graalvm/buildtools/utils/JUnitUtils.java b/common/utils/src/main/java/org/graalvm/buildtools/utils/JUnitUtils.java new file mode 100644 index 000000000..e8dd7cf70 --- /dev/null +++ b/common/utils/src/main/java/org/graalvm/buildtools/utils/JUnitUtils.java @@ -0,0 +1,17 @@ +package org.graalvm.buildtools.utils; + +import java.util.ArrayList; +import java.util.List; + +public final class JUnitUtils { + + public static List excludeJUnitClassInitializationFiles() { + List args = new ArrayList<>(); + args.add("--exclude-config"); + args.add(".*.jar"); + args.add("META-INF\\/native-image\\/org.junit.*.properties.*"); + + return args; + } + +} diff --git a/gradle/libs.versions.toml b/gradle/libs.versions.toml index aa952608a..8e6c4be4d 100644 --- a/gradle/libs.versions.toml +++ b/gradle/libs.versions.toml @@ -11,8 +11,8 @@ mavenEmbedder = "3.9.9" mavenResolver = "1.9.22" graalvm = "23.0.2" openjson = "1.0.13" -junitPlatform = "1.10.0" -junitJupiter = "5.11.0" +junitPlatform = "1.12.0" +junitJupiter = "5.12.0" slf4j = "1.7.9" groovy = "3.0.11" jetty = "11.0.11" diff --git a/native-gradle-plugin/src/main/java/org/graalvm/buildtools/gradle/NativeImagePlugin.java b/native-gradle-plugin/src/main/java/org/graalvm/buildtools/gradle/NativeImagePlugin.java index e8d09a54d..7ba724b74 100644 --- a/native-gradle-plugin/src/main/java/org/graalvm/buildtools/gradle/NativeImagePlugin.java +++ b/native-gradle-plugin/src/main/java/org/graalvm/buildtools/gradle/NativeImagePlugin.java @@ -64,6 +64,7 @@ import org.graalvm.buildtools.gradle.tasks.NativeRunTask; import org.graalvm.buildtools.gradle.tasks.actions.CleanupAgentFilesAction; import org.graalvm.buildtools.gradle.tasks.actions.MergeAgentFilesAction; +import org.graalvm.buildtools.utils.JUnitUtils; import org.graalvm.buildtools.utils.SharedConstants; import org.graalvm.reachability.DirectoryConfiguration; import org.gradle.api.Action; @@ -775,6 +776,9 @@ private static NativeImageOptions createTestOptions(GraalVMExtension graalExtens classpath.from(sourceSet.getOutput().getClassesDirs()); classpath.from(sourceSet.getOutput().getResourcesDir()); + /* in version 5.12.0 JUnit added initialize-at-builditime properties files which we need to exclude */ + testExtension.getBuildArgs().addAll(JUnitUtils.excludeJUnitClassInitializationFiles()); + return testExtension; } diff --git a/native-maven-plugin/src/main/java/org/graalvm/buildtools/maven/NativeTestMojo.java b/native-maven-plugin/src/main/java/org/graalvm/buildtools/maven/NativeTestMojo.java index a201c96f4..2177413e2 100644 --- a/native-maven-plugin/src/main/java/org/graalvm/buildtools/maven/NativeTestMojo.java +++ b/native-maven-plugin/src/main/java/org/graalvm/buildtools/maven/NativeTestMojo.java @@ -60,6 +60,8 @@ import org.eclipse.aether.resolution.DependencyRequest; import org.eclipse.aether.resolution.DependencyResolutionException; import org.eclipse.aether.resolution.DependencyResult; +import org.graalvm.buildtools.utils.FileUtils; +import org.graalvm.buildtools.utils.JUnitUtils; import org.graalvm.buildtools.utils.NativeImageConfigurationUtils; import java.io.IOException; @@ -74,6 +76,7 @@ import java.util.HashSet; import java.util.List; import java.util.Set; +import java.util.regex.Pattern; import java.util.stream.Collectors; import java.util.stream.Stream; @@ -157,6 +160,9 @@ public void execute() throws MojoExecutionException { configureEnvironment(); buildArgs.add("--features=org.graalvm.junit.platform.JUnitPlatformFeature"); + /* in version 5.12.0 JUnit added initialize-at-builditime properties files which we need to exclude */ + buildArgs.addAll(JUnitUtils.excludeJUnitClassInitializationFiles()); + if (systemProperties == null) { systemProperties = new HashMap<>(); } From 04198fced053bb03ca964888330110a1a86e7e89 Mon Sep 17 00:00:00 2001 From: David Nestorovic Date: Wed, 23 Apr 2025 11:30:11 +0200 Subject: [PATCH 24/28] Use JUnit 5.11.0 version --- gradle/libs.versions.toml | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/gradle/libs.versions.toml b/gradle/libs.versions.toml index 8e6c4be4d..80e42d905 100644 --- a/gradle/libs.versions.toml +++ b/gradle/libs.versions.toml @@ -11,8 +11,8 @@ mavenEmbedder = "3.9.9" mavenResolver = "1.9.22" graalvm = "23.0.2" openjson = "1.0.13" -junitPlatform = "1.12.0" -junitJupiter = "5.12.0" +junitPlatform = "1.11.0" +junitJupiter = "5.11.0" slf4j = "1.7.9" groovy = "3.0.11" jetty = "11.0.11" From 6f9748e9ce411ebf00b24b0dbb78c48d89073585 Mon Sep 17 00:00:00 2001 From: David Nestorovic Date: Thu, 24 Apr 2025 14:23:22 +0200 Subject: [PATCH 25/28] Use try with resources to properly close BufferedReader --- .../org/graalvm/junit/platform/JUnitPlatformFeature.java | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) diff --git a/common/junit-platform-native/src/main/java/org/graalvm/junit/platform/JUnitPlatformFeature.java b/common/junit-platform-native/src/main/java/org/graalvm/junit/platform/JUnitPlatformFeature.java index 8a3ddeecb..8c3576510 100644 --- a/common/junit-platform-native/src/main/java/org/graalvm/junit/platform/JUnitPlatformFeature.java +++ b/common/junit-platform-native/src/main/java/org/graalvm/junit/platform/JUnitPlatformFeature.java @@ -235,9 +235,9 @@ private static Class findClassOrNull(ClassLoader loader, String className) { private static void initializeClassesForJDK21OrEarlier() { try (InputStream is = JUnitPlatformFeature.class.getResourceAsStream("/initialize-at-buildtime")) { if (is != null) { - BufferedReader br = new BufferedReader(new InputStreamReader(is)); - br.lines().forEach(RuntimeClassInitialization::initializeAtBuildTime); - br.close(); + try (BufferedReader br = new BufferedReader(new InputStreamReader(is))) { + br.lines().forEach(RuntimeClassInitialization::initializeAtBuildTime); + } } } catch (IOException e) { throw new RuntimeException("Failed to process build time initializations for JDK 21 or earlier"); From 847ef4870583340b777edb2e47d5676ca486fbda Mon Sep 17 00:00:00 2001 From: David Nestorovic Date: Mon, 28 Apr 2025 12:11:39 +0200 Subject: [PATCH 26/28] Register all class members of inner classes for reflection --- common/graalvm-reachability-metadata/build.gradle.kts | 1 + common/junit-platform-native/build.gradle | 1 + .../gradle/native-image-testing.gradle | 2 +- .../org/graalvm/junit/platform/JUnitPlatformFeature.java | 7 ++++++- .../platform/config/core/NativeImageConfiguration.java | 5 ----- .../org/graalvm/buildtools/gradle/NativeImagePlugin.java | 2 +- .../java/org/graalvm/buildtools/maven/NativeTestMojo.java | 2 +- 7 files changed, 11 insertions(+), 9 deletions(-) diff --git a/common/graalvm-reachability-metadata/build.gradle.kts b/common/graalvm-reachability-metadata/build.gradle.kts index a55de5b36..994d6dca6 100644 --- a/common/graalvm-reachability-metadata/build.gradle.kts +++ b/common/graalvm-reachability-metadata/build.gradle.kts @@ -54,6 +54,7 @@ dependencies { implementation(libs.openjson) testImplementation(platform(libs.test.junit.bom)) testImplementation(libs.test.junit.jupiter.core) + testRuntimeOnly(libs.test.junit.platform.launcher) } tasks.withType().configureEach { diff --git a/common/junit-platform-native/build.gradle b/common/junit-platform-native/build.gradle index 3c47d325c..6231a8486 100644 --- a/common/junit-platform-native/build.gradle +++ b/common/junit-platform-native/build.gradle @@ -57,6 +57,7 @@ dependencies { implementation libs.test.junit.platform.launcher implementation libs.test.junit.jupiter.core testImplementation libs.test.junit.vintage + testRuntimeOnly libs.test.junit.platform.launcher } apply from: "gradle/native-image-testing.gradle" diff --git a/common/junit-platform-native/gradle/native-image-testing.gradle b/common/junit-platform-native/gradle/native-image-testing.gradle index 40391030f..814daef49 100644 --- a/common/junit-platform-native/gradle/native-image-testing.gradle +++ b/common/junit-platform-native/gradle/native-image-testing.gradle @@ -94,7 +94,7 @@ abstract class NativeTestArgumentProvider implements CommandLineArgumentProvider @InputDirectory abstract DirectoryProperty getExcludeConfigDir() - List addExcludeConfig() { + private List addExcludeConfig() { String[] jarPatternPair = getExcludeConfigDir().get().file("exclude-config").getAsFile().readLines().get(0).split(",") var jar = jarPatternPair[0] var pattern = jarPatternPair[1] diff --git a/common/junit-platform-native/src/main/java/org/graalvm/junit/platform/JUnitPlatformFeature.java b/common/junit-platform-native/src/main/java/org/graalvm/junit/platform/JUnitPlatformFeature.java index 8c3576510..6eb8efa55 100644 --- a/common/junit-platform-native/src/main/java/org/graalvm/junit/platform/JUnitPlatformFeature.java +++ b/common/junit-platform-native/src/main/java/org/graalvm/junit/platform/JUnitPlatformFeature.java @@ -92,7 +92,7 @@ public void afterRegistration(AfterRegistrationAccess access) { extensionConfigProviders.forEach(p -> p.initialize(access.getApplicationClassLoader(), nativeImageConfigImpl)); } - public static boolean debug() { + private static boolean debug() { return ImageSingletons.lookup(JUnitPlatformFeature.class).debug; } @@ -173,6 +173,11 @@ private void registerTestClassForReflection(Class clazz) { nativeImageConfigImpl.registerAllClassMembersForReflection(clazz); forEachProvider(p -> p.onTestClassRegistered(clazz, nativeImageConfigImpl)); + Class[] declaredClasses = clazz.getDeclaredClasses(); + for (Class declaredClass : declaredClasses) { + registerTestClassForReflection(declaredClass); + } + Class[] interfaces = clazz.getInterfaces(); for (Class inter : interfaces) { registerTestClassForReflection(inter); diff --git a/common/junit-platform-native/src/main/java/org/graalvm/junit/platform/config/core/NativeImageConfiguration.java b/common/junit-platform-native/src/main/java/org/graalvm/junit/platform/config/core/NativeImageConfiguration.java index 361388f8b..96b9ec089 100644 --- a/common/junit-platform-native/src/main/java/org/graalvm/junit/platform/config/core/NativeImageConfiguration.java +++ b/common/junit-platform-native/src/main/java/org/graalvm/junit/platform/config/core/NativeImageConfiguration.java @@ -66,11 +66,6 @@ default void registerAllClassMembersForReflection(Class... classes) { registerForReflection(clazz.getDeclaredConstructors()); registerForReflection(clazz.getDeclaredMethods()); registerForReflection(clazz.getDeclaredFields()); - - Class[] declaredClasses = clazz.getDeclaredClasses(); - for (Class cls : declaredClasses) { - registerAllClassMembersForReflection(cls); - } } } } diff --git a/native-gradle-plugin/src/main/java/org/graalvm/buildtools/gradle/NativeImagePlugin.java b/native-gradle-plugin/src/main/java/org/graalvm/buildtools/gradle/NativeImagePlugin.java index 7ba724b74..e0647eab9 100644 --- a/native-gradle-plugin/src/main/java/org/graalvm/buildtools/gradle/NativeImagePlugin.java +++ b/native-gradle-plugin/src/main/java/org/graalvm/buildtools/gradle/NativeImagePlugin.java @@ -776,7 +776,7 @@ private static NativeImageOptions createTestOptions(GraalVMExtension graalExtens classpath.from(sourceSet.getOutput().getClassesDirs()); classpath.from(sourceSet.getOutput().getResourcesDir()); - /* in version 5.12.0 JUnit added initialize-at-builditime properties files which we need to exclude */ + /* in version 5.12.0 JUnit added initialize-at-build-time properties files which we need to exclude */ testExtension.getBuildArgs().addAll(JUnitUtils.excludeJUnitClassInitializationFiles()); return testExtension; diff --git a/native-maven-plugin/src/main/java/org/graalvm/buildtools/maven/NativeTestMojo.java b/native-maven-plugin/src/main/java/org/graalvm/buildtools/maven/NativeTestMojo.java index 2177413e2..f6006b71e 100644 --- a/native-maven-plugin/src/main/java/org/graalvm/buildtools/maven/NativeTestMojo.java +++ b/native-maven-plugin/src/main/java/org/graalvm/buildtools/maven/NativeTestMojo.java @@ -160,7 +160,7 @@ public void execute() throws MojoExecutionException { configureEnvironment(); buildArgs.add("--features=org.graalvm.junit.platform.JUnitPlatformFeature"); - /* in version 5.12.0 JUnit added initialize-at-builditime properties files which we need to exclude */ + /* in version 5.12.0 JUnit added initialize-at-build-time properties files which we need to exclude */ buildArgs.addAll(JUnitUtils.excludeJUnitClassInitializationFiles()); if (systemProperties == null) { From 2c2ec24b23b0f5e8326f963974902ecbb66f0ba3 Mon Sep 17 00:00:00 2001 From: David Nestorovic Date: Mon, 28 Apr 2025 15:24:41 +0200 Subject: [PATCH 27/28] Remove unused java.beans.Introspector entry form initialize-at-buildtime list --- .../src/main/resources/initialize-at-buildtime | 1 - 1 file changed, 1 deletion(-) diff --git a/common/junit-platform-native/src/main/resources/initialize-at-buildtime b/common/junit-platform-native/src/main/resources/initialize-at-buildtime index 912ada016..052ceb5f8 100644 --- a/common/junit-platform-native/src/main/resources/initialize-at-buildtime +++ b/common/junit-platform-native/src/main/resources/initialize-at-buildtime @@ -1,4 +1,3 @@ -java.beans.Introspector org.junit.internal.runners.rules.RuleMemberValidator org.junit.jupiter.api.condition.OS org.junit.jupiter.api.DisplayNameGenerator$IndicativeSentences From 5b8d5fe7836609b26e6771f7b1b281d77f385459 Mon Sep 17 00:00:00 2001 From: David Nestorovic Date: Wed, 30 Apr 2025 12:59:29 +0200 Subject: [PATCH 28/28] Avoid infinite loops when registering test classes for reflection --- .../junit/platform/JUnitPlatformFeature.java | 23 ++++++++++++++++ .../jupiter/AbstractParentClassTests.java | 27 +++++++++++++++++++ 2 files changed, 50 insertions(+) diff --git a/common/junit-platform-native/src/main/java/org/graalvm/junit/platform/JUnitPlatformFeature.java b/common/junit-platform-native/src/main/java/org/graalvm/junit/platform/JUnitPlatformFeature.java index 6eb8efa55..5f22314ff 100644 --- a/common/junit-platform-native/src/main/java/org/graalvm/junit/platform/JUnitPlatformFeature.java +++ b/common/junit-platform-native/src/main/java/org/graalvm/junit/platform/JUnitPlatformFeature.java @@ -66,9 +66,11 @@ import java.io.UncheckedIOException; import java.nio.file.Files; import java.nio.file.Path; +import java.util.HashSet; import java.util.List; import java.util.Optional; import java.util.ServiceLoader; +import java.util.Set; import java.util.function.BiConsumer; import java.util.function.Consumer; import java.util.stream.Collectors; @@ -168,7 +170,28 @@ private void registerTestClassesForReflection(List .forEach(this::registerTestClassForReflection); } + private final Set> registeredClasses = new HashSet<>(); + + private boolean shouldRegisterClass(Class clazz) { + /* avoid registering java internal classes */ + if (ModuleLayer.boot().modules().contains(clazz.getModule())) { + return false; + } + + /* avoid loops (possible case: class B is inner class of A, and B extends A) */ + if (registeredClasses.contains(clazz)) { + return false; + } + registeredClasses.add(clazz); + + return true; + } + private void registerTestClassForReflection(Class clazz) { + if (!shouldRegisterClass(clazz)) { + return; + } + debug("Registering test class for reflection: %s", clazz.getName()); nativeImageConfigImpl.registerAllClassMembersForReflection(clazz); forEachProvider(p -> p.onTestClassRegistered(clazz, nativeImageConfigImpl)); diff --git a/common/junit-platform-native/src/test/java/org/graalvm/junit/jupiter/AbstractParentClassTests.java b/common/junit-platform-native/src/test/java/org/graalvm/junit/jupiter/AbstractParentClassTests.java index 6a90af460..73620a497 100644 --- a/common/junit-platform-native/src/test/java/org/graalvm/junit/jupiter/AbstractParentClassTests.java +++ b/common/junit-platform-native/src/test/java/org/graalvm/junit/jupiter/AbstractParentClassTests.java @@ -43,6 +43,7 @@ import org.junit.jupiter.api.Assertions; import org.junit.jupiter.api.BeforeAll; +import org.junit.jupiter.api.Test; import org.junit.jupiter.params.ParameterizedTest; import org.junit.jupiter.params.provider.Arguments; import org.junit.jupiter.params.provider.MethodSource; @@ -52,6 +53,32 @@ public class AbstractParentClassTests { + public static class OuterClass { + @Test + protected void test() { + Assertions.assertTrue(true, "Just a dummy test that should be executed in outer class"); + } + + /* Since at org.graalvm.junit.platform.JUnitPlatformFeature#registerTestClassForReflection we register all + * declared classes and superclass of the test class, and we do so recursively, we want to avoid infinite loop. + * This inheritance shows that we won't call registration of these classes indefinitely (call registration of + * all declared classes of AbstractParentClassTests, then recursively call superclass of InfiniteLoopTest and + * repeat the process indefinitely) */ + private class InfiniteLoopTest extends OuterClass { + @Test + protected void test() { + Assertions.assertTrue(true, "Just a dummy test that should be executed in inner class"); + } + } + + /* Since enum here is declared class of AbstractParentClassTests, we want to avoid registrations of + * enum's internal superclasses and sub-classes at org.graalvm.junit.platform.JUnitPlatformFeature#registerTestClassForReflection */ + private enum EnumTest { + SOME_VALUE, + OTHER_VALUE + } + } + public abstract static class MathPowerTests { protected static BiFunction powFunction;