Skip to content
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

Add basic Scala Weaver sbt support #8189

Draft
wants to merge 12 commits into
base: master
Choose a base branch
from
Draft
Original file line number Diff line number Diff line change
Expand Up @@ -61,6 +61,24 @@ public void onTestStart(
// do nothing
}

@Override
public void onTestStart(
SuiteKey suiteDescriptor,
TestKey descriptor,
String testSuiteName,
String testName,
@Nullable String testFramework,
@Nullable String testFrameworkVersion,
@Nullable String testParameters,
@Nullable Collection<String> categories,
@Nullable Class<?> testClass,
@Nullable String testMethodName,
@Nullable Method testMethod,
boolean isRetry,
@Nullable Long startTime) {
// do nothing
}

@Override
public void onTestSkip(TestKey descriptor, @Nullable String reason) {
// do nothing
Expand All @@ -76,6 +94,11 @@ public void onTestFinish(TestKey descriptor) {
// do nothing
}

@Override
public void onTestFinish(TestKey descriptor, @Nullable Long endTime) {
// do nothing
}

@Override
public void onTestIgnore(
SuiteKey suiteDescriptor,
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -135,6 +135,37 @@ public void onTestStart(
final @Nullable String testMethodName,
final @Nullable Method testMethod,
final boolean isRetry) {
onTestStart(
suiteDescriptor,
descriptor,
testSuiteName,
testName,
testFramework,
testFrameworkVersion,
testParameters,
categories,
testClass,
testMethodName,
testMethod,
isRetry,
null);
}

@Override
public void onTestStart(
final SuiteKey suiteDescriptor,
final TestKey descriptor,
final String testSuiteName,
final String testName,
final @Nullable String testFramework,
final @Nullable String testFrameworkVersion,
final @Nullable String testParameters,
final @Nullable Collection<String> categories,
final @Nullable Class<?> testClass,
final @Nullable String testMethodName,
final @Nullable Method testMethod,
final boolean isRetry,
final @Nullable Long startTime) {
if (skipTrace(testClass)) {
return;
}
Expand All @@ -148,7 +179,7 @@ public void onTestStart(
+ descriptor);
}

TestImpl test = testSuite.testStart(testName, testParameters, testMethod, null);
TestImpl test = testSuite.testStart(testName, testParameters, testMethod, startTime);

TestIdentifier thisTest = new TestIdentifier(testSuiteName, testName, testParameters);
if (testModule.isNew(thisTest)) {
Expand Down Expand Up @@ -213,12 +244,17 @@ public void onTestFailure(TestKey descriptor, @Nullable Throwable throwable) {

@Override
public void onTestFinish(TestKey descriptor) {
onTestFinish(descriptor, null);
}

@Override
public void onTestFinish(TestKey descriptor, @Nullable Long endTime) {
TestImpl test = inProgressTests.remove(descriptor);
if (test == null) {
log.debug("Ignoring finish event, could not find test {}", descriptor);
return;
}
test.end(null);
test.end(endTime);
}

@Override
Expand Down
33 changes: 33 additions & 0 deletions dd-java-agent/instrumentation/weaver/build.gradle
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)
}
249 changes: 249 additions & 0 deletions dd-java-agent/instrumentation/weaver/gradle.lockfile

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();
}
Copy link
Contributor

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 to super() and an overloaded constructor are absent.

View in Datadog  Leave us feedback  Documentation


@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, Instrumenter.HasMethodAdvice {

public WeaverInstrumentation() {
super("ci-visibility", "weaver");
}

@Override
public boolean isApplicable(Set<TargetSystem> enabledSystems) {
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

There's no need to override this method, since all it does is delegating to the parent class

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);
}
}
}
Loading
Loading