diff --git a/components/context/src/main/java/datadog/context/ContextBinder.java b/components/context/src/main/java/datadog/context/ContextBinder.java
index 385cca1f6c5..8a268028c52 100644
--- a/components/context/src/main/java/datadog/context/ContextBinder.java
+++ b/components/context/src/main/java/datadog/context/ContextBinder.java
@@ -29,9 +29,21 @@ public interface ContextBinder {
/**
* Requests use of a custom {@link ContextBinder}.
*
- * @param binder the binder to use (will replace any other binder in use).
+ *
Once the registered binder is used it cannot be replaced and this method will have no
+ * effect. To test different binders, make sure {@link #allowTesting()} is called early on.
+ *
+ * @param binder the binder to use.
*/
static void register(ContextBinder binder) {
ContextProviders.customBinder = binder;
}
+
+ /**
+ * Allow re-registration of custom {@link ContextBinder}s for testing.
+ *
+ * @return {@code true} if re-registration is allowed; otherwise {@code false}
+ */
+ static boolean allowTesting() {
+ return TestContextBinder.register();
+ }
}
diff --git a/components/context/src/main/java/datadog/context/ContextManager.java b/components/context/src/main/java/datadog/context/ContextManager.java
index 9c4a58cfa19..af0a2b9289a 100644
--- a/components/context/src/main/java/datadog/context/ContextManager.java
+++ b/components/context/src/main/java/datadog/context/ContextManager.java
@@ -28,9 +28,21 @@ public interface ContextManager {
/**
* Requests use of a custom {@link ContextManager}.
*
- * @param manager the manager to use (will replace any other manager in use).
+ *
Once the registered manager is used it cannot be replaced and this method will have no
+ * effect. To test different managers, make sure {@link #allowTesting()} is called early on.
+ *
+ * @param manager the manager to use.
*/
static void register(ContextManager manager) {
ContextProviders.customManager = manager;
}
+
+ /**
+ * Allow re-registration of custom {@link ContextManager}s for testing.
+ *
+ * @return {@code true} if re-registration is allowed; otherwise {@code false}
+ */
+ static boolean allowTesting() {
+ return TestContextManager.register();
+ }
}
diff --git a/components/context/src/main/java/datadog/context/ContextProviders.java b/components/context/src/main/java/datadog/context/ContextProviders.java
index 6895446947a..c4421b0a2ab 100644
--- a/components/context/src/main/java/datadog/context/ContextProviders.java
+++ b/components/context/src/main/java/datadog/context/ContextProviders.java
@@ -10,14 +10,14 @@ private static final class ProvidedManager {
static final ContextManager INSTANCE =
null != ContextProviders.customManager
? ContextProviders.customManager
- : new ThreadLocalContextManager();
+ : ThreadLocalContextManager.INSTANCE;
}
private static final class ProvidedBinder {
static final ContextBinder INSTANCE =
null != ContextProviders.customBinder
? ContextProviders.customBinder
- : new WeakMapContextBinder();
+ : WeakMapContextBinder.INSTANCE;
}
static ContextManager manager() {
diff --git a/components/context/src/main/java/datadog/context/TestContextBinder.java b/components/context/src/main/java/datadog/context/TestContextBinder.java
new file mode 100644
index 00000000000..e1fbbcf0a7f
--- /dev/null
+++ b/components/context/src/main/java/datadog/context/TestContextBinder.java
@@ -0,0 +1,39 @@
+package datadog.context;
+
+/** Test class that always delegates to the latest registered {@link ContextBinder}. */
+final class TestContextBinder implements ContextBinder {
+ private static final ContextBinder TEST_INSTANCE = new TestContextBinder();
+
+ private TestContextBinder() {}
+
+ static boolean register() {
+ // attempt to register before binder choice is locked, then check if we succeeded
+ ContextProviders.customBinder = TEST_INSTANCE;
+ return ContextProviders.binder() == TEST_INSTANCE;
+ }
+
+ @Override
+ public Context from(Object carrier) {
+ return delegate().from(carrier);
+ }
+
+ @Override
+ public void attachTo(Object carrier, Context context) {
+ delegate().attachTo(carrier, context);
+ }
+
+ @Override
+ public Context detachFrom(Object carrier) {
+ return delegate().detachFrom(carrier);
+ }
+
+ private static ContextBinder delegate() {
+ ContextBinder delegate = ContextProviders.customBinder;
+ if (delegate == TEST_INSTANCE) {
+ // fall back to default context binder
+ return WeakMapContextBinder.INSTANCE;
+ } else {
+ return delegate;
+ }
+ }
+}
diff --git a/components/context/src/main/java/datadog/context/TestContextManager.java b/components/context/src/main/java/datadog/context/TestContextManager.java
new file mode 100644
index 00000000000..9c60f4dc2e0
--- /dev/null
+++ b/components/context/src/main/java/datadog/context/TestContextManager.java
@@ -0,0 +1,39 @@
+package datadog.context;
+
+/** Test class that always delegates to the latest registered {@link ContextManager}. */
+final class TestContextManager implements ContextManager {
+ private static final ContextManager TEST_INSTANCE = new TestContextManager();
+
+ private TestContextManager() {}
+
+ static boolean register() {
+ // attempt to register before manager choice is locked, then check if we succeeded
+ ContextProviders.customManager = TEST_INSTANCE;
+ return ContextProviders.manager() == TEST_INSTANCE;
+ }
+
+ @Override
+ public Context current() {
+ return delegate().current();
+ }
+
+ @Override
+ public ContextScope attach(Context context) {
+ return delegate().attach(context);
+ }
+
+ @Override
+ public Context swap(Context context) {
+ return delegate().swap(context);
+ }
+
+ private static ContextManager delegate() {
+ ContextManager delegate = ContextProviders.customManager;
+ if (delegate == TEST_INSTANCE) {
+ // fall back to default context manager
+ return ThreadLocalContextManager.INSTANCE;
+ } else {
+ return delegate;
+ }
+ }
+}
diff --git a/components/context/src/main/java/datadog/context/ThreadLocalContextManager.java b/components/context/src/main/java/datadog/context/ThreadLocalContextManager.java
index 27c17445d14..0d652868e52 100644
--- a/components/context/src/main/java/datadog/context/ThreadLocalContextManager.java
+++ b/components/context/src/main/java/datadog/context/ThreadLocalContextManager.java
@@ -2,6 +2,8 @@
/** {@link ContextManager} that uses a {@link ThreadLocal} to track context per thread. */
final class ThreadLocalContextManager implements ContextManager {
+ static final ContextManager INSTANCE = new ThreadLocalContextManager();
+
private static final ThreadLocal CURRENT_HOLDER =
ThreadLocal.withInitial(() -> new Context[] {EmptyContext.INSTANCE});
diff --git a/components/context/src/main/java/datadog/context/WeakMapContextBinder.java b/components/context/src/main/java/datadog/context/WeakMapContextBinder.java
index eea3728fc8a..9b5fd24299e 100644
--- a/components/context/src/main/java/datadog/context/WeakMapContextBinder.java
+++ b/components/context/src/main/java/datadog/context/WeakMapContextBinder.java
@@ -11,6 +11,8 @@
/** {@link ContextBinder} that uses a global weak map of carriers to contexts. */
@ParametersAreNonnullByDefault
final class WeakMapContextBinder implements ContextBinder {
+ static final ContextBinder INSTANCE = new WeakMapContextBinder();
+
private static final Map