Fix AgentTestRunner bootstrapping
MODIFY
dd-java-agent/testing/src/main/java/datadog/trace/agent/test/SpockRunner.java
dd-java-agent/testing/src/main/java/datadog/trace/agent/test/SpockRunner.java
package datadog.trace.agent.test;
-import static com.google.common.base.StandardSystemProperty.JAVA_CLASS_PATH;
-import static com.google.common.base.StandardSystemProperty.PATH_SEPARATOR;
-import static datadog.trace.agent.tooling.Utils.BOOTSTRAP_PACKAGE_PREFIXES;
-
-import com.google.common.base.Splitter;
-import com.google.common.collect.ImmutableList;
import com.google.common.reflect.ClassPath;
-import datadog.trace.agent.tooling.Utils;
import java.io.File;
import java.io.IOException;
import java.lang.reflect.Field;
-import java.net.MalformedURLException;
-import java.net.URL;
-import java.net.URLClassLoader;
import java.util.Arrays;
import java.util.HashSet;
import java.util.Set;
@@ -32,6 +22,20 @@ import org.spockframework.runtime.Sputnik;
* </ul>
*/
public class SpockRunner extends Sputnik {
+ /**
+ * An exact copy of Utils#BOOSTRAP_PACKAGE_PREFIXES.
+ *
+ * <p>This list is needed to initialize the bootstrap classpath because Utils' static initializer
+ * references bootstrap classes (e.g. DatadogClassLoader).
+ */
+ public static final String[] BOOTSTRAP_PACKAGE_PREFIXES_COPY = {
+ "io.opentracing",
+ "datadog.slf4j",
+ "datadog.trace.bootstrap",
+ "datadog.trace.api",
+ "datadog.trace.context"
+ };
+
private static final String[] TEST_BOOTSTRAP_PREFIXES;
static {
@@ -47,9 +51,10 @@ public class SpockRunner extends Sputnik {
};
TEST_BOOTSTRAP_PREFIXES =
Arrays.copyOf(
- BOOTSTRAP_PACKAGE_PREFIXES, BOOTSTRAP_PACKAGE_PREFIXES.length + testBS.length);
+ BOOTSTRAP_PACKAGE_PREFIXES_COPY,
+ BOOTSTRAP_PACKAGE_PREFIXES_COPY.length + testBS.length);
for (int i = 0; i < testBS.length; ++i) {
- TEST_BOOTSTRAP_PREFIXES[i + BOOTSTRAP_PACKAGE_PREFIXES.length] = testBS[i];
+ TEST_BOOTSTRAP_PREFIXES[i + BOOTSTRAP_PACKAGE_PREFIXES_COPY.length] = testBS[i];
}
setupBootstrapClasspath();
@@ -100,22 +105,17 @@ public class SpockRunner extends Sputnik {
final File bootstrapJar = createBootstrapJar();
ByteBuddyAgent.getInstrumentation()
.appendToBootstrapClassLoaderSearch(new JarFile(bootstrapJar));
- Utils.getBootstrapProxy().addURL(bootstrapJar.toURI().toURL());
+ // Utils cannot be referenced before this line, as its static initializers load bootstrap
+ // classes (for example, the bootstrap proxy).
+ datadog.trace.agent.tooling.Utils.getBootstrapProxy().addURL(bootstrapJar.toURI().toURL());
} catch (IOException e) {
throw new RuntimeException(e);
}
}
private static File createBootstrapJar() throws IOException {
- ClassLoader loader = AgentTestRunner.class.getClassLoader();
- if (!(loader instanceof URLClassLoader)) {
- // java9's system loader does not extend URLClassLoader
- // which breaks Guava ClassPath lookup
- loader = buildJavaClassPathClassLoader();
- }
- final ClassPath testCP = ClassPath.from(loader);
Set<String> bootstrapClasses = new HashSet<String>();
- for (ClassPath.ClassInfo info : testCP.getAllClasses()) {
+ for (ClassPath.ClassInfo info : TestUtils.getTestClasspath().getAllClasses()) {
// if info starts with bootstrap prefix: add to bootstrap jar
for (int i = 0; i < TEST_BOOTSTRAP_PREFIXES.length; ++i) {
if (info.getName().startsWith(TEST_BOOTSTRAP_PREFIXES[i])) {
@@ -125,31 +125,9 @@ public class SpockRunner extends Sputnik {
}
}
return new File(
- TestUtils.createJarWithClasses(loader, bootstrapClasses.toArray(new String[0])).getFile());
- }
-
- /**
- * Parse JVM classpath and return ClassLoader containing all classpath entries. Inspired by Guava.
- *
- * <p>TODO: use we cannot use Guava version when we can update Guava to version that has this
- * logic, i.e. when we drop Java7 support.
- */
- private static ClassLoader buildJavaClassPathClassLoader() {
- ImmutableList.Builder<URL> urls = ImmutableList.builder();
- for (String entry : Splitter.on(PATH_SEPARATOR.value()).split(JAVA_CLASS_PATH.value())) {
- try {
- try {
- urls.add(new File(entry).toURI().toURL());
- } catch (SecurityException e) { // File.toURI checks to see if the file is a directory
- urls.add(new URL("file", null, new File(entry).getAbsolutePath()));
- }
- } catch (MalformedURLException e) {
- System.err.println(
- String.format(
- "Error injecting bootstrap jar: Malformed classpath entry: %s. %s", entry, e));
- }
- }
- return new URLClassLoader(urls.build().toArray(new URL[0]), null);
+ TestUtils.createJarWithClasses(
+ AgentTestRunner.class.getClassLoader(), bootstrapClasses.toArray(new String[0]))
+ .getFile());
}
/** Run test classes in a classloader which loads test classes before delegating. */

MODIFY
dd-java-agent/testing/src/main/java/datadog/trace/agent/test/TestUtils.java
dd-java-agent/testing/src/main/java/datadog/trace/agent/test/TestUtils.java
package datadog.trace.agent.test;
+import static com.google.common.base.StandardSystemProperty.JAVA_CLASS_PATH;
+import static com.google.common.base.StandardSystemProperty.PATH_SEPARATOR;
import static io.opentracing.log.Fields.ERROR_OBJECT;
+import com.google.common.base.Splitter;
+import com.google.common.collect.ImmutableList;
+import com.google.common.reflect.ClassPath;
import datadog.trace.agent.tooling.Utils;
import datadog.trace.context.TraceScope;
import io.opentracing.Scope;
@@ -16,8 +21,10 @@ import java.io.IOException;
import java.io.InputStream;
import java.lang.ref.WeakReference;
import java.lang.reflect.Field;
+import java.net.MalformedURLException;
import java.net.ServerSocket;
import java.net.URL;
+import java.net.URLClassLoader;
import java.util.Collections;
import java.util.UUID;
import java.util.concurrent.Callable;
@@ -26,6 +33,7 @@ import java.util.jar.JarOutputStream;
import java.util.jar.Manifest;
public class TestUtils {
+ private static final ClassPath testClasspath = computeTestClasspath();
public static void registerOrReplaceGlobalTracer(final Tracer tracer) {
try {
@@ -223,4 +231,46 @@ public class TestUtils {
System.runFinalization();
}
}
+
+ public static ClassPath getTestClasspath() {
+ return testClasspath;
+ }
+
+ private static ClassPath computeTestClasspath() {
+ ClassLoader testClassLoader = AgentTestRunner.class.getClassLoader();
+ if (!(testClassLoader instanceof URLClassLoader)) {
+ // java9's system loader does not extend URLClassLoader
+ // which breaks Guava ClassPath lookup
+ testClassLoader = buildJavaClassPathClassLoader();
+ }
+ try {
+ return ClassPath.from(testClassLoader);
+ } catch (IOException e) {
+ throw new RuntimeException(e);
+ }
+ }
+
+ /**
+ * Parse JVM classpath and return ClassLoader containing all classpath entries. Inspired by Guava.
+ *
+ * <p>TODO: use we cannot use Guava version when we can update Guava to version that has this
+ * logic, i.e. when we drop Java7 support.
+ */
+ private static ClassLoader buildJavaClassPathClassLoader() {
+ ImmutableList.Builder<URL> urls = ImmutableList.builder();
+ for (String entry : Splitter.on(PATH_SEPARATOR.value()).split(JAVA_CLASS_PATH.value())) {
+ try {
+ try {
+ urls.add(new File(entry).toURI().toURL());
+ } catch (SecurityException e) { // File.toURI checks to see if the file is a directory
+ urls.add(new URL("file", null, new File(entry).getAbsolutePath()));
+ }
+ } catch (MalformedURLException e) {
+ System.err.println(
+ String.format(
+ "Error injecting bootstrap jar: Malformed classpath entry: %s. %s", entry, e));
+ }
+ }
+ return new URLClassLoader(urls.build().toArray(new URL[0]), null);
+ }
}

MODIFY
dd-java-agent/testing/src/test/groovy/AgentTestRunnerTest.groovy
dd-java-agent/testing/src/test/groovy/AgentTestRunnerTest.groovy
+import com.google.common.reflect.ClassPath
import datadog.trace.agent.test.AgentTestRunner
+import datadog.trace.agent.test.SpockRunner
import datadog.trace.agent.test.TestUtils
+import datadog.trace.agent.tooling.Utils
import io.opentracing.Tracer
import java.lang.reflect.Field
@@ -19,16 +22,34 @@ class AgentTestRunnerTest extends AgentTestRunner {
AGENT_INSTALLED_IN_CLINIT = getAgentTransformer() != null
}
+ def "spock runner bootstrap prefixes correct for test setup"() {
+ expect:
+ SpockRunner.BOOTSTRAP_PACKAGE_PREFIXES_COPY == Utils.BOOTSTRAP_PACKAGE_PREFIXES
+ }
+
def "classpath setup"() {
+ setup:
+ final List<String> bootstrapClassesIncorrectlyLoaded = []
+ for (ClassPath.ClassInfo info : TestUtils.getTestClasspath().getAllClasses()) {
+ for (int i = 0; i < Utils.BOOTSTRAP_PACKAGE_PREFIXES.length; ++i) {
+ if (info.getName().startsWith(Utils.BOOTSTRAP_PACKAGE_PREFIXES[i])) {
+ Class<?> bootstrapClass = Class.forName(info.getName())
+ if (bootstrapClass.getClassLoader() != BOOTSTRAP_CLASSLOADER) {
+ bootstrapClassesIncorrectlyLoaded.add(bootstrapClass)
+ }
+ break
+ }
+ }
+ }
+
expect:
A_TRACER == null
DD_API_TRACER == null
- OT_LOADER == BOOTSTRAP_CLASSLOADER
!AGENT_INSTALLED_IN_CLINIT
getTestTracer() == TestUtils.getUnderlyingGlobalTracer()
getAgentTransformer() != null
- datadog.trace.api.Trace.getClassLoader() == BOOTSTRAP_CLASSLOADER
TestUtils.getUnderlyingGlobalTracer() == datadog.trace.api.GlobalTracer.get()
+ bootstrapClassesIncorrectlyLoaded == []
}
def "logging works"() {

Fix AgentTestRunner bootstrapping