ErrorMessageTests.swift 9.2 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360
  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 ArgumentParserTestHelpers
  12. import XCTest
  13. @testable import ArgumentParser
  14. final class ErrorMessageTests: XCTestCase {}
  15. // MARK: -
  16. private struct Bar: ParsableArguments {
  17. @Option() var name: String
  18. @Option(name: [.short, .long]) var format: String
  19. }
  20. // swift-format-ignore: AlwaysUseLowerCamelCase
  21. // https://github.com/apple/swift-argument-parser/issues/710
  22. extension ErrorMessageTests {
  23. func testMissing_1() {
  24. AssertErrorMessage(
  25. Bar.self, [], "Missing expected argument '--name <name>'")
  26. }
  27. func testMissing_2() {
  28. AssertErrorMessage(
  29. Bar.self, ["--name", "a"], "Missing expected argument '--format <format>'"
  30. )
  31. }
  32. func testUnknownOption_1() {
  33. AssertErrorMessage(
  34. Bar.self, ["--name", "a", "--format", "b", "--verbose"],
  35. "Unknown option '--verbose'")
  36. }
  37. func testUnknownOption_2() {
  38. AssertErrorMessage(
  39. Bar.self, ["--name", "a", "--format", "b", "-q"], "Unknown option '-q'")
  40. }
  41. func testUnknownOption_3() {
  42. AssertErrorMessage(
  43. Bar.self, ["--name", "a", "--format", "b", "-bar"],
  44. "Unknown option '-bar'")
  45. }
  46. func testUnknownOption_4() {
  47. AssertErrorMessage(
  48. Bar.self, ["--name", "a", "-foz", "b"], "Unknown option '-o'")
  49. }
  50. func testMissingValue_1() {
  51. AssertErrorMessage(
  52. Bar.self, ["--name", "a", "--format"],
  53. "Missing value for '--format <format>'")
  54. }
  55. func testMissingValue_2() {
  56. AssertErrorMessage(
  57. Bar.self, ["--name", "a", "-f"], "Missing value for '-f <format>'")
  58. }
  59. func testUnusedValue_1() {
  60. AssertErrorMessage(
  61. Bar.self, ["--name", "a", "--format", "f", "b"], "Unexpected argument 'b'"
  62. )
  63. }
  64. func testUnusedValue_2() {
  65. AssertErrorMessage(
  66. Bar.self, ["--name", "a", "--format", "f", "b", "baz"],
  67. "2 unexpected arguments: 'b', 'baz'")
  68. }
  69. }
  70. private enum Format: String, Equatable, Decodable, ExpressibleByArgument,
  71. CaseIterable
  72. {
  73. case text
  74. case json
  75. case csv
  76. }
  77. private enum Name: String, Equatable, Decodable, ExpressibleByArgument,
  78. CaseIterable
  79. {
  80. case bruce
  81. case clint
  82. case hulk
  83. case natasha
  84. case steve
  85. case thor
  86. case tony
  87. }
  88. private enum Counter: Int, ExpressibleByArgument, CaseIterable {
  89. case one = 1
  90. case two, three, four
  91. }
  92. private struct Foo: ParsableArguments {
  93. @Option(name: [.short, .long])
  94. var format: Format
  95. @Option(name: [.short, .long])
  96. var name: Name?
  97. }
  98. private struct EnumWithFewCasesArrayArgument: ParsableArguments {
  99. @Argument
  100. var formats: [Format]
  101. }
  102. private struct EnumWithManyCasesArrayArgument: ParsableArguments {
  103. @Argument
  104. var names: [Name]
  105. }
  106. private struct EnumWithIntRawValue: ParsableArguments {
  107. @Option var counter: Counter
  108. }
  109. extension ErrorMessageTests {
  110. func testWrongEnumValue() {
  111. AssertErrorMessage(
  112. Foo.self, ["--format", "png"],
  113. "The value 'png' is invalid for '--format <format>'. Please provide one of 'text', 'json' or 'csv'."
  114. )
  115. AssertErrorMessage(
  116. Foo.self, ["-f", "png"],
  117. "The value 'png' is invalid for '-f <format>'. Please provide one of 'text', 'json' or 'csv'."
  118. )
  119. AssertErrorMessage(
  120. Foo.self, ["-f", "text", "--name", "loki"],
  121. """
  122. The value 'loki' is invalid for '--name <name>'. Please provide one of the following:
  123. - bruce
  124. - clint
  125. - hulk
  126. - natasha
  127. - steve
  128. - thor
  129. - tony
  130. """)
  131. AssertErrorMessage(
  132. Foo.self, ["-f", "text", "-n", "loki"],
  133. """
  134. The value 'loki' is invalid for '-n <name>'. Please provide one of the following:
  135. - bruce
  136. - clint
  137. - hulk
  138. - natasha
  139. - steve
  140. - thor
  141. - tony
  142. """)
  143. AssertErrorMessage(
  144. EnumWithFewCasesArrayArgument.self, ["png"],
  145. "The value 'png' is invalid for '<formats>'. Please provide one of 'text', 'json' or 'csv'."
  146. )
  147. AssertErrorMessage(
  148. EnumWithManyCasesArrayArgument.self, ["loki"],
  149. """
  150. The value 'loki' is invalid for '<names>'. Please provide one of the following:
  151. - bruce
  152. - clint
  153. - hulk
  154. - natasha
  155. - steve
  156. - thor
  157. - tony
  158. """)
  159. AssertErrorMessage(
  160. EnumWithIntRawValue.self, ["--counter", "one"],
  161. """
  162. The value 'one' is invalid for '--counter <counter>'. \
  163. Please provide one of '1', '2', '3' or '4'.
  164. """)
  165. }
  166. }
  167. private struct Baz: ParsableArguments {
  168. @Flag
  169. var verbose: Bool = false
  170. }
  171. extension ErrorMessageTests {
  172. func testUnexpectedValue() {
  173. AssertErrorMessage(
  174. Baz.self, ["--verbose=foo"],
  175. "The option '--verbose' does not take any value, but 'foo' was specified."
  176. )
  177. }
  178. }
  179. private struct Qux: ParsableArguments {
  180. @Argument()
  181. var firstNumber: Int
  182. @Option(name: .customLong("number-two"))
  183. var secondNumber: Int
  184. }
  185. extension ErrorMessageTests {
  186. func testMissingArgument() {
  187. AssertErrorMessage(
  188. Qux.self, ["--number-two", "2"],
  189. "Missing expected argument '<first-number>'")
  190. }
  191. func testInvalidNumber() {
  192. AssertErrorMessage(
  193. Qux.self, ["--number-two", "2", "a"],
  194. "The value 'a' is invalid for '<first-number>'")
  195. AssertErrorMessage(
  196. Qux.self, ["--number-two", "a", "1"],
  197. "The value 'a' is invalid for '--number-two <number-two>'")
  198. }
  199. }
  200. private struct Qwz: ParsableArguments {
  201. @Option() var name: String?
  202. @Option(name: [.customLong("title", withSingleDash: true)]) var title: String?
  203. }
  204. // swift-format-ignore: AlwaysUseLowerCamelCase
  205. // https://github.com/apple/swift-argument-parser/issues/710
  206. extension ErrorMessageTests {
  207. func testMispelledArgument_1() {
  208. AssertErrorMessage(
  209. Qwz.self, ["--nme"], "Unknown option '--nme'. Did you mean '--name'?")
  210. AssertErrorMessage(
  211. Qwz.self, ["-name"], "Unknown option '-name'. Did you mean '--name'?")
  212. }
  213. func testMispelledArgument_2() {
  214. AssertErrorMessage(
  215. Qwz.self, ["-ttle"], "Unknown option '-ttle'. Did you mean '-title'?")
  216. AssertErrorMessage(
  217. Qwz.self, ["--title"], "Unknown option '--title'. Did you mean '-title'?")
  218. }
  219. func testMispelledArgument_3() {
  220. AssertErrorMessage(
  221. Qwz.self, ["--not-similar"], "Unknown option '--not-similar'")
  222. }
  223. func testMispelledArgument_4() {
  224. AssertErrorMessage(Qwz.self, ["-x"], "Unknown option '-x'")
  225. }
  226. }
  227. private struct Options: ParsableArguments {
  228. enum OutputBehaviour: String, EnumerableFlag {
  229. case stats, count, list
  230. static func name(for value: OutputBehaviour) -> NameSpecification {
  231. .shortAndLong
  232. }
  233. }
  234. @Flag(help: "Program output")
  235. var behaviour: OutputBehaviour = .list
  236. @Flag(inversion: .prefixedNo, exclusivity: .exclusive) var bool: Bool
  237. }
  238. private struct OptOptions: ParsableArguments {
  239. enum OutputBehaviour: String, EnumerableFlag {
  240. case stats, count, list
  241. static func name(for value: OutputBehaviour) -> NameSpecification {
  242. .short
  243. }
  244. }
  245. @Flag(help: "Program output")
  246. var behaviour: OutputBehaviour?
  247. }
  248. extension ErrorMessageTests {
  249. func testDuplicateFlags() {
  250. AssertErrorMessage(
  251. Options.self, ["--list", "--bool", "-s"],
  252. "Value to be set with flag \'-s\' had already been set with flag \'--list\'"
  253. )
  254. AssertErrorMessage(
  255. Options.self, ["-cbl"],
  256. "Value to be set with flag \'l\' in \'-cbl\' had already been set with flag \'c\' in \'-cbl\'"
  257. )
  258. AssertErrorMessage(
  259. Options.self, ["-bc", "--stats", "-l"],
  260. "Value to be set with flag \'--stats\' had already been set with flag \'c\' in \'-bc\'"
  261. )
  262. AssertErrorMessage(
  263. Options.self, ["--no-bool", "--bool"],
  264. "Value to be set with flag \'--bool\' had already been set with flag \'--no-bool\'"
  265. )
  266. AssertErrorMessage(
  267. OptOptions.self, ["-cbl"],
  268. "Value to be set with flag \'l\' in \'-cbl\' had already been set with flag \'c\' in \'-cbl\'"
  269. )
  270. }
  271. }
  272. // (see issue #434).
  273. private struct EmptyArray: ParsableArguments {
  274. @Option(parsing: .upToNextOption)
  275. var array: [String] = []
  276. @Flag(name: [.short, .long])
  277. var verbose = false
  278. }
  279. extension ErrorMessageTests {
  280. func testEmptyArrayOption() {
  281. AssertErrorMessage(
  282. EmptyArray.self, ["--array"], "Missing value for '--array <array>'")
  283. AssertErrorMessage(
  284. EmptyArray.self, ["--array", "--verbose"],
  285. "Missing value for '--array <array>'")
  286. AssertErrorMessage(
  287. EmptyArray.self, ["-verbose", "--array"],
  288. "Missing value for '--array <array>'")
  289. AssertErrorMessage(
  290. EmptyArray.self, ["--array", "-v"], "Missing value for '--array <array>'")
  291. AssertErrorMessage(
  292. EmptyArray.self, ["-v", "--array"], "Missing value for '--array <array>'")
  293. }
  294. }
  295. // MARK: -
  296. private struct Repeat: ParsableArguments {
  297. @Option() var count: Int?
  298. @Argument() var phrase: String
  299. }
  300. extension ErrorMessageTests {
  301. func testBadOptionBeforeArgument() {
  302. AssertErrorMessage(
  303. Repeat.self,
  304. ["--cont", "5", "Hello"],
  305. "Unknown option '--cont'. Did you mean '--count'?")
  306. }
  307. }