diff --git a/allure-cucumber7-jvm/README.md b/allure-cucumber7-jvm/README.md index 118b0622..936bf483 100644 --- a/allure-cucumber7-jvm/README.md +++ b/allure-cucumber7-jvm/README.md @@ -8,7 +8,8 @@ Use this module when your BDD tests run on Cucumber JVM 7 and you want features, - Allure Java 3.x requires Java 17 or newer. - This module targets Cucumber JVM 7.x. -- The current build validates against Cucumber JVM 7.34.3 and Gherkin 36.1.0. +- The main test suite validates against Cucumber JVM 7.34.7, and a focused compatibility test validates the + metadata-label tag contract against the oldest runtime supported by the current adapter API, 7.3.0. ## Installation diff --git a/allure-cucumber7-jvm/build.gradle.kts b/allure-cucumber7-jvm/build.gradle.kts index ea33cb51..9a644cd5 100644 --- a/allure-cucumber7-jvm/build.gradle.kts +++ b/allure-cucumber7-jvm/build.gradle.kts @@ -1,6 +1,7 @@ description = "Allure CucumberJVM 7.0" val cucumberVersion = "7.34.7" +val minimumCucumberVersion = "7.3.0" dependencies { api(project(":allure-java-commons")) @@ -35,4 +36,41 @@ tasks.jar { tasks.test { useJUnitPlatform() + systemProperty("allure.test.cucumber.version", cucumberVersion) +} + +val minimumCucumberTestRuntimeClasspath = configurations.testRuntimeClasspath.get().copyRecursive().apply { + resolutionStrategy.eachDependency { + if (requested.group == "io.cucumber" && requested.name == "cucumber-bom") { + useVersion(minimumCucumberVersion) + because("7.3.0 is the oldest Cucumber JVM runtime supported by the current adapter API") + } + } +} + +val cucumber7MinimumVersionTest = tasks.register("cucumber7MinimumVersionTest") { + description = "Runs the metadata-label tag regression against Cucumber JVM $minimumCucumberVersion" + group = "verification" + dependsOn(tasks.testClasses) + mustRunAfter(tasks.test) + + testClassesDirs = sourceSets.test.get().output.classesDirs + classpath = sourceSets.main.get().output + sourceSets.test.get().output + minimumCucumberTestRuntimeClasspath + useJUnitPlatform() + filter { + includeTestsMatching( + "io.qameta.allure.cucumber7jvm.AllureCucumber7JvmTest.shouldPreferMetadataTagHierarchyOverDefaults" + ) + } + + val standardTest = tasks.test.get() + systemProperties(standardTest.systemProperties) + systemProperty("allure.test.cucumber.version", minimumCucumberVersion) + jvmArgs = standardTest.jvmArgs + maxHeapSize = standardTest.maxHeapSize + maxParallelForks = standardTest.maxParallelForks +} + +tasks.check { + dependsOn(cucumber7MinimumVersionTest) } diff --git a/allure-cucumber7-jvm/src/test/java/io/qameta/allure/cucumber7jvm/AllureCucumber7JvmTest.java b/allure-cucumber7-jvm/src/test/java/io/qameta/allure/cucumber7jvm/AllureCucumber7JvmTest.java index 98f28ba6..5c4fd225 100644 --- a/allure-cucumber7-jvm/src/test/java/io/qameta/allure/cucumber7jvm/AllureCucumber7JvmTest.java +++ b/allure-cucumber7-jvm/src/test/java/io/qameta/allure/cucumber7jvm/AllureCucumber7JvmTest.java @@ -59,6 +59,8 @@ import java.util.stream.Collectors; import static io.qameta.allure.util.ResultsUtils.PACKAGE_LABEL_NAME; +import static io.qameta.allure.util.ResultsUtils.PARENT_SUITE_LABEL_NAME; +import static io.qameta.allure.util.ResultsUtils.SUB_SUITE_LABEL_NAME; import static io.qameta.allure.util.ResultsUtils.SUITE_LABEL_NAME; import static io.qameta.allure.util.ResultsUtils.TEST_CLASS_LABEL_NAME; import static io.qameta.allure.util.ResultsUtils.TEST_METHOD_LABEL_NAME; @@ -71,6 +73,8 @@ @IsolatedLifecycle class AllureCucumber7JvmTest { + private static final String EXPECTED_CUCUMBER_VERSION_PROPERTY = "allure.test.cucumber.version"; + @AllureFeatures.Base @Test void shouldSetName() { @@ -376,6 +380,46 @@ void shouldAddTags() { ); } + /** + * Metadata tags must replace Cucumber's generated hierarchy defaults so report generation cannot place the + * same scenario under both hierarchies. + */ + @Description + @Test + void shouldPreferMetadataTagHierarchyOverDefaults() { + assertThat(Runtime.class.getPackage().getImplementationVersion()) + .as("Cucumber runtime version") + .isEqualTo(System.getProperty(EXPECTED_CUCUMBER_VERSION_PROPERTY)); + + final AllureResults results = runFeature("features/metadata-tags.feature"); + + final List testResults = results.getTestResults(); + assertThat(testResults) + .hasSize(3) + .allSatisfy(testResult -> { + assertThat(testResult.getLabels()) + .filteredOn( + label -> PARENT_SUITE_LABEL_NAME.equals(label.getName()) + || SUITE_LABEL_NAME.equals(label.getName()) + || SUB_SUITE_LABEL_NAME.equals(label.getName()) + ) + .extracting(Label::getName, Label::getValue) + .containsExactlyInAnyOrder( + tuple(PARENT_SUITE_LABEL_NAME, "Cucumber"), + tuple(SUITE_LABEL_NAME, "Petstore"), + tuple(SUB_SUITE_LABEL_NAME, "CRUD") + ); + assertThat(testResult.getLabels()) + .filteredOn(label -> "tag".equals(label.getName())) + .extracting(Label::getValue) + .doesNotContain( + "allure.label.parentSuite:Cucumber", + "allure.label.suite:Petstore", + "allure.label.subSuite:CRUD" + ); + }); + } + @AllureFeatures.Links @ExtendWith(SystemPropertyExtension.class) @SystemProperty( diff --git a/allure-cucumber7-jvm/src/test/resources/features/metadata-tags.feature b/allure-cucumber7-jvm/src/test/resources/features/metadata-tags.feature new file mode 100644 index 00000000..63bb5bb5 --- /dev/null +++ b/allure-cucumber7-jvm/src/test/resources/features/metadata-tags.feature @@ -0,0 +1,16 @@ +@allure.label.parentSuite:Cucumber +@allure.label.suite:Petstore +@allure.label.subSuite:CRUD +Feature: Arithmetic_operations + + Scenario Outline: Addition + Given a is + And b is + When I add a to b + Then result is + + Examples: + | a | b | result | + | 1 | 1 | 2 | + | 2 | 1 | 3 | + | 2 | 7 | 9 | diff --git a/allure-java-commons/src/main/java/io/qameta/allure/AllureLifecycle.java b/allure-java-commons/src/main/java/io/qameta/allure/AllureLifecycle.java index 77ad20a0..ec7d5c0f 100644 --- a/allure-java-commons/src/main/java/io/qameta/allure/AllureLifecycle.java +++ b/allure-java-commons/src/main/java/io/qameta/allure/AllureLifecycle.java @@ -64,9 +64,13 @@ import java.util.concurrent.CopyOnWriteArrayList; import java.util.function.BiConsumer; import java.util.function.Consumer; +import java.util.regex.Matcher; +import java.util.regex.Pattern; import java.util.stream.Stream; import static io.qameta.allure.AllureConstants.ATTACHMENT_FILE_SUFFIX; +import static io.qameta.allure.util.ResultsUtils.TAG_LABEL_NAME; +import static io.qameta.allure.util.ResultsUtils.createLabel; import static io.qameta.allure.util.ResultsUtils.firstNonEmpty; import static io.qameta.allure.util.ResultsUtils.getStatus; import static io.qameta.allure.util.ResultsUtils.getStatusDetails; @@ -101,6 +105,10 @@ public class AllureLifecycle { private static final Logger LOGGER = LoggerFactory.getLogger(AllureLifecycle.class); + private static final Pattern ALLURE_LABEL_TAG = Pattern.compile( + "^@?allure\\.label\\.(?.+)[:=](?.+)$" + ); + private static final String EXTERNAL_KEY = "external key"; private static final String KEY_NOT_FOUND = "Could not {}: item with key {} not found"; @@ -346,8 +354,8 @@ public void updateTest(final Consumer update) { * Registers default labels for the test with given key. Default labels do not appear on the test result until * the test stops: {@link #stopTest(AllureExternalKey)} merges them after scope metadata, adding, for each * distinct label name, the default labels with that name only when the test has no labels with that name by - * then. Labels provided by the user — through annotations, the runtime API, or before fixtures — thus take - * precedence over defaults instead of being duplicated by them. Repeated calls accumulate. + * then. Labels provided by the user — through annotations, framework tags, the runtime API, or before fixtures + * — thus take precedence over defaults instead of being duplicated by them. Repeated calls accumulate. * *

Intended for the framework-computed grouping labels a user may legitimately override: the suite family * and BDD structure labels. System labels — framework, language, host, thread, package, testClass, testMethod @@ -366,11 +374,12 @@ public void addDefaultLabels(final AllureExternalKey key, final Collection