File: Issue%2BRecording.swift

package info (click to toggle)
swiftlang 6.0.3-2
  • links: PTS, VCS
  • area: main
  • in suites: forky, sid, trixie
  • size: 2,519,992 kB
  • sloc: cpp: 9,107,863; ansic: 2,040,022; asm: 1,135,751; python: 296,500; objc: 82,456; f90: 60,502; lisp: 34,951; pascal: 19,946; sh: 18,133; perl: 7,482; ml: 4,937; javascript: 4,117; makefile: 3,840; awk: 3,535; xml: 914; fortran: 619; cs: 573; ruby: 573
file content (276 lines) | stat: -rw-r--r-- 11,131 bytes parent folder | download
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
202
203
204
205
206
207
208
209
210
211
212
213
214
215
216
217
218
219
220
221
222
223
224
225
226
227
228
229
230
231
232
233
234
235
236
237
238
239
240
241
242
243
244
245
246
247
248
249
250
251
252
253
254
255
256
257
258
259
260
261
262
263
264
265
266
267
268
269
270
271
272
273
274
275
276
//
// This source file is part of the Swift.org open source project
//
// Copyright (c) 2023 Apple Inc. and the Swift project authors
// Licensed under Apache License v2.0 with Runtime Library Exception
//
// See https://swift.org/LICENSE.txt for license information
// See https://swift.org/CONTRIBUTORS.txt for Swift project authors
//

extension Issue {
  /// The known issue matcher, as set by `withKnownIssue()`, associated with the
  /// current task.
  ///
  /// If there is no call to `withKnownIssue()` executing on the current task,
  /// the value of this property is `nil`.
  @TaskLocal
  static var currentKnownIssueMatcher: KnownIssueMatcher?

  /// Record a new issue with the specified properties.
  ///
  /// - Parameters:
  ///   - kind: The kind of issue.
  ///   - comments: An array of comments describing the issue. This array may be
  ///     empty.
  ///   - backtrace: The backtrace of the issue, if available. This value is
  ///     used to construct an instance of ``SourceContext``.
  ///   - sourceLocation: The source location of the issue. This value is used
  ///     to construct an instance of ``SourceContext``.
  ///   - configuration: The test configuration to use when recording the issue.
  ///     The default value is ``Configuration/current``.
  ///
  /// - Returns: The issue that was recorded.
  @discardableResult
  static func record(_ kind: Kind, comments: [Comment], backtrace: Backtrace?, sourceLocation: SourceLocation, configuration: Configuration? = nil) -> Self {
    let sourceContext = SourceContext(backtrace: backtrace, sourceLocation: sourceLocation)
    return record(kind, comments: comments, sourceContext: sourceContext, configuration: configuration)
  }

  /// Record a new issue with the specified properties.
  ///
  /// - Parameters:
  ///   - kind: The kind of issue.
  ///   - comments: An array of comments describing the issue. This array may be
  ///     empty.
  ///   - sourceContext: The source context of the issue.
  ///   - configuration: The test configuration to use when recording the issue.
  ///     The default value is ``Configuration/current``.
  ///
  /// - Returns: The issue that was recorded.
  @discardableResult
  static func record(_ kind: Kind, comments: [Comment], sourceContext: SourceContext, configuration: Configuration? = nil) -> Self {
    let issue = Issue(kind: kind, comments: comments, sourceContext: sourceContext)
    return issue.record(configuration: configuration)
  }

  /// Record this issue by wrapping it in an ``Event`` and passing it to the
  /// current event handler.
  ///
  /// - Parameters:
  ///   - configuration: The test configuration to use when recording the issue.
  ///     The default value is ``Configuration/current``.
  ///
  /// - Returns: The issue that was recorded (`self` or a modified copy of it.)
  @discardableResult
  func record(configuration: Configuration? = nil) -> Self {
    // If this issue is a caught error of kind SystemError, reinterpret it as a
    // testing system issue instead (per the documentation for SystemError.)
    if case let .errorCaught(error) = kind, let error = error as? SystemError {
      var selfCopy = self
      selfCopy.kind = .system
      selfCopy.comments.append(Comment(rawValue: String(describing: error)))
      return selfCopy.record(configuration: configuration)
    }

    // If this issue matches via the known issue matcher, set a copy of it to be
    // known and record the copy instead.
    if !isKnown, let issueMatcher = Self.currentKnownIssueMatcher, issueMatcher(self) {
      var selfCopy = self
      selfCopy.isKnown = true
      return selfCopy.record(configuration: configuration)
    }

    Event.post(.issueRecorded(self), configuration: configuration)

    if !isKnown {
      // Since this is not a known issue, invoke the failure breakpoint.
      //
      // Do this after posting the event above, to allow the issue to be printed
      // to the console first (assuming the event handler does this), since that
      // can help explain the failure.
      failureBreakpoint()
    }

    return self
  }

  /// Record an issue when a running test fails unexpectedly.
  ///
  /// - Parameters:
  ///   - comment: A comment describing the expectation.
  ///   - sourceLocation: The source location to which the issue should be
  ///     attributed.
  ///
  /// - Returns: The issue that was recorded.
  ///
  /// Use this function if, while running a test, an issue occurs that cannot be
  /// represented as an expectation (using the ``expect(_:_:sourceLocation:)``
  /// or ``require(_:_:sourceLocation:)-5l63q`` macros.)
  @discardableResult public static func record(
    _ comment: Comment? = nil,
    sourceLocation: SourceLocation = #_sourceLocation
  ) -> Self {
    let sourceContext = SourceContext(backtrace: .current(), sourceLocation: sourceLocation)
    let issue = Issue(kind: .unconditional, comments: Array(comment), sourceContext: sourceContext)
    return issue.record()
  }
}

