Skip to content

Commit 43751f8

Browse files
committed
Support command-line arguments specified as --foo=bar.
SwiftPM and `swift test` use Swift Argument Parser which allows developers to specify arguments of the form `--foo=bar`. Our bare-bones argument parser doesn't currently recognize that pattern, which means that the developer could write `--foo=bar` but get the wrong behavior. This PR adds support for that pattern by changing how we parse command-line arguments to allow for both `--foo bar` and `--foo=bar`.
1 parent 56506bf commit 43751f8

File tree

2 files changed

+72
-31
lines changed

2 files changed

+72
-31
lines changed

Sources/Testing/ABI/EntryPoints/EntryPoint.swift

Lines changed: 51 additions & 31 deletions
Original file line numberDiff line numberDiff line change
@@ -352,6 +352,36 @@ extension __CommandLineArguments_v0: Codable {
352352
}
353353
}
354354

355+
extension RandomAccessCollection<String> {
356+
/// Get the value of the command line argument named at the given index.
357+
///
358+
/// - Parameters:
359+
/// - key: The key or name of the argument, e.g. `"--attachments-path"`.
360+
///
361+
/// - Returns: The value of the argument named by `key`. If no value is
362+
/// available, returns `nil`.
363+
///
364+
/// This function handles arguments of the form `--key value` and
365+
/// `--key=value`. Other argument syntaxes are not supported.
366+
fileprivate func argumentValue(forKey key: String) -> String? {
367+
if let index = firstIndex(of: key) {
368+
let nextIndex = self.index(after: index)
369+
if nextIndex < endIndex {
370+
return self[nextIndex]
371+
}
372+
} else {
373+
// Find an element equal to something like "--foo=bar" and split it.
374+
let prefix = "\(key)="
375+
let index = self.firstIndex { $0.hasPrefix(prefix) }
376+
if let index, case let key = self[index], let equalsIndex = key.firstIndex(of: "=") {
377+
return String(key[equalsIndex...].dropFirst())
378+
}
379+
}
380+
381+
return nil
382+
}
383+
}
384+
355385
/// Initialize this instance given a sequence of command-line arguments passed
356386
/// from Swift Package Manager.
357387
///
@@ -366,10 +396,6 @@ func parseCommandLineArguments(from args: [String]) throws -> __CommandLineArgum
366396
// Do not consider the executable path AKA argv[0].
367397
let args = args.dropFirst()
368398

