Skip to content

Commit

Permalink
spock 2
Browse files Browse the repository at this point in the history
  • Loading branch information
baev committed Oct 26, 2022
1 parent fe900f8 commit 356ff21
Show file tree
Hide file tree
Showing 24 changed files with 1,361 additions and 5 deletions.
Original file line number Diff line number Diff line change
Expand Up @@ -15,8 +15,10 @@
*/
package io.qameta.allure.util;

import io.qameta.allure.Flaky;
import io.qameta.allure.LabelAnnotation;
import io.qameta.allure.LinkAnnotation;
import io.qameta.allure.Muted;
import io.qameta.allure.model.Label;
import io.qameta.allure.model.Link;
import org.slf4j.Logger;
Expand Down Expand Up @@ -55,6 +57,26 @@ private AnnotationUtils() {
throw new IllegalStateException("Do not instance");
}

/**
* Returns true if {@link io.qameta.allure.Flaky} annotation is present.
*
* @param annotatedElement the element to search annotations on.
* @return true if {@link io.qameta.allure.Flaky} annotation is present, false otherwise.
*/
public static boolean isFlaky(final AnnotatedElement annotatedElement) {
return annotatedElement.isAnnotationPresent(Flaky.class);
}

/**
* Returns true if {@link io.qameta.allure.Muted} annotation is present.
*
* @param annotatedElement the element to search annotations on.
* @return true if {@link io.qameta.allure.Muted} annotation is present, false otherwise.
*/
public static boolean isMuted(final AnnotatedElement annotatedElement) {
return annotatedElement.isAnnotationPresent(Muted.class);
}

