Move JvmBootstrapTest into a fork inside a groovy test
MODIFY
dd-java-agent-ittests/dd-java-agent-ittests.gradle
dd-java-agent-ittests/dd-java-agent-ittests.gradle
@@ -54,16 +54,3 @@ tasks.withType(Test) {
parent.subprojects.collect { it.tasks.withType(Test) } each {
test.shouldRunAfter it
}
-
-task jvmStartupTest(type: Exec) {
- dependsOn project(':dd-java-agent').shadowJar
- dependsOn project(':dd-java-agent-ittests').compileTestJava
- workingDir "$buildDir/classes/java/test"
- commandLine "java"
- args "-javaagent:${project(':dd-java-agent').tasks.shadowJar.archivePath}"
- args "-Ddd.jmxfetch.enabled=true"
- args "-Djava.system.class.loader=jvmbootstraptest.CustomClassloader"
- args "jvmbootstraptest.JvmBootstrapTest"
- description = "Runs JVM Bootstrapping tests"
-}
-test.finalizedBy 'jvmStartupTest'

ADD
dd-java-agent-ittests/src/test/groovy/datadog/trace/agent/CustomLogManagerTest.groovy
+package datadog.trace.agent
+
+import datadog.trace.agent.test.IntegrationTestUtils
+import jvmbootstraptest.LogManagerSetter
+import spock.lang.Specification
+
+import java.lang.management.ManagementFactory
+import java.lang.management.RuntimeMXBean
+
+class CustomLogManagerTest extends Specification {
+ /**
+ * Forked jvm test must run in a pure java runtime because groovy sets the global log manager.
+ */
+ def "javaagent setup does not set the global log manager"() {
+ setup:
+ final RuntimeMXBean runtimeMxBean = ManagementFactory.getRuntimeMXBean()
+ String agentArg = null
+ for (String arg : runtimeMxBean.getInputArguments()) {
+ if (arg.startsWith("-javaagent")) {
+ agentArg = arg
+ break
+ }
+ }
+
+ expect:
+ agentArg != null
+ IntegrationTestUtils.runOnSeparateJvm(LogManagerSetter.getName()
+ , [agentArg, "-Ddd.jmxfetch.enabled=true"] as String[]
+ , "" as String[]
+ , true) == 0
+ }
+}

MODIFY
dd-java-agent-ittests/src/test/java/datadog/trace/agent/test/IntegrationTestUtils.java
dd-java-agent-ittests/src/test/java/datadog/trace/agent/test/IntegrationTestUtils.java
package datadog.trace.agent.test;
+import static java.nio.charset.StandardCharsets.UTF_8;
+
import io.opentracing.Scope;
import io.opentracing.Tracer;
import io.opentracing.util.GlobalTracer;
+import java.io.BufferedReader;
import java.io.File;
import java.io.FileOutputStream;
import java.io.IOException;
import java.io.InputStream;
+import java.io.InputStreamReader;
import java.lang.ref.WeakReference;
import java.lang.reflect.Field;
import java.net.URL;
+import java.util.ArrayList;
+import java.util.Arrays;
+import java.util.List;
import java.util.UUID;
import java.util.concurrent.Callable;
import java.util.jar.JarEntry;
@@ -180,4 +187,53 @@ public class IntegrationTestUtils {
System.runFinalization();
}
}
+
+ /**
+ * On a separate JVM, run the main method for a given class.
+ *
+ * @param mainClassName The name of the entry point class. Must declare a main method.
+ * @param printOutputStreams if true, print stdout and stderr of the child jvm
+ * @return the return code of the child jvm
+ * @throws Exception
+ */
+ public static int runOnSeparateJvm(
+ final String mainClassName,
+ final String[] jvmArgs,
+ final String[] mainMethodArgs,
+ final boolean printOutputStreams)
+ throws Exception {
+ final String separator = System.getProperty("file.separator");
+ final String classpath = System.getProperty("java.class.path");
+ final String path = System.getProperty("java.home") + separator + "bin" + separator + "java";
+ final List<String> commands = new ArrayList();
+ commands.add(path);
+ commands.addAll(Arrays.asList(jvmArgs));
+ commands.add("-cp");
+ commands.add(classpath);
+ commands.add(mainClassName);
+ commands.addAll(Arrays.asList(mainMethodArgs));
+ final ProcessBuilder processBuilder = new ProcessBuilder(commands.toArray(new String[0]));
+ final Process process = processBuilder.start();
+ final int result = process.waitFor();
+
+ if (printOutputStreams) {
+ final BufferedReader stdInput =
+ new BufferedReader(new InputStreamReader(process.getInputStream(), UTF_8));
+
+ final BufferedReader stdError =
+ new BufferedReader(new InputStreamReader(process.getErrorStream(), UTF_8));
+ System.out.println("--- " + mainClassName + " stdout ---");
+ String s = null;
+ while ((s = stdInput.readLine()) != null) {
+ System.out.println(s);
+ }
+ System.out.println("--- stdout end ---");
+ System.out.println("--- " + mainClassName + " stderr ---");
+ while ((s = stdError.readLine()) != null) {
+ System.out.println(s);
+ }
+ System.out.println("--- stderr end ---");
+ }
+ return result;
+ }
}