369-
func isLastArgument(at index: [String].Index) -> Bool {
370-
args.index(after: index) >= args.endIndex
371-
}
372-
373399
#if !SWT_NO_FILE_IO
374400
#if canImport(Foundation)
375401
// Configuration for the test run passed in as a JSON file (experimental)
@@ -379,9 +405,7 @@ func parseCommandLineArguments(from args: [String]) throws -> __CommandLineArgum
379405
// NOTE: While the output event stream is opened later, it is necessary to
380406
// open the configuration file early (here) in order to correctly construct
381407
// the resulting __CommandLineArguments_v0 instance.
382-
if let configurationIndex = args.firstIndex(of: "--configuration-path") ?? args.firstIndex(of: "--experimental-configuration-path"),
383-
!isLastArgument(at: configurationIndex) {
384-
let path = args[args.index(after: configurationIndex)]
408+
if let path = args.argumentValue(forKey: "--configuration-path") ?? args.argumentValue(forKey: "--experimental-configuration-path") {
385409
let file = try FileHandle(forReadingAtPath: path)
386410
let configurationJSON = try file.readToEnd()
387411
result = try configurationJSON.withUnsafeBufferPointer { configurationJSON in
@@ -394,24 +418,22 @@ func parseCommandLineArguments(from args: [String]) throws -> __CommandLineArgum
394418
}
395419

396420
// Event stream output
397-
if let eventOutputIndex = args.firstIndex(of: "--event-stream-output-path") ?? args.firstIndex(of: "--experimental-event-stream-output"),
398-
!isLastArgument(at: eventOutputIndex) {
399-
result.eventStreamOutputPath = args[args.index(after: eventOutputIndex)]
421+
if let path = args.argumentValue(forKey: "--event-stream-output-path") ?? args.argumentValue(forKey: "--experimental-event-stream-output") {
422+
result.eventStreamOutputPath = path
400423
}
424+
401425
// Event stream version
402426
do {
403-
var eventOutputVersionIndex: Array<String>.Index?
427+
var versionString: String?
404428
var allowExperimental = false
405-
eventOutputVersionIndex = args.firstIndex(of: "--event-stream-version")
406-
if eventOutputVersionIndex == nil {
407-
eventOutputVersionIndex = args.firstIndex(of: "--experimental-event-stream-version")
408-
if eventOutputVersionIndex != nil {
429+
versionString = args.argumentValue(forKey: "--event-stream-version")
430+
if versionString == nil {
431+
versionString = args.argumentValue(forKey: "--experimental-event-stream-version")
432+
if versionString != nil {
409433
allowExperimental = true
410434
}
411435
}
412-
if let eventOutputVersionIndex, !isLastArgument(at: eventOutputVersionIndex) {
413-
let versionString = args[args.index(after: eventOutputVersionIndex)]
414-
436+
if let versionString {
415437
// If the caller specified a version that could not be parsed, treat it as
416438
// an invalid argument.
417439
guard let eventStreamVersion = VersionNumber(versionString) else {
@@ -432,14 +454,13 @@ func parseCommandLineArguments(from args: [String]) throws -> __CommandLineArgum
432454
#endif
433455

434456
// XML output
435-
if let xunitOutputIndex = args.firstIndex(of: "--xunit-output"), !isLastArgument(at: xunitOutputIndex) {
436-
result.xunitOutput = args[args.index(after: xunitOutputIndex)]
457+
if let xunitOutputPath = args.argumentValue(forKey: "--xunit-output") {
458+
result.xunitOutput = xunitOutputPath
437459
}
438460

439461
// Attachment output
440-
if let attachmentsPathIndex = args.firstIndex(of: "--attachments-path") ?? args.firstIndex(of: "--experimental-attachments-path"),
441-
!isLastArgument(at: attachmentsPathIndex) {
442-
result.attachmentsPath = args[args.index(after: attachmentsPathIndex)]
462+
if let attachmentsPath = args.argumentValue(forKey: "--attachments-path") ?? args.argumentValue(forKey: "--experimental-attachments-path") {
463+
result.attachmentsPath = attachmentsPath
443464
}
444465
#endif
445466

@@ -457,13 +478,12 @@ func parseCommandLineArguments(from args: [String]) throws -> __CommandLineArgum
457478
}
458479

459480
// Whether or not to symbolicate backtraces in the event stream.
460-
if let symbolicateBacktracesIndex = args.firstIndex(of: "--symbolicate-backtraces"), !isLastArgument(at: symbolicateBacktracesIndex) {
461-
result.symbolicateBacktraces = args[args.index(after: symbolicateBacktracesIndex)]
481+
if let symbolicateBacktraces = args.argumentValue(forKey: "--symbolicate-backtraces") {
482+
result.symbolicateBacktraces = symbolicateBacktraces
462483
}
463484

464485
// Verbosity
465-
if let verbosityIndex = args.firstIndex(of: "--verbosity"), !isLastArgument(at: verbosityIndex),
466-
let verbosity = Int(args[args.index(after: verbosityIndex)]) {
486+
if let verbosity = args.argumentValue(forKey: "--verbosity").flatMap(Int.init) {
467487
result.verbosity = verbosity
468488
}
469489
if args.contains("--verbose") || args.contains("-v") {
@@ -492,11 +512,11 @@ func parseCommandLineArguments(from args: [String]) throws -> __CommandLineArgum
492512
}
493513

494514
// Set up the iteration policy for the test run.
495-
if let repetitionsIndex = args.firstIndex(of: "--repetitions"), !isLastArgument(at: repetitionsIndex) {
496-
result.repetitions = Int(args[args.index(after: repetitionsIndex)])
515+
if let repetitions = args.argumentValue(forKey: "--repetitions").flatMap(Int.init) {
516+
result.repetitions = repetitions
497517
}
498-
if let repeatUntilIndex = args.firstIndex(of: "--repeat-until"), !isLastArgument(at: repeatUntilIndex) {
499-
result.repeatUntil = args[args.index(after: repeatUntilIndex)]
518+
if let repeatUntil = args.argumentValue(forKey: "--repeat-until") {
519+
result.repeatUntil = repeatUntil
500520
}
501521

502522
return result

Tests/TestingTests/SwiftPMTests.swift

Lines changed: 21 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -492,4 +492,25 @@ struct SwiftPMTests {
492492
let args = try parseCommandLineArguments(from: ["PATH", "--verbosity", "12345"])
493493
#expect(args.verbosity == 12345)
494494
}
495+
496+
@Test("--foo=bar form")
497+
func equalsSignForm() throws {
498+
// We can split the string and parse the result correctly.
499+
do {
500+
let args = try parseCommandLineArguments(from: ["PATH", "--verbosity=12345"])
501+
#expect(args.verbosity == 12345)
502+
}
503+
504+
// We don't overrun the string and correctly handle empty values.
505+
do {
506+
let args = try parseCommandLineArguments(from: ["PATH", "--xunit-output="])
507+
#expect(args.xunitOutput == "")
508+
}
509+
510+
// We split at the first equals-sign.
511+
do {
512+
let args = try parseCommandLineArguments(from: ["PATH", "--xunit-output=abc=123"])
513+
#expect(args.xunitOutput == "abc=123")
514+
}
515+
}
495516
}

0 commit comments

Comments
 (0)