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

com.undefinedlabs.scope.rules.JUnit5ScopeAgentRule Maven / Gradle / Ivy

package com.undefinedlabs.scope.rules;

import com.undefinedlabs.scope.rules.frameworks.model.junit5.*;
import com.undefinedlabs.scope.ScopeGlobalTracer;
import com.undefinedlabs.scope.ScopeSpanContainer;
import com.undefinedlabs.scope.coverage.*;
import com.undefinedlabs.scope.coverage.extractor.model.ScopeCoverageData;
import com.undefinedlabs.scope.events.EventFieldsFactory;
import com.undefinedlabs.scope.events.exception.ThrowableEvent;
import com.undefinedlabs.scope.jdk.OptionalMapper;
import com.undefinedlabs.scope.rules.transformer.ScopeAgentAdvicedTransformer;
import com.undefinedlabs.scope.settings.ScopeSettings;
import com.undefinedlabs.scope.settings.ScopeSettingsResolver;
import com.undefinedlabs.scope.statistics.Statistics;
import com.undefinedlabs.scope.utils.SpanUtils;
import com.undefinedlabs.scope.utils.baggage.BaggageKeys;
import com.undefinedlabs.scope.utils.baggage.BaggageValues;
import com.undefinedlabs.scope.utils.event.EventValues;
import com.undefinedlabs.scope.utils.props.SystemProps;
import com.undefinedlabs.scope.utils.sourcecode.ExceptionSourceCodeFactory;
import com.undefinedlabs.scope.utils.sourcecode.ExceptionSourceCodeFrame;
import com.undefinedlabs.scope.utils.sourcecode.SourceCodeFrame;
import com.undefinedlabs.scope.utils.sourcecode.SourceCodeFrameFactory;
import com.undefinedlabs.scope.utils.tag.TagKeys;
import com.undefinedlabs.scope.utils.tag.TagValues;
import io.opentracing.Span;
import io.opentracing.Tracer;
import net.bytebuddy.agent.builder.AgentBuilder;
import net.bytebuddy.asm.Advice;
import com.undefinedlabs.scope.deps.org.apache.commons.lang3.StringUtils;
import org.junit.platform.engine.TestExecutionResult;
import org.junit.platform.launcher.TestExecutionListener;
import org.junit.platform.launcher.TestIdentifier;

import java.util.Collections;
import java.util.HashMap;
import java.util.Map;
import java.util.concurrent.ConcurrentHashMap;

import static net.bytebuddy.matcher.ElementMatchers.*;

public class JUnit5ScopeAgentRule extends AbstractScopeAgentRule {

    public static final Map TEST_IDENTIFIER_WAS_STARTED = new HashMap<>();
    public static final Map TEST_IDENTIFIER_WAS_FINISHED = new HashMap<>();
    public static final Map TEST_IDENTIFIER_WAS_SKIPPED = new HashMap<>();
    public static final Map SCOPE_SPAN_CONTAINER_BY_TEST_IDENTIFIER_MAP = new ConcurrentHashMap<>();

    @Override
    protected Iterable transformers() {
        final Class instrumentedClass = lookUp("org.junit.platform.launcher.TestExecutionListener");

        return (instrumentedClass != null) ?
                Collections.singleton(newAgentBuilder()
                        .type(isSubTypeOf(instrumentedClass), isSystemClassLoader()).transform(new ScopeAgentAdvicedTransformer(JUnit5StartedTestAdvice.class, named("executionStarted")))
                        .type(isSubTypeOf(instrumentedClass), isSystemClassLoader()).transform(new ScopeAgentAdvicedTransformer(JUnit5FinishedTestAdvice.class, named("executionFinished")))
                        .type(isSubTypeOf(instrumentedClass), isSystemClassLoader()).transform(new ScopeAgentAdvicedTransformer(JUnit5DisabledTestAdvice.class, named("executionSkipped"))))
                : emptyCollection();
    }

    public static class JUnit5StartedTestAdvice {

