CtrlK
BlogDocsLog inGet started
Tessl Logo

tessl/maven-io-opentelemetry--opentelemetry-sdk-testing

OpenTelemetry SDK Testing utilities providing comprehensive testing support for OpenTelemetry Java SDK applications with AssertJ assertions, in-memory exporters, and JUnit integration.

Pending
Overview
Eval results
Files

utilities.mddocs/

Time and Context Utilities

Utilities for controlling time during tests and managing context storage for test isolation and deterministic behavior. These utilities enable precise control over temporal aspects of telemetry and context propagation during testing.

Capabilities

Test Clock

Controllable clock implementation that allows manipulation of time during tests for deterministic and predictable timing behavior.

@ThreadSafe
class TestClock implements Clock {
    // Factory methods
    static TestClock create();
    static TestClock create(Instant instant);
    
    // Time manipulation
    synchronized void setTime(Instant instant);
    synchronized void advance(Duration duration);
    synchronized void advance(long duration, TimeUnit unit);
    
    // Clock interface methods
    synchronized long now();
    synchronized long nanoTime();
}

Usage examples:

// Create a test clock with current time
TestClock clock = TestClock.create();

// Create a test clock with specific time
TestClock fixedClock = TestClock.create(Instant.parse("2023-01-01T12:00:00Z"));

// Use in OpenTelemetry configuration
OpenTelemetrySdk sdk = OpenTelemetrySdk.builder()
    .setTracerProvider(
        SdkTracerProvider.builder()
            .setClock(clock)
            .build())
    .build();

// Control time during test execution
Tracer tracer = sdk.getTracer("test");

// Set specific start time
clock.setTime(Instant.parse("2023-01-01T10:00:00Z"));
Span span = tracer.spanBuilder("test-span").startSpan();

// Advance time and end span
clock.advance(Duration.ofSeconds(5));
span.end();

// Verify timing
List<SpanData> spans = spanExporter.getFinishedSpanItems();
assertThat(spans.get(0))
    .startsAt(Instant.parse("2023-01-01T10:00:00Z"))
    .endsAt(Instant.parse("2023-01-01T10:00:05Z"));

Advanced time control examples:

@Test
void testTimeBasedBehavior() {
    TestClock clock = TestClock.create(Instant.parse("2023-01-01T00:00:00Z"));
    
    // Configure service with test clock
    MyTimedService service = new MyTimedService(openTelemetry, clock);
    
    // Test behavior at specific times
    service.performScheduledTask(); // Task at midnight
    
    // Advance to next hour
    clock.advance(Duration.ofHours(1));
    service.performScheduledTask(); // Task at 1 AM
    
    // Verify time-based metrics
    assertThat(metricReader.collectAllMetrics())
        .extracting(MetricData::getName)
        .contains("scheduled.task.executions")
        .hasSize(2);
}

@Test
void testTimeoutBehavior() {
    TestClock clock = TestClock.create();
    TimeoutService service = new TimeoutService(openTelemetry, clock);
    
    // Start an operation with 30-second timeout
    clock.setTime(Instant.parse("2023-01-01T12:00:00Z"));
    service.startLongRunningOperation();
    
    // Advance past timeout
    clock.advance(Duration.ofSeconds(35));
    service.checkForTimeouts();
    
    // Verify timeout span
    assertThat(spanExporter.getFinishedSpanItems())
        .first()
        .hasName("long.running.operation")
        .hasStatusSatisfying(status -> 
            status.hasCode(StatusCode.ERROR)
                  .hasDescription("Operation timed out")
        );
}

@Test
void testMetricCollectionTiming() {
    TestClock clock = TestClock.create();
    
    // Configure metric reader with test clock
    InMemoryMetricReader reader = InMemoryMetricReader.builder()
        .setClock(clock)
        .build();
    
    SdkMeterProvider meterProvider = SdkMeterProvider.builder()
        .registerMetricReader(reader)
        .setClock(clock)
        .build();
        
    Meter meter = meterProvider.get("test");
    LongCounter counter = meter.counterBuilder("test.counter").build();
    
    // Record metrics at specific times
    clock.setTime(Instant.parse("2023-01-01T12:00:00Z"));
    counter.add(10);
    
    clock.advance(Duration.ofMinutes(1));
    counter.add(5);
    
    // Collect metrics and verify timestamps
    Collection<MetricData> metrics = reader.collectAllMetrics();
    // Verify metric timestamps align with controlled time
}

Context Storage Provider

Configurable context storage provider that allows switching context storage implementations during tests for testing context propagation and isolation.

class SettableContextStorageProvider implements ContextStorageProvider {
    // Static configuration methods
    static void setContextStorage(ContextStorage storage);
    static ContextStorage getContextStorage();
    
    // ContextStorageProvider interface method
    ContextStorage get();
}

