From fba5ff955a7d35c560daea58d4464a352c01384a Mon Sep 17 00:00:00 2001 From: kaklakariada Date: Mon, 17 Aug 2026 20:20:03 +0200 Subject: [PATCH 1/7] Add wanted statuses config --- example-projects/custom-config/build.gradle | 3 + .../gradle/OpenFastTracePlugin.java | 3 + .../gradle/config/TracingConfig.java | 13 +++ .../openfasttrace/gradle/task/TraceTask.java | 19 ++++- .../gradle/OpenFastTracePluginTest.java | 82 +++++++++++++------ 5 files changed, 93 insertions(+), 27 deletions(-) diff --git a/example-projects/custom-config/build.gradle b/example-projects/custom-config/build.gradle index 9c30279..5a8895d 100644 --- a/example-projects/custom-config/build.gradle +++ b/example-projects/custom-config/build.gradle @@ -5,6 +5,8 @@ plugins { def artifactTypes = findProperty('filteredArtifactTypes') artifactTypes = artifactTypes ? artifactTypes.split(',').toList() : null +def wantedStatuses = findProperty('filterWantedStatuses') +wantedStatuses = wantedStatuses ? wantedStatuses.split(',').toList() : null requirementTracing { failBuild = findProperty('failBuild') == 'true' inputDirectories = files('custom-dir') @@ -12,4 +14,5 @@ requirementTracing { reportFormat = 'plain' reportVerbosity = 'ALL' filteredArtifactTypes = artifactTypes + filterWantedStatuses = wantedStatuses } diff --git a/src/main/java/org/itsallcode/openfasttrace/gradle/OpenFastTracePlugin.java b/src/main/java/org/itsallcode/openfasttrace/gradle/OpenFastTracePlugin.java index 919f965..f7fdeb3 100644 --- a/src/main/java/org/itsallcode/openfasttrace/gradle/OpenFastTracePlugin.java +++ b/src/main/java/org/itsallcode/openfasttrace/gradle/OpenFastTracePlugin.java @@ -12,6 +12,7 @@ import org.gradle.api.logging.Logging; import org.gradle.api.plugins.ExtensionAware; import org.gradle.api.tasks.TaskProvider; +import org.itsallcode.openfasttrace.api.core.ItemStatus; import org.itsallcode.openfasttrace.gradle.config.TagPathConfiguration; import org.itsallcode.openfasttrace.gradle.config.TracingConfig; import org.itsallcode.openfasttrace.gradle.task.CollectTask; @@ -95,6 +96,8 @@ private static void configureTask(final Project rootProject, task.getFilteredArtifactTypes().set(config.getFilteredArtifactTypes()); task.getFilteredTags().set(config.getFilteredTags()); task.getFilterAcceptsItemsWithoutTag().set(config.getFilterAcceptsItemsWithoutTag()); + task.getFilterWantedStatuses().set( + config.getFilterWantedStatuses().map(String::toUpperCase).map(ItemStatus::valueOf)); task.getDetailsSectionDisplay().set(config.getDetailsSectionDisplay()); } diff --git a/src/main/java/org/itsallcode/openfasttrace/gradle/config/TracingConfig.java b/src/main/java/org/itsallcode/openfasttrace/gradle/config/TracingConfig.java index 035f030..a6f374a 100644 --- a/src/main/java/org/itsallcode/openfasttrace/gradle/config/TracingConfig.java +++ b/src/main/java/org/itsallcode/openfasttrace/gradle/config/TracingConfig.java @@ -1,6 +1,7 @@ package org.itsallcode.openfasttrace.gradle.config; import java.util.List; +import java.util.Set; import org.gradle.api.Project; import org.gradle.api.file.ConfigurableFileCollection; @@ -22,6 +23,7 @@ public class TracingConfig private final ListProperty importedRequirements; private final SetProperty filteredTags; private final SetProperty filteredArtifactTypes; + private final SetProperty filterWantedStatuses; private final Property filterAcceptsItemsWithoutTag; private final Property detailsSectionDisplay; private final Property failBuild; @@ -38,6 +40,7 @@ public TracingConfig(final Project project) this.filteredTags = project.getObjects().setProperty(String.class); this.filteredArtifactTypes = project.getObjects().setProperty(String.class); this.filterAcceptsItemsWithoutTag = project.getObjects().property(Boolean.class); + this.filterWantedStatuses = project.getObjects().setProperty(String.class); this.filterAcceptsItemsWithoutTag.set(true); this.detailsSectionDisplay = project.getObjects().property(DetailsSectionDisplay.class); this.detailsSectionDisplay.set(DetailsSectionDisplay.COLLAPSE); @@ -90,6 +93,11 @@ public Property getDetailsSectionDisplay() return detailsSectionDisplay; } + public SetProperty getFilterWantedStatuses() + { + return filterWantedStatuses; + } + public void setReportVerbosity(final String reportVerbosity) { setReportVerbosity(ReportVerbosity.valueOf(reportVerbosity)); @@ -140,6 +148,11 @@ public void setDetailsSectionDisplay(final String detailsSectionDisplay) this.detailsSectionDisplay.set(DetailsSectionDisplay.valueOf(detailsSectionDisplay)); } + public void setFilterWantedStatuses(final Set statuses) + { + this.filterWantedStatuses.set(statuses); + } + public TagPathConfiguration getTagPathConfig() { return ((ExtensionAware) this).getExtensions().getByType(TagPathConfiguration.class); diff --git a/src/main/java/org/itsallcode/openfasttrace/gradle/task/TraceTask.java b/src/main/java/org/itsallcode/openfasttrace/gradle/task/TraceTask.java index f48c9f5..23e5664 100644 --- a/src/main/java/org/itsallcode/openfasttrace/gradle/task/TraceTask.java +++ b/src/main/java/org/itsallcode/openfasttrace/gradle/task/TraceTask.java @@ -39,6 +39,8 @@ public class TraceTask extends DefaultTask private final Property filterAcceptsItemsWithoutTag = getProject().getObjects() .property(Boolean.class); private final Property failBuild = getProject().getObjects().property(Boolean.class); + private final SetProperty filterWantedStatuses = getProject().getObjects() + .setProperty(ItemStatus.class); @InputFile @PathSensitive(PathSensitivity.ABSOLUTE) @@ -102,6 +104,13 @@ public Property getFailBuild() return failBuild; } + @Input + @Optional + public SetProperty getFilterWantedStatuses() + { + return filterWantedStatuses; + } + private boolean shouldFailBuild() { return failBuild.getOrElse(true); @@ -161,10 +170,12 @@ private ImportSettings getImportSettings() private FilterSettings getFilterSettings() { - final FilterSettings settings = FilterSettings.builder() // - .artifactTypes(filteredArtifactTypes.getOrElse(emptySet())) // - .tags(filteredTags.get()) // - .withoutTags(filterAcceptsItemsWithoutTag.get()).build(); + final FilterSettings settings = FilterSettings.builder() + .artifactTypes(filteredArtifactTypes.getOrElse(emptySet())) + .tags(filteredTags.get()) + .withoutTags(filterAcceptsItemsWithoutTag.get()) + .wantedStatuses(filterWantedStatuses.get()) + .build(); getLogger().info("Filter settings: artifactTypes={}, tags={}, acceptItemsWithoutTag={}", settings.getArtifactTypes(), settings.getTags(), settings.isArtifactTypeCriteriaSet()); diff --git a/src/test/java/org/itsallcode/openfasttrace/gradle/OpenFastTracePluginTest.java b/src/test/java/org/itsallcode/openfasttrace/gradle/OpenFastTracePluginTest.java index e403077..20619ef 100644 --- a/src/test/java/org/itsallcode/openfasttrace/gradle/OpenFastTracePluginTest.java +++ b/src/test/java/org/itsallcode/openfasttrace/gradle/OpenFastTracePluginTest.java @@ -27,7 +27,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"); @@ -38,7 +39,8 @@ class OpenFastTracePluginTest @EnumSource void tracingTaskAddedToProject(final GradleTestConfig config) { - final BuildResult buildResult = runBuild(config, PROJECT_DEFAULT_CONFIG_DIR, "tasks"); + final BuildResult buildResult = runBuild(config, PROJECT_DEFAULT_CONFIG_DIR, + "tasks"); assertThat(buildResult.getOutput(), containsString( "traceRequirements - Trace requirements and generate tracing report")); } @@ -72,9 +74,11 @@ private void testConfigurationCache(final GradleTestConfig config, final Path pr @ParameterizedTest(name = "testTraceExampleProjectWithDefaultConfig {0}") @EnumSource - void testTraceExampleProjectWithDefaultConfig(final GradleTestConfig config) throws IOException + void testTraceExampleProjectWithDefaultConfig(final GradleTestConfig config) + throws IOException { - final BuildResult buildResult = runBuild(config, PROJECT_DEFAULT_CONFIG_DIR, "clean", + final BuildResult buildResult = runBuild(config, PROJECT_DEFAULT_CONFIG_DIR, + "clean", "traceRequirements"); assertThat(buildResult.task(":traceRequirements").getOutcome(), either(is(TaskOutcome.SUCCESS)).or(is(TaskOutcome.FROM_CACHE))); @@ -84,13 +88,15 @@ void testTraceExampleProjectWithDefaultConfig(final GradleTestConfig config) thr @ParameterizedTest(name = "testCollectExampleProjectWithCustomConfig {0}") @EnumSource - void testCollectExampleProjectWithCustomConfig(final GradleTestConfig config) throws IOException + void testCollectExampleProjectWithCustomConfig(final GradleTestConfig config) + throws IOException { final BuildResult buildResult = runBuild(config, PROJECT_CUSTOM_CONFIG_DIR, "clean", "collectRequirements"); assertThat(buildResult.task(":collectRequirements").getOutcome(), either(is(TaskOutcome.SUCCESS)).or(is(TaskOutcome.FROM_CACHE))); - assertFileContent(PROJECT_CUSTOM_CONFIG_DIR.resolve("build/reports/requirements.xml"), + assertFileContent( + PROJECT_CUSTOM_CONFIG_DIR.resolve("build/reports/requirements.xml"), "\n" + // "", // """ @@ -147,7 +153,8 @@ void testCollectIsUpToDateWhenAlreadyRunBefore(final GradleTestConfig config) assertThat(buildResult.task(":collectRequirements").getOutcome(), either(is(TaskOutcome.SUCCESS)).or(is(TaskOutcome.FROM_CACHE))); buildResult = runBuild(config, PROJECT_CUSTOM_CONFIG_DIR, "collectRequirements"); - assertEquals(TaskOutcome.UP_TO_DATE, buildResult.task(":collectRequirements").getOutcome()); + assertEquals(TaskOutcome.UP_TO_DATE, + buildResult.task(":collectRequirements").getOutcome()); } @ParameterizedTest(name = "testHtmlReportConfig {0}") @@ -173,12 +180,14 @@ void testTraceTaskUpToDateWhenAlreadyRun(final GradleTestConfig config) assertThat(buildResult.task(":traceRequirements").getOutcome(), either(is(TaskOutcome.SUCCESS)).or(is(TaskOutcome.FROM_CACHE))); buildResult = runBuild(config, HTML_REPORT_CONFIG_DIR, "traceRequirements"); - assertEquals(TaskOutcome.UP_TO_DATE, buildResult.task(":traceRequirements").getOutcome()); + assertEquals(TaskOutcome.UP_TO_DATE, + buildResult.task(":traceRequirements").getOutcome()); } @ParameterizedTest(name = "testTraceExampleProjectWithCustomConfig {0}") @EnumSource - void testTraceExampleProjectWithCustomConfig(final GradleTestConfig config) throws IOException + void testTraceExampleProjectWithCustomConfig(final GradleTestConfig config) + throws IOException { final BuildResult buildResult = runBuild(config, PROJECT_CUSTOM_CONFIG_DIR, "clean", "traceRequirements"); @@ -194,9 +203,11 @@ void testTraceExampleProjectWithCustomConfig(final GradleTestConfig config) thro void testTraceExampleProjectWithCustomConfigFailBuild(final GradleTestConfig config) throws IOException { - final BuildResult buildResult = runBuildExpectFailure(config, PROJECT_CUSTOM_CONFIG_DIR, + final BuildResult buildResult = runBuildExpectFailure(config, + PROJECT_CUSTOM_CONFIG_DIR, "clean", "traceRequirements", "-PfailBuild=true"); - assertEquals(TaskOutcome.FAILED, buildResult.task(":traceRequirements").getOutcome()); + assertEquals(TaskOutcome.FAILED, + buildResult.task(":traceRequirements").getOutcome()); assertFileContent(PROJECT_CUSTOM_CONFIG_DIR.resolve("build/custom-report.txt"), "not ok [ in: 1 / 1 ✔ | out: 0 / 0 ] dsn~exampleB~1 (impl, -utest)", "not ok - 2 total, 1 direct, 0 transitive defects"); @@ -207,14 +218,27 @@ void testTraceExampleProjectWithCustomConfigFailBuild(final GradleTestConfig con void filteredArtifactTypes(final GradleTestConfig config) { final BuildResult buildResult = runBuild(config, 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))); + } + + @ParameterizedTest(name = "filteredArtifactTypes {0}") + @EnumSource + void filteredWantedStatuses(final GradleTestConfig config) + { + final BuildResult buildResult = runBuild(config, PROJECT_CUSTOM_CONFIG_DIR, "clean", + "traceRequirements", "-PfailBuild=true", + "-PfilterWantedStatuses=approved"); assertThat(buildResult.task(":traceRequirements").getOutcome(), either(is(TaskOutcome.SUCCESS)).or(is(TaskOutcome.FROM_CACHE))); } @ParameterizedTest(name = "testTraceExampleProjectWithCustomConfigFailBuild {0}") @EnumSource - void testTraceExampleProjectWithCustomConfigFailBuildErrorMessage(final GradleTestConfig config) + void testTraceExampleProjectWithCustomConfigFailBuildErrorMessage( + final GradleTestConfig config) { try { @@ -225,7 +249,9 @@ void testTraceExampleProjectWithCustomConfigFailBuildErrorMessage(final GradleTe { 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(), @@ -241,7 +267,8 @@ void testTraceMultiProject(final GradleTestConfig config) throws IOException "traceRequirements"); assertThat(buildResult.task(":traceRequirements").getOutcome(), either(is(TaskOutcome.SUCCESS)).or(is(TaskOutcome.FROM_CACHE))); - assertFileContent(MULTI_PROJECT_DIR.resolve("build/custom-report.txt"), "ok - 6 total"); + assertFileContent(MULTI_PROJECT_DIR.resolve("build/custom-report.txt"), + "ok - 6 total"); } @ParameterizedTest(name = "traceDependencyProject {0}") @@ -251,7 +278,8 @@ void traceDependencyProject(final GradleTestConfig config) throws IOException BuildResult buildResult = runBuild(config, 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(config, DEPENDENCY_CONFIG_DIR, "traceRequirements"); @@ -269,7 +297,8 @@ void publishToMavenRepo(final GradleTestConfig config) throws IOException { final BuildResult buildResult = runBuild(config, 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"); @@ -301,7 +330,8 @@ void publishToMavenRepo(final GradleTestConfig config) throws IOException } } - private static String readEntry(final ZipFile zip, final String entryName) throws IOException + private static String readEntry(final ZipFile zip, final String entryName) + throws IOException { final ZipArchiveEntry reqirementsEntry = zip.getEntry(entryName); try (BufferedReader reader = new BufferedReader( @@ -317,12 +347,16 @@ private static void createDependencyZip(final Path dependencyZip) throws IOExcep try (ZipFileBuilder zipBuilder = ZipFileBuilder.create(dependencyZip)) { zipBuilder - .addEntry("source.java", PROJECT_DEFAULT_CONFIG_DIR.resolve("src/source.java")) // - .addEntry("spec.md", PROJECT_DEFAULT_CONFIG_DIR.resolve("doc/spec.md")); + .addEntry("source.java", + PROJECT_DEFAULT_CONFIG_DIR + .resolve("src/source.java")) // + .addEntry("spec.md", PROJECT_DEFAULT_CONFIG_DIR + .resolve("doc/spec.md")); } } - private static void assertFileContent(final Path file, final String... lines) throws IOException + private static void assertFileContent(final Path file, final String... lines) + throws IOException { final String fileContent = fileContent(file); for (final String line : lines) @@ -377,7 +411,8 @@ private static GradleRunner createGradleRunner(final GradleTestConfig config, private static boolean configurationCacheEnabled() { - return System.getProperty("enableConfigurationCache", "false").equalsIgnoreCase("true"); + return System.getProperty("enableConfigurationCache", "false") + .equalsIgnoreCase("true"); } private static void configureJacoco(final Path projectDir) @@ -389,7 +424,8 @@ private static void configureJacoco(final Path projectDir) return; } 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"); From 492e16284a5fe11152c950a36ad2fb73e56fdc71 Mon Sep 17 00:00:00 2001 From: kaklakariada Date: Tue, 18 Aug 2026 11:35:01 +0200 Subject: [PATCH 2/7] Fix compile error --- .../openfasttrace/gradle/OpenFastTracePlugin.java | 8 ++++++-- 1 file changed, 6 insertions(+), 2 deletions(-) diff --git a/src/main/java/org/itsallcode/openfasttrace/gradle/OpenFastTracePlugin.java b/src/main/java/org/itsallcode/openfasttrace/gradle/OpenFastTracePlugin.java index f7fdeb3..a11629d 100644 --- a/src/main/java/org/itsallcode/openfasttrace/gradle/OpenFastTracePlugin.java +++ b/src/main/java/org/itsallcode/openfasttrace/gradle/OpenFastTracePlugin.java @@ -96,8 +96,12 @@ private static void configureTask(final Project rootProject, task.getFilteredArtifactTypes().set(config.getFilteredArtifactTypes()); task.getFilteredTags().set(config.getFilteredTags()); task.getFilterAcceptsItemsWithoutTag().set(config.getFilterAcceptsItemsWithoutTag()); - task.getFilterWantedStatuses().set( - config.getFilterWantedStatuses().map(String::toUpperCase).map(ItemStatus::valueOf)); + + task.getFilterWantedStatuses() + .set(config.getFilterWantedStatuses().getOrElse(Collections.emptySet()).stream() + .map(String::toUpperCase) + .map(ItemStatus::valueOf) + .collect(toSet())); task.getDetailsSectionDisplay().set(config.getDetailsSectionDisplay()); } From 859cd13a3bf63ee16465f3249a14af197c37beaa Mon Sep 17 00:00:00 2001 From: kaklakariada Date: Tue, 18 Aug 2026 19:46:21 +0200 Subject: [PATCH 3/7] Add integration test for status filter --- CHANGELOG.md | 2 + README.md | 2 + .../custom-config/custom-dir/spec.md | 1 + .../gradle/OpenFastTracePluginTest.java | 39 ++++++++++++++++--- 4 files changed, 39 insertions(+), 5 deletions(-) diff --git a/CHANGELOG.md b/CHANGELOG.md index 5d1f502..0a4b161 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 ## [3.2.0] - ???? +- [PR #78](https://github.com/itsallcode/openfasttrace-gradle/pull/78) + - Simplify Gradle plugin integration tests - [PR #73](https://github.com/itsallcode/openfasttrace-gradle/pull/73) - Upgrade to OpenFastTrace [4.8.0](https://github.com/itsallcode/openfasttrace/releases/tag/4.8.0) - Upgrade integration tests to use Gradle 9.7.0. Gradle 8 is no longer supported. diff --git a/README.md b/README.md index c1bb107..3f5c92e 100644 --- a/README.md +++ b/README.md @@ -44,6 +44,7 @@ requirementTracing { reportVerbosity = 'failure_details' detailsSectionDisplay = 'collapse' filteredArtifactTypes = ["req", "dsn"] + filterWantedStatuses = ["draft", "approved"] } ``` @@ -67,6 +68,7 @@ You can configure the following properties: * `collapse` - hide details (default) * `expand` - show details * `filteredArtifactTypes`: Use only the listed artifact types during tracing +* `filterWantedStatuses`: Import only specification items that have a status contained in the list of statuses. Possible values: `draft`, `proposed`, `approved`, `rejected`. See the [OFT user guide](https://github.com/itsallcode/openfasttrace/blob/main/doc/user_guide/user_guide.md#filtering-by-status) for details. ### Configuring the Short Tag Importer diff --git a/example-projects/custom-config/custom-dir/spec.md b/example-projects/custom-config/custom-dir/spec.md index 3fc39c3..71e29da 100644 --- a/example-projects/custom-config/custom-dir/spec.md +++ b/example-projects/custom-config/custom-dir/spec.md @@ -4,3 +4,4 @@ Example requirement Needs: utest, impl +Status: draft diff --git a/src/test/java/org/itsallcode/openfasttrace/gradle/OpenFastTracePluginTest.java b/src/test/java/org/itsallcode/openfasttrace/gradle/OpenFastTracePluginTest.java index a4693b6..3e3143b 100644 --- a/src/test/java/org/itsallcode/openfasttrace/gradle/OpenFastTracePluginTest.java +++ b/src/test/java/org/itsallcode/openfasttrace/gradle/OpenFastTracePluginTest.java @@ -121,7 +121,7 @@ void testCollectExampleProjectWithCustomConfig() exampleB Tracing Example - approved + draft 1 """, @@ -186,7 +186,7 @@ void testTraceExampleProjectWithCustomConfig() 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 (impl, -utest)", + "not ok [ in: 1 / 1 ✔ | out: 0 / 0 ] dsn~exampleB~1 [draft] (impl, -utest)", "not ok - 2 total, 1 direct, 0 transitive defects"); } @@ -198,7 +198,7 @@ void testTraceExampleProjectWithCustomConfigFailBuild() 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 (impl, -utest)", + "not ok [ in: 1 / 1 ✔ | out: 0 / 0 ] dsn~exampleB~1 [draft] (impl, -utest)", "not ok - 2 total, 1 direct, 0 transitive defects"); } @@ -211,6 +211,33 @@ void filteredArtifactTypes() either(is(TaskOutcome.SUCCESS)).or(is(TaskOutcome.FROM_CACHE))); } + @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"); + } + + @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"), + "not ok [ in: 0 / 0 | out: 0 / 1 ✘ ] impl~exampleB-34853351~0 ()", + "not ok - 1 total, 1 direct, 0 transitive defects"); + } + @Test void testTraceExampleProjectWithCustomConfigFailBuildErrorMessage() { @@ -257,7 +284,8 @@ void traceDependencyProject() 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"); @@ -333,7 +361,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); } } From 6bc633260105ce0061a799a87050274034e608ed Mon Sep 17 00:00:00 2001 From: kaklakariada Date: Tue, 18 Aug 2026 20:04:41 +0200 Subject: [PATCH 4/7] Improve error message for invalid status --- .../gradle/OpenFastTracePlugin.java | 30 +++++++++++++++---- .../gradle/OpenFastTracePluginTest.java | 10 +++++++ 2 files changed, 34 insertions(+), 6 deletions(-) diff --git a/src/main/java/org/itsallcode/openfasttrace/gradle/OpenFastTracePlugin.java b/src/main/java/org/itsallcode/openfasttrace/gradle/OpenFastTracePlugin.java index a11629d..5250b4a 100644 --- a/src/main/java/org/itsallcode/openfasttrace/gradle/OpenFastTracePlugin.java +++ b/src/main/java/org/itsallcode/openfasttrace/gradle/OpenFastTracePlugin.java @@ -1,5 +1,6 @@ package org.itsallcode.openfasttrace.gradle; +import static java.util.stream.Collectors.joining; import static java.util.stream.Collectors.toSet; import java.io.File; @@ -96,15 +97,32 @@ private static void configureTask(final Project rootProject, task.getFilteredArtifactTypes().set(config.getFilteredArtifactTypes()); task.getFilteredTags().set(config.getFilteredTags()); task.getFilterAcceptsItemsWithoutTag().set(config.getFilterAcceptsItemsWithoutTag()); - - task.getFilterWantedStatuses() - .set(config.getFilterWantedStatuses().getOrElse(Collections.emptySet()).stream() - .map(String::toUpperCase) - .map(ItemStatus::valueOf) - .collect(toSet())); + task.getFilterWantedStatuses().set(getWantedStatuses(config)); task.getDetailsSectionDisplay().set(config.getDetailsSectionDisplay()); } + private static Set getWantedStatuses(final TracingConfig config) + { + return config.getFilterWantedStatuses().getOrElse(Collections.emptySet()).stream() + .map(OpenFastTracePlugin::convertStatus) + .collect(toSet()); + } + + private static ItemStatus convertStatus(final String value) + { + try + { + return ItemStatus.valueOf(value.toUpperCase(Locale.ROOT)); + } + catch (final IllegalArgumentException e) + { + final String validStatuses = Arrays.stream(ItemStatus.values()).map(ItemStatus::name) + .collect(joining(", ")); + throw new IllegalArgumentException( + "Invalid status '" + value + "'. Valid statuses are: " + validStatuses, e); + } + } + private static Set getAllInputDirectories(final Set allProjects) { return allProjects.stream() // diff --git a/src/test/java/org/itsallcode/openfasttrace/gradle/OpenFastTracePluginTest.java b/src/test/java/org/itsallcode/openfasttrace/gradle/OpenFastTracePluginTest.java index 3e3143b..ce0cd51 100644 --- a/src/test/java/org/itsallcode/openfasttrace/gradle/OpenFastTracePluginTest.java +++ b/src/test/java/org/itsallcode/openfasttrace/gradle/OpenFastTracePluginTest.java @@ -238,6 +238,16 @@ void filteredWantedStatusesNoMatch() "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")); + } + @Test void testTraceExampleProjectWithCustomConfigFailBuildErrorMessage() { From bc14535c29b032819d43d3b8fe8fa068530d8aa8 Mon Sep 17 00:00:00 2001 From: kaklakariada Date: Tue, 18 Aug 2026 20:11:42 +0200 Subject: [PATCH 5/7] Adapt test to various JVMs --- .../openfasttrace/gradle/OpenFastTracePluginTest.java | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/src/test/java/org/itsallcode/openfasttrace/gradle/OpenFastTracePluginTest.java b/src/test/java/org/itsallcode/openfasttrace/gradle/OpenFastTracePluginTest.java index ce0cd51..f8b0300 100644 --- a/src/test/java/org/itsallcode/openfasttrace/gradle/OpenFastTracePluginTest.java +++ b/src/test/java/org/itsallcode/openfasttrace/gradle/OpenFastTracePluginTest.java @@ -234,7 +234,8 @@ void filteredWantedStatusesNoMatch() either(is(TaskOutcome.SUCCESS)).or(is(TaskOutcome.FROM_CACHE))); TestUtil.assertFileContent( PROJECT_CUSTOM_CONFIG_DIR.resolve("build/custom-report.txt"), - "not ok [ in: 0 / 0 | out: 0 / 1 ✘ ] impl~exampleB-34853351~0 ()", + // Generated ID depends on JVM + "not ok [ in: 0 / 0 | out: 0 / 1 ✘ ] impl~exampleB-", "not ok - 1 total, 1 direct, 0 transitive defects"); } From 32d83211e66b53e9a23b61271c5fac533640a681 Mon Sep 17 00:00:00 2001 From: kaklakariada Date: Tue, 18 Aug 2026 20:18:26 +0200 Subject: [PATCH 6/7] Add changelog entry --- CHANGELOG.md | 4 +++- .../gradle/OpenFastTracePluginTest.java | 22 ++++++------------- 2 files changed, 10 insertions(+), 16 deletions(-) diff --git a/CHANGELOG.md b/CHANGELOG.md index 0a4b161..9c7cf61 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -6,8 +6,10 @@ and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0 ## [Unreleased] -## [3.2.0] - ???? +## [3.2.0] - 2026-08-18 +- [PR #79](https://github.com/itsallcode/openfasttrace-gradle/pull/79) + - Add support for filtering by item status - [PR #78](https://github.com/itsallcode/openfasttrace-gradle/pull/78) - Simplify Gradle plugin integration tests - [PR #73](https://github.com/itsallcode/openfasttrace-gradle/pull/73) diff --git a/src/test/java/org/itsallcode/openfasttrace/gradle/OpenFastTracePluginTest.java b/src/test/java/org/itsallcode/openfasttrace/gradle/OpenFastTracePluginTest.java index f8b0300..34d9f15 100644 --- a/src/test/java/org/itsallcode/openfasttrace/gradle/OpenFastTracePluginTest.java +++ b/src/test/java/org/itsallcode/openfasttrace/gradle/OpenFastTracePluginTest.java @@ -30,8 +30,7 @@ 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"); @@ -261,9 +260,7 @@ 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(), @@ -288,15 +285,13 @@ 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"); @@ -307,8 +302,7 @@ 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"); @@ -372,8 +366,7 @@ 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); } } @@ -422,8 +415,7 @@ 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"); From f7fdce9197c611f72c7b29355c5e4fc7a69258b6 Mon Sep 17 00:00:00 2001 From: kaklakariada Date: Tue, 18 Aug 2026 20:21:05 +0200 Subject: [PATCH 7/7] Add option to output --- .../itsallcode/openfasttrace/gradle/config/TracingConfig.java | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/main/java/org/itsallcode/openfasttrace/gradle/config/TracingConfig.java b/src/main/java/org/itsallcode/openfasttrace/gradle/config/TracingConfig.java index a6f374a..74af7ce 100644 --- a/src/main/java/org/itsallcode/openfasttrace/gradle/config/TracingConfig.java +++ b/src/main/java/org/itsallcode/openfasttrace/gradle/config/TracingConfig.java @@ -174,6 +174,6 @@ public String toString() return "TracingConfig [reportVerbosity=" + reportVerbosity + ", inputDirectories=" + inputDirectories + ", reportFile=" + reportFile + ", pathConfig=" + getTagPathConfig() + ", failBuild=" + failBuild + ", filteredArtifactTypes=" - + filteredArtifactTypes + "]"; + + filteredArtifactTypes + ", filterWantedStatuses=" + filterWantedStatuses + "]"; } }