More fixes.
Notably added a transformer to make config easier to test with by making INSTANCE public static volatile.
MODIFY
dd-java-agent/instrumentation/akka-http-10.0/src/test/groovy/AkkaHttpClientInstrumentationTest.groovy
dd-java-agent/instrumentation/akka-http-10.0/src/test/groovy/AkkaHttpClientInstrumentationTest.groovy
@@ -28,6 +28,7 @@ class AkkaHttpClientInstrumentationTest extends HttpClientTest<AkkaHttpClientDec
.addHeaders(headers.collect { RawHeader.create(it.key, it.value) })
def response = Http.get(system).singleRequest(request, materializer).toCompletableFuture().get()
+ blockUntilChildSpansFinished(1)
callback?.call()
return response.status().intValue()
}

MODIFY
dd-java-agent/instrumentation/jax-rs-client/src/test/groovy/JaxRsClientAsyncTest.groovy
dd-java-agent/instrumentation/jax-rs-client/src/test/groovy/JaxRsClientAsyncTest.groovy
@@ -3,6 +3,7 @@ import datadog.trace.instrumentation.jaxrs.JaxRsClientDecorator
import javax.ws.rs.client.AsyncInvoker
import javax.ws.rs.client.Client
import javax.ws.rs.client.ClientBuilder
+import javax.ws.rs.client.Entity
import javax.ws.rs.client.WebTarget
import javax.ws.rs.core.MediaType
import javax.ws.rs.core.Response
@@ -20,7 +21,8 @@ abstract class JaxRsClientAsyncTest extends HttpClientTest<JaxRsClientDecorator>
headers.each { builder.header(it.key, it.value) }
AsyncInvoker request = builder.async()
- Response response = request.method(method).get()
+ def body = BODY_METHODS.contains(method) ? Entity.text("") : null
+ Response response = request.method(method, (Entity) body).get()
callback?.call()
return response.status

