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

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

Go to download

Scope is a APM for tests to give engineering teams unprecedented visibility into their CI process to quickly identify, troubleshoot and fix failed builds. This artifact contains the classes to instrument the JUnit4 tests.

There is a newer version: 0.15.1-beta.2
Show newest version
package com.undefinedlabs.scope.rules;

import com.undefinedlabs.scope.ScopeSpanContainer;
import com.undefinedlabs.scope.annotations.NotInstrument;
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.rules.model.JUnit4ScopeDescription;
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.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 io.opentracing.util.GlobalTracer;
import net.bytebuddy.agent.builder.AgentBuilder;
import net.bytebuddy.asm.Advice;
import org.apache.commons.lang3.ClassUtils;
import org.apache.commons.lang3.StringUtils;
import org.apache.commons.lang3.reflect.MethodUtils;
import org.junit.Test;
import org.junit.runner.Description;
import org.junit.runner.notification.Failure;
import org.junit.runner.notification.RunNotifier;

import java.lang.reflect.Method;
import java.util.ArrayList;
import java.util.Collections;
import java.util.List;

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

public class JUnit4ScopeAgentRule extends AbstractScopeAgentRule {

    @Override
    protected String instrumentedClassName() {
        return "org.junit.runner.notification.RunNotifier";
    }

    @Override
    public Iterable transformers() {
        return Collections.singleton(new AgentBuilder.Default()
                .ignore(none())
                .with(AgentBuilder.InitializationStrategy.NoOp.INSTANCE)
                .with(AgentBuilder.RedefinitionStrategy.REDEFINITION)
                .with(AgentBuilder.RedefinitionStrategy.RETRANSFORMATION)
                .type(is(instrumentedClass()), isSystemClassLoader()).transform(new ScopeAgentAdvicedTransformer(JUnit4StartedTestAdvice.class, named("fireTestStarted")))
                .type(is(instrumentedClass()), isSystemClassLoader()).transform(new ScopeAgentAdvicedTransformer(JUnit4FailureTestAdvice.class, named("fireTestFailure")))
                .type(is(instrumentedClass()), isSystemClassLoader()).transform(new ScopeAgentAdvicedTransformer(JUnit4FinishedTestAdvice.class, named("fireTestFinished")))
                .type(is(instrumentedClass()), isSystemClassLoader()).transform(new ScopeAgentAdvicedTransformer(JUnit4IgnoredTestAdvice.class, named("fireTestIgnored")))
        );
    }

    public static class JUnit4StartedTestAdvice {

        @Advice.OnMethodExit
        public static void exit(@Advice.This RunNotifier thiz, @Advice.Argument(0) Description description) {
            if(description.getAnnotation(NotInstrument.class) != null) {
                return;
            }

           final JUnit4ScopeDescription junit4Description = new JUnit4ScopeDescription(description);
           final Tracer tracer = GlobalTracer.get();
           final SourceCodeFrame sourceCodeFrame = SourceCodeFrameFactory.INSTANCE.createFrame(junit4Description.getClassName(), junit4Description.getMethodName());


            final Span span = tracer.buildSpan(junit4Description.getMethodName() + (junit4Description.isParameterized() ? junit4Description.getHashedIteration() : ""))
                    .ignoreActiveSpan()
                    .withTag(TagKeys.COMPONENT, TagValues.Test.Framework.JUNIT_4)
                    .withTag(TagKeys.SPAN_KIND, TagValues.SPAN_KIND_TEST)
                    .withTag(TagKeys.Test.TEST_NAME, junit4Description.getMethodName() + (junit4Description.isParameterized() ? junit4Description.getHashedIteration() : ""))
                    .withTag(TagKeys.Test.TEST_SUITE, junit4Description.getClassName())
                    .withTag(TagKeys.Test.TEST_FRAMEWORK, TagValues.Test.Framework.JUNIT_4)
                    .withTag(TagKeys.Test.TEST_LANGUAGE, TagValues.Test.Language.JAVA)
                    .withTag(StringUtils.isNotEmpty(sourceCodeFrame.getLinkPathWithMethodBoundaries()) ? TagKeys.Test.TEST_CODE : TagKeys.SOURCE,
                            StringUtils.isNotEmpty(sourceCodeFrame.getLinkPathWithMethodBoundaries()) ? sourceCodeFrame.getLinkPathWithMethodBoundaries() : sourceCodeFrame.getLinkPathWithMethodLine())
                    .start();

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

            span.setBaggageItem(BaggageKeys.TRACE_KIND, BaggageValues.TRACE_KIND_TEST);
            final ScopeSpanContainer scopeSpanContainer =  new ScopeSpanContainer(span, tracer.activateSpan(span));

            JUnit4TestContextManager.INSTANCE.add(junit4Description, scopeSpanContainer);
            Statistics.INSTANCE.registerStartedTestSpan(span);
        }

    }

    public static class JUnit4FinishedTestAdvice {