Usage examples:

// Configure context storage for testing
@BeforeEach
void setUp() {
    // Set up thread-local context storage for isolation
    SettableContextStorageProvider.setContextStorage(
        ContextStorage.defaultStorage()
    );
}

@Test
void testContextPropagation() {
    OpenTelemetry openTelemetry = otelTesting.getOpenTelemetry();
    Tracer tracer = openTelemetry.getTracer("test");
    
    // Create parent span
    Span parentSpan = tracer.spanBuilder("parent").startSpan();
    
    try (Scope scope = parentSpan.makeCurrent()) {
        // Context should propagate to child operations
        MyService service = new MyService(openTelemetry);
        service.performOperation(); // Should create child span
        
        // Verify parent-child relationship
        assertThat(spanExporter.getFinishedSpanItems())
            .hasSize(1)
            .first()
            .hasParent(parentSpan);
    } finally {
        parentSpan.end();
    }
}

@Test
void testContextIsolation() {
    // Test that contexts don't leak between operations
    OpenTelemetry openTelemetry = otelTesting.getOpenTelemetry();
    Tracer tracer = openTelemetry.getTracer("test");
    
    // First operation with its own context
    Span span1 = tracer.spanBuilder("operation-1").startSpan();
    try (Scope scope1 = span1.makeCurrent()) {
        performFirstOperation();
    } finally {
        span1.end();
    }
    
    // Second operation should not see first operation's context
    Span span2 = tracer.spanBuilder("operation-2").startSpan();
    try (Scope scope2 = span2.makeCurrent()) {
        performSecondOperation();
    } finally {
        span2.end();
    }
    
    // Verify no cross-contamination
    List<SpanData> spans = spanExporter.getFinishedSpanItems();
    assertThat(spans).hasSize(2);
    assertThat(spans.get(0)).hasNoParent();
    assertThat(spans.get(1)).hasNoParent();
}

// Custom context storage for advanced testing scenarios
@Test
void testCustomContextStorage() {
    // Create custom context storage that tracks access
    TrackingContextStorage trackingStorage = new TrackingContextStorage();
    SettableContextStorageProvider.setContextStorage(trackingStorage);
    
    OpenTelemetry openTelemetry = otelTesting.getOpenTelemetry();
    Tracer tracer = openTelemetry.getTracer("test");
    
    Span span = tracer.spanBuilder("tracked-operation").startSpan();
    try (Scope scope = span.makeCurrent()) {
        // Perform operations that should access context
        performTrackedOperation();
    } finally {
        span.end();
    }
    
    // Verify context access patterns
    assertThat(trackingStorage.getAccessCount()).isGreaterThan(0);
    assertThat(trackingStorage.getLastAccessedContext()).isNotNull();
}

Integration with Other Testing Utilities

Examples showing how time and context utilities work together with other testing components:

@Test
void testCompleteTemporalScenario() {
    // Set up controlled time and context
    TestClock clock = TestClock.create(Instant.parse("2023-01-01T09:00:00Z"));
    SettableContextStorageProvider.setContextStorage(ContextStorage.defaultStorage());
    
    // Configure OpenTelemetry with test utilities
    InMemorySpanExporter spanExporter = InMemorySpanExporter.create();
    InMemoryMetricReader metricReader = InMemoryMetricReader.create();
    
    OpenTelemetrySdk sdk = OpenTelemetrySdk.builder()
        .setTracerProvider(
            SdkTracerProvider.builder()
                .setClock(clock)
                .addSpanProcessor(BatchSpanProcessor.builder(spanExporter).build())
                .build())
        .setMeterProvider(
            SdkMeterProvider.builder()
                .setClock(clock)
                .registerMetricReader(metricReader)
                .build())
        .build();
    
    // Simulate time-based business logic
    TimedBusinessService service = new TimedBusinessService(sdk, clock);
    
    // Execute operation at 9:00 AM
    service.startDailyProcessing();
    
    // Advance to 9:30 AM
    clock.advance(Duration.ofMinutes(30));
    service.performMidProcessingCheck();
    
    // Advance to 10:00 AM
    clock.advance(Duration.ofMinutes(30));
    service.completeDailyProcessing();
    
    // Verify temporal correctness
    List<SpanData> spans = spanExporter.getFinishedSpanItems();
    assertThat(spans)
        .extracting(SpanData::getName)
        .containsExactly(
            "daily.processing.start",
            "mid.processing.check", 
            "daily.processing.complete"
        );
    
    // Verify timing relationships
    assertThat(spans.get(0)).startsAt(Instant.parse("2023-01-01T09:00:00Z"));
    assertThat(spans.get(1)).startsAt(Instant.parse("2023-01-01T09:30:00Z"));
    assertThat(spans.get(2)).startsAt(Instant.parse("2023-01-01T10:00:00Z"));
    
    // Verify metrics collected at correct times
    Collection<MetricData> metrics = metricReader.collectAllMetrics();
    assertThat(metrics)
        .filteredOn(metric -> "processing.duration".equals(metric.getName()))
        .hasSize(1);
}

