All Downloads are FREE. Search and download functionalities are using the official Maven repository.

io.qameta.allure.testng.AllureTestNg Maven / Gradle / Ivy

There is a newer version: 2.29.0
Show newest version
/*
 *  Copyright 2016-2024 Qameta Software Inc
 *
 *  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.testng;

import io.qameta.allure.Allure;
import io.qameta.allure.AllureLifecycle;
import io.qameta.allure.Flaky;
import io.qameta.allure.Muted;
import io.qameta.allure.Severity;
import io.qameta.allure.SeverityLevel;
import io.qameta.allure.model.FixtureResult;
import io.qameta.allure.model.Label;
import io.qameta.allure.model.Link;
import io.qameta.allure.model.Parameter;
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.model.TestResultContainer;
import io.qameta.allure.testng.config.AllureTestNgConfig;
import io.qameta.allure.util.AnnotationUtils;
import io.qameta.allure.util.ObjectUtils;
import io.qameta.allure.util.ResultsUtils;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import org.testng.IAttributes;
import org.testng.IClass;
import org.testng.IConfigurationListener;
import org.testng.IInvokedMethod;
import org.testng.IInvokedMethodListener;
import org.testng.IMethodInstance;
import org.testng.IMethodInterceptor;
import org.testng.ISuite;
import org.testng.ISuiteListener;
import org.testng.ITestClass;
import org.testng.ITestContext;
import org.testng.ITestListener;
import org.testng.ITestNGMethod;
import org.testng.ITestResult;
import org.testng.annotations.Parameters;
import org.testng.internal.ConstructorOrMethod;
import org.testng.xml.XmlSuite;
import org.testng.xml.XmlTest;

import java.lang.reflect.AnnotatedElement;
import java.lang.reflect.Method;
import java.security.MessageDigest;
import java.util.ArrayList;
import java.util.Arrays;
import java.util.Collections;
import java.util.HashMap;
import java.util.HashSet;
import java.util.List;
import java.util.Map;
import java.util.Objects;
import java.util.Optional;
import java.util.Set;
import java.util.UUID;
import java.util.concurrent.ConcurrentHashMap;
import java.util.concurrent.locks.ReadWriteLock;
import java.util.concurrent.locks.ReentrantReadWriteLock;
import java.util.function.Consumer;
import java.util.stream.Collectors;
import java.util.stream.Stream;

import static io.qameta.allure.util.ResultsUtils.ALLURE_ID_LABEL_NAME;
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.getStatusDetails;
import static io.qameta.allure.util.ResultsUtils.processDescription;
import static java.nio.charset.StandardCharsets.UTF_8;
import static java.util.Comparator.comparing;
import static java.util.Objects.nonNull;

/**
 * Allure TestNG listener.
 */
