0
# Time and Context Utilities
1
2
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.
3
4
## Capabilities
5
6
### Test Clock
7
8
Controllable clock implementation that allows manipulation of time during tests for deterministic and predictable timing behavior.
9
10
```java { .api }
11
@ThreadSafe
12
class TestClock implements Clock {
13
// Factory methods
14
static TestClock create();
15
static TestClock create(Instant instant);
16
17
// Time manipulation
18
synchronized void setTime(Instant instant);
19
synchronized void advance(Duration duration);
20
synchronized void advance(long duration, TimeUnit unit);
21
22
// Clock interface methods
23
synchronized long now();
24
synchronized long nanoTime();
25
}
26
```
27
28
Usage examples:
29
30
```java
31
// Create a test clock with current time
32
TestClock clock = TestClock.create();
33
34
// Create a test clock with specific time
35
TestClock fixedClock = TestClock.create(Instant.parse("2023-01-01T12:00:00Z"));
36
37
// Use in OpenTelemetry configuration
38
OpenTelemetrySdk sdk = OpenTelemetrySdk.builder()
39
.setTracerProvider(
40
SdkTracerProvider.builder()
41
.setClock(clock)
42
.build())
43
.build();
44
45
// Control time during test execution
46
Tracer tracer = sdk.getTracer("test");
47
48
// Set specific start time
49
clock.setTime(Instant.parse("2023-01-01T10:00:00Z"));
50
Span span = tracer.spanBuilder("test-span").startSpan();
51
52
// Advance time and end span
53
clock.advance(Duration.ofSeconds(5));
54
span.end();
55
56
// Verify timing
57
List<SpanData> spans = spanExporter.getFinishedSpanItems();
58
assertThat(spans.get(0))
59
.startsAt(Instant.parse("2023-01-01T10:00:00Z"))
60
.endsAt(Instant.parse("2023-01-01T10:00:05Z"));
61
```
62
63
Advanced time control examples:
64
65
```java
66
@Test
67
void testTimeBasedBehavior() {
68
TestClock clock = TestClock.create(Instant.parse("2023-01-01T00:00:00Z"));
69
70
// Configure service with test clock
71
MyTimedService service = new MyTimedService(openTelemetry, clock);
72
73
// Test behavior at specific times
74
service.performScheduledTask(); // Task at midnight
75
76
// Advance to next hour
77
clock.advance(Duration.ofHours(1));
78
service.performScheduledTask(); // Task at 1 AM
79
80
// Verify time-based metrics
81
assertThat(metricReader.collectAllMetrics())
82
.extracting(MetricData::getName)
83
.contains("scheduled.task.executions")
84
.hasSize(2);
85
}
86
87
@Test
88
void testTimeoutBehavior() {
89
TestClock clock = TestClock.create();
90
TimeoutService service = new TimeoutService(openTelemetry, clock);
91
92
// Start an operation with 30-second timeout
93
clock.setTime(Instant.parse("2023-01-01T12:00:00Z"));
94
service.startLongRunningOperation();
95
96
// Advance past timeout
97
clock.advance(Duration.ofSeconds(35));
98
service.checkForTimeouts();
99
100
// Verify timeout span
101
assertThat(spanExporter.getFinishedSpanItems())
102
.first()
103
.hasName("long.running.operation")
104
.hasStatusSatisfying(status ->
105
status.hasCode(StatusCode.ERROR)
106
.hasDescription("Operation timed out")
107
);
108
}
109
110
@Test
111
void testMetricCollectionTiming() {
112
TestClock clock = TestClock.create();
113
114
// Configure metric reader with test clock
115
InMemoryMetricReader reader = InMemoryMetricReader.builder()
116
.setClock(clock)
117
.build();
118
119
SdkMeterProvider meterProvider = SdkMeterProvider.builder()
120
.registerMetricReader(reader)
121
.setClock(clock)
122
.build();
123
124
Meter meter = meterProvider.get("test");
125
LongCounter counter = meter.counterBuilder("test.counter").build();
126
127
// Record metrics at specific times
128
clock.setTime(Instant.parse("2023-01-01T12:00:00Z"));
129
counter.add(10);
130
131
clock.advance(Duration.ofMinutes(1));
132
counter.add(5);
133
134
// Collect metrics and verify timestamps
135
Collection<MetricData> metrics = reader.collectAllMetrics();
136
// Verify metric timestamps align with controlled time
137
}
138
```
139
140
### Context Storage Provider
141
142
Configurable context storage provider that allows switching context storage implementations during tests for testing context propagation and isolation.
143
144
```java { .api }
145
class SettableContextStorageProvider implements ContextStorageProvider {
146
// Static configuration methods
147
static void setContextStorage(ContextStorage storage);
148
static ContextStorage getContextStorage();
149
150
// ContextStorageProvider interface method
151
ContextStorage get();
152
}
153
```
154
155
Usage examples:
156
157
```java
158
// Configure context storage for testing
159
@BeforeEach
160
void setUp() {
161
// Set up thread-local context storage for isolation
162
SettableContextStorageProvider.setContextStorage(
163
ContextStorage.defaultStorage()
164
);
165
}
166
167
@Test
168
void testContextPropagation() {
169
OpenTelemetry openTelemetry = otelTesting.getOpenTelemetry();
170
Tracer tracer = openTelemetry.getTracer("test");
171
172
// Create parent span
173
Span parentSpan = tracer.spanBuilder("parent").startSpan();
174
175
try (Scope scope = parentSpan.makeCurrent()) {
176
// Context should propagate to child operations
177
MyService service = new MyService(openTelemetry);
178
service.performOperation(); // Should create child span
179
180
// Verify parent-child relationship
181
assertThat(spanExporter.getFinishedSpanItems())
182
.hasSize(1)
183
.first()
184
.hasParent(parentSpan);
185
} finally {
186
parentSpan.end();
187
}
188
}
189
190
@Test
191
void testContextIsolation() {
192
// Test that contexts don't leak between operations
193
OpenTelemetry openTelemetry = otelTesting.getOpenTelemetry();
194
Tracer tracer = openTelemetry.getTracer("test");
195
196
// First operation with its own context
197
Span span1 = tracer.spanBuilder("operation-1").startSpan();
198
try (Scope scope1 = span1.makeCurrent()) {
199
performFirstOperation();
200
} finally {
201
span1.end();
202
}
203
204
// Second operation should not see first operation's context
205
Span span2 = tracer.spanBuilder("operation-2").startSpan();
206
try (Scope scope2 = span2.makeCurrent()) {
207
performSecondOperation();
208
} finally {
209
span2.end();
210
}
211
212
// Verify no cross-contamination
213
List<SpanData> spans = spanExporter.getFinishedSpanItems();
214
assertThat(spans).hasSize(2);
215
assertThat(spans.get(0)).hasNoParent();
216
assertThat(spans.get(1)).hasNoParent();
217
}
218
219
// Custom context storage for advanced testing scenarios
220
@Test
221
void testCustomContextStorage() {
222
// Create custom context storage that tracks access
223
TrackingContextStorage trackingStorage = new TrackingContextStorage();
224
SettableContextStorageProvider.setContextStorage(trackingStorage);
225
226
OpenTelemetry openTelemetry = otelTesting.getOpenTelemetry();
227
Tracer tracer = openTelemetry.getTracer("test");
228
229
Span span = tracer.spanBuilder("tracked-operation").startSpan();
230
try (Scope scope = span.makeCurrent()) {
231
// Perform operations that should access context
232
performTrackedOperation();
233
} finally {
234
span.end();
235
}
236
237
// Verify context access patterns
238
assertThat(trackingStorage.getAccessCount()).isGreaterThan(0);
239
assertThat(trackingStorage.getLastAccessedContext()).isNotNull();
240
}
241
```
242
243
### Integration with Other Testing Utilities
244
245
Examples showing how time and context utilities work together with other testing components:
246
247
```java
248
@Test
249
void testCompleteTemporalScenario() {
250
// Set up controlled time and context
251
TestClock clock = TestClock.create(Instant.parse("2023-01-01T09:00:00Z"));
252
SettableContextStorageProvider.setContextStorage(ContextStorage.defaultStorage());
253
254
// Configure OpenTelemetry with test utilities
255
InMemorySpanExporter spanExporter = InMemorySpanExporter.create();
256
InMemoryMetricReader metricReader = InMemoryMetricReader.create();
257
258
OpenTelemetrySdk sdk = OpenTelemetrySdk.builder()
259
.setTracerProvider(
260
SdkTracerProvider.builder()
261
.setClock(clock)
262
.addSpanProcessor(BatchSpanProcessor.builder(spanExporter).build())
263
.build())
264
.setMeterProvider(
265
SdkMeterProvider.builder()
266
.setClock(clock)
267
.registerMetricReader(metricReader)
268
.build())
269
.build();
270
271
// Simulate time-based business logic
272
TimedBusinessService service = new TimedBusinessService(sdk, clock);
273
274
// Execute operation at 9:00 AM
275
service.startDailyProcessing();
276
277
// Advance to 9:30 AM
278
clock.advance(Duration.ofMinutes(30));
279
service.performMidProcessingCheck();
280
281
// Advance to 10:00 AM
282
clock.advance(Duration.ofMinutes(30));
283
service.completeDailyProcessing();
284
285
// Verify temporal correctness
286
List<SpanData> spans = spanExporter.getFinishedSpanItems();
287
assertThat(spans)
288
.extracting(SpanData::getName)
289
.containsExactly(
290
"daily.processing.start",
291
"mid.processing.check",
292
"daily.processing.complete"
293
);
294
295
// Verify timing relationships
296
assertThat(spans.get(0)).startsAt(Instant.parse("2023-01-01T09:00:00Z"));
297
assertThat(spans.get(1)).startsAt(Instant.parse("2023-01-01T09:30:00Z"));
298
assertThat(spans.get(2)).startsAt(Instant.parse("2023-01-01T10:00:00Z"));
299
300
// Verify metrics collected at correct times
301
Collection<MetricData> metrics = metricReader.collectAllMetrics();
302
assertThat(metrics)
303
.filteredOn(metric -> "processing.duration".equals(metric.getName()))
304
.hasSize(1);
305
}
306
307
@Test
308
void testConcurrentContextIsolation() {
309
TestClock clock = TestClock.create();
310
311
// Set up concurrent context isolation
312
SettableContextStorageProvider.setContextStorage(
313
new ThreadLocalContextStorage()
314
);
315
316
OpenTelemetry openTelemetry = otelTesting.getOpenTelemetry();
317
318
// Execute concurrent operations
319
CountDownLatch latch = new CountDownLatch(3);
320
ExecutorService executor = Executors.newFixedThreadPool(3);
321
322
for (int i = 0; i < 3; i++) {
323
final int operationId = i;
324
executor.submit(() -> {
325
try {
326
Tracer tracer = openTelemetry.getTracer("test");
327
Span span = tracer.spanBuilder("concurrent-operation-" + operationId).startSpan();
328
329
try (Scope scope = span.makeCurrent()) {
330
// Each thread should have isolated context
331
performConcurrentOperation(operationId);
332
Thread.sleep(100); // Simulate work
333
} finally {
334
span.end();
335
latch.countDown();
336
}
337
} catch (InterruptedException e) {
338
Thread.currentThread().interrupt();
339
}
340
});
341
}
342
343
// Wait for completion
344
assertThat(latch.await(5, TimeUnit.SECONDS)).isTrue();
345
346
// Verify each operation created its own span without interference
347
List<SpanData> spans = spanExporter.getFinishedSpanItems();
348
assertThat(spans).hasSize(3);
349
assertThat(spans).allSatisfy(span -> assertThat(span).hasNoParent());
350
351
executor.shutdown();
352
}
353
```
354
355
## Types
356
357
```java { .api }
358
// Time control interface
359
interface Clock {
360
/**
361
* Returns the current epoch time in milliseconds.
362
*/
363
long now();
364
365
/**
366
* Returns the current time in nanoseconds for high-resolution timing.
367
*/
368
long nanoTime();
369
}
370
371
// Context storage interfaces
372
interface ContextStorageProvider {
373
/**
374
* Returns the ContextStorage implementation to use.
375
*/
376
ContextStorage get();
377
}
378
379
interface ContextStorage {
380
/**
381
* Returns the default context storage implementation.
382
*/
383
static ContextStorage defaultStorage();
384
385
/**
386
* Attaches a context, returning a scope for cleanup.
387
*/
388
Scope attach(Context context);
389
390
/**
391
* Returns the current context.
392
*/
393
Context current();
394
}
395
396
// Time manipulation types
397
class Duration {
398
static Duration of(long amount, TemporalUnit unit);
399
static Duration ofNanos(long nanos);
400
static Duration ofSeconds(long seconds);
401
static Duration ofMinutes(long minutes);
402
static Duration ofHours(long hours);
403
static Duration ofDays(long days);
404
}
405
406
enum TimeUnit {
407
NANOSECONDS, MICROSECONDS, MILLISECONDS, SECONDS, MINUTES, HOURS, DAYS;
408
409
long toNanos(long duration);
410
long toMicros(long duration);
411
long toMillis(long duration);
412
long toSeconds(long duration);
413
}
414
415
class Instant {
416
static Instant now();
417
static Instant parse(CharSequence text);
418
static Instant ofEpochMilli(long epochMilli);
419
static Instant ofEpochSecond(long epochSecond);
420
421
long toEpochMilli();
422
long getEpochSecond();
423
int getNano();
424
Instant plus(Duration duration);
425
Instant minus(Duration duration);
426
}
427
428
// Context propagation types
429
interface Context {
430
static Context current();
431
static Context root();
432
433
<T> T get(ContextKey<T> key);
434
<T> Context with(ContextKey<T> key, T value);
435
}
436
437
interface Scope extends AutoCloseable {
438
void close();
439
}
440
441
class ContextKey<T> {
442
static <T> ContextKey<T> named(String name);
443
String getName();
444
}
445
```
446
447
## Best Practices
448
449
### Time Control Best Practices
450
451
```java
452
// Use fixed start times for deterministic tests
453
TestClock clock = TestClock.create(Instant.parse("2023-01-01T00:00:00Z"));
454
455
// Advance time in meaningful increments
456
clock.advance(Duration.ofSeconds(1)); // For second-precision tests
457
clock.advance(Duration.ofMillis(100)); // For millisecond-precision tests
458
459
// Reset clock state between tests
460
@BeforeEach
461
void resetClock() {
462
clock.setTime(Instant.parse("2023-01-01T00:00:00Z"));
463
}
464
```
465
466
### Context Isolation Best Practices
467
468
```java
469
// Ensure context cleanup in tests
470
@AfterEach
471
void cleanupContext() {
472
// Reset to default storage if changed
473
SettableContextStorageProvider.setContextStorage(ContextStorage.defaultStorage());
474
}
475
476
// Use try-with-resources for context scopes
477
try (Scope scope = span.makeCurrent()) {
478
// Operations within context
479
} // Automatic cleanup
480
```