Skip to content

Commit

Permalink
feat(context): Add API test coverage
Browse files Browse the repository at this point in the history
  • Loading branch information
PerfectSlayer committed Jan 6, 2025
1 parent 28d6fd6 commit af5d4e6
Show file tree
Hide file tree
Showing 4 changed files with 286 additions and 11 deletions.
Original file line number Diff line number Diff line change
Expand Up @@ -19,7 +19,7 @@ public class Concern {
* @param name the concern name, for debugging purpose only.
* @return The created concern.
*/
public static Concern create(String name) {
public static Concern named(String name) {
return new Concern(name, DEFAULT_PRIORITY);
}

Expand All @@ -31,13 +31,15 @@ public static Concern create(String name) {
* {@link #DEFAULT_PRIORITY}),
* @return The created concern.
*/
public static Concern createWithPriority(String name, int priority) {
public static Concern withPriority(String name, int priority) {
return new Concern(name, priority);
}

private Concern(String name, int priority) {
requireNonNull(name, "Concern name cannot be null");
assert priority >= 0 : "Concern priority cannot be negative";
if (priority < 0) {
throw new IllegalArgumentException("Concern priority cannot be negative");
}
this.name = name;
this.priority = priority;
}
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -9,7 +9,7 @@
public final class Propagators {
private static final Map<Concern, Propagator> PROPAGATORS = synchronizedMap(new HashMap<>());
private static volatile Propagator defaultPropagator = null;
private static volatile boolean isDefaultPropagatorSet = false;
private static volatile boolean defaultPropagatorSet = false;

private Propagators() {}

Expand All @@ -19,14 +19,14 @@ private Propagators() {}
* @return The default propagator.
*/
public static Propagator defaultPropagator() {
if (!isDefaultPropagatorSet) {
if (!defaultPropagatorSet) {
Propagator[] propagatorsByPriority =
PROPAGATORS.entrySet().stream()
.sorted(comparingInt(entry -> entry.getKey().priority()))
.map(Map.Entry::getValue)
.toArray(Propagator[]::new);
defaultPropagator = composite(propagatorsByPriority);
isDefaultPropagatorSet = true;
defaultPropagatorSet = true;
}
return defaultPropagator;
}
Expand All @@ -37,21 +37,21 @@ public static Propagator defaultPropagator() {
* @param concern the concern to get propagator for.
* @return the related propagator if registered, a {@link #noop()} propagator otherwise.
*/
public static Propagator propagatorFor(Concern concern) {
public static Propagator forConcern(Concern concern) {
return PROPAGATORS.getOrDefault(concern, NoopPropagator.INSTANCE);
}

/**
* Gets the propagator for given concerns.
* Gets the propagator for the given concerns.
*
* @param concerns the concerns to get propagators for.
* @return A propagator that will apply the concern propagators if registered, in the given
* concern order.
*/
public static Propagator propagatorsFor(Concern... concerns) {
public static Propagator forConcerns(Concern... concerns) {
Propagator[] propagators = new Propagator[concerns.length];
for (int i = 0; i < concerns.length; i++) {
propagators[i] = propagatorFor(concerns[i]);
propagators[i] = forConcern(concerns[i]);
}
return composite(propagators);
}
Expand All @@ -72,7 +72,13 @@ public static Propagator noop() {
* @return the composite propagator that will apply the propagators in their given order.
*/
public static Propagator composite(Propagator... propagators) {
return new CompositePropagator(propagators);
if (propagators.length == 0) {
return NoopPropagator.INSTANCE;
} else if (propagators.length == 1) {
return propagators[0];
} else {
return new CompositePropagator(propagators);
}
}

/**
Expand All @@ -83,5 +89,12 @@ public static Propagator composite(Propagator... propagators) {
*/
public static void register(Concern concern, Propagator propagator) {
PROPAGATORS.put(concern, propagator);
defaultPropagatorSet = false;
}

/** Clear all registered propagators. For testing purpose only. */
static void reset() {
PROPAGATORS.clear();
defaultPropagatorSet = false;
}
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,38 @@
package datadog.context.propagation;

import static datadog.context.propagation.Concern.DEFAULT_PRIORITY;
import static org.junit.jupiter.api.Assertions.*;

import org.junit.jupiter.api.Test;

class ConcernTest {
@Test
void testNamed() {
assertThrows(
NullPointerException.class,
() -> Concern.named(null),
"Should not create null named concern");
assertNotNull(Concern.named("name"));
}

@Test
void testWithPriority() {
assertThrows(
NullPointerException.class,
() -> Concern.withPriority(null, DEFAULT_PRIORITY),
"Should not create null named concern");
assertThrows(
IllegalArgumentException.class,
() -> Concern.withPriority("name", -1),
"Should not create negative priority concern");
assertNotNull(Concern.withPriority("high-priority", DEFAULT_PRIORITY - 10));
assertNotNull(Concern.withPriority("low-priority", DEFAULT_PRIORITY + 10));
}

@Test
void testName() {
String debugName = "name";
Concern concern = Concern.named(debugName);
assertEquals(debugName, concern.toString(), "Concern name mismatch");
}
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,222 @@
package datadog.context.propagation;

import static datadog.context.Context.root;
import static datadog.context.propagation.Concern.DEFAULT_PRIORITY;
import static java.util.Objects.requireNonNull;
import static org.junit.jupiter.api.Assertions.assertEquals;
import static org.junit.jupiter.api.Assertions.assertFalse;
import static org.junit.jupiter.api.Assertions.assertNotNull;
import static org.junit.jupiter.api.Assertions.assertTrue;

import datadog.context.Context;
import datadog.context.ContextKey;
import java.util.HashMap;
import java.util.Map;
import java.util.function.BiConsumer;
import javax.annotation.Nullable;
import org.junit.jupiter.api.AfterEach;
import org.junit.jupiter.api.BeforeEach;
import org.junit.jupiter.api.Test;

class PropagatorsTest {
static final MapCarrierAccessor ACCESSOR = new MapCarrierAccessor();

static final Concern TRACING = Concern.named("tracing");
static final ContextKey<String> TRACING_KEY = ContextKey.named("tracing");
static final Propagator TRACING_PROPAGATOR = new BasicPropagator(TRACING_KEY, "tracing");

static final Concern IAST = Concern.named("iast");
static final ContextKey<String> IAST_KEY = ContextKey.named("iast");
static final Propagator IAST_PROPAGATOR = new BasicPropagator(IAST_KEY, "iast");

static final Concern DEBUGGER = Concern.withPriority("debugger", DEFAULT_PRIORITY - 10);
static final ContextKey<String> DEBUGGER_KEY = ContextKey.named("debugger");
static final DependentPropagator DEBUGGER_PROPAGATOR =
new DependentPropagator(DEBUGGER_KEY, "debugger", TRACING_KEY);

static final Concern PROFILING = Concern.withPriority("profiling", DEFAULT_PRIORITY + 10);
static final ContextKey<String> PROFILING_KEY = ContextKey.named("profiling");
static final DependentPropagator PROFILING_PROPAGATOR =
new DependentPropagator(PROFILING_KEY, "profiling", TRACING_KEY);

static final Context CONTEXT =
root()
.with(TRACING_KEY, "sampled")
.with(IAST_KEY, "standalone")
.with(DEBUGGER_KEY, "debug")
.with(PROFILING_KEY, "profile");

static class MapCarrierAccessor
implements CarrierSetter<Map<String, String>>, CarrierVisitor<Map<String, String>> {
@Override
public void set(@Nullable Map<String, String> carrier, String key, String value) {
if (carrier != null && key != null) {
carrier.put(key, value);
}
}

@Override
public void forEachKeyValue(Map<String, String> carrier, BiConsumer<String, String> visitor) {
carrier.forEach(visitor);
}
}

static class BasicPropagator implements Propagator {
private final ContextKey<String> contextKey;
private final String carrierKey;

public BasicPropagator(ContextKey<String> contextKey, String carrierKey) {
this.contextKey = requireNonNull(contextKey);
this.carrierKey = requireNonNull(carrierKey);
}

@Override
public <C> void inject(Context context, C carrier, CarrierSetter<C> setter) {
String value = context.get(this.contextKey);
if (value != null) {
setter.set(carrier, this.carrierKey, value);
}
}

@Override
public <C> Context extract(Context context, C carrier, CarrierVisitor<C> visitor) {
String[] valueRef = new String[1];
visitor.forEachKeyValue(
carrier,
(key, value) -> {
if (this.carrierKey.equals(key)) {
valueRef[0] = value;
}
});
if (valueRef[0] != null) {
context = context.with(this.contextKey, valueRef[0]);
}
return context;
}
}

static class DependentPropagator extends BasicPropagator implements Propagator {
private final ContextKey<String> requiredContextKey;
private boolean keyFound;

public DependentPropagator(
ContextKey<String> contextKey, String carrierKey, ContextKey<String> requiredContextKey) {
super(contextKey, carrierKey);
this.requiredContextKey = requiredContextKey;
this.keyFound = false;
}

@Override
public <C> Context extract(Context context, C carrier, CarrierVisitor<C> visitor) {
this.keyFound = context.get(this.requiredContextKey) != null;
return super.extract(context, carrier, visitor);
}

public void reset() {
this.keyFound = false;
}
}

@BeforeEach
@AfterEach
void resetPropagators() {
Propagators.reset();
DEBUGGER_PROPAGATOR.reset();
PROFILING_PROPAGATOR.reset();
}

@Test
void testDefaultPropagator() {
Propagator noopPropagator = Propagators.defaultPropagator();
assertNotNull(
noopPropagator, "Default propagator should not be null when no propagator is registered");
assertInjectExtractContext(CONTEXT, noopPropagator);

Propagators.register(TRACING, TRACING_PROPAGATOR);
Propagator single = Propagators.defaultPropagator();
assertInjectExtractContext(CONTEXT, single, TRACING_KEY);

Propagators.register(IAST, IAST_PROPAGATOR);
Propagators.register(DEBUGGER, DEBUGGER_PROPAGATOR);
Propagators.register(PROFILING, PROFILING_PROPAGATOR);
Propagator composite = Propagators.defaultPropagator();
assertInjectExtractContext(
CONTEXT, composite, TRACING_KEY, IAST_KEY, DEBUGGER_KEY, PROFILING_KEY);
assertFalse(
DEBUGGER_PROPAGATOR.keyFound,
"Debugger propagator should have run before tracing propagator");
assertTrue(
PROFILING_PROPAGATOR.keyFound,
"Profiling propagator should have run after tracing propagator");

Propagator cached = Propagators.defaultPropagator();
assertEquals(composite, cached, "default propagator should be cached");
}

@Test
void testForConcern() {
// Test when not registered
Propagator propagator = Propagators.forConcern(TRACING);
assertNotNull(propagator, "Propagator should not be null when no propagator is registered");
assertNoopPropagator(propagator);
// Test when registered
Propagators.register(TRACING, TRACING_PROPAGATOR);
propagator = Propagators.forConcern(TRACING);
assertNotNull(propagator, "Propagator should not be null when registered");
assertInjectExtractContext(CONTEXT, propagator, TRACING_KEY);
}

@Test
void testForConcerns() {
// Test when none registered
Propagator propagator = Propagators.forConcerns(TRACING, IAST);
assertNotNull(propagator, "Propagator should not be null when no propagator is registered");
assertNoopPropagator(propagator);
// Test when only one is registered
Propagators.register(TRACING, TRACING_PROPAGATOR);
propagator = Propagators.forConcerns(TRACING, IAST);
assertNotNull(propagator, "Propagator should not be null when one is registered");
assertInjectExtractContext(CONTEXT, propagator, TRACING_KEY);
// Test when all registered
Propagators.register(IAST, IAST_PROPAGATOR);
propagator = Propagators.forConcerns(TRACING, IAST);
assertNotNull(propagator, "Propagator should not be null when all are registered");
assertInjectExtractContext(CONTEXT, propagator, TRACING_KEY, IAST_KEY);
// Test propagator order follow the given concerns order despite concern priority
Propagators.register(DEBUGGER, DEBUGGER_PROPAGATOR);
Propagators.register(PROFILING, PROFILING_PROPAGATOR);
propagator = Propagators.forConcerns(PROFILING, TRACING, DEBUGGER);
assertInjectExtractContext(CONTEXT, propagator, PROFILING_KEY, TRACING_KEY, DEBUGGER_KEY);
assertFalse(
PROFILING_PROPAGATOR.keyFound,
"Profiling propagator should have run before tracing propagator");
assertTrue(
DEBUGGER_PROPAGATOR.keyFound,
"Debugger propagator should have run before tracing propagator");
}

@Test
void testNoopPropagator() {
Propagator noopPropagator = Propagators.noop();
assertNotNull(noopPropagator, "noop propagator should not be null");
assertNoopPropagator(noopPropagator);
}

void assertNoopPropagator(Propagator noopPropagator) {
Map<String, String> carrier = new HashMap<>();
noopPropagator.inject(CONTEXT, carrier, ACCESSOR);
assertTrue(carrier.isEmpty(), "carrier should be empty");
Context extracted = noopPropagator.extract(root(), carrier, ACCESSOR);
assertEquals(root(), extracted, "extracted context should be empty");
}

void assertInjectExtractContext(Context context, Propagator propagator, ContextKey<?>... keys) {
Map<String, String> carrier = new HashMap<>();
propagator.inject(context, carrier, ACCESSOR);
Context extracted = propagator.extract(root(), carrier, ACCESSOR);
for (ContextKey<?> key : keys) {
assertEquals(
context.get(key), extracted.get(key), "Key " + key + " not injected nor extracted");
}
}
}

0 comments on commit af5d4e6

Please sign in to comment.