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

com.zebrunner.agent.testng.adapter.TestNGAdapter Maven / Gradle / Ivy

package com.zebrunner.agent.testng.adapter;

import org.testng.ISuite;
import org.testng.ITestContext;
import org.testng.ITestNGMethod;
import org.testng.ITestResult;
import org.testng.annotations.Test;
import org.testng.xml.XmlSuite;

import lombok.extern.slf4j.Slf4j;

import java.lang.reflect.Method;
import java.time.Instant;
import java.time.OffsetDateTime;
import java.time.ZoneId;
import java.util.Arrays;
import java.util.List;
import java.util.stream.Collectors;

import com.zebrunner.agent.core.config.ConfigurationHolder;
import com.zebrunner.agent.core.config.provider.SystemPropertiesConfigurationProvider;
import com.zebrunner.agent.core.registrar.RunContextHolder;
import com.zebrunner.agent.core.registrar.TestRunRegistrar;
import com.zebrunner.agent.core.registrar.descriptor.Status;
import com.zebrunner.agent.core.registrar.descriptor.TestFinishDescriptor;
import com.zebrunner.agent.core.registrar.descriptor.TestRunFinishDescriptor;
import com.zebrunner.agent.core.registrar.descriptor.TestRunStartDescriptor;
import com.zebrunner.agent.core.registrar.descriptor.TestStartDescriptor;
import com.zebrunner.agent.core.registrar.maintainer.ChainedMaintainerResolver;
import com.zebrunner.agent.testng.core.ExceptionUtils;
import com.zebrunner.agent.testng.core.FactoryInstanceHolder;
import com.zebrunner.agent.testng.core.RootXmlSuiteLabelAssigner;
import com.zebrunner.agent.testng.core.TestInvocationContext;
import com.zebrunner.agent.testng.core.config.RootXmlSuiteConfigurationProvider;
import com.zebrunner.agent.testng.core.maintainer.RootXmlSuiteMaintainerResolver;
import com.zebrunner.agent.testng.core.testname.TestNameResolverRegistry;
import com.zebrunner.agent.testng.listener.RetryService;
import com.zebrunner.agent.testng.listener.RunContextService;
import java.util.concurrent.atomic.AtomicBoolean;

/**
 * Adapter used to convert TestNG test domain to Zebrunner Agent domain
 */
@Slf4j
public class TestNGAdapter {

    private final TestRunRegistrar registrar;

    private XmlSuite rootXmlSuite;

    private static final AtomicBoolean CUCUMBER = new AtomicBoolean(true);

    public TestNGAdapter() {
        this.registrar = TestRunRegistrar.getInstance();
    }

    public void registerRunStart(ISuite suite) {
        if (rootXmlSuite == null) {
            XmlSuite xmlSuite = suite.getXmlSuite();

            XmlSuite parentXmlSuite = xmlSuite.getParentSuite() != null ? xmlSuite.getParentSuite() : xmlSuite;
            rootXmlSuite = parentXmlSuite;
            while (parentXmlSuite != null) {
                parentXmlSuite = rootXmlSuite.getParentSuite();
                rootXmlSuite = parentXmlSuite != null ? parentXmlSuite : rootXmlSuite;
            }
            String name = rootXmlSuite.getName();

            ChainedMaintainerResolver.addLast(new RootXmlSuiteMaintainerResolver(rootXmlSuite));

            ConfigurationHolder.addConfigurationProviderAfter(
                    new RootXmlSuiteConfigurationProvider(rootXmlSuite),
                    SystemPropertiesConfigurationProvider.class
            );

            registrar.registerStart(new TestRunStartDescriptor(name, "testng", OffsetDateTime.now(), name));
            RootXmlSuiteLabelAssigner.getInstance().assignTestRunLabels(rootXmlSuite);
        }
    }

    public void registerRunFinish(ISuite suite) {
        if (suite.getXmlSuite().getParentSuite() == null) {
            registrar.registerFinish(new TestRunFinishDescriptor(OffsetDateTime.now()));
        }
    }

    public void registerTestStart(ITestResult testResult) {
        if (isRetryFinished(testResult.getMethod(), testResult.getTestContext())) {
            log.debug("TestNGAdapter -> registerTestStart: retry is finished");

            TestInvocationContext testContext = this.buildTestStartInvocationContext(testResult);
            String correlationData = testContext.asJsonString();
            TestStartDescriptor testStartDescriptor = buildTestStartDescriptor(correlationData, testResult);

            setZebrunnerTestIdOnRerun(testResult, testResult.getMethod(), testStartDescriptor);

            String id = generateTestId(testContext);
            registrar.registerTestStart(id, testStartDescriptor);
        } else {
            log.debug("TestNGAdapter -> registerTestStart: retry is NOT finished");
        }
    }