/**
* Returns links created from Allure meta annotations specified on annotated element.
*
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -286,8 +286,12 @@ public static Optional<Status> getStatus(final Throwable throwable) {
public static Optional<StatusDetails> getStatusDetails(final Throwable e) {
return Optional.ofNullable(e)
.map(throwable -> new StatusDetails()
.setMessage(Optional.ofNullable(throwable.getMessage()).orElse(throwable.getClass().getName()))
.setTrace(getStackTraceAsString(throwable)));
.setMessage(Optional
.ofNullable(throwable.getMessage())
.orElse(throwable.getClass().getName())
)
.setTrace(getStackTraceAsString(throwable))
);
}

public static Optional<String> getJavadocDescription(final ClassLoader classLoader,
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -242,8 +242,8 @@ private Map<String, String> unwrap(final Map<String, String> data) {
final Map<String, String> res = new HashMap<>();
data.forEach((key, value) -> {
if (Objects.nonNull(value)
&& value.trim().isEmpty()
&& value.startsWith(ALLURE_REPORT_ENTRY_BLANK_PREFIX)) {
&& value.trim().isEmpty()
&& value.startsWith(ALLURE_REPORT_ENTRY_BLANK_PREFIX)) {
res.put(key, value.substring(ALLURE_REPORT_ENTRY_BLANK_PREFIX.length()));
} else {
res.put(key, value);
Expand Down Expand Up @@ -544,7 +544,23 @@ private void stopTestCase(final TestIdentifier testIdentifier,
}
result.setStage(Stage.FINISHED);
result.setStatus(status);
result.setStatusDetails(statusDetails);

final StatusDetails currentSd = result.getStatusDetails();
if (Objects.isNull(currentSd)) {
result.setStatusDetails(statusDetails);
} else if (Objects.nonNull(statusDetails)) {
Optional.of(statusDetails)
.map(StatusDetails::getMessage)
.ifPresent(currentSd::setMessage);

Optional.of(statusDetails)
.map(StatusDetails::getTrace)
.ifPresent(currentSd::setTrace);

currentSd.setMuted(currentSd.isMuted() || statusDetails.isMuted());
currentSd.setFlaky(currentSd.isFlaky() || statusDetails.isFlaky());
currentSd.setKnown(currentSd.isKnown() || statusDetails.isKnown());
}
});
getLifecycle().stopTestCase(uuid);
getLifecycle().writeTestCase(uuid);
Expand Down
60 changes: 60 additions & 0 deletions allure-spock2/build.gradle.kts
Original file line number Diff line number Diff line change
@@ -0,0 +1,60 @@
description = "Allure Spock 2 Framework Integration"

plugins {
groovy
}

val spockFrameworkVersion = "2.3-groovy-3.0"
val groovyVersion = "3.0.13"

dependencies {
api(project(":allure-java-commons"))
implementation("org.spockframework:spock-core:$spockFrameworkVersion")
compileOnly("org.aspectj:aspectjrt")
implementation(project(":allure-test-filter"))
testAnnotationProcessor("org.slf4j:slf4j-simple")
testAnnotationProcessor(project(":allure-descriptions-javadoc"))
testImplementation("io.github.glytching:junit-extensions")
testImplementation("org.assertj:assertj-core")
testImplementation("org.codehaus.groovy:groovy-all:${groovyVersion}")
testImplementation("org.junit.jupiter:junit-jupiter-api")
testImplementation("org.junit.jupiter:junit-jupiter-params")
testImplementation("org.junit.platform:junit-platform-launcher")
testImplementation("org.mockito:mockito-core")
testImplementation("org.slf4j:slf4j-simple")
testImplementation(project(":allure-assertj"))
testImplementation(project(":allure-java-commons-test"))
testImplementation(project(":allure-junit-platform"))
testRuntimeOnly("org.junit.jupiter:junit-jupiter-engine")
}

tasks.jar {
manifest {
attributes(mapOf(
"Automatic-Module-Name" to "io.qameta.allure.spock2"
))
}

from("src/main/services") {
into("META-INF/services")
}
}

tasks.test {
useJUnitPlatform()
exclude("**/samples/*")
}

val spiOffJar: Jar by tasks.creating(Jar::class) {
from(sourceSets.getByName("main").output)
archiveClassifier.set("spi-off")
}

publishing {
publications {
named<MavenPublication>("maven") {
artifact(spiOffJar)
}
}
}

238 changes: 238 additions & 0 deletions allure-spock2/src/main/java/io/qameta/allure/spock2/AllureSpock2.java
Original file line number Diff line number Diff line change
@@ -0,0 +1,238 @@
/*
* Copyright 2019 Qameta Software OÜ
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
package io.qameta.allure.spock2;

import io.qameta.allure.Allure;
import io.qameta.allure.AllureLifecycle;
import io.qameta.allure.model.Label;
import io.qameta.allure.model.Link;
import io.qameta.allure.model.Parameter;
import io.qameta.allure.model.Status;
import io.qameta.allure.model.StatusDetails;
import io.qameta.allure.model.TestResult;
import io.qameta.allure.util.AnnotationUtils;
import io.qameta.allure.util.ResultsUtils;
import org.spockframework.runtime.AbstractRunListener;
import org.spockframework.runtime.extension.IGlobalExtension;
import org.spockframework.runtime.model.ErrorInfo;
import org.spockframework.runtime.model.FeatureInfo;
import org.spockframework.runtime.model.IterationInfo;
import org.spockframework.runtime.model.MethodInfo;
import org.spockframework.runtime.model.SpecInfo;

import java.lang.reflect.Method;
import java.security.MessageDigest;
import java.util.ArrayList;
import java.util.Arrays;
import java.util.List;
import java.util.Objects;
import java.util.Set;
import java.util.UUID;
import java.util.stream.Collectors;
import java.util.stream.IntStream;

import static io.qameta.allure.util.ResultsUtils.bytesToHex;
import static io.qameta.allure.util.ResultsUtils.createFrameworkLabel;
import static io.qameta.allure.util.ResultsUtils.createHostLabel;
import static io.qameta.allure.util.ResultsUtils.createLanguageLabel;
import static io.qameta.allure.util.ResultsUtils.createPackageLabel;
import static io.qameta.allure.util.ResultsUtils.createParameter;
import static io.qameta.allure.util.ResultsUtils.createParentSuiteLabel;
import static io.qameta.allure.util.ResultsUtils.createSubSuiteLabel;
import static io.qameta.allure.util.ResultsUtils.createSuiteLabel;
import static io.qameta.allure.util.ResultsUtils.createTestClassLabel;
import static io.qameta.allure.util.ResultsUtils.createTestMethodLabel;
import static io.qameta.allure.util.ResultsUtils.createThreadLabel;
import static io.qameta.allure.util.ResultsUtils.firstNonEmpty;
import static io.qameta.allure.util.ResultsUtils.getMd5Digest;
import static io.qameta.allure.util.ResultsUtils.getProvidedLabels;
import static io.qameta.allure.util.ResultsUtils.getStatus;
import static io.qameta.allure.util.ResultsUtils.getStatusDetails;
import static io.qameta.allure.util.ResultsUtils.md5;
import static java.nio.charset.StandardCharsets.UTF_8;
import static java.util.Comparator.comparing;

/**
* @author charlie (Dmitry Baev).
*/
@SuppressWarnings({
"PMD.NcssCount"
})
public class AllureSpock2 extends AbstractRunListener implements IGlobalExtension {

private final ThreadLocal<String> testResults = new InheritableThreadLocal<String>() {
@Override
protected String initialValue() {
return UUID.randomUUID().toString();
}
};

private final AllureLifecycle lifecycle;

@SuppressWarnings("unused")
public AllureSpock2() {
this(Allure.getLifecycle());
}

public AllureSpock2(final AllureLifecycle lifecycle) {
this.lifecycle = lifecycle;
}

@Override
public void start() {
//do nothing at this point
}

@Override
public void visitSpec(final SpecInfo spec) {
spec.addListener(this);
}

@Override
public void stop() {
//do nothing at this point
}

@Override
public void beforeIteration(final IterationInfo iteration) {
final String uuid = testResults.get();

final FeatureInfo feature = iteration.getFeature();
final MethodInfo methodInfo = feature.getFeatureMethod();
final Method method = methodInfo.getReflection();
final Set<Label> featureLabels = AnnotationUtils.getLabels(method);
final Set<Link> featureLinks = AnnotationUtils.getLinks(method);
final SpecInfo specInfo = feature.getSpec();
final Class<?> clazz = specInfo.getReflection();
final Set<Label> specLabels = AnnotationUtils.getLabels(clazz);
final Set<Link> specLinks = AnnotationUtils.getLinks(clazz);
final boolean flaky = AnnotationUtils.isFlaky(method) || AnnotationUtils.isFlaky(clazz);
final boolean muted = AnnotationUtils.isMuted(method) || AnnotationUtils.isMuted(clazz);

final List<Parameter> parameters = getParameters(feature.getDataVariables(), iteration.getDataValues());
final SpecInfo subSpec = specInfo.getSubSpec();
final SpecInfo superSpec = specInfo.getSuperSpec();
final String packageName = specInfo.getPackage();
final String specName = specInfo.getName();
final String testClassName = feature.getSpec().getReflection().getName();
final String testMethodName = iteration.getDisplayName();

final List<Label> labels = new ArrayList<>(Arrays.asList(
createPackageLabel(packageName),
createTestClassLabel(testClassName),
createTestMethodLabel(testMethodName),
createSuiteLabel(specName),
createHostLabel(),
createThreadLabel(),
createFrameworkLabel("spock"),
createLanguageLabel("java")
));

if (Objects.nonNull(subSpec)) {
labels.add(createSubSuiteLabel(subSpec.getName()));
}

if (Objects.nonNull(superSpec)) {
labels.add(createParentSuiteLabel(superSpec.getName()));
}

labels.addAll(featureLabels);
labels.addAll(specLabels);
labels.addAll(getProvidedLabels());

final List<Link> links = new ArrayList<>(featureLinks);
links.addAll(specLinks);

final String qualifiedName = getQualifiedName(iteration);
final TestResult result = new TestResult()
.setUuid(uuid)
.setHistoryId(getHistoryId(qualifiedName, parameters))
.setTestCaseName(iteration.getName())
.setTestCaseId(md5(qualifiedName))
.setFullName(qualifiedName)
.setName(
firstNonEmpty(testMethodName, iteration.getName(), qualifiedName)
.orElse("Unknown")
)
.setStatusDetails(new StatusDetails()
.setFlaky(flaky)
.setMuted(muted)
)
.setParameters(parameters)
.setLinks(links)
.setLabels(labels);

ResultsUtils.processDescription(
getClass().getClassLoader(),
method,
result::setDescription,
result::setDescriptionHtml
);

getLifecycle().scheduleTestCase(result);
getLifecycle().startTestCase(uuid);
}

private String getQualifiedName(final IterationInfo iteration) {
return iteration.getFeature().getSpec().getReflection().getName() + "." + iteration.getName();
}

private String getHistoryId(final String name, final List<Parameter> parameters) {
final MessageDigest digest = getMd5Digest();
digest.update(name.getBytes(UTF_8));
parameters.stream()
.sorted(comparing(Parameter::getName).thenComparing(Parameter::getValue))
.forEachOrdered(parameter -> {
digest.update(parameter.getName().getBytes(UTF_8));
digest.update(parameter.getValue().getBytes(UTF_8));
});
final byte[] bytes = digest.digest();
return bytesToHex(bytes);
}

@Override
public void error(final ErrorInfo error) {
final String uuid = testResults.get();
getLifecycle().updateTestCase(uuid, testResult -> testResult
.setStatus(getStatus(error.getException()).orElse(null))
.setStatusDetails(getStatusDetails(error.getException()).orElse(null))
);
}

@Override
public void afterIteration(final IterationInfo iteration) {
final String uuid = testResults.get();
testResults.remove();

getLifecycle().updateTestCase(uuid, testResult -> {
if (Objects.isNull(testResult.getStatus())) {
testResult.setStatus(Status.PASSED);
}
});
getLifecycle().stopTestCase(uuid);
getLifecycle().writeTestCase(uuid);
}

private List<Parameter> getParameters(final List<String> names, final Object... values) {
return IntStream.range(0, Math.min(names.size(), values.length))
.mapToObj(index -> createParameter(names.get(index), values[index]))
.collect(Collectors.toList());
}

public AllureLifecycle getLifecycle() {
return lifecycle;
}
}
Loading

0 comments on commit 356ff21

Please sign in to comment.