MODIFY
dd-java-agent/instrumentation/jax-rs-client/src/test/groovy/JaxRsClientTest.groovy
dd-java-agent/instrumentation/jax-rs-client/src/test/groovy/JaxRsClientTest.groovy
@@ -2,6 +2,7 @@ import datadog.trace.agent.test.base.HttpClientTest
import datadog.trace.instrumentation.jaxrs.JaxRsClientDecorator
import javax.ws.rs.client.Client
import javax.ws.rs.client.ClientBuilder
+import javax.ws.rs.client.Entity
import javax.ws.rs.client.Invocation
import javax.ws.rs.client.WebTarget
import javax.ws.rs.core.MediaType
@@ -19,7 +20,8 @@ abstract class JaxRsClientTest extends HttpClientTest<JaxRsClientDecorator> {
WebTarget service = client.target(uri)
Invocation.Builder request = service.request(MediaType.TEXT_PLAIN)
headers.each { request.header(it.key, it.value) }
- Response response = request.method(method)
+ def body = BODY_METHODS.contains(method) ? Entity.text("") : null
+ Response response = request.method(method, (Entity) body)
callback?.call()
return response.status

MODIFY
dd-java-agent/testing/src/main/groovy/datadog/trace/agent/test/AgentTestRunner.java
dd-java-agent/testing/src/main/groovy/datadog/trace/agent/test/AgentTestRunner.java
package datadog.trace.agent.test;
+import static net.bytebuddy.matcher.ElementMatchers.named;
+import static net.bytebuddy.matcher.ElementMatchers.none;
+
import ch.qos.logback.classic.Level;
import ch.qos.logback.classic.Logger;
import com.google.common.collect.Sets;
@@ -10,6 +13,7 @@ import datadog.trace.agent.test.asserts.ListWriterAssert;
import datadog.trace.agent.test.utils.GlobalTracerUtils;
import datadog.trace.agent.tooling.AgentInstaller;
import datadog.trace.agent.tooling.Instrumenter;
+import datadog.trace.api.Config;
import datadog.trace.api.GlobalTracer;
import datadog.trace.common.writer.ListWriter;
import datadog.trace.common.writer.Writer;
@@ -20,6 +24,8 @@ import groovy.transform.stc.SimpleType;
import io.opentracing.Tracer;
import java.lang.instrument.ClassFileTransformer;
import java.lang.instrument.Instrumentation;
+import java.lang.reflect.Field;
+import java.lang.reflect.Modifier;
import java.util.List;
import java.util.ServiceLoader;
import java.util.Set;
@@ -28,8 +34,14 @@ import java.util.concurrent.atomic.AtomicInteger;
import lombok.extern.slf4j.Slf4j;
import net.bytebuddy.agent.ByteBuddyAgent;
import net.bytebuddy.agent.builder.AgentBuilder;
+import net.bytebuddy.agent.builder.ResettableClassFileTransformer;
+import net.bytebuddy.description.modifier.FieldManifestation;
+import net.bytebuddy.description.modifier.Ownership;
+import net.bytebuddy.description.modifier.Visibility;
import net.bytebuddy.description.type.TypeDescription;
+import net.bytebuddy.dynamic.ClassFileLocator;
import net.bytebuddy.dynamic.DynamicType;
+import net.bytebuddy.dynamic.Transformer;
import net.bytebuddy.utility.JavaModule;
import org.junit.AfterClass;
import org.junit.Before;
@@ -83,6 +95,12 @@ public abstract class AgentTestRunner extends Specification {
((Logger) LoggerFactory.getLogger(Logger.ROOT_LOGGER_NAME)).setLevel(Level.WARN);
((Logger) LoggerFactory.getLogger("datadog")).setLevel(Level.DEBUG);
+ try {
+ makeConfigInstanceModifiable();
+ } catch (final Exception e) {
+ throw new RuntimeException(e);
+ }
+
TEST_WRITER =
new ListWriter() {
@Override
@@ -188,6 +206,44 @@ public abstract class AgentTestRunner extends Specification {
: INSTRUMENTATION_ERROR_COUNT.get() + " Instrumentation errors during test";
}
+ private static void makeConfigInstanceModifiable()
+ throws ClassNotFoundException, NoSuchFieldException {
+ final ResettableClassFileTransformer transformer =
+ new AgentBuilder.Default()
+ // Config is injected into the bootstrap, so we need to provide a locator.
+ .with(
+ new AgentBuilder.LocationStrategy.Simple(
+ ClassFileLocator.ForClassLoader.ofSystemLoader()))
+ .ignore(none()) // Allow transforming boostrap classes
+ .type(named("datadog.trace.api.Config"))
+ .transform(
+ new AgentBuilder.Transformer() {
+ @Override
+ public DynamicType.Builder<?> transform(
+ final DynamicType.Builder<?> builder,
+ final TypeDescription typeDescription,
+ final ClassLoader classLoader,
+ final JavaModule module) {
+ // Add transformer to modify INSTANCE field access.
+ return builder
+ .field(named("INSTANCE"))
+ .transform(
+ Transformer.ForField.withModifiers(
+ Visibility.PUBLIC, Ownership.STATIC, FieldManifestation.VOLATILE));
+ }
+ })
+ .installOn(INSTRUMENTATION);
+
+ final Field field = Config.class.getDeclaredField("INSTANCE");
+ assert Modifier.isPublic(field.getModifiers());
+ assert Modifier.isStatic(field.getModifiers());
+ assert Modifier.isVolatile(field.getModifiers());
+ assert !Modifier.isFinal(field.getModifiers());
+
+ // No longer needed (Unless class gets retransformed somehow).
+ INSTRUMENTATION.removeTransformer(transformer);
+ }
+
public static void assertTraces(
final int size,
@ClosureParams(

MODIFY
dd-java-agent/testing/src/main/groovy/datadog/trace/agent/test/base/HttpClientTest.groovy
dd-java-agent/testing/src/main/groovy/datadog/trace/agent/test/base/HttpClientTest.groovy
@@ -68,7 +68,7 @@ abstract class HttpClientTest<T extends HttpClientDecorator> extends AgentTestRu
}
@Unroll
- def "basic #method request #url"() {
+ def "basic #method request #url - tagQueryString=#tagQueryString"() {
when:
def status = withConfigOverride(Config.HTTP_CLIENT_TAG_QUERY_STRING, "$tagQueryString") {
doRequest(method, url)

MODIFY
dd-java-agent/testing/src/main/groovy/datadog/trace/agent/test/utils/TraceUtils.groovy
dd-java-agent/testing/src/main/groovy/datadog/trace/agent/test/utils/TraceUtils.groovy
@@ -7,11 +7,12 @@ import io.opentracing.Scope
import io.opentracing.util.GlobalTracer
import lombok.SneakyThrows
-import java.lang.reflect.Field
import java.lang.reflect.Modifier
import java.util.concurrent.Callable
class TraceUtils {
+ static final def CONFIG_INSTANCE_FIELD = Config.getDeclaredField("INSTANCE")
+
private static final BaseDecorator DECORATOR = new BaseDecorator() {
protected String[] instrumentationNames() {
return new String[0]
@@ -61,14 +62,21 @@ class TraceUtils {
@SneakyThrows
synchronized static <T extends Object> Object withConfigOverride(final String name, final String value, final Callable<T> r) {
- def existingConfig = Config.get() // We can't reference INSTANCE directly or the reflection below will fail.
+ // Ensure the class was retransformed properly in AgentTestRunner.makeConfigInstanceModifiable()
+ assert Modifier.isPublic(CONFIG_INSTANCE_FIELD.getModifiers())
+ assert Modifier.isStatic(CONFIG_INSTANCE_FIELD.getModifiers())
+ assert Modifier.isVolatile(CONFIG_INSTANCE_FIELD.getModifiers())
+ assert !Modifier.isFinal(CONFIG_INSTANCE_FIELD.getModifiers())
+
+ def existingConfig = Config.get()
Properties properties = new Properties()
properties.put(name, value)
- setFinalStatic(Config.getDeclaredField("INSTANCE"), new Config(properties, existingConfig))
+ CONFIG_INSTANCE_FIELD.set(null, new Config(properties, existingConfig))
+ assert Config.get() != existingConfig
try {
return r.call()
} finally {
- setFinalStatic(Config.getDeclaredField("INSTANCE"), existingConfig)
+ CONFIG_INSTANCE_FIELD.set(null, existingConfig)
}
}
@@ -76,20 +84,12 @@ class TraceUtils {
* Calling will reset the runtimeId too, so it might cause problems around runtimeId verification.
*/
static void resetConfig() {
- setFinalStatic(Config.getDeclaredField("INSTANCE"), new Config())
- }
-
- private static void setFinalStatic(final Field field, final Object newValue) throws Exception {
- setFinal(field, null, newValue)
- }
-
- private static void setFinal(final Field field, final Object instance, final Object newValue) throws Exception {
- field.setAccessible(true)
-
- final Field modifiersField = Field.getDeclaredField("modifiers")
- modifiersField.setAccessible(true)
- modifiersField.setInt(field, field.getModifiers() & ~Modifier.FINAL)
+ // Ensure the class was retransformed properly in AgentTestRunner.makeConfigInstanceModifiable()
+ assert Modifier.isPublic(CONFIG_INSTANCE_FIELD.getModifiers())
+ assert Modifier.isStatic(CONFIG_INSTANCE_FIELD.getModifiers())
+ assert Modifier.isVolatile(CONFIG_INSTANCE_FIELD.getModifiers())
+ assert !Modifier.isFinal(CONFIG_INSTANCE_FIELD.getModifiers())
- field.set(instance, newValue)
+ CONFIG_INSTANCE_FIELD.set(null, new Config())
}
}

More fixes.

Notably added a transformer to make config easier to test with by making INSTANCE public static volatile.