Skip to content

Commit caf0c4b

Browse files
authored
introduce adaptor for jbehave 5 (via #937)
1 parent 466aa0b commit caf0c4b

23 files changed

+915
-1
lines changed

.github/ISSUE_TEMPLATE/bug_report.yml

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -41,6 +41,7 @@ body:
4141
- allure-java-commons
4242
- allure-jax-rs
4343
- allure-jbehave
44+
- allure-jbehave5
4445
- allure-jsonunit
4546
- allure-junit-platform
4647
- allure-junit4

.github/labeler.yml

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -38,7 +38,7 @@
3838
- "allure-jax-rs/**"
3939

4040
"theme:jbehave":
41-
- "allure-jbehave/**"
41+
- "allure-jbehave*/**"
4242

4343
"theme:jsonunit":
4444
- "allure-jsonunit/**"

allure-jbehave5/build.gradle.kts

Lines changed: 27 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,27 @@
1+
description = "Allure JBehave 5 Integration"
2+
3+
val jbehaveVersion = "5.1.1"
4+
5+
dependencies {
6+
api(project(":allure-java-commons"))
7+
implementation("org.jbehave:jbehave-core:$jbehaveVersion")
8+
testImplementation("org.assertj:assertj-core")
9+
testImplementation("org.junit.jupiter:junit-jupiter-api")
10+
testImplementation("org.mockito:mockito-core")
11+
testImplementation("org.slf4j:slf4j-simple")
12+
testImplementation(project(":allure-java-commons-test"))
13+
testImplementation(project(":allure-junit-platform"))
14+
testRuntimeOnly("org.junit.jupiter:junit-jupiter-engine")
15+
}
16+
17+
tasks.jar {
18+
manifest {
19+
attributes(mapOf(
20+
"Automatic-Module-Name" to "io.qameta.allure.jbehave5"
21+
))
22+
}
23+
}
24+
25+
tasks.test {
26+
useJUnitPlatform()
27+
}
Lines changed: 298 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,298 @@
1+
/*
2+
* Copyright 2019 Qameta Software OÜ
3+
*
4+
* Licensed under the Apache License, Version 2.0 (the "License");
5+
* you may not use this file except in compliance with the License.
6+
* You may obtain a copy of the License at
7+
*
8+
* http://www.apache.org/licenses/LICENSE-2.0
9+
*
10+
* Unless required by applicable law or agreed to in writing, software
11+
* distributed under the License is distributed on an "AS IS" BASIS,
12+
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
13+
* See the License for the specific language governing permissions and
14+
* limitations under the License.
15+
*/
16+
package io.qameta.allure.jbehave5;
17+
18+
import io.qameta.allure.Allure;
19+
import io.qameta.allure.AllureLifecycle;
20+
import io.qameta.allure.model.Label;
21+
import io.qameta.allure.model.Parameter;
22+
import io.qameta.allure.model.Stage;
23+
import io.qameta.allure.model.Status;
24+
import io.qameta.allure.model.StatusDetails;
25+
import io.qameta.allure.model.StepResult;
26+
import io.qameta.allure.model.TestResult;
27+
import org.jbehave.core.failures.UUIDExceptionWrapper;
28+
import org.jbehave.core.model.ExamplesTable;
29+
import org.jbehave.core.model.Scenario;
30+
import org.jbehave.core.model.Step;
31+
import org.jbehave.core.model.Story;
32+
import org.jbehave.core.reporters.NullStoryReporter;
33+
import org.jbehave.core.steps.StepCreator;
34+
import org.jbehave.core.steps.Timing;
35+
36+
import java.security.MessageDigest;
37+
import java.util.ArrayList;
38+
import java.util.Arrays;
39+
import java.util.Deque;
40+
import java.util.LinkedList;
41+
import java.util.List;
42+
import java.util.Map;
43+
import java.util.UUID;
44+
import java.util.concurrent.ConcurrentHashMap;
45+
import java.util.concurrent.locks.ReadWriteLock;
46+
import java.util.concurrent.locks.ReentrantReadWriteLock;
47+
import java.util.stream.Collectors;
48+
49+
import static io.qameta.allure.util.ResultsUtils.bytesToHex;
50+
import static io.qameta.allure.util.ResultsUtils.createFrameworkLabel;
51+
import static io.qameta.allure.util.ResultsUtils.createHostLabel;
52+
import static io.qameta.allure.util.ResultsUtils.createLanguageLabel;
53+
import static io.qameta.allure.util.ResultsUtils.createParameter;
54+
import static io.qameta.allure.util.ResultsUtils.createStoryLabel;
55+
import static io.qameta.allure.util.ResultsUtils.createThreadLabel;
56+
import static io.qameta.allure.util.ResultsUtils.getMd5Digest;
57+
import static io.qameta.allure.util.ResultsUtils.getStatus;
58+
import static io.qameta.allure.util.ResultsUtils.getStatusDetails;
59+
import static java.nio.charset.StandardCharsets.UTF_8;
60+
import static java.util.Collections.emptyList;
61+
import static java.util.Collections.emptyMap;
62+
import static java.util.Collections.singletonList;
63+
import static java.util.Comparator.comparing;
64+
65+
public class AllureJbehave5 extends NullStoryReporter {
66+
67+
private final AllureLifecycle lifecycle;
68+
69+
private final ThreadLocal<Story> currentStory = new InheritableThreadLocal<>();
70+
71+
private final ThreadLocal<Scenario> currentScenario = new InheritableThreadLocal<>();
72+
73+
private final Map<Scenario, List<String>> scenarioUuids = new ConcurrentHashMap<>();
74+
75+
private final ThreadLocal<Deque<Story>> givenStories = ThreadLocal.withInitial(LinkedList::new);
76+
77+
private final ReadWriteLock lock = new ReentrantReadWriteLock();
78+
79+
@SuppressWarnings("unused")
80+
public AllureJbehave5() {
81+
this(Allure.getLifecycle());
82+
}
83+
84+
public AllureJbehave5(final AllureLifecycle lifecycle) {
85+
this.lifecycle = lifecycle;
86+
}
87+
88+
@Override
89+
public void beforeStory(final Story story, final boolean givenStory) {
90+
if (givenStory) {
91+
givenStories.get().push(story);
92+
} else {
93+
currentStory.set(story);
94+
}
95+
}
96+
97+
@Override
98+
public void afterStory(final boolean givenStory) {
99+
if (givenStory) {
100+
givenStories.get().pop();
101+
} else {
102+
currentStory.remove();
103+
}
104+
}
105+
106+
@Override
107+
public void beforeScenario(final Scenario scenario) {
108+
if (isGivenStory()) {
109+
return;
110+
}
111+
currentScenario.set(scenario);
112+
113+
if (notParameterised(scenario)) {
114+
final String uuid = UUID.randomUUID().toString();
115+
usingWriteLock(() -> scenarioUuids.put(scenario, new ArrayList<>(singletonList(uuid))));
116+
startTestCase(uuid, scenario, emptyMap());
117+
} else {
118+
usingWriteLock(() -> scenarioUuids.put(scenario, new ArrayList<>()));
119+
}
120+
}
121+
122+
@Override
123+
public void beforeExamples(final List<String> steps, final ExamplesTable table) {
124+
if (isGivenStory()) {
125+
return;
126+
}
127+
final Scenario scenario = currentScenario.get();
128+
lock.writeLock().lock();
129+
try {
130+
scenarioUuids.put(scenario, new ArrayList<>());
131+
} finally {
132+
lock.writeLock().unlock();
133+
}
134+
}
135+
136+
@Override
137+
public void example(final Map<String, String> tableRow, final int exampleIndex) {
138+
if (isGivenStory()) {
139+
return;
140+
}
141+
final Scenario scenario = currentScenario.get();
142+
final String uuid = UUID.randomUUID().toString();
143+
usingWriteLock(() -> scenarioUuids.getOrDefault(scenario, new ArrayList<>()).add(uuid));
144+
startTestCase(uuid, scenario, tableRow);
145+
}
146+
147+
@Override
148+
public void afterScenario(final Timing timing) {
149+
if (isGivenStory()) {
150+
return;
151+
}
152+
final Scenario scenario = currentScenario.get();
153+
usingReadLock(() -> {
154+
final List<String> uuids = scenarioUuids.getOrDefault(scenario, emptyList());
155+
uuids.forEach(this::stopTestCase);
156+
});
157+
currentScenario.remove();
158+
usingWriteLock(() -> scenarioUuids.remove(scenario));
159+
}
160+
161+
@Override
162+
public void beforeStep(final Step step) {
163+
final String stepUuid = UUID.randomUUID().toString();
164+
getLifecycle().startStep(stepUuid, new StepResult().setName(step.getStepAsString()));
165+
}
166+
167+
@Override
168+
public void successful(final String step) {
169+
getLifecycle().updateTestCase(result -> result.setStatus(Status.PASSED));
170+
getLifecycle().updateStep(result -> result.setStatus(Status.PASSED));
171+
getLifecycle().stopStep();
172+
}
173+
174+
@Override
175+
public void ignorable(final String step) {
176+
getLifecycle().stopStep();
177+
}
178+
179+
@Override
180+
public void pending(final StepCreator.PendingStep step) {
181+
getLifecycle().updateStep(result -> result.setStatus(Status.SKIPPED));
182+
getLifecycle().stopStep();
183+
}
184+
185+
@Override
186+
public void notPerformed(final String step) {
187+
getLifecycle().stopStep();
188+
}
189+
190+
@Override
191+
public void failed(final String step, final Throwable cause) {
192+
final Throwable unwrapped = cause instanceof UUIDExceptionWrapper
193+
? cause.getCause()
194+
: cause;
195+
196+
197+
final Status status = getStatus(unwrapped).orElse(Status.FAILED);
198+
final StatusDetails statusDetails = getStatusDetails(unwrapped).orElseGet(StatusDetails::new);
199+
200+
getLifecycle().updateStep(result -> {
201+
result.setStatus(status);
202+
result.setStatusDetails(statusDetails);
203+
});
204+
205+
getLifecycle().updateTestCase(result -> {
206+
result.setStatus(status);
207+
result.setStatusDetails(statusDetails);
208+
});
209+
210+
getLifecycle().stopStep();
211+
}
212+
213+
214+
public AllureLifecycle getLifecycle() {
215+
return lifecycle;
216+
}
217+
218+
protected void startTestCase(final String uuid,
219+
final Scenario scenario,
220+
final Map<String, String> tableRow) {
221+
final Story story = currentStory.get();
222+
223+
final String name = scenario.getTitle();
224+
final String fullName = String.format("%s: %s", story.getName(), name);
225+
226+
final List<Parameter> parameters = tableRow.entrySet().stream()
227+
.map(entry -> createParameter(entry.getKey(), entry.getValue()))
228+
.collect(Collectors.toList());
229+
230+
final List<Label> labels = Arrays.asList(
231+
createStoryLabel(story.getName()),
232+
createHostLabel(),
233+
createThreadLabel(),
234+
createFrameworkLabel("jbehave"),
235+
createLanguageLabel("java")
236+
);
237+
238+
final String historyId = getHistoryId(fullName, parameters);
239+
240+
final TestResult result = new TestResult()
241+
.setUuid(uuid)
242+
.setName(name)
243+
.setFullName(fullName)
244+
.setStage(Stage.SCHEDULED)
245+
.setLabels(labels)
246+
.setParameters(parameters)
247+
.setDescription(story.getDescription().asString())
248+
.setHistoryId(historyId);
249+
250+
getLifecycle().scheduleTestCase(result);
251+
getLifecycle().startTestCase(result.getUuid());
252+
}
253+
254+
protected void stopTestCase(final String uuid) {
255+
getLifecycle().stopTestCase(uuid);
256+
getLifecycle().writeTestCase(uuid);
257+
}
258+
259+
protected boolean notParameterised(final Scenario scenario) {
260+
return scenario.getExamplesTable().getRowCount() == 0;
261+
}
262+
263+
protected String getHistoryId(final String fullName, final List<Parameter> parameters) {
264+
final MessageDigest digest = getMd5Digest();
265+
digest.update(fullName.getBytes(UTF_8));
266+
parameters.stream()
267+
.sorted(comparing(Parameter::getName).thenComparing(Parameter::getValue))
268+
.forEachOrdered(parameter -> {
269+
digest.update(parameter.getName().getBytes(UTF_8));
270+
digest.update(parameter.getValue().getBytes(UTF_8));
271+
});
272+
final byte[] bytes = digest.digest();
273+
return bytesToHex(bytes);
274+
}
275+
276+
private boolean isGivenStory() {
277+
return !givenStories.get().isEmpty();
278+
}
279+
280+
private void usingReadLock(final Runnable runnable) {
281+
lock.readLock().lock();
282+
try {
283+
runnable.run();
284+
} finally {
285+
lock.readLock().unlock();
286+
}
287+
}
288+
289+
private void usingWriteLock(final Runnable runnable) {
290+
lock.writeLock().lock();
291+
try {
292+
runnable.run();
293+
} finally {
294+
lock.writeLock().unlock();
295+
}
296+
}
297+
298+
}

0 commit comments

Comments
 (0)