Skip to content

Commit 6697f06

Browse files
authored
add citrus framework integration (fixes allure-framework#266, via allure-framework#295)
1 parent f6b0140 commit 6697f06

File tree

6 files changed

+559
-0
lines changed

6 files changed

+559
-0
lines changed

allure-citrus/build.gradle.kts

Lines changed: 38 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,38 @@
1+
description = "Allure Citrus Integration"
2+
3+
val agent by configurations.creating
4+
5+
val citrusVersion = "2.7.8"
6+
7+
dependencies {
8+
agent("org.aspectj:aspectjweaver")
9+
compile("com.consol.citrus:citrus-core:$citrusVersion")
10+
compile(project(":allure-java-commons"))
11+
testCompile("com.consol.citrus:citrus-http:$citrusVersion")
12+
testCompile("com.consol.citrus:citrus-java-dsl:$citrusVersion")
13+
testCompile("io.github.glytching:junit-extensions")
14+
testCompile("org.assertj:assertj-core")
15+
testCompile("org.junit.jupiter:junit-jupiter-api")
16+
testCompile("org.junit.jupiter:junit-jupiter-params")
17+
testCompile("org.slf4j:slf4j-simple")
18+
testCompile(project(":allure-assertj"))
19+
testCompile(project(":allure-java-commons-test"))
20+
testCompile(project(":allure-junit-platform"))
21+
testRuntime("org.junit.jupiter:junit-jupiter-engine")
22+
}
23+
24+
tasks.named<Jar>("jar") {
25+
manifest {
26+
attributes(mapOf(
27+
"Automatic-Module-Name" to "io.qameta.allure.citrus"
28+
))
29+
}
30+
}
31+
32+
tasks.named<Test>("test") {
33+
useJUnitPlatform()
34+
exclude("**/samples/*")
35+
doFirst {
36+
jvmArgs("-javaagent:${agent.singleFile}")
37+
}
38+
}
Lines changed: 288 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,288 @@
1+
package io.qameta.allure.citrus;
2+
3+
import com.consol.citrus.TestAction;
4+
import com.consol.citrus.TestCase;
5+
import com.consol.citrus.report.TestActionListener;
6+
import com.consol.citrus.report.TestListener;
7+
import com.consol.citrus.report.TestSuiteListener;
8+
import io.qameta.allure.Allure;
9+
import io.qameta.allure.AllureLifecycle;
10+
import io.qameta.allure.Description;
11+
import io.qameta.allure.Epic;
12+
import io.qameta.allure.Feature;
13+
import io.qameta.allure.Story;
14+
import io.qameta.allure.model.Label;
15+
import io.qameta.allure.model.Link;
16+
import io.qameta.allure.model.Parameter;
17+
import io.qameta.allure.model.Stage;
18+
import io.qameta.allure.model.Status;
19+
import io.qameta.allure.model.StatusDetails;
20+
import io.qameta.allure.model.StepResult;
21+
import io.qameta.allure.model.TestResult;
22+
import io.qameta.allure.util.ObjectUtils;
23+
import io.qameta.allure.util.ResultsUtils;
24+
25+
import java.lang.annotation.Annotation;
26+
import java.lang.annotation.Repeatable;
27+
import java.lang.reflect.AnnotatedElement;
28+
import java.lang.reflect.Method;
29+
import java.util.Arrays;
30+
import java.util.Collections;
31+
import java.util.List;
32+
import java.util.Map;
33+
import java.util.Objects;
34+
import java.util.Optional;
35+
import java.util.UUID;
36+
import java.util.concurrent.ConcurrentHashMap;
37+
import java.util.concurrent.locks.ReadWriteLock;
38+
import java.util.concurrent.locks.ReentrantReadWriteLock;
39+
import java.util.stream.Collectors;
40+
import java.util.stream.Stream;
41+
42+
import static io.qameta.allure.util.ResultsUtils.createFrameworkLabel;
43+
import static io.qameta.allure.util.ResultsUtils.createHostLabel;
44+
import static io.qameta.allure.util.ResultsUtils.createLanguageLabel;
45+
import static io.qameta.allure.util.ResultsUtils.createSuiteLabel;
46+
import static io.qameta.allure.util.ResultsUtils.createThreadLabel;
47+
import static io.qameta.allure.util.ResultsUtils.getProvidedLabels;
48+
49+
/**
50+
* @author charlie (Dmitry Baev).
51+
*/
52+
public class AllureCitrus implements TestListener, TestSuiteListener, TestActionListener {
53+
54+
private final Map<TestCase, String> testUuids = new ConcurrentHashMap<>();
55+
56+
private final ReadWriteLock lock = new ReentrantReadWriteLock();
57+
58+
private final AllureLifecycle lifecycle;
59+
60+
public AllureCitrus(final AllureLifecycle lifecycle) {
61+
this.lifecycle = lifecycle;
62+
}
63+
64+
@SuppressWarnings("unused")
65+
public AllureCitrus() {
66+
this.lifecycle = Allure.getLifecycle();
67+
}
68+
69+
public AllureLifecycle getLifecycle() {
70+
return lifecycle;
71+
}
72+
73+
@Override
74+
public void onStart() {
75+
//do nothing
76+
}
77+
78+
@Override
79+
public void onStartSuccess() {
80+
//do nothing
81+
}
82+
83+
@Override
84+
public void onStartFailure(final Throwable cause) {
85+
//do nothing
86+
}
87+
88+
@Override
89+
public void onFinish() {
90+
//do nothing
91+
}
92+
93+
@Override
94+
public void onFinishSuccess() {
95+
//do nothing
96+
}
97+
98+
@Override
99+
public void onFinishFailure(final Throwable cause) {
100+
//do nothing
101+
}
102+
103+
@Override
104+
public void onTestStart(final TestCase test) {
105+
startTestCase(test);
106+
}
107+
108+
@Override
109+
public void onTestFinish(final TestCase test) {
110+
//do nothing
111+
}
112+
113+
@Override
114+
public void onTestSuccess(final TestCase test) {
115+
stopTestCase(test, Status.PASSED, null);
116+
}
117+
118+
@Override
119+
public void onTestFailure(final TestCase test, final Throwable cause) {
120+
final Status status = ResultsUtils.getStatus(cause).orElse(Status.BROKEN);
121+
final StatusDetails details = ResultsUtils.getStatusDetails(cause).orElse(null);
122+
stopTestCase(test, status, details);
123+
}
124+
125+
@Override
126+
public void onTestSkipped(final TestCase test) {
127+
//do nothing
128+
}
129+
130+
@Override
131+
public void onTestActionStart(final TestCase testCase, final TestAction testAction) {
132+
final String parentUuid = getUuid(testCase);
133+
final String uuid = UUID.randomUUID().toString();
134+
getLifecycle().startStep(parentUuid, uuid, new StepResult().setName(testAction.getName()));
135+
}
136+
137+
@Override
138+
public void onTestActionFinish(final TestCase testCase, final TestAction testAction) {
139+
getLifecycle().stopStep();
140+
}
141+
142+
@Override
143+
public void onTestActionSkipped(final TestCase testCase, final TestAction testAction) {
144+
//do nothing
145+
}
146+
147+
private void startTestCase(final TestCase testCase) {
148+
final String uuid = createUuid(testCase);
149+
150+
final TestResult result = new TestResult()
151+
.setUuid(uuid)
152+
.setName(testCase.getName())
153+
.setStage(Stage.RUNNING);
154+
155+
result.getLabels().addAll(getProvidedLabels());
156+
157+
158+
final Optional<? extends Class<?>> testClass = Optional.ofNullable(testCase.getTestClass());
159+
testClass.map(this::getLabels).ifPresent(result.getLabels()::addAll);
160+
testClass.map(this::getLinks).ifPresent(result.getLinks()::addAll);
161+
162+
result.getLabels().addAll(Arrays.asList(
163+
createHostLabel(),
164+
createThreadLabel(),
165+
createFrameworkLabel("citrus"),
166+
createLanguageLabel("java")
167+
));
168+
169+
testClass.ifPresent(aClass -> {
170+
final String suiteName = aClass.getCanonicalName();
171+
result.getLabels().add(createSuiteLabel(suiteName));
172+
});
173+
174+
final Optional<String> classDescription = testClass.flatMap(this::getDescription);
175+
final String description = Stream.of(classDescription)
176+
.filter(Optional::isPresent)
177+
.map(Optional::get)
178+
.collect(Collectors.joining("\n\n"));
179+
180+
result.setDescription(description);
181+
182+
getLifecycle().scheduleTestCase(result);
183+
getLifecycle().startTestCase(uuid);
184+
}
185+
186+
private void stopTestCase(final TestCase testCase,
187+
final Status status,
188+
final StatusDetails details) {
189+
final String uuid = removeUuid(testCase);
190+
final Map<String, Object> definitions = testCase.getVariableDefinitions();
191+
final List<Parameter> parameters = definitions.entrySet().stream()
192+
.map(entry -> new Parameter()
193+
.setName(entry.getKey())
194+
.setValue(ObjectUtils.toString(entry.getValue()))
195+
)
196+
.collect(Collectors.toList());
197+
198+
getLifecycle().updateTestCase(uuid, result -> {
199+
result.setParameters(parameters);
200+
result.setStage(Stage.FINISHED);
201+
result.setStatus(status);
202+
result.setStatusDetails(details);
203+
});
204+
getLifecycle().stopTestCase(uuid);
205+
getLifecycle().writeTestCase(uuid);
206+
}
207+
208+
209+
private String createUuid(final TestCase testCase) {
210+
final String uuid = UUID.randomUUID().toString();
211+
try {
212+
lock.writeLock().lock();
213+
testUuids.put(testCase, uuid);
214+
} finally {
215+
lock.writeLock().unlock();
216+
}
217+
return uuid;
218+
}
219+
220+
private String getUuid(final TestCase testCase) {
221+
try {
222+
lock.readLock().lock();
223+
return testUuids.get(testCase);
224+
} finally {
225+
lock.readLock().unlock();
226+
}
227+
}
228+
229+
private String removeUuid(final TestCase testCase) {
230+
try {
231+
lock.writeLock().lock();
232+
return testUuids.remove(testCase);
233+
} finally {
234+
lock.writeLock().unlock();
235+
}
236+
}
237+
238+
private Optional<String> getDescription(final AnnotatedElement annotatedElement) {
239+
return getAnnotations(annotatedElement, Description.class)
240+
.map(Description::value)
241+
.findAny();
242+
}
243+
244+
private List<Label> getLabels(final AnnotatedElement annotatedElement) {
245+
return Stream.of(
246+
getAnnotations(annotatedElement, Epic.class).map(ResultsUtils::createLabel),
247+
getAnnotations(annotatedElement, Feature.class).map(ResultsUtils::createLabel),
248+
getAnnotations(annotatedElement, Story.class).map(ResultsUtils::createLabel)
249+
).reduce(Stream::concat).orElseGet(Stream::empty).collect(Collectors.toList());
250+
}
251+
252+
private List<Link> getLinks(final AnnotatedElement annotatedElement) {
253+
return Stream.of(
254+
getAnnotations(annotatedElement, io.qameta.allure.Link.class).map(ResultsUtils::createLink),
255+
getAnnotations(annotatedElement, io.qameta.allure.Issue.class).map(ResultsUtils::createLink),
256+
getAnnotations(annotatedElement, io.qameta.allure.TmsLink.class).map(ResultsUtils::createLink))
257+
.reduce(Stream::concat).orElseGet(Stream::empty).collect(Collectors.toList());
258+
}
259+
260+
private <T extends Annotation> Stream<T> getAnnotations(final AnnotatedElement annotatedElement,
261+
final Class<T> annotationClass) {
262+
final T annotation = annotatedElement.getAnnotation(annotationClass);
263+
return Stream.concat(
264+
extractRepeatable(annotatedElement, annotationClass).stream(),
265+
Objects.isNull(annotation) ? Stream.empty() : Stream.of(annotation)
266+
);
267+
}
268+
269+
@SuppressWarnings("unchecked")
270+
private <T extends Annotation> List<T> extractRepeatable(final AnnotatedElement annotatedElement,
271+
final Class<T> annotationClass) {
272+
if (annotationClass.isAnnotationPresent(Repeatable.class)) {
273+
final Repeatable repeatable = annotationClass.getAnnotation(Repeatable.class);
274+
final Class<? extends Annotation> wrapper = repeatable.value();
275+
final Annotation annotation = annotatedElement.getAnnotation(wrapper);
276+
if (Objects.nonNull(annotation)) {
277+
try {
278+
final Method value = annotation.getClass().getMethod("value");
279+
final Object annotations = value.invoke(annotation);
280+
return Arrays.asList((T[]) annotations);
281+
} catch (Exception e) {
282+
throw new IllegalStateException(e);
283+
}
284+
}
285+
}
286+
return Collections.emptyList();
287+
}
288+
}

0 commit comments

Comments
 (0)