Skip to content

Commit 12398e6

Browse files
noronhaabaev
authored andcommitted
add allure-cucumber3-jvm (via #225)
1 parent 5fd6fce commit 12398e6

19 files changed

Lines changed: 1173 additions & 0 deletions

allure-cucumber3-jvm/build.gradle

Lines changed: 30 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,30 @@
1+
description = 'Allure CucumberJVM 3.0'
2+
3+
apply from: "${gradleScriptDir}/maven-publish.gradle"
4+
apply from: "${gradleScriptDir}/bintray.gradle"
5+
apply plugin: 'maven'
6+
7+
configurations {
8+
agent
9+
}
10+
11+
def cucumber_version = '3.0.0'
12+
13+
dependencies {
14+
agent 'org.aspectj:aspectjweaver'
15+
16+
compile project(':allure-java-commons')
17+
compile group: 'io.cucumber', name: 'cucumber-core', version: "${cucumber_version}"
18+
compile group: 'io.cucumber', name: 'cucumber-java', version: "${cucumber_version}"
19+
20+
testCompile group: 'io.cucumber', name: 'cucumber-testng', version: "${cucumber_version}"
21+
}
22+
23+
test.doFirst {
24+
jvmArgs "-javaagent:${configurations.agent.singleFile}"
25+
}
26+
27+
test {
28+
systemProperty 'allure.model.indentOutput', true
29+
systemProperty 'allure.results.directory', 'build/allure-results'
30+
}
Lines changed: 315 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,315 @@
1+
package io.qameta.allure.cucumber3jvm;
2+
3+
import cucumber.api.HookTestStep;
4+
import cucumber.api.HookType;
5+
import cucumber.api.PendingException;
6+
import cucumber.api.PickleStepTestStep;
7+
import cucumber.api.Result;
8+
import cucumber.api.TestCase;
9+
import cucumber.api.TestStep;
10+
import cucumber.api.event.EventHandler;
11+
import cucumber.api.event.EventPublisher;
12+
import cucumber.api.event.TestSourceRead;
13+
import cucumber.api.event.TestCaseStarted;
14+
import cucumber.api.event.TestCaseFinished;
15+
import cucumber.api.event.TestStepStarted;
16+
import cucumber.api.event.TestStepFinished;
17+
import cucumber.api.formatter.Formatter;
18+
19+
import gherkin.ast.Feature;
20+
import gherkin.ast.ScenarioDefinition;
21+
import gherkin.ast.ScenarioOutline;
22+
import gherkin.ast.Examples;
23+
import gherkin.ast.TableRow;
24+
import gherkin.pickles.PickleCell;
25+
import gherkin.pickles.PickleRow;
26+
import gherkin.pickles.PickleTable;
27+
import gherkin.pickles.PickleTag;
28+
import io.qameta.allure.Allure;
29+
import io.qameta.allure.AllureLifecycle;
30+
import io.qameta.allure.model.Parameter;
31+
import io.qameta.allure.model.Status;
32+
import io.qameta.allure.model.TestResult;
33+
import io.qameta.allure.model.StepResult;
34+
import io.qameta.allure.model.StatusDetails;
35+
import io.qameta.allure.util.ResultsUtils;
36+
37+
import java.io.ByteArrayInputStream;
38+
import java.nio.charset.Charset;
39+
import java.util.Deque;
40+
import java.util.LinkedList;
41+
import java.util.List;
42+
import java.util.Map;
43+
import java.util.HashMap;
44+
import java.util.Optional;
45+
import java.util.UUID;
46+
import java.util.function.Consumer;
47+
import java.util.stream.Collectors;
48+
import java.util.stream.IntStream;
49+
50+
/**
51+
* Allure plugin for Cucumber JVM 3.0.
52+
*/
53+
@SuppressWarnings({
54+
"PMD.ExcessiveImports",
55+
"ClassFanOutComplexity", "ClassDataAbstractionCoupling"
56+
})
57+
public class AllureCucumber3Jvm implements Formatter {
58+
59+
private final AllureLifecycle lifecycle;
60+
61+
private final Map<String, String> scenarioUuids = new HashMap<>();
62+
63+
private final CucumberSourceUtils cucumberSourceUtils = new CucumberSourceUtils();
64+
private Feature currentFeature;
65+
private String currentFeatureFile;
66+
private TestCase currentTestCase;
67+
68+
private final EventHandler<TestSourceRead> featureStartedHandler = this::handleFeatureStartedHandler;
69+
private final EventHandler<TestCaseStarted> caseStartedHandler = this::handleTestCaseStarted;
70+
private final EventHandler<TestCaseFinished> caseFinishedHandler = this::handleTestCaseFinished;
71+
private final EventHandler<TestStepStarted> stepStartedHandler = this::handleTestStepStarted;
72+
private final EventHandler<TestStepFinished> stepFinishedHandler = this::handleTestStepFinished;
73+
74+
public AllureCucumber3Jvm() {
75+
this.lifecycle = Allure.getLifecycle();
76+
}
77+
78+
@Override
79+
public void setEventPublisher(final EventPublisher publisher) {
80+
publisher.registerHandlerFor(TestSourceRead.class, featureStartedHandler);
81+
82+
publisher.registerHandlerFor(TestCaseStarted.class, caseStartedHandler);
83+
publisher.registerHandlerFor(TestCaseFinished.class, caseFinishedHandler);
84+
85+
publisher.registerHandlerFor(TestStepStarted.class, stepStartedHandler);
86+
publisher.registerHandlerFor(TestStepFinished.class, stepFinishedHandler);
87+
}
88+
89+
/*
90+
Event Handlers
91+
*/
92+
93+
private void handleFeatureStartedHandler(final TestSourceRead event) {
94+
cucumberSourceUtils.addTestSourceReadEvent(event.uri, event);
95+
}
96+
97+
private void handleTestCaseStarted(final TestCaseStarted event) {
98+
currentFeatureFile = event.testCase.getUri();
99+
currentFeature = cucumberSourceUtils.getFeature(currentFeatureFile);
100+
101+
currentTestCase = event.testCase;
102+
103+
final Deque<PickleTag> tags = new LinkedList<>();
104+
tags.addAll(event.testCase.getTags());
105+
106+
final LabelBuilder labelBuilder = new LabelBuilder(currentFeature, event.testCase, tags);
107+
108+
final TestResult result = new TestResult()
109+
.withUuid(getTestCaseUuid(event.testCase))
110+
.withHistoryId(getHistoryId(event.testCase))
111+
.withName(event.testCase.getName())
112+
.withLabels(labelBuilder.getScenarioLabels())
113+
.withLinks(labelBuilder.getScenarioLinks());
114+
115+
final ScenarioDefinition scenarioDefinition =
116+
cucumberSourceUtils.getScenarioDefinition(currentFeatureFile, currentTestCase.getLine());
117+
if (scenarioDefinition instanceof ScenarioOutline) {
118+
result.withParameters(
119+
getExamplesAsParameters((ScenarioOutline) scenarioDefinition)
120+
);
121+
}
122+
123+
if (currentFeature.getDescription() != null && !currentFeature.getDescription().isEmpty()) {
124+
result.withDescription(currentFeature.getDescription());
125+
}
126+
127+
lifecycle.scheduleTestCase(result);
128+
lifecycle.startTestCase(getTestCaseUuid(event.testCase));
129+
}
130+
131+
private void handleTestCaseFinished(final TestCaseFinished event) {
132+
final StatusDetails statusDetails =
133+
ResultsUtils.getStatusDetails(event.result.getError()).orElse(new StatusDetails());
134+
135+
if (statusDetails.getMessage() != null && statusDetails.getTrace() != null) {
136+
lifecycle.updateTestCase(getTestCaseUuid(event.testCase), scenarioResult ->
137+
scenarioResult
138+
.withStatus(translateTestCaseStatus(event.result))
139+
.withStatusDetails(statusDetails));
140+
} else {
141+
lifecycle.updateTestCase(getTestCaseUuid(event.testCase), scenarioResult ->
142+
scenarioResult
143+
.withStatus(translateTestCaseStatus(event.result)));
144+
}
145+
146+
lifecycle.stopTestCase(getTestCaseUuid(event.testCase));
147+
lifecycle.writeTestCase(getTestCaseUuid(event.testCase));
148+
}
149+
150+
private void handleTestStepStarted(final TestStepStarted event) {
151+
if (event.testStep instanceof PickleStepTestStep) {
152+
final PickleStepTestStep pickleStepTestStep = (PickleStepTestStep) event.testStep;
153+
154+
final String stepKeyword = Optional.ofNullable(
155+
cucumberSourceUtils.getKeywordFromSource(currentFeatureFile, pickleStepTestStep.getStepLine())
156+
).orElse("UNDEFINED");
157+
158+
final StepResult stepResult = new StepResult()
159+
.withName(String.format("%s %s", stepKeyword, pickleStepTestStep.getPickleStep().getText()))
160+
.withStart(System.currentTimeMillis());
161+
162+
lifecycle.startStep(getTestCaseUuid(currentTestCase), getStepUuid(event.testStep), stepResult);
163+
164+
pickleStepTestStep.getStepArgument().stream()
165+
.filter(argument -> argument instanceof PickleTable)
166+
.findFirst()
167+
.ifPresent(table -> createDataTableAttachment((PickleTable) table));
168+
} else if (event.testStep instanceof HookTestStep) {
169+
final HookTestStep hookTestStep = (HookTestStep) event.testStep;
170+
final StepResult stepResult = new StepResult()
171+
.withName(hookTestStep.getHookType().toString())
172+
.withStart(System.currentTimeMillis());
173+
174+
lifecycle.startStep(getTestCaseUuid(currentTestCase), getHookStepUuid(event.testStep), stepResult);
175+
} else {
176+
throw new IllegalStateException();
177+
}
178+
}
179+
180+
private void handleTestStepFinished(final TestStepFinished event) {
181+
if (event.testStep instanceof PickleStepTestStep) {
182+
handlePickleStep(event);
183+
} else if (event.testStep instanceof HookTestStep) {
184+
handleHookStep(event);
185+
} else {
186+
throw new IllegalStateException();
187+
}
188+
}
189+
190+
/*
191+
Utility Methods
192+
*/
193+
194+
private String getTestCaseUuid(final TestCase testCase) {
195+
return scenarioUuids.computeIfAbsent(getHistoryId(testCase), it -> UUID.randomUUID().toString());
196+
}
197+
198+
private String getStepUuid(final TestStep step) {
199+
final PickleStepTestStep pickleStep = (PickleStepTestStep) step;
200+
return currentFeature.getName() + getTestCaseUuid(currentTestCase)
201+
+ pickleStep.getStepText() + pickleStep.getStepLine();
202+
}
203+
204+
private String getHookStepUuid(final TestStep step) {
205+
final HookTestStep hookTestStep = (HookTestStep) step;
206+
return currentFeature.getName() + getTestCaseUuid(currentTestCase)
207+
+ hookTestStep.getHookType().toString() + step.getCodeLocation();
208+
}
209+
210+
private String getHistoryId(final TestCase testCase) {
211+
final String testCaseLocation = testCase.getUri() + ":" + testCase.getLine();
212+
return Utils.md5(testCaseLocation);
213+
}
214+
215+
private Status translateTestCaseStatus(final Result testCaseResult) {
216+
Status allureStatus;
217+
if (testCaseResult.getStatus() == Result.Type.UNDEFINED || testCaseResult.getStatus() == Result.Type.PENDING) {
218+
allureStatus = Status.SKIPPED;
219+
} else {
220+
try {
221+
allureStatus = Status.fromValue(testCaseResult.getStatus().lowerCaseName());
222+
} catch (IllegalArgumentException e) {
223+
allureStatus = Status.BROKEN;
224+
}
225+
}
226+
return allureStatus;
227+
}
228+
229+
private List<Parameter> getExamplesAsParameters(final ScenarioOutline scenarioOutline) {
230+
final Examples examples = scenarioOutline.getExamples().get(0);
231+
final TableRow row = examples.getTableBody().stream()
232+
.filter(example -> example.getLocation().getLine() == currentTestCase.getLine())
233+
.findFirst().get();
234+
return IntStream.range(0, examples.getTableHeader().getCells().size()).mapToObj(index -> {
235+
final String name = examples.getTableHeader().getCells().get(index).getValue();
236+
final String value = row.getCells().get(index).getValue();
237+
return new Parameter().withName(name).withValue(value);
238+
}).collect(Collectors.toList());
239+
}
240+
241+
private void createDataTableAttachment(final PickleTable pickleTable) {
242+
final List<PickleRow> rows = pickleTable.getRows();
243+
244+
final StringBuilder dataTableCsv = new StringBuilder();
245+
if (!rows.isEmpty()) {
246+
rows.forEach(dataTableRow -> {
247+
dataTableCsv.append(
248+
dataTableRow.getCells().stream()
249+
.map(PickleCell::getValue)
250+
.collect(Collectors.joining("\t"))
251+
);
252+
dataTableCsv.append('\n');
253+
});
254+
255+
final String attachmentSource = lifecycle
256+
.prepareAttachment("Data table", "text/tab-separated-values", "csv");
257+
lifecycle.writeAttachment(attachmentSource,
258+
new ByteArrayInputStream(dataTableCsv.toString().getBytes(Charset.forName("UTF-8"))));
259+
}
260+
}
261+
262+
private void handleHookStep(final TestStepFinished event) {
263+
final String uuid = getHookStepUuid(event.testStep);
264+
Consumer<StepResult> stepResult = result -> result.withStatus(translateTestCaseStatus(event.result));
265+
266+
if (!Status.PASSED.equals(translateTestCaseStatus(event.result))) {
267+
final StatusDetails statusDetails = ResultsUtils.getStatusDetails(event.result.getError()).get();
268+
final HookTestStep hookTestStep = (HookTestStep) event.testStep;
269+
if (hookTestStep.getHookType() == HookType.Before) {
270+
final TagParser tagParser = new TagParser(currentFeature, currentTestCase);
271+
statusDetails
272+
.withMessage("Before is failed: " + event.result.getError().getLocalizedMessage())
273+
.withFlaky(tagParser.isFlaky())
274+
.withMuted(tagParser.isMuted())
275+
.withKnown(tagParser.isKnown());
276+
lifecycle.updateTestCase(getTestCaseUuid(currentTestCase), scenarioResult ->
277+
scenarioResult.withStatus(Status.SKIPPED)
278+
.withStatusDetails(statusDetails));
279+
}
280+
stepResult = result -> result
281+
.withStatus(translateTestCaseStatus(event.result))
282+
.withStatusDetails(statusDetails);
283+
}
284+
285+
lifecycle.updateStep(uuid, stepResult);
286+
lifecycle.stopStep(uuid);
287+
}
288+
289+
private void handlePickleStep(final TestStepFinished event) {
290+
final StatusDetails statusDetails;
291+
if (event.result.getStatus() == Result.Type.UNDEFINED) {
292+
statusDetails =
293+
ResultsUtils.getStatusDetails(new PendingException("TODO: implement me"))
294+
.orElse(new StatusDetails());
295+
lifecycle.updateTestCase(getTestCaseUuid(currentTestCase), scenarioResult ->
296+
scenarioResult
297+
.withStatus(translateTestCaseStatus(event.result))
298+
.withStatusDetails(statusDetails));
299+
} else {
300+
statusDetails =
301+
ResultsUtils.getStatusDetails(event.result.getError())
302+
.orElse(new StatusDetails());
303+
}
304+
305+
final TagParser tagParser = new TagParser(currentFeature, currentTestCase);
306+
statusDetails
307+
.withFlaky(tagParser.isFlaky())
308+
.withMuted(tagParser.isMuted())
309+
.withKnown(tagParser.isKnown());
310+
311+
lifecycle.updateStep(getStepUuid(event.testStep), stepResult ->
312+
stepResult.withStatus(translateTestCaseStatus(event.result)));
313+
lifecycle.stopStep(getStepUuid(event.testStep));
314+
}
315+
}

0 commit comments

Comments
 (0)