Skip to content

Add the current configuration to Event.Context. #677

New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Merged
merged 1 commit into from
Sep 11, 2024
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
4 changes: 2 additions & 2 deletions Sources/Testing/ABI/EntryPoints/EntryPoint.swift
Original file line number Diff line number Diff line change
Expand Up @@ -47,12 +47,12 @@ func entryPoint(passing args: __CommandLineArguments_v0?, eventHandler: Event.Ha
}
oldEventHandler(event, context)
}
configuration.verbosity = args.verbosity

#if !SWT_NO_FILE_IO
// Configure the event recorder to write events to stderr.
var options = Event.ConsoleOutputRecorder.Options()
options = .for(.stderr)
options.verbosity = args.verbosity
let eventRecorder = Event.ConsoleOutputRecorder(options: options) { string in
try? FileHandle.stderr.write(string)
}
Expand Down Expand Up @@ -91,7 +91,7 @@ func entryPoint(passing args: __CommandLineArguments_v0?, eventHandler: Event.Ha
// Post an event for every discovered test. These events are turned into
// JSON objects if JSON output is enabled.
for test in tests {
Event.post(.testDiscovered, for: test, testCase: nil, configuration: configuration)
Event.post(.testDiscovered, for: (test, nil), configuration: configuration)
}
} else {
// Run the tests.
Expand Down
26 changes: 19 additions & 7 deletions Sources/Testing/Events/Event.swift
Original file line number Diff line number Diff line change
Expand Up @@ -186,24 +186,28 @@ public struct Event: Sendable {
///
/// - Parameters:
/// - kind: The kind of event that occurred.
/// - test: The test for which the event occurred, if any.
/// - testCase: The test case for which the event occurred, if any.
/// - testAndTestCase: The test and test case for which the event occurred,
/// if any. The default value of this argument is ``Test/current`` and
/// ``Test/Case/current``.
/// - instant: The instant at which the event occurred. The default value
/// of this argument is `.now`.
/// - configuration: The configuration whose event handler should handle
/// this event. If `nil` is passed, the current task's configuration is
/// used, if known.
static func post(
_ kind: Kind,
for test: Test? = .current,
testCase: Test.Case? = .current,
for testAndTestCase: (Test?, Test.Case?) = currentTestAndTestCase(),
instant: Test.Clock.Instant = .now,
configuration: Configuration? = nil
) {
// Create both the event and its associated context here at same point, to
// ensure their task local-derived values are the same.
// ensure their task local-derived values are the same. Note we set the
// configuration property of Event.Context to nil initially because we'll
// reset it to the actual configuration that handles the event when we call
// handleEvent() later, so there's no need to make a copy of it yet.
let (test, testCase) = testAndTestCase
let event = Event(kind, testID: test?.id, testCaseID: testCase?.id, instant: instant)
let context = Event.Context(test: test, testCase: testCase)
let context = Event.Context(test: test, testCase: testCase, configuration: nil)
event._post(in: context, configuration: configuration)
}
}
Expand Down Expand Up @@ -239,16 +243,24 @@ extension Event {
/// functions), the value of this property is `nil`.
public var testCase: Test.Case?

/// The configuration handling the corresponding event, if any.
///
/// The value of this property is a copy of the configuration that owns the
/// currently-running event handler; to avoid reference cycles, the
/// ``Configuration/eventHandler`` property of this instance is cleared.
public var configuration: Configuration?

/// Initialize a new instance of this type.
///
/// - Parameters:
/// - test: The test for which this instance's associated event occurred,
/// if any.
/// - testCase: The test case for which this instance's associated event
/// occurred, if any.
init(test: Test? = .current, testCase: Test.Case? = .current) {
init(test: Test?, testCase: Test.Case?, configuration: Configuration?) {
self.test = test
self.testCase = testCase
self.configuration = configuration
}
}

Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -61,14 +61,6 @@ extension Event {
public var useSFSymbols = false
#endif

/// The level of verbosity of the output.
///
/// When the value of this property is greater than `0`, additional output
/// is provided. When the value of this property is less than `0`, some
/// output is suppressed. The exact effects of this property are
/// implementation-defined and subject to change.
public var verbosity = 0

/// Storage for ``tagColors``.
private var _tagColors = Tag.Color.predefined

Expand Down Expand Up @@ -309,7 +301,7 @@ extension Event.ConsoleOutputRecorder {
/// - Returns: Whether any output was produced and written to this instance's
/// destination.
@discardableResult public func record(_ event: borrowing Event, in context: borrowing Event.Context) -> Bool {
let messages = _humanReadableOutputRecorder.record(event, in: context, verbosity: options.verbosity)
let messages = _humanReadableOutputRecorder.record(event, in: context)
for message in messages {
let symbol = message.symbol?.stringValue(options: options) ?? " "

Expand Down Expand Up @@ -342,3 +334,13 @@ extension Event.ConsoleOutputRecorder {
return "\(symbol) \(message)\n"
}
}

// MARK: - Deprecated

extension Event.ConsoleOutputRecorder.Options {
@available(*, deprecated, message: "Set Configuration.verbosity instead.")
public var verbosity: Int {
get { 0 }
set {}
}
}
Original file line number Diff line number Diff line change
Expand Up @@ -202,19 +202,11 @@ extension Event.HumanReadableOutputRecorder {
/// - Parameters:
/// - event: The event to record.
/// - eventContext: The context associated with the event.
/// - verbosity: How verbose output should be. When the value of this
/// argument is greater than `0`, additional output is provided. When the
/// value of this argument is less than `0`, some output is suppressed.
/// The exact effects of this argument are implementation-defined and
/// subject to change.
///
/// - Returns: An array of zero or more messages that can be displayed to the
/// user.
@discardableResult public func record(
_ event: borrowing Event,
in eventContext: borrowing Event.Context,
verbosity: Int = 0
) -> [Message] {
@discardableResult public func record(_ event: borrowing Event, in eventContext: borrowing Event.Context) -> [Message] {
let verbosity = eventContext.configuration?.verbosity ?? 0
let test = eventContext.test
let testName = if let test {
if let displayName = test.displayName {
Expand All @@ -230,7 +222,7 @@ extension Event.HumanReadableOutputRecorder {
"«unknown»"
}
let instant = event.instant
let iterationCount = Configuration.current?.repetitionPolicy.maximumIterationCount
let iterationCount = eventContext.configuration?.repetitionPolicy.maximumIterationCount

// First, make any updates to the context/state associated with this
// recorder.
Expand Down Expand Up @@ -509,3 +501,12 @@ extension Event.HumanReadableOutputRecorder {
// MARK: - Codable

extension Event.HumanReadableOutputRecorder.Message: Codable {}

// MARK: - Deprecated

extension Event.HumanReadableOutputRecorder {
@available(*, deprecated, message: "Use record(_:in:) instead. Verbosity is now controlled by eventContext.configuration.verbosity.")
@discardableResult public func record(_ event: borrowing Event, in eventContext: borrowing Event.Context, verbosity: Int) -> [Message] {
record(event, in: eventContext)
}
}
5 changes: 4 additions & 1 deletion Sources/Testing/Running/Configuration+EventHandling.swift
Original file line number Diff line number Diff line change
Expand Up @@ -20,6 +20,9 @@ extension Configuration {
/// `eventHandler` but this method may also be used as a customization point
/// to change how the event is passed to the event handler.
func handleEvent(_ event: borrowing Event, in context: borrowing Event.Context) {
eventHandler(event, context)
var contextCopy = copy context
contextCopy.configuration = self
contextCopy.configuration?.eventHandler = { _, _ in }
eventHandler(event, contextCopy)
}
}
8 changes: 8 additions & 0 deletions Sources/Testing/Running/Configuration.swift
Original file line number Diff line number Diff line change
Expand Up @@ -195,6 +195,14 @@ public struct Configuration: Sendable {
}
#endif

/// How verbose human-readable output should be.
///
/// When the value of this property is greater than `0`, additional output
/// is provided. When the value of this property is less than `0`, some
/// output is suppressed. The exact effects of this property are determined by
/// the instance's event handler.
public var verbosity = 0

// MARK: - Test selection

/// The test filter to which tests should be filtered when run.
Expand Down
13 changes: 13 additions & 0 deletions Sources/Testing/Running/Runner.RuntimeState.swift
Original file line number Diff line number Diff line change
Expand Up @@ -222,3 +222,16 @@ extension Test.Case {
return try await Runner.RuntimeState.$current.withValue(runtimeState, operation: body)
}
}

/// Get the current test and test case in a single operation.
///
/// - Returns: The current test and test case.
///
/// This function is more efficient than calling both ``Test/current`` and
/// ``Test/Case/current``.
func currentTestAndTestCase() -> (Test?, Test.Case?) {
guard let state = Runner.RuntimeState.current else {
return (nil, nil)
}
return (state.test, state.testCase)
}
26 changes: 13 additions & 13 deletions Sources/Testing/Running/Runner.swift
Original file line number Diff line number Diff line change
Expand Up @@ -171,18 +171,18 @@ extension Runner {

// Determine what action to take for this step.
if let step = stepGraph.value {
Event.post(.planStepStarted(step), for: step.test, configuration: configuration)
Event.post(.planStepStarted(step), for: (step.test, nil), configuration: configuration)

// Determine what kind of event to send for this step based on its action.
switch step.action {
case .run:
Event.post(.testStarted, for: step.test, configuration: configuration)
Event.post(.testStarted, for: (step.test, nil), configuration: configuration)
shouldSendTestEnded = true
case let .skip(skipInfo):
Event.post(.testSkipped(skipInfo), for: step.test, configuration: configuration)
Event.post(.testSkipped(skipInfo), for: (step.test, nil), configuration: configuration)
shouldSendTestEnded = false
case let .recordIssue(issue):
Event.post(.issueRecorded(issue), for: step.test, configuration: configuration)
Event.post(.issueRecorded(issue), for: (step.test, nil), configuration: configuration)
shouldSendTestEnded = false
}
} else {
Expand All @@ -191,9 +191,9 @@ extension Runner {
defer {
if let step = stepGraph.value {
if shouldSendTestEnded {
Event.post(.testEnded, for: step.test, configuration: configuration)
Event.post(.testEnded, for: (step.test, nil), configuration: configuration)
}
Event.post(.planStepEnded(step), for: step.test, configuration: configuration)
Event.post(.planStepEnded(step), for: (step.test, nil), configuration: configuration)
}
}

Expand Down Expand Up @@ -327,9 +327,9 @@ extension Runner {
// Exit early if the task has already been cancelled.
try Task.checkCancellation()

Event.post(.testCaseStarted, for: step.test, testCase: testCase, configuration: configuration)
Event.post(.testCaseStarted, for: (step.test, testCase), configuration: configuration)
defer {
Event.post(.testCaseEnded, for: step.test, testCase: testCase, configuration: configuration)
Event.post(.testCaseEnded, for: (step.test, testCase), configuration: configuration)
}

await Test.Case.withCurrent(testCase) {
Expand Down Expand Up @@ -386,19 +386,19 @@ extension Runner {
// Post an event for every test in the test plan being run. These events
// are turned into JSON objects if JSON output is enabled.
for test in runner.plan.steps.lazy.map(\.test) {
Event.post(.testDiscovered, for: test, testCase: nil, configuration: runner.configuration)
Event.post(.testDiscovered, for: (test, nil), configuration: runner.configuration)
}

Event.post(.runStarted, for: nil, testCase: nil, configuration: runner.configuration)
Event.post(.runStarted, for: (nil, nil), configuration: runner.configuration)
defer {
Event.post(.runEnded, for: nil, testCase: nil, configuration: runner.configuration)
Event.post(.runEnded, for: (nil, nil), configuration: runner.configuration)
}

let repetitionPolicy = runner.configuration.repetitionPolicy
for iterationIndex in 0 ..< repetitionPolicy.maximumIterationCount {
Event.post(.iterationStarted(iterationIndex), for: nil, testCase: nil, configuration: runner.configuration)
Event.post(.iterationStarted(iterationIndex), for: (nil, nil), configuration: runner.configuration)
defer {
Event.post(.iterationEnded(iterationIndex), for: nil, testCase: nil, configuration: runner.configuration)
Event.post(.iterationEnded(iterationIndex), for: (nil, nil), configuration: runner.configuration)
}

await withTaskGroup(of: Void.self) { [runner] taskGroup in
Expand Down
16 changes: 6 additions & 10 deletions Tests/TestingTests/EventRecorderTests.swift
Original file line number Diff line number Diff line change
Expand Up @@ -97,15 +97,13 @@ struct EventRecorderTests {
func verboseOutput() async throws {
let stream = Stream()

var options = Event.ConsoleOutputRecorder.Options()
options.verbosity = 1

var configuration = Configuration()
configuration.deliverExpectationCheckedEvents = true
let eventRecorder = Event.ConsoleOutputRecorder(options: options, writingUsing: stream.write)
let eventRecorder = Event.ConsoleOutputRecorder(writingUsing: stream.write)
configuration.eventHandler = { event, context in
eventRecorder.record(event, in: context)
}
configuration.verbosity = 1

await runTest(for: WrittenTests.self, configuration: configuration)

Expand All @@ -124,15 +122,13 @@ struct EventRecorderTests {
func quietOutput() async throws {
let stream = Stream()

var options = Event.ConsoleOutputRecorder.Options()
options.verbosity = -1

var configuration = Configuration()
configuration.deliverExpectationCheckedEvents = true
let eventRecorder = Event.ConsoleOutputRecorder(options: options, writingUsing: stream.write)
let eventRecorder = Event.ConsoleOutputRecorder(writingUsing: stream.write)
configuration.eventHandler = { event, context in
eventRecorder.record(event, in: context)
}
configuration.verbosity = -1

await runTest(for: WrittenTests.self, configuration: configuration)

Expand Down Expand Up @@ -364,7 +360,7 @@ struct EventRecorderTests {
func humanReadableRecorderCountsIssuesWithoutTests() {
let issue = Issue(kind: .unconditional, comments: [], sourceContext: .init())
let event = Event(.issueRecorded(issue), testID: nil, testCaseID: nil)
let context = Event.Context(test: nil, testCase: nil)
let context = Event.Context(test: nil, testCase: nil, configuration: nil)

let recorder = Event.HumanReadableOutputRecorder()
let messages = recorder.record(event, in: context)
Expand All @@ -379,7 +375,7 @@ struct EventRecorderTests {
func junitRecorderCountsIssuesWithoutTests() async throws {
let issue = Issue(kind: .unconditional, comments: [], sourceContext: .init())
let event = Event(.issueRecorded(issue), testID: nil, testCaseID: nil)
let context = Event.Context(test: nil, testCase: nil)
let context = Event.Context(test: nil, testCase: nil, configuration: nil)

let recorder = Event.JUnitXMLRecorder { string in
if string.contains("<testsuite") {
Expand Down
2 changes: 1 addition & 1 deletion Tests/TestingTests/EventTests.swift
Original file line number Diff line number Diff line change
Expand Up @@ -67,7 +67,7 @@ struct EventTests {

@Test("Event.Contexts's Codable Conformances")
func codable() async throws {
let eventContext = Event.Context()
let eventContext = Event.Context(test: .current, testCase: .current, configuration: .current)
let snapshot = Event.Context.Snapshot(snapshotting: eventContext)

let decoded = try JSON.encodeAndDecode(snapshot)
Expand Down
10 changes: 5 additions & 5 deletions Tests/TestingTests/SwiftPMTests.swift
Original file line number Diff line number Diff line change
Expand Up @@ -167,9 +167,9 @@ struct SwiftPMTests {
}
do {
let configuration = try configurationForEntryPoint(withArguments: ["PATH", "--xunit-output", temporaryFilePath])
let eventContext = Event.Context()
configuration.eventHandler(Event(.runStarted, testID: nil, testCaseID: nil), eventContext)
configuration.eventHandler(Event(.runEnded, testID: nil, testCaseID: nil), eventContext)
let eventContext = Event.Context(test: nil, testCase: nil, configuration: nil)
configuration.handleEvent(Event(.runStarted, testID: nil, testCaseID: nil), in: eventContext)
configuration.handleEvent(Event(.runEnded, testID: nil, testCaseID: nil), in: eventContext)
}

let fileHandle = try FileHandle(forReadingAtPath: temporaryFilePath)
Expand Down Expand Up @@ -236,12 +236,12 @@ struct SwiftPMTests {
do {
let configuration = try configurationForEntryPoint(withArguments: ["PATH", outputArgumentName, temporaryFilePath, versionArgumentName, version])
let test = Test {}
let eventContext = Event.Context(test: test)
let eventContext = Event.Context(test: test, testCase: nil, configuration: nil)

configuration.handleEvent(Event(.testDiscovered, testID: test.id, testCaseID: nil), in: eventContext)
configuration.handleEvent(Event(.runStarted, testID: nil, testCaseID: nil), in: eventContext)
do {
let eventContext = Event.Context(test: test)
let eventContext = Event.Context(test: test, testCase: nil, configuration: nil)
configuration.handleEvent(Event(.testStarted, testID: test.id, testCaseID: nil), in: eventContext)
configuration.handleEvent(Event(.testEnded, testID: test.id, testCaseID: nil), in: eventContext)
}
Expand Down