        @Advice.OnMethodExit
        public static void exit(@Advice.This TestExecutionListener thiz, @Advice.Argument(0) TestIdentifier testIdentifier) {
            if (testIdentifier.isContainer()) {
                return;
            }

            synchronized (TEST_IDENTIFIER_WAS_STARTED) {
                if (Boolean.TRUE.equals(TEST_IDENTIFIER_WAS_STARTED.get(testIdentifier))) {
                    return;
                }


                final Tracer tracer = ScopeGlobalTracer.get();
                final JUnit5ScopeUniqueId jUnit5ScopeUniqueId = new JUnit5ScopeUniqueId(testIdentifier.getUniqueId());
                final SourceCodeFrame sourceCodeFrame = SourceCodeFrameFactory.INSTANCE.createFrame(jUnit5ScopeUniqueId.getClassName(), jUnit5ScopeUniqueId.getMethodName());

                if (StringUtils.isEmpty(jUnit5ScopeUniqueId.getClassName()) || StringUtils.isEmpty(jUnit5ScopeUniqueId.getMethodName())) {
                    return;
                }

                final String testName = jUnit5ScopeUniqueId.getMethodName() + (StringUtils.isNotEmpty(jUnit5ScopeUniqueId.getInvocation()) ? "[" + jUnit5ScopeUniqueId.getInvocation() + "]" : "");
                final Span span = tracer.buildSpan(testName)
                        .ignoreActiveSpan()
                        .withTag(TagKeys.COMPONENT, TagValues.Test.Framework.JUNIT_5)
                        .withTag(TagKeys.SPAN_KIND, TagValues.SPAN_KIND_TEST)
                        .withTag(TagKeys.Test.TEST_NAME, StringUtils.isNotEmpty(testName) ? testName : "")
                        .withTag(TagKeys.Test.TEST_SUITE, StringUtils.isNotEmpty(jUnit5ScopeUniqueId.getClassName()) ? jUnit5ScopeUniqueId.getClassName() : "")
                        .withTag(TagKeys.Test.TEST_FRAMEWORK, TagValues.Test.Framework.JUNIT_5)
                        .withTag(TagKeys.Test.TEST_LANGUAGE, TagValues.Test.Language.JAVA)
                        .start();

                if(StringUtils.isNotEmpty(sourceCodeFrame.getLinkPathWithMethodBoundaries())) {
                    span.setTag(TagKeys.Test.TEST_CODE, sourceCodeFrame.getLinkPathWithMethodBoundaries());
                } else if(StringUtils.isNotEmpty(sourceCodeFrame.getLinkPathWithMethodLine())) {
                    span.setTag(TagKeys.SOURCE, sourceCodeFrame.getLinkPathWithMethodLine());
                }

                if ((boolean) ScopeSettingsResolver.INSTANCE.get().getSetting(ScopeSettings.SCOPE_CODE_PATH_ENABLED)) {
                    GlobalCoverageReporter.get().startSession();
                    span.setBaggageItem(BaggageKeys.COVERAGE_ENABLED, String.valueOf(true));
                }

                Statistics.INSTANCE.registerStartedTestSpan(span);

                span.setBaggageItem(BaggageKeys.TRACE_KIND, BaggageValues.TRACE_KIND_TEST);
                tracer.activateSpan(span);
                SCOPE_SPAN_CONTAINER_BY_TEST_IDENTIFIER_MAP.put(testIdentifier, new ScopeSpanContainer(span, tracer.activateSpan(span)));
                TEST_IDENTIFIER_WAS_STARTED.put(testIdentifier, Boolean.TRUE);
            }
        }

    }

    public static class JUnit5FinishedTestAdvice {

        @Advice.OnMethodExit
        public static void exit(@Advice.This TestExecutionListener thiz, @Advice.Argument(0) TestIdentifier testIdentifier, @Advice.Argument(1) TestExecutionResult testExecutionResult) {
            if (testIdentifier.isContainer()) {
                return;
            }

            synchronized (TEST_IDENTIFIER_WAS_FINISHED) {
                if (Boolean.TRUE.equals(TEST_IDENTIFIER_WAS_FINISHED.get(testIdentifier))) {
                    return;
                }

                final ScopeSpanContainer scopeSpanContainer = SCOPE_SPAN_CONTAINER_BY_TEST_IDENTIFIER_MAP.get(testIdentifier);
                if (scopeSpanContainer == null) {
                    return;
                }

                final Span span = scopeSpanContainer.getSpan();
                span.setTag(TagKeys.ERROR, !TestExecutionResult.Status.SUCCESSFUL.equals(testExecutionResult.getStatus()));

                final Throwable throwable = (Throwable) OptionalMapper.getByMethod(testExecutionResult, "getThrowable");
                if (throwable != null) {
                    final ExceptionSourceCodeFrame exceptionSourceCodeFrame = ExceptionSourceCodeFactory.INSTANCE.createFrame(throwable);
                    final ThrowableEvent.Builder throwableEventBuilder = ThrowableEvent.newBuilder();
                    throwableEventBuilder
                            .withEventType((exceptionSourceCodeFrame.getUserThrowable() instanceof AssertionError) ? EventValues.Test.TEST_FAILURE : EventValues.Test.TEST_ERROR)
                            .withThrowable(exceptionSourceCodeFrame.getUserThrowable())
                            .withSource(exceptionSourceCodeFrame.getSourceCodeFrame().getLinkPathWithMethodLine())
                            .build();

                    span.setTag(TagKeys.Test.TEST_STATUS, TagValues.Test.TEST_FAIL);
                    span.log(EventFieldsFactory.INSTANCE.createFields(throwableEventBuilder.build()));

                } else {
                    span.setTag(TagKeys.Test.TEST_STATUS, TagValues.Test.TEST_PASS);
                }

                if ((boolean) ScopeSettingsResolver.INSTANCE.get().getSetting(ScopeSettings.SCOPE_CODE_PATH_ENABLED)) {
                    final CoverageSession coverageSession = GlobalCoverageReporter.get().activeSession();
                    if (coverageSession != null) {
                        final CoverageSessionInfo coverageSessionInfo = coverageSession.close();
                        final CoverageSessionReport coverageReport = CoverageSessionAnalyzer.INSTANCE.analyze(coverageSessionInfo);
                        final ScopeCoverageData coverageData = ScopeCoverageExtractor.INSTANCE.analyze(coverageReport);
                        SpanUtils.INSTANCE.setTagObject(span, TagKeys.Test.TEST_COVERAGE, coverageData);
                    }
                }

                span.finish();
                Statistics.INSTANCE.registerFinishedTestSpan(span);

                scopeSpanContainer.getScope().close();
                SCOPE_SPAN_CONTAINER_BY_TEST_IDENTIFIER_MAP.remove(testIdentifier);
                TEST_IDENTIFIER_WAS_FINISHED.put(testIdentifier, Boolean.TRUE);
            }

        }
    }

