Skip to content

Commit 17f4c8d

Browse files
committed
Add TestTracer to new TestTracingKit
1 parent d1ba77e commit 17f4c8d

File tree

8 files changed

+889
-8
lines changed

8 files changed

+889
-8
lines changed

Package.swift

Lines changed: 13 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -6,6 +6,7 @@ let package = Package(
66
products: [
77
.library(name: "Instrumentation", targets: ["Instrumentation"]),
88
.library(name: "Tracing", targets: ["Tracing"]),
9+
.library(name: "TracingTestKit", targets: ["TracingTestKit"]),
910
],
1011
dependencies: [
1112
.package(url: "https://github.com/apple/swift-service-context.git", from: "1.1.0")
@@ -44,6 +45,18 @@ let package = Package(
4445
.target(name: "Tracing")
4546
]
4647
),
48+
.target(
49+
name: "TracingTestKit",
50+
dependencies: [
51+
.target(name: "Tracing")
52+
]
53+
),
54+
.testTarget(
55+
name: "TracingTestKitTests",
56+
dependencies: [
57+
.target(name: "TracingTestKit")
58+
]
59+
),
4760

4861
// ==== --------------------------------------------------------------------------------------------------------
4962
// MARK: Wasm Support

Sources/Tracing/Tracer.swift

Lines changed: 3 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -349,7 +349,7 @@ public func withSpan<T, Instant: TracerInstant>(
349349
#endif
350350

351351
#if compiler(>=6.0)
352-
@_disfavoredOverload@available(*, deprecated, message: "Prefer #isolation version of this API")
352+
@_disfavoredOverload @available(*, deprecated, message: "Prefer #isolation version of this API")
353353
#endif
354354
@available(macOS 10.15, iOS 13, tvOS 13, watchOS 6, *) // for TaskLocal ServiceContext
355355
public func withSpan<T, Instant: TracerInstant>(
@@ -425,7 +425,7 @@ public func withSpan<T>(
425425
#endif
426426

427427
#if compiler(>=6.0)
428-
@_disfavoredOverload@available(*, deprecated, message: "Prefer #isolation version of this API")
428+
@_disfavoredOverload @available(*, deprecated, message: "Prefer #isolation version of this API")
429429
#endif
430430
@available(macOS 10.15, iOS 13, tvOS 13, watchOS 6, *) // for TaskLocal ServiceContext
431431
public func withSpan<T>(
@@ -502,7 +502,7 @@ public func withSpan<T>(
502502
#endif
503503

504504
#if compiler(>=6.0)
505-
@_disfavoredOverload@available(*, deprecated, message: "Prefer #isolation version of this API")
505+
@_disfavoredOverload @available(*, deprecated, message: "Prefer #isolation version of this API")
506506
#endif
507507
@available(macOS 10.15, iOS 13, tvOS 13, watchOS 6, *)
508508
public func withSpan<T>(

Sources/Tracing/TracerProtocol+Legacy.swift

Lines changed: 3 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -341,7 +341,7 @@ extension LegacyTracer {
341341

342342
#if compiler(>=6.0)
343343
// swift-format-ignore: Spacing // fights with formatter
344-
@_disfavoredOverload@available(*, deprecated, message: "Prefer #isolation version of this API")
344+
@_disfavoredOverload @available(*, deprecated, message: "Prefer #isolation version of this API")
345345
#endif
346346
public func withAnySpan<T, Instant: TracerInstant>(
347347
_ operationName: String,
@@ -432,7 +432,7 @@ extension LegacyTracer {
432432

433433
#if compiler(>=6.0)
434434
// swift-format-ignore: Spacing // fights with formatter
435-
@_disfavoredOverload@available(*, deprecated, message: "Prefer #isolation version of this API")
435+
@_disfavoredOverload @available(*, deprecated, message: "Prefer #isolation version of this API")
436436
#endif
437437
public func withAnySpan<T>(
438438
_ operationName: String,
@@ -631,7 +631,7 @@ extension Tracer {
631631

632632
#if compiler(>=6.0)
633633
// swift-format-ignore: Spacing // fights with formatter
634-
@_disfavoredOverload@available(*, deprecated, message: "Prefer #isolation version of this API")
634+
@_disfavoredOverload @available(*, deprecated, message: "Prefer #isolation version of this API")
635635
#endif
636636
public func withAnySpan<T>(
637637
_ operationName: String,

Sources/Tracing/TracerProtocol.swift

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -292,7 +292,7 @@ extension Tracer {
292292

293293
#if compiler(>=6.0)
294294
// swift-format-ignore: Spacing // fights with formatter
295-
@_disfavoredOverload@available(*, deprecated, message: "Prefer #isolation version of this API")
295+
@_disfavoredOverload @available(*, deprecated, message: "Prefer #isolation version of this API")
296296
#endif
297297
public func withSpan<T>(
298298
_ operationName: String,
@@ -382,7 +382,7 @@ extension Tracer {
382382

383383
#if compiler(>=6.0)
384384
// swift-format-ignore: Spacing // fights with formatter
385-
@_disfavoredOverload@available(*, deprecated, message: "Prefer #isolation version of this API")
385+
@_disfavoredOverload @available(*, deprecated, message: "Prefer #isolation version of this API")
386386
#endif
387387
public func withSpan<T>(
388388
_ operationName: String,

Sources/TracingTestKit/TestSpan.swift

Lines changed: 160 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,160 @@
1+
//===----------------------------------------------------------------------===//
2+
//
3+
// This source file is part of the Swift Distributed Tracing open source project
4+
//
5+
// Copyright (c) 2025 Apple Inc. and the Swift Distributed Tracing project authors
6+
// Licensed under Apache License v2.0
7+
//
8+
// See LICENSE.txt for license information
9+
// See CONTRIBUTORS.txt for the list of Swift Distributed Tracing project authors
10+
//
11+
// SPDX-License-Identifier: Apache-2.0
12+
//
13+
//===----------------------------------------------------------------------===//
14+
15+
@_spi(Locking) import Instrumentation
16+
import Tracing
17+
18+
public struct TestSpan: Span {
19+
public let context: ServiceContext
20+
public let spanContext: TestSpanContext
21+
public let startInstant: any TracerInstant
22+
23+
init(
24+
operationName: String,
25+
context: ServiceContext,
26+
spanContext: TestSpanContext,
27+
startInstant: any TracerInstant,
28+
onEnd: @escaping @Sendable (FinishedTestSpan) -> Void
29+
) {
30+
self._operationName = LockedValueBox(operationName)
31+
self.context = context
32+
self.spanContext = spanContext
33+
self.startInstant = startInstant
34+
self.onEnd = onEnd
35+
}
36+
37+
public var isRecording: Bool {
38+
_isRecording.withValue(\.self)
39+
}
40+
41+
public var operationName: String {
42+
get {
43+
_operationName.withValue(\.self)
44+
}
45+
nonmutating set {
46+
assertIsRecording()
47+
_operationName.withValue { $0 = newValue }
48+
}
49+
}
50+
51+
public var attributes: SpanAttributes {
52+
get {
53+
_attributes.withValue(\.self)
54+
}
55+
nonmutating set {
56+
assertIsRecording()
57+
_attributes.withValue { $0 = newValue }
58+
}
59+
}
60+
61+
public var events: [SpanEvent] {
62+
_events.withValue(\.self)
63+
}
64+
65+
public func addEvent(_ event: SpanEvent) {
66+
assertIsRecording()
67+
_events.withValue { $0.append(event) }
68+
}
69+
70+
public var links: [SpanLink] {
71+
_links.withValue(\.self)
72+
}
73+
74+
public func addLink(_ link: SpanLink) {
75+
assertIsRecording()
76+
_links.withValue { $0.append(link) }
77+
}
78+
79+
public var errors: [RecordedError] {
80+
_errors.withValue(\.self)
81+
}
82+
83+
public func recordError(
84+
_ error: any Error,
85+
attributes: SpanAttributes,
86+
at instant: @autoclosure () -> some TracerInstant
87+
) {
88+
assertIsRecording()
89+
_errors.withValue {
90+
$0.append(RecordedError(error: error, attributes: attributes, instant: instant()))
91+
}
92+
}
93+
94+
public var status: SpanStatus? {
95+
_status.withValue(\.self)
96+
}
97+
98+
public func setStatus(_ status: SpanStatus) {
99+
assertIsRecording()
100+
_status.withValue { $0 = status }
101+
}
102+
103+
public func end(at instant: @autoclosure () -> some TracerInstant) {
104+
assertIsRecording()
105+
let finishedSpan = FinishedTestSpan(
106+
operationName: operationName,
107+
context: context,
108+
spanContext: spanContext,
109+
startInstant: startInstant,
110+
endInstant: instant(),
111+
attributes: attributes,
112+
events: events,
113+
links: links,
114+
errors: errors,
115+
status: status
116+
)
117+
_isRecording.withValue { $0 = false }
118+
onEnd(finishedSpan)
119+
}
120+
121+
public struct RecordedError: Sendable {
122+
public let error: Error
123+
public let attributes: SpanAttributes
124+
public let instant: any TracerInstant
125+
}
126+
127+
private let _operationName: LockedValueBox<String>
128+
private let _attributes = LockedValueBox<SpanAttributes>([:])
129+
private let _events = LockedValueBox<[SpanEvent]>([])
130+
private let _links = LockedValueBox<[SpanLink]>([])
131+
private let _errors = LockedValueBox<[RecordedError]>([])
132+
private let _status = LockedValueBox<SpanStatus?>(nil)
133+
private let _isRecording = LockedValueBox<Bool>(true)
134+
private let onEnd: @Sendable (FinishedTestSpan) -> Void
135+
136+
private func assertIsRecording(
137+
file: StaticString = #file,
138+
line: UInt = #line
139+
) {
140+
assert(
141+
_isRecording.withValue(\.self) == true,
142+
"Attempted to mutate already ended span.",
143+
file: file,
144+
line: line
145+
)
146+
}
147+
}
148+
149+
public struct FinishedTestSpan: Sendable {
150+
public let operationName: String
151+
public let context: ServiceContext
152+
public let spanContext: TestSpanContext
153+
public let startInstant: any TracerInstant
154+
public let endInstant: any TracerInstant
155+
public let attributes: SpanAttributes
156+
public let events: [SpanEvent]
157+
public let links: [SpanLink]
158+
public let errors: [TestSpan.RecordedError]
159+
public let status: SpanStatus?
160+
}
Lines changed: 42 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,42 @@
1+
//===----------------------------------------------------------------------===//
2+
//
3+
// This source file is part of the Swift Distributed Tracing open source project
4+
//
5+
// Copyright (c) 2025 Apple Inc. and the Swift Distributed Tracing project authors
6+
// Licensed under Apache License v2.0
7+
//
8+
// See LICENSE.txt for license information
9+
// See CONTRIBUTORS.txt for the list of Swift Distributed Tracing project authors
10+
//
11+
// SPDX-License-Identifier: Apache-2.0
12+
//
13+
//===----------------------------------------------------------------------===//
14+
15+
import ServiceContextModule
16+
17+
public struct TestSpanContext: Sendable, Hashable {
18+
public let traceID: String
19+
public let spanID: String
20+
public let parentSpanID: String?
21+
22+
public init(traceID: String, spanID: String, parentSpanID: String?) {
23+
self.traceID = traceID
24+
self.spanID = spanID
25+
self.parentSpanID = parentSpanID
26+
}
27+
}
28+
29+
extension ServiceContext {
30+
var testSpanContext: TestSpanContext? {
31+
get {
32+
self[TestSpanContextKey.self]
33+
}
34+
set {
35+
self[TestSpanContextKey.self] = newValue
36+
}
37+
}
38+
}
39+
40+
private struct TestSpanContextKey: ServiceContextKey {
41+
typealias Value = TestSpanContext
42+
}

0 commit comments

Comments
 (0)