Merge pull request #527 from DataDog/ark/fix-agent-test-runner
Fix AgentTestRunner bootstrapping
This commit is contained in:
commit
64aed18f76
|
@ -6,7 +6,11 @@ import java.lang.reflect.Method;
|
|||
import java.net.URL;
|
||||
|
||||
public class Utils {
|
||||
/* packages which will be loaded on the bootstrap classloader*/
|
||||
/**
|
||||
* packages which will be loaded on the bootstrap classloader
|
||||
*
|
||||
* <p>Updates should be mirrored in TestUtils#BOOTSTRAP_PACKAGE_PREFIXES_COPY
|
||||
*/
|
||||
public static final String[] BOOTSTRAP_PACKAGE_PREFIXES = {
|
||||
"io.opentracing",
|
||||
"datadog.slf4j",
|
||||
|
@ -14,6 +18,7 @@ public class Utils {
|
|||
"datadog.trace.api",
|
||||
"datadog.trace.context"
|
||||
};
|
||||
|
||||
public static final String[] AGENT_PACKAGE_PREFIXES = {
|
||||
"datadog.trace.common",
|
||||
"datadog.trace.agent",
|
||||
|
|
|
@ -1,19 +1,10 @@
|
|||
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.lang.reflect.Method;
|
||||
import java.util.Arrays;
|
||||
import java.util.HashSet;
|
||||
import java.util.Set;
|
||||
|
@ -32,6 +23,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 +52,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();
|
||||
|
@ -61,6 +67,7 @@ public class SpockRunner extends Sputnik {
|
|||
throws InitializationError, NoSuchFieldException, SecurityException, IllegalArgumentException,
|
||||
IllegalAccessException {
|
||||
super(shadowTestClass(clazz));
|
||||
assertNoBootstrapClassesInTestClass(clazz);
|
||||
// access the classloader created in shadowTestClass above
|
||||
Field clazzField = Sputnik.class.getDeclaredField("clazz");
|
||||
try {
|
||||
|
@ -72,6 +79,37 @@ public class SpockRunner extends Sputnik {
|
|||
}
|
||||
}
|
||||
|
||||
private static void assertNoBootstrapClassesInTestClass(Class<?> testClass) {
|
||||
for (final Field field : testClass.getDeclaredFields()) {
|
||||
assertNotBootstrapClass(testClass, field.getType());
|
||||
}
|
||||
|
||||
for (final Method method : testClass.getDeclaredMethods()) {
|
||||
assertNotBootstrapClass(testClass, method.getReturnType());
|
||||
for (final Class paramType : method.getParameterTypes()) {
|
||||
assertNotBootstrapClass(testClass, paramType);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
private static void assertNotBootstrapClass(Class<?> testClass, Class<?> clazz) {
|
||||
if ((!clazz.isPrimitive()) && isBootstrapClass(clazz.getName())) {
|
||||
throw new IllegalStateException(
|
||||
testClass.getName()
|
||||
+ ": Bootstrap classes are not allowed in test class field or method signatures. Offending class: "
|
||||
+ clazz.getName());
|
||||
}
|
||||
}
|
||||
|
||||
private static boolean isBootstrapClass(String className) {
|
||||
for (int i = 0; i < TEST_BOOTSTRAP_PREFIXES.length; ++i) {
|
||||
if (className.startsWith(TEST_BOOTSTRAP_PREFIXES[i])) {
|
||||
return true;
|
||||
}
|
||||
}
|
||||
return false;
|
||||
}
|
||||
|
||||
// Shadow the test class with bytes loaded by InstrumentationClassLoader
|
||||
private static Class<?> shadowTestClass(final Class<?> clazz) {
|
||||
try {
|
||||
|
@ -100,56 +138,26 @@ 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])) {
|
||||
bootstrapClasses.add(info.getResourceName());
|
||||
break;
|
||||
}
|
||||
if (isBootstrapClass(info.getName())) {
|
||||
bootstrapClasses.add(info.getResourceName());
|
||||
}
|
||||
}
|
||||
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. */
|
||||
|
|
|
@ -1,7 +1,12 @@
|
|||
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,43 @@ 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.
|
||||
*/
|
||||
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);
|
||||
}
|
||||
}
|
||||
|
|
|
@ -1,6 +1,11 @@
|
|||
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.Span
|
||||
import io.opentracing.Tracer
|
||||
import spock.lang.Shared
|
||||
|
||||
import java.lang.reflect.Field
|
||||
|
||||
|
@ -8,10 +13,9 @@ class AgentTestRunnerTest extends AgentTestRunner {
|
|||
private static final ClassLoader BOOTSTRAP_CLASSLOADER = null
|
||||
private static final ClassLoader OT_LOADER
|
||||
private static final boolean AGENT_INSTALLED_IN_CLINIT
|
||||
// having opentracing class in test field should not cause problems
|
||||
private static final Tracer A_TRACER = null
|
||||
// having dd tracer api class in test field should not cause problems
|
||||
private static final datadog.trace.api.Tracer DD_API_TRACER = null
|
||||
|
||||
@Shared
|
||||
private Class sharedSpanClass
|
||||
|
||||
static {
|
||||
// when test class initializes, opentracing should be set up, but not the agent.
|
||||
|
@ -19,16 +23,39 @@ class AgentTestRunnerTest extends AgentTestRunner {
|
|||
AGENT_INSTALLED_IN_CLINIT = getAgentTransformer() != null
|
||||
}
|
||||
|
||||
def "classpath setup"() {
|
||||
def setupSpec() {
|
||||
sharedSpanClass = Span
|
||||
}
|
||||
|
||||
def "spock runner bootstrap prefixes correct for test setup"() {
|
||||
expect:
|
||||
A_TRACER == null
|
||||
DD_API_TRACER == null
|
||||
OT_LOADER == BOOTSTRAP_CLASSLOADER
|
||||
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:
|
||||
// shared OT classes should cause no trouble
|
||||
sharedSpanClass.getClassLoader() == BOOTSTRAP_CLASSLOADER
|
||||
Tracer.getClassLoader() == 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"() {
|
||||
|
|
|
@ -5,7 +5,8 @@ minimumInstructionCoverage = 0.6
|
|||
excludedClassesConverage += [
|
||||
'datadog.trace.agent.test.asserts.*Assert',
|
||||
'datadog.trace.agent.test.AgentTestRunner.ErrorCountingListener',
|
||||
'datadog.trace.agent.test.OkHttpUtils'
|
||||
'datadog.trace.agent.test.OkHttpUtils',
|
||||
'datadog.trace.agent.test.TestUtils'
|
||||
]
|
||||
|
||||
dependencies {
|
||||
|
|
Loading…
Reference in New Issue