From 1cb5c438d97ed9486313ce0d7511d1e7f062ad44 Mon Sep 17 00:00:00 2001 From: Dmitry Baev Date: Thu, 1 Nov 2018 17:46:35 +0300 Subject: [PATCH 1/4] improve junit platform integration --- allure-junit-platform/build.gradle.kts | 5 +- .../junitplatform/AllureJunitPlatform.java | 311 +++++++++++------- .../AllureJunitPlatformTest.java | 198 ++++++++--- .../features/DisplayNameOnClassTests.java | 16 + .../junitplatform/features/DynamicTests.java | 20 ++ ...> TestClassWithDisplayNameAnnotation.java} | 2 +- ...estClassWithoutDisplayNameAnnotation.java} | 2 +- .../features}/TestWithClassLabels.java | 4 +- .../features}/TestWithClassLinks.java | 8 +- ...riptions.java => TestWithDescription.java} | 2 +- ...playName.java => TestWithDisplayName.java} | 2 +- .../features}/TestWithMethodLabels.java | 4 +- .../features}/TestWithMethodLinks.java | 8 +- ...TestsWithSteps.java => TestWithSteps.java} | 2 +- allure-junit5/build.gradle.kts | 8 - .../AllureJunit5AnnotationProcessor.java | 107 +----- .../qameta/allure/junit5/AnnotationsTest.java | 138 -------- .../allure/junit5/samples/DynamicTests.java | 40 --- 18 files changed, 421 insertions(+), 456 deletions(-) create mode 100644 allure-junit-platform/src/test/java/io/qameta/allure/junitplatform/features/DisplayNameOnClassTests.java rename allure-junit-platform/src/test/java/io/qameta/allure/junitplatform/features/{TestsClassWithDisplayNameAnnotation.java => TestClassWithDisplayNameAnnotation.java} (83%) rename allure-junit-platform/src/test/java/io/qameta/allure/junitplatform/features/{TestsClassWithoutDisplayNameAnnotation.java => TestClassWithoutDisplayNameAnnotation.java} (76%) rename {allure-junit5/src/test/java/io/qameta/allure/junit5/samples => allure-junit-platform/src/test/java/io/qameta/allure/junitplatform/features}/TestWithClassLabels.java (85%) rename {allure-junit5/src/test/java/io/qameta/allure/junit5/samples => allure-junit-platform/src/test/java/io/qameta/allure/junitplatform/features}/TestWithClassLinks.java (70%) rename allure-junit-platform/src/test/java/io/qameta/allure/junitplatform/features/{TestsWithDescriptions.java => TestWithDescription.java} (87%) rename allure-junit-platform/src/test/java/io/qameta/allure/junitplatform/features/{TestsWithDisplayName.java => TestWithDisplayName.java} (88%) rename {allure-junit5/src/test/java/io/qameta/allure/junit5/samples => allure-junit-platform/src/test/java/io/qameta/allure/junitplatform/features}/TestWithMethodLabels.java (86%) rename {allure-junit5/src/test/java/io/qameta/allure/junit5/samples => allure-junit-platform/src/test/java/io/qameta/allure/junitplatform/features}/TestWithMethodLinks.java (72%) rename allure-junit-platform/src/test/java/io/qameta/allure/junitplatform/features/{TestsWithSteps.java => TestWithSteps.java} (96%) delete mode 100644 allure-junit5/src/test/java/io/qameta/allure/junit5/AnnotationsTest.java delete mode 100644 allure-junit5/src/test/java/io/qameta/allure/junit5/samples/DynamicTests.java diff --git a/allure-junit-platform/build.gradle.kts b/allure-junit-platform/build.gradle.kts index f18918b11..65a5e5706 100644 --- a/allure-junit-platform/build.gradle.kts +++ b/allure-junit-platform/build.gradle.kts @@ -4,14 +4,15 @@ val agent by configurations.creating dependencies { agent("org.aspectj:aspectjweaver") - compile(project(":allure-java-commons")) compile("org.junit.jupiter:junit-jupiter-api") compile("org.junit.platform:junit-platform-launcher") - testCompile(project(":allure-java-commons-test")) + compile(project(":allure-java-commons")) + testCompile("io.github.glytching:junit-extensions") testCompile("org.assertj:assertj-core") testCompile("org.junit.jupiter:junit-jupiter-api") testCompile("org.junit.jupiter:junit-jupiter-params") testCompile("org.slf4j:slf4j-simple") + testCompile(project(":allure-java-commons-test")) testRuntime("org.junit.jupiter:junit-jupiter-engine") } diff --git a/allure-junit-platform/src/main/java/io/qameta/allure/junitplatform/AllureJunitPlatform.java b/allure-junit-platform/src/main/java/io/qameta/allure/junitplatform/AllureJunitPlatform.java index ace4fe289..b07bf3d9c 100644 --- a/allure-junit-platform/src/main/java/io/qameta/allure/junitplatform/AllureJunitPlatform.java +++ b/allure-junit-platform/src/main/java/io/qameta/allure/junitplatform/AllureJunitPlatform.java @@ -3,9 +3,16 @@ import io.qameta.allure.Allure; import io.qameta.allure.AllureLifecycle; import io.qameta.allure.Description; +import io.qameta.allure.Epic; +import io.qameta.allure.Feature; +import io.qameta.allure.Owner; +import io.qameta.allure.Severity; +import io.qameta.allure.Story; import io.qameta.allure.model.Label; +import io.qameta.allure.model.Link; import io.qameta.allure.model.Stage; import io.qameta.allure.model.Status; +import io.qameta.allure.model.StatusDetails; import io.qameta.allure.model.TestResult; import io.qameta.allure.util.ResultsUtils; import org.junit.jupiter.api.DisplayName; @@ -16,18 +23,29 @@ import org.slf4j.Logger; import org.slf4j.LoggerFactory; +import java.lang.annotation.Annotation; +import java.lang.annotation.Repeatable; +import java.lang.reflect.AnnotatedElement; +import java.lang.reflect.Method; import java.math.BigInteger; -import java.security.MessageDigest; -import java.security.NoSuchAlgorithmException; +import java.util.Arrays; +import java.util.Collections; import java.util.List; +import java.util.Map; +import java.util.Objects; import java.util.Optional; import java.util.UUID; +import java.util.concurrent.ConcurrentHashMap; +import java.util.concurrent.locks.ReadWriteLock; +import java.util.concurrent.locks.ReentrantReadWriteLock; import java.util.stream.Collectors; +import java.util.stream.Stream; import static io.qameta.allure.model.Status.FAILED; import static io.qameta.allure.model.Status.PASSED; import static io.qameta.allure.model.Status.SKIPPED; import static io.qameta.allure.util.ResultsUtils.getHostName; +import static io.qameta.allure.util.ResultsUtils.getMd5Digest; import static io.qameta.allure.util.ResultsUtils.getThreadName; import static java.nio.charset.StandardCharsets.UTF_8; @@ -43,11 +61,12 @@ public class AllureJunitPlatform implements TestExecutionListener { private static final String PACKAGE = "package"; private static final String THREAD = "thread"; private static final String HOST = "host"; - private static final String CLASS_NAME = "className"; - private static final String METHOD_NAME = "methodName"; + private static final String TEST_CLASS = "testClass"; + private static final String TEST_METHOD = "testMethod"; - private final ThreadLocal tests - = InheritableThreadLocal.withInitial(() -> UUID.randomUUID().toString()); + private final Map testUuids = new ConcurrentHashMap<>(); + + private final ReadWriteLock lock = new ReentrantReadWriteLock(); private final AllureLifecycle lifecycle; @@ -66,87 +85,26 @@ public AllureLifecycle getLifecycle() { @Override public void executionStarted(final TestIdentifier testIdentifier) { if (testIdentifier.isTest()) { - final Optional methodSource = testIdentifier.getSource() - .filter(MethodSource.class::isInstance) - .map(MethodSource.class::cast); - final String uuid = tests.get(); - final TestResult result = new TestResult() - .setUuid(uuid) - .setName(testIdentifier.getDisplayName()) - .setLabels(getTags(testIdentifier)) - .setHistoryId(getHistoryId(testIdentifier)) - .setStage(Stage.RUNNING); - - result.getLabels().add(new Label().setName(THREAD).setValue(getThreadName())); - result.getLabels().add(new Label().setName(HOST).setValue(getHostName())); - - methodSource.ifPresent(source -> updateResultFromSource(result, source)); - - getLifecycle().scheduleTestCase(result); - getLifecycle().startTestCase(uuid); + startTestCase(testIdentifier); } } @Override public void executionSkipped(final TestIdentifier testIdentifier, final String reason) { if (testIdentifier.isTest()) { - final String uuid = tests.get(); - final TestResult result = new TestResult() - .setUuid(uuid) - .setName(testIdentifier.getDisplayName()) - .setLabels(getTags(testIdentifier)) - .setHistoryId(getHistoryId(testIdentifier)) - .setStage(Stage.RUNNING); - - result.getLabels().add(new Label().setName(THREAD).setValue(getThreadName())); - result.getLabels().add(new Label().setName(HOST).setValue(getHostName())); - - testIdentifier - .getSource() - .filter(MethodSource.class::isInstance) - .map(MethodSource.class::cast) - .ifPresent(source -> updateResultFromSource(result, source)); - - getLifecycle().scheduleTestCase(result); - getLifecycle().startTestCase(uuid); - - tests.remove(); - getLifecycle().updateTestCase(uuid, testResult -> { - testResult.setStage(Stage.FINISHED); - testResult.setStatus(SKIPPED); - }); - getLifecycle().stopTestCase(uuid); - getLifecycle().writeTestCase(uuid); + startTestCase(testIdentifier); + stopTestCase(testIdentifier, SKIPPED, new StatusDetails().setMessage(reason)); } } @Override public void executionFinished(final TestIdentifier testIdentifier, final TestExecutionResult testExecutionResult) { if (testIdentifier.isTest()) { - final String uuid = tests.get(); - tests.remove(); - getLifecycle().updateTestCase(uuid, result -> { - result.setStage(Stage.FINISHED); - switch (testExecutionResult.getStatus()) { - case FAILED: - testExecutionResult.getThrowable().ifPresent(throwable -> { - result.setStatus(getStatus(throwable)); - result.setStatusDetails(ResultsUtils.getStatusDetails(throwable).orElse(null)); - }); - break; - case SUCCESSFUL: - result.setStatus(PASSED); - break; - default: - result.setStatus(SKIPPED); - testExecutionResult.getThrowable().ifPresent(throwable -> - result.setStatusDetails(ResultsUtils.getStatusDetails(throwable).orElse(null)) - ); - break; - } - }); - getLifecycle().stopTestCase(uuid); - getLifecycle().writeTestCase(uuid); + final Status status = extractStatus(testExecutionResult); + final StatusDetails statusDetails = testExecutionResult.getThrowable() + .flatMap(ResultsUtils::getStatusDetails) + .orElse(null); + stopTestCase(testIdentifier, status, statusDetails); } } @@ -154,17 +112,104 @@ protected Status getStatus(final Throwable throwable) { return ResultsUtils.getStatus(throwable).orElse(FAILED); } - protected void updateResultFromSource(final TestResult result, final MethodSource source) { - result.setFullName(String.format( - "%s.%s", - source.getClassName(), - source.getMethodName() - )); - result.setDescription(getDescription(source)); - result.getLabels().add(new Label().setName(CLASS_NAME).setValue(source.getClassName())); - result.getLabels().add(new Label().setName(METHOD_NAME).setValue(source.getMethodName())); - result.getLabels().add(new Label().setName(SUITE).setValue(getSuite(source))); - result.getLabels().add(new Label().setName(PACKAGE).setValue(source.getClassName())); + private void startTestCase(final TestIdentifier testIdentifier) { + final String uuid = createUuid(testIdentifier); + + final Optional methodSource = testIdentifier.getSource() + .filter(MethodSource.class::isInstance) + .map(MethodSource.class::cast); + + final Optional testMethod = methodSource.flatMap(this::getTestMethod); + final Optional> testClass = methodSource.flatMap(this::getTestClass); + + final TestResult result = new TestResult() + .setUuid(uuid) + .setName(testIdentifier.getDisplayName()) + .setLabels(getTags(testIdentifier)) + .setHistoryId(getHistoryId(testIdentifier)) + .setStage(Stage.RUNNING); + + testClass.map(this::getLabels).ifPresent(result.getLabels()::addAll); + testMethod.map(this::getLabels).ifPresent(result.getLabels()::addAll); + + testClass.map(this::getLinks).ifPresent(result.getLinks()::addAll); + testMethod.map(this::getLinks).ifPresent(result.getLinks()::addAll); + + result.getLabels().add(new Label().setName(THREAD).setValue(getThreadName())); + result.getLabels().add(new Label().setName(HOST).setValue(getHostName())); + + methodSource.ifPresent(source -> { + result.setFullName(String.format("%s.%s", source.getClassName(), source.getMethodName())); + result.getLabels().add(new Label().setName(PACKAGE).setValue(source.getClassName())); + result.getLabels().add(new Label().setName(TEST_CLASS).setValue(source.getClassName())); + result.getLabels().add(new Label().setName(TEST_METHOD).setValue(source.getMethodName())); + }); + + testClass.ifPresent(aClass -> { + final String suiteName = getDisplayName(aClass).orElse(aClass.getCanonicalName()); + result.getLabels().add(new Label().setName(SUITE).setValue(suiteName)); + }); + + final Optional classDescription = testClass.flatMap(this::getDescription); + final Optional methodDescription = testMethod.flatMap(this::getDescription); + + final String description = Stream.of(classDescription, methodDescription) + .filter(Optional::isPresent) + .map(Optional::get) + .collect(Collectors.joining("\n\n")); + + result.setDescription(description); + + getLifecycle().scheduleTestCase(result); + getLifecycle().startTestCase(uuid); + } + + private void stopTestCase(final TestIdentifier testIdentifier, + final Status status, + final StatusDetails statusDetails) { + + final String uuid = removeUuid(testIdentifier); + getLifecycle().updateTestCase(uuid, result -> { + result.setStage(Stage.FINISHED); + result.setStatus(status); + result.setStatusDetails(statusDetails); + }); + getLifecycle().stopTestCase(uuid); + getLifecycle().writeTestCase(uuid); + } + + private String createUuid(final TestIdentifier testIdentifier) { + final String uuid = UUID.randomUUID().toString(); + try { + lock.writeLock().lock(); + testUuids.put(testIdentifier, uuid); + } finally { + lock.writeLock().unlock(); + } + return uuid; + } + + private String removeUuid(final TestIdentifier testIdentifier) { + try { + lock.writeLock().lock(); + return testUuids.remove(testIdentifier); + } finally { + lock.writeLock().unlock(); + } + } + + @SuppressWarnings("ReturnCount") + private Status extractStatus(final TestExecutionResult testExecutionResult) { + switch (testExecutionResult.getStatus()) { + case FAILED: + return testExecutionResult.getThrowable().isPresent() + ? getStatus(testExecutionResult.getThrowable().get()) + : FAILED; + case SUCCESSFUL: + return PASSED; + default: + return SKIPPED; + } } private List