        @Advice.OnMethodExit
        public static void exit(@Advice.Argument(0) Description description) {
            if(description.getAnnotation(NotInstrument.class) != null) {
                return;
            }

            final JUnit4ScopeDescription jUnit4Description = new JUnit4ScopeDescription(description);
            final ScopeSpanContainer scopeSpanContainer = JUnit4TestContextManager.INSTANCE.poll(jUnit4Description);
            if(scopeSpanContainer == null){
                return;
            }

            final Span span = scopeSpanContainer.getSpan();
            final String propagationTestStatusTag = (String) scopeSpanContainer.getPropagationTag(TagKeys.Test.TEST_STATUS);
            span.setTag(TagKeys.Test.TEST_STATUS, StringUtils.isNotEmpty(propagationTestStatusTag) ? propagationTestStatusTag : TagValues.Test.TEST_PASS);
            span.setTag(TagKeys.ERROR, TagValues.Test.TEST_FAIL.equals(propagationTestStatusTag) || TagValues.Test.TEST_ERROR.equals(propagationTestStatusTag));

            if((boolean) ScopeSettingsResolver.INSTANCE.get().getSetting(ScopeSettings.SCOPE_CODE_COVERAGE)){
                final CoverageSession coverageSession = GlobalCoverageReporter.get().activeSession();
                 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();
            scopeSpanContainer.getScope().close();
            Statistics.INSTANCE.registerFinishedTestSpan(span);
        }

    }

    public static class JUnit4FailureTestAdvice {

        @Advice.OnMethodExit
        public static void exit(@Advice.Argument(0) Failure failure) {
            if(failure == null
                    || failure.getDescription() == null
                    || failure.getDescription().getMethodName() == null
                    || failure.getException() == null
                    || failure.getDescription().getAnnotation(NotInstrument.class) != null) {
                return;
            }

            final JUnit4ScopeDescription jUnit4Description = new JUnit4ScopeDescription(failure.getDescription());
            final ScopeSpanContainer scopeSpanContainer = JUnit4TestContextManager.INSTANCE.peek(jUnit4Description);
            if(scopeSpanContainer == null){
                return;
            }

            final ExceptionSourceCodeFrame exceptionSourceCodeFrame = ExceptionSourceCodeFactory.INSTANCE.createFrame(failure.getException());

            final ThrowableEvent.Builder throwableEventBuilder = ThrowableEvent.newBuilder();
            final ThrowableEvent throwableEvent = throwableEventBuilder
                    .withEventType((exceptionSourceCodeFrame.getUserThrowable() instanceof AssertionError)
                            ? EventValues.Test.TEST_FAILURE
                            : EventValues.Test.TEST_ERROR)
                    .withThrowable(exceptionSourceCodeFrame.getUserThrowable())
                    .withSource(exceptionSourceCodeFrame.getSourceCodeFrame().getLinkPathWithMethodLine())
                    .build();

            scopeSpanContainer.getSpan().log(EventFieldsFactory.INSTANCE.createFields(throwableEvent));
            scopeSpanContainer.putPropagationTag(TagKeys.Test.TEST_STATUS, (failure.getException() instanceof AssertionError) ? TagValues.Test.TEST_FAIL : TagValues.Test.TEST_ERROR);
            scopeSpanContainer.putPropagationTag(TagKeys.ERROR, true);
        }
    }

    public static class JUnit4IgnoredTestAdvice {

        @Advice.OnMethodExit
        public static void exit(@Advice.This RunNotifier thiz, @Advice.Argument(0) Description description) {
            if(description.getAnnotation(NotInstrument.class) != null) {
                return;
            }

            final List testMethodsNames = new ArrayList<>();

            if(StringUtils.isNotEmpty(description.getMethodName())) {
                testMethodsNames.add(description.getMethodName());
            } else {
                try {
                    final Class testClass = ClassUtils.getClass(description.getClassName());
                    final List testMethods = MethodUtils.getMethodsListWithAnnotation(testClass, Test.class);
                    for (final Method testMethod : testMethods) {
                        testMethodsNames.add(testMethod.getName());
                    }
                } catch(final ClassNotFoundException e){
                    //N/A
                }
            }

            final JUnit4ScopeDescription junit4Description = new JUnit4ScopeDescription(description);
            final Tracer tracer = GlobalTracer.get();
            for(final String testMethodName : testMethodsNames) {
                final SourceCodeFrame sourceCodeFrame = SourceCodeFrameFactory.INSTANCE.createFrame(junit4Description.getClassName(), testMethodName);

                final Span span = tracer.buildSpan(testMethodName)
                        .ignoreActiveSpan()
                        .withTag(TagKeys.COMPONENT, TagValues.Test.Framework.JUNIT_4)
                        .withTag(TagKeys.SPAN_KIND, TagValues.SPAN_KIND_TEST)
                        .withTag(TagKeys.Test.TEST_NAME, StringUtils.isNotEmpty(testMethodName) ? testMethodName : "")
                        .withTag(TagKeys.Test.TEST_SUITE, junit4Description.getClassName())
                        .withTag(TagKeys.Test.TEST_FRAMEWORK, TagValues.Test.Framework.JUNIT_4)
                        .withTag(TagKeys.Test.TEST_LANGUAGE, TagValues.Test.Language.JAVA)
                        .withTag(TagKeys.Test.TEST_STATUS, TagValues.Test.TEST_SKIP)
                        .withTag(StringUtils.isNotEmpty(sourceCodeFrame.getLinkPathWithMethodBoundaries()) ? TagKeys.Test.TEST_CODE : TagKeys.SOURCE,
                                StringUtils.isNotEmpty(sourceCodeFrame.getLinkPathWithMethodBoundaries()) ? sourceCodeFrame.getLinkPathWithMethodBoundaries() : sourceCodeFrame.getLinkPathWithMethodLine())
                        .withTag(TagKeys.ERROR, false)
                        .start();

                Statistics.INSTANCE.registerStartedTestSpan(span);

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

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




© 2015 - 2025 Weber Informatics LLC | Privacy Policy