Files

298 lines
7.5 KiB
Swift

//===----------------------------------------------------------------------===//
//
// This source file is part of the Swift Argument Parser open source project
//
// Copyright (c) 2020 Apple Inc. and the Swift project authors
// Licensed under Apache License v2.0 with Runtime Library Exception
//
// See https://swift.org/LICENSE.txt for license information
//
//===----------------------------------------------------------------------===//
import ArgumentParser
import ArgumentParserTestHelpers
import XCTest
final class SubcommandEndToEndTests: XCTestCase {
}
// MARK: Single value String
private struct Foo: ParsableCommand {
static let configuration =
CommandConfiguration(subcommands: [CommandA.self, CommandB.self])
@Option() var name: String
}
private struct CommandA: ParsableCommand {
static let configuration = CommandConfiguration(commandName: "a")
@OptionGroup() var foo: Foo
@Option() var bar: Int
}
private struct CommandB: ParsableCommand {
static let configuration = CommandConfiguration(commandName: "b")
@OptionGroup() var foo: Foo
@Option() var baz: String
}
// swift-format-ignore: AlwaysUseLowerCamelCase
// https://github.com/apple/swift-argument-parser/issues/710
extension SubcommandEndToEndTests {
func testParsing_SubCommand() throws {
AssertParseCommand(
Foo.self, CommandA.self, ["--name", "Foo", "a", "--bar", "42"]
) { a in
XCTAssertEqual(a.bar, 42)
XCTAssertEqual(a.foo.name, "Foo")
}
AssertParseCommand(
Foo.self, CommandB.self, ["--name", "A", "b", "--baz", "abc"]
) { b in
XCTAssertEqual(b.baz, "abc")
XCTAssertEqual(b.foo.name, "A")
}
}
func testParsing_SubCommand_manual() throws {
AssertParseCommand(
Foo.self, CommandA.self, ["--name", "Foo", "a", "--bar", "42"]
) { a in
XCTAssertEqual(a.bar, 42)
XCTAssertEqual(a.foo.name, "Foo")
}
AssertParseCommand(Foo.self, Foo.self, ["--name", "Foo"]) { foo in
XCTAssertEqual(foo.name, "Foo")
}
}
func testParsing_SubCommand_help() throws {
let helpFoo = Foo.message(for: CleanExit.helpRequest())
let helpA = Foo.message(for: CleanExit.helpRequest(CommandA.self))
let helpB = Foo.message(for: CleanExit.helpRequest(CommandB.self))
AssertEqualStrings(
actual: helpFoo,
expected: """
USAGE: foo --name <name> <subcommand>
OPTIONS:
--name <name>
-h, --help Show help information.
SUBCOMMANDS:
a
b
See 'foo help <subcommand>' for detailed help.
""")
AssertEqualStrings(
actual: helpA,
expected: """
USAGE: foo a --name <name> --bar <bar>
OPTIONS:
--name <name>
--bar <bar>
-h, --help Show help information.
""")
AssertEqualStrings(
actual: helpB,
expected: """
USAGE: foo b --name <name> --baz <baz>
OPTIONS:
--name <name>
--baz <baz>
-h, --help Show help information.
""")
}
func testParsing_SubCommand_fails() throws {
XCTAssertThrowsError(
try Foo.parse(["--name", "Foo", "a", "--baz", "42"]),
"'baz' is not an option for the 'a' subcommand.")
XCTAssertThrowsError(
try Foo.parse(["--name", "Foo", "b", "--bar", "42"]),
"'bar' is not an option for the 'b' subcommand.")
}
}
private struct Math: ParsableCommand {
enum Operation: String, ExpressibleByArgument {
case add
case multiply
}
@Option(help: "The operation to perform")
var operation: Operation = .add
@Flag(name: [.short, .long])
var verbose: Bool = false
@Argument(help: "The first operand")
var operands: [Int] = []
var didRun = false
mutating func run() {
XCTAssertEqual(operation, .multiply)
XCTAssertTrue(verbose)
XCTAssertEqual(operands, [5, 11])
didRun = true
}
}
// swift-format-ignore: AlwaysUseLowerCamelCase
// https://github.com/apple/swift-argument-parser/issues/710
extension SubcommandEndToEndTests {
func testParsing_SingleCommand() throws {
var mathCommand =
try Math.parseAsRoot(["--operation", "multiply", "-v", "5", "11"])
as! Math
XCTAssertFalse(mathCommand.didRun)
mathCommand.run()
XCTAssertTrue(mathCommand.didRun)
}
}
// MARK: Nested Command Arguments Validated
struct BaseCommand: ParsableCommand {
enum BaseCommandError: Error {
case baseCommandFailure
case subCommandFailure
}
static let baseFlagValue = "base"
static let configuration = CommandConfiguration(
commandName: "base",
subcommands: [SubCommand.self]
)
@Option()
var baseFlag: String
mutating func validate() throws {
guard baseFlag == BaseCommand.baseFlagValue else {
throw BaseCommandError.baseCommandFailure
}
}
}
extension BaseCommand {
struct SubCommand: ParsableCommand {
static let subFlagValue = "sub"
static let configuration = CommandConfiguration(
commandName: "sub",
subcommands: [SubSubCommand.self]
)
@Option()
var subFlag: String
mutating func validate() throws {
guard subFlag == SubCommand.subFlagValue else {
throw BaseCommandError.subCommandFailure
}
}
}
}
extension BaseCommand.SubCommand {
struct SubSubCommand: ParsableCommand, TestableParsableArguments {
let didValidateExpectation = XCTestExpectation(
singleExpectation: "did validate subcommand")
static let configuration = CommandConfiguration(
commandName: "subsub"
)
@Flag
var subSubFlag: Bool = false
private enum CodingKeys: CodingKey {
case subSubFlag
}
}
}
// swift-format-ignore: AlwaysUseLowerCamelCase
// https://github.com/apple/swift-argument-parser/issues/710
extension SubcommandEndToEndTests {
func testValidate_subcommands() {
// provide a value to base-flag that will throw
AssertErrorMessage(
BaseCommand.self,
["--base-flag", "foo", "sub", "--sub-flag", "foo", "subsub"],
"baseCommandFailure"
)
// provide a value to sub-flag that will throw
AssertErrorMessage(
BaseCommand.self,
[
"--base-flag", BaseCommand.baseFlagValue, "sub", "--sub-flag", "foo",
"subsub",
],
"subCommandFailure"
)
// provide a valid command and make sure both validates succeed
AssertParseCommand(
BaseCommand.self,
BaseCommand.SubCommand.SubSubCommand.self,
[
"--base-flag", BaseCommand.baseFlagValue, "sub", "--sub-flag",
BaseCommand.SubCommand.subFlagValue, "subsub", "--sub-sub-flag",
]
) { cmd in
XCTAssertTrue(cmd.subSubFlag)
// make sure that the instance of SubSubCommand provided
// had its validate method called, not just that any instance of SubSubCommand was validated
wait(for: [cmd.didValidateExpectation], timeout: 0.1)
}
}
}
// MARK: Version flags
private struct A: ParsableCommand {
static let configuration = CommandConfiguration(
version: "1.0.0",
subcommands: [HasVersionFlag.self, NoVersionFlag.self])
struct HasVersionFlag: ParsableCommand {
@Flag var version: Bool = false
}
struct NoVersionFlag: ParsableCommand {
@Flag var hello: Bool = false
}
}
extension SubcommandEndToEndTests {
func testParsingVersionFlags() throws {
AssertErrorMessage(A.self, ["--version"], "1.0.0")
AssertErrorMessage(A.self, ["no-version-flag", "--version"], "1.0.0")
AssertParseCommand(
A.self, A.HasVersionFlag.self, ["has-version-flag", "--version"]
) { cmd in
XCTAssertTrue(cmd.version)
}
}
}