-
Notifications
You must be signed in to change notification settings - Fork 324
Add basic Scala Weaver sbt support #8189
New issue
Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.
By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.
Already on GitHub? Sign in to your account
Changes from 8 commits
f10262f
9aebeb1
28bd1e7
bc83850
100aafe
6fb4039
af77573
933ea53
c28c082
9028e01
339d59d
af56922
25de5c4
File filter
Filter by extension
Conversations
Jump to
Diff view
Diff view
There are no files selected for viewing
| Original file line number | Diff line number | Diff line change |
|---|---|---|
| @@ -0,0 +1,33 @@ | ||
| apply from: "$rootDir/gradle/java.gradle" | ||
| apply plugin: 'scala' | ||
|
|
||
| muzzle { | ||
| pass { | ||
| group = 'com.disneystreaming' | ||
| module = 'weaver-cats_3' | ||
| versions = '[0.8.4,)' | ||
| } | ||
| } | ||
|
|
||
| addTestSuiteForDir('latestDepTest', 'test') | ||
|
|
||
| dependencies { | ||
| compileOnly group: 'com.disneystreaming', name: 'weaver-cats_3', version: '0.8.4' | ||
|
|
||
| testImplementation testFixtures(project(':dd-java-agent:agent-ci-visibility')) | ||
|
|
||
| testImplementation group: 'org.scala-lang', name: 'scala-library', version: '2.12.20' | ||
| testImplementation group: 'com.disneystreaming', name: 'weaver-cats_3', version: '0.8.4' | ||
|
|
||
| testImplementation group: 'com.disneystreaming', name: 'weaver-cats_3', version: '+' | ||
| } | ||
|
|
||
| compileTestGroovy { | ||
| dependsOn compileTestScala | ||
| classpath += files(sourceSets.test.scala.destinationDirectory) | ||
| } | ||
|
|
||
| compileLatestDepTestGroovy { | ||
| dependsOn compileLatestDepTestScala | ||
| classpath += files(sourceSets.latestDepTest.scala.destinationDirectory) | ||
| } |
Large diffs are not rendered by default.
| Original file line number | Diff line number | Diff line change |
|---|---|---|
| @@ -0,0 +1,136 @@ | ||
| package datadog.trace.instrumentation.weaver; | ||
|
|
||
| import datadog.trace.api.civisibility.InstrumentationBridge; | ||
| import datadog.trace.api.civisibility.events.TestDescriptor; | ||
| import datadog.trace.api.civisibility.events.TestEventsHandler; | ||
| import datadog.trace.api.civisibility.events.TestSuiteDescriptor; | ||
| import datadog.trace.api.civisibility.telemetry.tag.TestFrameworkInstrumentation; | ||
| import datadog.trace.api.time.SystemTimeSource; | ||
| import datadog.trace.util.AgentThreadFactory; | ||
| import java.lang.reflect.Method; | ||
| import java.util.Collection; | ||
| import java.util.Collections; | ||
| import sbt.testing.TaskDef; | ||
| import weaver.Result; | ||
| import weaver.TestOutcome; | ||
| import weaver.framework.SuiteFinished; | ||
| import weaver.framework.SuiteStarted; | ||
| import weaver.framework.TestFinished; | ||
|
|
||
| public class DatadogWeaverReporter { | ||
|
|
||
| private static final String TEST_FRAMEWORK = "weaver"; | ||
| private static final String TEST_FRAMEWORK_VERSION = WeaverUtils.getWeaverVersion(); | ||
|
|
||
| private static volatile TestEventsHandler<TestSuiteDescriptor, TestDescriptor> | ||
| TEST_EVENTS_HANDLER; | ||
|
|
||
| static { | ||
| Runtime.getRuntime() | ||
| .addShutdownHook( | ||
| AgentThreadFactory.newAgentThread( | ||
| AgentThreadFactory.AgentThread.CI_TEST_EVENTS_SHUTDOWN_HOOK, | ||
| DatadogWeaverReporter::stop, | ||
| false)); | ||
| } | ||
|
|
||
| public static synchronized void start() { | ||
| if (TEST_EVENTS_HANDLER == null) { | ||
| TEST_EVENTS_HANDLER = InstrumentationBridge.createTestEventsHandler("weaver", null, null); | ||
| } | ||
| } | ||
|
|
||
| public static synchronized void stop() { | ||
| if (TEST_EVENTS_HANDLER != null) { | ||
| TEST_EVENTS_HANDLER.close(); | ||
| TEST_EVENTS_HANDLER = null; | ||
| } | ||
| } | ||
|
|
||
| public static void onSuiteStart(SuiteStarted event) { | ||
| String testSuiteName = event.name(); | ||
| Class<?> testClass = WeaverUtils.getClass(testSuiteName); | ||
| Collection<String> categories = Collections.emptyList(); | ||
| boolean parallelized = true; | ||
|
|
||
| TEST_EVENTS_HANDLER.onTestSuiteStart( | ||
| new TestSuiteDescriptor(testSuiteName, testClass), | ||
| testSuiteName, | ||
| TEST_FRAMEWORK, | ||
| TEST_FRAMEWORK_VERSION, | ||
| testClass, | ||
| categories, | ||
| parallelized, | ||
| TestFrameworkInstrumentation.WEAVER); | ||
| } | ||
|
|
||
| public static void onSuiteFinish(SuiteFinished event) { | ||
| String testSuiteName = event.name(); | ||
| Class<?> testClass = WeaverUtils.getClass(testSuiteName); | ||
|
|
||
| TEST_EVENTS_HANDLER.onTestSuiteFinish(new TestSuiteDescriptor(testSuiteName, testClass)); | ||
| } | ||
|
|
||
| public static void onTestFinished(TestFinished event, TaskDef taskDef) { | ||
| if (!(event.outcome() instanceof TestOutcome.Default)) { | ||
| // Cannot obtain desired information without the TestOutcome.Default fields | ||
| return; | ||
| } | ||
|
|
||
| TestOutcome.Default testOutcome = (TestOutcome.Default) event.outcome(); | ||
| String testSuiteName = taskDef.fullyQualifiedName(); | ||
| Class<?> testClass = WeaverUtils.getClass(testSuiteName); | ||
| TestSuiteDescriptor testSuiteDescriptor = new TestSuiteDescriptor(testSuiteName, testClass); | ||
| String testName = event.outcome().name(); | ||
| Object testQualifier = null; | ||
| String testParameters = null; | ||
| Collection<String> categories = Collections.emptyList(); | ||
| TestDescriptor testDescriptor = | ||
| new TestDescriptor(testSuiteName, testClass, testName, testParameters, testQualifier); | ||
| String testMethodName = null; | ||
| Method testMethod = null; | ||
| boolean isRetry = false; | ||
|
|
||
| // Only test finish is reported, so fake test start timestamp | ||
| long endMicros = SystemTimeSource.INSTANCE.getCurrentTimeMicros(); | ||
| long startMicros = endMicros - testOutcome.duration().toMicros(); | ||
| TEST_EVENTS_HANDLER.onTestStart( | ||
| testSuiteDescriptor, | ||
| testDescriptor, | ||
| testSuiteName, | ||
| testName, | ||
| TEST_FRAMEWORK, | ||
| TEST_FRAMEWORK_VERSION, | ||
| testParameters, | ||
| categories, | ||
| testClass, | ||
| testMethodName, | ||
| testMethod, | ||
| isRetry, | ||
| startMicros); | ||
|
|
||
| if (testOutcome.result() instanceof Result.Ignored) { | ||
| Result.Ignored result = (Result.Ignored) testOutcome.result(); | ||
| String reason = result.reason().getOrElse(null); | ||
| TEST_EVENTS_HANDLER.onTestSkip(testDescriptor, reason); | ||
| } else if (testOutcome.result() instanceof Result.Cancelled) { | ||
| Result.Cancelled result = (Result.Cancelled) testOutcome.result(); | ||
| String reason = result.reason().getOrElse(null); | ||
| TEST_EVENTS_HANDLER.onTestSkip(testDescriptor, reason); | ||
| } else if (testOutcome.result() instanceof Result.Failure) { | ||
| Result.Failure result = (Result.Failure) testOutcome.result(); | ||
| Throwable throwable = result.source().getOrElse(null); | ||
| TEST_EVENTS_HANDLER.onTestFailure(testDescriptor, throwable); | ||
| } else if (testOutcome.result() instanceof Result.Failures) { | ||
| Result.Failures result = (Result.Failures) testOutcome.result(); | ||
| Throwable throwable = result.failures().head().source().getOrElse(null); | ||
| TEST_EVENTS_HANDLER.onTestFailure(testDescriptor, throwable); | ||
| } else if (testOutcome.result() instanceof Result.Exception) { | ||
| Result.Exception result = (Result.Exception) testOutcome.result(); | ||
| Throwable throwable = result.source(); | ||
| TEST_EVENTS_HANDLER.onTestFailure(testDescriptor, throwable); | ||
| } | ||
|
|
||
| TEST_EVENTS_HANDLER.onTestFinish(testDescriptor, endMicros); | ||
| } | ||
| } |
| Original file line number | Diff line number | Diff line change |
|---|---|---|
| @@ -0,0 +1,36 @@ | ||
| package datadog.trace.instrumentation.weaver; | ||
|
|
||
| import java.util.concurrent.ConcurrentLinkedQueue; | ||
| import sbt.testing.TaskDef; | ||
| import weaver.framework.RunEvent; | ||
| import weaver.framework.SuiteFinished; | ||
| import weaver.framework.SuiteStarted; | ||
| import weaver.framework.TestFinished; | ||
|
|
||
| public final class TaskDefAwareQueueProxy<T> extends ConcurrentLinkedQueue<T> { | ||
|
|
||
| private final TaskDef taskDef; | ||
| private final ConcurrentLinkedQueue<T> delegate; | ||
|
|
||
| public TaskDefAwareQueueProxy(TaskDef taskDef, ConcurrentLinkedQueue<T> delegate) { | ||
| this.taskDef = taskDef; | ||
| this.delegate = delegate; | ||
| DatadogWeaverReporter.start(); | ||
| } | ||
|
|
||
| @Override | ||
| public T poll() { | ||
| T event = delegate.poll(); | ||
| if (event instanceof RunEvent) { | ||
| // handle event here, using taskDef reference to get suite details | ||
| if (event instanceof SuiteStarted) { | ||
| DatadogWeaverReporter.onSuiteStart((SuiteStarted) event); | ||
| } else if (event instanceof SuiteFinished) { | ||
| DatadogWeaverReporter.onSuiteFinish((SuiteFinished) event); | ||
| } else if (event instanceof TestFinished) { | ||
| DatadogWeaverReporter.onTestFinished((TestFinished) event, taskDef); | ||
| } | ||
| } | ||
| return event; | ||
| } | ||
| } | ||
| Original file line number | Diff line number | Diff line change |
|---|---|---|
| @@ -0,0 +1,56 @@ | ||
| package datadog.trace.instrumentation.weaver; | ||
|
|
||
| import static net.bytebuddy.matcher.ElementMatchers.isConstructor; | ||
|
|
||
| import com.google.auto.service.AutoService; | ||
| import datadog.trace.agent.tooling.Instrumenter; | ||
| import datadog.trace.agent.tooling.InstrumenterModule; | ||
| import java.util.Set; | ||
| import java.util.concurrent.ConcurrentLinkedQueue; | ||
| import net.bytebuddy.asm.Advice; | ||
| import sbt.testing.TaskDef; | ||
| import weaver.framework.SuiteEvent; | ||
|
|
||
| @AutoService(InstrumenterModule.class) | ||
| public class WeaverInstrumentation extends InstrumenterModule.CiVisibility | ||
| implements Instrumenter.ForSingleType { | ||
|
|
||
| public WeaverInstrumentation() { | ||
| super("ci-visibility", "weaver"); | ||
| } | ||
|
|
||
| @Override | ||
| public boolean isApplicable(Set<TargetSystem> enabledSystems) { | ||
|
||
| return super.isApplicable(enabledSystems); | ||
| } | ||
|
|
||
| @Override | ||
| public String instrumentedType() { | ||
| return "weaver.framework.SbtTask"; | ||
| } | ||
|
|
||
| @Override | ||
| public String[] helperClassNames() { | ||
| return new String[] { | ||
| packageName + ".DatadogWeaverReporter", | ||
| packageName + ".TaskDefAwareQueueProxy", | ||
| packageName + ".WeaverUtils", | ||
| }; | ||
| } | ||
|
|
||
| @Override | ||
| public void methodAdvice(MethodTransformer transformer) { | ||
| transformer.applyAdvice( | ||
| isConstructor(), WeaverInstrumentation.class.getName() + "$SbtTaskCreationAdvice"); | ||
| } | ||
|
|
||
| public static class SbtTaskCreationAdvice { | ||
| @Advice.OnMethodExit(suppress = Throwable.class) | ||
| public static void onTaskCreation( | ||
| @Advice.FieldValue(value = "queue", readOnly = false) | ||
| ConcurrentLinkedQueue<SuiteEvent> queue, | ||
| @Advice.FieldValue("taskDef") TaskDef taskDef) { | ||
| queue = new TaskDefAwareQueueProxy<SuiteEvent>(taskDef, queue); | ||
| } | ||
| } | ||
| } | ||
| Original file line number | Diff line number | Diff line change |
|---|---|---|
| @@ -0,0 +1,53 @@ | ||
| package datadog.trace.instrumentation.weaver; | ||
|
|
||
| import java.io.InputStream; | ||
| import java.net.URL; | ||
| import java.util.Properties; | ||
| import javax.annotation.Nullable; | ||
| import org.slf4j.Logger; | ||
| import org.slf4j.LoggerFactory; | ||
| import weaver.framework.SbtTask; | ||
|
|
||
| public abstract class WeaverUtils { | ||
|
|
||
| private static final Logger log = LoggerFactory.getLogger(WeaverUtils.class); | ||
|
|
||
| private static final ClassLoader CLASS_LOADER = SbtTask.class.getClassLoader(); | ||
|
|
||
| private WeaverUtils() {} | ||
|
|
||
| public static @Nullable String getWeaverVersion() { | ||
| try { | ||
| String className = '/' + SbtTask.class.getName().replace('.', '/') + ".class"; | ||
| URL classResource = SbtTask.class.getResource(className); | ||
| if (classResource == null) { | ||
| return null; | ||
| } | ||
|
|
||
| String classPath = classResource.toString(); | ||
| String manifestPath = | ||
| classPath.substring(0, classPath.lastIndexOf("!") + 1) + "/META-INF/MANIFEST.MF"; | ||
| try (InputStream manifestStream = new URL(manifestPath).openStream()) { | ||
| Properties manifestProperties = new Properties(); | ||
| manifestProperties.load(manifestStream); | ||
| return manifestProperties.getProperty("Implementation-Version"); | ||
| } | ||
| } catch (Exception e) { | ||
| return null; | ||
| } | ||
| } | ||
|
|
||
| @Nullable | ||
| public static Class<?> getClass(String className) { | ||
| if (className.isEmpty()) { | ||
| return null; | ||
| } | ||
| try { | ||
| return CLASS_LOADER.loadClass(className); | ||
| } catch (Exception e) { | ||
| log.debug("Could not load class {}", className, e); | ||
| log.warn("Could not load a Weaver class"); | ||
|
||
| return null; | ||
| } | ||
| } | ||
| } | ||
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
⚪ Code Quality Violation
Consider adding super() or this() to your constructor (...read more)
In Java, it is suggested to call
super()in an extended class. This rule will report a violation if both a call tosuper()and an overloaded constructor are absent.