Skip to content

Commit 91f2768

Browse files
committed
Merge remote-tracking branch 'origin/main' into main-6.2-merge
2 parents 3497bf2 + 8a6ed78 commit 91f2768

File tree

12 files changed

+184
-67
lines changed

12 files changed

+184
-67
lines changed

.github/ISSUE_TEMPLATE/01-bug-report.yml

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -9,7 +9,7 @@
99
name: 🪲 Report a bug
1010
description: >
1111
Report a deviation from expected or documented behavior.
12-
labels: [bug, triage-needed]
12+
labels: [bug]
1313
body:
1414
- type: markdown
1515
attributes:

.github/ISSUE_TEMPLATE/02-change-request.yml

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -9,7 +9,7 @@
99
name: 🌟 Request a change
1010
description: >
1111
Request a feature, API, improvement, or other change.
12-
labels: [enhancement, triage-needed]
12+
labels: [enhancement]
1313
body:
1414
- type: markdown
1515
attributes:

Documentation/CMake.md

Lines changed: 24 additions & 7 deletions
Original file line numberDiff line numberDiff line change
@@ -59,21 +59,38 @@ endif()
5959
## Add an entry point
6060

6161
You must include a source file in your test executable target with a
62-
`@main` entry point. The following example uses the SwiftPM entry point:
62+
`@main` entry point. The example main below requires the experimental
63+
`Extern` feature. The function `swt_abiv0_getEntryPoint` is exported
64+
from the swift-testing dylib. As such, its declaration could instead
65+
be written in a C header file with its own `module.modulemap`, or
66+
the runtime address could be obtained via
67+
[`dlsym()`](https://pubs.opengroup.org/onlinepubs/9799919799/functions/dlsym.html) or
68+
[`GetProcAddress()`](https://learn.microsoft.com/en-us/windows/win32/api/libloaderapi/nf-libloaderapi-getprocaddress).
6369

6470
```swift
65-
import Testing
71+
typealias EntryPoint = @convention(thin) @Sendable (_ configurationJSON: UnsafeRawBufferPointer?, _ recordHandler: @escaping @Sendable (_ recordJSON: UnsafeRawBufferPointer) -> Void) async throws -> Bool
72+
73+
@_extern(c, "swt_abiv0_getEntryPoint")
74+
func swt_abiv0_getEntryPoint() -> UnsafeRawPointer
6675

6776
@main struct Runner {
68-
static func main() async {
69-
await Testing.__swiftPMEntryPoint() as Never
77+
static func main() async throws {
78+
nonisolated(unsafe) let configurationJSON: UnsafeRawBufferPointer? = nil
79+
let recordHandler: @Sendable (UnsafeRawBufferPointer) -> Void = { _ in }
80+
81+
let entryPoint = unsafeBitCast(swt_abiv0_getEntryPoint(), to: EntryPoint.self)
82+
83+
if try await entryPoint(configurationJSON, recordHandler) {
84+
exit(EXIT_SUCCESS)
85+
} else {
86+
exit(EXIT_FAILURE)
87+
}
7088
}
7189
}
7290
```
7391

74-
> [!WARNING]
75-
> The entry point is expected to change to an entry point designed for other
76-
> build systems prior to the initial stable release of Swift Testing.
92+
For more information on the input configuration and output records of the ABI entry
93+
point, refer to the [ABI documentation](ABI/JSON.md).
7794

7895
## Integrate with CTest
7996

Package.swift

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -288,6 +288,8 @@ extension Array where Element == PackageDescription.SwiftSetting {
288288
// new-enough toolchain.
289289
.enableExperimentalFeature("AllowUnsafeAttribute"),
290290

291+
.enableUpcomingFeature("InferIsolatedConformances"),
292+
291293
// When building as a package, the macro plugin always builds as an
292294
// executable rather than a library.
293295
.define("SWT_NO_LIBRARY_MACRO_PLUGINS"),

Sources/CMakeLists.txt

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -66,7 +66,7 @@ if(SwiftTesting_MACRO STREQUAL "<auto>")
6666
if(NOT SwiftTesting_BuildMacrosAsExecutables)
6767
if(CMAKE_HOST_SYSTEM_NAME STREQUAL "Darwin")
6868
set(SwiftTesting_MACRO_PATH "${SwiftTesting_MACRO_INSTALL_PREFIX}/lib/swift/host/plugins/testing/libTestingMacros.dylib")
69-
elseif(CMAKE_HOST_SYSTEM_NAME STREQUAL "Linux" or CMAKE_HOST_SYSTEM_NAME STREQUAL "FreeBSD")
69+
elseif(CMAKE_HOST_SYSTEM_NAME STREQUAL "Linux" OR CMAKE_HOST_SYSTEM_NAME STREQUAL "FreeBSD")
7070
set(SwiftTesting_MACRO_PATH "${SwiftTesting_MACRO_INSTALL_PREFIX}/lib/swift/host/plugins/libTestingMacros.so")
7171
elseif(CMAKE_HOST_SYSTEM_NAME STREQUAL "Windows")
7272
set(SwiftTesting_MACRO_PATH "${SwiftTesting_MACRO_INSTALL_PREFIX}/bin/TestingMacros.dll")

Sources/Testing/Events/Recorder/Event.HumanReadableOutputRecorder.swift

Lines changed: 77 additions & 25 deletions
Original file line numberDiff line numberDiff line change
@@ -36,7 +36,7 @@ extension Event {
3636

3737
/// A type that contains mutable context for
3838
/// ``Event/ConsoleOutputRecorder``.
39-
private struct _Context {
39+
fileprivate struct Context {
4040
/// The instant at which the run started.
4141
var runStartInstant: Test.Clock.Instant?
4242

@@ -51,6 +51,17 @@ extension Event {
5151
/// The number of test suites started or skipped during the run.
5252
var suiteCount = 0
5353

54+
/// An enumeration describing the various keys which can be used in a test
55+
/// data graph for an output recorder.
56+
enum TestDataKey: Hashable {
57+
/// A string key, typically containing one key from the key path
58+
/// representation of a ``Test/ID`` instance.
59+
case string(String)
60+
61+
/// A test case ID.
62+
case testCaseID(Test.Case.ID)
63+
}
64+
5465
/// A type describing data tracked on a per-test basis.
5566
struct TestData {
5667
/// The instant at which the test started.
@@ -62,18 +73,15 @@ extension Event {
6273

6374
/// The number of known issues recorded for the test.
6475
var knownIssueCount = 0
65-
66-
/// The number of test cases for the test.
67-
var testCasesCount = 0
6876
}
6977

7078
/// Data tracked on a per-test basis.
71-
var testData = Graph<String, TestData?>()
79+
var testData = Graph<TestDataKey, TestData?>()
7280
}
7381

7482
/// This event recorder's mutable context about events it has received,
7583
/// which may be used to inform how subsequent events are written.
76-
private var _context = Locked(rawValue: _Context())
84+
private var _context = Locked(rawValue: Context())
7785

7886
/// Initialize a new human-readable event recorder.
7987
///
@@ -128,7 +136,9 @@ extension Event.HumanReadableOutputRecorder {
128136
/// - graph: The graph to walk while counting issues.
129137
///
130138
/// - Returns: A tuple containing the number of issues recorded in `graph`.
131-
private func _issueCounts(in graph: Graph<String, Event.HumanReadableOutputRecorder._Context.TestData?>?) -> (errorIssueCount: Int, warningIssueCount: Int, knownIssueCount: Int, totalIssueCount: Int, description: String) {
139+
private func _issueCounts(
140+
in graph: Graph<Event.HumanReadableOutputRecorder.Context.TestDataKey, Event.HumanReadableOutputRecorder.Context.TestData?>?
141+
) -> (errorIssueCount: Int, warningIssueCount: Int, knownIssueCount: Int, totalIssueCount: Int, description: String) {
132142
guard let graph else {
133143
return (0, 0, 0, 0, "")
134144
}
@@ -241,6 +251,7 @@ extension Event.HumanReadableOutputRecorder {
241251
0
242252
}
243253
let test = eventContext.test
254+
let keyPath = eventContext.keyPath
244255
let testName = if let test {
245256
if let displayName = test.displayName {
246257
if verbosity > 0 {
@@ -271,7 +282,7 @@ extension Event.HumanReadableOutputRecorder {
271282

272283
case .testStarted:
273284
let test = test!
274-
context.testData[test.id.keyPathRepresentation] = .init(startInstant: instant)
285+
context.testData[keyPath] = .init(startInstant: instant)
275286
if test.isSuite {
276287
context.suiteCount += 1
277288
} else {
@@ -287,23 +298,17 @@ extension Event.HumanReadableOutputRecorder {
287298
}
288299

289300
case let .issueRecorded(issue):
290-
let id: [String] = if let test {
291-
test.id.keyPathRepresentation
292-
} else {
293-
[]
294-
}
295-
var testData = context.testData[id] ?? .init(startInstant: instant)
301+
var testData = context.testData[keyPath] ?? .init(startInstant: instant)
296302
if issue.isKnown {
297303
testData.knownIssueCount += 1
298304
} else {
299305
let issueCount = testData.issueCount[issue.severity] ?? 0
300306
testData.issueCount[issue.severity] = issueCount + 1
301307
}
302-
context.testData[id] = testData
308+
context.testData[keyPath] = testData
303309

304310
case .testCaseStarted:
305-
let test = test!
306-
context.testData[test.id.keyPathRepresentation]?.testCasesCount += 1
311+
context.testData[keyPath] = .init(startInstant: instant)
307312

308313
default:
309314
// These events do not manipulate the context structure.
@@ -384,13 +389,12 @@ extension Event.HumanReadableOutputRecorder {
384389

385390
case .testEnded:
386391
let test = test!
387-
let id = test.id
388-
let testDataGraph = context.testData.subgraph(at: id.keyPathRepresentation)
392+
let testDataGraph = context.testData.subgraph(at: keyPath)
389393
let testData = testDataGraph?.value ?? .init(startInstant: instant)
390394
let issues = _issueCounts(in: testDataGraph)
391395
let duration = testData.startInstant.descriptionOfDuration(to: instant)
392-
let testCasesCount = if test.isParameterized {
393-
" with \(testData.testCasesCount.counting("test case"))"
396+
let testCasesCount = if test.isParameterized, let testDataGraph {
397+
" with \(testDataGraph.children.count.counting("test case"))"
394398
} else {
395399
""
396400
}
@@ -517,15 +521,37 @@ extension Event.HumanReadableOutputRecorder {
517521
break
518522
}
519523

524+
let status = verbosity > 0 ? " started" : ""
525+
520526
return [
521527
Message(
522528
symbol: .default,
523-
stringValue: "Passing \(arguments.count.counting("argument")) \(testCase.labeledArguments(includingQualifiedTypeNames: verbosity > 0)) to \(testName)"
529+
stringValue: "Test case passing \(arguments.count.counting("argument")) \(testCase.labeledArguments(includingQualifiedTypeNames: verbosity > 0)) to \(testName)\(status) started."
524530
)
525531
]
526532

527533
case .testCaseEnded:
528-
break
534+
guard verbosity > 0, let testCase = eventContext.testCase, testCase.isParameterized, let arguments = testCase.arguments else {
535+
break
536+
}
537+
538+
let testDataGraph = context.testData.subgraph(at: keyPath)
539+
let testData = testDataGraph?.value ?? .init(startInstant: instant)
540+
let issues = _issueCounts(in: testDataGraph)
541+
let duration = testData.startInstant.descriptionOfDuration(to: instant)
542+
543+
let message = if issues.errorIssueCount > 0 {
544+
Message(
545+
symbol: .fail,
546+
stringValue: "Test case passing \(arguments.count.counting("argument")) \(testCase.labeledArguments(includingQualifiedTypeNames: verbosity > 0)) to \(testName) failed after \(duration)\(issues.description)."
547+
)
548+
} else {
549+
Message(
550+
symbol: .pass(knownIssueCount: issues.knownIssueCount),
551+
stringValue: "Test case passing \(arguments.count.counting("argument")) \(testCase.labeledArguments(includingQualifiedTypeNames: verbosity > 0)) to \(testName) passed after \(duration)\(issues.description)."
552+
)
553+
}
554+
return [message]
529555

530556
case let .iterationEnded(index):
531557
guard let iterationStartInstant = context.iterationStartInstant else {
@@ -542,6 +568,7 @@ extension Event.HumanReadableOutputRecorder {
542568

543569
case .runEnded:
544570
let testCount = context.testCount
571+
let suiteCount = context.suiteCount
545572
let issues = _issueCounts(in: context.testData)
546573
let runStartInstant = context.runStartInstant ?? instant
547574
let duration = runStartInstant.descriptionOfDuration(to: instant)
@@ -550,14 +577,14 @@ extension Event.HumanReadableOutputRecorder {
550577
[
551578
Message(
552579
symbol: .fail,
553-
stringValue: "Test run with \(testCount.counting("test")) failed after \(duration)\(issues.description)."
580+
stringValue: "Test run with \(testCount.counting("test")) in \(suiteCount.counting("suite")) failed after \(duration)\(issues.description)."
554581
)
555582
]
556583
} else {
557584
[
558585
Message(
559586
symbol: .pass(knownIssueCount: issues.knownIssueCount),
560-
stringValue: "Test run with \(testCount.counting("test")) passed after \(duration)\(issues.description)."
587+
stringValue: "Test run with \(testCount.counting("test")) in \(suiteCount.counting("suite")) passed after \(duration)\(issues.description)."
561588
)
562589
]
563590
}
@@ -567,6 +594,31 @@ extension Event.HumanReadableOutputRecorder {
567594
}
568595
}
569596

597+
extension Test.ID {
598+
/// The key path in a test data graph representing this test ID.
599+
fileprivate var keyPath: some Collection<Event.HumanReadableOutputRecorder.Context.TestDataKey> {
600+
keyPathRepresentation.map { .string($0) }
601+
}
602+
}
603+
604+
extension Event.Context {
605+
/// The key path in a test data graph representing this event this context is
606+
/// associated with, including its test and/or test case IDs.
607+
fileprivate var keyPath: some Collection<Event.HumanReadableOutputRecorder.Context.TestDataKey> {
608+
var keyPath = [Event.HumanReadableOutputRecorder.Context.TestDataKey]()
609+
610+
if let test {
611+
keyPath.append(contentsOf: test.id.keyPath)
612+
613+
if let testCase {
614+
keyPath.append(.testCaseID(testCase.id))
615+
}
616+
}
617+
618+
return keyPath
619+
}
620+
}
621+
570622
// MARK: - Codable
571623

572624
extension Event.HumanReadableOutputRecorder.Message: Codable {}

Sources/Testing/Running/Configuration.TestFilter.swift

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -514,7 +514,7 @@ extension Configuration.TestFilter.Kind {
514514

515515
/// A protocol representing a value which can be filtered using
516516
/// ``Configuration/TestFilter-swift.struct``.
517-
private protocol _FilterableItem {
517+
private protocol _FilterableItem: Sendable {
518518
/// The test this item represents.
519519
var test: Test { get }
520520

Sources/Testing/SourceAttribution/CustomTestStringConvertible.swift

Lines changed: 6 additions & 6 deletions
Original file line numberDiff line numberDiff line change
@@ -42,9 +42,9 @@
4242
/// the default description of a value may not be adequately descriptive:
4343
///
4444
/// ```
45-
/// ◇ Passing argument food → .paella to isDelicious(_:)
46-
/// ◇ Passing argument food → .oden to isDelicious(_:)
47-
/// ◇ Passing argument food → .ragu to isDelicious(_:)
45+
/// ◇ Test case passing 1 argument food → .paella to isDelicious(_:) started.
46+
/// ◇ Test case passing 1 argument food → .oden to isDelicious(_:) started.
47+
/// ◇ Test case passing 1 argument food → .ragu to isDelicious(_:) started.
4848
/// ```
4949
///
5050
/// By adopting ``CustomTestStringConvertible``, customized descriptions can be
@@ -69,9 +69,9 @@
6969
/// ``testDescription`` property:
7070
///
7171
/// ```
72-
/// ◇ Passing argument food → paella valenciana to isDelicious(_:)
73-
/// ◇ Passing argument food → おでん to isDelicious(_:)
74-
/// ◇ Passing argument food → ragù alla bolognese to isDelicious(_:)
72+
/// ◇ Test case passing 1 argument food → paella valenciana to isDelicious(_:) started.
73+
/// ◇ Test case passing 1 argument food → おでん to isDelicious(_:) started.
74+
/// ◇ Test case passing 1 argument food → ragù alla bolognese to isDelicious(_:) started.
7575
/// ```
7676
///
7777
/// ## See Also

0 commit comments

Comments
 (0)