    public void registerHeadlessTestStart(ITestResult testResult, ITestNGMethod nextTestMethod) {
        if (!registrar.isTestStarted()) { // we should not register the same headless test twice
            if (isRetryFinished(nextTestMethod, testResult.getTestContext())) {
                log.debug("TestNGAdapter -> registerHeadlessTestStart: retry is finished");

                TestInvocationContext testContext = this.buildTestStartInvocationContext(testResult);
                TestStartDescriptor testStartDescriptor = buildTestStartDescriptor(null, testResult);

                setZebrunnerTestIdOnRerun(testResult, nextTestMethod, testStartDescriptor);

                String id = generateTestId(testContext);
                registrar.registerHeadlessTestStart(id, testStartDescriptor);
            } else {
                log.debug("TestNGAdapter -> registerHeadlessTestStart: retry is NOT finished");
            }
        }
    }

    private TestInvocationContext buildTestStartInvocationContext(ITestResult testResult) {
        ITestNGMethod testMethod = testResult.getMethod();
        ITestContext testContext = testResult.getTestContext();
        Object[] parameters = testResult.getParameters();

        int dataProviderIndex = RunContextService.getCurrentDataProviderIndex(testMethod, testContext, parameters);
        RunContextService.setCurrentDataProviderData(testMethod, testContext, parameters, dataProviderIndex);

        int invocationIndex = RunContextService.getMethodInvocationIndex(testMethod, testContext);

        return buildTestInvocationContext(testMethod, dataProviderIndex, parameters, invocationIndex);
    }

    private void setZebrunnerTestIdOnRerun(ITestResult testResult, ITestNGMethod testMethod, TestStartDescriptor testStartDescriptor) {
        if (RunContextHolder.isRerun()) {
            ITestContext context = testResult.getTestContext();
            Object[] parameters = testResult.getParameters();

            // when parametrized method has at least on @BeforeMethod,
            // then parameters array contains argument(s) of the @BeforeMethod(s).
            //
            // thus, if current run is a rerun, then we will not be able to figure out
            // what method should be restarted in Zebrunner,
            // because arguments index constitutes identity of a test in Zebrunner
            //
            // the workaround here is to add not used argument for the @BeforeMethod(s) with type Object[].
            // in this case, TestNG will propagate the test method's arguments in this variable
            // and the agent will be able to identify appropriate existing Zebrunner test
            int dataProviderIndex = RunContextService.getCurrentDataProviderIndex(testMethod, context, parameters);
            if (dataProviderIndex == -1 && testMethod.isDataDriven() && parameters != null) {
                for (Object parameter : parameters) {
                    if (parameter instanceof Object[]) {
                        dataProviderIndex = RunContextService.getCurrentDataProviderIndex(testMethod, context, ((Object[]) parameter));
                    }
                }
            }

            RunContextService.getZebrunnerTestIdOnRerun(testMethod, dataProviderIndex)
                             .ifPresent(testStartDescriptor::setZebrunnerId);
        }
    }

    private TestStartDescriptor buildTestStartDescriptor(String correlationData, ITestResult testResult) {
        ITestNGMethod testMethod = testResult.getMethod();
        ITestContext context = testResult.getTestContext();
        Object[] parameters = testResult.getParameters();

        String displayName = TestNameResolverRegistry.get().resolve(testResult);
        OffsetDateTime startedAt = ofMillis(testResult.getStartMillis());
        Class realClass = testResult.getTestClass().getRealClass();
        String realClassName = null;
        Method method = testMethod.getConstructorOrMethod().getMethod();
        String methodName = null;

        Integer dataProviderIndex = RunContextService.getCurrentDataProviderIndex(testMethod, context, parameters);
        if (dataProviderIndex == -1) {
            dataProviderIndex = null;
        }

        if (CUCUMBER.get()) {
            try {
                Class pickleWrapperClass = Class.forName("io.cucumber.testng.PickleWrapper");
                Class featureWrapperClass = Class.forName("io.cucumber.testng.FeatureWrapper");
                String featureName = Arrays.stream(parameters)
                        .filter(featureWrapperClass::isInstance)
                        .map(feature -> feature.toString()
                                .replaceAll("^[\"]|[\"]$", ""))
                        .findAny()
                        .orElseThrow(ClassNotFoundException::new);
                String pickleName = Arrays.stream(parameters)
                        .filter(pickleWrapperClass::isInstance)
                        .map(pickle -> pickle.toString()
                                .replaceAll("^[\"]|[\"]$", ""))
                        .findAny()
                        .orElseThrow(ClassNotFoundException::new);
                realClassName = featureName;
                methodName = pickleName;
            } catch (ClassNotFoundException e) {
                CUCUMBER.set(false);
            }
        }

        return TestStartDescriptor.builder()
                                  .correlationData(correlationData)
                                  .name(displayName)
                                  .startedAt(startedAt)
                                  .testClass(realClass)
                                  .testClassName(realClassName)
                                  .testMethod(method)
                                  .testMethodName(methodName)
                                  .argumentsIndex(dataProviderIndex)
                                  .testGroups(Arrays.asList(testMethod.getGroups()))
                                  .build();
    }