DELETE
dd-java-agent-ittests/src/test/java/jvmbootstraptest/CustomClassloader.java
-package jvmbootstraptest;
-
-import java.net.URL;
-import java.net.URLClassLoader;
-
-public class CustomClassloader extends URLClassLoader {
- static {
- ClassLoader.registerAsParallelCapable();
- }
-
- public CustomClassloader(ClassLoader parent) {
- this(new URL[0], parent);
- }
-
- public CustomClassloader(URL[] urls, ClassLoader parent) {
- super(urls, parent);
- }
-
- @Override
- public Object getClassLoadingLock(String className) {
- return super.getClassLoadingLock(className);
- }
-}

DELETE
dd-java-agent-ittests/src/test/java/jvmbootstraptest/JvmBootstrapTest.java
-package jvmbootstraptest;
-
-import java.util.logging.LogManager;
-
-public class JvmBootstrapTest {
- /**
- * See gradle task `jvmStartupTest`. This runs a paired-down jvm in order to assert that the
- * javaagent initializes correctly without interference by gradle/junit/spock.
- */
- public static void main(String... args) throws Exception {
- // Sleeping here to give the agent more time to initialize and potentially do something wrong.
- // This is an attempt to be as mean to the agent as possible.
- Thread.sleep(2000);
- final CustomClassloader systemLoader = (CustomClassloader) ClassLoader.getSystemClassLoader();
-
- // sync around the classloading lock to guarantee datadog threads won't be able to see the
- // logging manager property if they're currently initializing the logging manager.
- synchronized (systemLoader.getClassLoadingLock("java.util.logging.LogManager")) {
- System.setProperty("java.util.logging.manager", CustomLogManager.class.getName());
- customAssert(
- LogManager.getLogManager().getClass() == CustomLogManager.class,
- true,
- "Javaagent should not prevent setting a custom log manager");
- }
- }
-
- private static void customAssert(boolean got, boolean expected, String assertionMessage) {
- if (got != expected) {
- throw new RuntimeException("Assertion failed: " + assertionMessage);
- }
- }
-}

