diff --git a/Sources/ArgumentParserTestHelpers/TestHelpers.swift b/Sources/ArgumentParserTestHelpers/TestHelpers.swift index 3cda44c2e..3677b64f0 100644 --- a/Sources/ArgumentParserTestHelpers/TestHelpers.swift +++ b/Sources/ArgumentParserTestHelpers/TestHelpers.swift @@ -51,7 +51,7 @@ public func AssertResultFailure( switch expression() { case .success: let msg = message() - XCTFail(msg.isEmpty ? "Incorrectly succeeded" : msg, file: file, line: line) + XCTFail(msg.isEmpty ? "Incorrectly succeeded" : msg, file: (file), line: line) case .failure: break } @@ -60,20 +60,20 @@ public func AssertResultFailure( public func AssertErrorMessage(_ type: A.Type, _ arguments: [String], _ errorMessage: String, file: StaticString = #file, line: UInt = #line) where A: ParsableArguments { do { _ = try A.parse(arguments) - XCTFail("Parsing should have failed.", file: file, line: line) + XCTFail("Parsing should have failed.", file: (file), line: line) } catch { // We expect to hit this path, i.e. getting an error: - XCTAssertEqual(A.message(for: error), errorMessage, file: file, line: line) + XCTAssertEqual(A.message(for: error), errorMessage, file: (file), line: line) } } public func AssertFullErrorMessage(_ type: A.Type, _ arguments: [String], _ errorMessage: String, file: StaticString = #file, line: UInt = #line) where A: ParsableArguments { do { _ = try A.parse(arguments) - XCTFail("Parsing should have failed.", file: file, line: line) + XCTFail("Parsing should have failed.", file: (file), line: line) } catch { // We expect to hit this path, i.e. getting an error: - XCTAssertEqual(A.fullMessage(for: error), errorMessage, file: file, line: line) + XCTAssertEqual(A.fullMessage(for: error), errorMessage, file: (file), line: line) } } @@ -83,7 +83,7 @@ public func AssertParse(_ type: A.Type, _ arguments: [String], file: StaticSt try closure(parsed) } catch { let message = type.message(for: error) - XCTFail("\"\(message)\" — \(error)", file: file, line: line) + XCTFail("\"\(message)\" — \(error)", file: (file), line: line) } } @@ -91,13 +91,13 @@ public func AssertParseCommand(_ rootCommand: ParsableComman do { let command = try rootCommand.parseAsRoot(arguments) guard let aCommand = command as? A else { - XCTFail("Command is of unexpected type: \(command)", file: file, line: line) + XCTFail("Command is of unexpected type: \(command)", file: (file), line: line) return } try closure(aCommand) } catch { let message = rootCommand.message(for: error) - XCTFail("\"\(message)\" — \(error)", file: file, line: line) + XCTFail("\"\(message)\" — \(error)", file: (file), line: line) } } @@ -105,9 +105,9 @@ public func AssertEqualStringsIgnoringTrailingWhitespace(_ string1: String, _ st let lines1 = string1.split(separator: "\n", omittingEmptySubsequences: false) let lines2 = string2.split(separator: "\n", omittingEmptySubsequences: false) - XCTAssertEqual(lines1.count, lines2.count, "Strings have different numbers of lines.", file: file, line: line) + XCTAssertEqual(lines1.count, lines2.count, "Strings have different numbers of lines.", file: (file), line: line) for (line1, line2) in zip(lines1, lines2) { - XCTAssertEqual(line1.trimmed(), line2.trimmed(), file: file, line: line) + XCTAssertEqual(line1.trimmed(), line2.trimmed(), file: (file), line: line) } } @@ -117,7 +117,7 @@ public func AssertHelp( ) { do { _ = try T.parse(["-h"]) - XCTFail(file: file, line: line) + XCTFail(file: (file), line: line) } catch { let helpString = T.fullMessage(for: error) AssertEqualStringsIgnoringTrailingWhitespace( @@ -159,7 +159,7 @@ extension XCTest { let commandURL = debugURL.appendingPathComponent(commandName) guard (try? commandURL.checkResourceIsReachable()) ?? false else { XCTFail("No executable at '\(commandURL.standardizedFileURL.path)'.", - file: file, line: line) + file: (file), line: line) return } @@ -178,7 +178,7 @@ extension XCTest { if #available(macOS 10.13, *) { guard (try? process.run()) != nil else { - XCTFail("Couldn't run command process.", file: file, line: line) + XCTFail("Couldn't run command process.", file: (file), line: line) return } } else { @@ -196,6 +196,6 @@ extension XCTest { AssertEqualStringsIgnoringTrailingWhitespace(expected, errorActual + outputActual, file: file, line: line) } - XCTAssertEqual(process.terminationStatus, exitCode.rawValue, file: file, line: line) + XCTAssertEqual(process.terminationStatus, exitCode.rawValue, file: (file), line: line) } } diff --git a/Tests/ArgumentParserEndToEndTests/FlagsEndToEndTests.swift b/Tests/ArgumentParserEndToEndTests/FlagsEndToEndTests.swift index 389578de7..a70ea4e22 100644 --- a/Tests/ArgumentParserEndToEndTests/FlagsEndToEndTests.swift +++ b/Tests/ArgumentParserEndToEndTests/FlagsEndToEndTests.swift @@ -330,37 +330,3 @@ extension FlagsEndToEndTests { } } } - -fileprivate struct DeprecatedFlags: ParsableArguments { - enum One: String, CaseIterable { - case one - } - enum Two: String, CaseIterable { - case two - } - enum Three: String, CaseIterable { - case three - case four - } - - @Flag() var single: One - @Flag() var optional: Two? - @Flag() var array: [Three] - @Flag(name: .long) var size: Size? -} - -extension FlagsEndToEndTests { - func testParsingDeprecatedFlags() throws { - AssertParse(DeprecatedFlags.self, ["--one"]) { options in - XCTAssertEqual(options.single, .one) - XCTAssertNil(options.optional) - XCTAssertTrue(options.array.isEmpty) - } - - AssertParse(DeprecatedFlags.self, ["--one", "--two", "--three", "--four", "--three"]) { options in - XCTAssertEqual(options.single, .one) - XCTAssertEqual(options.optional, .two) - XCTAssertEqual(options.array, [.three, .four, .three]) - } - } -} diff --git a/Tests/ArgumentParserEndToEndTests/SourceCompatEndToEndTests.swift b/Tests/ArgumentParserEndToEndTests/SourceCompatEndToEndTests.swift new file mode 100644 index 000000000..0e3e372f6 --- /dev/null +++ b/Tests/ArgumentParserEndToEndTests/SourceCompatEndToEndTests.swift @@ -0,0 +1,242 @@ +//===----------------------------------------------------------*- swift -*-===// +// +// This source file is part of the Swift Argument Parser open source project +// +// Copyright (c) 2020 Apple Inc. and the Swift project authors +// Licensed under Apache License v2.0 with Runtime Library Exception +// +// See https://swift.org/LICENSE.txt for license information +// +//===----------------------------------------------------------------------===// + +import XCTest +import ArgumentParserTestHelpers +import ArgumentParser + +/// The goal of this test class is to validate source compatibility. By running +/// this class's tests, all property wrapper initializers should be called. +final class SourceCompatEndToEndTests: XCTestCase {} + +// MARK: - Property Wrapper Initializers + +fileprivate struct AlmostAllArguments: ParsableArguments { + @Argument(default: 0, help: "") var a: Int + @Argument() var a0: Int + @Argument(help: "") var a1: Int + @Argument(default: 0) var a2: Int + + @Argument(default: 0, help: "", transform: { _ in 0 }) var b: Int + @Argument(default: 0) var b1: Int + @Argument(help: "") var b2: Int + @Argument(transform: { _ in 0 }) var b3: Int + @Argument(help: "", transform: { _ in 0 }) var b4: Int + @Argument(default: 0, transform: { _ in 0 }) var b5: Int + @Argument(default: 0, help: "") var b6: Int + + @Argument(default: 0, help: "") var c: Int? + @Argument() var c0: Int? + @Argument(help: "") var c1: Int? + @Argument(default: 0) var c2: Int? + + @Argument(default: 0, help: "", transform: { _ in 0 }) var d: Int? + @Argument(default: 0) var d1: Int? + @Argument(help: "") var d2: Int? + @Argument(transform: { _ in 0 }) var d3: Int? + @Argument(help: "", transform: { _ in 0 }) var d4: Int? + @Argument(default: 0, transform: { _ in 0 }) var d5: Int? + @Argument(default: 0, help: "") var d6: Int? + + @Argument(parsing: .remaining, help: "") var e: [Int] + @Argument() var e0: [Int] + @Argument(help: "") var e1: [Int] + @Argument(parsing: .remaining) var e2: [Int] + @Argument(parsing: .remaining, help: "", transform: { _ in 0 }) var e3: [Int] + @Argument(transform: { _ in 0 }) var e4: [Int] + @Argument(help: "", transform: { _ in 0 }) var e5: [Int] + @Argument(parsing: .remaining, transform: { _ in 0 }) var e6: [Int] +} + +fileprivate struct AllOptions: ParsableArguments { + @Option(name: .long, default: 0, parsing: .next, help: "") var a: Int + @Option(default: 0, parsing: .next, help: "") var a1: Int + @Option(name: .long, parsing: .next, help: "") var a2: Int + @Option(name: .long, default: 0, help: "") var a3: Int + @Option(parsing: .next, help: "") var a4: Int + @Option(default: 0, help: "") var a5: Int + @Option(default: 0, parsing: .next) var a6: Int + @Option(name: .long, help: "") var a7: Int + @Option(name: .long, parsing: .next) var a8: Int + @Option(name: .long, default: 0) var a9: Int + @Option(name: .long) var a10: Int + @Option(default: 0) var a11: Int + @Option(parsing: .next) var a12: Int + @Option(help: "") var a13: Int + + @Option(name: .long, default: 0, parsing: .next, help: "") var b: Int? + @Option(default: 0, parsing: .next, help: "") var b1: Int? + @Option(name: .long, parsing: .next, help: "") var b2: Int? + @Option(name: .long, default: 0, help: "") var b3: Int? + @Option(parsing: .next, help: "") var b4: Int? + @Option(default: 0, help: "") var b5: Int? + @Option(default: 0, parsing: .next) var b6: Int? + @Option(name: .long, help: "") var b7: Int? + @Option(name: .long, parsing: .next) var b8: Int? + @Option(name: .long, default: 0) var b9: Int? + @Option(name: .long) var b10: Int? + @Option(default: 0) var b11: Int? + @Option(parsing: .next) var b12: Int? + @Option(help: "") var b13: Int? + + @Option(name: .long, default: 0, parsing: .next, help: "", transform: { _ in 0 }) var c: Int + @Option(default: 0, parsing: .next, help: "", transform: { _ in 0 }) var c1: Int + @Option(name: .long, parsing: .next, help: "", transform: { _ in 0 }) var c2: Int + @Option(name: .long, default: 0, help: "", transform: { _ in 0 }) var c3: Int + @Option(parsing: .next, help: "", transform: { _ in 0 }) var c4: Int + @Option(default: 0, help: "", transform: { _ in 0 }) var c5: Int + @Option(default: 0, parsing: .next, transform: { _ in 0 }) var c6: Int + @Option(name: .long, help: "", transform: { _ in 0 }) var c7: Int + @Option(name: .long, parsing: .next, transform: { _ in 0 }) var c8: Int + @Option(name: .long, default: 0, transform: { _ in 0 }) var c9: Int + @Option(name: .long, transform: { _ in 0 }) var c10: Int + @Option(default: 0, transform: { _ in 0 }) var c11: Int + @Option(parsing: .next, transform: { _ in 0 }) var c12: Int + @Option(help: "", transform: { _ in 0 }) var c13: Int + + @Option(name: .long, default: 0, parsing: .next, help: "", transform: { _ in 0 }) var d: Int? + @Option(default: 0, parsing: .next, help: "", transform: { _ in 0 }) var d1: Int? + @Option(name: .long, parsing: .next, help: "", transform: { _ in 0 }) var d2: Int? + @Option(name: .long, default: 0, help: "", transform: { _ in 0 }) var d3: Int? + @Option(parsing: .next, help: "", transform: { _ in 0 }) var d4: Int? + @Option(default: 0, help: "", transform: { _ in 0 }) var d5: Int? + @Option(default: 0, parsing: .next, transform: { _ in 0 }) var d6: Int? + @Option(name: .long, help: "", transform: { _ in 0 }) var d7: Int? + @Option(name: .long, parsing: .next, transform: { _ in 0 }) var d8: Int? + @Option(name: .long, default: 0, transform: { _ in 0 }) var d9: Int? + @Option(name: .long, transform: { _ in 0 }) var d10: Int? + @Option(default: 0, transform: { _ in 0 }) var d11: Int? + @Option(parsing: .next, transform: { _ in 0 }) var d12: Int? + @Option(help: "", transform: { _ in 0 }) var d13: Int? + + @Option(name: .long, parsing: .singleValue, help: "") var e: [Int] + @Option(parsing: .singleValue, help: "") var e1: [Int] + @Option(name: .long, help: "") var e2: [Int] + @Option(name: .long, parsing: .singleValue) var e3: [Int] + @Option(name: .long) var e4: [Int] + @Option(parsing: .singleValue) var e5: [Int] + @Option(help: "") var e6: [Int] + + @Option(name: .long, parsing: .singleValue, help: "", transform: { _ in 0 }) var f: [Int] + @Option(parsing: .singleValue, help: "", transform: { _ in 0 }) var f1: [Int] + @Option(name: .long, help: "", transform: { _ in 0 }) var f2: [Int] + @Option(name: .long, parsing: .singleValue, transform: { _ in 0 }) var f3: [Int] + @Option(name: .long, transform: { _ in 0 }) var f4: [Int] + @Option(parsing: .singleValue, transform: { _ in 0 }) var f5: [Int] + @Option(help: "", transform: { _ in 0 }) var f6: [Int] +} + +struct AllFlags: ParsableArguments { + enum E: String, EnumerableFlag { + case one, two, three + } + + @Flag(name: .long, help: "") var a: Bool + @Flag() var a0: Bool + @Flag(name: .long) var a1: Bool + @Flag(help: "") var a2: Bool + + @Flag(name: .long, inversion: .prefixedNo, exclusivity: .chooseLast, help: "") var b: Bool + @Flag(inversion: .prefixedNo, exclusivity: .chooseLast, help: "") var b1: Bool + @Flag(name: .long, inversion: .prefixedNo, help: "") var b2: Bool + @Flag(name: .long, inversion: .prefixedNo, exclusivity: .chooseLast) var b3: Bool + @Flag(inversion: .prefixedNo, help: "") var b4: Bool + @Flag(inversion: .prefixedNo, exclusivity: .chooseLast) var b5: Bool + @Flag(name: .long, inversion: .prefixedNo) var b6: Bool + @Flag(inversion: .prefixedNo) var b7: Bool + + @Flag(name: .long, default: false, inversion: .prefixedNo, exclusivity: .chooseLast, help: "") var c: Bool + @Flag(default: false, inversion: .prefixedNo, exclusivity: .chooseLast, help: "") var c1: Bool + @Flag(name: .long, default: false, inversion: .prefixedNo, help: "") var c2: Bool + @Flag(name: .long, default: false, inversion: .prefixedNo, exclusivity: .chooseLast) var c3: Bool + @Flag(default: false, inversion: .prefixedNo, help: "") var c4: Bool + @Flag(default: false, inversion: .prefixedNo, exclusivity: .chooseLast) var c5: Bool + @Flag(name: .long, default: false, inversion: .prefixedNo) var c6: Bool + @Flag(default: false, inversion: .prefixedNo) var c7: Bool + + @Flag(name: .long, default: nil, inversion: .prefixedNo, exclusivity: .chooseLast, help: "") var d: Bool + @Flag(default: nil, inversion: .prefixedNo, exclusivity: .chooseLast, help: "") var d1: Bool + @Flag(name: .long, default: nil, inversion: .prefixedNo, help: "") var d2: Bool + @Flag(name: .long, default: nil, inversion: .prefixedNo, exclusivity: .chooseLast) var d3: Bool + @Flag(default: nil, inversion: .prefixedNo, help: "") var d4: Bool + @Flag(default: nil, inversion: .prefixedNo, exclusivity: .chooseLast) var d5: Bool + @Flag(name: .long, default: nil, inversion: .prefixedNo) var d6: Bool + @Flag(default: nil, inversion: .prefixedNo) var d7: Bool + + @Flag(name: .long, help: "") var e: Int + @Flag() var e0: Int + @Flag(name: .long) var e1: Int + @Flag(help: "") var e2: Int + + @Flag(default: .one, exclusivity: .chooseLast, help: "") var f: E + @Flag() var f1: E + @Flag(exclusivity: .chooseLast, help: "") var f2: E + @Flag(default: .one, help: "") var f3: E + @Flag(default: .one, exclusivity: .chooseLast) var f4: E + @Flag(help: "") var f5: E + @Flag(exclusivity: .chooseLast) var f6: E + @Flag(default: .one) var f7: E + + @Flag(exclusivity: .chooseLast, help: "") var g: E? + @Flag() var g1: E? + @Flag(help: "") var g2: E? + @Flag(exclusivity: .chooseLast) var g3: E? + + @Flag(help: "") var h: [E] + @Flag() var h1: [E] +} + +extension SourceCompatEndToEndTests { + func testParsingAll() throws { + // This is just checking building the argument definitions, not the actual + // validation or usage of these definitions, which would fail. + _ = AlmostAllArguments() + _ = AllOptions() + _ = AllFlags() + } +} + +// MARK: - Deprecated APIs + +fileprivate struct DeprecatedFlags: ParsableArguments { + enum One: String, CaseIterable { + case one + } + enum Two: String, CaseIterable { + case two + } + enum Three: String, CaseIterable { + case three + case four + } + + @Flag() var single: One + @Flag() var optional: Two? + @Flag() var array: [Three] + @Flag(name: .long) var size: Size? +} + +extension SourceCompatEndToEndTests { + func testParsingDeprecatedFlags() throws { + AssertParse(DeprecatedFlags.self, ["--one"]) { options in + XCTAssertEqual(options.single, .one) + XCTAssertNil(options.optional) + XCTAssertTrue(options.array.isEmpty) + } + + AssertParse(DeprecatedFlags.self, ["--one", "--two", "--three", "--four", "--three"]) { options in + XCTAssertEqual(options.single, .one) + XCTAssertEqual(options.optional, .two) + XCTAssertEqual(options.array, [.three, .four, .three]) + } + } +} + diff --git a/Tests/ArgumentParserUnitTests/NameSpecificationTests.swift b/Tests/ArgumentParserUnitTests/NameSpecificationTests.swift index db2ee1a79..956a4a83a 100644 --- a/Tests/ArgumentParserUnitTests/NameSpecificationTests.swift +++ b/Tests/ArgumentParserUnitTests/NameSpecificationTests.swift @@ -49,10 +49,10 @@ fileprivate func Assert(nameSpecification: NameSpecification, key: String, makeN fileprivate func Assert(names: [N], expected: [N], file: StaticString = #file, line: UInt = #line) where N: Equatable { names.forEach { - XCTAssert(expected.contains($0), "Unexpected name '\($0)'.", file: file, line: line) + XCTAssert(expected.contains($0), "Unexpected name '\($0)'.", file: (file), line: line) } expected.forEach { - XCTAssert(names.contains($0), "Missing name '\($0)'.", file: file, line: line) + XCTAssert(names.contains($0), "Missing name '\($0)'.", file: (file), line: line) } } diff --git a/Tests/ArgumentParserUnitTests/ParsableArgumentsValidationTests.swift b/Tests/ArgumentParserUnitTests/ParsableArgumentsValidationTests.swift index d6fc3e1cc..33c200cca 100644 --- a/Tests/ArgumentParserUnitTests/ParsableArgumentsValidationTests.swift +++ b/Tests/ArgumentParserUnitTests/ParsableArgumentsValidationTests.swift @@ -324,7 +324,7 @@ final class ParsableArgumentsValidationTests: XCTestCase { } // MARK: CaseIterable enum flag has first letter duplication - fileprivate enum ExampleEnum: String, ExpressibleByArgument, CaseIterable { + fileprivate enum ExampleEnum: String, EnumerableFlag { case first case second case other diff --git a/Tests/ArgumentParserUnitTests/SplitArgumentTests.swift b/Tests/ArgumentParserUnitTests/SplitArgumentTests.swift index cf0875d6b..6c458a6da 100644 --- a/Tests/ArgumentParserUnitTests/SplitArgumentTests.swift +++ b/Tests/ArgumentParserUnitTests/SplitArgumentTests.swift @@ -21,25 +21,25 @@ extension SplitArguments.InputIndex: ExpressibleByIntegerLiteral { private func AssertIndexEqual(_ sut: SplitArguments, at index: Int, inputIndex: Int, subIndex: SplitArguments.SubIndex, file: StaticString = #file, line: UInt = #line) { guard index < sut.elements.count else { - XCTFail("Element index \(index) is out of range. sur only has \(sut.elements.count) elements.", file: file, line: line) + XCTFail("Element index \(index) is out of range. sur only has \(sut.elements.count) elements.", file: (file), line: line) return } let splitIndex = sut.elements[index].0 let expected = SplitArguments.Index(inputIndex: SplitArguments.InputIndex(rawValue: inputIndex), subIndex: subIndex) if splitIndex.inputIndex != expected.inputIndex { - XCTFail("inputIndex does not match: \(splitIndex.inputIndex.rawValue) != \(expected.inputIndex.rawValue)", file: file, line: line) + XCTFail("inputIndex does not match: \(splitIndex.inputIndex.rawValue) != \(expected.inputIndex.rawValue)", file: (file), line: line) } if splitIndex.subIndex != expected.subIndex { - XCTFail("inputIndex does not match: \(splitIndex.subIndex) != \(expected.subIndex)", file: file, line: line) + XCTFail("inputIndex does not match: \(splitIndex.subIndex) != \(expected.subIndex)", file: (file), line: line) } } private func AssertElementEqual(_ sut: SplitArguments, at index: Int, _ element: SplitArguments.Element, file: StaticString = #file, line: UInt = #line) { guard index < sut.elements.count else { - XCTFail("Element index \(index) is out of range. sur only has \(sut.elements.count) elements.", file: file, line: line) + XCTFail("Element index \(index) is out of range. sur only has \(sut.elements.count) elements.", file: (file), line: line) return } - XCTAssertEqual(sut.elements[index].1, element, file: file, line: line) + XCTAssertEqual(sut.elements[index].1, element, file: (file), line: line) } final class SplitArgumentTests: XCTestCase {