diff --git a/Tests/InstrumentationTests/InstrumentationSystemTests.swift b/Tests/InstrumentationTests/InstrumentationSystemTests.swift deleted file mode 100644 index c614e4f6..00000000 --- a/Tests/InstrumentationTests/InstrumentationSystemTests.swift +++ /dev/null @@ -1,94 +0,0 @@ -//===----------------------------------------------------------------------===// -// -// This source file is part of the Swift Distributed Tracing open source project -// -// Copyright (c) 2020-2023 Apple Inc. and the Swift Distributed Tracing project authors -// Licensed under Apache License v2.0 -// -// See LICENSE.txt for license information -// See CONTRIBUTORS.txt for the list of Swift Distributed Tracing project authors -// -// SPDX-License-Identifier: Apache-2.0 -// -//===----------------------------------------------------------------------===// - -import ServiceContextModule -import XCTest - -@testable import Instrumentation - -extension InstrumentationSystem { - public static func _instrument(of instrumentType: I.Type) -> I? where I: Instrument { - self._findInstrument(where: { $0 is I }) as? I - } -} - -final class InstrumentationSystemTests: XCTestCase { - override class func tearDown() { - super.tearDown() - InstrumentationSystem.bootstrapInternal(nil) - } - - func testItProvidesAccessToASingletonInstrument() { - let tracer = FakeTracer() - let instrument = FakeInstrument() - let multiplexInstrument = MultiplexInstrument([tracer, instrument]) - - XCTAssertNil(InstrumentationSystem._instrument(of: FakeTracer.self)) - XCTAssertNil(InstrumentationSystem._instrument(of: FakeInstrument.self)) - - InstrumentationSystem.bootstrapInternal(multiplexInstrument) - XCTAssert(InstrumentationSystem.instrument is MultiplexInstrument) - XCTAssert(InstrumentationSystem._instrument(of: FakeTracer.self) === tracer) - XCTAssert(InstrumentationSystem._instrument(of: FakeInstrument.self) === instrument) - - InstrumentationSystem.bootstrapInternal(tracer) - XCTAssertFalse(InstrumentationSystem.instrument is MultiplexInstrument) - XCTAssert(InstrumentationSystem._instrument(of: FakeTracer.self) === tracer) - XCTAssertNil(InstrumentationSystem._instrument(of: FakeInstrument.self)) - } -} - -private final class FakeTracer: Instrument { - func inject( - _ context: ServiceContext, - into carrier: inout Carrier, - using injector: Inject - ) - where - Inject: Injector, - Carrier == Inject.Carrier - {} - - func extract( - _ carrier: Carrier, - into context: inout ServiceContext, - using extractor: Extract - ) - where - Extract: Extractor, - Carrier == Extract.Carrier - {} -} - -private final class FakeInstrument: Instrument { - func inject( - _ context: ServiceContext, - into carrier: inout Carrier, - using injector: Inject - ) - where - Inject: Injector, - Carrier == Inject.Carrier - {} - - func extract( - _ carrier: Carrier, - into context: inout ServiceContext, - using extractor: Extract - ) - where - Extract: Extractor, - Carrier == Extract.Carrier - {} -} diff --git a/Tests/TracingTests/ActorTracingTests.swift b/Tests/TracingTests/ActorTracingTests.swift index 784a489e..e84a9ac7 100644 --- a/Tests/TracingTests/ActorTracingTests.swift +++ b/Tests/TracingTests/ActorTracingTests.swift @@ -18,12 +18,8 @@ import XCTest @testable import Instrumentation +/// This is a compile-time test final class ActorTracingTests: XCTestCase { - override class func tearDown() { - super.tearDown() - InstrumentationSystem.bootstrapInternal(nil) - } - func test() {} } diff --git a/Tests/TracingTests/DynamicTracepointTracerTests.swift b/Tests/TracingTests/DynamicTracepointTracerTests.swift index 5acb1bad..09439485 100644 --- a/Tests/TracingTests/DynamicTracepointTracerTests.swift +++ b/Tests/TracingTests/DynamicTracepointTracerTests.swift @@ -19,19 +19,9 @@ import XCTest @testable import Instrumentation final class DynamicTracepointTracerTests: XCTestCase { - override class func tearDown() { - super.tearDown() - InstrumentationSystem.bootstrapInternal(nil) - } - func test_adhoc_enableBySourceLoc() { let tracer = DynamicTracepointTestTracer() - InstrumentationSystem.bootstrapInternal(tracer) - defer { - InstrumentationSystem.bootstrapInternal(NoOpTracer()) - } - let fileID = #fileID let fakeLine: UInt = 77 // trick number, see withSpan below. let fakeNextLine: UInt = fakeLine + 11 @@ -71,19 +61,14 @@ final class DynamicTracepointTracerTests: XCTestCase { func test_adhoc_enableByFunction() { let tracer = DynamicTracepointTestTracer() - InstrumentationSystem.bootstrapInternal(tracer) - defer { - InstrumentationSystem.bootstrapInternal(NoOpTracer()) - } - let fileID = #fileID - tracer.enableTracepoint(function: "traceMeLogic(fakeLine:)") + tracer.enableTracepoint(function: "traceMeLogic(fakeLine:tracer:)") let fakeLine: UInt = 66 let fakeNextLine: UInt = fakeLine + 11 - self.logic(fakeLine: 55) - self.traceMeLogic(fakeLine: fakeLine) + self.logic(fakeLine: 55, tracer: tracer) + self.traceMeLogic(fakeLine: fakeLine, tracer: tracer) XCTAssertEqual(tracer.spans.count, 2) for span in tracer.spans { @@ -93,15 +78,15 @@ final class DynamicTracepointTracerTests: XCTestCase { XCTAssertEqual(tracer.spans[1].context.spanID, "span-id-fake-\(fileID)-\(fakeNextLine)") } - func logic(fakeLine: UInt) { - InstrumentationSystem.tracer.withSpan("\(#function)-dont", line: fakeLine) { _ in + func logic(fakeLine: UInt, tracer: any Tracer) { + tracer.withSpan("\(#function)-dont", line: fakeLine) { _ in // inside } } - func traceMeLogic(fakeLine: UInt) { - InstrumentationSystem.tracer.withSpan("\(#function)-yes", line: fakeLine) { _ in - InstrumentationSystem.tracer.withSpan("\(#function)-yes-inside", line: fakeLine + 11) { _ in + func traceMeLogic(fakeLine: UInt, tracer: any Tracer) { + tracer.withSpan("\(#function)-yes", line: fakeLine) { _ in + tracer.withSpan("\(#function)-yes-inside", line: fakeLine + 11) { _ in // inside } } diff --git a/Tests/TracingTests/SpanTests.swift b/Tests/TracingTests/SpanTests.swift index 68fb2de1..741bb9f0 100644 --- a/Tests/TracingTests/SpanTests.swift +++ b/Tests/TracingTests/SpanTests.swift @@ -82,7 +82,8 @@ final class SpanTests: XCTestCase { } func testSpanAttributeIsExpressibleByArrayLiteral() { - let s = InstrumentationSystem.legacyTracer.startAnySpan("", context: .topLevel) + let tracer = TestTracer() + let s = tracer.startAnySpan("", context: .topLevel) s.attributes["hi"] = [42, 21] s.attributes["hi"] = [42.10, 21.0] s.attributes["hi"] = [true, false] @@ -91,12 +92,8 @@ final class SpanTests: XCTestCase { } func testSpanAttributeSetEntireCollection() { - InstrumentationSystem.bootstrapInternal(TestTracer()) - defer { - InstrumentationSystem.bootstrapInternal(NoOpTracer()) - } - - let s = InstrumentationSystem.legacyTracer.startAnySpan("", context: .topLevel) + let tracer = TestTracer() + let s = tracer.startAnySpan("", context: .topLevel) var attrs = s.attributes attrs["one"] = 42 attrs["two"] = [1, 2, 34] diff --git a/Tests/TracingTests/TestTracer.swift b/Tests/TracingTests/TestTracer.swift index 5930e3ad..580df93f 100644 --- a/Tests/TracingTests/TestTracer.swift +++ b/Tests/TracingTests/TestTracer.swift @@ -118,7 +118,7 @@ extension ServiceContext { /// Only intended to be used in single-threaded testing. final class TestSpan: Span { - private let kind: SpanKind + let kind: SpanKind private var status: SpanStatus? diff --git a/Tests/TracingTests/TracedLock.swift b/Tests/TracingTests/TracedLock.swift index dd8de968..0d13b544 100644 --- a/Tests/TracingTests/TracedLock.swift +++ b/Tests/TracingTests/TracedLock.swift @@ -30,10 +30,10 @@ final class TracedLock: @unchecked Sendable { self.underlyingLock = NSLock() } - func lock(context: ServiceContext) { + func lock(context: ServiceContext, tracer: any Tracer) { // time here self.underlyingLock.lock() - self.activeSpan = InstrumentationSystem.legacyTracer.startAnySpan(self.name, context: context) + self.activeSpan = tracer.startSpan(self.name, context: context) } func unlock(context: ServiceContext) { @@ -42,8 +42,8 @@ final class TracedLock: @unchecked Sendable { self.underlyingLock.unlock() } - func withLock(context: ServiceContext, _ closure: () -> Void) { - self.lock(context: context) + func withLock(context: ServiceContext, tracer: any Tracer, _ closure: () -> Void) { + self.lock(context: context, tracer: tracer) defer { self.unlock(context: context) } closure() } diff --git a/Tests/TracingTests/TracedLockTests.swift b/Tests/TracingTests/TracedLockTests.swift index 339735c1..92d6a698 100644 --- a/Tests/TracingTests/TracedLockTests.swift +++ b/Tests/TracingTests/TracedLockTests.swift @@ -19,15 +19,8 @@ import XCTest @testable import Instrumentation final class TracedLockTests: XCTestCase { - override class func tearDown() { - super.tearDown() - InstrumentationSystem.bootstrapInternal(nil) - } - func test_tracesLockedTime() { let tracer = TracedLockPrintlnTracer() - InstrumentationSystem.bootstrapInternal(tracer) - let lock = TracedLock(name: "my-cool-lock") func launchTask(_ name: String) { @@ -35,7 +28,7 @@ final class TracedLockTests: XCTestCase { var context = ServiceContext.topLevel context[TaskIDKey.self] = name - lock.lock(context: context) + lock.lock(context: context, tracer: tracer) lock.unlock(context: context) } } diff --git a/Tests/TracingTests/TracerTests.swift b/Tests/TracingTests/TracerTests.swift index 16c8b9d5..b76a4aa0 100644 --- a/Tests/TracingTests/TracerTests.swift +++ b/Tests/TracingTests/TracerTests.swift @@ -23,24 +23,14 @@ import XCTest #endif final class TracerTests: XCTestCase { - override class func tearDown() { - super.tearDown() - InstrumentationSystem.bootstrapInternal(nil) - } - func testContextPropagation() { let tracer = TestTracer() - InstrumentationSystem.bootstrapInternal(tracer) - defer { - InstrumentationSystem.bootstrapInternal(NoOpTracer()) - } - let httpServer = FakeHTTPServer { context, _, client -> FakeHTTPResponse in - client.performRequest(context, request: FakeHTTPRequest(path: "/test", headers: [])) + client.performRequest(context, request: FakeHTTPRequest(path: "/test", headers: []), tracer: tracer) return FakeHTTPResponse(status: 418) } - httpServer.receive(FakeHTTPRequest(path: "/", headers: [("trace-id", "test")])) + httpServer.receive(FakeHTTPRequest(path: "/", headers: [("trace-id", "test")]), tracer: tracer) XCTAssertEqual(tracer.spans.count, 2) for span in tracer.spans { @@ -49,14 +39,15 @@ final class TracerTests: XCTestCase { } func testContextPropagationWithNoOpSpan() { + let tracer = TestTracer() let httpServer = FakeHTTPServer { _, _, client -> FakeHTTPResponse in var context = ServiceContext.topLevel context.traceID = "test" - client.performRequest(context, request: FakeHTTPRequest(path: "/test", headers: [])) + client.performRequest(context, request: FakeHTTPRequest(path: "/test", headers: []), tracer: tracer) return FakeHTTPResponse(status: 418) } - httpServer.receive(FakeHTTPRequest(path: "/", headers: [("trace-id", "test")])) + httpServer.receive(FakeHTTPRequest(path: "/", headers: [("trace-id", "test")]), tracer: tracer) XCTAssertEqual(httpServer.client.contexts.count, 1) XCTAssertEqual(httpServer.client.contexts.first?.traceID, "test") @@ -67,10 +58,6 @@ final class TracerTests: XCTestCase { return } let tracer = TestTracer() - InstrumentationSystem.bootstrapInternal(tracer) - defer { - InstrumentationSystem.bootstrapInternal(NoOpTracer()) - } var spanEnded = false tracer.onEndSpan = { _ in @@ -87,10 +74,6 @@ final class TracerTests: XCTestCase { func testWithSpan_throws() { let tracer = TestTracer() - InstrumentationSystem.bootstrapInternal(tracer) - defer { - InstrumentationSystem.bootstrapInternal(NoOpTracer()) - } var spanEnded = false tracer.onEndSpan = { _ in spanEnded = true } @@ -113,10 +96,6 @@ final class TracerTests: XCTestCase { } let tracer = TestTracer() - InstrumentationSystem.bootstrapInternal(tracer) - defer { - InstrumentationSystem.bootstrapInternal(nil) - } var spanEnded = false tracer.onEndSpan = { _ in spanEnded = true } @@ -140,10 +119,6 @@ final class TracerTests: XCTestCase { } let tracer = TestTracer() - InstrumentationSystem.bootstrapInternal(tracer) - defer { - InstrumentationSystem.bootstrapInternal(nil) - } var spanEnded = false tracer.onEndSpan = { _ in spanEnded = true } @@ -168,10 +143,6 @@ final class TracerTests: XCTestCase { } let tracer = TestTracer() - InstrumentationSystem.bootstrapInternal(tracer) - defer { - InstrumentationSystem.bootstrapInternal(nil) - } let spanEnded: LockedValueBox = .init(false) tracer.onEndSpan = { _ in spanEnded.withValue { $0 = true } } @@ -197,10 +168,6 @@ final class TracerTests: XCTestCase { } let tracer = TestTracer() - InstrumentationSystem.bootstrapInternal(tracer) - defer { - InstrumentationSystem.bootstrapInternal(nil) - } let spanEnded: LockedValueBox = .init(false) tracer.onEndSpan = { _ in spanEnded.withValue { $0 = true } } @@ -230,10 +197,6 @@ final class TracerTests: XCTestCase { } let tracer = TestTracer() - InstrumentationSystem.bootstrapInternal(tracer) - defer { - InstrumentationSystem.bootstrapInternal(nil) - } let spanEnded: LockedValueBox = .init(false) tracer.onEndSpan = { _ in spanEnded.withValue { $0 = true } } @@ -260,10 +223,6 @@ final class TracerTests: XCTestCase { } let tracer = TestTracer() - InstrumentationSystem.bootstrapInternal(tracer) - defer { - InstrumentationSystem.bootstrapInternal(nil) - } let spanEnded: LockedValueBox = .init(false) tracer.onEndSpan = { _ in spanEnded.withValue { $0 = true } } @@ -274,7 +233,7 @@ final class TracerTests: XCTestCase { self.testAsync { do { - _ = try await withSpan("hello", operation) + _ = try await tracer.withSpan("hello", operation) } catch { XCTAssertTrue(spanEnded.withValue { $0 }) XCTAssertEqual(error as? ExampleSpanError, ExampleSpanError()) @@ -290,10 +249,6 @@ final class TracerTests: XCTestCase { } let tracer = TestTracer() - InstrumentationSystem.bootstrapInternal(tracer) - defer { - InstrumentationSystem.bootstrapInternal(nil) - } let spanEnded: LockedValueBox = .init(false) tracer.onEndSpan = { _ in spanEnded.withValue { $0 = true } } @@ -304,7 +259,7 @@ final class TracerTests: XCTestCase { self.testAsync { do { - _ = try await withSpan("hello", operation) + _ = try await tracer.withSpan("hello", operation) } catch { XCTAssertTrue(spanEnded.withValue { $0 }) XCTAssertEqual(error as? ExampleSpanError, ExampleSpanError()) @@ -320,10 +275,6 @@ final class TracerTests: XCTestCase { } let tracer = TestTracer() - InstrumentationSystem.bootstrapInternal(tracer) - defer { - InstrumentationSystem.bootstrapInternal(nil) - } var endedSpan: TestSpan? tracer.onEndSpan = { span in endedSpan = span } @@ -358,15 +309,11 @@ final class TracerTests: XCTestCase { func testWithSpanShouldNotMissPropagatingInstant() { let tracer = TestTracer() - InstrumentationSystem.bootstrapInternal(tracer) - defer { - InstrumentationSystem.bootstrapInternal(nil) - } let clock = DefaultTracerClock() let instant = clock.now - withSpan("span", at: instant) { _ in } + tracer.withSpan("span", at: instant) { _ in } let span = tracer.spans.first! XCTAssertEqual(span.startTimestampNanosSinceEpoch, instant.nanosecondsSinceEpoch) @@ -428,11 +375,11 @@ struct FakeHTTPServer { self.client = FakeHTTPClient() } - func receive(_ request: FakeHTTPRequest) { + func receive(_ request: FakeHTTPRequest, tracer: any Tracer & Instrument) { var context = ServiceContext.topLevel - InstrumentationSystem.instrument.extract(request.headers, into: &context, using: HTTPHeadersExtractor()) + tracer.extract(request.headers, into: &context, using: HTTPHeadersExtractor()) - let span = InstrumentationSystem.tracer.startSpan("GET \(request.path)", context: context) + let span = tracer.startSpan("GET \(request.path)", context: context) let response = self.catchAllHandler(span.context, request, self.client) span.attributes["http.status"] = response.status @@ -446,12 +393,12 @@ struct FakeHTTPServer { final class FakeHTTPClient { private(set) var contexts = [ServiceContext]() - func performRequest(_ context: ServiceContext, request: FakeHTTPRequest) { + func performRequest(_ context: ServiceContext, request: FakeHTTPRequest, tracer: any LegacyTracer) { var request = request - let span = InstrumentationSystem.legacyTracer.startAnySpan("GET \(request.path)", context: context) + let span = tracer.startAnySpan("GET \(request.path)", context: context) self.contexts.append(span.context) - InstrumentationSystem.instrument.inject(context, into: &request.headers, using: HTTPHeadersInjector()) + tracer.inject(context, into: &request.headers, using: HTTPHeadersInjector()) span.end() } } diff --git a/Tests/TracingTests/TracerTimeTests.swift b/Tests/TracingTests/TracerTimeTests.swift index 40bca84d..d0eae163 100644 --- a/Tests/TracingTests/TracerTimeTests.swift +++ b/Tests/TracingTests/TracerTimeTests.swift @@ -20,11 +20,6 @@ import struct Foundation.Date @testable import Instrumentation final class TracerTimeTests: XCTestCase { - override class func tearDown() { - super.tearDown() - InstrumentationSystem.bootstrapInternal(nil) - } - func testTracerTime() { let t = DefaultTracerClock.now let d = Date() @@ -37,10 +32,6 @@ final class TracerTimeTests: XCTestCase { func testMockTimeStartSpan() { let tracer = TestTracer() - InstrumentationSystem.bootstrapInternal(tracer) - defer { - InstrumentationSystem.bootstrapInternal(NoOpTracer()) - } let mockClock = MockClock() mockClock.setTime(13) diff --git a/Tests/TracingTests/TracingInstrumentationSystemTests.swift b/Tests/TracingTests/TracingInstrumentationSystemTests.swift index fb656a36..ede6a3ce 100644 --- a/Tests/TracingTests/TracingInstrumentationSystemTests.swift +++ b/Tests/TracingTests/TracingInstrumentationSystemTests.swift @@ -31,7 +31,8 @@ extension InstrumentationSystem { } } -final class TracingInstrumentationSystemTests: XCTestCase { +/// This is the only test relying in the global InstrumentationSystem +final class GlobalTracingInstrumentationSystemTests: XCTestCase { override class func tearDown() { super.tearDown() InstrumentationSystem.bootstrapInternal(nil) @@ -64,3 +65,190 @@ final class TracingInstrumentationSystemTests: XCTestCase { XCTAssert(InstrumentationSystem.tracer is TestTracer) } } + +final class GlobalTracingMethodsTests: XCTestCase { + override class func tearDown() { + super.tearDown() + InstrumentationSystem.bootstrapInternal(nil) + } + + func testGlobalTracingMethods() async { + // Bootstrap with TestTracer to capture spans + let tracer = TestTracer() + InstrumentationSystem.bootstrapInternal(tracer) + + // Create custom timestamps for testing + let clock = DefaultTracerClock() + let customInstant1 = clock.now + let customInstant2 = clock.now + let customInstant3 = clock.now + + // Create custom contexts to verify they're preserved + var customContext1 = ServiceContext.topLevel + customContext1[TestContextKey.self] = "context1" + + var customContext2 = ServiceContext.topLevel + customContext2[TestContextKey.self] = "context2" + + // Test 1: startSpan with custom instant + let span1 = startSpan( + "startSpan-with-instant", + at: customInstant1, + context: customContext1, + ofKind: .client + ) + span1.end() + + // Test 2: startSpan without instant (uses default) + let span2 = startSpan( + "startSpan-default-instant", + context: customContext2, + ofKind: .server + ) + span2.end() + + // Test 3: startSpan with instant (different parameter order) + let span3 = startSpan( + "startSpan-instant-alt", + context: .topLevel, + ofKind: .producer, + at: customInstant2 + ) + span3.end() + + // Test 4: withSpan synchronous with custom instant + let result1 = withSpan( + "withSpan-sync-instant", + at: customInstant3, + context: customContext1, + ofKind: .consumer + ) { span -> String in + XCTAssertEqual(span.operationName, "withSpan-sync-instant") + return "sync-result" + } + XCTAssertEqual(result1, "sync-result") + + // Test 5: withSpan synchronous without instant + let result2 = withSpan( + "withSpan-sync-default", + context: customContext2, + ofKind: .internal + ) { span -> Int in + XCTAssertEqual(span.operationName, "withSpan-sync-default") + return 42 + } + XCTAssertEqual(result2, 42) + + // Test 6: withSpan synchronous with instant (alt parameter order) + let result3 = withSpan( + "withSpan-sync-instant-alt", + context: .topLevel, + ofKind: .server, + at: clock.now + ) { _ in + "alt-result" + } + XCTAssertEqual(result3, "alt-result") + + // Test 7: withSpan async with custom instant and isolation + let result4 = await withSpan( + "withSpan-async-instant-isolation", + at: clock.now, + context: customContext1, + ofKind: .client, + isolation: nil + ) { span -> String in + XCTAssertEqual(span.operationName, "withSpan-async-instant-isolation") + return "async-result" + } + XCTAssertEqual(result4, "async-result") + + // Test 8: withSpan async without instant but with isolation + let result5 = await withSpan( + "withSpan-async-default-isolation", + context: customContext2, + ofKind: .producer, + isolation: nil + ) { span -> Bool in + XCTAssertEqual(span.operationName, "withSpan-async-default-isolation") + return true + } + XCTAssertEqual(result5, true) + + // Test 9: withSpan async with instant, isolation (alt parameter order) + let result6 = await withSpan( + "withSpan-async-instant-isolation-alt", + context: .topLevel, + ofKind: .consumer, + at: clock.now, + isolation: nil + ) { _ in + 99 + } + XCTAssertEqual(result6, 99) + + // Verify all spans were recorded with correct properties + let finishedSpans = tracer.spans + XCTAssertEqual(finishedSpans.count, 9, "Expected 9 finished spans") + + // Verify span 1: startSpan with custom instant + let recorded1 = finishedSpans[0] + XCTAssertEqual(recorded1.operationName, "startSpan-with-instant") + XCTAssertEqual(recorded1.kind, .client) + XCTAssertEqual(recorded1.context[TestContextKey.self], "context1") + XCTAssertEqual(recorded1.startTimestampNanosSinceEpoch, customInstant1.nanosecondsSinceEpoch) + + // Verify span 2: startSpan without instant + let recorded2 = finishedSpans[1] + XCTAssertEqual(recorded2.operationName, "startSpan-default-instant") + XCTAssertEqual(recorded2.kind, .server) + XCTAssertEqual(recorded2.context[TestContextKey.self], "context2") + // Note: Can't verify exact instant since it used DefaultTracerClock.now + + // Verify span 3: startSpan with instant (alt) + let recorded3 = finishedSpans[2] + XCTAssertEqual(recorded3.operationName, "startSpan-instant-alt") + XCTAssertEqual(recorded3.kind, .producer) + XCTAssertEqual(recorded3.startTimestampNanosSinceEpoch, customInstant2.nanosecondsSinceEpoch) + + // Verify span 4: withSpan sync with instant + let recorded4 = finishedSpans[3] + XCTAssertEqual(recorded4.operationName, "withSpan-sync-instant") + XCTAssertEqual(recorded4.kind, .consumer) + XCTAssertEqual(recorded4.context[TestContextKey.self], "context1") + XCTAssertEqual(recorded4.startTimestampNanosSinceEpoch, customInstant3.nanosecondsSinceEpoch) + + // Verify span 5: withSpan sync without instant + let recorded5 = finishedSpans[4] + XCTAssertEqual(recorded5.operationName, "withSpan-sync-default") + XCTAssertEqual(recorded5.kind, .internal) + XCTAssertEqual(recorded5.context[TestContextKey.self], "context2") + + // Verify span 6: withSpan sync with instant (alt) + let recorded6 = finishedSpans[5] + XCTAssertEqual(recorded6.operationName, "withSpan-sync-instant-alt") + XCTAssertEqual(recorded6.kind, .server) + + // Verify span 7: withSpan async with instant and isolation + let recorded7 = finishedSpans[6] + XCTAssertEqual(recorded7.operationName, "withSpan-async-instant-isolation") + XCTAssertEqual(recorded7.kind, .client) + XCTAssertEqual(recorded7.context[TestContextKey.self], "context1") + + // Verify span 8: withSpan async without instant but with isolation + let recorded8 = finishedSpans[7] + XCTAssertEqual(recorded8.operationName, "withSpan-async-default-isolation") + XCTAssertEqual(recorded8.kind, .producer) + XCTAssertEqual(recorded8.context[TestContextKey.self], "context2") + + // Verify span 9: withSpan async with instant and isolation (alt) + let recorded9 = finishedSpans[8] + XCTAssertEqual(recorded9.operationName, "withSpan-async-instant-isolation-alt") + XCTAssertEqual(recorded9.kind, .consumer) + } +} + +// Test context key for verification +private enum TestContextKey: ServiceContextKey { + typealias Value = String +}