com.undefinedlabs.scope.rules.JUnit4ScopeAgentRule Maven / Gradle / Ivy
Go to download
Show more of this group Show more artifacts with this name
Show all versions of scope-rule-junit4 Show documentation
Show all versions of scope-rule-junit4 Show documentation
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.
package com.undefinedlabs.scope.rules;
import com.undefinedlabs.scope.ScopeGlobalTracer;
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 net.bytebuddy.agent.builder.AgentBuilder;
import net.bytebuddy.asm.Advice;
import net.bytebuddy.description.type.TypeDescription;
import net.bytebuddy.matcher.ElementMatchers;
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
public Iterable extends AgentBuilder> transformers() {
final String instrumentedClassName = "org.junit.runner.notification.RunNotifier";
return Collections.singleton(newAgentBuilder()
.type(named(instrumentedClassName), isSystemClassLoader()).transform(new ScopeAgentAdvicedTransformer(JUnit4StartedTestAdvice.class, named("fireTestStarted")))
.type(named(instrumentedClassName), isSystemClassLoader()).transform(new ScopeAgentAdvicedTransformer(JUnit4FailureTestAdvice.class, named("fireTestFailure")))
.type(named(instrumentedClassName), isSystemClassLoader()).transform(new ScopeAgentAdvicedTransformer(JUnit4FinishedTestAdvice.class, named("fireTestFinished")))
.type(named(instrumentedClassName), isSystemClassLoader()).transform(new ScopeAgentAdvicedTransformer(JUnit4IgnoredTestAdvice.class, named("fireTestIgnored")))
);
}
public static class JUnit4StartedTestAdvice {
@Advice.OnMethodExit
public static void exit(@Advice.This Object thizObj, @Advice.Argument(0) Object descriptionObj) {
final RunNotifier thiz = (RunNotifier) thizObj;
final Description description = (Description) descriptionObj;
if(description.getAnnotation(NotInstrument.class) != null) {
return;
}
final JUnit4ScopeDescription junit4Description = new JUnit4ScopeDescription(description);
final Tracer tracer = ScopeGlobalTracer.get();
final SourceCodeFrame sourceCodeFrame = SourceCodeFrameFactory.INSTANCE.createFrame(junit4Description.getClassName(), junit4Description.getMethodNameNoSpecialChars());
final Span span = tracer.buildSpan(junit4Description.getMethodName())
.ignoreActiveSpan()
.withTag(TagKeys.COMPONENT, TagValues.Test.Framework.JUNIT_4)
.withTag(TagKeys.SPAN_KIND, TagValues.SPAN_KIND_TEST)
.withTag(TagKeys.Test.TEST_NAME, junit4Description.getMethodName())
.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) Object descriptionObj) {
final Description description = (Description) descriptionObj;
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) Object failureObj) {
final Failure failure = (Failure) failureObj;
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 Object thizObj, @Advice.Argument(0) Object descriptionObj) {
final RunNotifier thiz = (RunNotifier) thizObj;
final Description description = (Description) descriptionObj;
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 = ScopeGlobalTracer.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