    public void registerTestFinish(ITestResult testResult) {
        long endedAtMillis = testResult.getEndMillis();
        OffsetDateTime endedAt = ofMillis(endedAtMillis);

        TestFinishDescriptor testFinishDescriptor = new TestFinishDescriptor(Status.PASSED, endedAt);

        TestInvocationContext testContext = buildTestFinishInvocationContext(testResult);
        String id = generateTestId(testContext);
        registrar.registerTestFinish(id, testFinishDescriptor);

        // forcibly disable retry otherwise passed can't be registered in reporting tool!
        RetryService.setRetryFinished(testResult.getMethod(), testResult.getTestContext());
    }

    public void registerFailedTestFinish(ITestResult testResult) {
        if (isRetryFinished(testResult.getMethod(), testResult.getTestContext())) {
            log.debug("TestNGAdapter -> registerFailedTestFinish: retry is finished");

            TestInvocationContext testContext = buildTestFinishInvocationContext(testResult);
            String id = generateTestId(testContext);

            if (!registrar.isTestStarted(id)) {
                registerTestStart(testResult);
            }

            long endedAtMillis = testResult.getEndMillis();
            OffsetDateTime endedAt = ofMillis(endedAtMillis);
            String errorMessage = ExceptionUtils.getStacktrace(testResult.getThrowable());

            TestFinishDescriptor result = new TestFinishDescriptor(Status.FAILED, endedAt, errorMessage);
            registrar.registerTestFinish(id, result);
        } else {
            log.debug("TestNGAdapter -> registerFailedTestFinish: retry is NOT finished");
        }
    }

    public void registerSkippedTestFinish(ITestResult testResult) {
        if (isRetryFinished(testResult.getMethod(), testResult.getTestContext())) {
            log.debug("TestNGAdapter -> registerSkippedTestFinish: retry is finished");

            TestInvocationContext testContext = buildTestFinishInvocationContext(testResult);
            String id = generateTestId(testContext);

            OffsetDateTime endedAt = ofMillis(testResult.getEndMillis());
            String errorMessage = ExceptionUtils.getStacktrace(testResult.getThrowable());

            TestFinishDescriptor result = new TestFinishDescriptor(Status.SKIPPED, endedAt, errorMessage);
            registrar.registerTestFinish(id, result);
        } else {
            log.debug("TestNGAdapter -> registerSkippedTestFinish: retry is NOT finished");
        }
    }

    private TestInvocationContext buildTestFinishInvocationContext(ITestResult testResult) {
        ITestNGMethod testMethod = testResult.getMethod();
        ITestContext testContext = testResult.getTestContext();
        Object[] parameters = testResult.getParameters();

        int dataProviderIndex = RunContextService.getCurrentDataProviderIndex(testMethod, testContext, parameters);
//        if (dataProviderIndex == -1) {
//            // if the data provider data has been modified during the test,
//            // then the parameters at the end of the test will not be equal to the parameters at the start of the test.
//            // for such cases, we try to memorize the original method arguments in thread-local variable
//            // and search by them here
//            parameters = testStartDataProviderData.get();
//            RunContextService.getCurrentDataProviderIndex(testMethod, testContext, parameters);
//        }
        int invocationIndex = RunContextService.getMethodInvocationIndex(testMethod, testContext);

        return buildTestInvocationContext(testMethod, dataProviderIndex, parameters, invocationIndex);
    }

    public void registerAfterTestStart() {
        registrar.registerAfterTestStart();
    }

    public void registerAfterTestFinish() {
        registrar.registerAfterTestFinish();
    }

    public void clearConfigurationLogs() {
        registrar.clearConfigurationLogs();
    }

    private TestInvocationContext buildTestInvocationContext(ITestNGMethod testMethod, int dataProviderIndex, Object[] parameters, int invocationIndex) {
        String displayName = null;
        Test testAnnotation = testMethod.getConstructorOrMethod()
                                        .getMethod()
                                        .getAnnotation(org.testng.annotations.Test.class);
        if (testAnnotation != null) {
            displayName = testAnnotation.testName();
        }

        List stringParameters = Arrays.stream(parameters)
                                              .map(Object::toString)
                                              .collect(Collectors.toList());
        List parameterClassNames = Arrays.stream(testMethod.getConstructorOrMethod().getParameterTypes())
                                                 .map(Class::getName)
                                                 .collect(Collectors.toList());
        int instanceIndex = FactoryInstanceHolder.getInstanceIndex(testMethod);

        return TestInvocationContext.builder()
                                    .className(testMethod.getTestClass().getName())
                                    .methodName(testMethod.getMethodName())
                                    .displayName(displayName)
                                    .parameters(stringParameters)
                                    .parameterClassNames(parameterClassNames)
                                    .dataProviderIndex(dataProviderIndex)
                                    .instanceIndex(instanceIndex)
                                    .invocationIndex(invocationIndex)
                                    .build();
    }

    private boolean isRetryFinished(ITestNGMethod method, ITestContext context) {
        return RetryService.isRetryFinished(method, context);
    }

    private String generateTestId(TestInvocationContext testInvocationContext) {
        return testInvocationContext.toString();
    }

    private OffsetDateTime ofMillis(long epochMillis) {
        return OffsetDateTime.ofInstant(Instant.ofEpochMilli(epochMillis), ZoneId.systemDefault());
    }

}




© 2015 - 2024 Weber Informatics LLC | Privacy Policy