From e1cbfcdd8817aeb3c4d244b56d71c562c41b590d Mon Sep 17 00:00:00 2001 From: kaklakariada Date: Thu, 20 Aug 2026 09:38:16 +0200 Subject: [PATCH 01/12] Create test fixture --- .../gradle/OpenFastTracePluginTest.java | 76 +++++++---- .../gradle/PluginTestFixture.java | 128 ++++++++++++++++++ 2 files changed, 175 insertions(+), 29 deletions(-) create mode 100644 src/test/java/org/itsallcode/openfasttrace/gradle/PluginTestFixture.java diff --git a/src/test/java/org/itsallcode/openfasttrace/gradle/OpenFastTracePluginTest.java b/src/test/java/org/itsallcode/openfasttrace/gradle/OpenFastTracePluginTest.java index 34d9f15..d66cabe 100644 --- a/src/test/java/org/itsallcode/openfasttrace/gradle/OpenFastTracePluginTest.java +++ b/src/test/java/org/itsallcode/openfasttrace/gradle/OpenFastTracePluginTest.java @@ -30,7 +30,8 @@ class OpenFastTracePluginTest private static final boolean ENABLE_WARNINGS = true; private static final Path EXAMPLES_DIR = Paths.get("example-projects").toAbsolutePath(); - private static final Path PROJECT_DEFAULT_CONFIG_DIR = EXAMPLES_DIR.resolve("default-config"); + private static final Path PROJECT_DEFAULT_CONFIG_DIR = EXAMPLES_DIR + .resolve("default-config"); private static final Path PROJECT_CUSTOM_CONFIG_DIR = EXAMPLES_DIR.resolve("custom-config"); private static final Path MULTI_PROJECT_DIR = EXAMPLES_DIR.resolve("multi-project"); private static final Path DEPENDENCY_CONFIG_DIR = EXAMPLES_DIR.resolve("dependency-config"); @@ -43,9 +44,9 @@ class OpenFastTracePluginTest @Test void tracingTaskAddedToProject() { - final BuildResult buildResult = runBuild(PROJECT_DEFAULT_CONFIG_DIR, "tasks"); - assertThat(buildResult.getOutput(), containsString( - "traceRequirements - Trace requirements and generate tracing report")); + testFixture(PROJECT_DEFAULT_CONFIG_DIR).withArgs("tasks").run() + .assertOutput(containsString( + "traceRequirements - Trace requirements and generate tracing report")); } @Test @@ -63,25 +64,24 @@ void pluginUsesConfigurationCacheWithMultiModuleProject() private void testConfigurationCache(final Path projectDir) { assumeTrue(configurationCacheEnabled(), "Configuration cache is not enabled"); - BuildResult buildResult = runBuild(projectDir, "tasks"); - assertThat(buildResult.getOutput(), containsString( + final PluginTestFixture testFixture = testFixture(projectDir).withArgs("tasks"); + + testFixture.run().assertOutput(containsString( "traceRequirements - Trace requirements and generate tracing report")); - buildResult = runBuild(projectDir, "tasks"); - assertThat(buildResult.getOutput(), - allOf(containsString( - "traceRequirements - Trace requirements and generate tracing report"), - containsString("Reusing configuration cache."))); + + testFixture.run().assertOutput(allOf(containsString( + "traceRequirements - Trace requirements and generate tracing report"), + containsString("Reusing configuration cache."))); } @Test void testTraceExampleProjectWithDefaultConfig() { - final BuildResult buildResult = runBuild(PROJECT_DEFAULT_CONFIG_DIR, "clean", - "traceRequirements"); - assertThat(buildResult.task(":traceRequirements").getOutcome(), - either(is(TaskOutcome.SUCCESS)).or(is(TaskOutcome.FROM_CACHE))); - TestUtil.assertFileContent(PROJECT_DEFAULT_CONFIG_DIR.resolve("build/reports/tracing.txt"), - "ok - 0 total"); + testFixture(PROJECT_DEFAULT_CONFIG_DIR).withArgs("clean", "traceRequirements") + .withReportFile(Path.of("build/reports/tracing.txt")) + .run() + .assertTraceOutcomeSuccessOrFromCache() + .assertReportFileLines("ok - 0 total"); } @Test @@ -159,7 +159,8 @@ void testHtmlReportConfig() "traceRequirements"); assertThat(buildResult.task(":traceRequirements").getOutcome(), either(is(TaskOutcome.SUCCESS)).or(is(TaskOutcome.FROM_CACHE))); - TestUtil.assertFileContent(HTML_REPORT_CONFIG_DIR.resolve("build/reports/tracing.html"), + TestUtil.assertFileContent( + HTML_REPORT_CONFIG_DIR.resolve("build/reports/tracing.html"), "", "", "
"); @@ -184,7 +185,8 @@ void testTraceExampleProjectWithCustomConfig() "traceRequirements"); assertThat(buildResult.task(":traceRequirements").getOutcome(), either(is(TaskOutcome.SUCCESS)).or(is(TaskOutcome.FROM_CACHE))); - TestUtil.assertFileContent(PROJECT_CUSTOM_CONFIG_DIR.resolve("build/custom-report.txt"), + TestUtil.assertFileContent( + PROJECT_CUSTOM_CONFIG_DIR.resolve("build/custom-report.txt"), "not ok [ in: 1 / 1 ✔ | out: 0 / 0 ] dsn~exampleB~1 [draft] (impl, -utest)", "not ok - 2 total, 1 direct, 0 transitive defects"); } @@ -196,7 +198,8 @@ void testTraceExampleProjectWithCustomConfigFailBuild() "clean", "traceRequirements", "-PfailBuild=true"); assertEquals(TaskOutcome.FAILED, buildResult.task(":traceRequirements").getOutcome()); - TestUtil.assertFileContent(PROJECT_CUSTOM_CONFIG_DIR.resolve("build/custom-report.txt"), + TestUtil.assertFileContent( + PROJECT_CUSTOM_CONFIG_DIR.resolve("build/custom-report.txt"), "not ok [ in: 1 / 1 ✔ | out: 0 / 0 ] dsn~exampleB~1 [draft] (impl, -utest)", "not ok - 2 total, 1 direct, 0 transitive defects"); } @@ -205,7 +208,8 @@ void testTraceExampleProjectWithCustomConfigFailBuild() void filteredArtifactTypes() { final BuildResult buildResult = runBuild(PROJECT_CUSTOM_CONFIG_DIR, "clean", - "traceRequirements", "-PfailBuild=true", "-PfilteredArtifactTypes=dsn"); + "traceRequirements", "-PfailBuild=true", + "-PfilteredArtifactTypes=dsn"); assertThat(buildResult.task(":traceRequirements").getOutcome(), either(is(TaskOutcome.SUCCESS)).or(is(TaskOutcome.FROM_CACHE))); } @@ -218,7 +222,8 @@ void filteredWantedStatuses() "-PfilterWantedStatuses=draft,approved"); assertThat(buildResult.task(":traceRequirements").getOutcome(), either(is(TaskOutcome.SUCCESS)).or(is(TaskOutcome.FROM_CACHE))); - TestUtil.assertFileContent(PROJECT_CUSTOM_CONFIG_DIR.resolve("build/custom-report.txt"), + TestUtil.assertFileContent( + PROJECT_CUSTOM_CONFIG_DIR.resolve("build/custom-report.txt"), "not ok [ in: 1 / 1 ✔ | out: 0 / 0 ] dsn~exampleB~1 [draft] (impl, -utest)", "not ok - 2 total, 1 direct, 0 transitive defects"); } @@ -241,7 +246,8 @@ void filteredWantedStatusesNoMatch() @Test void filteredWantedStatusesInvalidStatus() { - final BuildResult buildResult = runBuildExpectFailure(PROJECT_CUSTOM_CONFIG_DIR, "clean", + final BuildResult buildResult = runBuildExpectFailure(PROJECT_CUSTOM_CONFIG_DIR, + "clean", "traceRequirements", "-PfilterWantedStatuses=invalid"); assertThat(buildResult.getOutput(), containsString( @@ -260,7 +266,9 @@ void testTraceExampleProjectWithCustomConfigFailBuildErrorMessage() { assertAll( () -> assertEquals(TaskOutcome.FAILED, - e.getBuildResult().task(":traceRequirements").getOutcome()), + e.getBuildResult() + .task(":traceRequirements") + .getOutcome()), () -> assertThat(e.getMessage(), startsWith("Unexpected build execution failure")), () -> assertThat(e.getMessage(), @@ -285,13 +293,15 @@ void traceDependencyProject() BuildResult buildResult = runBuild(DEPENDENCY_CONFIG_DIR, "clean"); assertThat(buildResult.task(":clean").getOutcome(), either(is(TaskOutcome.SUCCESS)).or(is(TaskOutcome.UP_TO_DATE))); - final Path dependencyZip = DEPENDENCY_CONFIG_DIR.resolve("build/repo/requirements-1.0.zip"); + final Path dependencyZip = DEPENDENCY_CONFIG_DIR + .resolve("build/repo/requirements-1.0.zip"); createDependencyZip(dependencyZip); buildResult = runBuild(DEPENDENCY_CONFIG_DIR, "traceRequirements"); assertThat(buildResult.task(":traceRequirements").getOutcome(), either(is(TaskOutcome.SUCCESS)).or(is(TaskOutcome.FROM_CACHE))); - TestUtil.assertFileContent(DEPENDENCY_CONFIG_DIR.resolve("build/reports/tracing.txt"), + TestUtil.assertFileContent( + DEPENDENCY_CONFIG_DIR.resolve("build/reports/tracing.txt"), "requirements-1.0.zip!spec.md:2", "requirements-1.0.zip!source.java:1", "not ok - 2 total, 1 direct, 0 transitive defects"); @@ -302,7 +312,8 @@ void publishToMavenRepo() { final BuildResult buildResult = runBuild(PUBLISH_CONFIG_DIR, "clean", "publishToMavenLocal"); - assertEquals(TaskOutcome.SUCCESS, buildResult.task(":publishToMavenLocal").getOutcome()); + assertEquals(TaskOutcome.SUCCESS, + buildResult.task(":publishToMavenLocal").getOutcome()); final Path archive = PUBLISH_CONFIG_DIR .resolve("build/distributions/publish-config-1.0.zip"); @@ -366,7 +377,8 @@ private static void createDependencyZip(final Path dependencyZip) } catch (final IOException e) { - throw new UncheckedIOException("Failed to create dependency zip " + dependencyZip, e); + throw new UncheckedIOException( + "Failed to create dependency zip " + dependencyZip, e); } } @@ -415,7 +427,8 @@ private static boolean configurationCacheEnabled() private static void configureJacoco(final Path projectDir) { final Optional testkitGradleConfig = TestUtil - .readResource(OpenFastTracePluginTest.class, "/testkit-gradle.properties"); + .readResource(OpenFastTracePluginTest.class, + "/testkit-gradle.properties"); if (testkitGradleConfig.isEmpty()) { LOG.info("Testkit gradle config not available. Skipping configuration"); @@ -426,4 +439,9 @@ private static void configureJacoco(final Path projectDir) LOG.info("Writing testkit gradle config to {}", gradleProperties); TestUtil.writeFile(gradleProperties, testkitGradleConfig.get()); } + + private PluginTestFixture testFixture(final Path projectDir) + { + return PluginTestFixture.create(config, projectDir); + } } diff --git a/src/test/java/org/itsallcode/openfasttrace/gradle/PluginTestFixture.java b/src/test/java/org/itsallcode/openfasttrace/gradle/PluginTestFixture.java new file mode 100644 index 0000000..f64a5a5 --- /dev/null +++ b/src/test/java/org/itsallcode/openfasttrace/gradle/PluginTestFixture.java @@ -0,0 +1,128 @@ +package org.itsallcode.openfasttrace.gradle; + +import static org.hamcrest.CoreMatchers.either; +import static org.hamcrest.CoreMatchers.is; +import static org.hamcrest.MatcherAssert.assertThat; + +import java.nio.file.Path; +import java.util.*; + +import org.gradle.api.logging.Logger; +import org.gradle.api.logging.Logging; +import org.gradle.testkit.runner.*; +import org.hamcrest.Matcher; + +class PluginTestFixture +{ + private static final Logger LOG = Logging.getLogger(PluginTestFixture.class); + + private final GradleTestConfig config; + private final Path projectDir; + private String[] arguments; + private Path relativeReportPath; + + private PluginTestFixture(final GradleTestConfig config, final Path projectDir) + { + this.config = config; + this.projectDir = projectDir; + } + + static PluginTestFixture create(final GradleTestConfig config, final Path projectDir) + { + return new PluginTestFixture(config, projectDir); + } + + PluginTestFixture withArgs(final String... args) + { + this.arguments = args; + return this; + } + + public PluginTestFixture withReportFile(final Path relativeReportPath) + { + this.relativeReportPath = relativeReportPath; + return this; + } + + Result run() + { + final GradleRunner runner = createGradleRunner(); + final BuildResult buildResult = runner.build(); + return new Result(buildResult); + } + + private GradleRunner createGradleRunner() + { + configureJacoco(projectDir); + final List allArgs = new ArrayList<>(); + allArgs.addAll(List.of(arguments)); + allArgs.addAll(List.of("--info", "--stacktrace", "--build-cache")); + if (configurationCacheEnabled()) + { + allArgs.add("--configuration-cache"); + } + allArgs.addAll(List.of("--warning-mode", "all")); + final GradleRunner runner = GradleRunner.create() + .withProjectDir(projectDir.toFile()) + .withPluginClasspath() + .withArguments(allArgs) + .forwardOutput(); + if (config.gradleVersion != null) + { + runner.withGradleVersion(config.gradleVersion); + } + return runner; + } + + private static boolean configurationCacheEnabled() + { + return System.getProperty("enableConfigurationCache", "false") + .equalsIgnoreCase("true"); + } + + private static void configureJacoco(final Path projectDir) + { + final Optional testkitGradleConfig = TestUtil + .readResource(OpenFastTracePluginTest.class, + "/testkit-gradle.properties"); + if (testkitGradleConfig.isEmpty()) + { + LOG.info("Testkit gradle config not available. Skipping configuration"); + return; + } + LOG.info("Found testkit gradle config: {}", testkitGradleConfig.get()); + final Path gradleProperties = projectDir.resolve("gradle.properties"); + LOG.info("Writing testkit gradle config to {}", gradleProperties); + TestUtil.writeFile(gradleProperties, testkitGradleConfig.get()); + } + + class Result + { + + private final BuildResult buildResult; + + private Result(final BuildResult buildResult) + { + this.buildResult = buildResult; + } + + void assertOutput(final Matcher matcher) + { + assertThat(buildResult.getOutput(), matcher); + } + + Result assertTraceOutcomeSuccessOrFromCache() + { + assertThat(buildResult.task(":traceRequirements").getOutcome(), + either(is(TaskOutcome.SUCCESS)).or(is(TaskOutcome.FROM_CACHE))); + return this; + } + + Result assertReportFileLines(final String... lines) + { + final Path reportFile = projectDir.resolve(relativeReportPath); + TestUtil.assertFileContent(reportFile, lines); + return this; + } + } +} From 8bc9aee960a4112e78e54e5047280adef8f87e12 Mon Sep 17 00:00:00 2001 From: kaklakariada Date: Thu, 20 Aug 2026 14:35:03 +0200 Subject: [PATCH 02/12] Add test fixture --- .../gradle/OpenFastTracePluginTest.java | 138 +++++++++--------- .../gradle/PluginTestFixture.java | 25 +++- 2 files changed, 90 insertions(+), 73 deletions(-) diff --git a/src/test/java/org/itsallcode/openfasttrace/gradle/OpenFastTracePluginTest.java b/src/test/java/org/itsallcode/openfasttrace/gradle/OpenFastTracePluginTest.java index d66cabe..29d431a 100644 --- a/src/test/java/org/itsallcode/openfasttrace/gradle/OpenFastTracePluginTest.java +++ b/src/test/java/org/itsallcode/openfasttrace/gradle/OpenFastTracePluginTest.java @@ -87,95 +87,89 @@ void testTraceExampleProjectWithDefaultConfig() @Test void testCollectExampleProjectWithCustomConfig() { - final BuildResult buildResult = runBuild(PROJECT_CUSTOM_CONFIG_DIR, "clean", - "collectRequirements"); - assertThat(buildResult.task(":collectRequirements").getOutcome(), - either(is(TaskOutcome.SUCCESS)).or(is(TaskOutcome.FROM_CACHE))); - TestUtil.assertFileContent( - PROJECT_CUSTOM_CONFIG_DIR.resolve("build/reports/requirements.xml"), - "\n" + + testFixture(PROJECT_CUSTOM_CONFIG_DIR).withArgs("clean", "collectRequirements") + .withReportFile(Path.of("build/reports/requirements.xml")) + .run().assertCollectOutcomeSuccessOrFromCache() + .assertReportFileLines("\n" + "", - """ - - - exampleB\ - """, """ - - approved - 0 - """, - - """ - 1 - - - dsn:exampleB - 1 - - - """, - - """ - - - exampleB - Tracing Example - draft - 1 - """, - - """ - 2 - Example requirement - - utest - impl - - - """, - - " \n" + - ""); + """ + + + exampleB\ + """, """ + + approved + 0 + """, + + """ + 1 + + + dsn:exampleB + 1 + + + """, + + """ + + + exampleB + Tracing Example + draft + 1 + """, + + """ + 2 + Example requirement + + utest + impl + + + """, + + " \n" + + ""); } @Test void testCollectIsUpToDateWhenAlreadyRunBefore() { - BuildResult buildResult = runBuild(PROJECT_CUSTOM_CONFIG_DIR, "clean", + final PluginTestFixture fixture = testFixture(PROJECT_CUSTOM_CONFIG_DIR).withArgs("clean", "collectRequirements"); - assertThat(buildResult.task(":clean").getOutcome(), - either(is(TaskOutcome.SUCCESS)).or(is(TaskOutcome.FROM_CACHE))); - assertThat(buildResult.task(":collectRequirements").getOutcome(), - either(is(TaskOutcome.SUCCESS)).or(is(TaskOutcome.FROM_CACHE))); - buildResult = runBuild(PROJECT_CUSTOM_CONFIG_DIR, "collectRequirements"); - assertEquals(TaskOutcome.UP_TO_DATE, - buildResult.task(":collectRequirements").getOutcome()); + fixture + .run().assertCollectOutcomeSuccessOrFromCache() + .assertOutcomeSuccessOrFromCache(":clean") + .assertCollectOutcomeSuccessOrFromCache(); + + fixture.withArgs("collectRequirements").run() + .assertOutcome(":collectRequirements", TaskOutcome.UP_TO_DATE); } @Test void testHtmlReportConfig() { - final BuildResult buildResult = runBuild(HTML_REPORT_CONFIG_DIR, "clean", - "traceRequirements"); - assertThat(buildResult.task(":traceRequirements").getOutcome(), - either(is(TaskOutcome.SUCCESS)).or(is(TaskOutcome.FROM_CACHE))); - TestUtil.assertFileContent( - HTML_REPORT_CONFIG_DIR.resolve("build/reports/tracing.html"), - "", - "", - "
"); + testFixture(HTML_REPORT_CONFIG_DIR) + .withArgs("clean", "traceRequirements") + .withReportFile(Path.of("build/reports/tracing.html")) + .run() + .assertTraceOutcomeSuccessOrFromCache() + .assertReportFileLines("", + "", + "
"); } @Test void testTraceTaskUpToDateWhenAlreadyRun() { - BuildResult buildResult = runBuild(HTML_REPORT_CONFIG_DIR, "clean", - "traceRequirements"); - assertThat(buildResult.task(":traceRequirements").getOutcome(), - either(is(TaskOutcome.SUCCESS)).or(is(TaskOutcome.FROM_CACHE))); - buildResult = runBuild(HTML_REPORT_CONFIG_DIR, "traceRequirements"); - assertEquals(TaskOutcome.UP_TO_DATE, - buildResult.task(":traceRequirements").getOutcome()); + final PluginTestFixture testFixture = testFixture(HTML_REPORT_CONFIG_DIR); + testFixture.withArgs("clean", "traceRequirements").run() + .assertTraceOutcomeSuccessOrFromCache(); + testFixture.withArgs("traceRequirements").run().assertOutcome(":traceRequirements", + TaskOutcome.UP_TO_DATE); } @Test diff --git a/src/test/java/org/itsallcode/openfasttrace/gradle/PluginTestFixture.java b/src/test/java/org/itsallcode/openfasttrace/gradle/PluginTestFixture.java index f64a5a5..c7335c7 100644 --- a/src/test/java/org/itsallcode/openfasttrace/gradle/PluginTestFixture.java +++ b/src/test/java/org/itsallcode/openfasttrace/gradle/PluginTestFixture.java @@ -3,6 +3,7 @@ import static org.hamcrest.CoreMatchers.either; import static org.hamcrest.CoreMatchers.is; import static org.hamcrest.MatcherAssert.assertThat; +import static org.junit.jupiter.api.Assertions.assertEquals; import java.nio.file.Path; import java.util.*; @@ -113,7 +114,29 @@ void assertOutput(final Matcher matcher) Result assertTraceOutcomeSuccessOrFromCache() { - assertThat(buildResult.task(":traceRequirements").getOutcome(), + return assertOutcomeSuccessOrFromCache(":traceRequirements"); + } + + Result assertCollectOutcomeUpToDate() + { + return this.assertOutcome(":collectRequirements", TaskOutcome.UP_TO_DATE); + } + + Result assertOutcome(final String taskPath, final TaskOutcome expectedOutcome) + { + assertEquals(expectedOutcome, buildResult.task(taskPath).getOutcome(), + "Outcome of task " + taskPath); + return this; + } + + Result assertCollectOutcomeSuccessOrFromCache() + { + return assertOutcomeSuccessOrFromCache(":collectRequirements"); + } + + Result assertOutcomeSuccessOrFromCache(final String taskPath) + { + assertThat(buildResult.task(taskPath).getOutcome(), either(is(TaskOutcome.SUCCESS)).or(is(TaskOutcome.FROM_CACHE))); return this; } From 66afb011f676aeaa90fec8c735555e364850c635 Mon Sep 17 00:00:00 2001 From: kaklakariada Date: Thu, 20 Aug 2026 14:43:53 +0200 Subject: [PATCH 03/12] Complete refactoring of tests --- .../gradle/OpenFastTracePluginTest.java | 216 ++++++------------ .../gradle/PluginTestFixture.java | 7 + 2 files changed, 78 insertions(+), 145 deletions(-) diff --git a/src/test/java/org/itsallcode/openfasttrace/gradle/OpenFastTracePluginTest.java b/src/test/java/org/itsallcode/openfasttrace/gradle/OpenFastTracePluginTest.java index 29d431a..388ac6b 100644 --- a/src/test/java/org/itsallcode/openfasttrace/gradle/OpenFastTracePluginTest.java +++ b/src/test/java/org/itsallcode/openfasttrace/gradle/OpenFastTracePluginTest.java @@ -1,34 +1,29 @@ package org.itsallcode.openfasttrace.gradle; import static java.util.stream.Collectors.joining; -import static org.hamcrest.CoreMatchers.*; +import static org.hamcrest.CoreMatchers.containsString; +import static org.hamcrest.CoreMatchers.startsWith; import static org.hamcrest.MatcherAssert.assertThat; import static org.hamcrest.Matchers.allOf; -import static org.hamcrest.Matchers.either; import static org.junit.jupiter.api.Assertions.*; import static org.junit.jupiter.api.Assumptions.assumeTrue; import java.io.*; import java.nio.file.*; -import java.util.*; -import org.gradle.api.logging.Logging; import org.gradle.internal.impldep.org.apache.commons.compress.archivers.zip.ZipArchiveEntry; import org.gradle.internal.impldep.org.apache.commons.compress.archivers.zip.ZipFile; -import org.gradle.testkit.runner.*; +import org.gradle.testkit.runner.TaskOutcome; +import org.gradle.testkit.runner.UnexpectedBuildFailure; import org.junit.jupiter.api.Test; import org.junit.jupiter.params.Parameter; import org.junit.jupiter.params.ParameterizedClass; import org.junit.jupiter.params.provider.EnumSource; -import org.slf4j.Logger; @ParameterizedClass(name = "OpenFastTracePluginTest {0}") @EnumSource(GradleTestConfig.class) class OpenFastTracePluginTest { - private static final Logger LOG = Logging.getLogger(OpenFastTracePluginTest.class); - - private static final boolean ENABLE_WARNINGS = true; private static final Path EXAMPLES_DIR = Paths.get("example-projects").toAbsolutePath(); private static final Path PROJECT_DEFAULT_CONFIG_DIR = EXAMPLES_DIR .resolve("default-config"); @@ -175,139 +170,122 @@ void testTraceTaskUpToDateWhenAlreadyRun() @Test void testTraceExampleProjectWithCustomConfig() { - final BuildResult buildResult = runBuild(PROJECT_CUSTOM_CONFIG_DIR, "clean", - "traceRequirements"); - assertThat(buildResult.task(":traceRequirements").getOutcome(), - either(is(TaskOutcome.SUCCESS)).or(is(TaskOutcome.FROM_CACHE))); - TestUtil.assertFileContent( - PROJECT_CUSTOM_CONFIG_DIR.resolve("build/custom-report.txt"), - "not ok [ in: 1 / 1 ✔ | out: 0 / 0 ] dsn~exampleB~1 [draft] (impl, -utest)", - "not ok - 2 total, 1 direct, 0 transitive defects"); + testFixture(PROJECT_CUSTOM_CONFIG_DIR).withArgs("clean", "traceRequirements") + .withReportFile(Path.of("build/custom-report.txt")) + .run().assertTraceOutcomeSuccessOrFromCache() + .assertReportFileLines( + "not ok [ in: 1 / 1 ✔ | out: 0 / 0 ] dsn~exampleB~1 [draft] (impl, -utest)", + "not ok - 2 total, 1 direct, 0 transitive defects"); } @Test void testTraceExampleProjectWithCustomConfigFailBuild() { - final BuildResult buildResult = runBuildExpectFailure(PROJECT_CUSTOM_CONFIG_DIR, - "clean", "traceRequirements", "-PfailBuild=true"); - assertEquals(TaskOutcome.FAILED, - buildResult.task(":traceRequirements").getOutcome()); - TestUtil.assertFileContent( - PROJECT_CUSTOM_CONFIG_DIR.resolve("build/custom-report.txt"), - "not ok [ in: 1 / 1 ✔ | out: 0 / 0 ] dsn~exampleB~1 [draft] (impl, -utest)", - "not ok - 2 total, 1 direct, 0 transitive defects"); + testFixture(PROJECT_CUSTOM_CONFIG_DIR) + .withArgs("clean", "traceRequirements", "-PfailBuild=true") + .withReportFile(Path.of("build/custom-report.txt")) + .runExpectingFailure() + .assertOutcome(":traceRequirements", TaskOutcome.FAILED) + .assertReportFileLines( + "not ok [ in: 1 / 1 ✔ | out: 0 / 0 ] dsn~exampleB~1 [draft] (impl, -utest)", + "not ok - 2 total, 1 direct, 0 transitive defects"); } @Test void filteredArtifactTypes() { - final BuildResult buildResult = runBuild(PROJECT_CUSTOM_CONFIG_DIR, "clean", - "traceRequirements", "-PfailBuild=true", - "-PfilteredArtifactTypes=dsn"); - assertThat(buildResult.task(":traceRequirements").getOutcome(), - either(is(TaskOutcome.SUCCESS)).or(is(TaskOutcome.FROM_CACHE))); + testFixture(PROJECT_CUSTOM_CONFIG_DIR) + .withArgs("clean", "traceRequirements", "-PfailBuild=true", + "-PfilteredArtifactTypes=dsn") + .run().assertTraceOutcomeSuccessOrFromCache(); } @Test void filteredWantedStatuses() { - final BuildResult buildResult = runBuild(PROJECT_CUSTOM_CONFIG_DIR, "clean", - "traceRequirements", - "-PfilterWantedStatuses=draft,approved"); - assertThat(buildResult.task(":traceRequirements").getOutcome(), - either(is(TaskOutcome.SUCCESS)).or(is(TaskOutcome.FROM_CACHE))); - TestUtil.assertFileContent( - PROJECT_CUSTOM_CONFIG_DIR.resolve("build/custom-report.txt"), - "not ok [ in: 1 / 1 ✔ | out: 0 / 0 ] dsn~exampleB~1 [draft] (impl, -utest)", - "not ok - 2 total, 1 direct, 0 transitive defects"); + testFixture(PROJECT_CUSTOM_CONFIG_DIR) + .withArgs("clean", "traceRequirements", + "-PfilterWantedStatuses=draft,approved") + .withReportFile(Path.of("build/custom-report.txt")) + .run().assertTraceOutcomeSuccessOrFromCache() + .assertReportFileLines( + "not ok [ in: 1 / 1 ✔ | out: 0 / 0 ] dsn~exampleB~1 [draft] (impl, -utest)", + "not ok - 2 total, 1 direct, 0 transitive defects"); } @Test void filteredWantedStatusesNoMatch() { - final BuildResult buildResult = runBuild(PROJECT_CUSTOM_CONFIG_DIR, "clean", - "traceRequirements", - "-PfilterWantedStatuses=approved"); - assertThat(buildResult.task(":traceRequirements").getOutcome(), - either(is(TaskOutcome.SUCCESS)).or(is(TaskOutcome.FROM_CACHE))); - TestUtil.assertFileContent( - PROJECT_CUSTOM_CONFIG_DIR.resolve("build/custom-report.txt"), - // Generated ID depends on JVM - "not ok [ in: 0 / 0 | out: 0 / 1 ✘ ] impl~exampleB-", - "not ok - 1 total, 1 direct, 0 transitive defects"); + testFixture(PROJECT_CUSTOM_CONFIG_DIR) + .withArgs("clean", "traceRequirements", + "-PfilterWantedStatuses=approved") + .withReportFile(Path.of("build/custom-report.txt")) + .run().assertTraceOutcomeSuccessOrFromCache() + .assertReportFileLines( + // Generated ID depends on JVM + "not ok [ in: 0 / 0 | out: 0 / 1 ✘ ] impl~exampleB-", + "not ok - 1 total, 1 direct, 0 transitive defects"); } @Test void filteredWantedStatusesInvalidStatus() { - final BuildResult buildResult = runBuildExpectFailure(PROJECT_CUSTOM_CONFIG_DIR, - "clean", - "traceRequirements", - "-PfilterWantedStatuses=invalid"); - assertThat(buildResult.getOutput(), containsString( - "Invalid status 'invalid'. Valid statuses are: APPROVED, PROPOSED, DRAFT, REJECTED")); + testFixture(PROJECT_CUSTOM_CONFIG_DIR) + .withArgs("clean", "traceRequirements", + "-PfilterWantedStatuses=invalid") + .runExpectingFailure() + .assertOutput(containsString( + "Invalid status 'invalid'. Valid statuses are: APPROVED, PROPOSED, DRAFT, REJECTED")); } @Test void testTraceExampleProjectWithCustomConfigFailBuildErrorMessage() { - try - { - runBuild(PROJECT_CUSTOM_CONFIG_DIR, "clean", "traceRequirements", - "-PfailBuild=true"); - } - catch (final UnexpectedBuildFailure e) - { - assertAll( - () -> assertEquals(TaskOutcome.FAILED, - e.getBuildResult() - .task(":traceRequirements") - .getOutcome()), - () -> assertThat(e.getMessage(), - startsWith("Unexpected build execution failure")), - () -> assertThat(e.getMessage(), - containsString("Requirement tracing found 1 defects. See report at"))); - } + final PluginTestFixture fixture = testFixture(PROJECT_CUSTOM_CONFIG_DIR) + .withArgs("clean", "traceRequirements", "-PfailBuild=true"); + final UnexpectedBuildFailure exception = assertThrows(UnexpectedBuildFailure.class, + fixture::run); + assertAll( + () -> assertEquals(TaskOutcome.FAILED, + exception.getBuildResult().task(":traceRequirements").getOutcome()), + () -> assertThat(exception.getMessage(), + startsWith("Unexpected build execution failure")), + () -> assertThat(exception.getMessage(), + containsString("Requirement tracing found 1 defects. See report at"))); } @Test void testTraceMultiProject() { - final BuildResult buildResult = runBuild(MULTI_PROJECT_DIR, "clean", - "traceRequirements"); - assertThat(buildResult.task(":traceRequirements").getOutcome(), - either(is(TaskOutcome.SUCCESS)).or(is(TaskOutcome.FROM_CACHE))); - TestUtil.assertFileContent(MULTI_PROJECT_DIR.resolve("build/custom-report.txt"), - "ok - 6 total"); + testFixture(MULTI_PROJECT_DIR).withArgs("clean", "traceRequirements") + .withReportFile(Path.of("build/custom-report.txt")) + .run().assertTraceOutcomeSuccessOrFromCache() + .assertReportFileLines("ok - 6 total"); } @Test void traceDependencyProject() { - BuildResult buildResult = runBuild(DEPENDENCY_CONFIG_DIR, "clean"); - assertThat(buildResult.task(":clean").getOutcome(), - either(is(TaskOutcome.SUCCESS)).or(is(TaskOutcome.UP_TO_DATE))); + testFixture(DEPENDENCY_CONFIG_DIR).withArgs("clean").run() + .assertOutcome(":clean", TaskOutcome.SUCCESS); final Path dependencyZip = DEPENDENCY_CONFIG_DIR .resolve("build/repo/requirements-1.0.zip"); createDependencyZip(dependencyZip); - buildResult = runBuild(DEPENDENCY_CONFIG_DIR, "traceRequirements"); - assertThat(buildResult.task(":traceRequirements").getOutcome(), - either(is(TaskOutcome.SUCCESS)).or(is(TaskOutcome.FROM_CACHE))); - TestUtil.assertFileContent( - DEPENDENCY_CONFIG_DIR.resolve("build/reports/tracing.txt"), - "requirements-1.0.zip!spec.md:2", - "requirements-1.0.zip!source.java:1", - "not ok - 2 total, 1 direct, 0 transitive defects"); + testFixture(DEPENDENCY_CONFIG_DIR).withArgs("traceRequirements") + .withReportFile(Path.of("build/reports/tracing.txt")) + .run().assertTraceOutcomeSuccessOrFromCache() + .assertReportFileLines( + "requirements-1.0.zip!spec.md:2", + "requirements-1.0.zip!source.java:1", + "not ok - 2 total, 1 direct, 0 transitive defects"); } @Test void publishToMavenRepo() { - final BuildResult buildResult = runBuild(PUBLISH_CONFIG_DIR, "clean", - "publishToMavenLocal"); - assertEquals(TaskOutcome.SUCCESS, - buildResult.task(":publishToMavenLocal").getOutcome()); + testFixture(PUBLISH_CONFIG_DIR).withArgs("clean", "publishToMavenLocal") + .run().assertOutcome(":publishToMavenLocal", TaskOutcome.SUCCESS); final Path archive = PUBLISH_CONFIG_DIR .resolve("build/distributions/publish-config-1.0.zip"); @@ -376,64 +354,12 @@ private static void createDependencyZip(final Path dependencyZip) } } - private BuildResult runBuildExpectFailure(final Path projectDir, final String... arguments) - { - return createGradleRunner(projectDir, arguments).buildAndFail(); - } - - private BuildResult runBuild(final Path projectDir, final String... arguments) - { - return createGradleRunner(projectDir, arguments).build(); - } - - private GradleRunner createGradleRunner(final Path projectDir, final String... arguments) - { - configureJacoco(projectDir); - final List allArgs = new ArrayList<>(); - allArgs.addAll(List.of(arguments)); - allArgs.addAll(List.of("--info", "--stacktrace", "--build-cache")); - if (configurationCacheEnabled()) - { - allArgs.add("--configuration-cache"); - } - if (ENABLE_WARNINGS) - { - allArgs.addAll(List.of("--warning-mode", "all")); - } - final GradleRunner runner = GradleRunner.create() - .withProjectDir(projectDir.toFile()) - .withPluginClasspath() - .withArguments(allArgs) - .forwardOutput(); - if (config.gradleVersion != null) - { - runner.withGradleVersion(config.gradleVersion); - } - return runner; - } - private static boolean configurationCacheEnabled() { return System.getProperty("enableConfigurationCache", "false") .equalsIgnoreCase("true"); } - private static void configureJacoco(final Path projectDir) - { - final Optional testkitGradleConfig = TestUtil - .readResource(OpenFastTracePluginTest.class, - "/testkit-gradle.properties"); - if (testkitGradleConfig.isEmpty()) - { - LOG.info("Testkit gradle config not available. Skipping configuration"); - return; - } - LOG.info("Found testkit gradle config: {}", testkitGradleConfig.get()); - final Path gradleProperties = projectDir.resolve("gradle.properties"); - LOG.info("Writing testkit gradle config to {}", gradleProperties); - TestUtil.writeFile(gradleProperties, testkitGradleConfig.get()); - } - private PluginTestFixture testFixture(final Path projectDir) { return PluginTestFixture.create(config, projectDir); diff --git a/src/test/java/org/itsallcode/openfasttrace/gradle/PluginTestFixture.java b/src/test/java/org/itsallcode/openfasttrace/gradle/PluginTestFixture.java index c7335c7..a9d30a3 100644 --- a/src/test/java/org/itsallcode/openfasttrace/gradle/PluginTestFixture.java +++ b/src/test/java/org/itsallcode/openfasttrace/gradle/PluginTestFixture.java @@ -52,6 +52,13 @@ Result run() return new Result(buildResult); } + Result runExpectingFailure() + { + final GradleRunner runner = createGradleRunner(); + final BuildResult buildResult = runner.buildAndFail(); + return new Result(buildResult); + } + private GradleRunner createGradleRunner() { configureJacoco(projectDir); From 652d9562cf088eab9a1185fcd9e9cd792322a49f Mon Sep 17 00:00:00 2001 From: kaklakariada Date: Thu, 20 Aug 2026 14:48:34 +0200 Subject: [PATCH 04/12] Cleanup tests --- .../gradle/OpenFastTracePluginTest.java | 46 +++++++++---------- .../gradle/PluginTestFixture.java | 6 ++- 2 files changed, 28 insertions(+), 24 deletions(-) diff --git a/src/test/java/org/itsallcode/openfasttrace/gradle/OpenFastTracePluginTest.java b/src/test/java/org/itsallcode/openfasttrace/gradle/OpenFastTracePluginTest.java index 388ac6b..03a7c9f 100644 --- a/src/test/java/org/itsallcode/openfasttrace/gradle/OpenFastTracePluginTest.java +++ b/src/test/java/org/itsallcode/openfasttrace/gradle/OpenFastTracePluginTest.java @@ -39,7 +39,7 @@ class OpenFastTracePluginTest @Test void tracingTaskAddedToProject() { - testFixture(PROJECT_DEFAULT_CONFIG_DIR).withArgs("tasks").run() + fixture(PROJECT_DEFAULT_CONFIG_DIR).withArgs("tasks").run() .assertOutput(containsString( "traceRequirements - Trace requirements and generate tracing report")); } @@ -59,12 +59,12 @@ void pluginUsesConfigurationCacheWithMultiModuleProject() private void testConfigurationCache(final Path projectDir) { assumeTrue(configurationCacheEnabled(), "Configuration cache is not enabled"); - final PluginTestFixture testFixture = testFixture(projectDir).withArgs("tasks"); + final PluginTestFixture fixture = fixture(projectDir).withArgs("tasks"); - testFixture.run().assertOutput(containsString( + fixture.run().assertOutput(containsString( "traceRequirements - Trace requirements and generate tracing report")); - testFixture.run().assertOutput(allOf(containsString( + fixture.run().assertOutput(allOf(containsString( "traceRequirements - Trace requirements and generate tracing report"), containsString("Reusing configuration cache."))); } @@ -72,7 +72,7 @@ private void testConfigurationCache(final Path projectDir) @Test void testTraceExampleProjectWithDefaultConfig() { - testFixture(PROJECT_DEFAULT_CONFIG_DIR).withArgs("clean", "traceRequirements") + fixture(PROJECT_DEFAULT_CONFIG_DIR).withArgs("clean", "traceRequirements") .withReportFile(Path.of("build/reports/tracing.txt")) .run() .assertTraceOutcomeSuccessOrFromCache() @@ -82,7 +82,7 @@ void testTraceExampleProjectWithDefaultConfig() @Test void testCollectExampleProjectWithCustomConfig() { - testFixture(PROJECT_CUSTOM_CONFIG_DIR).withArgs("clean", "collectRequirements") + fixture(PROJECT_CUSTOM_CONFIG_DIR).withArgs("clean", "collectRequirements") .withReportFile(Path.of("build/reports/requirements.xml")) .run().assertCollectOutcomeSuccessOrFromCache() .assertReportFileLines("\n" + @@ -133,7 +133,7 @@ void testCollectExampleProjectWithCustomConfig() @Test void testCollectIsUpToDateWhenAlreadyRunBefore() { - final PluginTestFixture fixture = testFixture(PROJECT_CUSTOM_CONFIG_DIR).withArgs("clean", + final PluginTestFixture fixture = fixture(PROJECT_CUSTOM_CONFIG_DIR).withArgs("clean", "collectRequirements"); fixture .run().assertCollectOutcomeSuccessOrFromCache() @@ -147,7 +147,7 @@ void testCollectIsUpToDateWhenAlreadyRunBefore() @Test void testHtmlReportConfig() { - testFixture(HTML_REPORT_CONFIG_DIR) + fixture(HTML_REPORT_CONFIG_DIR) .withArgs("clean", "traceRequirements") .withReportFile(Path.of("build/reports/tracing.html")) .run() @@ -160,17 +160,17 @@ void testHtmlReportConfig() @Test void testTraceTaskUpToDateWhenAlreadyRun() { - final PluginTestFixture testFixture = testFixture(HTML_REPORT_CONFIG_DIR); - testFixture.withArgs("clean", "traceRequirements").run() + final PluginTestFixture fixture = fixture(HTML_REPORT_CONFIG_DIR); + fixture.withArgs("clean", "traceRequirements").run() .assertTraceOutcomeSuccessOrFromCache(); - testFixture.withArgs("traceRequirements").run().assertOutcome(":traceRequirements", + fixture.withArgs("traceRequirements").run().assertOutcome(":traceRequirements", TaskOutcome.UP_TO_DATE); } @Test void testTraceExampleProjectWithCustomConfig() { - testFixture(PROJECT_CUSTOM_CONFIG_DIR).withArgs("clean", "traceRequirements") + fixture(PROJECT_CUSTOM_CONFIG_DIR).withArgs("clean", "traceRequirements") .withReportFile(Path.of("build/custom-report.txt")) .run().assertTraceOutcomeSuccessOrFromCache() .assertReportFileLines( @@ -181,7 +181,7 @@ void testTraceExampleProjectWithCustomConfig() @Test void testTraceExampleProjectWithCustomConfigFailBuild() { - testFixture(PROJECT_CUSTOM_CONFIG_DIR) + fixture(PROJECT_CUSTOM_CONFIG_DIR) .withArgs("clean", "traceRequirements", "-PfailBuild=true") .withReportFile(Path.of("build/custom-report.txt")) .runExpectingFailure() @@ -194,7 +194,7 @@ void testTraceExampleProjectWithCustomConfigFailBuild() @Test void filteredArtifactTypes() { - testFixture(PROJECT_CUSTOM_CONFIG_DIR) + fixture(PROJECT_CUSTOM_CONFIG_DIR) .withArgs("clean", "traceRequirements", "-PfailBuild=true", "-PfilteredArtifactTypes=dsn") .run().assertTraceOutcomeSuccessOrFromCache(); @@ -203,7 +203,7 @@ void filteredArtifactTypes() @Test void filteredWantedStatuses() { - testFixture(PROJECT_CUSTOM_CONFIG_DIR) + fixture(PROJECT_CUSTOM_CONFIG_DIR) .withArgs("clean", "traceRequirements", "-PfilterWantedStatuses=draft,approved") .withReportFile(Path.of("build/custom-report.txt")) @@ -216,7 +216,7 @@ void filteredWantedStatuses() @Test void filteredWantedStatusesNoMatch() { - testFixture(PROJECT_CUSTOM_CONFIG_DIR) + fixture(PROJECT_CUSTOM_CONFIG_DIR) .withArgs("clean", "traceRequirements", "-PfilterWantedStatuses=approved") .withReportFile(Path.of("build/custom-report.txt")) @@ -230,7 +230,7 @@ void filteredWantedStatusesNoMatch() @Test void filteredWantedStatusesInvalidStatus() { - testFixture(PROJECT_CUSTOM_CONFIG_DIR) + fixture(PROJECT_CUSTOM_CONFIG_DIR) .withArgs("clean", "traceRequirements", "-PfilterWantedStatuses=invalid") .runExpectingFailure() @@ -241,7 +241,7 @@ void filteredWantedStatusesInvalidStatus() @Test void testTraceExampleProjectWithCustomConfigFailBuildErrorMessage() { - final PluginTestFixture fixture = testFixture(PROJECT_CUSTOM_CONFIG_DIR) + final PluginTestFixture fixture = fixture(PROJECT_CUSTOM_CONFIG_DIR) .withArgs("clean", "traceRequirements", "-PfailBuild=true"); final UnexpectedBuildFailure exception = assertThrows(UnexpectedBuildFailure.class, fixture::run); @@ -257,7 +257,7 @@ void testTraceExampleProjectWithCustomConfigFailBuildErrorMessage() @Test void testTraceMultiProject() { - testFixture(MULTI_PROJECT_DIR).withArgs("clean", "traceRequirements") + fixture(MULTI_PROJECT_DIR).withArgs("clean", "traceRequirements") .withReportFile(Path.of("build/custom-report.txt")) .run().assertTraceOutcomeSuccessOrFromCache() .assertReportFileLines("ok - 6 total"); @@ -266,13 +266,13 @@ void testTraceMultiProject() @Test void traceDependencyProject() { - testFixture(DEPENDENCY_CONFIG_DIR).withArgs("clean").run() + fixture(DEPENDENCY_CONFIG_DIR).withArgs("clean").run() .assertOutcome(":clean", TaskOutcome.SUCCESS); final Path dependencyZip = DEPENDENCY_CONFIG_DIR .resolve("build/repo/requirements-1.0.zip"); createDependencyZip(dependencyZip); - testFixture(DEPENDENCY_CONFIG_DIR).withArgs("traceRequirements") + fixture(DEPENDENCY_CONFIG_DIR).withArgs("traceRequirements") .withReportFile(Path.of("build/reports/tracing.txt")) .run().assertTraceOutcomeSuccessOrFromCache() .assertReportFileLines( @@ -284,7 +284,7 @@ void traceDependencyProject() @Test void publishToMavenRepo() { - testFixture(PUBLISH_CONFIG_DIR).withArgs("clean", "publishToMavenLocal") + fixture(PUBLISH_CONFIG_DIR).withArgs("clean", "publishToMavenLocal") .run().assertOutcome(":publishToMavenLocal", TaskOutcome.SUCCESS); final Path archive = PUBLISH_CONFIG_DIR @@ -360,7 +360,7 @@ private static boolean configurationCacheEnabled() .equalsIgnoreCase("true"); } - private PluginTestFixture testFixture(final Path projectDir) + private PluginTestFixture fixture(final Path projectDir) { return PluginTestFixture.create(config, projectDir); } diff --git a/src/test/java/org/itsallcode/openfasttrace/gradle/PluginTestFixture.java b/src/test/java/org/itsallcode/openfasttrace/gradle/PluginTestFixture.java index a9d30a3..39d9c4e 100644 --- a/src/test/java/org/itsallcode/openfasttrace/gradle/PluginTestFixture.java +++ b/src/test/java/org/itsallcode/openfasttrace/gradle/PluginTestFixture.java @@ -4,6 +4,7 @@ import static org.hamcrest.CoreMatchers.is; import static org.hamcrest.MatcherAssert.assertThat; import static org.junit.jupiter.api.Assertions.assertEquals; +import static org.junit.jupiter.api.Assertions.assertNotNull; import java.nio.file.Path; import java.util.*; @@ -61,6 +62,7 @@ Result runExpectingFailure() private GradleRunner createGradleRunner() { + assertNotNull(arguments, "Arguments must be set before running the test fixture"); configureJacoco(projectDir); final List allArgs = new ArrayList<>(); allArgs.addAll(List.of(arguments)); @@ -91,7 +93,7 @@ private static boolean configurationCacheEnabled() private static void configureJacoco(final Path projectDir) { final Optional testkitGradleConfig = TestUtil - .readResource(OpenFastTracePluginTest.class, + .readResource(PluginTestFixture.class, "/testkit-gradle.properties"); if (testkitGradleConfig.isEmpty()) { @@ -150,6 +152,8 @@ Result assertOutcomeSuccessOrFromCache(final String taskPath) Result assertReportFileLines(final String... lines) { + assertNotNull(relativeReportPath, + "Report file path must be set before asserting report file lines"); final Path reportFile = projectDir.resolve(relativeReportPath); TestUtil.assertFileContent(reportFile, lines); return this; From c3c4504ed973bf6b56ccbb9ac4dc62e18630bc5f Mon Sep 17 00:00:00 2001 From: kaklakariada Date: Thu, 20 Aug 2026 14:50:27 +0200 Subject: [PATCH 05/12] Add changelog entry --- CHANGELOG.md | 3 +++ 1 file changed, 3 insertions(+) diff --git a/CHANGELOG.md b/CHANGELOG.md index 9c7cf61..0ec9b38 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -6,6 +6,9 @@ and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0 ## [Unreleased] +- [PR #82](https://github.com/itsallcode/openfasttrace-gradle/pull/82) + - Refactor plugin integration tests + ## [3.2.0] - 2026-08-18 - [PR #79](https://github.com/itsallcode/openfasttrace-gradle/pull/79) From 7eb906f595f526eda439d021bcc5796f2d6cd160 Mon Sep 17 00:00:00 2001 From: kaklakariada Date: Thu, 20 Aug 2026 14:55:09 +0200 Subject: [PATCH 06/12] Code cleanup --- .../gradle/OpenFastTracePluginTest.java | 34 +++++++++++-------- .../gradle/PluginTestFixture.java | 1 - 2 files changed, 19 insertions(+), 16 deletions(-) diff --git a/src/test/java/org/itsallcode/openfasttrace/gradle/OpenFastTracePluginTest.java b/src/test/java/org/itsallcode/openfasttrace/gradle/OpenFastTracePluginTest.java index 03a7c9f..ce61879 100644 --- a/src/test/java/org/itsallcode/openfasttrace/gradle/OpenFastTracePluginTest.java +++ b/src/test/java/org/itsallcode/openfasttrace/gradle/OpenFastTracePluginTest.java @@ -133,10 +133,9 @@ void testCollectExampleProjectWithCustomConfig() @Test void testCollectIsUpToDateWhenAlreadyRunBefore() { - final PluginTestFixture fixture = fixture(PROJECT_CUSTOM_CONFIG_DIR).withArgs("clean", - "collectRequirements"); - fixture - .run().assertCollectOutcomeSuccessOrFromCache() + final PluginTestFixture fixture = fixture(PROJECT_CUSTOM_CONFIG_DIR); + fixture.withArgs("clean", "collectRequirements").run() + .assertCollectOutcomeSuccessOrFromCache() .assertOutcomeSuccessOrFromCache(":clean") .assertCollectOutcomeSuccessOrFromCache(); @@ -172,7 +171,8 @@ void testTraceExampleProjectWithCustomConfig() { fixture(PROJECT_CUSTOM_CONFIG_DIR).withArgs("clean", "traceRequirements") .withReportFile(Path.of("build/custom-report.txt")) - .run().assertTraceOutcomeSuccessOrFromCache() + .run() + .assertTraceOutcomeSuccessOrFromCache() .assertReportFileLines( "not ok [ in: 1 / 1 ✔ | out: 0 / 0 ] dsn~exampleB~1 [draft] (impl, -utest)", "not ok - 2 total, 1 direct, 0 transitive defects"); @@ -197,7 +197,8 @@ void filteredArtifactTypes() fixture(PROJECT_CUSTOM_CONFIG_DIR) .withArgs("clean", "traceRequirements", "-PfailBuild=true", "-PfilteredArtifactTypes=dsn") - .run().assertTraceOutcomeSuccessOrFromCache(); + .run() + .assertTraceOutcomeSuccessOrFromCache(); } @Test @@ -207,7 +208,8 @@ void filteredWantedStatuses() .withArgs("clean", "traceRequirements", "-PfilterWantedStatuses=draft,approved") .withReportFile(Path.of("build/custom-report.txt")) - .run().assertTraceOutcomeSuccessOrFromCache() + .run() + .assertTraceOutcomeSuccessOrFromCache() .assertReportFileLines( "not ok [ in: 1 / 1 ✔ | out: 0 / 0 ] dsn~exampleB~1 [draft] (impl, -utest)", "not ok - 2 total, 1 direct, 0 transitive defects"); @@ -217,10 +219,10 @@ void filteredWantedStatuses() void filteredWantedStatusesNoMatch() { fixture(PROJECT_CUSTOM_CONFIG_DIR) - .withArgs("clean", "traceRequirements", - "-PfilterWantedStatuses=approved") + .withArgs("clean", "traceRequirements", "-PfilterWantedStatuses=approved") .withReportFile(Path.of("build/custom-report.txt")) - .run().assertTraceOutcomeSuccessOrFromCache() + .run() + .assertTraceOutcomeSuccessOrFromCache() .assertReportFileLines( // Generated ID depends on JVM "not ok [ in: 0 / 0 | out: 0 / 1 ✘ ] impl~exampleB-", @@ -231,8 +233,7 @@ void filteredWantedStatusesNoMatch() void filteredWantedStatusesInvalidStatus() { fixture(PROJECT_CUSTOM_CONFIG_DIR) - .withArgs("clean", "traceRequirements", - "-PfilterWantedStatuses=invalid") + .withArgs("clean", "traceRequirements", "-PfilterWantedStatuses=invalid") .runExpectingFailure() .assertOutput(containsString( "Invalid status 'invalid'. Valid statuses are: APPROVED, PROPOSED, DRAFT, REJECTED")); @@ -259,7 +260,8 @@ void testTraceMultiProject() { fixture(MULTI_PROJECT_DIR).withArgs("clean", "traceRequirements") .withReportFile(Path.of("build/custom-report.txt")) - .run().assertTraceOutcomeSuccessOrFromCache() + .run() + .assertTraceOutcomeSuccessOrFromCache() .assertReportFileLines("ok - 6 total"); } @@ -274,7 +276,8 @@ void traceDependencyProject() fixture(DEPENDENCY_CONFIG_DIR).withArgs("traceRequirements") .withReportFile(Path.of("build/reports/tracing.txt")) - .run().assertTraceOutcomeSuccessOrFromCache() + .run() + .assertTraceOutcomeSuccessOrFromCache() .assertReportFileLines( "requirements-1.0.zip!spec.md:2", "requirements-1.0.zip!source.java:1", @@ -285,7 +288,8 @@ void traceDependencyProject() void publishToMavenRepo() { fixture(PUBLISH_CONFIG_DIR).withArgs("clean", "publishToMavenLocal") - .run().assertOutcome(":publishToMavenLocal", TaskOutcome.SUCCESS); + .run() + .assertOutcome(":publishToMavenLocal", TaskOutcome.SUCCESS); final Path archive = PUBLISH_CONFIG_DIR .resolve("build/distributions/publish-config-1.0.zip"); diff --git a/src/test/java/org/itsallcode/openfasttrace/gradle/PluginTestFixture.java b/src/test/java/org/itsallcode/openfasttrace/gradle/PluginTestFixture.java index 39d9c4e..931cc13 100644 --- a/src/test/java/org/itsallcode/openfasttrace/gradle/PluginTestFixture.java +++ b/src/test/java/org/itsallcode/openfasttrace/gradle/PluginTestFixture.java @@ -108,7 +108,6 @@ private static void configureJacoco(final Path projectDir) class Result { - private final BuildResult buildResult; private Result(final BuildResult buildResult) From 75fea5120ba08501f4d7184b523a2e5673d3bbbe Mon Sep 17 00:00:00 2001 From: kaklakariada Date: Thu, 20 Aug 2026 20:08:37 +0200 Subject: [PATCH 07/12] Fix integration tests --- .../gradle/OpenFastTracePluginTest.java | 24 ++++++++++++------- .../gradle/PluginTestFixture.java | 16 +++++++------ 2 files changed, 25 insertions(+), 15 deletions(-) diff --git a/src/test/java/org/itsallcode/openfasttrace/gradle/OpenFastTracePluginTest.java b/src/test/java/org/itsallcode/openfasttrace/gradle/OpenFastTracePluginTest.java index ce61879..6db0b20 100644 --- a/src/test/java/org/itsallcode/openfasttrace/gradle/OpenFastTracePluginTest.java +++ b/src/test/java/org/itsallcode/openfasttrace/gradle/OpenFastTracePluginTest.java @@ -85,8 +85,9 @@ void testCollectExampleProjectWithCustomConfig() fixture(PROJECT_CUSTOM_CONFIG_DIR).withArgs("clean", "collectRequirements") .withReportFile(Path.of("build/reports/requirements.xml")) .run().assertCollectOutcomeSuccessOrFromCache() - .assertReportFileLines("\n" + - "", + .assertReportFileLines( + "\n" + + "", """ @@ -219,7 +220,8 @@ void filteredWantedStatuses() void filteredWantedStatusesNoMatch() { fixture(PROJECT_CUSTOM_CONFIG_DIR) - .withArgs("clean", "traceRequirements", "-PfilterWantedStatuses=approved") + .withArgs("clean", "traceRequirements", + "-PfilterWantedStatuses=approved") .withReportFile(Path.of("build/custom-report.txt")) .run() .assertTraceOutcomeSuccessOrFromCache() @@ -233,7 +235,8 @@ void filteredWantedStatusesNoMatch() void filteredWantedStatusesInvalidStatus() { fixture(PROJECT_CUSTOM_CONFIG_DIR) - .withArgs("clean", "traceRequirements", "-PfilterWantedStatuses=invalid") + .withArgs("clean", "traceRequirements", + "-PfilterWantedStatuses=invalid") .runExpectingFailure() .assertOutput(containsString( "Invalid status 'invalid'. Valid statuses are: APPROVED, PROPOSED, DRAFT, REJECTED")); @@ -248,7 +251,9 @@ void testTraceExampleProjectWithCustomConfigFailBuildErrorMessage() fixture::run); assertAll( () -> assertEquals(TaskOutcome.FAILED, - exception.getBuildResult().task(":traceRequirements").getOutcome()), + exception.getBuildResult() + .task(":traceRequirements") + .getOutcome()), () -> assertThat(exception.getMessage(), startsWith("Unexpected build execution failure")), () -> assertThat(exception.getMessage(), @@ -268,13 +273,16 @@ void testTraceMultiProject() @Test void traceDependencyProject() { - fixture(DEPENDENCY_CONFIG_DIR).withArgs("clean").run() - .assertOutcome(":clean", TaskOutcome.SUCCESS); + final PluginTestFixture fixture = fixture(DEPENDENCY_CONFIG_DIR); + fixture.withArgs("clean") + .run() + .assertOutcomeSuccessOrFromCache(":clean"); + final Path dependencyZip = DEPENDENCY_CONFIG_DIR .resolve("build/repo/requirements-1.0.zip"); createDependencyZip(dependencyZip); - fixture(DEPENDENCY_CONFIG_DIR).withArgs("traceRequirements") + fixture.withArgs("traceRequirements") .withReportFile(Path.of("build/reports/tracing.txt")) .run() .assertTraceOutcomeSuccessOrFromCache() diff --git a/src/test/java/org/itsallcode/openfasttrace/gradle/PluginTestFixture.java b/src/test/java/org/itsallcode/openfasttrace/gradle/PluginTestFixture.java index 931cc13..ba78e36 100644 --- a/src/test/java/org/itsallcode/openfasttrace/gradle/PluginTestFixture.java +++ b/src/test/java/org/itsallcode/openfasttrace/gradle/PluginTestFixture.java @@ -1,9 +1,7 @@ package org.itsallcode.openfasttrace.gradle; -import static org.hamcrest.CoreMatchers.either; -import static org.hamcrest.CoreMatchers.is; +import static org.hamcrest.CoreMatchers.*; import static org.hamcrest.MatcherAssert.assertThat; -import static org.junit.jupiter.api.Assertions.assertEquals; import static org.junit.jupiter.api.Assertions.assertNotNull; import java.nio.file.Path; @@ -132,8 +130,13 @@ Result assertCollectOutcomeUpToDate() Result assertOutcome(final String taskPath, final TaskOutcome expectedOutcome) { - assertEquals(expectedOutcome, buildResult.task(taskPath).getOutcome(), - "Outcome of task " + taskPath); + return assertOutcome(taskPath, equalTo(expectedOutcome)); + } + + Result assertOutcome(final String taskPath, final Matcher matcher) + { + assertThat("Outcome of task " + taskPath, buildResult.task(taskPath).getOutcome(), + matcher); return this; } @@ -144,9 +147,8 @@ Result assertCollectOutcomeSuccessOrFromCache() Result assertOutcomeSuccessOrFromCache(final String taskPath) { - assertThat(buildResult.task(taskPath).getOutcome(), + return assertOutcome(taskPath, either(is(TaskOutcome.SUCCESS)).or(is(TaskOutcome.FROM_CACHE))); - return this; } Result assertReportFileLines(final String... lines) From 5e332ff90ebc17de58fe6ee2e383c895f408582d Mon Sep 17 00:00:00 2001 From: kaklakariada Date: Thu, 20 Aug 2026 20:16:31 +0200 Subject: [PATCH 08/12] Adapt integration test --- .../openfasttrace/gradle/OpenFastTracePluginTest.java | 7 ++++--- 1 file changed, 4 insertions(+), 3 deletions(-) diff --git a/src/test/java/org/itsallcode/openfasttrace/gradle/OpenFastTracePluginTest.java b/src/test/java/org/itsallcode/openfasttrace/gradle/OpenFastTracePluginTest.java index 6db0b20..f1f1d9f 100644 --- a/src/test/java/org/itsallcode/openfasttrace/gradle/OpenFastTracePluginTest.java +++ b/src/test/java/org/itsallcode/openfasttrace/gradle/OpenFastTracePluginTest.java @@ -1,10 +1,10 @@ package org.itsallcode.openfasttrace.gradle; import static java.util.stream.Collectors.joining; -import static org.hamcrest.CoreMatchers.containsString; -import static org.hamcrest.CoreMatchers.startsWith; +import static org.hamcrest.CoreMatchers.*; import static org.hamcrest.MatcherAssert.assertThat; import static org.hamcrest.Matchers.allOf; +import static org.hamcrest.Matchers.either; import static org.junit.jupiter.api.Assertions.*; import static org.junit.jupiter.api.Assumptions.assumeTrue; @@ -276,7 +276,8 @@ void traceDependencyProject() final PluginTestFixture fixture = fixture(DEPENDENCY_CONFIG_DIR); fixture.withArgs("clean") .run() - .assertOutcomeSuccessOrFromCache(":clean"); + .assertOutcome(":clean", + either(is(TaskOutcome.SUCCESS)).or(is(TaskOutcome.UP_TO_DATE))); final Path dependencyZip = DEPENDENCY_CONFIG_DIR .resolve("build/repo/requirements-1.0.zip"); From bd6c9a50cd6dd644f2a1f439a9946d147518e17c Mon Sep 17 00:00:00 2001 From: kaklakariada Date: Thu, 20 Aug 2026 20:22:44 +0200 Subject: [PATCH 09/12] Update ignored files --- .gitignore | 3 +++ 1 file changed, 3 insertions(+) diff --git a/.gitignore b/.gitignore index 363ecd5..5afaf07 100644 --- a/.gitignore +++ b/.gitignore @@ -3,6 +3,9 @@ /.project /.classpath /.settings/org.eclipse.buildship.core.prefs +/.settings/org.eclipse.core.resources.prefs +/.settings/org.eclipse.jdt.apt.core.prefs +/.settings/org.eclipse.m2e.core.prefs /bin/ /build/ /.idea/ From 7950acbd0a715cb19152832befb8443d1b26dcd8 Mon Sep 17 00:00:00 2001 From: kaklakariada Date: Thu, 20 Aug 2026 20:25:44 +0200 Subject: [PATCH 10/12] Remove duplicated code --- .../gradle/OpenFastTracePluginTest.java | 12 +++--------- .../openfasttrace/gradle/PluginTestFixture.java | 8 +++++++- 2 files changed, 10 insertions(+), 10 deletions(-) diff --git a/src/test/java/org/itsallcode/openfasttrace/gradle/OpenFastTracePluginTest.java b/src/test/java/org/itsallcode/openfasttrace/gradle/OpenFastTracePluginTest.java index f1f1d9f..10698cb 100644 --- a/src/test/java/org/itsallcode/openfasttrace/gradle/OpenFastTracePluginTest.java +++ b/src/test/java/org/itsallcode/openfasttrace/gradle/OpenFastTracePluginTest.java @@ -6,7 +6,6 @@ import static org.hamcrest.Matchers.allOf; import static org.hamcrest.Matchers.either; import static org.junit.jupiter.api.Assertions.*; -import static org.junit.jupiter.api.Assumptions.assumeTrue; import java.io.*; import java.nio.file.*; @@ -58,7 +57,7 @@ void pluginUsesConfigurationCacheWithMultiModuleProject() private void testConfigurationCache(final Path projectDir) { - assumeTrue(configurationCacheEnabled(), "Configuration cache is not enabled"); + PluginTestFixture.assumeConfigurationCacheEnabled(); final PluginTestFixture fixture = fixture(projectDir).withArgs("tasks"); fixture.run().assertOutput(containsString( @@ -277,7 +276,8 @@ void traceDependencyProject() fixture.withArgs("clean") .run() .assertOutcome(":clean", - either(is(TaskOutcome.SUCCESS)).or(is(TaskOutcome.UP_TO_DATE))); + either(is(TaskOutcome.SUCCESS)) + .or(is(TaskOutcome.UP_TO_DATE))); final Path dependencyZip = DEPENDENCY_CONFIG_DIR .resolve("build/repo/requirements-1.0.zip"); @@ -367,12 +367,6 @@ private static void createDependencyZip(final Path dependencyZip) } } - private static boolean configurationCacheEnabled() - { - return System.getProperty("enableConfigurationCache", "false") - .equalsIgnoreCase("true"); - } - private PluginTestFixture fixture(final Path projectDir) { return PluginTestFixture.create(config, projectDir); diff --git a/src/test/java/org/itsallcode/openfasttrace/gradle/PluginTestFixture.java b/src/test/java/org/itsallcode/openfasttrace/gradle/PluginTestFixture.java index ba78e36..698b35f 100644 --- a/src/test/java/org/itsallcode/openfasttrace/gradle/PluginTestFixture.java +++ b/src/test/java/org/itsallcode/openfasttrace/gradle/PluginTestFixture.java @@ -3,6 +3,7 @@ import static org.hamcrest.CoreMatchers.*; import static org.hamcrest.MatcherAssert.assertThat; import static org.junit.jupiter.api.Assertions.assertNotNull; +import static org.junit.jupiter.api.Assumptions.assumeTrue; import java.nio.file.Path; import java.util.*; @@ -82,7 +83,12 @@ private GradleRunner createGradleRunner() return runner; } - private static boolean configurationCacheEnabled() + static void assumeConfigurationCacheEnabled() + { + assumeTrue(configurationCacheEnabled(), "Configuration cache is not enabled"); + } + + static boolean configurationCacheEnabled() { return System.getProperty("enableConfigurationCache", "false") .equalsIgnoreCase("true"); From 71b760b68a32185f4ade96def3f7167f8fa1866a Mon Sep 17 00:00:00 2001 From: kaklakariada Date: Thu, 20 Aug 2026 20:33:53 +0200 Subject: [PATCH 11/12] Implement copilot review findings --- .../openfasttrace/gradle/OpenFastTracePluginTest.java | 5 +++-- .../itsallcode/openfasttrace/gradle/PluginTestFixture.java | 5 +++-- 2 files changed, 6 insertions(+), 4 deletions(-) diff --git a/src/test/java/org/itsallcode/openfasttrace/gradle/OpenFastTracePluginTest.java b/src/test/java/org/itsallcode/openfasttrace/gradle/OpenFastTracePluginTest.java index 10698cb..ad30b19 100644 --- a/src/test/java/org/itsallcode/openfasttrace/gradle/OpenFastTracePluginTest.java +++ b/src/test/java/org/itsallcode/openfasttrace/gradle/OpenFastTracePluginTest.java @@ -135,8 +135,9 @@ void testCollectIsUpToDateWhenAlreadyRunBefore() { final PluginTestFixture fixture = fixture(PROJECT_CUSTOM_CONFIG_DIR); fixture.withArgs("clean", "collectRequirements").run() - .assertCollectOutcomeSuccessOrFromCache() - .assertOutcomeSuccessOrFromCache(":clean") + .assertOutcome(":clean", + either(is(TaskOutcome.SUCCESS)) + .or(is(TaskOutcome.UP_TO_DATE))) .assertCollectOutcomeSuccessOrFromCache(); fixture.withArgs("collectRequirements").run() diff --git a/src/test/java/org/itsallcode/openfasttrace/gradle/PluginTestFixture.java b/src/test/java/org/itsallcode/openfasttrace/gradle/PluginTestFixture.java index 698b35f..ba20c4d 100644 --- a/src/test/java/org/itsallcode/openfasttrace/gradle/PluginTestFixture.java +++ b/src/test/java/org/itsallcode/openfasttrace/gradle/PluginTestFixture.java @@ -141,8 +141,9 @@ Result assertOutcome(final String taskPath, final TaskOutcome expectedOutcome) Result assertOutcome(final String taskPath, final Matcher matcher) { - assertThat("Outcome of task " + taskPath, buildResult.task(taskPath).getOutcome(), - matcher); + final BuildTask task = buildResult.task(taskPath); + assertNotNull(task, "Task '" + taskPath + "' was not executed"); + assertThat("Outcome of task " + taskPath, task.getOutcome(), matcher); return this; } From c23b48c3e8bb51eb4d1a7d80da5173526350a711 Mon Sep 17 00:00:00 2001 From: kaklakariada Date: Thu, 20 Aug 2026 20:39:33 +0200 Subject: [PATCH 12/12] Speedup sonar build --- build.gradle | 2 -- 1 file changed, 2 deletions(-) diff --git a/build.gradle b/build.gradle index 5f32a3a..5cb7ddc 100644 --- a/build.gradle +++ b/build.gradle @@ -156,8 +156,6 @@ test { finalizedBy jacocoTestReport } -project.tasks["sonar"].dependsOn jacocoTestReport - sonarqube { properties { property "sonar.host.url", "https://sonarcloud.io"