mirror of
https://github.com/carbon-language/carbon-lang.git
synced 2026-09-24 20:10:13 +01:00
Refactor command line errors to mirror diagnostic style (#4568)
This changes to an `Error` return to let the driver do the "error: " prefix, except for one case with `help` that needs more work to change (I'm not planning on picking up that TODO). It also changes capitalization, backtick use, and a few minor punctuation things to try to better match the diagnostic style. This also adds `Error` matchers so that the changes to command line testing are clearer.
This commit is contained in:
@@ -41,6 +41,7 @@ cc_library(
|
||||
hdrs = ["command_line.h"],
|
||||
deps = [
|
||||
":check",
|
||||
":error",
|
||||
":ostream",
|
||||
"@llvm-project//llvm:Support",
|
||||
],
|
||||
@@ -52,6 +53,7 @@ cc_test(
|
||||
srcs = ["command_line_test.cpp"],
|
||||
deps = [
|
||||
":command_line",
|
||||
":error_test_helpers",
|
||||
"//testing/base:gtest_main",
|
||||
"//testing/base:test_raw_ostream",
|
||||
"@googletest//:gtest",
|
||||
@@ -122,12 +124,23 @@ cc_library(
|
||||
],
|
||||
)
|
||||
|
||||
cc_library(
|
||||
name = "error_test_helpers",
|
||||
testonly = 1,
|
||||
hdrs = ["error_test_helpers.h"],
|
||||
deps = [
|
||||
":error",
|
||||
"@googletest//:gtest",
|
||||
],
|
||||
)
|
||||
|
||||
cc_test(
|
||||
name = "error_test",
|
||||
size = "small",
|
||||
srcs = ["error_test.cpp"],
|
||||
deps = [
|
||||
":error",
|
||||
":error_test_helpers",
|
||||
"//testing/base:gtest_main",
|
||||
"//testing/base:test_raw_ostream",
|
||||
"@googletest//:gtest",
|
||||
|
||||
+157
-180
@@ -15,8 +15,6 @@ namespace Carbon::CommandLine {
|
||||
auto operator<<(llvm::raw_ostream& output, ParseResult result)
|
||||
-> llvm::raw_ostream& {
|
||||
switch (result) {
|
||||
case ParseResult::Error:
|
||||
return output << "Error";
|
||||
case ParseResult::MetaSuccess:
|
||||
return output << "MetaSuccess";
|
||||
case ParseResult::Success:
|
||||
@@ -311,8 +309,10 @@ void MetaPrinter::PrintHelpForSubcommandName(
|
||||
}
|
||||
}
|
||||
|
||||
*out_ << "ERROR: Could not find a subcommand named '" << subcommand_name
|
||||
<< "'.\n";
|
||||
// TODO: This should really be connected up so that parsing can return an
|
||||
// Error instead of ParseResult::MetaSuccess in this case.
|
||||
*out_ << "error: could not find a subcommand named '" << subcommand_name
|
||||
<< "'\n";
|
||||
}
|
||||
|
||||
void MetaPrinter::PrintVersion(const Command& command) const {
|
||||
@@ -330,12 +330,12 @@ void MetaPrinter::PrintVersion(const Command& command) const {
|
||||
void MetaPrinter::PrintSubcommands(const Command& command) const {
|
||||
for (const auto& subcommand :
|
||||
llvm::ArrayRef(command.subcommands).drop_back()) {
|
||||
*out_ << "'" << subcommand->info.name << "', ";
|
||||
*out_ << "`" << subcommand->info.name << "`, ";
|
||||
}
|
||||
if (command.subcommands.size() > 1) {
|
||||
*out_ << "or ";
|
||||
}
|
||||
*out_ << "'" << command.subcommands.back()->info.name << "'";
|
||||
*out_ << "`" << command.subcommands.back()->info.name << "`";
|
||||
}
|
||||
|
||||
void MetaPrinter::PrintRawVersion(const Command& command,
|
||||
@@ -632,7 +632,7 @@ void MetaPrinter::PrintUsage(const Command& command) const {
|
||||
if (!command.parent) {
|
||||
*out_ << "Usage:\n";
|
||||
} else {
|
||||
*out_ << "Subcommand '" << command.info.name << "' usage:\n";
|
||||
*out_ << "Subcommand `" << command.info.name << "` usage:\n";
|
||||
}
|
||||
PrintRawUsage(command, " ");
|
||||
}
|
||||
@@ -648,7 +648,7 @@ void MetaPrinter::PrintHelpSubcommands(const Command& command) const {
|
||||
if (!command.parent) {
|
||||
*out_ << "\nSubcommands:";
|
||||
} else {
|
||||
*out_ << "\nSubcommand '" << command.info.name << "' subcommands:";
|
||||
*out_ << "\nSubcommand `" << command.info.name << "` subcommands:";
|
||||
}
|
||||
}
|
||||
*out_ << "\n";
|
||||
@@ -668,8 +668,8 @@ void MetaPrinter::PrintHelpPositionalArgs(const Command& command) const {
|
||||
if (!command.parent) {
|
||||
*out_ << "\nPositional arguments:";
|
||||
} else {
|
||||
*out_ << "\nSubcommand '" << command.info.name
|
||||
<< "' positional arguments:";
|
||||
*out_ << "\nSubcommand `" << command.info.name
|
||||
<< "` positional arguments:";
|
||||
}
|
||||
}
|
||||
*out_ << "\n";
|
||||
@@ -692,7 +692,7 @@ void MetaPrinter::PrintHelpOptions(const Command& command) const {
|
||||
} else if (!command.parent) {
|
||||
*out_ << "\nCommand options:";
|
||||
} else {
|
||||
*out_ << "\nSubcommand '" << command.info.name << "' options:";
|
||||
*out_ << "\nSubcommand `" << command.info.name << "` options:";
|
||||
}
|
||||
}
|
||||
*out_ << "\n";
|
||||
@@ -711,12 +711,12 @@ void MetaPrinter::PrintHelpOptions(const Command& command) const {
|
||||
|
||||
class Parser {
|
||||
public:
|
||||
// `out` and `errors` must not be null.
|
||||
explicit Parser(llvm::raw_ostream* out, llvm::raw_ostream* errors,
|
||||
CommandInfo command_info,
|
||||
// `out` must not be null.
|
||||
explicit Parser(llvm::raw_ostream* out, CommandInfo command_info,
|
||||
llvm::function_ref<void(CommandBuilder&)> build);
|
||||
|
||||
auto Parse(llvm::ArrayRef<llvm::StringRef> unparsed_args) -> ParseResult;
|
||||
auto Parse(llvm::ArrayRef<llvm::StringRef> unparsed_args)
|
||||
-> ErrorOr<ParseResult>;
|
||||
|
||||
private:
|
||||
friend CommandBuilder;
|
||||
@@ -739,39 +739,38 @@ class Parser {
|
||||
void SetOptionDefault(const Arg& option);
|
||||
|
||||
auto ParseNegatedFlag(const Arg& flag, std::optional<llvm::StringRef> value)
|
||||
-> bool;
|
||||
auto ParseFlag(const Arg& flag, std::optional<llvm::StringRef> value) -> bool;
|
||||
auto ParseIntegerArgValue(const Arg& arg, llvm::StringRef value) -> bool;
|
||||
auto ParseStringArgValue(const Arg& arg, llvm::StringRef value) -> bool;
|
||||
auto ParseOneOfArgValue(const Arg& arg, llvm::StringRef value) -> bool;
|
||||
-> ErrorOr<Success>;
|
||||
auto ParseFlag(const Arg& flag, std::optional<llvm::StringRef> value)
|
||||
-> ErrorOr<Success>;
|
||||
auto ParseIntegerArgValue(const Arg& arg, llvm::StringRef value)
|
||||
-> ErrorOr<Success>;
|
||||
auto ParseStringArgValue(const Arg& arg, llvm::StringRef value)
|
||||
-> ErrorOr<Success>;
|
||||
auto ParseOneOfArgValue(const Arg& arg, llvm::StringRef value)
|
||||
-> ErrorOr<Success>;
|
||||
auto ParseArg(const Arg& arg, bool short_spelling,
|
||||
std::optional<llvm::StringRef> value, bool negated_name = false)
|
||||
-> bool;
|
||||
-> ErrorOr<Success>;
|
||||
|
||||
auto SplitValue(llvm::StringRef& unparsed_arg)
|
||||
-> std::optional<llvm::StringRef>;
|
||||
auto ParseLongOption(llvm::StringRef unparsed_arg) -> bool;
|
||||
auto ParseShortOptionSeq(llvm::StringRef unparsed_arg) -> bool;
|
||||
auto FinalizeParsedOptions() -> bool;
|
||||
auto ParseLongOption(llvm::StringRef unparsed_arg) -> ErrorOr<Success>;
|
||||
auto ParseShortOptionSeq(llvm::StringRef unparsed_arg) -> ErrorOr<Success>;
|
||||
auto FinalizeParsedOptions() -> ErrorOr<Success>;
|
||||
|
||||
auto ParsePositionalArg(llvm::StringRef unparsed_arg) -> bool;
|
||||
auto ParseSubcommand(llvm::StringRef unparsed_arg) -> bool;
|
||||
auto ParsePositionalArg(llvm::StringRef unparsed_arg) -> ErrorOr<Success>;
|
||||
auto ParseSubcommand(llvm::StringRef unparsed_arg) -> ErrorOr<Success>;
|
||||
|
||||
auto ParsePositionalSuffix(llvm::ArrayRef<llvm::StringRef> unparsed_args)
|
||||
-> bool;
|
||||
-> ErrorOr<Success>;
|
||||
|
||||
auto FinalizeParse() -> ParseResult;
|
||||
auto FinalizeParse() -> ErrorOr<ParseResult>;
|
||||
|
||||
// When building a command, it registers arguments and potentially subcommands
|
||||
// that are meta actions to print things to standard out, so we build a meta
|
||||
// printer for that here.
|
||||
MetaPrinter meta_printer_;
|
||||
|
||||
// Most parsing output goes to an error stream, and we also provide an
|
||||
// error-oriented meta printer for when that is useful during parsing.
|
||||
llvm::raw_ostream* errors_;
|
||||
MetaPrinter error_meta_printer_;
|
||||
|
||||
Command root_command_;
|
||||
|
||||
const Command* command_;
|
||||
@@ -833,89 +832,92 @@ void Parser::SetOptionDefault(const Arg& option) {
|
||||
}
|
||||
|
||||
auto Parser::ParseNegatedFlag(const Arg& flag,
|
||||
std::optional<llvm::StringRef> value) -> bool {
|
||||
std::optional<llvm::StringRef> value)
|
||||
-> ErrorOr<Success> {
|
||||
if (flag.kind != Arg::Kind::Flag) {
|
||||
*errors_ << "ERROR: Cannot use a negated flag name by prefixing it with "
|
||||
"'no-' when it isn't a boolean flag argument.\n";
|
||||
return false;
|
||||
return Error(
|
||||
"cannot use a negated flag name by prefixing it with `no-` when it "
|
||||
"isn't a boolean flag argument");
|
||||
}
|
||||
if (value) {
|
||||
*errors_ << "ERROR: Cannot specify a value when using a flag name prefixed "
|
||||
"with 'no-' -- that prefix implies a value of 'false'.\n";
|
||||
return false;
|
||||
return Error(
|
||||
"cannot specify a value when using a flag name prefixed with `no-` -- "
|
||||
"that prefix implies a value of `false`");
|
||||
}
|
||||
*flag.flag_storage = false;
|
||||
return true;
|
||||
return Success();
|
||||
}
|
||||
|
||||
auto Parser::ParseFlag(const Arg& flag, std::optional<llvm::StringRef> value)
|
||||
-> bool {
|
||||
-> ErrorOr<Success> {
|
||||
CARBON_CHECK(flag.kind == Arg::Kind::Flag, "Incorrect kind: {0}", flag.kind);
|
||||
if (!value || *value == "true") {
|
||||
*flag.flag_storage = true;
|
||||
} else if (*value == "false") {
|
||||
*flag.flag_storage = false;
|
||||
} else {
|
||||
*errors_ << "ERROR: Invalid value specified for the boolean flag '--"
|
||||
<< flag.info.name << "': " << *value << "\n";
|
||||
|
||||
return false;
|
||||
return Error(llvm::formatv(
|
||||
"invalid value specified for the boolean flag `--{0}`: {1}",
|
||||
flag.info.name, *value));
|
||||
}
|
||||
return true;
|
||||
return Success();
|
||||
}
|
||||
|
||||
auto Parser::ParseIntegerArgValue(const Arg& arg, llvm::StringRef value)
|
||||
-> bool {
|
||||
-> ErrorOr<Success> {
|
||||
CARBON_CHECK(arg.kind == Arg::Kind::Integer, "Incorrect kind: {0}", arg.kind);
|
||||
int integer_value;
|
||||
// Note that this method returns *true* on error!
|
||||
if (value.getAsInteger(/*Radix=*/0, integer_value)) {
|
||||
*errors_ << "ERROR: Cannot parse value for option '--" << arg.info.name
|
||||
<< "' as an integer: " << value << "\n";
|
||||
return false;
|
||||
return Error(llvm::formatv(
|
||||
"cannot parse value for option `--{0}` as an integer: {1}",
|
||||
arg.info.name, value));
|
||||
}
|
||||
if (!arg.is_append) {
|
||||
*arg.integer_storage = integer_value;
|
||||
} else {
|
||||
arg.integer_sequence->push_back(integer_value);
|
||||
}
|
||||
return true;
|
||||
return Success();
|
||||
}
|
||||
|
||||
auto Parser::ParseStringArgValue(const Arg& arg, llvm::StringRef value)
|
||||
-> bool {
|
||||
-> ErrorOr<Success> {
|
||||
CARBON_CHECK(arg.kind == Arg::Kind::String, "Incorrect kind: {0}", arg.kind);
|
||||
if (!arg.is_append) {
|
||||
*arg.string_storage = value;
|
||||
} else {
|
||||
arg.string_sequence->push_back(value);
|
||||
}
|
||||
return true;
|
||||
return Success();
|
||||
}
|
||||
|
||||
auto Parser::ParseOneOfArgValue(const Arg& arg, llvm::StringRef value) -> bool {
|
||||
auto Parser::ParseOneOfArgValue(const Arg& arg, llvm::StringRef value)
|
||||
-> ErrorOr<Success> {
|
||||
CARBON_CHECK(arg.kind == Arg::Kind::OneOf, "Incorrect kind: {0}", arg.kind);
|
||||
if (!arg.value_action(arg, value)) {
|
||||
*errors_ << "ERROR: Option '--" << arg.info.name << "=";
|
||||
llvm::printEscapedString(value, *errors_);
|
||||
*errors_ << "' has an invalid value '";
|
||||
llvm::printEscapedString(value, *errors_);
|
||||
*errors_ << "'; valid values are: ";
|
||||
std::string error_str;
|
||||
llvm::raw_string_ostream error(error_str);
|
||||
error << "option `--" << arg.info.name << "=";
|
||||
llvm::printEscapedString(value, error);
|
||||
error << "` has an invalid value `";
|
||||
llvm::printEscapedString(value, error);
|
||||
error << "`; valid values are: ";
|
||||
for (auto value_string : arg.value_strings.drop_back()) {
|
||||
*errors_ << "'" << value_string << "', ";
|
||||
error << "`" << value_string << "`, ";
|
||||
}
|
||||
if (arg.value_strings.size() > 1) {
|
||||
*errors_ << "or ";
|
||||
error << "or ";
|
||||
}
|
||||
*errors_ << "'" << arg.value_strings.back() << "'\n";
|
||||
return false;
|
||||
error << "`" << arg.value_strings.back() << "`";
|
||||
return Error(error_str);
|
||||
}
|
||||
return true;
|
||||
return Success();
|
||||
}
|
||||
|
||||
auto Parser::ParseArg(const Arg& arg, bool short_spelling,
|
||||
std::optional<llvm::StringRef> value, bool negated_name)
|
||||
-> bool {
|
||||
-> ErrorOr<Success> {
|
||||
// If this argument has a meta action, replace the current meta action with
|
||||
// it.
|
||||
if (arg.meta_action) {
|
||||
@@ -932,10 +934,10 @@ auto Parser::ParseArg(const Arg& arg, bool short_spelling,
|
||||
|
||||
std::string name;
|
||||
if (short_spelling) {
|
||||
name = llvm::formatv("'-{0}' (short for '--{1}')", arg.info.short_name,
|
||||
name = llvm::formatv("`-{0}` (short for `--{1}`)", arg.info.short_name,
|
||||
arg.info.name);
|
||||
} else {
|
||||
name = llvm::formatv("'--{0}'", arg.info.name);
|
||||
name = llvm::formatv("`--{0}`", arg.info.name);
|
||||
}
|
||||
|
||||
if (!value) {
|
||||
@@ -943,15 +945,14 @@ auto Parser::ParseArg(const Arg& arg, bool short_spelling,
|
||||
// an option and handle it as such.
|
||||
if (arg.kind == Arg::Kind::MetaActionOnly) {
|
||||
// Nothing further to do here, this is only a meta-action.
|
||||
return true;
|
||||
return Success();
|
||||
}
|
||||
if (!arg.has_default) {
|
||||
*errors_ << "ERROR: Option " << name
|
||||
<< " requires a value to be provided and none was.\n";
|
||||
return false;
|
||||
return Error(llvm::formatv(
|
||||
"option {0} requires a value to be provided and none was", name));
|
||||
}
|
||||
SetOptionDefault(arg);
|
||||
return true;
|
||||
return Success();
|
||||
}
|
||||
|
||||
// There is a value to parse as part of the argument.
|
||||
@@ -963,11 +964,10 @@ auto Parser::ParseArg(const Arg& arg, bool short_spelling,
|
||||
case Arg::Kind::OneOf:
|
||||
return ParseOneOfArgValue(arg, *value);
|
||||
case Arg::Kind::MetaActionOnly:
|
||||
*errors_ << "ERROR: Option " << name
|
||||
<< " cannot be used with a value, and '" << *value
|
||||
<< "' was provided.\n";
|
||||
// TODO: improve message
|
||||
return false;
|
||||
// TODO: Improve message.
|
||||
return Error(llvm::formatv(
|
||||
"option {0} cannot be used with a value, and '{1}' was provided",
|
||||
name, value));
|
||||
case Arg::Kind::Flag:
|
||||
case Arg::Kind::Invalid:
|
||||
CARBON_FATAL("Invalid kind!");
|
||||
@@ -986,7 +986,7 @@ auto Parser::SplitValue(llvm::StringRef& unparsed_arg)
|
||||
return value;
|
||||
}
|
||||
|
||||
auto Parser::ParseLongOption(llvm::StringRef unparsed_arg) -> bool {
|
||||
auto Parser::ParseLongOption(llvm::StringRef unparsed_arg) -> ErrorOr<Success> {
|
||||
CARBON_CHECK(unparsed_arg.starts_with("--") && unparsed_arg.size() > 2,
|
||||
"Must only be called on a potential long option.");
|
||||
|
||||
@@ -997,10 +997,9 @@ auto Parser::ParseLongOption(llvm::StringRef unparsed_arg) -> bool {
|
||||
|
||||
auto option_it = option_map_.find(unparsed_arg);
|
||||
if (option_it == option_map_.end()) {
|
||||
*errors_ << "ERROR: Unknown option '--" << (negated_name ? "no-" : "")
|
||||
<< unparsed_arg << "'\n";
|
||||
// TODO: improve error
|
||||
return false;
|
||||
// TODO: Improve error.
|
||||
return Error(llvm::formatv("unknown option `--{0}{1}`",
|
||||
negated_name ? "no-" : "", unparsed_arg));
|
||||
}
|
||||
|
||||
// Mark this option as parsed.
|
||||
@@ -1011,41 +1010,38 @@ auto Parser::ParseLongOption(llvm::StringRef unparsed_arg) -> bool {
|
||||
return ParseArg(option, /*short_spelling=*/false, value, negated_name);
|
||||
}
|
||||
|
||||
auto Parser::ParseShortOptionSeq(llvm::StringRef unparsed_arg) -> bool {
|
||||
auto Parser::ParseShortOptionSeq(llvm::StringRef unparsed_arg)
|
||||
-> ErrorOr<Success> {
|
||||
CARBON_CHECK(unparsed_arg.starts_with("-") && unparsed_arg.size() > 1,
|
||||
"Must only be called on a potential short option sequence.");
|
||||
|
||||
unparsed_arg = unparsed_arg.drop_front();
|
||||
std::optional<llvm::StringRef> value = SplitValue(unparsed_arg);
|
||||
if (value && unparsed_arg.size() != 1) {
|
||||
*errors_ << "ERROR: Cannot provide a value to the group of multiple short "
|
||||
"options '-"
|
||||
<< unparsed_arg
|
||||
<< "=...'; values must be provided to a single option, using "
|
||||
"either the short or long spelling.\n";
|
||||
return false;
|
||||
return Error(llvm::formatv(
|
||||
"cannot provide a value to the group of multiple short options "
|
||||
"`-{0}=...`; values must be provided to a single option, using "
|
||||
"either the short or long spelling",
|
||||
unparsed_arg));
|
||||
}
|
||||
|
||||
for (unsigned char c : unparsed_arg) {
|
||||
auto* arg_entry =
|
||||
(c < short_option_table_.size()) ? short_option_table_[c] : nullptr;
|
||||
if (!arg_entry) {
|
||||
*errors_ << "ERROR: Unknown short option '" << c << "'\n";
|
||||
return false;
|
||||
return Error(llvm::formatv("unknown short option `{0}`", c));
|
||||
}
|
||||
// Mark this argument as parsed.
|
||||
arg_entry->setInt(true);
|
||||
|
||||
// Parse the argument, including the value if this is the last.
|
||||
const Arg& arg = *arg_entry->getPointer();
|
||||
if (!ParseArg(arg, /*short_spelling=*/true, value)) {
|
||||
return false;
|
||||
}
|
||||
CARBON_RETURN_IF_ERROR(ParseArg(arg, /*short_spelling=*/true, value));
|
||||
}
|
||||
return true;
|
||||
return Success();
|
||||
}
|
||||
|
||||
auto Parser::FinalizeParsedOptions() -> bool {
|
||||
auto Parser::FinalizeParsedOptions() -> ErrorOr<Success> {
|
||||
llvm::SmallVector<const Arg*> missing_options;
|
||||
for (const auto& option_entry : option_map_) {
|
||||
const Arg* option = option_entry.second.getPointer();
|
||||
@@ -1062,7 +1058,7 @@ auto Parser::FinalizeParsedOptions() -> bool {
|
||||
}
|
||||
}
|
||||
if (missing_options.empty()) {
|
||||
return true;
|
||||
return Success();
|
||||
}
|
||||
|
||||
// Sort the missing arguments by name to provide a stable and deterministic
|
||||
@@ -1073,23 +1069,24 @@ auto Parser::FinalizeParsedOptions() -> bool {
|
||||
return lhs->info.name < rhs->info.name;
|
||||
});
|
||||
|
||||
std::string error_str = "required options not provided: ";
|
||||
llvm::raw_string_ostream error(error_str);
|
||||
llvm::ListSeparator sep;
|
||||
for (const Arg* option : missing_options) {
|
||||
*errors_ << "ERROR: Required option '--" << option->info.name
|
||||
<< "' not provided.\n";
|
||||
error << sep << "--" << option->info.name;
|
||||
}
|
||||
|
||||
return false;
|
||||
return Error(error_str);
|
||||
}
|
||||
|
||||
auto Parser::ParsePositionalArg(llvm::StringRef unparsed_arg) -> bool {
|
||||
auto Parser::ParsePositionalArg(llvm::StringRef unparsed_arg)
|
||||
-> ErrorOr<Success> {
|
||||
if (static_cast<size_t>(positional_arg_index_) >=
|
||||
command_->positional_args.size()) {
|
||||
*errors_ << "ERROR: Completed parsing all "
|
||||
<< command_->positional_args.size()
|
||||
<< " configured positional arguments, and found an additional "
|
||||
"positional argument: '"
|
||||
<< unparsed_arg << "'\n";
|
||||
return false;
|
||||
return Error(llvm::formatv(
|
||||
"completed parsing all {0} configured positional arguments, and found "
|
||||
"an additional positional argument: `{1}`",
|
||||
command_->positional_args.size(), unparsed_arg));
|
||||
}
|
||||
|
||||
const Arg& arg = *command_->positional_args[positional_arg_index_];
|
||||
@@ -1107,29 +1104,28 @@ auto Parser::ParsePositionalArg(llvm::StringRef unparsed_arg) -> bool {
|
||||
return ParseArg(arg, /*short_spelling=*/false, unparsed_arg);
|
||||
}
|
||||
|
||||
auto Parser::ParseSubcommand(llvm::StringRef unparsed_arg) -> bool {
|
||||
auto Parser::ParseSubcommand(llvm::StringRef unparsed_arg) -> ErrorOr<Success> {
|
||||
auto subcommand_it = subcommand_map_.find(unparsed_arg);
|
||||
if (subcommand_it == subcommand_map_.end()) {
|
||||
*errors_ << "ERROR: Invalid subcommand '" << unparsed_arg
|
||||
<< "'. Available subcommands: ";
|
||||
error_meta_printer_.PrintSubcommands(*command_);
|
||||
*errors_ << "\n";
|
||||
return false;
|
||||
std::string error_str;
|
||||
llvm::raw_string_ostream error(error_str);
|
||||
error << "invalid subcommand `" << unparsed_arg
|
||||
<< "`; available subcommands: ";
|
||||
MetaPrinter(&error).PrintSubcommands(*command_);
|
||||
return Error(error_str);
|
||||
}
|
||||
|
||||
// Before we recurse into the subcommand, verify that all the required
|
||||
// arguments for this command were in fact parsed.
|
||||
if (!FinalizeParsedOptions()) {
|
||||
return false;
|
||||
}
|
||||
CARBON_RETURN_IF_ERROR(FinalizeParsedOptions());
|
||||
|
||||
// Recurse into the subcommand, tracking the active command.
|
||||
command_ = subcommand_it->second;
|
||||
PopulateMaps(*command_);
|
||||
return true;
|
||||
return Success();
|
||||
}
|
||||
|
||||
auto Parser::FinalizeParse() -> ParseResult {
|
||||
auto Parser::FinalizeParse() -> ErrorOr<ParseResult> {
|
||||
// If an argument action is provided, we run that and consider the parse
|
||||
// meta-successful rather than verifying required arguments were provided and
|
||||
// the (sub)command action.
|
||||
@@ -1139,9 +1135,7 @@ auto Parser::FinalizeParse() -> ParseResult {
|
||||
}
|
||||
|
||||
// Verify we're not missing any arguments.
|
||||
if (!FinalizeParsedOptions()) {
|
||||
return ParseResult::Error;
|
||||
}
|
||||
CARBON_RETURN_IF_ERROR(FinalizeParsedOptions());
|
||||
|
||||
// If we were appending to a positional argument, mark that as complete.
|
||||
llvm::ArrayRef positional_args = command_->positional_args;
|
||||
@@ -1159,10 +1153,10 @@ auto Parser::FinalizeParse() -> ParseResult {
|
||||
// There are un-parsed positional arguments, make sure they aren't required.
|
||||
const Arg& missing_arg = *unparsed_positional_args.front();
|
||||
if (missing_arg.is_required) {
|
||||
*errors_ << "ERROR: Not all required positional arguments were provided. "
|
||||
"First missing and required positional argument: '"
|
||||
<< missing_arg.info.name << "'\n";
|
||||
return ParseResult::Error;
|
||||
return Error(
|
||||
llvm::formatv("not all required positional arguments were provided; "
|
||||
"first missing and required positional argument: `{0}`",
|
||||
missing_arg.info.name));
|
||||
}
|
||||
for (const auto& arg_ptr : unparsed_positional_args) {
|
||||
CARBON_CHECK(
|
||||
@@ -1174,11 +1168,13 @@ auto Parser::FinalizeParse() -> ParseResult {
|
||||
switch (command_->kind) {
|
||||
case Command::Kind::Invalid:
|
||||
CARBON_FATAL("Should never have a parser with an invalid command!");
|
||||
case Command::Kind::RequiresSubcommand:
|
||||
*errors_ << "ERROR: No subcommand specified. Available subcommands: ";
|
||||
error_meta_printer_.PrintSubcommands(*command_);
|
||||
*errors_ << "\n";
|
||||
return ParseResult::Error;
|
||||
case Command::Kind::RequiresSubcommand: {
|
||||
std::string error_str;
|
||||
llvm::raw_string_ostream error(error_str);
|
||||
error << "no subcommand specified; available subcommands: ";
|
||||
MetaPrinter(&error).PrintSubcommands(*command_);
|
||||
return Error(error_str);
|
||||
}
|
||||
case Command::Kind::Action:
|
||||
// All arguments have been successfully parsed, run any action for the
|
||||
// most specific selected command. Only the leaf command's action is run.
|
||||
@@ -1191,7 +1187,7 @@ auto Parser::FinalizeParse() -> ParseResult {
|
||||
}
|
||||
|
||||
auto Parser::ParsePositionalSuffix(
|
||||
llvm::ArrayRef<llvm::StringRef> unparsed_args) -> bool {
|
||||
llvm::ArrayRef<llvm::StringRef> unparsed_args) -> ErrorOr<Success> {
|
||||
CARBON_CHECK(
|
||||
!command_->positional_args.empty(),
|
||||
"Cannot do positional suffix parsing without positional arguments!");
|
||||
@@ -1207,9 +1203,7 @@ auto Parser::ParsePositionalSuffix(
|
||||
unparsed_args = unparsed_args.drop_front();
|
||||
|
||||
if (unparsed_arg != "--") {
|
||||
if (!ParsePositionalArg(unparsed_arg)) {
|
||||
return false;
|
||||
}
|
||||
CARBON_RETURN_IF_ERROR(ParsePositionalArg(unparsed_arg));
|
||||
empty_positional = false;
|
||||
continue;
|
||||
}
|
||||
@@ -1218,28 +1212,23 @@ auto Parser::ParsePositionalSuffix(
|
||||
++positional_arg_index_;
|
||||
if (static_cast<size_t>(positional_arg_index_) >=
|
||||
command_->positional_args.size()) {
|
||||
*errors_
|
||||
<< "ERROR: Completed parsing all "
|
||||
<< command_->positional_args.size()
|
||||
<< " configured positional arguments, but found a subsequent `--` "
|
||||
"and have no further positional arguments to parse beyond it.\n";
|
||||
return false;
|
||||
return Error(
|
||||
llvm::formatv("completed parsing all {0} configured positional "
|
||||
"arguments, but found a subsequent `--` and have no "
|
||||
"further positional arguments to parse beyond it",
|
||||
command_->positional_args.size()));
|
||||
}
|
||||
}
|
||||
appending_to_positional_arg_ = false;
|
||||
empty_positional = true;
|
||||
}
|
||||
|
||||
return true;
|
||||
return Success();
|
||||
}
|
||||
|
||||
Parser::Parser(llvm::raw_ostream* out, llvm::raw_ostream* errors,
|
||||
CommandInfo command_info,
|
||||
Parser::Parser(llvm::raw_ostream* out, CommandInfo command_info,
|
||||
llvm::function_ref<void(CommandBuilder&)> build)
|
||||
: meta_printer_(out),
|
||||
errors_(errors),
|
||||
error_meta_printer_(errors),
|
||||
root_command_(command_info) {
|
||||
: meta_printer_(out), root_command_(command_info) {
|
||||
// Run the command building lambda on a builder for the root command.
|
||||
CommandBuilder builder(&root_command_, &meta_printer_);
|
||||
build(builder);
|
||||
@@ -1248,7 +1237,7 @@ Parser::Parser(llvm::raw_ostream* out, llvm::raw_ostream* errors,
|
||||
}
|
||||
|
||||
auto Parser::Parse(llvm::ArrayRef<llvm::StringRef> unparsed_args)
|
||||
-> ParseResult {
|
||||
-> ErrorOr<ParseResult> {
|
||||
PopulateMaps(*command_);
|
||||
|
||||
while (!unparsed_args.empty()) {
|
||||
@@ -1258,22 +1247,18 @@ auto Parser::Parse(llvm::ArrayRef<llvm::StringRef> unparsed_args)
|
||||
// positional suffix parsing without dropping this argument.
|
||||
if (unparsed_arg == "--") {
|
||||
if (command_->positional_args.empty()) {
|
||||
*errors_ << "ERROR: Cannot meaningfully end option and subcommand "
|
||||
"arguments with a `--` argument when there are no "
|
||||
"positional arguments to parse.\n";
|
||||
return ParseResult::Error;
|
||||
return Error(
|
||||
"cannot meaningfully end option and subcommand arguments with a "
|
||||
"`--` argument when there are no positional arguments to parse");
|
||||
}
|
||||
if (static_cast<size_t>(positional_arg_index_) >=
|
||||
command_->positional_args.size()) {
|
||||
*errors_
|
||||
<< "ERROR: Switched to purely positional arguments with a `--` "
|
||||
"argument despite already having parsed all positional "
|
||||
"arguments for this command.\n";
|
||||
return ParseResult::Error;
|
||||
}
|
||||
if (!ParsePositionalSuffix(unparsed_args)) {
|
||||
return ParseResult::Error;
|
||||
return Error(
|
||||
"switched to purely positional arguments with a `--` argument "
|
||||
"despite already having parsed all positional arguments for this "
|
||||
"command");
|
||||
}
|
||||
CARBON_RETURN_IF_ERROR(ParsePositionalSuffix(unparsed_args));
|
||||
// No more unparsed arguments to handle.
|
||||
break;
|
||||
}
|
||||
@@ -1284,16 +1269,12 @@ auto Parser::Parse(llvm::ArrayRef<llvm::StringRef> unparsed_args)
|
||||
|
||||
if (unparsed_arg.starts_with("--")) {
|
||||
// Note that the exact argument "--" has been handled above already.
|
||||
if (!ParseLongOption(unparsed_arg)) {
|
||||
return ParseResult::Error;
|
||||
}
|
||||
CARBON_RETURN_IF_ERROR(ParseLongOption(unparsed_arg));
|
||||
continue;
|
||||
}
|
||||
|
||||
if (unparsed_arg.starts_with("-") && unparsed_arg.size() > 1) {
|
||||
if (!ParseShortOptionSeq(unparsed_arg)) {
|
||||
return ParseResult::Error;
|
||||
}
|
||||
CARBON_RETURN_IF_ERROR(ParseShortOptionSeq(unparsed_arg));
|
||||
continue;
|
||||
}
|
||||
|
||||
@@ -1301,20 +1282,16 @@ auto Parser::Parse(llvm::ArrayRef<llvm::StringRef> unparsed_args)
|
||||
command_->positional_args.empty() || command_->subcommands.empty(),
|
||||
"Cannot have both positional arguments and subcommands!");
|
||||
if (command_->positional_args.empty() && command_->subcommands.empty()) {
|
||||
*errors_ << "ERROR: Found unexpected positional argument or subcommand: '"
|
||||
<< unparsed_arg << "'\n";
|
||||
return ParseResult::Error;
|
||||
return Error(llvm::formatv(
|
||||
"found unexpected positional argument or subcommand: `{0}`",
|
||||
unparsed_arg));
|
||||
}
|
||||
|
||||
if (!command_->positional_args.empty()) {
|
||||
if (!ParsePositionalArg(unparsed_arg)) {
|
||||
return ParseResult::Error;
|
||||
}
|
||||
CARBON_RETURN_IF_ERROR(ParsePositionalArg(unparsed_arg));
|
||||
continue;
|
||||
}
|
||||
if (!ParseSubcommand(unparsed_arg)) {
|
||||
return ParseResult::Error;
|
||||
}
|
||||
CARBON_RETURN_IF_ERROR(ParseSubcommand(unparsed_arg));
|
||||
}
|
||||
|
||||
return FinalizeParse();
|
||||
@@ -1531,12 +1508,12 @@ void CommandBuilder::Finalize() {
|
||||
}
|
||||
|
||||
auto Parse(llvm::ArrayRef<llvm::StringRef> unparsed_args,
|
||||
llvm::raw_ostream& out, llvm::raw_ostream& errors,
|
||||
CommandInfo command_info,
|
||||
llvm::function_ref<void(CommandBuilder&)> build) -> ParseResult {
|
||||
llvm::raw_ostream& out, CommandInfo command_info,
|
||||
llvm::function_ref<void(CommandBuilder&)> build)
|
||||
-> ErrorOr<ParseResult> {
|
||||
// Build a parser, which includes building the command description provided by
|
||||
// the user.
|
||||
Parser parser(&out, &errors, command_info, build);
|
||||
Parser parser(&out, command_info, build);
|
||||
|
||||
// Now parse the arguments provided using that parser.
|
||||
return parser.Parse(unparsed_args);
|
||||
|
||||
+5
-11
@@ -9,6 +9,7 @@
|
||||
#include <utility>
|
||||
|
||||
#include "common/check.h"
|
||||
#include "common/error.h"
|
||||
#include "common/ostream.h"
|
||||
#include "llvm/ADT/ArrayRef.h"
|
||||
#include "llvm/ADT/DenseSet.h"
|
||||
@@ -220,15 +221,8 @@ class MetaPrinter;
|
||||
class Parser;
|
||||
class CommandBuilder;
|
||||
|
||||
// The result of parsing arguments can be a parse error, a successfully parsed
|
||||
// command line, or a meta-success due to triggering a meta-action during the
|
||||
// parse such as rendering help text.
|
||||
// The result of parsing arguments.
|
||||
enum class ParseResult : int8_t {
|
||||
// Signifies an error parsing arguments. It will have been diagnosed using
|
||||
// the streams provided to the parser, and no useful parsed arguments are
|
||||
// available.
|
||||
Error,
|
||||
|
||||
// Signifies that program arguments were successfully parsed and can be
|
||||
// used.
|
||||
Success,
|
||||
@@ -660,9 +654,9 @@ class CommandBuilder {
|
||||
// are printed to `errors`, but meta-actions like printing a command's help go
|
||||
// to `out`.
|
||||
auto Parse(llvm::ArrayRef<llvm::StringRef> unparsed_args,
|
||||
llvm::raw_ostream& out, llvm::raw_ostream& errors,
|
||||
CommandInfo command_info,
|
||||
llvm::function_ref<void(CommandBuilder&)> build) -> ParseResult;
|
||||
llvm::raw_ostream& out, CommandInfo command_info,
|
||||
llvm::function_ref<void(CommandBuilder&)> build)
|
||||
-> ErrorOr<ParseResult>;
|
||||
|
||||
// Implementation details only below.
|
||||
|
||||
|
||||
+145
-154
@@ -7,17 +7,18 @@
|
||||
#include <gmock/gmock.h>
|
||||
#include <gtest/gtest.h>
|
||||
|
||||
#include "common/error_test_helpers.h"
|
||||
#include "llvm/Support/FormatVariadic.h"
|
||||
#include "testing/base/test_raw_ostream.h"
|
||||
|
||||
namespace Carbon::CommandLine {
|
||||
namespace {
|
||||
|
||||
using ::Carbon::Testing::IsError;
|
||||
using ::Carbon::Testing::IsSuccess;
|
||||
using ::Carbon::Testing::TestRawOstream;
|
||||
using ::testing::ElementsAre;
|
||||
using ::testing::Eq;
|
||||
using ::testing::HasSubstr;
|
||||
using ::testing::Not;
|
||||
using ::testing::StrEq;
|
||||
|
||||
constexpr CommandInfo TestCommandInfo = {
|
||||
@@ -41,19 +42,18 @@ TEST(ArgParserTest, BasicCommand) {
|
||||
int integer_option = -1;
|
||||
llvm::StringRef string_option = "";
|
||||
auto parse = [&](llvm::ArrayRef<llvm::StringRef> args) {
|
||||
return Parse(
|
||||
args, llvm::errs(), llvm::errs(), TestCommandInfo, [&](auto& b) {
|
||||
b.AddFlag({.name = "flag"}, [&](auto& arg_b) { arg_b.Set(&flag); });
|
||||
b.AddIntegerOption({.name = "option1"},
|
||||
[&](auto& arg_b) { arg_b.Set(&integer_option); });
|
||||
b.AddStringOption({.name = "option2"},
|
||||
[&](auto& arg_b) { arg_b.Set(&string_option); });
|
||||
b.Do([] {});
|
||||
});
|
||||
return Parse(args, llvm::errs(), TestCommandInfo, [&](auto& b) {
|
||||
b.AddFlag({.name = "flag"}, [&](auto& arg_b) { arg_b.Set(&flag); });
|
||||
b.AddIntegerOption({.name = "option1"},
|
||||
[&](auto& arg_b) { arg_b.Set(&integer_option); });
|
||||
b.AddStringOption({.name = "option2"},
|
||||
[&](auto& arg_b) { arg_b.Set(&string_option); });
|
||||
b.Do([] {});
|
||||
});
|
||||
};
|
||||
|
||||
EXPECT_THAT(parse({"--flag", "--option2=value", "--option1=42"}),
|
||||
Eq(ParseResult::Success));
|
||||
IsSuccess(Eq(ParseResult::Success)));
|
||||
EXPECT_TRUE(flag);
|
||||
EXPECT_THAT(integer_option, Eq(42));
|
||||
EXPECT_THAT(string_option, StrEq("value"));
|
||||
@@ -62,47 +62,46 @@ TEST(ArgParserTest, BasicCommand) {
|
||||
TEST(ArgParserTest, BooleanFlags) {
|
||||
bool flag = false;
|
||||
auto parse = [&](llvm::ArrayRef<llvm::StringRef> args, llvm::raw_ostream& s) {
|
||||
return Parse(args, s, s, TestCommandInfo, [&](auto& b) {
|
||||
return Parse(args, s, TestCommandInfo, [&](auto& b) {
|
||||
b.AddFlag({.name = "flag"}, [&](auto& arg_b) { arg_b.Set(&flag); });
|
||||
b.Do([] {});
|
||||
});
|
||||
};
|
||||
|
||||
EXPECT_THAT(parse({"--no-flag"}, llvm::errs()), Eq(ParseResult::Success));
|
||||
EXPECT_THAT(parse({"--no-flag"}, llvm::errs()),
|
||||
IsSuccess(Eq(ParseResult::Success)));
|
||||
EXPECT_FALSE(flag);
|
||||
|
||||
EXPECT_THAT(parse({"--flag", "--no-flag"}, llvm::errs()),
|
||||
Eq(ParseResult::Success));
|
||||
IsSuccess(Eq(ParseResult::Success)));
|
||||
EXPECT_FALSE(flag);
|
||||
|
||||
EXPECT_THAT(parse({"--no-flag", "--flag"}, llvm::errs()),
|
||||
Eq(ParseResult::Success));
|
||||
IsSuccess(Eq(ParseResult::Success)));
|
||||
EXPECT_TRUE(flag);
|
||||
|
||||
EXPECT_THAT(parse({"--no-flag", "--flag", "--flag=false"}, llvm::errs()),
|
||||
Eq(ParseResult::Success));
|
||||
IsSuccess(Eq(ParseResult::Success)));
|
||||
EXPECT_FALSE(flag);
|
||||
|
||||
EXPECT_THAT(parse({"--no-flag", "--flag=true"}, llvm::errs()),
|
||||
Eq(ParseResult::Success));
|
||||
IsSuccess(Eq(ParseResult::Success)));
|
||||
EXPECT_TRUE(flag);
|
||||
|
||||
EXPECT_THAT(parse({"--no-flag", "--flag=true", "--no-flag"}, llvm::errs()),
|
||||
Eq(ParseResult::Success));
|
||||
IsSuccess(Eq(ParseResult::Success)));
|
||||
EXPECT_FALSE(flag);
|
||||
|
||||
TestRawOstream os;
|
||||
EXPECT_THAT(parse({"--no-flag=true"}, os), Eq(ParseResult::Error));
|
||||
EXPECT_THAT(
|
||||
os.TakeStr(),
|
||||
StrEq("ERROR: Cannot specify a value when using a flag name prefixed "
|
||||
"with 'no-' -- that prefix implies a value of 'false'.\n"));
|
||||
parse({"--no-flag=true"}, os),
|
||||
IsError(StrEq("cannot specify a value when using a flag name prefixed "
|
||||
"with `no-` -- that prefix implies a value of `false`")));
|
||||
|
||||
EXPECT_THAT(parse({"--no-flag=false"}, os), Eq(ParseResult::Error));
|
||||
EXPECT_THAT(
|
||||
os.TakeStr(),
|
||||
StrEq("ERROR: Cannot specify a value when using a flag name prefixed "
|
||||
"with 'no-' -- that prefix implies a value of 'false'.\n"));
|
||||
parse({"--no-flag=false"}, os),
|
||||
IsError(StrEq("cannot specify a value when using a flag name prefixed "
|
||||
"with `no-` -- that prefix implies a value of `false`")));
|
||||
}
|
||||
|
||||
TEST(ArgParserTest, ArgDefaults) {
|
||||
@@ -110,7 +109,7 @@ TEST(ArgParserTest, ArgDefaults) {
|
||||
int integer_option = -1;
|
||||
llvm::StringRef string_option = "";
|
||||
auto parse = [&](llvm::ArrayRef<llvm::StringRef> args, llvm::raw_ostream& s) {
|
||||
return Parse(args, s, s, TestCommandInfo, [&](auto& b) {
|
||||
return Parse(args, s, TestCommandInfo, [&](auto& b) {
|
||||
b.AddFlag({.name = "flag"}, [&](auto& arg_b) {
|
||||
arg_b.Default(true);
|
||||
arg_b.Set(&flag);
|
||||
@@ -127,19 +126,19 @@ TEST(ArgParserTest, ArgDefaults) {
|
||||
});
|
||||
};
|
||||
|
||||
EXPECT_THAT(parse({}, llvm::errs()), Eq(ParseResult::Success));
|
||||
EXPECT_THAT(parse({}, llvm::errs()), IsSuccess(Eq(ParseResult::Success)));
|
||||
EXPECT_TRUE(flag);
|
||||
EXPECT_THAT(integer_option, Eq(7));
|
||||
EXPECT_THAT(string_option, StrEq("default"));
|
||||
|
||||
EXPECT_THAT(parse({"--option1", "--option2"}, llvm::errs()),
|
||||
Eq(ParseResult::Success));
|
||||
IsSuccess(Eq(ParseResult::Success)));
|
||||
EXPECT_TRUE(flag);
|
||||
EXPECT_THAT(integer_option, Eq(7));
|
||||
EXPECT_THAT(string_option, StrEq("default"));
|
||||
|
||||
EXPECT_THAT(parse({"--option1=42", "--option2=explicit"}, llvm::errs()),
|
||||
Eq(ParseResult::Success));
|
||||
IsSuccess(Eq(ParseResult::Success)));
|
||||
EXPECT_TRUE(flag);
|
||||
EXPECT_THAT(integer_option, Eq(42));
|
||||
EXPECT_THAT(string_option, StrEq("explicit"));
|
||||
@@ -147,7 +146,7 @@ TEST(ArgParserTest, ArgDefaults) {
|
||||
EXPECT_THAT(
|
||||
parse({"--option1=42", "--option2=explicit", "--option1", "--option2"},
|
||||
llvm::errs()),
|
||||
Eq(ParseResult::Success));
|
||||
IsSuccess(Eq(ParseResult::Success)));
|
||||
EXPECT_TRUE(flag);
|
||||
EXPECT_THAT(integer_option, Eq(7));
|
||||
EXPECT_THAT(string_option, StrEq("default"));
|
||||
@@ -158,7 +157,7 @@ TEST(ArgParserTest, ShortArgs) {
|
||||
bool example = false;
|
||||
int integer_option = -1;
|
||||
auto parse = [&](llvm::ArrayRef<llvm::StringRef> args, llvm::raw_ostream& s) {
|
||||
return Parse(args, s, s, TestCommandInfo, [&](auto& b) {
|
||||
return Parse(args, s, TestCommandInfo, [&](auto& b) {
|
||||
b.AddFlag({.name = "flag", .short_name = "f"},
|
||||
[&](auto& arg_b) { arg_b.Set(&flag); });
|
||||
b.AddFlag({.name = "example", .short_name = "x"},
|
||||
@@ -175,7 +174,7 @@ TEST(ArgParserTest, ShortArgs) {
|
||||
};
|
||||
|
||||
EXPECT_THAT(parse({"-f", "-o=42", "-x"}, llvm::errs()),
|
||||
Eq(ParseResult::Success));
|
||||
IsSuccess(Eq(ParseResult::Success)));
|
||||
EXPECT_TRUE(flag);
|
||||
EXPECT_TRUE(example);
|
||||
EXPECT_THAT(integer_option, Eq(42));
|
||||
@@ -183,27 +182,27 @@ TEST(ArgParserTest, ShortArgs) {
|
||||
flag = false;
|
||||
example = false;
|
||||
EXPECT_THAT(parse({"--option1=13", "-xfo"}, llvm::errs()),
|
||||
Eq(ParseResult::Success));
|
||||
IsSuccess(Eq(ParseResult::Success)));
|
||||
EXPECT_TRUE(flag);
|
||||
EXPECT_TRUE(example);
|
||||
EXPECT_THAT(integer_option, Eq(123));
|
||||
|
||||
TestRawOstream os;
|
||||
EXPECT_THAT(parse({"-xzf"}, os), Eq(ParseResult::Error));
|
||||
EXPECT_THAT(os.TakeStr(),
|
||||
StrEq("ERROR: Option '-z' (short for '--option2') requires a "
|
||||
"value to be provided and none was.\n"));
|
||||
|
||||
EXPECT_THAT(parse({"--option2"}, os), Eq(ParseResult::Error));
|
||||
EXPECT_THAT(os.TakeStr(), StrEq("ERROR: Option '--option2' requires a value "
|
||||
"to be provided and none was.\n"));
|
||||
|
||||
EXPECT_THAT(parse({"-xz=123"}, os), Eq(ParseResult::Error));
|
||||
EXPECT_THAT(
|
||||
os.TakeStr(),
|
||||
StrEq("ERROR: Cannot provide a value to the group of multiple short "
|
||||
"options '-xz=...'; values must be provided to a single option, "
|
||||
"using either the short or long spelling.\n"));
|
||||
parse({"-xzf"}, os),
|
||||
IsError(StrEq("option `-z` (short for `--option2`) requires a value to "
|
||||
"be provided and none was")));
|
||||
|
||||
EXPECT_THAT(
|
||||
parse({"--option2"}, os),
|
||||
IsError(StrEq(
|
||||
"option `--option2` requires a value to be provided and none was")));
|
||||
|
||||
EXPECT_THAT(
|
||||
parse({"-xz=123"}, os),
|
||||
IsError(StrEq("cannot provide a value to the group of multiple short "
|
||||
"options `-xz=...`; values must be provided to a single "
|
||||
"option, using either the short or long spelling")));
|
||||
}
|
||||
|
||||
TEST(ArgParserTest, PositionalArgs) {
|
||||
@@ -212,7 +211,7 @@ TEST(ArgParserTest, PositionalArgs) {
|
||||
llvm::StringRef source_string;
|
||||
llvm::StringRef dest_string;
|
||||
auto parse = [&](llvm::ArrayRef<llvm::StringRef> args, llvm::raw_ostream& s) {
|
||||
return Parse(args, s, s, TestCommandInfo, [&](auto& b) {
|
||||
return Parse(args, s, TestCommandInfo, [&](auto& b) {
|
||||
b.AddFlag({.name = "flag"}, [&](auto& arg_b) { arg_b.Set(&flag); });
|
||||
b.AddStringOption({.name = "option"},
|
||||
[&](auto& arg_b) { arg_b.Set(&string_option); });
|
||||
@@ -229,25 +228,25 @@ TEST(ArgParserTest, PositionalArgs) {
|
||||
};
|
||||
|
||||
TestRawOstream os;
|
||||
EXPECT_THAT(parse({"--flag", "--option=x"}, os), Eq(ParseResult::Error));
|
||||
EXPECT_THAT(
|
||||
os.TakeStr(),
|
||||
StrEq("ERROR: Not all required positional arguments were provided. First "
|
||||
"missing and required positional argument: 'source'\n"));
|
||||
EXPECT_THAT(parse({"--flag", "--option=x"}, os),
|
||||
IsError(StrEq(
|
||||
"not all required positional arguments were provided; first "
|
||||
"missing and required positional argument: `source`")));
|
||||
|
||||
EXPECT_THAT(parse({"src", "--flag", "--option=value", "dst"}, llvm::errs()),
|
||||
Eq(ParseResult::Success));
|
||||
IsSuccess(Eq(ParseResult::Success)));
|
||||
EXPECT_TRUE(flag);
|
||||
EXPECT_THAT(string_option, StrEq("value"));
|
||||
EXPECT_THAT(source_string, StrEq("src"));
|
||||
EXPECT_THAT(dest_string, StrEq("dst"));
|
||||
|
||||
EXPECT_THAT(parse({"src2", "--", "dst2"}, llvm::errs()),
|
||||
Eq(ParseResult::Success));
|
||||
IsSuccess(Eq(ParseResult::Success)));
|
||||
EXPECT_THAT(source_string, StrEq("src2"));
|
||||
EXPECT_THAT(dest_string, StrEq("dst2"));
|
||||
|
||||
EXPECT_THAT(parse({"-", "--", "-"}, llvm::errs()), Eq(ParseResult::Success));
|
||||
EXPECT_THAT(parse({"-", "--", "-"}, llvm::errs()),
|
||||
IsSuccess(Eq(ParseResult::Success)));
|
||||
EXPECT_THAT(source_string, StrEq("-"));
|
||||
EXPECT_THAT(dest_string, StrEq("-"));
|
||||
}
|
||||
@@ -258,7 +257,7 @@ TEST(ArgParserTest, PositionalAppendArgs) {
|
||||
llvm::SmallVector<llvm::StringRef> source_strings;
|
||||
llvm::SmallVector<llvm::StringRef> dest_strings;
|
||||
auto parse = [&](llvm::ArrayRef<llvm::StringRef> args, llvm::raw_ostream& s) {
|
||||
return Parse(args, s, s, TestCommandInfo, [&](auto& b) {
|
||||
return Parse(args, s, TestCommandInfo, [&](auto& b) {
|
||||
b.AddFlag({.name = "flag"}, [&](auto& arg_b) { arg_b.Set(&flag); });
|
||||
b.AddStringOption({.name = "option"},
|
||||
[&](auto& arg_b) { arg_b.Set(&string_option); });
|
||||
@@ -275,16 +274,15 @@ TEST(ArgParserTest, PositionalAppendArgs) {
|
||||
};
|
||||
|
||||
TestRawOstream os;
|
||||
EXPECT_THAT(parse({"--flag", "--option=x"}, os), Eq(ParseResult::Error));
|
||||
EXPECT_THAT(
|
||||
os.TakeStr(),
|
||||
StrEq("ERROR: Not all required positional arguments were provided. First "
|
||||
"missing and required positional argument: 'sources'\n"));
|
||||
EXPECT_THAT(parse({"--flag", "--option=x"}, os),
|
||||
IsError(StrEq(
|
||||
"not all required positional arguments were provided; first "
|
||||
"missing and required positional argument: `sources`")));
|
||||
|
||||
EXPECT_THAT(
|
||||
parse({"src1", "--flag", "src2", "--option=value", "--", "--dst--"},
|
||||
llvm::errs()),
|
||||
Eq(ParseResult::Success));
|
||||
IsSuccess(Eq(ParseResult::Success)));
|
||||
EXPECT_TRUE(flag);
|
||||
EXPECT_THAT(string_option, StrEq("value"));
|
||||
EXPECT_THAT(source_strings, ElementsAre(StrEq("src1"), StrEq("src2")));
|
||||
@@ -294,7 +292,7 @@ TEST(ArgParserTest, PositionalAppendArgs) {
|
||||
dest_strings.clear();
|
||||
EXPECT_THAT(
|
||||
parse({"--", "--src1--", "--src2--", "--", "dst1", "dst2"}, llvm::errs()),
|
||||
Eq(ParseResult::Success));
|
||||
IsSuccess(Eq(ParseResult::Success)));
|
||||
EXPECT_THAT(source_strings,
|
||||
ElementsAre(StrEq("--src1--"), StrEq("--src2--")));
|
||||
EXPECT_THAT(dest_strings, ElementsAre(StrEq("dst1"), StrEq("dst2")));
|
||||
@@ -302,7 +300,7 @@ TEST(ArgParserTest, PositionalAppendArgs) {
|
||||
source_strings.clear();
|
||||
dest_strings.clear();
|
||||
EXPECT_THAT(parse({"--", "--", "dst1", "dst2"}, llvm::errs()),
|
||||
Eq(ParseResult::Success));
|
||||
IsSuccess(Eq(ParseResult::Success)));
|
||||
EXPECT_THAT(source_strings, ElementsAre());
|
||||
EXPECT_THAT(dest_strings, ElementsAre(StrEq("dst1"), StrEq("dst2")));
|
||||
}
|
||||
@@ -316,7 +314,7 @@ TEST(ArgParserTest, BasicSubcommands) {
|
||||
bool subsub_command = false;
|
||||
|
||||
auto parse = [&](llvm::ArrayRef<llvm::StringRef> args, llvm::raw_ostream& s) {
|
||||
return Parse(args, s, s, TestCommandInfo, [&](auto& b) {
|
||||
return Parse(args, s, TestCommandInfo, [&](auto& b) {
|
||||
b.AddSubcommand({.name = "sub1"}, [&](auto& sub_b) {
|
||||
sub_b.AddFlag({.name = "flag"}, [&](auto& arg_b) { arg_b.Set(&flag); });
|
||||
sub_b.AddStringOption({.name = "option"},
|
||||
@@ -338,39 +336,36 @@ TEST(ArgParserTest, BasicSubcommands) {
|
||||
};
|
||||
|
||||
TestRawOstream os;
|
||||
EXPECT_THAT(parse({}, os), Eq(ParseResult::Error));
|
||||
EXPECT_THAT(os.TakeStr(), StrEq("ERROR: No subcommand specified. Available "
|
||||
"subcommands: 'sub1', 'sub2', or 'help'\n"));
|
||||
EXPECT_THAT(parse({}, os),
|
||||
IsError(StrEq("no subcommand specified; available subcommands: "
|
||||
"`sub1`, `sub2`, or `help`")));
|
||||
|
||||
EXPECT_THAT(parse({"--flag"}, os), Eq(ParseResult::Error));
|
||||
EXPECT_THAT(os.TakeStr(), StrEq("ERROR: Unknown option '--flag'\n"));
|
||||
EXPECT_THAT(parse({"--flag"}, os), IsError(StrEq("unknown option `--flag`")));
|
||||
|
||||
EXPECT_THAT(parse({"sub3"}, os), Eq(ParseResult::Error));
|
||||
EXPECT_THAT(os.TakeStr(), StrEq("ERROR: Invalid subcommand 'sub3'. Available "
|
||||
"subcommands: 'sub1', 'sub2', or 'help'\n"));
|
||||
EXPECT_THAT(parse({"sub3"}, os),
|
||||
IsError(StrEq("invalid subcommand `sub3`; available subcommands: "
|
||||
"`sub1`, `sub2`, or `help`")));
|
||||
|
||||
EXPECT_THAT(parse({"--flag", "sub1"}, os), Eq(ParseResult::Error));
|
||||
EXPECT_THAT(os.TakeStr(), StrEq("ERROR: Unknown option '--flag'\n"));
|
||||
EXPECT_THAT(parse({"--flag", "sub1"}, os),
|
||||
IsError(StrEq("unknown option `--flag`")));
|
||||
|
||||
EXPECT_THAT(parse({"sub1", "--flag"}, llvm::errs()),
|
||||
Eq(ParseResult::Success));
|
||||
IsSuccess(Eq(ParseResult::Success)));
|
||||
EXPECT_THAT(subcommand, Eq(TestSubcommand::Sub1));
|
||||
EXPECT_TRUE(flag);
|
||||
|
||||
EXPECT_THAT(parse({"sub2", "--option=value"}, llvm::errs()),
|
||||
Eq(ParseResult::Success));
|
||||
IsSuccess(Eq(ParseResult::Success)));
|
||||
EXPECT_THAT(subcommand, Eq(TestSubcommand::Sub2));
|
||||
EXPECT_THAT(option1, StrEq("value"));
|
||||
|
||||
EXPECT_THAT(parse({"sub2", "--option=abc", "subsub42", "--option=xyz"}, os),
|
||||
Eq(ParseResult::Error));
|
||||
EXPECT_THAT(os.TakeStr(),
|
||||
StrEq("ERROR: Invalid subcommand 'subsub42'. Available "
|
||||
"subcommands: 'subsub', or 'help'\n"));
|
||||
IsError(StrEq("invalid subcommand `subsub42`; available "
|
||||
"subcommands: `subsub`, or `help`")));
|
||||
|
||||
EXPECT_THAT(
|
||||
parse({"sub2", "--option=abc", "subsub", "--option=xyz"}, llvm::errs()),
|
||||
Eq(ParseResult::Success));
|
||||
IsSuccess(Eq(ParseResult::Success)));
|
||||
EXPECT_TRUE(subsub_command);
|
||||
EXPECT_THAT(option1, StrEq("abc"));
|
||||
EXPECT_THAT(option2, StrEq("xyz"));
|
||||
@@ -380,7 +375,7 @@ TEST(ArgParserTest, Appending) {
|
||||
llvm::SmallVector<int> integers;
|
||||
llvm::SmallVector<llvm::StringRef> strings;
|
||||
auto parse = [&](llvm::ArrayRef<llvm::StringRef> args, llvm::raw_ostream& s) {
|
||||
return Parse(args, s, s, TestCommandInfo, [&](auto& b) {
|
||||
return Parse(args, s, TestCommandInfo, [&](auto& b) {
|
||||
b.AddIntegerOption({.name = "int"},
|
||||
[&](auto& arg_b) { arg_b.Append(&integers); });
|
||||
b.AddStringOption({.name = "str"},
|
||||
@@ -389,22 +384,22 @@ TEST(ArgParserTest, Appending) {
|
||||
});
|
||||
};
|
||||
|
||||
EXPECT_THAT(parse({}, llvm::errs()), Eq(ParseResult::Success));
|
||||
EXPECT_THAT(parse({}, llvm::errs()), IsSuccess(Eq(ParseResult::Success)));
|
||||
EXPECT_THAT(integers, ElementsAre());
|
||||
EXPECT_THAT(strings, ElementsAre());
|
||||
|
||||
EXPECT_THAT(parse({"--int=1", "--int=2", "--int=3"}, llvm::errs()),
|
||||
Eq(ParseResult::Success));
|
||||
IsSuccess(Eq(ParseResult::Success)));
|
||||
EXPECT_THAT(integers, ElementsAre(Eq(1), Eq(2), Eq(3)));
|
||||
EXPECT_THAT(strings, ElementsAre());
|
||||
|
||||
EXPECT_THAT(parse({"--str=a", "--str=b", "--str=c"}, llvm::errs()),
|
||||
Eq(ParseResult::Success));
|
||||
IsSuccess(Eq(ParseResult::Success)));
|
||||
EXPECT_THAT(integers, ElementsAre(Eq(1), Eq(2), Eq(3)));
|
||||
EXPECT_THAT(strings, ElementsAre(StrEq("a"), StrEq("b"), StrEq("c")));
|
||||
|
||||
EXPECT_THAT(parse({"--str=d", "--int=4", "--str=e"}, llvm::errs()),
|
||||
Eq(ParseResult::Success));
|
||||
IsSuccess(Eq(ParseResult::Success)));
|
||||
EXPECT_THAT(integers, ElementsAre(Eq(1), Eq(2), Eq(3), Eq(4)));
|
||||
EXPECT_THAT(strings, ElementsAre(StrEq("a"), StrEq("b"), StrEq("c"),
|
||||
StrEq("d"), StrEq("e")));
|
||||
@@ -416,7 +411,7 @@ TEST(ArgParserTest, PositionalAppending) {
|
||||
llvm::SmallVector<llvm::StringRef> strings2;
|
||||
llvm::SmallVector<llvm::StringRef> strings3;
|
||||
auto parse = [&](llvm::ArrayRef<llvm::StringRef> args, llvm::raw_ostream& s) {
|
||||
return Parse(args, s, s, TestCommandInfo, [&](auto& b) {
|
||||
return Parse(args, s, TestCommandInfo, [&](auto& b) {
|
||||
b.AddStringOption({.name = "opt"},
|
||||
[&](auto& arg_b) { arg_b.Append(&option_strings); });
|
||||
b.AddStringPositionalArg({.name = "s1"},
|
||||
@@ -432,7 +427,7 @@ TEST(ArgParserTest, PositionalAppending) {
|
||||
EXPECT_THAT(parse({"a", "--opt=x", "b", "--opt=y", "--", "c", "--opt=z", "d",
|
||||
"--", "e", "f"},
|
||||
llvm::errs()),
|
||||
Eq(ParseResult::Success));
|
||||
IsSuccess(Eq(ParseResult::Success)));
|
||||
EXPECT_THAT(option_strings, ElementsAre(StrEq("x"), StrEq("y")));
|
||||
EXPECT_THAT(strings1, ElementsAre(StrEq("a"), StrEq("b")));
|
||||
EXPECT_THAT(strings2, ElementsAre(StrEq("c"), StrEq("--opt=z"), StrEq("d")));
|
||||
@@ -442,7 +437,7 @@ TEST(ArgParserTest, PositionalAppending) {
|
||||
TEST(ArgParserTest, OneOfOption) {
|
||||
int value = 0;
|
||||
auto parse = [&](llvm::ArrayRef<llvm::StringRef> args, llvm::raw_ostream& s) {
|
||||
return Parse(args, s, s, TestCommandInfo, [&](auto& b) {
|
||||
return Parse(args, s, TestCommandInfo, [&](auto& b) {
|
||||
b.AddOneOfOption({.name = "option"}, [&](auto& arg_b) {
|
||||
arg_b.SetOneOf(
|
||||
{
|
||||
@@ -456,45 +451,48 @@ TEST(ArgParserTest, OneOfOption) {
|
||||
});
|
||||
};
|
||||
|
||||
EXPECT_THAT(parse({"--option=x"}, llvm::errs()), Eq(ParseResult::Success));
|
||||
EXPECT_THAT(parse({"--option=x"}, llvm::errs()),
|
||||
IsSuccess(Eq(ParseResult::Success)));
|
||||
EXPECT_THAT(value, Eq(1));
|
||||
|
||||
EXPECT_THAT(parse({"--option=y"}, llvm::errs()), Eq(ParseResult::Success));
|
||||
EXPECT_THAT(parse({"--option=y"}, llvm::errs()),
|
||||
IsSuccess(Eq(ParseResult::Success)));
|
||||
EXPECT_THAT(value, Eq(2));
|
||||
|
||||
EXPECT_THAT(parse({"--option=z"}, llvm::errs()), Eq(ParseResult::Success));
|
||||
EXPECT_THAT(parse({"--option=z"}, llvm::errs()),
|
||||
IsSuccess(Eq(ParseResult::Success)));
|
||||
EXPECT_THAT(value, Eq(3));
|
||||
|
||||
TestRawOstream os;
|
||||
|
||||
EXPECT_THAT(parse({"--option"}, os), Eq(ParseResult::Error));
|
||||
EXPECT_THAT(os.TakeStr(), StrEq("ERROR: Option '--option' requires a value "
|
||||
"to be provided and none was.\n"));
|
||||
EXPECT_THAT(
|
||||
parse({"--option"}, os),
|
||||
IsError(StrEq(
|
||||
"option `--option` requires a value to be provided and none was")));
|
||||
|
||||
constexpr const char* ErrorStr =
|
||||
"ERROR: Option '--option={0}' has an invalid value '{0}'; valid values "
|
||||
"are: 'x', 'y', or 'z'\n";
|
||||
EXPECT_THAT(parse({"--option=a"}, os), Eq(ParseResult::Error));
|
||||
EXPECT_THAT(os.TakeStr(), StrEq(llvm::formatv(ErrorStr, "a")));
|
||||
"option `--option={0}` has an invalid value `{0}`; valid values are: "
|
||||
"`x`, `y`, or `z`";
|
||||
EXPECT_THAT(parse({"--option=a"}, os),
|
||||
IsError(StrEq(llvm::formatv(ErrorStr, "a"))));
|
||||
|
||||
EXPECT_THAT(parse({"--option="}, os), Eq(ParseResult::Error));
|
||||
EXPECT_THAT(os.TakeStr(), StrEq(llvm::formatv(ErrorStr, "")));
|
||||
EXPECT_THAT(parse({"--option="}, os),
|
||||
IsError(StrEq(llvm::formatv(ErrorStr, ""))));
|
||||
|
||||
EXPECT_THAT(parse({"--option=xx"}, os), Eq(ParseResult::Error));
|
||||
EXPECT_THAT(os.TakeStr(), StrEq(llvm::formatv(ErrorStr, "xx")));
|
||||
EXPECT_THAT(parse({"--option=xx"}, os),
|
||||
IsError(StrEq(llvm::formatv(ErrorStr, "xx"))));
|
||||
|
||||
EXPECT_THAT(parse({"--option=\xFF"}, os), Eq(ParseResult::Error));
|
||||
EXPECT_THAT(os.TakeStr(), StrEq(llvm::formatv(ErrorStr, "\\FF")));
|
||||
EXPECT_THAT(parse({"--option=\xFF"}, os),
|
||||
IsError(StrEq(llvm::formatv(ErrorStr, "\\FF"))));
|
||||
|
||||
EXPECT_THAT(parse({llvm::StringRef("--option=\0", 10)}, os),
|
||||
Eq(ParseResult::Error));
|
||||
EXPECT_THAT(os.TakeStr(), StrEq(llvm::formatv(ErrorStr, "\\00")));
|
||||
IsError(StrEq(llvm::formatv(ErrorStr, "\\00"))));
|
||||
}
|
||||
|
||||
TEST(ArgParserTest, OneOfOptionAppending) {
|
||||
llvm::SmallVector<int> values;
|
||||
auto parse = [&](llvm::ArrayRef<llvm::StringRef> args, llvm::raw_ostream& s) {
|
||||
return Parse(args, s, s, TestCommandInfo, [&](auto& b) {
|
||||
return Parse(args, s, TestCommandInfo, [&](auto& b) {
|
||||
b.AddOneOfOption({.name = "option"}, [&](auto& arg_b) {
|
||||
arg_b.AppendOneOf(
|
||||
{
|
||||
@@ -508,15 +506,15 @@ TEST(ArgParserTest, OneOfOptionAppending) {
|
||||
});
|
||||
};
|
||||
|
||||
EXPECT_THAT(parse({}, llvm::errs()), Eq(ParseResult::Success));
|
||||
EXPECT_THAT(parse({}, llvm::errs()), IsSuccess(Eq(ParseResult::Success)));
|
||||
EXPECT_THAT(values, ElementsAre());
|
||||
|
||||
EXPECT_THAT(parse({"--option=x", "--option=y", "--option=z"}, llvm::errs()),
|
||||
Eq(ParseResult::Success));
|
||||
IsSuccess(Eq(ParseResult::Success)));
|
||||
EXPECT_THAT(values, ElementsAre(Eq(1), Eq(2), Eq(3)));
|
||||
|
||||
EXPECT_THAT(parse({"--option=y", "--option=y", "--option=x"}, llvm::errs()),
|
||||
Eq(ParseResult::Success));
|
||||
IsSuccess(Eq(ParseResult::Success)));
|
||||
EXPECT_THAT(values, ElementsAre(Eq(1), Eq(2), Eq(3), Eq(2), Eq(2), Eq(1)));
|
||||
}
|
||||
|
||||
@@ -530,7 +528,7 @@ TEST(ArgParserTest, RequiredArgs) {
|
||||
TestEnum enum_sub_option;
|
||||
|
||||
auto parse = [&](llvm::ArrayRef<llvm::StringRef> args, llvm::raw_ostream& s) {
|
||||
return Parse(args, s, s, TestCommandInfo, [&](auto& b) {
|
||||
return Parse(args, s, TestCommandInfo, [&](auto& b) {
|
||||
b.AddIntegerOption({.name = "opt1"}, [&](auto& arg_b) {
|
||||
arg_b.Required(true);
|
||||
arg_b.Set(&integer_option);
|
||||
@@ -574,46 +572,35 @@ TEST(ArgParserTest, RequiredArgs) {
|
||||
});
|
||||
};
|
||||
|
||||
constexpr const char* ErrorStr =
|
||||
"ERROR: Required option '{0}' not provided.\n";
|
||||
TestRawOstream os;
|
||||
EXPECT_THAT(parse({}, os), Eq(ParseResult::Error));
|
||||
auto errors = os.TakeStr();
|
||||
EXPECT_THAT(errors, HasSubstr(llvm::formatv(ErrorStr, "--opt1")));
|
||||
EXPECT_THAT(errors, HasSubstr(llvm::formatv(ErrorStr, "--opt2")));
|
||||
EXPECT_THAT(parse({}, os),
|
||||
IsError(StrEq("required options not provided: --opt1, --opt2")));
|
||||
|
||||
EXPECT_THAT(parse({"--opt2=xyz"}, os), Eq(ParseResult::Error));
|
||||
EXPECT_THAT(os.TakeStr(), StrEq(llvm::formatv(ErrorStr, "--opt1")));
|
||||
EXPECT_THAT(parse({"--opt2=xyz"}, os),
|
||||
IsError(StrEq("required options not provided: --opt1")));
|
||||
|
||||
EXPECT_THAT(parse({"--opt2=xyz", "--opt1=42"}, llvm::errs()),
|
||||
Eq(ParseResult::Success));
|
||||
IsSuccess(Eq(ParseResult::Success)));
|
||||
EXPECT_THAT(integer_option, Eq(42));
|
||||
EXPECT_THAT(string_option, StrEq("xyz"));
|
||||
|
||||
EXPECT_THAT(parse({"--opt2=xyz", "--opt1=42", "sub2"}, os),
|
||||
Eq(ParseResult::Error));
|
||||
errors = os.TakeStr();
|
||||
EXPECT_THAT(errors, HasSubstr(llvm::formatv(ErrorStr, "--sub-opt1")));
|
||||
EXPECT_THAT(errors, HasSubstr(llvm::formatv(ErrorStr, "--sub-opt2")));
|
||||
EXPECT_THAT(errors, HasSubstr(llvm::formatv(ErrorStr, "--sub-opt3")));
|
||||
IsError(StrEq("required options not provided: --sub-opt1, "
|
||||
"--sub-opt2, --sub-opt3")));
|
||||
|
||||
EXPECT_THAT(parse({"--opt2=xyz", "--opt1=42", "sub2", "--sub-opt3=b"}, os),
|
||||
Eq(ParseResult::Error));
|
||||
errors = os.TakeStr();
|
||||
EXPECT_THAT(errors, HasSubstr(llvm::formatv(ErrorStr, "--sub-opt1")));
|
||||
EXPECT_THAT(errors, HasSubstr(llvm::formatv(ErrorStr, "--sub-opt2")));
|
||||
EXPECT_THAT(errors, Not(HasSubstr(llvm::formatv(ErrorStr, "--sub-opt3"))));
|
||||
EXPECT_THAT(
|
||||
parse({"--opt2=xyz", "--opt1=42", "sub2", "--sub-opt3=b"}, os),
|
||||
IsError(StrEq("required options not provided: --sub-opt1, --sub-opt2")));
|
||||
|
||||
EXPECT_THAT(parse({"--opt2=xyz", "--opt1=42", "sub2", "--sub-opt3=b",
|
||||
"--sub-opt1=13"},
|
||||
os),
|
||||
Eq(ParseResult::Error));
|
||||
EXPECT_THAT(os.TakeStr(), StrEq(llvm::formatv(ErrorStr, "--sub-opt2")));
|
||||
IsError(StrEq("required options not provided: --sub-opt2")));
|
||||
|
||||
EXPECT_THAT(parse({"--opt2=xyz", "--opt1=42", "sub2", "--sub-opt3=b",
|
||||
"--sub-opt1=13", "--sub-opt2=abc"},
|
||||
llvm::errs()),
|
||||
Eq(ParseResult::Success));
|
||||
IsSuccess(Eq(ParseResult::Success)));
|
||||
EXPECT_THAT(integer_option, Eq(42));
|
||||
EXPECT_THAT(string_option, StrEq("xyz"));
|
||||
EXPECT_THAT(subcommand, Eq(TestSubcommand::Sub2));
|
||||
@@ -628,7 +615,7 @@ TEST(ArgParserTest, HelpAndVersion) {
|
||||
llvm::StringRef string = "";
|
||||
auto parse = [&](llvm::ArrayRef<llvm::StringRef> args, llvm::raw_ostream& s) {
|
||||
return Parse( // Force line break.
|
||||
args, s, s,
|
||||
args, s,
|
||||
{
|
||||
.name = "test",
|
||||
.version = "Test Command -- version 1.2.3",
|
||||
@@ -747,7 +734,8 @@ A hidden subcommand.
|
||||
|
||||
TestRawOstream os;
|
||||
|
||||
EXPECT_THAT(parse({"--flag", "--help"}, os), Eq(ParseResult::MetaSuccess));
|
||||
EXPECT_THAT(parse({"--flag", "--help"}, os),
|
||||
IsSuccess(Eq(ParseResult::MetaSuccess)));
|
||||
std::string help_flag_output = os.TakeStr();
|
||||
EXPECT_THAT(help_flag_output, StrEq(llvm::StringRef(R"""(
|
||||
Test Command -- version 1.2.3
|
||||
@@ -789,10 +777,11 @@ Closing remarks.
|
||||
|
||||
)""")
|
||||
.ltrim('\n')));
|
||||
EXPECT_THAT(parse({"help"}, os), Eq(ParseResult::MetaSuccess));
|
||||
EXPECT_THAT(parse({"help"}, os), IsSuccess(Eq(ParseResult::MetaSuccess)));
|
||||
EXPECT_THAT(os.TakeStr(), StrEq(help_flag_output));
|
||||
|
||||
EXPECT_THAT(parse({"--version"}, os), Eq(ParseResult::MetaSuccess));
|
||||
EXPECT_THAT(parse({"--version"}, os),
|
||||
IsSuccess(Eq(ParseResult::MetaSuccess)));
|
||||
std::string version_flag_output = os.TakeStr();
|
||||
EXPECT_THAT(version_flag_output, StrEq(llvm::StringRef(R"""(
|
||||
Test Command -- version 1.2.3
|
||||
@@ -801,21 +790,21 @@ Build timestamp: )""" __TIMESTAMP__ R"""(
|
||||
Build config: test-config-info
|
||||
)""")
|
||||
.ltrim('\n')));
|
||||
EXPECT_THAT(parse({"version"}, os), Eq(ParseResult::MetaSuccess));
|
||||
EXPECT_THAT(parse({"version"}, os), IsSuccess(Eq(ParseResult::MetaSuccess)));
|
||||
EXPECT_THAT(os.TakeStr(), StrEq(version_flag_output));
|
||||
|
||||
EXPECT_THAT(parse({"--flag", "edit", "--option=42", "--help"}, os),
|
||||
Eq(ParseResult::MetaSuccess));
|
||||
IsSuccess(Eq(ParseResult::MetaSuccess)));
|
||||
std::string edit_help_output = os.TakeStr();
|
||||
EXPECT_THAT(edit_help_output, StrEq(llvm::StringRef(R"""(
|
||||
Edit the widget.
|
||||
|
||||
This will take the provided widgets and edit them.
|
||||
|
||||
Subcommand 'edit' usage:
|
||||
Subcommand `edit` usage:
|
||||
test [-f] edit [--option=...]
|
||||
|
||||
Subcommand 'edit' options:
|
||||
Subcommand `edit` options:
|
||||
-o, --option=...
|
||||
An integer option.
|
||||
|
||||
@@ -831,21 +820,22 @@ That's all.
|
||||
)""")
|
||||
.ltrim('\n')));
|
||||
|
||||
EXPECT_THAT(parse({"help", "edit"}, os), Eq(ParseResult::MetaSuccess));
|
||||
EXPECT_THAT(parse({"help", "edit"}, os),
|
||||
IsSuccess(Eq(ParseResult::MetaSuccess)));
|
||||
EXPECT_THAT(os.TakeStr(), StrEq(edit_help_output));
|
||||
|
||||
EXPECT_THAT(parse({"--flag", "run", "--option=abc", "--help"}, os),
|
||||
Eq(ParseResult::MetaSuccess));
|
||||
IsSuccess(Eq(ParseResult::MetaSuccess)));
|
||||
std::string run_help_output = os.TakeStr();
|
||||
EXPECT_THAT(run_help_output, StrEq(llvm::StringRef(R"""(
|
||||
Run wombats across the screen.
|
||||
|
||||
This will cause several wombats to run across your screen.
|
||||
|
||||
Subcommand 'run' usage:
|
||||
Subcommand `run` usage:
|
||||
test [-f] run [OPTIONS]
|
||||
|
||||
Subcommand 'run' options:
|
||||
Subcommand `run` options:
|
||||
-o, --option=...
|
||||
A string option.
|
||||
|
||||
@@ -869,7 +859,8 @@ Or it won't, who knows.
|
||||
)""")
|
||||
.ltrim('\n')));
|
||||
|
||||
EXPECT_THAT(parse({"help", "run"}, os), Eq(ParseResult::MetaSuccess));
|
||||
EXPECT_THAT(parse({"help", "run"}, os),
|
||||
IsSuccess(Eq(ParseResult::MetaSuccess)));
|
||||
EXPECT_THAT(os.TakeStr(), StrEq(run_help_output));
|
||||
}
|
||||
|
||||
@@ -877,7 +868,7 @@ TEST(ArgParserTest, HelpMarkdownLike) {
|
||||
bool flag = false;
|
||||
auto parse = [&](llvm::ArrayRef<llvm::StringRef> args, llvm::raw_ostream& s) {
|
||||
return Parse( // Force line break.
|
||||
args, s, s, {.name = "test"}, [&](auto& b) {
|
||||
args, s, {.name = "test"}, [&](auto& b) {
|
||||
b.AddFlag(
|
||||
{
|
||||
.name = "flag",
|
||||
@@ -907,7 +898,7 @@ x
|
||||
};
|
||||
|
||||
TestRawOstream os;
|
||||
EXPECT_THAT(parse({"--help"}, os), Eq(ParseResult::MetaSuccess));
|
||||
EXPECT_THAT(parse({"--help"}, os), IsSuccess(Eq(ParseResult::MetaSuccess)));
|
||||
EXPECT_THAT(os.TakeStr(), StrEq(llvm::StringRef(R"""(
|
||||
Usage:
|
||||
test [--flag]
|
||||
|
||||
+3
-1
@@ -17,7 +17,9 @@ namespace Carbon {
|
||||
// Success values should be represented as the presence of a value in ErrorOr,
|
||||
// using `ErrorOr<Success>` and `return Success();` if no value needs to be
|
||||
// returned.
|
||||
struct Success {};
|
||||
struct Success : public Printable<Success> {
|
||||
void Print(llvm::raw_ostream& out) const { out << "Success"; }
|
||||
};
|
||||
|
||||
// Tracks an error message.
|
||||
//
|
||||
|
||||
+12
-11
@@ -6,11 +6,16 @@
|
||||
|
||||
#include <gtest/gtest.h>
|
||||
|
||||
#include "common/error_test_helpers.h"
|
||||
#include "testing/base/test_raw_ostream.h"
|
||||
|
||||
namespace Carbon {
|
||||
namespace {
|
||||
|
||||
using ::Carbon::Testing::IsError;
|
||||
using ::Carbon::Testing::IsSuccess;
|
||||
using ::testing::Eq;
|
||||
|
||||
TEST(ErrorTest, Error) {
|
||||
Error err("test");
|
||||
EXPECT_EQ(err.message(), "test");
|
||||
@@ -26,8 +31,8 @@ TEST(ErrorTest, IndirectError) { EXPECT_EQ(IndirectError().message(), "test"); }
|
||||
|
||||
TEST(ErrorTest, ErrorOr) {
|
||||
ErrorOr<int> err(Error("test"));
|
||||
EXPECT_FALSE(err.ok());
|
||||
EXPECT_EQ(err.error().message(), "test");
|
||||
|
||||
EXPECT_THAT(err, IsError("test"));
|
||||
}
|
||||
|
||||
TEST(ErrorTest, ErrorOrValue) { EXPECT_TRUE(ErrorOr<int>(0).ok()); }
|
||||
@@ -66,8 +71,7 @@ TEST(ErrorTest, ReturnIfErrorHasError) {
|
||||
CARBON_RETURN_IF_ERROR(ErrorOr<Success>(Error("error")));
|
||||
return Success();
|
||||
}();
|
||||
ASSERT_FALSE(result.ok());
|
||||
EXPECT_EQ(result.error().message(), "error");
|
||||
EXPECT_THAT(result, IsError("error"));
|
||||
}
|
||||
|
||||
TEST(ErrorTest, AssignOrReturnNoError) {
|
||||
@@ -78,8 +82,7 @@ TEST(ErrorTest, AssignOrReturnNoError) {
|
||||
CARBON_ASSIGN_OR_RETURN(c, ErrorOr<int>(3));
|
||||
return a + b + c;
|
||||
}();
|
||||
ASSERT_TRUE(result.ok());
|
||||
EXPECT_EQ(6, *result);
|
||||
EXPECT_THAT(result, IsSuccess(Eq(6)));
|
||||
}
|
||||
|
||||
TEST(ErrorTest, AssignOrReturnHasDirectError) {
|
||||
@@ -87,7 +90,7 @@ TEST(ErrorTest, AssignOrReturnHasDirectError) {
|
||||
CARBON_RETURN_IF_ERROR(ErrorOr<int>(Error("error")));
|
||||
return 0;
|
||||
}();
|
||||
ASSERT_FALSE(result.ok());
|
||||
EXPECT_THAT(result, IsError("error"));
|
||||
}
|
||||
|
||||
TEST(ErrorTest, AssignOrReturnHasErrorInExpected) {
|
||||
@@ -95,14 +98,12 @@ TEST(ErrorTest, AssignOrReturnHasErrorInExpected) {
|
||||
CARBON_ASSIGN_OR_RETURN(int a, ErrorOr<int>(Error("error")));
|
||||
return a;
|
||||
}();
|
||||
ASSERT_FALSE(result.ok());
|
||||
EXPECT_EQ(result.error().message(), "error");
|
||||
EXPECT_THAT(result, IsError("error"));
|
||||
}
|
||||
|
||||
TEST(ErrorTest, ErrorBuilderOperatorImplicitCast) {
|
||||
ErrorOr<int> result = ErrorBuilder() << "msg";
|
||||
ASSERT_FALSE(result.ok());
|
||||
EXPECT_EQ(result.error().message(), "msg");
|
||||
EXPECT_THAT(result, IsError("msg"));
|
||||
}
|
||||
|
||||
TEST(ErrorTest, StreamError) {
|
||||
|
||||
@@ -0,0 +1,111 @@
|
||||
// Part of the Carbon Language project, under the Apache License v2.0 with LLVM
|
||||
// Exceptions. See /LICENSE for license information.
|
||||
// SPDX-License-Identifier: Apache-2.0 WITH LLVM-exception
|
||||
|
||||
#ifndef CARBON_COMMON_ERROR_TEST_HELPERS_H_
|
||||
#define CARBON_COMMON_ERROR_TEST_HELPERS_H_
|
||||
|
||||
#include <gmock/gmock.h>
|
||||
|
||||
#include "common/error.h"
|
||||
|
||||
namespace Carbon::Testing {
|
||||
|
||||
// Matches the message for an error state of `ErrorOr<T>`. For example:
|
||||
// EXPECT_THAT(my_result, IsError(StrEq("error message")));
|
||||
class IsError {
|
||||
public:
|
||||
// NOLINTNEXTLINE(readability-identifier-naming)
|
||||
using is_gtest_matcher = void;
|
||||
|
||||
explicit IsError(::testing::Matcher<std::string> matcher)
|
||||
: matcher_(std::move(matcher)) {}
|
||||
|
||||
template <typename T>
|
||||
auto MatchAndExplain(const ErrorOr<T>& result,
|
||||
::testing::MatchResultListener* listener) const -> bool {
|
||||
if (result.ok()) {
|
||||
*listener->stream() << "is a success";
|
||||
return false;
|
||||
} else {
|
||||
return matcher_.MatchAndExplain(result.error().message(), listener);
|
||||
}
|
||||
}
|
||||
|
||||
auto DescribeTo(std::ostream* os) const -> void {
|
||||
*os << "is an error and matches ";
|
||||
matcher_.DescribeTo(os);
|
||||
}
|
||||
|
||||
auto DescribeNegationTo(std::ostream* os) const -> void {
|
||||
*os << "is a success or does not match ";
|
||||
matcher_.DescribeTo(os);
|
||||
}
|
||||
|
||||
private:
|
||||
::testing::Matcher<std::string> matcher_;
|
||||
};
|
||||
|
||||
// Matches the value for a non-error state of `ErrorOr<T>`. For example:
|
||||
// EXPECT_THAT(my_result, IsSuccess(Eq(3)));
|
||||
template <typename InnerMatcher>
|
||||
class IsSuccessMatcher {
|
||||
public:
|
||||
// NOLINTNEXTLINE(readability-identifier-naming)
|
||||
using is_gtest_matcher = void;
|
||||
|
||||
explicit IsSuccessMatcher(InnerMatcher matcher)
|
||||
: matcher_(std::move(matcher)) {}
|
||||
|
||||
template <typename T>
|
||||
auto MatchAndExplain(const ErrorOr<T>& result,
|
||||
::testing::MatchResultListener* listener) const -> bool {
|
||||
if (result.ok()) {
|
||||
return ::testing::Matcher<T>(matcher_).MatchAndExplain(*result, listener);
|
||||
} else {
|
||||
*listener->stream() << "is an error with `" << result.error().message()
|
||||
<< "`";
|
||||
return false;
|
||||
}
|
||||
}
|
||||
|
||||
auto DescribeTo(std::ostream* os) const -> void {
|
||||
*os << "is a success and matches ";
|
||||
matcher_.DescribeTo(os);
|
||||
}
|
||||
|
||||
auto DescribeNegationTo(std::ostream* os) const -> void {
|
||||
*os << "is an error or does not match ";
|
||||
matcher_.DescribeTo(os);
|
||||
}
|
||||
|
||||
private:
|
||||
InnerMatcher matcher_;
|
||||
};
|
||||
|
||||
// Wraps `IsSuccessMatcher` for the inner matcher deduction.
|
||||
template <typename InnerMatcher>
|
||||
auto IsSuccess(InnerMatcher matcher) -> IsSuccessMatcher<InnerMatcher> {
|
||||
return IsSuccessMatcher<InnerMatcher>(matcher);
|
||||
}
|
||||
|
||||
} // namespace Carbon::Testing
|
||||
|
||||
namespace Carbon {
|
||||
|
||||
// Supports printing `ErrorOr<T>` to `std::ostream` in tests.
|
||||
template <typename T>
|
||||
auto operator<<(std::ostream& out, const ErrorOr<T>& error_or)
|
||||
-> std::ostream& {
|
||||
if (error_or.ok()) {
|
||||
out << llvm::formatv("ErrorOr{{.value = `{0}`}}", *error_or);
|
||||
} else {
|
||||
out << llvm::formatv("ErrorOr{{.error = \"{0}\"}}",
|
||||
error_or.error().message());
|
||||
}
|
||||
return out;
|
||||
}
|
||||
|
||||
} // namespace Carbon
|
||||
|
||||
#endif // CARBON_COMMON_ERROR_TEST_HELPERS_H_
|
||||
@@ -54,9 +54,8 @@ auto Run(llvm::ArrayRef<llvm::StringRef> args) -> bool {
|
||||
int lines = 10'000;
|
||||
SourceGen::Language language;
|
||||
|
||||
CommandLine::ParseResult parsed_args = CommandLine::Parse(
|
||||
args, llvm::outs(), llvm::errs(), Info,
|
||||
[&](CommandLine::CommandBuilder& b) {
|
||||
auto parse_result = CommandLine::Parse(
|
||||
args, llvm::outs(), Info, [&](CommandLine::CommandBuilder& b) {
|
||||
b.AddStringOption(OutputArgInfo,
|
||||
[&](auto& arg_b) { arg_b.Set(&output_filename); });
|
||||
b.AddIntegerOption(LinesArgInfo,
|
||||
@@ -74,9 +73,10 @@ auto Run(llvm::ArrayRef<llvm::StringRef> args) -> bool {
|
||||
// No-op action as there is only one operation for this command.
|
||||
b.Do([] {});
|
||||
});
|
||||
if (parsed_args == CommandLine::ParseResult::Error) {
|
||||
if (!parse_result.ok()) {
|
||||
llvm::errs() << "error: " << *parse_result << "\n";
|
||||
return false;
|
||||
} else if (parsed_args == CommandLine::ParseResult::MetaSuccess) {
|
||||
} else if (*parse_result == CommandLine::ParseResult::MetaSuccess) {
|
||||
// Fully handled by the CLI library.
|
||||
return true;
|
||||
}
|
||||
|
||||
@@ -84,19 +84,21 @@ auto Options::Build(CommandLine::CommandBuilder& b) -> void {
|
||||
|
||||
auto Driver::RunCommand(llvm::ArrayRef<llvm::StringRef> args) -> DriverResult {
|
||||
if (driver_env_.installation->error()) {
|
||||
llvm::errs() << "error: " << *driver_env_.installation->error() << "\n";
|
||||
driver_env_.error_stream << "error: " << *driver_env_.installation->error()
|
||||
<< "\n";
|
||||
return {.success = false};
|
||||
}
|
||||
|
||||
Options options;
|
||||
|
||||
CommandLine::ParseResult result = CommandLine::Parse(
|
||||
args, driver_env_.output_stream, driver_env_.error_stream, Options::Info,
|
||||
ErrorOr<CommandLine::ParseResult> result = CommandLine::Parse(
|
||||
args, driver_env_.output_stream, Options::Info,
|
||||
[&](CommandLine::CommandBuilder& b) { options.Build(b); });
|
||||
|
||||
if (result == CommandLine::ParseResult::Error) {
|
||||
if (!result.ok()) {
|
||||
driver_env_.error_stream << "error: " << result.error() << "\n";
|
||||
return {.success = false};
|
||||
} else if (result == CommandLine::ParseResult::MetaSuccess) {
|
||||
} else if (*result == CommandLine::ParseResult::MetaSuccess) {
|
||||
return {.success = true};
|
||||
}
|
||||
|
||||
|
||||
@@ -105,13 +105,13 @@ class DriverTest : public testing::Test {
|
||||
|
||||
TEST_F(DriverTest, BadCommandErrors) {
|
||||
EXPECT_FALSE(driver_.RunCommand({}).success);
|
||||
EXPECT_THAT(test_error_stream_.TakeStr(), HasSubstr("ERROR"));
|
||||
EXPECT_THAT(test_error_stream_.TakeStr(), HasSubstr("error:"));
|
||||
|
||||
EXPECT_FALSE(driver_.RunCommand({"foo"}).success);
|
||||
EXPECT_THAT(test_error_stream_.TakeStr(), HasSubstr("ERROR"));
|
||||
EXPECT_THAT(test_error_stream_.TakeStr(), HasSubstr("error:"));
|
||||
|
||||
EXPECT_FALSE(driver_.RunCommand({"foo --bar --baz"}).success);
|
||||
EXPECT_THAT(test_error_stream_.TakeStr(), HasSubstr("ERROR"));
|
||||
EXPECT_THAT(test_error_stream_.TakeStr(), HasSubstr("error:"));
|
||||
}
|
||||
|
||||
TEST_F(DriverTest, CompileCommandErrors) {
|
||||
@@ -119,8 +119,8 @@ TEST_F(DriverTest, CompileCommandErrors) {
|
||||
EXPECT_FALSE(driver_.RunCommand({"compile"}).success);
|
||||
EXPECT_THAT(
|
||||
test_error_stream_.TakeStr(),
|
||||
StrEq("ERROR: Not all required positional arguments were provided. First "
|
||||
"missing and required positional argument: 'FILE'\n"));
|
||||
StrEq("error: not all required positional arguments were provided; first "
|
||||
"missing and required positional argument: `FILE`\n"));
|
||||
|
||||
// Invalid output filename. No reliably error message here.
|
||||
// TODO: Likely want a different filename on Windows.
|
||||
|
||||
+12
@@ -0,0 +1,12 @@
|
||||
// Part of the Carbon Language project, under the Apache License v2.0 with LLVM
|
||||
// Exceptions. See /LICENSE for license information.
|
||||
// SPDX-License-Identifier: Apache-2.0 WITH LLVM-exception
|
||||
//
|
||||
// ARGS: compile --non-existent-flag
|
||||
//
|
||||
// AUTOUPDATE
|
||||
// TIP: To test this file alone, run:
|
||||
// TIP: bazel test //toolchain/testing:file_test --test_arg=--file_tests=toolchain/driver/testdata/fail_flag.carbon
|
||||
// TIP: To dump output, run:
|
||||
// TIP: bazel run //toolchain/testing:file_test -- --dump_output --file_tests=toolchain/driver/testdata/fail_flag.carbon
|
||||
// CHECK:STDERR: error: unknown option `--non-existent-flag`
|
||||
Reference in New Issue
Block a user