From c31d9ca9d1ab6f436be93dc6f3ece038d193c224 Mon Sep 17 00:00:00 2001 From: Jonathan Grynspan Date: Wed, 12 Jun 2024 15:44:52 -0400 Subject: [PATCH] Add an experimental console output prefix option for command-line output. MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit This PR adds the ability to specify a prefix that should be applied to all output from the command line when using `swift test`. For example, if you specify the prefix "`[WHEE]`", then you'll get output like: > [WHEE] ◇ Test run started. > [WHEE] ↳ Testing Library Version: ff2775952f346dd94757adffa7f7ba57bcc4b2d1 (modified) > [WHEE] ◇ Suite "My suite" started. (etc.) Since the `--experimental-console-output-prefix` argument is not recognized by Swift Package Manager, you can also specify the prefix using an environment variable, `"SWT_EXPERIMENTAL_CONSOLE_OUTPUT_PREFIX"`. Resolves #472. Resolves rdar://129677014. --- Sources/Testing/EntryPoints/EntryPoint.swift | 13 ++++++++++ .../Event.ConsoleOutputRecorder.swift | 14 ++++++++--- Tests/TestingTests/EventRecorderTests.swift | 25 +++++++++++++++++++ Tests/TestingTests/SwiftPMTests.swift | 18 +++++++++++++ 4 files changed, 67 insertions(+), 3 deletions(-) diff --git a/Sources/Testing/EntryPoints/EntryPoint.swift b/Sources/Testing/EntryPoints/EntryPoint.swift index a459c4d48..2ef207ce2 100644 --- a/Sources/Testing/EntryPoints/EntryPoint.swift +++ b/Sources/Testing/EntryPoints/EntryPoint.swift @@ -66,6 +66,9 @@ func entryPoint(passing args: __CommandLineArguments_v0?, eventHandler: Event.Ha var options = Event.ConsoleOutputRecorder.Options() options = .for(.stderr) options.verbosity = args.verbosity + if let prefix = args.experimentalConsoleOutputPrefix { + options.prefix = prefix + } let eventRecorder = Event.ConsoleOutputRecorder(options: options) { string in try? FileHandle.stderr.write(string) } @@ -228,6 +231,9 @@ public struct __CommandLineArguments_v0: Sendable { /// 0 JSON schema is finalized. public var experimentalEventStreamVersion: Int? + /// The value of the `--experimental-console-output-prefix` argument. + public var experimentalConsoleOutputPrefix: String? + /// The value(s) of the `--filter` argument. public var filter: [String]? @@ -341,6 +347,13 @@ func parseCommandLineArguments(from args: [String]) throws -> __CommandLineArgum result.quiet = true } + // Console output prefix + if let prefixIndex = args.firstIndex(of: "--experimental-console-output-prefix"), !isLastArgument(at: prefixIndex) { + result.experimentalConsoleOutputPrefix = args[args.index(after: prefixIndex)] + } else if let prefix = Environment.variable(named: "SWT_EXPERIMENTAL_CONSOLE_OUTPUT_PREFIX") { + result.experimentalConsoleOutputPrefix = prefix + } + // Filtering func filterValues(forArgumentsWithLabel label: String) -> [String] { args.indices.lazy diff --git a/Sources/Testing/Events/Recorder/Event.ConsoleOutputRecorder.swift b/Sources/Testing/Events/Recorder/Event.ConsoleOutputRecorder.swift index cfbf4cac8..451bf33af 100644 --- a/Sources/Testing/Events/Recorder/Event.ConsoleOutputRecorder.swift +++ b/Sources/Testing/Events/Recorder/Event.ConsoleOutputRecorder.swift @@ -97,6 +97,12 @@ extension Event { } } + /// A prefix to apply to all lines of text in the output. + /// + /// If set, the value of this property is written _before_ any symbol or + /// library-supplied text. + public var prefix: String? + public init() {} } @@ -309,15 +315,16 @@ extension Event.ConsoleOutputRecorder { @discardableResult public func record(_ event: borrowing Event, in context: borrowing Event.Context) -> Bool { let messages = _humanReadableOutputRecorder.record(event, in: context, verbosity: options.verbosity) for message in messages { + let prefix = options.prefix ?? "" let symbol = message.symbol?.stringValue(options: options) ?? " " if case .details = message.symbol, options.useANSIEscapeCodes, options.ansiColorBitDepth > 1 { // Special-case the detail symbol to apply grey to the entire line of // text instead of just the symbol. - write("\(_ansiEscapeCodePrefix)90m\(symbol) \(message.stringValue)\(_resetANSIEscapeCode)\n") + write("\(prefix)\(_ansiEscapeCodePrefix)90m\(symbol) \(message.stringValue)\(_resetANSIEscapeCode)\n") } else { let colorDots = context.test.map(\.tags).map { self.colorDots(for: $0) } ?? "" - write("\(symbol) \(colorDots)\(message.stringValue)\n") + write("\(prefix)\(symbol) \(colorDots)\(message.stringValue)\n") } } return !messages.isEmpty @@ -336,7 +343,8 @@ extension Event.ConsoleOutputRecorder { /// /// The caller is responsible for presenting this message to the user. static func warning(_ message: String, options: Event.ConsoleOutputRecorder.Options) -> String { + let prefix = options.prefix ?? "" let symbol = Event.Symbol.warning.stringValue(options: options) - return "\(symbol) \(message)\n" + return "\(prefix)\(symbol) \(message)\n" } } diff --git a/Tests/TestingTests/EventRecorderTests.swift b/Tests/TestingTests/EventRecorderTests.swift index dedf59ecd..f3f056cfe 100644 --- a/Tests/TestingTests/EventRecorderTests.swift +++ b/Tests/TestingTests/EventRecorderTests.swift @@ -145,6 +145,31 @@ struct EventRecorderTests { } } + @Test("Output with prefix on each line") + func prefixedOutput() async throws { + let stream = Stream() + let prefix = ">>What Fools These Prefixes Be<<" + + var options = Event.ConsoleOutputRecorder.Options() + options.prefix = prefix + + var configuration = Configuration() + configuration.deliverExpectationCheckedEvents = true + let eventRecorder = Event.ConsoleOutputRecorder(options: options, writingUsing: stream.write) + configuration.eventHandler = { event, context in + eventRecorder.record(event, in: context) + } + + await runTest(for: WrittenTests.self, configuration: configuration) + + let buffer = stream.buffer.rawValue + #expect(buffer.contains(prefix)) + + if testsWithSignificantIOAreEnabled { + print(buffer, terminator: "") + } + } + #if !os(Windows) @available(_regexAPI, *) @Test( diff --git a/Tests/TestingTests/SwiftPMTests.swift b/Tests/TestingTests/SwiftPMTests.swift index d17dcbab4..a18b68184 100644 --- a/Tests/TestingTests/SwiftPMTests.swift +++ b/Tests/TestingTests/SwiftPMTests.swift @@ -250,6 +250,24 @@ struct SwiftPMTests { #expect(eventRecords.count == 4) } #endif + @Test("--experimental-console-output-prefix argument") + func experimentalConsoleOutputPrefixArgument() throws { + let prefix = ">>What light through yonder prefix breaks?<<" + let args = try parseCommandLineArguments(from: ["PATH", "--experimental-console-output-prefix", prefix]) + #expect(args.experimentalConsoleOutputPrefix == prefix) + } + + @Test("SWT_EXPERIMENTAL_CONSOLE_OUTPUT_PREFIX environment variable") + func experimentalConsoleOutputPrefixEnvironmentVariable() throws { + let prefix = ">>My horse, my horse, my prefix for a horse!<<" + let oldValue = Environment.variable(named: "SWT_EXPERIMENTAL_CONSOLE_OUTPUT_PREFIX") + Environment.setVariable(prefix, named: "SWT_EXPERIMENTAL_CONSOLE_OUTPUT_PREFIX") + defer { + Environment.setVariable(oldValue, named: "SWT_EXPERIMENTAL_CONSOLE_OUTPUT_PREFIX") + } + let args = try parseCommandLineArguments(from: ["PATH"]) + #expect(args.experimentalConsoleOutputPrefix == prefix) + } #endif @Test("--repetitions argument (alone)")