Files
swift-argument-parser/Tests/ArgumentParserEndToEndTests/OptionGroupEndToEndTests.swift
Nate Cook a9b9644153 Stop removing underscores from CodingKey names in InputKey (#548)
When a property wrapper is applied to a property, the property's
storage is given a name with a prefixed underscore. That is,
for a property named `x`, the actual storage is named `_x`.

That prefixed storage is what is visible through reflection, so
when building an ArgumentSet from a command type's Mirror, we
need to remove the leading underscore. This is done when creating
an InputKey for each property.

However, InputKeys are also created from CodingKeys during
decoding of a ParsableCommand. These CodingKeys _do not_ have
the leading underscore that is visible, so any underscores
that appear are actually from the declaration of the property
with an underscored name. Removing leading underscores from
CodingKey names results in a mismatch when trying to find
the decoded value.

This change simplifies the InputKey type to use an array
path instead of an indirect enum and removes the leading
underscore dropping when creating an InputKey from a CodingKey.

rdar://104928743
2023-02-02 08:33:51 -06:00

178 lines
5.7 KiB
Swift

//===----------------------------------------------------------*- 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 XCTest
import ArgumentParserTestHelpers
import ArgumentParser
final class OptionGroupEndToEndTests: XCTestCase {
}
fileprivate struct Inner: TestableParsableArguments {
@Flag(name: [.short, .long])
var extraVerbiage: Bool = false
@Option
var size: Int = 0
@Argument()
var name: String
let didValidateExpectation = XCTestExpectation(singleExpectation: "inner validated")
private enum CodingKeys: CodingKey {
case extraVerbiage
case size
case name
}
}
fileprivate struct Outer: TestableParsableArguments {
@Flag
var verbose: Bool = false
@Argument()
var before: String
@OptionGroup()
var inner: Inner
@Argument()
var after: String
let didValidateExpectation = XCTestExpectation(singleExpectation: "outer validated")
private enum CodingKeys: CodingKey {
case verbose
case before
case inner
case after
}
}
fileprivate struct Command: TestableParsableCommand {
static let configuration = CommandConfiguration(commandName: "testCommand")
@OptionGroup()
var outer: Outer
let didValidateExpectation = XCTestExpectation(singleExpectation: "Command validated")
let didRunExpectation = XCTestExpectation(singleExpectation: "Command ran")
private enum CodingKeys: CodingKey {
case outer
}
}
extension OptionGroupEndToEndTests {
func testOptionGroup_Defaults() throws {
AssertParse(Outer.self, ["prefix", "name", "postfix"]) { options in
XCTAssertEqual(options.verbose, false)
XCTAssertEqual(options.before, "prefix")
XCTAssertEqual(options.after, "postfix")
XCTAssertEqual(options.inner.extraVerbiage, false)
XCTAssertEqual(options.inner.size, 0)
XCTAssertEqual(options.inner.name, "name")
}
AssertParse(Outer.self, ["prefix", "--extra-verbiage", "name", "postfix", "--verbose", "--size", "5"]) { options in
XCTAssertEqual(options.verbose, true)
XCTAssertEqual(options.before, "prefix")
XCTAssertEqual(options.after, "postfix")
XCTAssertEqual(options.inner.extraVerbiage, true)
XCTAssertEqual(options.inner.size, 5)
XCTAssertEqual(options.inner.name, "name")
}
}
func testOptionGroup_isValidated() {
// Parse the command, this should cause validation to be once each on
// - command.outer.inner
// - command.outer
// - command
AssertParseCommand(Command.self, Command.self, ["prefix", "name", "postfix"]) { command in
wait(for: [command.didValidateExpectation, command.outer.didValidateExpectation, command.outer.inner.didValidateExpectation], timeout: 0.1)
}
}
func testOptionGroup_Fails() throws {
XCTAssertThrowsError(try Outer.parse([]))
XCTAssertThrowsError(try Outer.parse(["prefix"]))
XCTAssertThrowsError(try Outer.parse(["prefix", "name"]))
XCTAssertThrowsError(try Outer.parse(["prefix", "name", "postfix", "extra"]))
XCTAssertThrowsError(try Outer.parse(["prefix", "name", "postfix", "--size", "a"]))
}
}
fileprivate struct DuplicatedFlagGroupCustom: ParsableArguments {
@Flag(name: .customLong("duplicated-option"))
var duplicated: Bool = false
}
fileprivate struct DuplicatedFlagGroupCustomCommand: ParsableCommand {
@Flag var duplicated: Bool = false
@OptionGroup var option: DuplicatedFlagGroupCustom
}
fileprivate struct DuplicatedFlagGroupLong: ParsableArguments {
@Flag var duplicated: Bool = false
}
fileprivate struct DuplicatedFlagGroupLongCommand: ParsableCommand {
@Flag(name: .customLong("duplicated-option"))
var duplicated: Bool = false
@OptionGroup var option: DuplicatedFlagGroupLong
}
extension OptionGroupEndToEndTests {
func testUniqueNamesForDuplicatedFlag_NoFlags() throws {
AssertParse(DuplicatedFlagGroupCustomCommand.self, []) { command in
XCTAssertFalse(command.duplicated)
XCTAssertFalse(command.option.duplicated)
}
AssertParse(DuplicatedFlagGroupLongCommand.self, []) { command in
XCTAssertFalse(command.duplicated)
XCTAssertFalse(command.option.duplicated)
}
}
func testUniqueNamesForDuplicatedFlag_RootOnly() throws {
AssertParse(DuplicatedFlagGroupCustomCommand.self, ["--duplicated"]) { command in
XCTAssertTrue(command.duplicated)
XCTAssertFalse(command.option.duplicated)
}
AssertParse(DuplicatedFlagGroupLongCommand.self, ["--duplicated"]) { command in
XCTAssertFalse(command.duplicated)
XCTAssertTrue(command.option.duplicated)
}
}
func testUniqueNamesForDuplicatedFlag_OptionOnly() throws {
AssertParse(DuplicatedFlagGroupCustomCommand.self, ["--duplicated-option"]) { command in
XCTAssertFalse(command.duplicated)
XCTAssertTrue(command.option.duplicated)
}
AssertParse(DuplicatedFlagGroupLongCommand.self, ["--duplicated-option"]) { command in
XCTAssertTrue(command.duplicated)
XCTAssertFalse(command.option.duplicated)
}
}
func testUniqueNamesForDuplicatedFlag_RootAndOption() throws {
AssertParse(DuplicatedFlagGroupCustomCommand.self, ["--duplicated", "--duplicated-option"]) { command in
XCTAssertTrue(command.duplicated)
XCTAssertTrue(command.option.duplicated)
}
AssertParse(DuplicatedFlagGroupLongCommand.self, ["--duplicated", "--duplicated-option"]) { command in
XCTAssertTrue(command.duplicated)
XCTAssertTrue(command.option.duplicated)
}
}
}