mirror of
https://github.com/apple/swift-argument-parser.git
synced 2026-06-06 20:18:23 +00:00
379 lines
12 KiB
Swift
379 lines
12 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 ArgumentParserTestHelpers
|
|
import ArgumentParserToolInfo
|
|
import XCTest
|
|
|
|
@testable import ArgumentParser
|
|
|
|
final class DefaultSubcommandEndToEndTests: XCTestCase {
|
|
}
|
|
|
|
// MARK: -
|
|
|
|
private struct Main: ParsableCommand {
|
|
static let configuration = CommandConfiguration(
|
|
subcommands: [Default.self, Foo.self, Bar.self],
|
|
defaultSubcommand: Default.self
|
|
)
|
|
}
|
|
|
|
private struct Default: ParsableCommand {
|
|
enum Mode: String, CaseIterable, ExpressibleByArgument {
|
|
case foo, bar, baz
|
|
}
|
|
|
|
@Option var mode: Mode = .foo
|
|
}
|
|
|
|
private struct Foo: ParsableCommand {}
|
|
private struct Bar: ParsableCommand {}
|
|
|
|
extension DefaultSubcommandEndToEndTests {
|
|
func testDefaultSubcommand() {
|
|
AssertParseCommand(Main.self, Default.self, []) { def in
|
|
XCTAssertEqual(.foo, def.mode)
|
|
}
|
|
|
|
AssertParseCommand(Main.self, Default.self, ["--mode=bar"]) { def in
|
|
XCTAssertEqual(.bar, def.mode)
|
|
}
|
|
|
|
AssertParseCommand(Main.self, Default.self, ["--mode", "bar"]) { def in
|
|
XCTAssertEqual(.bar, def.mode)
|
|
}
|
|
|
|
AssertParseCommand(Main.self, Default.self, ["--mode", "baz"]) { def in
|
|
XCTAssertEqual(.baz, def.mode)
|
|
}
|
|
}
|
|
|
|
func testNonDefaultSubcommand() {
|
|
AssertParseCommand(Main.self, Foo.self, ["foo"]) { _ in }
|
|
AssertParseCommand(Main.self, Bar.self, ["bar"]) { _ in }
|
|
|
|
AssertParseCommand(Main.self, Default.self, ["default", "--mode", "bar"]) {
|
|
def in
|
|
XCTAssertEqual(.bar, def.mode)
|
|
}
|
|
}
|
|
|
|
func testParsingFailure() {
|
|
XCTAssertThrowsError(try Main.parseAsRoot(["--mode", "qux"]))
|
|
XCTAssertThrowsError(try Main.parseAsRoot(["qux"]))
|
|
}
|
|
}
|
|
|
|
extension DefaultSubcommandEndToEndTests {
|
|
fileprivate struct MyCommand: ParsableCommand {
|
|
static let configuration = CommandConfiguration(
|
|
subcommands: [
|
|
Plugin.self, NonDefault.self, Other.self, Child.self, BadParent.self,
|
|
],
|
|
defaultSubcommand: Plugin.self
|
|
)
|
|
|
|
@Option var foo: String?
|
|
|
|
@OptionGroup
|
|
var options: CommonOptions
|
|
}
|
|
|
|
fileprivate struct CommonOptions: ParsableArguments {
|
|
@Flag(
|
|
name: [.customLong("verbose"), .customShort("v")],
|
|
help: "Enable verbose aoutput.")
|
|
var verbose = false
|
|
}
|
|
|
|
fileprivate struct Plugin: ParsableCommand {
|
|
@OptionGroup var options: CommonOptions
|
|
@Argument var pluginName: String
|
|
|
|
@Argument(parsing: .captureForPassthrough)
|
|
var pluginArguments: [String] = []
|
|
}
|
|
|
|
fileprivate struct NonDefault: ParsableCommand {
|
|
@OptionGroup var options: CommonOptions
|
|
@Argument var pluginName: String
|
|
|
|
@Argument(parsing: .captureForPassthrough)
|
|
var pluginArguments: [String] = []
|
|
}
|
|
|
|
fileprivate struct Other: ParsableCommand {
|
|
@OptionGroup var options: CommonOptions
|
|
}
|
|
|
|
fileprivate struct Child: ParsableCommand {
|
|
@ParentCommand var parent: MyCommand
|
|
}
|
|
|
|
fileprivate struct BadParent: ParsableCommand {
|
|
@ParentCommand var notMyParent: Other
|
|
}
|
|
|
|
func testAccessToParent() throws {
|
|
AssertParseCommand(
|
|
MyCommand.self, Child.self, ["--verbose", "--foo=bar", "child"]
|
|
) { child in
|
|
XCTAssertEqual(child.parent.foo, "bar")
|
|
XCTAssertEqual(child.parent.options.verbose, true)
|
|
}
|
|
}
|
|
|
|
func testNotMyParent() throws {
|
|
AssertParseCommandErrorMessage(
|
|
MyCommand.self, BadParent.self, ["--verbose", "bad-parent"],
|
|
"Command 'Other' is not a parent of the current command.")
|
|
}
|
|
|
|
func testNotLeakingParentOptions() throws {
|
|
// Verify that the help for the child command doesn't leak the parent command's options in the help
|
|
let childHelp = MyCommand.message(for: CleanExit.helpRequest(Child.self))
|
|
XCTAssertEqual(
|
|
childHelp,
|
|
"""
|
|
USAGE: my-command child
|
|
|
|
OPTIONS:
|
|
-h, --help Show help information.
|
|
|
|
""")
|
|
|
|
// Now check that the foo option doesn't leak into the JSON dump
|
|
let toolInfo = ToolInfoV0(commandStack: [MyCommand.self.asCommand])
|
|
|
|
let arguments = toolInfo.command.arguments
|
|
guard let arguments else {
|
|
XCTFail(
|
|
"MyCommand is expected to have a top-level command arguments in its tool info"
|
|
)
|
|
return
|
|
}
|
|
|
|
let subcommands = toolInfo.command.subcommands
|
|
guard let subcommands else {
|
|
XCTFail(
|
|
"MyCommand is expected to have a top-level command arguments in its tool info"
|
|
)
|
|
return
|
|
}
|
|
|
|
// The foo option is present int he parent
|
|
XCTAssertNotNil(arguments.first { $0.valueName == "foo" })
|
|
|
|
let childInfo = subcommands.first { cmd in
|
|
cmd.commandName == "child"
|
|
}
|
|
|
|
guard let childInfo else {
|
|
XCTFail("The child subcommand is expected to be present in the tool info")
|
|
return
|
|
}
|
|
|
|
guard let childArguments = childInfo.arguments else {
|
|
XCTFail(
|
|
"The child subcommand is expected to have arguments in the tool info")
|
|
return
|
|
}
|
|
|
|
// It's not there in the child subcommand
|
|
XCTAssertNil(childArguments.first { $0.valueName == "foo" })
|
|
}
|
|
|
|
func testRemainingDefaultImplicit() throws {
|
|
AssertParseCommand(MyCommand.self, Plugin.self, ["my-plugin"]) { plugin in
|
|
XCTAssertEqual(plugin.pluginName, "my-plugin")
|
|
XCTAssertEqual(plugin.pluginArguments, [])
|
|
XCTAssertEqual(plugin.options.verbose, false)
|
|
}
|
|
AssertParseCommand(MyCommand.self, Plugin.self, ["my-plugin", "--verbose"])
|
|
{ plugin in
|
|
XCTAssertEqual(plugin.pluginName, "my-plugin")
|
|
XCTAssertEqual(plugin.pluginArguments, ["--verbose"])
|
|
XCTAssertEqual(plugin.options.verbose, false)
|
|
}
|
|
AssertParseCommand(
|
|
MyCommand.self, Plugin.self, ["--verbose", "my-plugin", "--verbose"]
|
|
) { plugin in
|
|
XCTAssertEqual(plugin.pluginName, "my-plugin")
|
|
XCTAssertEqual(plugin.pluginArguments, ["--verbose"])
|
|
XCTAssertEqual(plugin.options.verbose, true)
|
|
}
|
|
AssertParseCommand(MyCommand.self, Plugin.self, ["my-plugin", "--help"]) {
|
|
plugin in
|
|
XCTAssertEqual(plugin.pluginName, "my-plugin")
|
|
XCTAssertEqual(plugin.pluginArguments, ["--help"])
|
|
XCTAssertEqual(plugin.options.verbose, false)
|
|
}
|
|
}
|
|
|
|
func testRemainingDefaultExplicit() throws {
|
|
AssertParseCommand(MyCommand.self, Plugin.self, ["plugin", "my-plugin"]) {
|
|
plugin in
|
|
XCTAssertEqual(plugin.pluginName, "my-plugin")
|
|
XCTAssertEqual(plugin.pluginArguments, [])
|
|
XCTAssertEqual(plugin.options.verbose, false)
|
|
}
|
|
AssertParseCommand(
|
|
MyCommand.self, Plugin.self, ["plugin", "my-plugin", "--verbose"]
|
|
) { plugin in
|
|
XCTAssertEqual(plugin.pluginName, "my-plugin")
|
|
XCTAssertEqual(plugin.pluginArguments, ["--verbose"])
|
|
XCTAssertEqual(plugin.options.verbose, false)
|
|
}
|
|
AssertParseCommand(
|
|
MyCommand.self, Plugin.self,
|
|
["--verbose", "plugin", "my-plugin", "--verbose"]
|
|
) { plugin in
|
|
XCTAssertEqual(plugin.pluginName, "my-plugin")
|
|
XCTAssertEqual(plugin.pluginArguments, ["--verbose"])
|
|
XCTAssertEqual(plugin.options.verbose, true)
|
|
}
|
|
AssertParseCommand(
|
|
MyCommand.self, Plugin.self,
|
|
["--verbose", "plugin", "my-plugin", "--help"]
|
|
) { plugin in
|
|
XCTAssertEqual(plugin.pluginName, "my-plugin")
|
|
XCTAssertEqual(plugin.pluginArguments, ["--help"])
|
|
XCTAssertEqual(plugin.options.verbose, true)
|
|
}
|
|
}
|
|
|
|
func testRemainingNonDefault() throws {
|
|
AssertParseCommand(
|
|
MyCommand.self, NonDefault.self, ["non-default", "my-plugin"]
|
|
) { nondef in
|
|
XCTAssertEqual(nondef.pluginName, "my-plugin")
|
|
XCTAssertEqual(nondef.pluginArguments, [])
|
|
XCTAssertEqual(nondef.options.verbose, false)
|
|
}
|
|
AssertParseCommand(
|
|
MyCommand.self, NonDefault.self,
|
|
["non-default", "my-plugin", "--verbose"]
|
|
) { nondef in
|
|
XCTAssertEqual(nondef.pluginName, "my-plugin")
|
|
XCTAssertEqual(nondef.pluginArguments, ["--verbose"])
|
|
XCTAssertEqual(nondef.options.verbose, false)
|
|
}
|
|
AssertParseCommand(
|
|
MyCommand.self, NonDefault.self,
|
|
["--verbose", "non-default", "my-plugin", "--verbose"]
|
|
) { nondef in
|
|
XCTAssertEqual(nondef.pluginName, "my-plugin")
|
|
XCTAssertEqual(nondef.pluginArguments, ["--verbose"])
|
|
XCTAssertEqual(nondef.options.verbose, true)
|
|
}
|
|
AssertParseCommand(
|
|
MyCommand.self, NonDefault.self,
|
|
["--verbose", "non-default", "my-plugin", "--help"]
|
|
) { nondef in
|
|
XCTAssertEqual(nondef.pluginName, "my-plugin")
|
|
XCTAssertEqual(nondef.pluginArguments, ["--help"])
|
|
XCTAssertEqual(nondef.options.verbose, true)
|
|
}
|
|
}
|
|
|
|
func testRemainingDefaultOther() throws {
|
|
AssertParseCommand(MyCommand.self, Other.self, ["other"]) { other in
|
|
XCTAssertEqual(other.options.verbose, false)
|
|
}
|
|
AssertParseCommand(MyCommand.self, Other.self, ["other", "--verbose"]) {
|
|
other in
|
|
XCTAssertEqual(other.options.verbose, true)
|
|
}
|
|
}
|
|
|
|
func testRemainingDefaultFailure() {
|
|
XCTAssertThrowsError(try MyCommand.parseAsRoot([]))
|
|
XCTAssertThrowsError(try MyCommand.parseAsRoot(["--verbose"]))
|
|
XCTAssertThrowsError(
|
|
try MyCommand.parseAsRoot(["plugin", "--verbose", "my-plugin"]))
|
|
}
|
|
}
|
|
|
|
extension DefaultSubcommandEndToEndTests {
|
|
struct RootWithPassthroughDefault: ParsableCommand {
|
|
static let configuration = CommandConfiguration(
|
|
subcommands: [PassthroughDefault.self],
|
|
defaultSubcommand: PassthroughDefault.self,
|
|
helpNames: [.short, .long, .customLong("help", withSingleDash: true)]
|
|
)
|
|
}
|
|
|
|
struct PassthroughDefault: ParsableCommand {
|
|
@Argument(parsing: .captureForPassthrough)
|
|
var remaining: [String] = []
|
|
}
|
|
|
|
// Test fix for https://github.com/apple/swift-package-manager/issues/7218
|
|
func testHelpWithPassthroughDefault() throws {
|
|
AssertParseCommand(
|
|
RootWithPassthroughDefault.self, HelpCommand.self, ["-h"]
|
|
) { _ in }
|
|
AssertParseCommand(
|
|
RootWithPassthroughDefault.self, HelpCommand.self, ["-help"]
|
|
) { _ in }
|
|
AssertParseCommand(
|
|
RootWithPassthroughDefault.self, HelpCommand.self, ["--help"]
|
|
) { _ in }
|
|
}
|
|
}
|
|
|
|
extension DefaultSubcommandEndToEndTests {
|
|
struct NestedDefaultSubcommandHelp: ParsableCommand {
|
|
static let configuration = CommandConfiguration(
|
|
subcommands: [Default.self, Nested.self],
|
|
defaultSubcommand: Default.self
|
|
)
|
|
|
|
struct Default: ParsableCommand {}
|
|
|
|
struct Nested: ParsableCommand {
|
|
static let configuration = CommandConfiguration(
|
|
commandName: "nested",
|
|
subcommands: [NestedDefault.self, NestedOther.self],
|
|
defaultSubcommand: NestedDefault.self
|
|
)
|
|
}
|
|
|
|
struct NestedDefault: ParsableCommand {}
|
|
struct NestedOther: ParsableCommand {}
|
|
}
|
|
|
|
// Test fix for https://github.com/apple/swift-argument-parser/issues/865
|
|
func testHelpWithNestedDefaultSubcommand() throws {
|
|
AssertParseCommand(
|
|
NestedDefaultSubcommandHelp.self, HelpCommand.self, ["--help"]
|
|
) { command in
|
|
XCTAssert(command.commandStack.count == 1)
|
|
XCTAssert(
|
|
type(of: command.commandStack[0])
|
|
== NestedDefaultSubcommandHelp.Type.self)
|
|
}
|
|
|
|
AssertParseCommand(
|
|
NestedDefaultSubcommandHelp.self, HelpCommand.self, ["nested", "--help"]
|
|
) { command in
|
|
XCTAssert(command.commandStack.count == 2)
|
|
XCTAssert(
|
|
type(of: command.commandStack[0])
|
|
== NestedDefaultSubcommandHelp.Type.self)
|
|
XCTAssert(
|
|
type(of: command.commandStack[1])
|
|
== NestedDefaultSubcommandHelp.Nested.Type.self)
|
|
}
|
|
}
|
|
}
|