RepeatingEndToEndTests.swift 13 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393394395396397398399400401402403404405406407408409410411412413414415416417418419420421422423424425426427428429430431432433434
  1. //===----------------------------------------------------------------------===//
  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. import ArgumentParser
  12. import ArgumentParserTestHelpers
  13. import XCTest
  14. final class RepeatingEndToEndTests: XCTestCase {
  15. }
  16. // MARK: -
  17. private struct Bar: ParsableArguments {
  18. @Option() var name: [String] = []
  19. }
  20. // swift-format-ignore: AlwaysUseLowerCamelCase
  21. // https://github.com/apple/swift-argument-parser/issues/710
  22. extension RepeatingEndToEndTests {
  23. func testParsing_repeatingString() throws {
  24. AssertParse(Bar.self, []) { bar in
  25. XCTAssertTrue(bar.name.isEmpty)
  26. }
  27. AssertParse(Bar.self, ["--name", "Bar"]) { bar in
  28. XCTAssertEqual(bar.name.count, 1)
  29. XCTAssertEqual(bar.name.first, "Bar")
  30. }
  31. AssertParse(Bar.self, ["--name", "Bar", "--name", "Foo"]) { bar in
  32. XCTAssertEqual(bar.name.count, 2)
  33. XCTAssertEqual(bar.name.first, "Bar")
  34. XCTAssertEqual(bar.name.last, "Foo")
  35. }
  36. }
  37. }
  38. // MARK: -
  39. private struct Foo: ParsableArguments {
  40. @Flag()
  41. var verbose: Int
  42. }
  43. // swift-format-ignore: AlwaysUseLowerCamelCase
  44. // https://github.com/apple/swift-argument-parser/issues/710
  45. extension RepeatingEndToEndTests {
  46. func testParsing_incrementInteger() throws {
  47. AssertParse(Foo.self, []) { options in
  48. XCTAssertEqual(options.verbose, 0)
  49. }
  50. AssertParse(Foo.self, ["--verbose"]) { options in
  51. XCTAssertEqual(options.verbose, 1)
  52. }
  53. AssertParse(Foo.self, ["--verbose", "--verbose"]) { options in
  54. XCTAssertEqual(options.verbose, 2)
  55. }
  56. }
  57. }
  58. // MARK: -
  59. private struct Baz: ParsableArguments {
  60. @Flag var verbose: Bool = false
  61. @Option(parsing: .remaining) var names: [String] = []
  62. }
  63. // swift-format-ignore: AlwaysUseLowerCamelCase
  64. // https://github.com/apple/swift-argument-parser/issues/710
  65. extension RepeatingEndToEndTests {
  66. func testParsing_repeatingStringRemaining_1() {
  67. AssertParse(Baz.self, []) { baz in
  68. XCTAssertFalse(baz.verbose)
  69. XCTAssertTrue(baz.names.isEmpty)
  70. }
  71. }
  72. func testParsing_repeatingStringRemaining_2() {
  73. AssertParse(Baz.self, ["--names"]) { baz in
  74. XCTAssertFalse(baz.verbose)
  75. XCTAssertTrue(baz.names.isEmpty)
  76. }
  77. }
  78. func testParsing_repeatingStringRemaining_3() {
  79. AssertParse(Baz.self, ["--names", "one"]) { baz in
  80. XCTAssertFalse(baz.verbose)
  81. XCTAssertEqual(baz.names, ["one"])
  82. }
  83. }
  84. func testParsing_repeatingStringRemaining_4() {
  85. AssertParse(Baz.self, ["--names", "one", "two"]) { baz in
  86. XCTAssertFalse(baz.verbose)
  87. XCTAssertEqual(baz.names, ["one", "two"])
  88. }
  89. }
  90. func testParsing_repeatingStringRemaining_5() {
  91. AssertParse(Baz.self, ["--verbose", "--names", "one", "two"]) { baz in
  92. XCTAssertTrue(baz.verbose)
  93. XCTAssertEqual(baz.names, ["one", "two"])
  94. }
  95. }
  96. func testParsing_repeatingStringRemaining_6() {
  97. AssertParse(Baz.self, ["--names", "one", "two", "--verbose"]) { baz in
  98. XCTAssertFalse(baz.verbose)
  99. XCTAssertEqual(baz.names, ["one", "two", "--verbose"])
  100. }
  101. }
  102. func testParsing_repeatingStringRemaining_7() {
  103. AssertParse(Baz.self, ["--verbose", "--names", "one", "two", "--verbose"]) {
  104. baz in
  105. XCTAssertTrue(baz.verbose)
  106. XCTAssertEqual(baz.names, ["one", "two", "--verbose"])
  107. }
  108. }
  109. func testParsing_repeatingStringRemaining_8() {
  110. AssertParse(
  111. Baz.self,
  112. ["--verbose", "--names", "one", "two", "--verbose", "--other", "three"]
  113. ) { baz in
  114. XCTAssertTrue(baz.verbose)
  115. XCTAssertEqual(
  116. baz.names, ["one", "two", "--verbose", "--other", "three"])
  117. }
  118. }
  119. }
  120. // MARK: -
  121. private struct Outer: ParsableCommand {
  122. static let configuration = CommandConfiguration(subcommands: [Inner.self])
  123. }
  124. private struct Inner: ParsableCommand {
  125. @Flag
  126. var verbose: Bool = false
  127. @Argument(parsing: .captureForPassthrough)
  128. var files: [String] = []
  129. }
  130. // swift-format-ignore: AlwaysUseLowerCamelCase
  131. // https://github.com/apple/swift-argument-parser/issues/710
  132. extension RepeatingEndToEndTests {
  133. func testParsing_subcommandRemaining() {
  134. AssertParseCommand(
  135. Outer.self, Inner.self,
  136. ["inner", "--verbose", "one", "two", "--", "three", "--other"]
  137. ) { inner in
  138. XCTAssertTrue(inner.verbose)
  139. XCTAssertEqual(inner.files, ["one", "two", "--", "three", "--other"])
  140. }
  141. }
  142. }
  143. // MARK: -
  144. private struct Qux: ParsableArguments {
  145. @Option(parsing: .upToNextOption) var names: [String] = []
  146. @Flag var verbose: Bool = false
  147. @Argument() var extra: String?
  148. }
  149. // swift-format-ignore: AlwaysUseLowerCamelCase
  150. // https://github.com/apple/swift-argument-parser/issues/710
  151. extension RepeatingEndToEndTests {
  152. func testParsing_repeatingStringUpToNext() throws {
  153. AssertParse(Qux.self, []) { qux in
  154. XCTAssertFalse(qux.verbose)
  155. XCTAssertTrue(qux.names.isEmpty)
  156. XCTAssertNil(qux.extra)
  157. }
  158. AssertParse(Qux.self, ["--names", "one"]) { qux in
  159. XCTAssertFalse(qux.verbose)
  160. XCTAssertEqual(qux.names, ["one"])
  161. XCTAssertNil(qux.extra)
  162. }
  163. AssertParse(
  164. Qux.self,
  165. [
  166. "--names", "one", "two", "--verbose", "--names", "three", "--names",
  167. "four",
  168. ]
  169. ) { qux in
  170. XCTAssertTrue(qux.verbose)
  171. XCTAssertEqual(qux.names, ["one", "two", "three", "four"])
  172. XCTAssertNil(qux.extra)
  173. }
  174. AssertParse(
  175. Qux.self,
  176. [
  177. "extra", "--names", "one", "--names", "two", "--verbose", "--names",
  178. "three", "four",
  179. ]
  180. ) { qux in
  181. XCTAssertTrue(qux.verbose)
  182. XCTAssertEqual(qux.names, ["one", "two", "three", "four"])
  183. XCTAssertEqual(qux.extra, "extra")
  184. }
  185. AssertParse(Qux.self, ["--names", "one", "two"]) { qux in
  186. XCTAssertFalse(qux.verbose)
  187. XCTAssertEqual(qux.names, ["one", "two"])
  188. XCTAssertNil(qux.extra)
  189. }
  190. AssertParse(Qux.self, ["--names", "one", "two", "--verbose"]) { qux in
  191. XCTAssertTrue(qux.verbose)
  192. XCTAssertEqual(qux.names, ["one", "two"])
  193. XCTAssertNil(qux.extra)
  194. }
  195. AssertParse(Qux.self, ["--names", "one", "two", "--verbose", "three"]) {
  196. qux in
  197. XCTAssertTrue(qux.verbose)
  198. XCTAssertEqual(qux.names, ["one", "two"])
  199. XCTAssertEqual(qux.extra, "three")
  200. }
  201. AssertParse(Qux.self, ["--verbose", "--names", "one", "two"]) { qux in
  202. XCTAssertTrue(qux.verbose)
  203. XCTAssertEqual(qux.names, ["one", "two"])
  204. XCTAssertNil(qux.extra)
  205. }
  206. AssertParse(Qux.self, ["--verbose", "--names=one"]) { qux in
  207. XCTAssertTrue(qux.verbose)
  208. XCTAssertEqual(qux.names, ["one"])
  209. XCTAssertNil(qux.extra)
  210. }
  211. AssertParse(Qux.self, ["--verbose", "--names=one", "two"]) { qux in
  212. XCTAssertTrue(qux.verbose)
  213. XCTAssertEqual(qux.names, ["one", "two"])
  214. XCTAssertNil(qux.extra)
  215. }
  216. AssertParse(Qux.self, ["--names=one", "--verbose", "two"]) { qux in
  217. XCTAssertTrue(qux.verbose)
  218. XCTAssertEqual(qux.names, ["one"])
  219. XCTAssertEqual(qux.extra, "two")
  220. }
  221. }
  222. func testParsing_repeatingStringUpToNext_Fails() throws {
  223. XCTAssertThrowsError(try Qux.parse(["--names", "one", "--other"]))
  224. XCTAssertThrowsError(try Qux.parse(["--names", "one", "two", "--other"]))
  225. XCTAssertThrowsError(try Qux.parse(["--names", "--other"]))
  226. XCTAssertThrowsError(try Qux.parse(["--names", "--verbose"]))
  227. XCTAssertThrowsError(try Qux.parse(["--names", "--verbose", "three"]))
  228. }
  229. }
  230. // MARK: -
  231. private struct Wobble: ParsableArguments {
  232. struct WobbleError: Error {}
  233. struct Name: Equatable, Sendable {
  234. var value: String
  235. init(_ value: String) throws {
  236. if value == "bad" { throw WobbleError() }
  237. self.value = value
  238. }
  239. }
  240. @Option(transform: Name.init) var names: [Name] = []
  241. @Option(parsing: .upToNextOption, transform: Name.init) var moreNames:
  242. [Name] = []
  243. @Option(parsing: .remaining, transform: Name.init) var evenMoreNames: [Name] =
  244. []
  245. }
  246. // swift-format-ignore: AlwaysUseLowerCamelCase
  247. // https://github.com/apple/swift-argument-parser/issues/710
  248. extension RepeatingEndToEndTests {
  249. func testParsing_repeatingWithTransform() throws {
  250. let names = ["--names", "one", "--names", "two"]
  251. let moreNames = ["--more-names", "three", "four", "five"]
  252. let evenMoreNames = ["--even-more-names", "six", "--seven", "--eight"]
  253. AssertParse(Wobble.self, []) { wobble in
  254. XCTAssertTrue(wobble.names.isEmpty)
  255. XCTAssertTrue(wobble.moreNames.isEmpty)
  256. XCTAssertTrue(wobble.evenMoreNames.isEmpty)
  257. }
  258. AssertParse(Wobble.self, names) { wobble in
  259. XCTAssertEqual(wobble.names.map { $0.value }, ["one", "two"])
  260. XCTAssertTrue(wobble.moreNames.isEmpty)
  261. XCTAssertTrue(wobble.evenMoreNames.isEmpty)
  262. }
  263. AssertParse(Wobble.self, moreNames) { wobble in
  264. XCTAssertTrue(wobble.names.isEmpty)
  265. XCTAssertEqual(
  266. wobble.moreNames.map { $0.value }, ["three", "four", "five"])
  267. XCTAssertTrue(wobble.evenMoreNames.isEmpty)
  268. }
  269. AssertParse(Wobble.self, evenMoreNames) { wobble in
  270. XCTAssertTrue(wobble.names.isEmpty)
  271. XCTAssertTrue(wobble.moreNames.isEmpty)
  272. XCTAssertEqual(
  273. wobble.evenMoreNames.map { $0.value }, ["six", "--seven", "--eight"])
  274. }
  275. AssertParse(Wobble.self, Array([names, moreNames, evenMoreNames].joined()))
  276. { wobble in
  277. XCTAssertEqual(wobble.names.map { $0.value }, ["one", "two"])
  278. XCTAssertEqual(
  279. wobble.moreNames.map { $0.value }, ["three", "four", "five"])
  280. XCTAssertEqual(
  281. wobble.evenMoreNames.map { $0.value }, ["six", "--seven", "--eight"])
  282. }
  283. AssertParse(Wobble.self, Array([moreNames, names, evenMoreNames].joined()))
  284. { wobble in
  285. XCTAssertEqual(wobble.names.map { $0.value }, ["one", "two"])
  286. XCTAssertEqual(
  287. wobble.moreNames.map { $0.value }, ["three", "four", "five"])
  288. XCTAssertEqual(
  289. wobble.evenMoreNames.map { $0.value }, ["six", "--seven", "--eight"])
  290. }
  291. AssertParse(Wobble.self, Array([moreNames, evenMoreNames, names].joined()))
  292. { wobble in
  293. XCTAssertTrue(wobble.names.isEmpty)
  294. XCTAssertEqual(
  295. wobble.moreNames.map { $0.value }, ["three", "four", "five"])
  296. XCTAssertEqual(
  297. wobble.evenMoreNames.map { $0.value },
  298. ["six", "--seven", "--eight", "--names", "one", "--names", "two"])
  299. }
  300. }
  301. func testParsing_repeatingWithTransform_Fails() throws {
  302. XCTAssertThrowsError(try Wobble.parse(["--names", "one", "--other"]))
  303. XCTAssertThrowsError(try Wobble.parse(["--more-names", "one", "--other"]))
  304. XCTAssertThrowsError(
  305. try Wobble.parse(["--names", "one", "--names", "bad"]))
  306. XCTAssertThrowsError(
  307. try Wobble.parse(["--more-names", "one", "two", "bad", "--names", "one"]))
  308. XCTAssertThrowsError(
  309. try Wobble.parse([
  310. "--even-more-names", "one", "two", "--names", "one", "bad",
  311. ]))
  312. }
  313. }
  314. // MARK: -
  315. private struct Weazle: ParsableArguments {
  316. @Flag var verbose: Bool = false
  317. @Argument() var names: [String] = []
  318. }
  319. // swift-format-ignore: AlwaysUseLowerCamelCase
  320. // https://github.com/apple/swift-argument-parser/issues/710
  321. extension RepeatingEndToEndTests {
  322. func testParsing_repeatingArgument() throws {
  323. AssertParse(Weazle.self, ["one", "two", "three", "--verbose"]) { weazle in
  324. XCTAssertTrue(weazle.verbose)
  325. XCTAssertEqual(weazle.names, ["one", "two", "three"])
  326. }
  327. AssertParse(Weazle.self, ["--verbose", "one", "two", "three"]) { weazle in
  328. XCTAssertTrue(weazle.verbose)
  329. XCTAssertEqual(weazle.names, ["one", "two", "three"])
  330. }
  331. AssertParse(
  332. Weazle.self, ["one", "two", "three", "--", "--other", "--verbose"]
  333. ) { weazle in
  334. XCTAssertFalse(weazle.verbose)
  335. XCTAssertEqual(
  336. weazle.names, ["one", "two", "three", "--other", "--verbose"])
  337. }
  338. }
  339. }
  340. // MARK: -
  341. struct PerformanceTest: ParsableCommand {
  342. @Option(name: .short) var bundleIdentifiers: [String] = []
  343. mutating func run() throws { print(bundleIdentifiers) }
  344. }
  345. private func argumentGenerator(_ count: Int) -> [String] {
  346. Array((1...count).map { ["-b", "bundle-id\($0)"] }.joined())
  347. }
  348. private func time(_ body: () -> Void) -> TimeInterval {
  349. let start = Date()
  350. body()
  351. return Date().timeIntervalSince(start)
  352. }
  353. // swift-format-ignore: AlwaysUseLowerCamelCase
  354. // https://github.com/apple/swift-argument-parser/issues/710
  355. extension RepeatingEndToEndTests {
  356. // A regression test against array parsing performance going non-linear.
  357. func testParsing_repeatingPerformance() throws {
  358. let timeFor20 = time {
  359. AssertParse(PerformanceTest.self, argumentGenerator(100)) { test in
  360. XCTAssertEqual(100, test.bundleIdentifiers.count)
  361. }
  362. }
  363. let timeFor40 = time {
  364. AssertParse(PerformanceTest.self, argumentGenerator(200)) { test in
  365. XCTAssertEqual(200, test.bundleIdentifiers.count)
  366. }
  367. }
  368. XCTAssertLessThan(timeFor40, timeFor20 * 10)
  369. }
  370. }