feat: add Activity tracing for OpenTelemetry support#4844
Conversation
Code ReviewI found a performance issue that violates CLAUDE.md requirements.
|
…tible instrumentation Add first-party trace spans at every level of the test lifecycle (session, assembly, class, test) using System.Diagnostics.ActivitySource. Users get OpenTelemetry-compatible tracing automatically when they configure an exporter. Zero cost when no listener is attached. - Add TUnitActivitySource singleton with StartActivity/RecordException/StopActivity helpers - Add Activity property to Context base class (inherited by all context types) - Start/stop session, assembly, class spans in HookExecutor - Start/stop test spans in TestExecutor with result/skip/retry tags - Stop failed attempt Activity on retry in RetryHelper - All Activity code behind #if NET (ActivitySource requires .NET 5+) - Explicit ActivityContext parenting for parallel safety Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
- Span names: use verb+object pattern (test session, test assembly, test suite, test case) - Use OTel test.* namespace: test.case.name, test.case.result.status, test.suite.name - Result values: lowercase per OTel (pass/fail/skipped instead of Passed/Failed/Skipped) - Status codes: leave Unset on success (not Ok) per instrumentation library conventions - Add error.type tag alongside exception events per OTel recording-errors spec - Categories: pass as string[] array instead of comma-joined string - Remove redundant HasListeners() guard (StartActivity returns null natively) Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
Covers ActivitySource subscription, span hierarchy, attribute reference tables, status conventions, retry behavior, and exporter setup examples. Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
Wrap all 4 StartActivity call sites with TUnitActivitySource.Source.HasListeners() so that tag collection expressions, string.Join, and ToArray allocations are never evaluated when no listener is attached. This ensures true zero-cost when tracing is disabled, per CLAUDE.md Rule 4 (Performance First). Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
ac93d2c to
4792026
Compare
Summary
System.Diagnostics.Activitytrace spans at every level of the test lifecycle (session, assembly, class, test) for OpenTelemetry-compatible distributed tracingActivityListeneris attached —ActivitySource.HasListeners()short-circuits all instrumentation#if NET(ActivitySource requires .NET 5+; TUnit targets netstandard2.0)What's included
tunit.sessiontunit.session.id,tunit.filtertunit.assemblytunit.assembly.nametunit.classtunit.class.name,tunit.class.namespacetunit.testtunit.test.name,tunit.test.class,tunit.test.method,tunit.test.id,tunit.test.categoriesPassed/Failed/Skipped), retry attempt, skip reasonexception.type,exception.message,exception.stacktrace)ActivityContextparenting (not ambientActivity.Current) for parallel safetyFiles changed
TUnit.Core/TUnitActivitySource.csActivitySourcesingleton + helpersTUnit.Core/Context.csActivity?property + disposal on base classTUnit.Engine/Services/HookExecutor.csTUnit.Engine/TestExecutor.csTUnit.Engine/Services/TestExecution/RetryHelper.csTest plan
dotnet build TUnit.Core— succeeds on all TFMs (netstandard2.0, net8.0, net9.0, net10.0)dotnet build TUnit.Engine— succeeds on all TFMsdotnet test --project TUnit.UnitTests— all 556 tests pass (no behavioral change when no listener attached)"TUnit"source, verify span hierarchy in output🤖 Generated with Claude Code