@SuppressWarnings({
        "ClassDataAbstractionCoupling",
        "ClassFanOutComplexity",
        "PMD.CyclomaticComplexity",
        "PMD.ExcessiveClassLength",
        "PMD.ExcessiveImports",
        "PMD.NcssCount",
        "PMD.TooManyMethods",
})
public class AllureTestNg implements
        ISuiteListener,
        ITestListener,
        IInvokedMethodListener,
        IConfigurationListener,
        IMethodInterceptor {

    private static final Logger LOGGER = LoggerFactory.getLogger(AllureTestNg.class);

    private static final String ALLURE_UUID = "ALLURE_UUID";
    private static final List> INJECTED_TYPES = Arrays.asList(
            ITestContext.class, ITestResult.class, XmlTest.class, Method.class, Object[].class
    );

    private static final boolean HAS_CUCUMBERJVM7_IN_CLASSPATH
            = isClassAvailableOnClasspath("io.qameta.allure.cucumber7jvm.AllureCucumber7Jvm");

    private static final boolean HAS_CUCUMBERJVM6_IN_CLASSPATH
            = isClassAvailableOnClasspath("io.qameta.allure.cucumber6jvm.AllureCucumber6Jvm");

    private static final boolean HAS_CUCUMBERJVM5_IN_CLASSPATH
            = isClassAvailableOnClasspath("io.qameta.allure.cucumber5jvm.AllureCucumber5Jvm");

    private static final boolean HAS_CUCUMBERJVM4_IN_CLASSPATH
            = isClassAvailableOnClasspath("io.qameta.allure.cucumber4jvm.AllureCucumber4Jvm");

    /**
     * Store current testng result uuid to attach before/after methods into.
     */
    private final ThreadLocal currentTestResult = ThreadLocal
            .withInitial(Current::new);
    /**
     * Store current container uuid for fake containers around before/after methods.
     */
    private final ThreadLocal currentTestContainer = ThreadLocal
            .withInitial(() -> UUID.randomUUID().toString());
    /**
     * Store uuid for current executable item to catch steps and attachments.
     */
    private final ThreadLocal currentExecutable = ThreadLocal
            .withInitial(() -> UUID.randomUUID().toString());
    /**
     * Store uuid for class test containers.
     */
    private final Map classContainerUuidStorage = new ConcurrentHashMap<>();
    private final ReadWriteLock lock = new ReentrantReadWriteLock();
    private final AllureLifecycle lifecycle;
    private final AllureTestNgTestFilter testFilter;

    private final AllureTestNgConfig config;

    /**
     * Package private constructor to allow custom configurations for unit tests.
     */
    AllureTestNg(final AllureLifecycle lifecycle,
                 final AllureTestNgTestFilter testFilter,
                 final AllureTestNgConfig config) {
        this.lifecycle = lifecycle;
        this.testFilter = testFilter;
        this.config = config;
    }

    public AllureTestNg(final AllureLifecycle lifecycle,
                        final AllureTestNgTestFilter testFilter) {
        this.lifecycle = lifecycle;
        this.testFilter = testFilter;
        this.config = AllureTestNgConfig.loadConfigProperties();
    }

    public AllureTestNg(final AllureLifecycle lifecycle) {
        this(lifecycle, new AllureTestNgTestFilter());
    }

    public AllureTestNg() {
        this(Allure.getLifecycle());
    }

    private static String safeExtractSuiteName(final ITestClass testClass) {
        final Optional xmlTest = Optional.ofNullable(testClass.getXmlTest());
        return xmlTest.map(XmlTest::getSuite).map(XmlSuite::getName).orElse("Undefined suite");
    }

    private static String safeExtractTestTag(final ITestClass testClass) {
        final Optional xmlTest = Optional.ofNullable(testClass.getXmlTest());
        return xmlTest.map(XmlTest::getName).orElse("Undefined testng tag");
    }

    private static String safeExtractTestClassName(final ITestClass testClass) {
        return firstNonEmpty(testClass.getTestName(), testClass.getName()).orElse("Undefined class name");
    }

    public AllureLifecycle getLifecycle() {
        return lifecycle;
    }

    @Override
    public List intercept(final List methods,
                                           final ITestContext context) {
        return testFilter.intercept(methods, context);
    }

    @Override
    public void onStart(final ISuite suite) {
        final TestResultContainer result = new TestResultContainer()
                .setUuid(getUniqueUuid(suite))
                .setName(suite.getName())
                .setStart(System.currentTimeMillis());
        getLifecycle().startTestContainer(result);
    }

    @Override
    public void onStart(final ITestContext context) {
        final String parentUuid = getUniqueUuid(context.getSuite());
        final String uuid = getUniqueUuid(context);
        final TestResultContainer container = new TestResultContainer()
                .setUuid(uuid)
                .setName(context.getName())
                .setStart(System.currentTimeMillis());
        getLifecycle().startTestContainer(parentUuid, container);

        Stream.of(context.getAllTestMethods())
                .map(ITestNGMethod::getTestClass)
                .distinct()
                .forEach(this::onBeforeClass);

        if (!config.isHideDisabledTests()) {
            context.getExcludedMethods().stream()
                    .filter(ITestNGMethod::isTest)
                    .filter(method -> !method.getEnabled())
                    .filter(testFilter::isSelected)
                    .forEach(method -> createFakeResult(context, method));
        }
    }

    protected void createFakeResult(final ITestContext context, final ITestNGMethod method) {
        final String uuid = UUID.randomUUID().toString();
        final String parentUuid = UUID.randomUUID().toString();
        startTestCase(context, method, method.getTestClass(), new Object[]{}, parentUuid, uuid);
        stopTestCase(uuid, null, null);
    }

    @Override
    public void onFinish(final ISuite suite) {
        final String uuid = getUniqueUuid(suite);
        getLifecycle().stopTestContainer(uuid);
        getLifecycle().writeTestContainer(uuid);

    }

    @Override
    public void onFinish(final ITestContext context) {
        final String uuid = getUniqueUuid(context);
        getLifecycle().stopTestContainer(uuid);
        getLifecycle().writeTestContainer(uuid);

        Stream.of(context.getAllTestMethods())
                .map(ITestNGMethod::getTestClass)
                .distinct()
                .forEach(this::onAfterClass);
    }

    public void onBeforeClass(final ITestClass testClass) {
        final String uuid = UUID.randomUUID().toString();
        final TestResultContainer container = new TestResultContainer()
                .setUuid(uuid)
                .setName(testClass.getName());
        getLifecycle().startTestContainer(container);
        setClassContainer(testClass, uuid);
    }

    public void onAfterClass(final ITestClass testClass) {
        getClassContainer(testClass).ifPresent(uuid -> {
            getLifecycle().stopTestContainer(uuid);
            getLifecycle().writeTestContainer(uuid);
        });
    }

    @Override
    public void onTestStart(final ITestResult testResult) {
        if (shouldSkipReportingFor(testResult)) {
            return;
        }

        Current current = currentTestResult.get();
        if (current.isStarted()) {
            current = refreshContext();
        }
        current.test();
        final String uuid = current.getUuid();
        final String parentUuid = getUniqueUuid(testResult.getTestContext());

        startTestCase(testResult, parentUuid, uuid);

        Optional.of(testResult)
                .map(ITestResult::getMethod)
                .map(ITestNGMethod::getTestClass)
                .ifPresent(clazz -> addClassContainerChild(clazz, uuid));
    }

    @SuppressWarnings("BooleanExpressionComplexity")
    protected boolean shouldSkipReportingFor(final ITestResult testResult) {
        final String[] groups = Optional.of(testResult)
                .map(ITestResult::getMethod)
                .map(ITestNGMethod::getGroups)
                .orElseGet(() -> new String[]{});

        if (groups.length == 0) {
            return false;
        }

        final Set groupsSet = new HashSet<>(Arrays.asList(groups));
        return (HAS_CUCUMBERJVM7_IN_CLASSPATH
                || HAS_CUCUMBERJVM6_IN_CLASSPATH
                || HAS_CUCUMBERJVM5_IN_CLASSPATH
                || HAS_CUCUMBERJVM4_IN_CLASSPATH
               ) && groupsSet.contains("cucumber");
    }

    protected void startTestCase(final ITestResult testResult,
                                 final String parentUuid,
                                 final String uuid) {
        startTestCase(
                testResult.getTestContext(),
                testResult.getMethod(),
                testResult.getTestClass(),
                testResult.getParameters(),
                parentUuid,
                uuid
        );
    }

    @SuppressWarnings({"Indentation", "PMD.ExcessiveMethodLength"})
    protected void startTestCase(final ITestContext context,
                                 final ITestNGMethod method,
                                 final IClass iClass,
                                 final Object[] params,
                                 final String parentUuid,
                                 final String uuid) {
        final ITestClass testClass = method.getTestClass();
        final List




© 2015 - 2024 Weber Informatics LLC | Privacy Policy