Skip to content

Commit 250d772

Browse files
authored
let custom grouping labels override framework defaults (via #1329)
1 parent 248b80e commit 250d772

25 files changed

Lines changed: 683 additions & 44 deletions

File tree

allure-citrus/src/main/java/io/qameta/allure/citrus/AllureCitrus.java

Lines changed: 9 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -40,6 +40,7 @@
4040
import java.lang.annotation.Repeatable;
4141
import java.lang.reflect.AnnotatedElement;
4242
import java.lang.reflect.Method;
43+
import java.util.ArrayList;
4344
import java.util.Arrays;
4445
import java.util.Collections;
4546
import java.util.List;
@@ -57,6 +58,7 @@
5758
import static io.qameta.allure.util.ResultsUtils.createLanguageLabel;
5859
import static io.qameta.allure.util.ResultsUtils.createParameter;
5960
import static io.qameta.allure.util.ResultsUtils.createSuiteLabel;
61+
import static io.qameta.allure.util.ResultsUtils.createTestClassLabel;
6062
import static io.qameta.allure.util.ResultsUtils.createThreadLabel;
6163
import static io.qameta.allure.util.ResultsUtils.createTitlePath;
6264
import static io.qameta.allure.util.ResultsUtils.getProvidedLabels;
@@ -241,6 +243,10 @@ private void startTest(final TestCase testCase) {
241243
testClass.map(this::getLabels).ifPresent(result.getLabels()::addAll);
242244
testClass.map(this::getLinks).ifPresent(result.getLinks()::addAll);
243245

246+
// the test is represented by a class only when the test case is class-backed (java dsl);
247+
// the test method stays unknown — the test case name may be customized by the user
248+
testClass.ifPresent(aClass -> result.getLabels().add(createTestClassLabel(aClass.getName())));
249+
244250
result.getLabels().addAll(
245251
Arrays.asList(
246252
createHostLabel(),
@@ -250,9 +256,10 @@ private void startTest(final TestCase testCase) {
250256
)
251257
);
252258

259+
final List<Label> defaultLabels = new ArrayList<>();
253260
testClass.ifPresent(aClass -> {
254261
final String suiteName = aClass.getCanonicalName();
255-
result.getLabels().add(createSuiteLabel(suiteName));
262+
defaultLabels.add(createSuiteLabel(suiteName));
256263
});
257264

258265
final Optional<String> classDescription = testClass.flatMap(this::getDescription);
@@ -264,6 +271,7 @@ private void startTest(final TestCase testCase) {
264271
result.setDescription(description);
265272

266273
getLifecycle().scheduleTest(testKey, result);
274+
getLifecycle().addDefaultLabels(testKey, defaultLabels);
267275
getLifecycle().startTest(testKey);
268276
}
269277

allure-citrus/src/test/java/io/qameta/allure/citrus/AllureCitrusTest.java

Lines changed: 21 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -27,6 +27,7 @@
2727
import io.qameta.allure.Allure;
2828
import io.qameta.allure.AllureLifecycle;
2929
import io.qameta.allure.Step;
30+
import io.qameta.allure.model.Label;
3031
import io.qameta.allure.model.Parameter;
3132
import io.qameta.allure.model.Stage;
3233
import io.qameta.allure.model.Status;
@@ -63,6 +64,26 @@ void shouldSetName() {
6364
.containsExactly("com", "consol", "citrus", "dsl", "design", "DefaultTestDesigner");
6465
}
6566

67+
@AllureFeatures.Base
68+
@Test
69+
void shouldSetTestClassLabelForClassBackedTests() {
70+
final DefaultTestDesigner designer = new DefaultTestDesigner();
71+
designer.name("Simple test");
72+
73+
final AllureResults results = run(designer);
74+
assertThat(results.getTestResults())
75+
.hasSize(1)
76+
.flatExtracting(TestResult::getLabels)
77+
.extracting(Label::getName, Label::getValue)
78+
.contains(tuple("testClass", "com.consol.citrus.dsl.design.DefaultTestDesigner"));
79+
80+
// the test method is not a known code location for citrus test cases
81+
assertThat(results.getTestResults())
82+
.flatExtracting(TestResult::getLabels)
83+
.extracting(Label::getName)
84+
.doesNotContain("testMethod");
85+
}
86+
6687
@AllureFeatures.PassedTests
6788
@Test
6889
void shouldSetStatus() {

allure-cucumber7-jvm/src/main/java/io/qameta/allure/cucumber7jvm/AllureCucumber7Jvm.java

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -214,6 +214,7 @@ private void handleTestCaseStarted(final TestCaseStarted event) {
214214

215215
final AllureExternalKey testKey = testKey(testCaseUuid);
216216
lifecycle.scheduleTest(testKey, result);
217+
lifecycle.addDefaultLabels(testKey, labelBuilder.getDefaultLabels());
217218
lifecycle.startTest(testKey);
218219
}
219220

allure-cucumber7-jvm/src/main/java/io/qameta/allure/cucumber7jvm/LabelBuilder.java

Lines changed: 13 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -42,7 +42,6 @@
4242
import static io.qameta.allure.util.ResultsUtils.createLanguageLabel;
4343
import static io.qameta.allure.util.ResultsUtils.createStoryLabel;
4444
import static io.qameta.allure.util.ResultsUtils.createSuiteLabel;
45-
import static io.qameta.allure.util.ResultsUtils.createTestClassLabel;
4645
import static io.qameta.allure.util.ResultsUtils.createThreadLabel;
4746

4847
/**
@@ -60,6 +59,7 @@ final class LabelBuilder {
6059
private static final String OWNER = "@OWNER";
6160

6261
private final List<Label> scenarioLabels = new ArrayList<>();
62+
private final List<Label> defaultLabels = new ArrayList<>();
6363
private final List<Link> scenarioLinks = new ArrayList<>();
6464

6565
LabelBuilder(final Feature feature, final TestCase scenario, final Deque<String> tags) {
@@ -121,16 +121,20 @@ final class LabelBuilder {
121121
Arrays.asList(
122122
createHostLabel(),
123123
createThreadLabel(),
124-
createFeatureLabel(featureName),
125-
createStoryLabel(scenario.getName()),
126-
createSuiteLabel(featureName),
127-
createTestClassLabel(scenario.getName()),
128124
createFrameworkLabel("cucumber7jvm"),
129125
createLanguageLabel("java"),
130126
createLabel("gherkin_uri", uri.toString())
131127
)
132128
);
133129

130+
defaultLabels.addAll(
131+
Arrays.asList(
132+
createFeatureLabel(featureName),
133+
createStoryLabel(scenario.getName()),
134+
createSuiteLabel(featureName)
135+
)
136+
);
137+
134138
featurePackage(uri.toString(), featureName)
135139
.map(ResultsUtils::createPackageLabel)
136140
.ifPresent(scenarioLabels::add);
@@ -140,6 +144,10 @@ List<Label> getScenarioLabels() {
140144
return scenarioLabels;
141145
}
142146

147+
List<Label> getDefaultLabels() {
148+
return defaultLabels;
149+
}
150+
143151
List<Link> getScenarioLinks() {
144152
return scenarioLinks;
145153
}

allure-cucumber7-jvm/src/test/java/io/qameta/allure/cucumber7jvm/AllureCucumber7JvmTest.java

Lines changed: 8 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -57,6 +57,7 @@
5757
import static io.qameta.allure.util.ResultsUtils.PACKAGE_LABEL_NAME;
5858
import static io.qameta.allure.util.ResultsUtils.SUITE_LABEL_NAME;
5959
import static io.qameta.allure.util.ResultsUtils.TEST_CLASS_LABEL_NAME;
60+
import static io.qameta.allure.util.ResultsUtils.TEST_METHOD_LABEL_NAME;
6061
import static io.qameta.allure.util.ResultsUtils.md5;
6162
import static org.assertj.core.api.Assertions.assertThat;
6263
import static org.assertj.core.api.Assertions.tuple;
@@ -429,9 +430,14 @@ void shouldAddCommonLabels() {
429430
.extracting(Label::getName, Label::getValue)
430431
.contains(
431432
tuple(PACKAGE_LABEL_NAME, "src.test.resources.features.tags_feature.Test Simple Scenarios"),
432-
tuple(SUITE_LABEL_NAME, "Test Simple Scenarios"),
433-
tuple(TEST_CLASS_LABEL_NAME, "Add a to b")
433+
tuple(SUITE_LABEL_NAME, "Test Simple Scenarios")
434434
);
435+
436+
// scenarios are not represented by code, so the code-location labels stay unknown
437+
assertThat(testResults)
438+
.flatExtracting(TestResult::getLabels)
439+
.extracting(Label::getName)
440+
.doesNotContain(TEST_CLASS_LABEL_NAME, TEST_METHOD_LABEL_NAME);
435441
}
436442

437443
@AllureFeatures.Steps

allure-java-commons/src/main/java/io/qameta/allure/AllureLifecycle.java

Lines changed: 65 additions & 7 deletions
Original file line numberDiff line numberDiff line change
@@ -24,6 +24,7 @@
2424
import io.qameta.allure.listener.TestLifecycleListener;
2525
import io.qameta.allure.model.Attachment;
2626
import io.qameta.allure.model.FixtureResult;
27+
import io.qameta.allure.model.Label;
2728
import io.qameta.allure.model.Parameter;
2829
import io.qameta.allure.model.ScopeFixtureResult;
2930
import io.qameta.allure.model.ScopeFixtureType;
@@ -47,6 +48,7 @@
4748
import java.util.ArrayList;
4849
import java.util.Collection;
4950
import java.util.Comparator;
51+
import java.util.HashSet;
5052
import java.util.LinkedHashSet;
5153
import java.util.List;
5254
import java.util.Map;
@@ -58,6 +60,7 @@
5860
import java.util.concurrent.CompletableFuture;
5961
import java.util.concurrent.CompletionStage;
6062
import java.util.concurrent.ConcurrentHashMap;
63+
import java.util.concurrent.CopyOnWriteArrayList;
6164
import java.util.concurrent.atomic.AtomicBoolean;
6265
import java.util.function.BiConsumer;
6366
import java.util.function.Consumer;
@@ -329,10 +332,34 @@ public void updateTest(final Consumer<TestResult> update) {
329332
}
330333

331334
/**
332-
* Stops test by given key. The test must be running; scope metadata is merged into the test here. If the test has
333-
* a test case id but no history id, a compatibility history id is generated from the test case id and the final
334-
* parameters. A history id supplied by a {@link TestLifecycleListener#beforeTestStop(TestResult)} listener is
335-
* preserved. Unbinds the calling thread only if the test is the calling thread's root.
335+
* Registers default labels for the test with given key. Default labels do not appear on the test result until
336+
* the test stops: {@link #stopTest(AllureExternalKey)} merges them after scope metadata, adding, for each
337+
* distinct label name, the default labels with that name only when the test has no labels with that name by
338+
* then. Labels provided by the user — through annotations, the runtime API, or before fixtures — thus take
339+
* precedence over defaults instead of being duplicated by them. Repeated calls accumulate.
340+
*
341+
* <p>Intended for the framework-computed grouping labels a user may legitimately override: the suite family
342+
* and BDD structure labels. System labels — framework, language, host, thread, package, testClass, testMethod
343+
* — are facts about the run, not defaults: set them eagerly on the result (host and thread are overridable
344+
* only through their dedicated properties, see {@code ResultsUtils}).</p>
345+
*
346+
* @param key the external test key
347+
* @param labels the default labels
348+
*/
349+
public void addDefaultLabels(final AllureExternalKey key, final Collection<Label> labels) {
350+
final TestItem item = getItem(key, TestItem.class, "add default labels");
351+
if (Objects.isNull(item)) {
352+
return;
353+
}
354+
item.defaultLabels().addAll(labels);
355+
}
356+
357+
/**
358+
* Stops test by given key. The test must be running; scope metadata is merged into the test here, then default
359+
* labels are applied for each label name the test still has no labels for. If the test has a test case id but
360+
* no history id, a compatibility history id is generated from the test case id and the final parameters. A
361+
* history id supplied by a {@link TestLifecycleListener#beforeTestStop(TestResult)} listener is preserved.
362+
* Unbinds the calling thread only if the test is the calling thread's root.
336363
*
337364
* @param key the external test key
338365
*/
@@ -357,6 +384,7 @@ public void stopTest(final AllureExternalKey key) {
357384
testResult.setParameters(new ArrayList<>());
358385
}
359386
applyScopeMetadata(item);
387+
applyDefaultLabels(item);
360388
if (Objects.isNull(testResult.getHistoryId()) && Objects.nonNull(testResult.getTestCaseId())) {
361389
testResult.setHistoryId(calculateHistoryId(testResult.getTestCaseId(), testResult.getParameters()));
362390
}
@@ -1312,6 +1340,34 @@ private void applyScopeMetadata(final TestItem item) {
13121340
}
13131341
}
13141342

1343+
/**
1344+
* Adds the test's registered default labels — for each distinct label name, only when the test has no labels
1345+
* with that name. Runs after scope metadata is merged, so labels set from before fixtures also take precedence
1346+
* over defaults.
1347+
*/
1348+
private static void applyDefaultLabels(final TestItem item) {
1349+
if (item.defaultLabels().isEmpty()) {
1350+
return;
1351+
}
1352+
final TestResult testResult = item.result();
1353+
// the result may carry an immutable label list, so merge into a mutable copy
1354+
final List<Label> labels = Objects.isNull(testResult.getLabels())
1355+
? new ArrayList<>()
1356+
: new ArrayList<>(testResult.getLabels());
1357+
final Set<String> presentNames = new HashSet<>();
1358+
for (final Label label : labels) {
1359+
if (Objects.nonNull(label)) {
1360+
presentNames.add(label.getName());
1361+
}
1362+
}
1363+
for (final Label label : item.defaultLabels()) {
1364+
if (Objects.nonNull(label) && !presentNames.contains(label.getName())) {
1365+
labels.add(label);
1366+
}
1367+
}
1368+
testResult.setLabels(labels);
1369+
}
1370+
13151371
private static void mergeScopeMetadata(final ScopeResult scope, final TestResult testResult) {
13161372
testResult.getLabels().addAll(scope.getLabels());
13171373
testResult.getLinks().addAll(scope.getLinks());
@@ -1360,12 +1416,14 @@ private static void normalizeScope(final ScopeResult scope) {
13601416

13611417
/**
13621418
* Internal item of a scheduled or running test: the result model, the scopes the test is linked to (their
1363-
* metadata is merged into the test at stop), and the async attachment futures awaited before the test is written.
1419+
* metadata is merged into the test at stop), the default labels (applied at stop for label names the test has
1420+
* no labels for), and the async attachment futures awaited before the test is written.
13641421
*/
1365-
private record TestItem(TestResult result, Set<AllureExternalKey> scopes, Set<CompletableFuture<?>> futures) {
1422+
private record TestItem(TestResult result, Set<AllureExternalKey> scopes, List<Label> defaultLabels,
1423+
Set<CompletableFuture<?>> futures) {
13661424

13671425
private TestItem(final TestResult result) {
1368-
this(result, ConcurrentHashMap.newKeySet(), ConcurrentHashMap.newKeySet());
1426+
this(result, ConcurrentHashMap.newKeySet(), new CopyOnWriteArrayList<>(), ConcurrentHashMap.newKeySet());
13691427
}
13701428
}
13711429

0 commit comments

Comments
 (0)