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/
diff --git a/CHANGELOG.md b/CHANGELOG.md
index 46519fe..d72b830 100644
--- a/CHANGELOG.md
+++ b/CHANGELOG.md
@@ -8,6 +8,8 @@ and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0
- [PR #80](https://github.com/itsallcode/openfasttrace-gradle/pull/80)
- Fix JavaDoc warnings and let build fail on warnings
+- [PR #82](https://github.com/itsallcode/openfasttrace-gradle/pull/82)
+ - Refactor plugin integration tests
## [3.2.0] - 2026-08-18
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"
diff --git a/src/test/java/org/itsallcode/openfasttrace/gradle/OpenFastTracePluginTest.java b/src/test/java/org/itsallcode/openfasttrace/gradle/OpenFastTracePluginTest.java
index 34d9f15..ad30b19 100644
--- a/src/test/java/org/itsallcode/openfasttrace/gradle/OpenFastTracePluginTest.java
+++ b/src/test/java/org/itsallcode/openfasttrace/gradle/OpenFastTracePluginTest.java
@@ -6,31 +6,26 @@
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");
+ 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 +38,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"));
+ fixture(PROJECT_DEFAULT_CONFIG_DIR).withArgs("tasks").run()
+ .assertOutput(containsString(
+ "traceRequirements - Trace requirements and generate tracing report"));
}
@Test
@@ -62,247 +57,249 @@ void pluginUsesConfigurationCacheWithMultiModuleProject()
private void testConfigurationCache(final Path projectDir)
{
- assumeTrue(configurationCacheEnabled(), "Configuration cache is not enabled");
- BuildResult buildResult = runBuild(projectDir, "tasks");
- assertThat(buildResult.getOutput(), containsString(
+ PluginTestFixture.assumeConfigurationCacheEnabled();
+ final PluginTestFixture fixture = fixture(projectDir).withArgs("tasks");
+
+ fixture.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.")));
+
+ fixture.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");
+ fixture(PROJECT_DEFAULT_CONFIG_DIR).withArgs("clean", "traceRequirements")
+ .withReportFile(Path.of("build/reports/tracing.txt"))
+ .run()
+ .assertTraceOutcomeSuccessOrFromCache()
+ .assertReportFileLines("ok - 0 total");
}
@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" +
- "",
- """
-
-
- exampleB\
- """, """
-
- approved
- 0
- """,
-
- """
- 1
-
-
- dsn:exampleB
- 1
-
-
- """,
-
- """
-
-
- exampleB
- Tracing Example
- draft
- 1
- """,
-
- """
- 2
- Example requirement
-
- utest
- impl
-
-
- """,
-
- " \n" +
- "");
+ fixture(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" +
+ "");
}
@Test
void testCollectIsUpToDateWhenAlreadyRunBefore()
{
- BuildResult buildResult = runBuild(PROJECT_CUSTOM_CONFIG_DIR, "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());
+ final PluginTestFixture fixture = fixture(PROJECT_CUSTOM_CONFIG_DIR);
+ fixture.withArgs("clean", "collectRequirements").run()
+ .assertOutcome(":clean",
+ either(is(TaskOutcome.SUCCESS))
+ .or(is(TaskOutcome.UP_TO_DATE)))
+ .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"),
- "",
- "✗",
- "");
+ fixture(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 fixture = fixture(HTML_REPORT_CONFIG_DIR);
+ fixture.withArgs("clean", "traceRequirements").run()
+ .assertTraceOutcomeSuccessOrFromCache();
+ fixture.withArgs("traceRequirements").run().assertOutcome(":traceRequirements",
+ TaskOutcome.UP_TO_DATE);
}
@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");
+ fixture(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");
+ fixture(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)));
+ fixture(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");
+ fixture(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");
+ fixture(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"));
+ fixture(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 = fixture(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");
+ fixture(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)));
- final Path dependencyZip = DEPENDENCY_CONFIG_DIR.resolve("build/repo/requirements-1.0.zip");
+ final PluginTestFixture fixture = fixture(DEPENDENCY_CONFIG_DIR);
+ fixture.withArgs("clean")
+ .run()
+ .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");
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");
+ fixture.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());
+ fixture(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");
@@ -366,64 +363,13 @@ private static void createDependencyZip(final Path dependencyZip)
}
catch (final IOException e)
{
- throw new UncheckedIOException("Failed to create dependency zip " + dependencyZip, e);
- }
- }
-
- 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"));
+ throw new UncheckedIOException(
+ "Failed to create dependency zip " + dependencyZip, e);
}
- 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()
+ private PluginTestFixture fixture(final Path projectDir)
{
- 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());
+ 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..ba20c4d
--- /dev/null
+++ b/src/test/java/org/itsallcode/openfasttrace/gradle/PluginTestFixture.java
@@ -0,0 +1,170 @@
+package org.itsallcode.openfasttrace.gradle;
+
+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.*;
+
+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);
+ }
+
+ Result runExpectingFailure()
+ {
+ final GradleRunner runner = createGradleRunner();
+ final BuildResult buildResult = runner.buildAndFail();
+ return new Result(buildResult);
+ }
+
+ 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));
+ 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;
+ }
+
+ static void assumeConfigurationCacheEnabled()
+ {
+ assumeTrue(configurationCacheEnabled(), "Configuration cache is not enabled");
+ }
+
+ static boolean configurationCacheEnabled()
+ {
+ return System.getProperty("enableConfigurationCache", "false")
+ .equalsIgnoreCase("true");
+ }
+
+ private static void configureJacoco(final Path projectDir)
+ {
+ final Optional testkitGradleConfig = TestUtil
+ .readResource(PluginTestFixture.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()
+ {
+ return assertOutcomeSuccessOrFromCache(":traceRequirements");
+ }
+
+ Result assertCollectOutcomeUpToDate()
+ {
+ return this.assertOutcome(":collectRequirements", TaskOutcome.UP_TO_DATE);
+ }
+
+ Result assertOutcome(final String taskPath, final TaskOutcome expectedOutcome)
+ {
+ return assertOutcome(taskPath, equalTo(expectedOutcome));
+ }
+
+ Result assertOutcome(final String taskPath, final Matcher matcher)
+ {
+ final BuildTask task = buildResult.task(taskPath);
+ assertNotNull(task, "Task '" + taskPath + "' was not executed");
+ assertThat("Outcome of task " + taskPath, task.getOutcome(), matcher);
+ return this;
+ }
+
+ Result assertCollectOutcomeSuccessOrFromCache()
+ {
+ return assertOutcomeSuccessOrFromCache(":collectRequirements");
+ }
+
+ Result assertOutcomeSuccessOrFromCache(final String taskPath)
+ {
+ return assertOutcome(taskPath,
+ either(is(TaskOutcome.SUCCESS)).or(is(TaskOutcome.FROM_CACHE)));
+ }
+
+ 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;
+ }
+ }
+}