    public static class JUnit5DisabledTestAdvice {

        @Advice.OnMethodExit
        public static void exit(@Advice.This TestExecutionListener thiz, @Advice.Argument(0) TestIdentifier testIdentifier, @Advice.Argument(1) String reason) {
            if (testIdentifier.isContainer()) {
                return;
            }

            synchronized (TEST_IDENTIFIER_WAS_SKIPPED) {
                if (Boolean.TRUE.equals(TEST_IDENTIFIER_WAS_SKIPPED.get(testIdentifier))) {
                    return;
                }


                final Tracer tracer = ScopeGlobalTracer.get();
                final JUnit5ScopeUniqueId jUnit5ScopeUniqueId = new JUnit5ScopeUniqueId(testIdentifier.getUniqueId());
                final JUnit5ScopeTestDisplayName jUnit5ScopeTestDisplayName = new JUnit5ScopeTestDisplayName(testIdentifier.getDisplayName());
                final SourceCodeFrame sourceCodeFrame = SourceCodeFrameFactory.INSTANCE.createFrame(jUnit5ScopeUniqueId.getClassName(), jUnit5ScopeUniqueId.getMethodName());

                if (StringUtils.isEmpty(jUnit5ScopeUniqueId.getClassName()) || StringUtils.isEmpty(jUnit5ScopeTestDisplayName.get())) {
                    return;
                }

                final Span span = tracer.buildSpan(jUnit5ScopeTestDisplayName.get())
                        .ignoreActiveSpan()
                        .withTag(TagKeys.COMPONENT, TagValues.Test.Framework.JUNIT_5)
                        .withTag(TagKeys.SPAN_KIND, TagValues.SPAN_KIND_TEST)
                        .withTag(TagKeys.Test.TEST_NAME, StringUtils.isNotEmpty(jUnit5ScopeTestDisplayName.get()) ? jUnit5ScopeTestDisplayName.get() : "")
                        .withTag(TagKeys.Test.TEST_SUITE, StringUtils.isNotEmpty(jUnit5ScopeUniqueId.getClassName()) ? jUnit5ScopeUniqueId.getClassName() : "")
                        .withTag(TagKeys.Test.TEST_FRAMEWORK, TagValues.Test.Framework.JUNIT_5)
                        .withTag(TagKeys.Test.TEST_LANGUAGE, TagValues.Test.Language.JAVA)
                        .withTag(TagKeys.Test.TEST_STATUS, TagValues.Test.TEST_SKIP)
                        .withTag(TagKeys.ERROR, false)
                        .start();

                if(StringUtils.isNotEmpty(sourceCodeFrame.getLinkPathWithMethodBoundaries())) {
                    span.setTag(TagKeys.Test.TEST_CODE, sourceCodeFrame.getLinkPathWithMethodBoundaries());
                } else if(StringUtils.isNotEmpty(sourceCodeFrame.getLinkPathWithMethodLine())) {
                    span.setTag(TagKeys.SOURCE, sourceCodeFrame.getLinkPathWithMethodLine());
                }

                Statistics.INSTANCE.registerStartedTestSpan(span);

                span.setBaggageItem(BaggageKeys.TRACE_KIND, BaggageValues.TRACE_KIND_TEST);
                span.finish(SpanUtils.INSTANCE.getStartTimestampMicros(span));

                Statistics.INSTANCE.registerFinishedTestSpan(span);
                TEST_IDENTIFIER_WAS_SKIPPED.put(testIdentifier, Boolean.TRUE);
            }
        }

    }
}




© 2015 - 2025 Weber Informatics LLC | Privacy Policy