ValidationEndToEndTests.swift 4.4 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185
  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 ValidationEndToEndTests: XCTestCase {
  15. }
  16. private enum UserValidationError: LocalizedError {
  17. case userValidationError
  18. var errorDescription: String? {
  19. switch self {
  20. case .userValidationError:
  21. return "UserValidationError"
  22. }
  23. }
  24. }
  25. private struct Foo: ParsableArguments {
  26. static let usageString: String = """
  27. Usage: foo [--count <count>] [<names> ...] [--version] [--throw]
  28. See 'foo --help' for more information.
  29. """
  30. static let helpString: String = """
  31. USAGE: foo [--count <count>] [<names> ...] [--version] [--throw]
  32. ARGUMENTS:
  33. <names>
  34. OPTIONS:
  35. --count <count>
  36. --version
  37. --throw
  38. -h, --help Show help information.
  39. """
  40. @Option()
  41. var count: Int?
  42. @Argument()
  43. var names: [String] = []
  44. @Flag
  45. var version: Bool = false
  46. @Flag(name: [.customLong("throw")])
  47. var throwCustomError: Bool = false
  48. @Flag(help: .hidden)
  49. var showUsageOnly: Bool = false
  50. @Flag(help: .hidden)
  51. var failValidationSilently: Bool = false
  52. @Flag(help: .hidden)
  53. var failSilently: Bool = false
  54. mutating func validate() throws {
  55. if version {
  56. throw CleanExit.message("0.0.1")
  57. }
  58. if names.isEmpty {
  59. throw ValidationError("Must specify at least one name.")
  60. }
  61. if let count = count, names.count != count {
  62. throw ValidationError(
  63. "Number of names (\(names.count)) doesn't match count (\(count)).")
  64. }
  65. if throwCustomError {
  66. throw UserValidationError.userValidationError
  67. }
  68. if showUsageOnly {
  69. throw ValidationError("")
  70. }
  71. if failValidationSilently {
  72. throw ExitCode.validationFailure
  73. }
  74. if failSilently {
  75. throw ExitCode.failure
  76. }
  77. }
  78. }
  79. // swift-format-ignore: AlwaysUseLowerCamelCase
  80. // https://github.com/apple/swift-argument-parser/issues/710
  81. extension ValidationEndToEndTests {
  82. func testValidation() throws {
  83. AssertParse(Foo.self, ["Joe"]) { foo in
  84. XCTAssertEqual(foo.names, ["Joe"])
  85. XCTAssertNil(foo.count)
  86. }
  87. AssertParse(Foo.self, ["Joe", "Moe", "--count", "2"]) { foo in
  88. XCTAssertEqual(foo.names, ["Joe", "Moe"])
  89. XCTAssertEqual(foo.count, 2)
  90. }
  91. }
  92. func testValidation_Version() throws {
  93. AssertErrorMessage(Foo.self, ["--version"], "0.0.1")
  94. AssertFullErrorMessage(Foo.self, ["--version"], "0.0.1")
  95. }
  96. func testValidation_Fails() throws {
  97. AssertErrorMessage(Foo.self, [], "Must specify at least one name.")
  98. AssertFullErrorMessage(
  99. Foo.self, [],
  100. """
  101. Error: Must specify at least one name.
  102. \(Foo.helpString)
  103. """)
  104. AssertErrorMessage(
  105. Foo.self, ["--count", "3", "Joe"],
  106. """
  107. Number of names (1) doesn't match count (3).
  108. """)
  109. AssertFullErrorMessage(
  110. Foo.self, ["--count", "3", "Joe"],
  111. """
  112. Error: Number of names (1) doesn't match count (3).
  113. \(Foo.usageString)
  114. """)
  115. }
  116. func testCustomErrorValidation() {
  117. // verify that error description is printed if available via LocalizedError
  118. AssertErrorMessage(
  119. Foo.self, ["--throw", "Joe"],
  120. UserValidationError.userValidationError.errorDescription!)
  121. }
  122. func testEmptyErrorValidation() {
  123. AssertErrorMessage(Foo.self, ["--show-usage-only", "Joe"], "")
  124. AssertFullErrorMessage(
  125. Foo.self, ["--show-usage-only", "Joe"], Foo.usageString)
  126. AssertFullErrorMessage(Foo.self, ["--fail-validation-silently", "Joe"], "")
  127. AssertFullErrorMessage(Foo.self, ["--fail-silently", "Joe"], "")
  128. }
  129. }
  130. private struct FooCommand: ParsableCommand {
  131. @Flag(help: .hidden)
  132. var foo = false
  133. @Flag(help: .hidden)
  134. var bar = false
  135. mutating func validate() throws {
  136. if foo {
  137. // --foo implies --bar
  138. bar = true
  139. }
  140. }
  141. func run() throws {
  142. XCTAssertEqual(foo, bar)
  143. }
  144. }
  145. extension ValidationEndToEndTests {
  146. func testMutationsPreserved() throws {
  147. var foo = try FooCommand.parseAsRoot(["--foo"])
  148. try foo.run()
  149. }
  150. }