SubcommandEndToEndTests.swift 7.5 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297
  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 SubcommandEndToEndTests: XCTestCase {
  15. }
  16. // MARK: Single value String
  17. private struct Foo: ParsableCommand {
  18. static let configuration =
  19. CommandConfiguration(subcommands: [CommandA.self, CommandB.self])
  20. @Option() var name: String
  21. }
  22. private struct CommandA: ParsableCommand {
  23. static let configuration = CommandConfiguration(commandName: "a")
  24. @OptionGroup() var foo: Foo
  25. @Option() var bar: Int
  26. }
  27. private struct CommandB: ParsableCommand {
  28. static let configuration = CommandConfiguration(commandName: "b")
  29. @OptionGroup() var foo: Foo
  30. @Option() var baz: String
  31. }
  32. // swift-format-ignore: AlwaysUseLowerCamelCase
  33. // https://github.com/apple/swift-argument-parser/issues/710
  34. extension SubcommandEndToEndTests {
  35. func testParsing_SubCommand() throws {
  36. AssertParseCommand(
  37. Foo.self, CommandA.self, ["--name", "Foo", "a", "--bar", "42"]
  38. ) { a in
  39. XCTAssertEqual(a.bar, 42)
  40. XCTAssertEqual(a.foo.name, "Foo")
  41. }
  42. AssertParseCommand(
  43. Foo.self, CommandB.self, ["--name", "A", "b", "--baz", "abc"]
  44. ) { b in
  45. XCTAssertEqual(b.baz, "abc")
  46. XCTAssertEqual(b.foo.name, "A")
  47. }
  48. }
  49. func testParsing_SubCommand_manual() throws {
  50. AssertParseCommand(
  51. Foo.self, CommandA.self, ["--name", "Foo", "a", "--bar", "42"]
  52. ) { a in
  53. XCTAssertEqual(a.bar, 42)
  54. XCTAssertEqual(a.foo.name, "Foo")
  55. }
  56. AssertParseCommand(Foo.self, Foo.self, ["--name", "Foo"]) { foo in
  57. XCTAssertEqual(foo.name, "Foo")
  58. }
  59. }
  60. func testParsing_SubCommand_help() throws {
  61. let helpFoo = Foo.message(for: CleanExit.helpRequest())
  62. let helpA = Foo.message(for: CleanExit.helpRequest(CommandA.self))
  63. let helpB = Foo.message(for: CleanExit.helpRequest(CommandB.self))
  64. AssertEqualStrings(
  65. actual: helpFoo,
  66. expected: """
  67. USAGE: foo --name <name> <subcommand>
  68. OPTIONS:
  69. --name <name>
  70. -h, --help Show help information.
  71. SUBCOMMANDS:
  72. a
  73. b
  74. See 'foo help <subcommand>' for detailed help.
  75. """)
  76. AssertEqualStrings(
  77. actual: helpA,
  78. expected: """
  79. USAGE: foo a --name <name> --bar <bar>
  80. OPTIONS:
  81. --name <name>
  82. --bar <bar>
  83. -h, --help Show help information.
  84. """)
  85. AssertEqualStrings(
  86. actual: helpB,
  87. expected: """
  88. USAGE: foo b --name <name> --baz <baz>
  89. OPTIONS:
  90. --name <name>
  91. --baz <baz>
  92. -h, --help Show help information.
  93. """)
  94. }
  95. func testParsing_SubCommand_fails() throws {
  96. XCTAssertThrowsError(
  97. try Foo.parse(["--name", "Foo", "a", "--baz", "42"]),
  98. "'baz' is not an option for the 'a' subcommand.")
  99. XCTAssertThrowsError(
  100. try Foo.parse(["--name", "Foo", "b", "--bar", "42"]),
  101. "'bar' is not an option for the 'b' subcommand.")
  102. }
  103. }
  104. private struct Math: ParsableCommand {
  105. enum Operation: String, ExpressibleByArgument {
  106. case add
  107. case multiply
  108. }
  109. @Option(help: "The operation to perform")
  110. var operation: Operation = .add
  111. @Flag(name: [.short, .long])
  112. var verbose: Bool = false
  113. @Argument(help: "The first operand")
  114. var operands: [Int] = []
  115. var didRun = false
  116. mutating func run() {
  117. XCTAssertEqual(operation, .multiply)
  118. XCTAssertTrue(verbose)
  119. XCTAssertEqual(operands, [5, 11])
  120. didRun = true
  121. }
  122. }
  123. // swift-format-ignore: AlwaysUseLowerCamelCase
  124. // https://github.com/apple/swift-argument-parser/issues/710
  125. extension SubcommandEndToEndTests {
  126. func testParsing_SingleCommand() throws {
  127. var mathCommand =
  128. try Math.parseAsRoot(["--operation", "multiply", "-v", "5", "11"])
  129. as! Math
  130. XCTAssertFalse(mathCommand.didRun)
  131. mathCommand.run()
  132. XCTAssertTrue(mathCommand.didRun)
  133. }
  134. }
  135. // MARK: Nested Command Arguments Validated
  136. struct BaseCommand: ParsableCommand {
  137. enum BaseCommandError: Error {
  138. case baseCommandFailure
  139. case subCommandFailure
  140. }
  141. static let baseFlagValue = "base"
  142. static let configuration = CommandConfiguration(
  143. commandName: "base",
  144. subcommands: [SubCommand.self]
  145. )
  146. @Option()
  147. var baseFlag: String
  148. mutating func validate() throws {
  149. guard baseFlag == BaseCommand.baseFlagValue else {
  150. throw BaseCommandError.baseCommandFailure
  151. }
  152. }
  153. }
  154. extension BaseCommand {
  155. struct SubCommand: ParsableCommand {
  156. static let subFlagValue = "sub"
  157. static let configuration = CommandConfiguration(
  158. commandName: "sub",
  159. subcommands: [SubSubCommand.self]
  160. )
  161. @Option()
  162. var subFlag: String
  163. mutating func validate() throws {
  164. guard subFlag == SubCommand.subFlagValue else {
  165. throw BaseCommandError.subCommandFailure
  166. }
  167. }
  168. }
  169. }
  170. extension BaseCommand.SubCommand {
  171. struct SubSubCommand: ParsableCommand, TestableParsableArguments {
  172. let didValidateExpectation = XCTestExpectation(
  173. singleExpectation: "did validate subcommand")
  174. static let configuration = CommandConfiguration(
  175. commandName: "subsub"
  176. )
  177. @Flag
  178. var subSubFlag: Bool = false
  179. private enum CodingKeys: CodingKey {
  180. case subSubFlag
  181. }
  182. }
  183. }
  184. // swift-format-ignore: AlwaysUseLowerCamelCase
  185. // https://github.com/apple/swift-argument-parser/issues/710
  186. extension SubcommandEndToEndTests {
  187. func testValidate_subcommands() {
  188. // provide a value to base-flag that will throw
  189. AssertErrorMessage(
  190. BaseCommand.self,
  191. ["--base-flag", "foo", "sub", "--sub-flag", "foo", "subsub"],
  192. "baseCommandFailure"
  193. )
  194. // provide a value to sub-flag that will throw
  195. AssertErrorMessage(
  196. BaseCommand.self,
  197. [
  198. "--base-flag", BaseCommand.baseFlagValue, "sub", "--sub-flag", "foo",
  199. "subsub",
  200. ],
  201. "subCommandFailure"
  202. )
  203. // provide a valid command and make sure both validates succeed
  204. AssertParseCommand(
  205. BaseCommand.self,
  206. BaseCommand.SubCommand.SubSubCommand.self,
  207. [
  208. "--base-flag", BaseCommand.baseFlagValue, "sub", "--sub-flag",
  209. BaseCommand.SubCommand.subFlagValue, "subsub", "--sub-sub-flag",
  210. ]
  211. ) { cmd in
  212. XCTAssertTrue(cmd.subSubFlag)
  213. // make sure that the instance of SubSubCommand provided
  214. // had its validate method called, not just that any instance of SubSubCommand was validated
  215. wait(for: [cmd.didValidateExpectation], timeout: 0.1)
  216. }
  217. }
  218. }
  219. // MARK: Version flags
  220. private struct A: ParsableCommand {
  221. static let configuration = CommandConfiguration(
  222. version: "1.0.0",
  223. subcommands: [HasVersionFlag.self, NoVersionFlag.self])
  224. struct HasVersionFlag: ParsableCommand {
  225. @Flag var version: Bool = false
  226. }
  227. struct NoVersionFlag: ParsableCommand {
  228. @Flag var hello: Bool = false
  229. }
  230. }
  231. extension SubcommandEndToEndTests {
  232. func testParsingVersionFlags() throws {
  233. AssertErrorMessage(A.self, ["--version"], "1.0.0")
  234. AssertErrorMessage(A.self, ["no-version-flag", "--version"], "1.0.0")
  235. AssertParseCommand(
  236. A.self, A.HasVersionFlag.self, ["has-version-flag", "--version"]
  237. ) { cmd in
  238. XCTAssertTrue(cmd.version)
  239. }
  240. }
  241. }