Skip to content

Commit 6c6b77c

Browse files
authored
Test source compatibility for property wrappers (#172)
* Eliminate #file warnings * Test source compatibility for property wrappers This adds compilation tests for all the property wrappers, including all the various permutations of their default parameter values.
1 parent 4c07125 commit 6c6b77c

File tree

6 files changed

+264
-56
lines changed

6 files changed

+264
-56
lines changed

Sources/ArgumentParserTestHelpers/TestHelpers.swift

Lines changed: 14 additions & 14 deletions
Original file line numberDiff line numberDiff line change
@@ -51,7 +51,7 @@ public func AssertResultFailure<T, U: Error>(
5151
switch expression() {
5252
case .success:
5353
let msg = message()
54-
XCTFail(msg.isEmpty ? "Incorrectly succeeded" : msg, file: file, line: line)
54+
XCTFail(msg.isEmpty ? "Incorrectly succeeded" : msg, file: (file), line: line)
5555
case .failure:
5656
break
5757
}
@@ -60,20 +60,20 @@ public func AssertResultFailure<T, U: Error>(
6060
public func AssertErrorMessage<A>(_ type: A.Type, _ arguments: [String], _ errorMessage: String, file: StaticString = #file, line: UInt = #line) where A: ParsableArguments {
6161
do {
6262
_ = try A.parse(arguments)
63-
XCTFail("Parsing should have failed.", file: file, line: line)
63+
XCTFail("Parsing should have failed.", file: (file), line: line)
6464
} catch {
6565
// We expect to hit this path, i.e. getting an error:
66-
XCTAssertEqual(A.message(for: error), errorMessage, file: file, line: line)
66+
XCTAssertEqual(A.message(for: error), errorMessage, file: (file), line: line)
6767
}
6868
}
6969

7070
public func AssertFullErrorMessage<A>(_ type: A.Type, _ arguments: [String], _ errorMessage: String, file: StaticString = #file, line: UInt = #line) where A: ParsableArguments {
7171
do {
7272
_ = try A.parse(arguments)
73-
XCTFail("Parsing should have failed.", file: file, line: line)
73+
XCTFail("Parsing should have failed.", file: (file), line: line)
7474
} catch {
7575
// We expect to hit this path, i.e. getting an error:
76-
XCTAssertEqual(A.fullMessage(for: error), errorMessage, file: file, line: line)
76+
XCTAssertEqual(A.fullMessage(for: error), errorMessage, file: (file), line: line)
7777
}
7878
}
7979

@@ -83,31 +83,31 @@ public func AssertParse<A>(_ type: A.Type, _ arguments: [String], file: StaticSt
8383
try closure(parsed)
8484
} catch {
8585
let message = type.message(for: error)
86-
XCTFail("\"\(message)\"\(error)", file: file, line: line)
86+
XCTFail("\"\(message)\"\(error)", file: (file), line: line)
8787
}
8888
}
8989

9090
public func AssertParseCommand<A: ParsableCommand>(_ rootCommand: ParsableCommand.Type, _ type: A.Type, _ arguments: [String], file: StaticString = #file, line: UInt = #line, closure: (A) throws -> Void) {
9191
do {
9292
let command = try rootCommand.parseAsRoot(arguments)
9393
guard let aCommand = command as? A else {
94-
XCTFail("Command is of unexpected type: \(command)", file: file, line: line)
94+
XCTFail("Command is of unexpected type: \(command)", file: (file), line: line)
9595
return
9696
}
9797
try closure(aCommand)
9898
} catch {
9999
let message = rootCommand.message(for: error)
100-
XCTFail("\"\(message)\"\(error)", file: file, line: line)
100+
XCTFail("\"\(message)\"\(error)", file: (file), line: line)
101101
}
102102
}
103103

104104
public func AssertEqualStringsIgnoringTrailingWhitespace(_ string1: String, _ string2: String, file: StaticString = #file, line: UInt = #line) {
105105
let lines1 = string1.split(separator: "\n", omittingEmptySubsequences: false)
106106
let lines2 = string2.split(separator: "\n", omittingEmptySubsequences: false)
107107

108-
XCTAssertEqual(lines1.count, lines2.count, "Strings have different numbers of lines.", file: file, line: line)
108+
XCTAssertEqual(lines1.count, lines2.count, "Strings have different numbers of lines.", file: (file), line: line)
109109
for (line1, line2) in zip(lines1, lines2) {
110-
XCTAssertEqual(line1.trimmed(), line2.trimmed(), file: file, line: line)
110+
XCTAssertEqual(line1.trimmed(), line2.trimmed(), file: (file), line: line)
111111
}
112112
}
113113

@@ -117,7 +117,7 @@ public func AssertHelp<T: ParsableArguments>(
117117
) {
118118
do {
119119
_ = try T.parse(["-h"])
120-
XCTFail(file: file, line: line)
120+
XCTFail(file: (file), line: line)
121121
} catch {
122122
let helpString = T.fullMessage(for: error)
123123
AssertEqualStringsIgnoringTrailingWhitespace(
@@ -159,7 +159,7 @@ extension XCTest {
159159
let commandURL = debugURL.appendingPathComponent(commandName)
160160
guard (try? commandURL.checkResourceIsReachable()) ?? false else {
161161
XCTFail("No executable at '\(commandURL.standardizedFileURL.path)'.",
162-
file: file, line: line)
162+
file: (file), line: line)
163163
return
164164
}
165165

@@ -178,7 +178,7 @@ extension XCTest {
178178

179179
if #available(macOS 10.13, *) {
180180
guard (try? process.run()) != nil else {
181-
XCTFail("Couldn't run command process.", file: file, line: line)
181+
XCTFail("Couldn't run command process.", file: (file), line: line)
182182
return
183183
}
184184
} else {
@@ -196,6 +196,6 @@ extension XCTest {
196196
AssertEqualStringsIgnoringTrailingWhitespace(expected, errorActual + outputActual, file: file, line: line)
197197
}
198198

199-
XCTAssertEqual(process.terminationStatus, exitCode.rawValue, file: file, line: line)
199+
XCTAssertEqual(process.terminationStatus, exitCode.rawValue, file: (file), line: line)
200200
}
201201
}

Tests/ArgumentParserEndToEndTests/FlagsEndToEndTests.swift

Lines changed: 0 additions & 34 deletions
Original file line numberDiff line numberDiff line change
@@ -330,37 +330,3 @@ extension FlagsEndToEndTests {
330330
}
331331
}
332332
}
333-
334-
fileprivate struct DeprecatedFlags: ParsableArguments {
335-
enum One: String, CaseIterable {
336-
case one
337-
}
338-
enum Two: String, CaseIterable {
339-
case two
340-
}
341-
enum Three: String, CaseIterable {
342-
case three
343-
case four
344-
}
345-
346-
@Flag() var single: One
347-
@Flag() var optional: Two?
348-
@Flag() var array: [Three]
349-
@Flag(name: .long) var size: Size?
350-
}
351-
352-
extension FlagsEndToEndTests {
353-
func testParsingDeprecatedFlags() throws {
354-
AssertParse(DeprecatedFlags.self, ["--one"]) { options in
355-
XCTAssertEqual(options.single, .one)
356-
XCTAssertNil(options.optional)
357-
XCTAssertTrue(options.array.isEmpty)
358-
}
359-
360-
AssertParse(DeprecatedFlags.self, ["--one", "--two", "--three", "--four", "--three"]) { options in
361-
XCTAssertEqual(options.single, .one)
362-
XCTAssertEqual(options.optional, .two)
363-
XCTAssertEqual(options.array, [.three, .four, .three])
364-
}
365-
}
366-
}
Lines changed: 242 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,242 @@
1+
//===----------------------------------------------------------*- swift -*-===//
2+
//
3+
// This source file is part of the Swift Argument Parser open source project
4+
//
5+
// Copyright (c) 2020 Apple Inc. and the Swift project authors
6+
// Licensed under Apache License v2.0 with Runtime Library Exception
7+
//
8+
// See https://swift.org/LICENSE.txt for license information
9+
//
10+
//===----------------------------------------------------------------------===//
11+
12+
import XCTest
13+
import ArgumentParserTestHelpers
14+
import ArgumentParser
15+
16+
/// The goal of this test class is to validate source compatibility. By running
17+
/// this class's tests, all property wrapper initializers should be called.
18+
final class SourceCompatEndToEndTests: XCTestCase {}
19+
20+
// MARK: - Property Wrapper Initializers
21+
22+
fileprivate struct AlmostAllArguments: ParsableArguments {
23+
@Argument(default: 0, help: "") var a: Int
24+
@Argument() var a0: Int
25+
@Argument(help: "") var a1: Int
26+
@Argument(default: 0) var a2: Int
27+
28+
@Argument(default: 0, help: "", transform: { _ in 0 }) var b: Int
29+
@Argument(default: 0) var b1: Int
30+
@Argument(help: "") var b2: Int
31+
@Argument(transform: { _ in 0 }) var b3: Int
32+
@Argument(help: "", transform: { _ in 0 }) var b4: Int
33+
@Argument(default: 0, transform: { _ in 0 }) var b5: Int
34+
@Argument(default: 0, help: "") var b6: Int
35+
36+
@Argument(default: 0, help: "") var c: Int?
37+
@Argument() var c0: Int?
38+
@Argument(help: "") var c1: Int?
39+
@Argument(default: 0) var c2: Int?
40+
41+
@Argument(default: 0, help: "", transform: { _ in 0 }) var d: Int?
42+
@Argument(default: 0) var d1: Int?
43+
@Argument(help: "") var d2: Int?
44+
@Argument(transform: { _ in 0 }) var d3: Int?
45+
@Argument(help: "", transform: { _ in 0 }) var d4: Int?
46+
@Argument(default: 0, transform: { _ in 0 }) var d5: Int?
47+
@Argument(default: 0, help: "") var d6: Int?
48+
49+
@Argument(parsing: .remaining, help: "") var e: [Int]
50+
@Argument() var e0: [Int]
51+
@Argument(help: "") var e1: [Int]
52+
@Argument(parsing: .remaining) var e2: [Int]
53+
@Argument(parsing: .remaining, help: "", transform: { _ in 0 }) var e3: [Int]
54+
@Argument(transform: { _ in 0 }) var e4: [Int]
55+
@Argument(help: "", transform: { _ in 0 }) var e5: [Int]
56+
@Argument(parsing: .remaining, transform: { _ in 0 }) var e6: [Int]
57+
}
58+
59+
fileprivate struct AllOptions: ParsableArguments {
60+
@Option(name: .long, default: 0, parsing: .next, help: "") var a: Int
61+
@Option(default: 0, parsing: .next, help: "") var a1: Int
62+
@Option(name: .long, parsing: .next, help: "") var a2: Int
63+
@Option(name: .long, default: 0, help: "") var a3: Int
64+
@Option(parsing: .next, help: "") var a4: Int
65+
@Option(default: 0, help: "") var a5: Int
66+
@Option(default: 0, parsing: .next) var a6: Int
67+
@Option(name: .long, help: "") var a7: Int
68+
@Option(name: .long, parsing: .next) var a8: Int
69+
@Option(name: .long, default: 0) var a9: Int
70+
@Option(name: .long) var a10: Int
71+
@Option(default: 0) var a11: Int
72+
@Option(parsing: .next) var a12: Int
73+
@Option(help: "") var a13: Int
74+
75+
@Option(name: .long, default: 0, parsing: .next, help: "") var b: Int?
76+
@Option(default: 0, parsing: .next, help: "") var b1: Int?
77+
@Option(name: .long, parsing: .next, help: "") var b2: Int?
78+
@Option(name: .long, default: 0, help: "") var b3: Int?
79+
@Option(parsing: .next, help: "") var b4: Int?
80+
@Option(default: 0, help: "") var b5: Int?
81+
@Option(default: 0, parsing: .next) var b6: Int?
82+
@Option(name: .long, help: "") var b7: Int?
83+
@Option(name: .long, parsing: .next) var b8: Int?
84+
@Option(name: .long, default: 0) var b9: Int?
85+
@Option(name: .long) var b10: Int?
86+
@Option(default: 0) var b11: Int?
87+
@Option(parsing: .next) var b12: Int?
88+
@Option(help: "") var b13: Int?
89+
90+
@Option(name: .long, default: 0, parsing: .next, help: "", transform: { _ in 0 }) var c: Int
91+
@Option(default: 0, parsing: .next, help: "", transform: { _ in 0 }) var c1: Int
92+
@Option(name: .long, parsing: .next, help: "", transform: { _ in 0 }) var c2: Int
93+
@Option(name: .long, default: 0, help: "", transform: { _ in 0 }) var c3: Int
94+
@Option(parsing: .next, help: "", transform: { _ in 0 }) var c4: Int
95+
@Option(default: 0, help: "", transform: { _ in 0 }) var c5: Int
96+
@Option(default: 0, parsing: .next, transform: { _ in 0 }) var c6: Int
97+
@Option(name: .long, help: "", transform: { _ in 0 }) var c7: Int
98+
@Option(name: .long, parsing: .next, transform: { _ in 0 }) var c8: Int
99+
@Option(name: .long, default: 0, transform: { _ in 0 }) var c9: Int
100+
@Option(name: .long, transform: { _ in 0 }) var c10: Int
101+
@Option(default: 0, transform: { _ in 0 }) var c11: Int
102+
@Option(parsing: .next, transform: { _ in 0 }) var c12: Int
103+
@Option(help: "", transform: { _ in 0 }) var c13: Int
104+
105+
@Option(name: .long, default: 0, parsing: .next, help: "", transform: { _ in 0 }) var d: Int?
106+
@Option(default: 0, parsing: .next, help: "", transform: { _ in 0 }) var d1: Int?
107+
@Option(name: .long, parsing: .next, help: "", transform: { _ in 0 }) var d2: Int?
108+
@Option(name: .long, default: 0, help: "", transform: { _ in 0 }) var d3: Int?
109+
@Option(parsing: .next, help: "", transform: { _ in 0 }) var d4: Int?
110+
@Option(default: 0, help: "", transform: { _ in 0 }) var d5: Int?
111+
@Option(default: 0, parsing: .next, transform: { _ in 0 }) var d6: Int?
112+
@Option(name: .long, help: "", transform: { _ in 0 }) var d7: Int?
113+
@Option(name: .long, parsing: .next, transform: { _ in 0 }) var d8: Int?
114+
@Option(name: .long, default: 0, transform: { _ in 0 }) var d9: Int?
115+
@Option(name: .long, transform: { _ in 0 }) var d10: Int?
116+
@Option(default: 0, transform: { _ in 0 }) var d11: Int?
117+
@Option(parsing: .next, transform: { _ in 0 }) var d12: Int?
118+
@Option(help: "", transform: { _ in 0 }) var d13: Int?
119+
120+
@Option(name: .long, parsing: .singleValue, help: "") var e: [Int]
121+
@Option(parsing: .singleValue, help: "") var e1: [Int]
122+
@Option(name: .long, help: "") var e2: [Int]
123+
@Option(name: .long, parsing: .singleValue) var e3: [Int]
124+
@Option(name: .long) var e4: [Int]
125+
@Option(parsing: .singleValue) var e5: [Int]
126+
@Option(help: "") var e6: [Int]
127+
128+
@Option(name: .long, parsing: .singleValue, help: "", transform: { _ in 0 }) var f: [Int]
129+
@Option(parsing: .singleValue, help: "", transform: { _ in 0 }) var f1: [Int]
130+
@Option(name: .long, help: "", transform: { _ in 0 }) var f2: [Int]
131+
@Option(name: .long, parsing: .singleValue, transform: { _ in 0 }) var f3: [Int]
132+
@Option(name: .long, transform: { _ in 0 }) var f4: [Int]
133+
@Option(parsing: .singleValue, transform: { _ in 0 }) var f5: [Int]
134+
@Option(help: "", transform: { _ in 0 }) var f6: [Int]
135+
}
136+
137+
struct AllFlags: ParsableArguments {
138+
enum E: String, EnumerableFlag {
139+
case one, two, three
140+
}
141+
142+
@Flag(name: .long, help: "") var a: Bool
143+
@Flag() var a0: Bool
144+
@Flag(name: .long) var a1: Bool
145+
@Flag(help: "") var a2: Bool
146+
147+
@Flag(name: .long, inversion: .prefixedNo, exclusivity: .chooseLast, help: "") var b: Bool
148+
@Flag(inversion: .prefixedNo, exclusivity: .chooseLast, help: "") var b1: Bool
149+
@Flag(name: .long, inversion: .prefixedNo, help: "") var b2: Bool
150+
@Flag(name: .long, inversion: .prefixedNo, exclusivity: .chooseLast) var b3: Bool
151+
@Flag(inversion: .prefixedNo, help: "") var b4: Bool
152+
@Flag(inversion: .prefixedNo, exclusivity: .chooseLast) var b5: Bool
153+
@Flag(name: .long, inversion: .prefixedNo) var b6: Bool
154+
@Flag(inversion: .prefixedNo) var b7: Bool
155+
156+
@Flag(name: .long, default: false, inversion: .prefixedNo, exclusivity: .chooseLast, help: "") var c: Bool
157+
@Flag(default: false, inversion: .prefixedNo, exclusivity: .chooseLast, help: "") var c1: Bool
158+
@Flag(name: .long, default: false, inversion: .prefixedNo, help: "") var c2: Bool
159+
@Flag(name: .long, default: false, inversion: .prefixedNo, exclusivity: .chooseLast) var c3: Bool
160+
@Flag(default: false, inversion: .prefixedNo, help: "") var c4: Bool
161+
@Flag(default: false, inversion: .prefixedNo, exclusivity: .chooseLast) var c5: Bool
162+
@Flag(name: .long, default: false, inversion: .prefixedNo) var c6: Bool
163+
@Flag(default: false, inversion: .prefixedNo) var c7: Bool
164+
165+
@Flag(name: .long, default: nil, inversion: .prefixedNo, exclusivity: .chooseLast, help: "") var d: Bool
166+
@Flag(default: nil, inversion: .prefixedNo, exclusivity: .chooseLast, help: "") var d1: Bool
167+
@Flag(name: .long, default: nil, inversion: .prefixedNo, help: "") var d2: Bool
168+
@Flag(name: .long, default: nil, inversion: .prefixedNo, exclusivity: .chooseLast) var d3: Bool
169+
@Flag(default: nil, inversion: .prefixedNo, help: "") var d4: Bool
170+
@Flag(default: nil, inversion: .prefixedNo, exclusivity: .chooseLast) var d5: Bool
171+
@Flag(name: .long, default: nil, inversion: .prefixedNo) var d6: Bool
172+
@Flag(default: nil, inversion: .prefixedNo) var d7: Bool
173+
174+
@Flag(name: .long, help: "") var e: Int
175+
@Flag() var e0: Int
176+
@Flag(name: .long) var e1: Int
177+
@Flag(help: "") var e2: Int
178+
179+
@Flag(default: .one, exclusivity: .chooseLast, help: "") var f: E
180+
@Flag() var f1: E
181+
@Flag(exclusivity: .chooseLast, help: "") var f2: E
182+
@Flag(default: .one, help: "") var f3: E
183+
@Flag(default: .one, exclusivity: .chooseLast) var f4: E
184+
@Flag(help: "") var f5: E
185+
@Flag(exclusivity: .chooseLast) var f6: E
186+
@Flag(default: .one) var f7: E
187+
188+
@Flag(exclusivity: .chooseLast, help: "") var g: E?
189+
@Flag() var g1: E?
190+
@Flag(help: "") var g2: E?
191+
@Flag(exclusivity: .chooseLast) var g3: E?
192+
193+
@Flag(help: "") var h: [E]
194+
@Flag() var h1: [E]
195+
}
196+
197+
extension SourceCompatEndToEndTests {
198+
func testParsingAll() throws {
199+
// This is just checking building the argument definitions, not the actual
200+
// validation or usage of these definitions, which would fail.
201+
_ = AlmostAllArguments()
202+
_ = AllOptions()
203+
_ = AllFlags()
204+
}
205+
}
206+
207+
// MARK: - Deprecated APIs
208+
209+
fileprivate struct DeprecatedFlags: ParsableArguments {
210+
enum One: String, CaseIterable {
211+
case one
212+
}
213+
enum Two: String, CaseIterable {
214+
case two
215+
}
216+
enum Three: String, CaseIterable {
217+
case three
218+
case four
219+
}
220+
221+
@Flag() var single: One
222+
@Flag() var optional: Two?
223+
@Flag() var array: [Three]
224+
@Flag(name: .long) var size: Size?
225+
}
226+
227+
extension SourceCompatEndToEndTests {
228+
func testParsingDeprecatedFlags() throws {
229+
AssertParse(DeprecatedFlags.self, ["--one"]) { options in
230+
XCTAssertEqual(options.single, .one)
231+
XCTAssertNil(options.optional)
232+
XCTAssertTrue(options.array.isEmpty)
233+
}
234+
235+
AssertParse(DeprecatedFlags.self, ["--one", "--two", "--three", "--four", "--three"]) { options in
236+
XCTAssertEqual(options.single, .one)
237+
XCTAssertEqual(options.optional, .two)
238+
XCTAssertEqual(options.array, [.three, .four, .three])
239+
}
240+
}
241+
}
242+

Tests/ArgumentParserUnitTests/NameSpecificationTests.swift

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -49,10 +49,10 @@ fileprivate func Assert(nameSpecification: NameSpecification, key: String, makeN
4949

5050
fileprivate func Assert<N>(names: [N], expected: [N], file: StaticString = #file, line: UInt = #line) where N: Equatable {
5151
names.forEach {
52-
XCTAssert(expected.contains($0), "Unexpected name '\($0)'.", file: file, line: line)
52+
XCTAssert(expected.contains($0), "Unexpected name '\($0)'.", file: (file), line: line)
5353
}
5454
expected.forEach {
55-
XCTAssert(names.contains($0), "Missing name '\($0)'.", file: file, line: line)
55+
XCTAssert(names.contains($0), "Missing name '\($0)'.", file: (file), line: line)
5656
}
5757
}
5858

0 commit comments

Comments
 (0)