From 7593ded3d68ffdf11439eb5fe18328d2a06c2d92 Mon Sep 17 00:00:00 2001
From: Marc Philipp <mail@marcphilipp.de>
Date: Fri, 20 Dec 2024 09:00:26 +0100
Subject: [PATCH] Add native-image.properties files to jars (#4208)

This allows adding or removing entries from the set of classes that are
initialized at native image build time without requiring changes to
graalvm/native-build-tools. The new precompiled script plugin introduced
in this commit defines two Gradle tasks: `nativeImageProperties` and
`validateNativeImageProperties`. The former is used for writing the
`native-image.properties` file to the correct resource folder based (by
convention) on the `src/nativeImage/initialize-at-build-time` file in
each affected subproject. The latter validates that all listed classes
exist and acts as a safeguard when classes are renamed or deleted.

Resolves #4207.
---
 ...itbuild.native-image-properties.gradle.kts | 55 +++++++++++++++++++
 .../graalvm/NativeImagePropertiesExtension.kt |  7 +++
 .../junit-jupiter-api.gradle.kts              |  1 +
 .../src/nativeImage/initialize-at-build-time  |  4 ++
 .../junit-jupiter-engine.gradle.kts           |  1 +
 .../src/nativeImage/initialize-at-build-time  | 22 ++++++++
 .../junit-jupiter-params.gradle.kts           |  1 +
 .../src/nativeImage/initialize-at-build-time  |  2 +
 .../junit-platform-commons.gradle.kts         |  1 +
 .../src/nativeImage/initialize-at-build-time  |  5 ++
 .../junit-platform-engine.gradle.kts          |  1 +
 .../src/nativeImage/initialize-at-build-time  |  7 +++
 .../junit-platform-launcher.gradle.kts        |  1 +
 .../src/nativeImage/initialize-at-build-time  | 19 +++++++
 .../junit-platform-reporting.gradle.kts       |  1 +
 .../src/nativeImage/initialize-at-build-time  |  2 +
 .../junit-platform-suite-engine.gradle.kts    |  1 +
 .../src/nativeImage/initialize-at-build-time  |  4 ++
 .../junit-vintage-engine.gradle.kts           |  1 +
 .../src/nativeImage/initialize-at-build-time  |  5 ++
 .../projects/graalvm-starter/build.gradle.kts | 11 +++-
 .../com/example/project/GraalvmSuite.java     | 18 ++++++
 .../com/example/project/VintageTests.java     | 19 +++++++
 .../support/tests/GraalVmStarterTests.java    |  1 +
 24 files changed, 187 insertions(+), 3 deletions(-)
 create mode 100644 gradle/plugins/common/src/main/kotlin/junitbuild.native-image-properties.gradle.kts
 create mode 100644 gradle/plugins/common/src/main/kotlin/junitbuild/graalvm/NativeImagePropertiesExtension.kt
 create mode 100644 junit-jupiter-api/src/nativeImage/initialize-at-build-time
 create mode 100644 junit-jupiter-engine/src/nativeImage/initialize-at-build-time
 create mode 100644 junit-jupiter-params/src/nativeImage/initialize-at-build-time
 create mode 100644 junit-platform-commons/src/nativeImage/initialize-at-build-time
 create mode 100644 junit-platform-engine/src/nativeImage/initialize-at-build-time
 create mode 100644 junit-platform-launcher/src/nativeImage/initialize-at-build-time
 create mode 100644 junit-platform-reporting/src/nativeImage/initialize-at-build-time
 create mode 100644 junit-platform-suite-engine/src/nativeImage/initialize-at-build-time
 create mode 100644 junit-vintage-engine/src/nativeImage/initialize-at-build-time
 create mode 100644 platform-tooling-support-tests/projects/graalvm-starter/src/test/java/com/example/project/GraalvmSuite.java
 create mode 100644 platform-tooling-support-tests/projects/graalvm-starter/src/test/java/com/example/project/VintageTests.java

diff --git a/gradle/plugins/common/src/main/kotlin/junitbuild.native-image-properties.gradle.kts b/gradle/plugins/common/src/main/kotlin/junitbuild.native-image-properties.gradle.kts
new file mode 100644
index 000000000000..262535f2083a
--- /dev/null
+++ b/gradle/plugins/common/src/main/kotlin/junitbuild.native-image-properties.gradle.kts
@@ -0,0 +1,55 @@
+import junitbuild.graalvm.NativeImagePropertiesExtension
+import java.util.zip.ZipFile
+
+plugins {
+	`java-library`
+}
+
+val extension = extensions.create<NativeImagePropertiesExtension>("nativeImageProperties").apply {
+	val resourceFile: RegularFile = layout.projectDirectory.file("src/nativeImage/initialize-at-build-time")
+	if (resourceFile.asFile.exists()) {
+		initializeAtBuildTime.convention(providers.fileContents(resourceFile).asText.map { it.trim().lines() })
+	} else {
+		initializeAtBuildTime.empty()
+	}
+	initializeAtBuildTime.finalizeValueOnRead()
+}
+
+val outputDir = layout.buildDirectory.dir("resources/nativeImage")
+
+val propertyFileTask = tasks.register<WriteProperties>("nativeImageProperties") {
+	destinationFile = outputDir.map { it.file("META-INF/native-image/${project.group}/${project.name}/native-image.properties") }
+	// see https://www.graalvm.org/latest/reference-manual/native-image/overview/BuildConfiguration/#configuration-file-format
+	property("Args", extension.initializeAtBuildTime.map {
+		if (it.isEmpty()) {
+			""
+		} else {
+			"--initialize-at-build-time=${it.joinToString(",")}"
+		}
+	})
+}
+
+val validationTask = tasks.register("validateNativeImageProperties") {
+	dependsOn(tasks.jar)
+	doLast {
+		val zipEntries = ZipFile(tasks.jar.get().archiveFile.get().asFile).use { zipFile ->
+			zipFile.entries().asSequence().map { it.name }.filter { it.endsWith(".class") }.toSet()
+		}
+		val missingClasses = extension.initializeAtBuildTime.get().filter { className ->
+			!zipEntries.contains("${className.replace('.', '/')}.class")
+		}
+		if (missingClasses.isNotEmpty()) {
+			throw GradleException("The following classes were specified as initialize-at-build-time but do not exist (you should probably remove them from nativeImageProperties.initializeAtBuildTime):\n${missingClasses.joinToString("\n- ", "- ")}")
+		}
+	}
+}
+
+tasks.check {
+	dependsOn(validationTask)
+}
+
+sourceSets {
+	main {
+		output.dir(propertyFileTask.map { outputDir })
+	}
+}
diff --git a/gradle/plugins/common/src/main/kotlin/junitbuild/graalvm/NativeImagePropertiesExtension.kt b/gradle/plugins/common/src/main/kotlin/junitbuild/graalvm/NativeImagePropertiesExtension.kt
new file mode 100644
index 000000000000..48cb4042c439
--- /dev/null
+++ b/gradle/plugins/common/src/main/kotlin/junitbuild/graalvm/NativeImagePropertiesExtension.kt
@@ -0,0 +1,7 @@
+package junitbuild.graalvm
+
+import org.gradle.api.provider.SetProperty
+
+abstract class NativeImagePropertiesExtension {
+    abstract val initializeAtBuildTime: SetProperty<String>
+}
diff --git a/junit-jupiter-api/junit-jupiter-api.gradle.kts b/junit-jupiter-api/junit-jupiter-api.gradle.kts
index 402b5323eb57..378fc86a3d5d 100644
--- a/junit-jupiter-api/junit-jupiter-api.gradle.kts
+++ b/junit-jupiter-api/junit-jupiter-api.gradle.kts
@@ -1,6 +1,7 @@
 plugins {
 	id("junitbuild.kotlin-library-conventions")
 	id("junitbuild.code-generator")
+	id("junitbuild.native-image-properties")
 	`java-test-fixtures`
 }
 
diff --git a/junit-jupiter-api/src/nativeImage/initialize-at-build-time b/junit-jupiter-api/src/nativeImage/initialize-at-build-time
new file mode 100644
index 000000000000..b8fb5c3d7514
--- /dev/null
+++ b/junit-jupiter-api/src/nativeImage/initialize-at-build-time
@@ -0,0 +1,4 @@
+org.junit.jupiter.api.DisplayNameGenerator$Standard
+org.junit.jupiter.api.TestInstance$Lifecycle
+org.junit.jupiter.api.condition.OS
+org.junit.jupiter.api.extension.ConditionEvaluationResult
diff --git a/junit-jupiter-engine/junit-jupiter-engine.gradle.kts b/junit-jupiter-engine/junit-jupiter-engine.gradle.kts
index 04d86e5f0da7..819993462c0e 100644
--- a/junit-jupiter-engine/junit-jupiter-engine.gradle.kts
+++ b/junit-jupiter-engine/junit-jupiter-engine.gradle.kts
@@ -1,5 +1,6 @@
 plugins {
 	id("junitbuild.kotlin-library-conventions")
+	id("junitbuild.native-image-properties")
 	`java-test-fixtures`
 }
 
diff --git a/junit-jupiter-engine/src/nativeImage/initialize-at-build-time b/junit-jupiter-engine/src/nativeImage/initialize-at-build-time
new file mode 100644
index 000000000000..05880451fb5a
--- /dev/null
+++ b/junit-jupiter-engine/src/nativeImage/initialize-at-build-time
@@ -0,0 +1,22 @@
+org.junit.jupiter.engine.JupiterTestEngine
+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.ClassTestDescriptor
+org.junit.jupiter.engine.descriptor.ClassBasedTestDescriptor
+org.junit.jupiter.engine.descriptor.DynamicDescendantFilter
+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.NestedClassTestDescriptor
+org.junit.jupiter.engine.descriptor.TestFactoryTestDescriptor
+org.junit.jupiter.engine.descriptor.TestMethodTestDescriptor
+org.junit.jupiter.engine.descriptor.TestTemplateTestDescriptor
+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
diff --git a/junit-jupiter-params/junit-jupiter-params.gradle.kts b/junit-jupiter-params/junit-jupiter-params.gradle.kts
index d05a3b72e3e4..e481fdd13674 100644
--- a/junit-jupiter-params/junit-jupiter-params.gradle.kts
+++ b/junit-jupiter-params/junit-jupiter-params.gradle.kts
@@ -2,6 +2,7 @@ plugins {
 	id("junitbuild.kotlin-library-conventions")
 	id("junitbuild.shadow-conventions")
 	id("junitbuild.jmh-conventions")
+	id("junitbuild.native-image-properties")
 }
 
 description = "JUnit Jupiter Params"
diff --git a/junit-jupiter-params/src/nativeImage/initialize-at-build-time b/junit-jupiter-params/src/nativeImage/initialize-at-build-time
new file mode 100644
index 000000000000..44ca7ffbd8ad
--- /dev/null
+++ b/junit-jupiter-params/src/nativeImage/initialize-at-build-time
@@ -0,0 +1,2 @@
+org.junit.jupiter.params.provider.EnumSource$Mode
+org.junit.jupiter.params.provider.EnumSource$Mode$Validator
diff --git a/junit-platform-commons/junit-platform-commons.gradle.kts b/junit-platform-commons/junit-platform-commons.gradle.kts
index 3465b0078020..3de45a7edfee 100644
--- a/junit-platform-commons/junit-platform-commons.gradle.kts
+++ b/junit-platform-commons/junit-platform-commons.gradle.kts
@@ -3,6 +3,7 @@ import junitbuild.java.UpdateJarAction
 plugins {
 	id("junitbuild.java-library-conventions")
 	id("junitbuild.java-multi-release-sources")
+	id("junitbuild.native-image-properties")
 	`java-test-fixtures`
 }
 
diff --git a/junit-platform-commons/src/nativeImage/initialize-at-build-time b/junit-platform-commons/src/nativeImage/initialize-at-build-time
new file mode 100644
index 000000000000..a6c384232123
--- /dev/null
+++ b/junit-platform-commons/src/nativeImage/initialize-at-build-time
@@ -0,0 +1,5 @@
+org.junit.platform.commons.util.StringUtils
+org.junit.platform.commons.logging.LoggerFactory$DelegatingLogger
+org.junit.platform.commons.logging.LoggerFactory
+org.junit.platform.commons.util.ReflectionUtils
+org.junit.platform.commons.util.LruCache
diff --git a/junit-platform-engine/junit-platform-engine.gradle.kts b/junit-platform-engine/junit-platform-engine.gradle.kts
index 416b227b00c1..ef73763146a5 100644
--- a/junit-platform-engine/junit-platform-engine.gradle.kts
+++ b/junit-platform-engine/junit-platform-engine.gradle.kts
@@ -1,5 +1,6 @@
 plugins {
 	id("junitbuild.java-library-conventions")
+	id("junitbuild.native-image-properties")
 	`java-test-fixtures`
 }
 
diff --git a/junit-platform-engine/src/nativeImage/initialize-at-build-time b/junit-platform-engine/src/nativeImage/initialize-at-build-time
new file mode 100644
index 000000000000..5b1168bb74a6
--- /dev/null
+++ b/junit-platform-engine/src/nativeImage/initialize-at-build-time
@@ -0,0 +1,7 @@
+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.engine.support.descriptor.ClassSource
+org.junit.platform.engine.support.descriptor.MethodSource
+org.junit.platform.engine.support.hierarchical.Node$ExecutionMode
diff --git a/junit-platform-launcher/junit-platform-launcher.gradle.kts b/junit-platform-launcher/junit-platform-launcher.gradle.kts
index a9b3630762c8..acda6a79436f 100644
--- a/junit-platform-launcher/junit-platform-launcher.gradle.kts
+++ b/junit-platform-launcher/junit-platform-launcher.gradle.kts
@@ -1,5 +1,6 @@
 plugins {
 	id("junitbuild.java-library-conventions")
+	id("junitbuild.native-image-properties")
 	`java-test-fixtures`
 }
 
diff --git a/junit-platform-launcher/src/nativeImage/initialize-at-build-time b/junit-platform-launcher/src/nativeImage/initialize-at-build-time
new file mode 100644
index 000000000000..4b3770ab5fb1
--- /dev/null
+++ b/junit-platform-launcher/src/nativeImage/initialize-at-build-time
@@ -0,0 +1,19 @@
+org.junit.platform.launcher.LauncherSessionListener$1
+org.junit.platform.launcher.TestIdentifier
+org.junit.platform.launcher.core.DefaultLauncher
+org.junit.platform.launcher.core.DefaultLauncherConfig
+org.junit.platform.launcher.core.EngineDiscoveryOrchestrator
+org.junit.platform.launcher.core.EngineExecutionOrchestrator
+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.LauncherListenerRegistry
+org.junit.platform.launcher.core.ListenerRegistry
+org.junit.platform.launcher.core.SessionPerRequestLauncher
+org.junit.platform.launcher.listeners.UniqueIdTrackingListener
diff --git a/junit-platform-reporting/junit-platform-reporting.gradle.kts b/junit-platform-reporting/junit-platform-reporting.gradle.kts
index f1dede61555d..e9094c04f2e1 100644
--- a/junit-platform-reporting/junit-platform-reporting.gradle.kts
+++ b/junit-platform-reporting/junit-platform-reporting.gradle.kts
@@ -1,5 +1,6 @@
 plugins {
 	id("junitbuild.java-library-conventions")
+	id("junitbuild.native-image-properties")
 	id("junitbuild.shadow-conventions")
 	`java-test-fixtures`
 }
diff --git a/junit-platform-reporting/src/nativeImage/initialize-at-build-time b/junit-platform-reporting/src/nativeImage/initialize-at-build-time
new file mode 100644
index 000000000000..1b4f355f53cf
--- /dev/null
+++ b/junit-platform-reporting/src/nativeImage/initialize-at-build-time
@@ -0,0 +1,2 @@
+org.junit.platform.reporting.open.xml.OpenTestReportGeneratingListener
+org.junit.platform.reporting.shadow.org.opentest4j.reporting.events.api.DocumentWriter$1
diff --git a/junit-platform-suite-engine/junit-platform-suite-engine.gradle.kts b/junit-platform-suite-engine/junit-platform-suite-engine.gradle.kts
index 36abcdbc088d..72f90de35321 100644
--- a/junit-platform-suite-engine/junit-platform-suite-engine.gradle.kts
+++ b/junit-platform-suite-engine/junit-platform-suite-engine.gradle.kts
@@ -1,5 +1,6 @@
 plugins {
 	id("junitbuild.java-library-conventions")
+	id("junitbuild.native-image-properties")
 }
 
 description = "JUnit Platform Suite Engine"
diff --git a/junit-platform-suite-engine/src/nativeImage/initialize-at-build-time b/junit-platform-suite-engine/src/nativeImage/initialize-at-build-time
new file mode 100644
index 000000000000..a6d7d06046b1
--- /dev/null
+++ b/junit-platform-suite-engine/src/nativeImage/initialize-at-build-time
@@ -0,0 +1,4 @@
+org.junit.platform.suite.engine.SuiteEngineDescriptor
+org.junit.platform.suite.engine.SuiteLauncher
+org.junit.platform.suite.engine.SuiteTestDescriptor
+org.junit.platform.suite.engine.SuiteTestEngine
diff --git a/junit-vintage-engine/junit-vintage-engine.gradle.kts b/junit-vintage-engine/junit-vintage-engine.gradle.kts
index 9390bdcd3b4e..d2b22d6b711f 100644
--- a/junit-vintage-engine/junit-vintage-engine.gradle.kts
+++ b/junit-vintage-engine/junit-vintage-engine.gradle.kts
@@ -1,6 +1,7 @@
 plugins {
 	id("junitbuild.java-library-conventions")
 	id("junitbuild.junit4-compatibility")
+	id("junitbuild.native-image-properties")
 	id("junitbuild.testing-conventions")
 	`java-test-fixtures`
 	groovy
diff --git a/junit-vintage-engine/src/nativeImage/initialize-at-build-time b/junit-vintage-engine/src/nativeImage/initialize-at-build-time
new file mode 100644
index 000000000000..75ff3d41de5a
--- /dev/null
+++ b/junit-vintage-engine/src/nativeImage/initialize-at-build-time
@@ -0,0 +1,5 @@
+org.junit.vintage.engine.VintageTestEngine
+org.junit.vintage.engine.descriptor.RunnerTestDescriptor
+org.junit.vintage.engine.descriptor.VintageEngineDescriptor
+org.junit.vintage.engine.support.UniqueIdReader
+org.junit.vintage.engine.support.UniqueIdStringifier
diff --git a/platform-tooling-support-tests/projects/graalvm-starter/build.gradle.kts b/platform-tooling-support-tests/projects/graalvm-starter/build.gradle.kts
index 77a7a5053c65..6203c65fdcac 100644
--- a/platform-tooling-support-tests/projects/graalvm-starter/build.gradle.kts
+++ b/platform-tooling-support-tests/projects/graalvm-starter/build.gradle.kts
@@ -5,6 +5,7 @@ plugins {
 
 val jupiterVersion: String by project
 val platformVersion: String by project
+val vintageVersion: String by project
 
 repositories {
 	maven { url = uri(file(System.getProperty("maven.repo"))) }
@@ -13,11 +14,16 @@ repositories {
 
 dependencies {
 	testImplementation("org.junit.jupiter:junit-jupiter:$jupiterVersion")
+	testImplementation("junit:junit:4.13.2")
+	testImplementation("org.junit.platform:junit-platform-suite:$platformVersion")
+	testRuntimeOnly("org.junit.vintage:junit-vintage-engine:$vintageVersion")
 	testRuntimeOnly("org.junit.platform:junit-platform-reporting:$platformVersion")
 }
 
 tasks.test {
-	useJUnitPlatform()
+	useJUnitPlatform {
+		includeEngines("junit-platform-suite")
+	}
 
 	val outputDir = reports.junitXml.outputLocation
 	jvmArgumentProviders += CommandLineArgumentProvider {
@@ -31,8 +37,7 @@ tasks.test {
 graalvmNative {
 	binaries {
 		named("test") {
-			// TODO #3040 Add to native-image.properties
-			buildArgs.add("--initialize-at-build-time=org.junit.platform.launcher.core.HierarchicalOutputDirectoryProvider")
+			buildArgs.add("--strict-image-heap")
 			buildArgs.add("-H:+ReportExceptionStackTraces")
 		}
 	}
diff --git a/platform-tooling-support-tests/projects/graalvm-starter/src/test/java/com/example/project/GraalvmSuite.java b/platform-tooling-support-tests/projects/graalvm-starter/src/test/java/com/example/project/GraalvmSuite.java
new file mode 100644
index 000000000000..82796513068d
--- /dev/null
+++ b/platform-tooling-support-tests/projects/graalvm-starter/src/test/java/com/example/project/GraalvmSuite.java
@@ -0,0 +1,18 @@
+/*
+ * Copyright 2015-2024 the original author or authors.
+ *
+ * All rights reserved. This program and the accompanying materials are
+ * made available under the terms of the Eclipse Public License v2.0 which
+ * accompanies this distribution and is available at
+ *
+ * https://www.eclipse.org/legal/epl-v20.html
+ */
+
+package com.example.project;
+
+import org.junit.platform.suite.api.*;
+
+@Suite
+@SelectPackages("com.example.project")
+public class GraalvmSuite {
+}
diff --git a/platform-tooling-support-tests/projects/graalvm-starter/src/test/java/com/example/project/VintageTests.java b/platform-tooling-support-tests/projects/graalvm-starter/src/test/java/com/example/project/VintageTests.java
new file mode 100644
index 000000000000..3ce117dea75b
--- /dev/null
+++ b/platform-tooling-support-tests/projects/graalvm-starter/src/test/java/com/example/project/VintageTests.java
@@ -0,0 +1,19 @@
+/*
+ * Copyright 2015-2024 the original author or authors.
+ *
+ * All rights reserved. This program and the accompanying materials are
+ * made available under the terms of the Eclipse Public License v2.0 which
+ * accompanies this distribution and is available at
+ *
+ * https://www.eclipse.org/legal/epl-v20.html
+ */
+
+package com.example.project;
+
+import org.junit.Test;
+
+public class VintageTests {
+	@Test
+	public void test() {
+	}
+}
diff --git a/platform-tooling-support-tests/src/test/java/platform/tooling/support/tests/GraalVmStarterTests.java b/platform-tooling-support-tests/src/test/java/platform/tooling/support/tests/GraalVmStarterTests.java
index 9376899354f1..1a36ff86d129 100644
--- a/platform-tooling-support-tests/src/test/java/platform/tooling/support/tests/GraalVmStarterTests.java
+++ b/platform-tooling-support-tests/src/test/java/platform/tooling/support/tests/GraalVmStarterTests.java
@@ -53,6 +53,7 @@ void runsTestsInNativeImage(@TempDir Path workspace, @FilePrefix("gradle") Outpu
 				.anyMatch(line -> line.contains("CalculatorTests > 1 + 1 = 2 SUCCESSFUL")) //
 				.anyMatch(line -> line.contains("CalculatorTests > 1 + 100 = 101 SUCCESSFUL")) //
 				.anyMatch(line -> line.contains("ClassLevelAnnotationTests$Inner > test() SUCCESSFUL")) //
+				.anyMatch(line -> line.contains("com.example.project.VintageTests > test SUCCESSFUL")) //
 				.anyMatch(line -> line.contains("BUILD SUCCESSFUL"));
 	}
 }