// MARK: - Recording issues for errors

extension Issue {
  /// Record a new issue when a running test unexpectedly catches an error.
  ///
  /// - Parameters:
  ///   - error: The error that caused the issue.
  ///   - comment: A comment describing the expectation.
  ///   - sourceLocation: The source location to which the issue should be
  ///     attributed.
  ///
  /// - Returns: The issue that was recorded.
  ///
  /// This function can be used if an unexpected error is caught while running a
  /// test and it should be treated as a test failure. If an error is thrown
  /// from a test function, it is automatically recorded as an issue and this
  /// function does not need to be used.
  @discardableResult public static func record(
    _ error: any Error,
    _ comment: Comment? = nil,
    sourceLocation: SourceLocation = #_sourceLocation
  ) -> Self {
    let backtrace = Backtrace(forFirstThrowOf: error) ?? Backtrace.current()
    let sourceContext = SourceContext(backtrace: backtrace, sourceLocation: sourceLocation)
    let issue = Issue(kind: .errorCaught(error), comments: Array(comment), sourceContext: sourceContext)
    return issue.record()
  }

  /// Catch any error thrown from a closure and record it as an issue instead of
  /// allowing it to propagate to the caller.
  ///
  /// - Parameters:
  ///   - sourceLocation: The source location to attribute any caught error to.
  ///   - configuration: The test configuration to use when recording an issue.
  ///     The default value is ``Configuration/current``.
  ///   - body: A closure that might throw an error.
  ///
  /// - Returns: The issue representing the caught error, if any error was
  ///   caught, otherwise `nil`.
  @discardableResult
  static func withErrorRecording(
    at sourceLocation: SourceLocation,
    configuration: Configuration? = nil,
    _ body: () throws -> Void
  ) -> (any Error)? {
    // Ensure that we are capturing backtraces for errors before we start
    // expecting to see them.
    Backtrace.startCachingForThrownErrors()
    defer {
      Backtrace.flushThrownErrorCache()
    }

    do {
      try body()
    } catch is ExpectationFailedError {
      // This error is thrown by expectation checking functions to indicate a
      // condition evaluated to `false`. Those functions record their own issue,
      // so we don't need to record another one redundantly.
    } catch {
      Issue.record(
        .errorCaught(error),
        comments: [],
        backtrace: Backtrace(forFirstThrowOf: error),
        sourceLocation: sourceLocation,
        configuration: configuration
      )
      return error
    }

    return nil
  }

  /// Catch any error thrown from an asynchronous closure and record it as an
  /// issue instead of allowing it to propagate to the caller.
  ///
  /// - Parameters:
  ///   - sourceLocation: The source location to attribute any caught error to.
  ///   - configuration: The test configuration to use when recording an issue.
  ///     The default value is ``Configuration/current``.
  ///   - isolation: The actor to which `body` is isolated, if any.
  ///   - body: An asynchronous closure that might throw an error.
  ///
  /// - Returns: The issue representing the caught error, if any error was
  ///   caught, otherwise `nil`.
  @discardableResult
  static func withErrorRecording(
    at sourceLocation: SourceLocation,
    configuration: Configuration? = nil,
    isolation: isolated (any Actor)? = #isolation,
    _ body: () async throws -> Void
  ) async -> (any Error)? {
    // Ensure that we are capturing backtraces for errors before we start
    // expecting to see them.
    Backtrace.startCachingForThrownErrors()
    defer {
      Backtrace.flushThrownErrorCache()
    }

    do {
      try await body()
    } catch is ExpectationFailedError {
      // This error is thrown by expectation checking functions to indicate a
      // condition evaluated to `false`. Those functions record their own issue,
      // so we don't need to record another one redundantly.
    } catch {
      Issue.record(
        .errorCaught(error),
        comments: [],
        backtrace: Backtrace(forFirstThrowOf: error),
        sourceLocation: sourceLocation,
        configuration: configuration
      )
      return error
    }

    return nil
  }
}

// MARK: - Debugging failures

/// A unique value used by ``failureBreakpoint()``.
@usableFromInline nonisolated(unsafe) var failureBreakpointValue = 0

/// A function called by the testing library when a failure occurs.
///
/// Whenever a test failure (specifically, a non-known ``Issue``) is recorded,
/// the testing library calls this function synchronously. This facilitates
/// interactive debugging of test failures: If you add a symbolic breakpoint
/// specifying the name of this function, the debugger will pause execution and
/// allow you to inspect the process state.
///
/// When creating a symbolic breakpoint for this function, it is recommended
/// that you constrain it to the `Testing` module to avoid collisions with
/// similarly-named functions in other modules. If you are using LLDB, you can
/// use the following command to create the breakpoint:
///
/// ```lldb
/// (lldb) breakpoint set -s Testing -n "failureBreakpoint()"
/// ```
///
/// This function performs no action of its own. It is not part of the public
/// interface of the testing library, but it is exported and its symbol name
/// must remain stable.
@inline(never) @_optimize(none)
@usableFromInline
func failureBreakpoint() {
  // This function's body cannot be completely empty or else linker symbol
  // de-duplication will cause its symbol to be consolidated with that of
  // another, arbitrarily chosen empty function in this module. This linker
  // behavior can be disabled by passing the `-no_deduplicate` flag described in
  // ld(1), but that would disable it module-wide and sacrifice optimization
  // opportunities elsewhere. Instead, this function performs a trivial
  // operation on a usable-from-inline value, which the compiler must assume
  // cannot be optimized away.
  failureBreakpointValue = 1
}