@Test
void testConcurrentContextIsolation() {
    TestClock clock = TestClock.create();
    
    // Set up concurrent context isolation
    SettableContextStorageProvider.setContextStorage(
        new ThreadLocalContextStorage()
    );
    
    OpenTelemetry openTelemetry = otelTesting.getOpenTelemetry();
    
    // Execute concurrent operations
    CountDownLatch latch = new CountDownLatch(3);
    ExecutorService executor = Executors.newFixedThreadPool(3);
    
    for (int i = 0; i < 3; i++) {
        final int operationId = i;
        executor.submit(() -> {
            try {
                Tracer tracer = openTelemetry.getTracer("test");
                Span span = tracer.spanBuilder("concurrent-operation-" + operationId).startSpan();
                
                try (Scope scope = span.makeCurrent()) {
                    // Each thread should have isolated context
                    performConcurrentOperation(operationId);
                    Thread.sleep(100); // Simulate work
                } finally {
                    span.end();
                    latch.countDown();
                }
            } catch (InterruptedException e) {
                Thread.currentThread().interrupt();
            }
        });
    }
    
    // Wait for completion
    assertThat(latch.await(5, TimeUnit.SECONDS)).isTrue();
    
    // Verify each operation created its own span without interference
    List<SpanData> spans = spanExporter.getFinishedSpanItems();
    assertThat(spans).hasSize(3);
    assertThat(spans).allSatisfy(span -> assertThat(span).hasNoParent());
    
    executor.shutdown();
}

Types

// Time control interface
interface Clock {
    /**
     * Returns the current epoch time in milliseconds.
     */
    long now();
    
    /**
     * Returns the current time in nanoseconds for high-resolution timing.
     */
    long nanoTime();
}

// Context storage interfaces
interface ContextStorageProvider {
    /**
     * Returns the ContextStorage implementation to use.
     */
    ContextStorage get();
}

interface ContextStorage {
    /**
     * Returns the default context storage implementation.
     */
    static ContextStorage defaultStorage();
    
    /**
     * Attaches a context, returning a scope for cleanup.
     */
    Scope attach(Context context);
    
    /**
     * Returns the current context.
     */
    Context current();
}

// Time manipulation types
class Duration {
    static Duration of(long amount, TemporalUnit unit);
    static Duration ofNanos(long nanos);
    static Duration ofSeconds(long seconds);
    static Duration ofMinutes(long minutes);
    static Duration ofHours(long hours);
    static Duration ofDays(long days);
}

enum TimeUnit {
    NANOSECONDS, MICROSECONDS, MILLISECONDS, SECONDS, MINUTES, HOURS, DAYS;
    
    long toNanos(long duration);
    long toMicros(long duration);
    long toMillis(long duration);
    long toSeconds(long duration);
}

class Instant {
    static Instant now();
    static Instant parse(CharSequence text);
    static Instant ofEpochMilli(long epochMilli);
    static Instant ofEpochSecond(long epochSecond);
    
    long toEpochMilli();
    long getEpochSecond();
    int getNano();
    Instant plus(Duration duration);
    Instant minus(Duration duration);
}

// Context propagation types
interface Context {
    static Context current();
    static Context root();
    
    <T> T get(ContextKey<T> key);
    <T> Context with(ContextKey<T> key, T value);
}

interface Scope extends AutoCloseable {
    void close();
}

class ContextKey<T> {
    static <T> ContextKey<T> named(String name);
    String getName();
}

Best Practices

Time Control Best Practices

// Use fixed start times for deterministic tests
TestClock clock = TestClock.create(Instant.parse("2023-01-01T00:00:00Z"));

// Advance time in meaningful increments
clock.advance(Duration.ofSeconds(1)); // For second-precision tests
clock.advance(Duration.ofMillis(100)); // For millisecond-precision tests

// Reset clock state between tests
@BeforeEach
void resetClock() {
    clock.setTime(Instant.parse("2023-01-01T00:00:00Z"));
}

Context Isolation Best Practices

// Ensure context cleanup in tests
@AfterEach 
void cleanupContext() {
    // Reset to default storage if changed
    SettableContextStorageProvider.setContextStorage(ContextStorage.defaultStorage());
}

// Use try-with-resources for context scopes
try (Scope scope = span.makeCurrent()) {
    // Operations within context
} // Automatic cleanup

Install with Tessl CLI

npx tessl i tessl/maven-io-opentelemetry--opentelemetry-sdk-testing

docs

assertions.md

exporters.md

index.md

junit-integration.md

test-builders.md

utilities.md

tile.json