Skip to content
Merged
Show file tree
Hide file tree
Changes from 8 commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
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 {

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

Choose a reason for hiding this comment

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

Let's remove this warning and leave only the debug log.
These duplicate log lines are leftovers from when only warnings and error were reported to telemetry (and the warning messages were made more generic on purpose because whatever was written to telemetry had to have low cardinality).
Currently even debug messages are written to telemetry if they include a stacktrace, so duplicating this error with the warn log level makes no sense.

return null;
}
}
}
Loading
Loading