Skip to content

Commit f5fd88e

Browse files
authored
Add an experimental CLI argument for passing a configuration value as JSON. (#402)
This PR adds a new CLI argument, `--experimental-configuration-path`, which can be used to specify the path to a file containing an instance of `__CommandLineArguments_v0` encoded as JSON. `/dev/stdin` or a named pipe can be specified if desired. Changes to SwiftPM will be needed to support passing this argument all the way through from `swift test`. ### Checklist: - [x] Code and documentation should follow the style of the [Style Guide](https://github.com/apple/swift-testing/blob/main/Documentation/StyleGuide.md). - [x] If public symbols are renamed or modified, DocC references should be updated.
1 parent d84ec9e commit f5fd88e

File tree

2 files changed

+47
-17
lines changed

2 files changed

+47
-17
lines changed

Diff for: Sources/Testing/EntryPoints/EntryPoint.swift

+40-17
Original file line numberDiff line numberDiff line change
@@ -192,9 +192,46 @@ func parseCommandLineArguments(from args: [String]) throws -> __CommandLineArgum
192192
// Do not consider the executable path AKA argv[0].
193193
let args = args.dropFirst()
194194

195+
func isLastArgument(at index: [String].Index) -> Bool {
196+
args.index(after: index) >= args.endIndex
197+
}
198+
199+
#if !SWT_NO_FILE_IO
200+
#if canImport(Foundation)
201+
// Configuration for the test run passed in as a JSON file (experimental)
202+
//
203+
// This argument should always be the first one we parse.
204+
//
205+
// NOTE: While the output event stream is opened later, it is necessary to
206+
// open the configuration file early (here) in order to correctly construct
207+
// the resulting __CommandLineArguments_v0 instance.
208+
if let configurationIndex = args.firstIndex(of: "--experimental-configuration-path"), !isLastArgument(at: configurationIndex) {
209+
let path = args[args.index(after: configurationIndex)]
210+
let file = try FileHandle(forReadingAtPath: path)
211+
let configurationJSON = try file.readToEnd()
212+
result = try configurationJSON.withUnsafeBufferPointer { configurationJSON in
213+
try JSON.decode(__CommandLineArguments_v0.self, from: .init(configurationJSON))
214+
}
215+
216+
// NOTE: We don't return early or block other arguments here: a caller is
217+
// allowed to pass a configuration AND --verbose and they'll both be
218+
// respected (it should be the least "surprising" outcome of passing both.)
219+
}
220+
221+
// Event stream output (experimental)
222+
if let eventOutputIndex = args.firstIndex(of: "--experimental-event-stream-output"), !isLastArgument(at: eventOutputIndex) {
223+
result.experimentalEventStreamOutput = args[args.index(after: eventOutputIndex)]
224+
}
225+
#endif
226+
227+
// XML output
228+
if let xunitOutputIndex = args.firstIndex(of: "--xunit-output"), !isLastArgument(at: xunitOutputIndex) {
229+
result.xunitOutput = args[args.index(after: xunitOutputIndex)]
230+
}
231+
#endif
232+
195233
if args.contains("--list-tests") {
196234
result.listTests = true
197-
return result // do not bother parsing the other arguments
198235
}
199236

200237
// Parallelization (on by default)
@@ -206,20 +243,6 @@ func parseCommandLineArguments(from args: [String]) throws -> __CommandLineArgum
206243
result.verbose = true
207244
}
208245

209-
#if !SWT_NO_FILE_IO
210-
// XML output
211-
if let xunitOutputIndex = args.firstIndex(of: "--xunit-output"), xunitOutputIndex < args.endIndex {
212-
result.xunitOutput = args[args.index(after: xunitOutputIndex)]
213-
}
214-
215-
#if canImport(Foundation)
216-
// Event stream output (experimental)
217-
if let eventOutputIndex = args.firstIndex(of: "--experimental-event-stream-output"), eventOutputIndex < args.endIndex {
218-
result.experimentalEventStreamOutput = args[args.index(after: eventOutputIndex)]
219-
}
220-
#endif
221-
#endif
222-
223246
// Filtering
224247
func filterValues(forArgumentsWithLabel label: String) -> [String] {
225248
args.indices.lazy
@@ -230,10 +253,10 @@ func parseCommandLineArguments(from args: [String]) throws -> __CommandLineArgum
230253
result.skip = filterValues(forArgumentsWithLabel: "--skip")
231254

232255
// Set up the iteration policy for the test run.
233-
if let repetitionsIndex = args.firstIndex(of: "--repetitions"), repetitionsIndex < args.endIndex {
256+
if let repetitionsIndex = args.firstIndex(of: "--repetitions"), !isLastArgument(at: repetitionsIndex) {
234257
result.repetitions = Int(args[args.index(after: repetitionsIndex)])
235258
}
236-
if let repeatUntilIndex = args.firstIndex(of: "--repeat-until"), repeatUntilIndex < args.endIndex {
259+
if let repeatUntilIndex = args.firstIndex(of: "--repeat-until"), !isLastArgument(at: repeatUntilIndex) {
237260
result.repeatUntil = args[args.index(after: repeatUntilIndex)]
238261
}
239262

Diff for: Tests/TestingTests/SwiftPMTests.swift

+7
Original file line numberDiff line numberDiff line change
@@ -132,6 +132,13 @@ struct SwiftPMTests {
132132
}
133133
}
134134

135+
@Test("--xunit-output argument (missing path)")
136+
func xunitOutputWithMissingPath() throws {
137+
// Test that a missing path doesn't read off the end of the argument array.
138+
let args = try parseCommandLineArguments(from: ["PATH", "--xunit-output"])
139+
#expect(args.xunitOutput == nil)
140+
}
141+
135142
@Test("--xunit-output argument (writes to file)")
136143
func xunitOutputIsWrittenToFile() throws {
137144
// Test that a file is opened when requested. Testing of the actual output

0 commit comments

Comments
 (0)