ADD
dd-java-agent-ittests/src/test/java/jvmbootstraptest/LogManagerSetter.java
+package jvmbootstraptest;
+
+import java.lang.reflect.Method;
+import java.util.logging.LogManager;
+
+public class LogManagerSetter {
+ // Intentionally doing the string replace to bypass gradle shadow rename
+ // loggerClassName = java.util.logging.Logger
+ private static final String loggerClassName =
+ "java.util.logging.TMP".replaceFirst("TMP", "Logger");
+
+ public static void main(String... args) throws Exception {
+ final ClassLoader systemLoader = ClassLoader.getSystemClassLoader();
+
+ // once the logger class has been initialized, we know jmxfetch is running and can proceed with
+ // the test
+ final Method method =
+ ClassLoader.class.getDeclaredMethod("findBootstrapClassOrNull", String.class);
+ try {
+ method.setAccessible(true);
+ while (method.invoke(systemLoader, loggerClassName) == null) {
+ Thread.sleep(1);
+ }
+ } finally {
+ method.setAccessible(false);
+ }
+
+ System.setProperty("java.util.logging.manager", CustomLogManager.class.getName());
+ customAssert(
+ LogManager.getLogManager().getClass(),
+ CustomLogManager.class,
+ "Javaagent should not prevent setting a custom log manager");
+ }
+
+ private static void customAssert(Object got, Object expected, String assertionMessage) {
+ if ((null == got && got != expected) || !got.equals(expected)) {
+ throw new RuntimeException(
+ "Assertion failed. Expected <" + expected + "> got <" + got + "> " + assertionMessage);
+ }
+ }
+}

MODIFY
dd-java-agent/agent-bootstrap/src/main/java/datadog/trace/bootstrap/DatadogClassLoader.java
dd-java-agent/agent-bootstrap/src/main/java/datadog/trace/bootstrap/DatadogClassLoader.java
@@ -38,6 +38,14 @@ public class DatadogClassLoader extends URLClassLoader {
}
}
+ /**
+ * @param className binary name of class
+ * @return true if this loader has attempted to load the given class
+ */
+ public boolean hasLoadedClass(final String className) {
+ return findLoadedClass(className) != null;
+ }
+
public BootstrapClassLoaderProxy getBootstrapProxy() {
return bootstrapProxy;
}

MODIFY
dd-java-agent/agent-jmxfetch/agent-jmxfetch.gradle
dd-java-agent/agent-jmxfetch/agent-jmxfetch.gradle
@@ -4,7 +4,6 @@ plugins {
apply from: "${rootDir}/gradle/java.gradle"
dependencies {
- compile 'com.heliosapm.jmxlocal:jmxlocal:1.0'
compile 'com.datadoghq:jmxfetch:0.24.0'
compile deps.slf4j
compile project(':dd-trace-api')

MODIFY
dd-java-agent/agent-tooling/src/main/java/datadog/trace/agent/tooling/muzzle/MuzzleVersionScanPlugin.java
dd-java-agent/agent-tooling/src/main/java/datadog/trace/agent/tooling/muzzle/MuzzleVersionScanPlugin.java
@@ -52,6 +52,7 @@ public class MuzzleVersionScanPlugin {
.newInstance();
}
if (!(instrumenter instanceof Instrumenter.Default)) {
+ // only default Instrumenters use muzzle. Skip custom instrumenters.
continue;
}
Method m = null;
@@ -98,6 +99,7 @@ public class MuzzleVersionScanPlugin {
.newInstance();
}
if (!(instrumenter instanceof Instrumenter.Default)) {
+ // only default Instrumenters use muzzle. Skip custom instrumenters.
continue;
}
Instrumenter.Default defaultInstrumenter = (Instrumenter.Default) instrumenter;

MODIFY
dd-java-agent/instrumentation/jre-logging/src/main/java/datadog/trace/instrumentation/jre/logging/LoggerInstrumentation.java
dd-java-agent/instrumentation/jre-logging/src/main/java/datadog/trace/instrumentation/jre/logging/LoggerInstrumentation.java
@@ -188,6 +188,8 @@ public class LoggerInstrumentation implements Instrumenter {
"getName",
"()Ljava/lang/String;",
false);
+ // TODO: move dd- prefix to a constant once agent-bootstrap project is visible to
+ // all subprojects
mv.visitLdcInsn("dd-");
mv.visitMethodInsn(
Opcodes.INVOKEVIRTUAL,

Move JvmBootstrapTest into a fork inside a groovy test