Command.cpp (25740B)
1 // Copyright (c) Microsoft Corporation. 2 // Licensed under the MIT License. 3 #include "pch.h" 4 #include "TestCommon.h" 5 #include <Command.h> 6 #include <AppInstallerStrings.h> 7 #include <Commands/RootCommand.h> 8 9 using namespace std::string_literals; 10 using namespace std::string_view_literals; 11 using namespace TestCommon; 12 using namespace AppInstaller; 13 using namespace AppInstaller::CLI; 14 using namespace AppInstaller::CLI::Execution; 15 16 std::string GetCommandName(const std::unique_ptr<Command>& command) 17 { 18 return std::string{ command->FullName() }; 19 } 20 21 std::vector<std::string_view> GetCommandAliases(const std::unique_ptr<Command>& command) 22 { 23 return std::vector<std::string_view> { command->Aliases() }; 24 } 25 26 std::string GetArgumentName(const Argument& arg) 27 { 28 return std::string{ arg.Name() }; 29 } 30 31 std::string GetArgumentAlternateName(const Argument& arg) 32 { 33 if (arg.AlternateName() == Argument::NoAlternateName) 34 { 35 return {}; 36 } 37 else 38 { 39 return std::string{ arg.AlternateName() }; 40 } 41 } 42 43 std::string GetArgumentAlias(const Argument& arg) 44 { 45 if (arg.Alias() == ArgumentCommon::NoAlias) 46 { 47 return {}; 48 } 49 else 50 { 51 return std::string(1, arg.Alias()); 52 } 53 } 54 55 bool StringIsLowercase(const std::string& s) 56 { 57 return Utility::ToLower(s) == s; 58 } 59 60 template <typename Enumerable, typename Op> 61 void EnsureStringsAreLowercaseAndNoCollisions(const std::string& info, const Enumerable& e, Op& op, std::unordered_set<std::string>& values, bool requireLower = true) 62 { 63 INFO(info); 64 65 for (const auto& val : e) 66 { 67 std::string valString = op(val); 68 if (valString.empty()) 69 { 70 continue; 71 } 72 INFO(valString); 73 74 if (requireLower) 75 { 76 REQUIRE(StringIsLowercase(valString)); 77 } 78 79 REQUIRE(values.find(valString) == values.end()); 80 81 values.emplace(std::move(valString)); 82 } 83 } 84 85 template <typename Enumerable, typename Op> 86 void EnsureStringsAreLowercaseAndNoCollisions(const std::string& info, const Enumerable& e, Op& op, bool requireLower = true) 87 { 88 std::unordered_set<std::string> values; 89 EnsureStringsAreLowercaseAndNoCollisions(info, e, op, values, requireLower); 90 } 91 92 template <typename Enumerable, typename Op> 93 void EnsureVectorStringViewsAreLowercaseAndNoCollisions(const std::string& info, const Enumerable& e, Op& op, std::unordered_set<std::string>& values, bool requireLower = true) 94 { 95 INFO(info); 96 97 for (const auto& val : e) 98 { 99 std::vector<std::string_view> aliasVector = op(val); 100 std::vector<std::string> valVector(aliasVector.begin(), aliasVector.end()); 101 if (valVector.empty()) 102 { 103 continue; 104 } 105 // When op returns a vector, we need to ensure every value in the vector does not cause a collision 106 for (auto& valString : valVector) 107 { 108 INFO(valString); 109 110 if (requireLower) 111 { 112 REQUIRE(StringIsLowercase(valString)); 113 } 114 115 REQUIRE(values.find(valString) == values.end()); 116 values.emplace(std::move(valString)); 117 } 118 } 119 } 120 121 void EnsureCommandConsistency(const Command& command) 122 { 123 // Command names and aliases exist in the same space, so both need to be checked as a set 124 // However, collisions do not occur between levels, so the full name must be used to check for collision 125 std::unordered_set<std::string> allCommandAliasNames; 126 EnsureStringsAreLowercaseAndNoCollisions(command.FullName() + " commands", command.GetCommands(), GetCommandName, allCommandAliasNames); 127 EnsureVectorStringViewsAreLowercaseAndNoCollisions(command.FullName() + " aliases", command.GetCommands(), GetCommandAliases, allCommandAliasNames); 128 129 auto args = command.GetArguments(); 130 Argument::GetCommon(args); 131 132 // Argument names and alternate names exist in the same space, so both need to be checked as a set 133 std::unordered_set<std::string> allArgumentNames; 134 EnsureStringsAreLowercaseAndNoCollisions(command.FullName() + " argument names", args, GetArgumentName, allArgumentNames); 135 EnsureStringsAreLowercaseAndNoCollisions(command.FullName() + " argument alternate name", args, GetArgumentAlternateName, allArgumentNames); 136 // Argument aliases use a different space than the names and can be checked separately 137 EnsureStringsAreLowercaseAndNoCollisions(command.FullName() + " argument alias", args, GetArgumentAlias, false); 138 139 // No : allowed in commands 140 for (const auto& comm : command.GetCommands()) 141 { 142 INFO(command.FullName()); 143 INFO(comm->Name()); 144 145 REQUIRE(comm->Name().find_first_of(Command::ParentSplitChar) == std::string_view::npos); 146 } 147 148 // No = allowed in arguments 149 // All positional args should be listed first 150 bool foundNonPositional = false; 151 bool queryArgPresent = false; 152 bool multiQueryArgPresent = false; 153 for (const auto& arg : command.GetArguments()) 154 { 155 INFO(command.FullName()); 156 INFO(arg.Name()); 157 158 REQUIRE(arg.Name().find_first_of(APPINSTALLER_CLI_ARGUMENT_SPLIT_CHAR) == std::string_view::npos); 159 160 if (arg.Type() == ArgumentType::Positional) 161 { 162 REQUIRE(!foundNonPositional); 163 } 164 else 165 { 166 foundNonPositional = true; 167 } 168 169 if (arg.ExecArgType() == Execution::Args::Type::Query) 170 { 171 queryArgPresent = true; 172 } 173 174 if (arg.ExecArgType() == Execution::Args::Type::MultiQuery) 175 { 176 multiQueryArgPresent = true; 177 } 178 } 179 180 REQUIRE((!queryArgPresent || !multiQueryArgPresent)); 181 182 // Recurse for all subcommands 183 for (const auto& sub : command.GetCommands()) 184 { 185 EnsureCommandConsistency(*sub.get()); 186 } 187 } 188 189 // This test ensure that the command tree we expose does not have any inconsistencies. 190 // 1. No command name collisions 191 // 2. All command names are lower cased 192 // 3. No argument name collisions 193 // 4. All arguments are lower cased 194 // 5. No argument alias collisions 195 // 6. All argument alias are lower cased 196 // 7. No argument names contain '=' 197 // 8. All positional arguments are first in the list 198 // 9. No command includes both Query and MultiQuery arguments 199 TEST_CASE("EnsureCommandTreeConsistency", "[command]") 200 { 201 RootCommand root; 202 EnsureCommandConsistency(root); 203 } 204 205 struct TestCommand : public Command 206 { 207 TestCommand(std::vector<Argument> args) : Command("test", ""), m_args(std::move(args)) {} 208 209 std::vector<Argument> GetArguments() const override 210 { 211 return m_args; 212 } 213 214 CLI::Resource::LocString ShortDescription() const override { return {}; } 215 CLI::Resource::LocString LongDescription() const override { return {}; } 216 217 std::vector<Argument> m_args; 218 }; 219 220 // Matcher that lets us verify CommandExceptions. 221 struct CommandExceptionMatcher : public Catch::Matchers::MatcherBase<CommandException> 222 { 223 CommandExceptionMatcher(CLI::Resource::LocString message) : m_expectedMessage(std::move(message)) {} 224 225 bool match(const CommandException& ce) const override 226 { 227 return ce.Message() == m_expectedMessage; 228 } 229 230 std::string describe() const override 231 { 232 std::ostringstream result; 233 result << "has message == " << m_expectedMessage; 234 return result.str(); 235 } 236 237 private: 238 CLI::Resource::LocString m_expectedMessage; 239 }; 240 241 namespace Catch { 242 template<> 243 struct StringMaker<CommandException> { 244 static std::string convert(CommandException const& ce) { 245 return Utility::Format("CommandException{ '{0}' }", ce.Message().get()); 246 } 247 }; 248 } 249 250 #define REQUIRE_COMMAND_EXCEPTION(_expr_, _arg_) REQUIRE_THROWS_MATCHES(_expr_, CommandException, CommandExceptionMatcher(_arg_)) 251 252 void RequireValueParsedToArg(const std::string& value, const Argument& arg, const Args& args) 253 { 254 REQUIRE(args.Contains(arg.ExecArgType())); 255 REQUIRE(value == args.GetArg(arg.ExecArgType())); 256 } 257 258 void RequireValuesParsedToArg(const std::vector<std::string>& values, Args::Type execArgType, const Args& args) 259 { 260 REQUIRE(args.Contains(execArgType)); 261 REQUIRE(args.GetCount(execArgType) == values.size()); 262 263 auto argValues = args.GetArgs(execArgType); 264 for (size_t i = 0; i < values.size(); ++i) 265 { 266 REQUIRE(argValues->at(i) == values[i]); 267 } 268 } 269 270 void RequireValuesParsedToArg(const std::vector<std::string>& values, const Argument& arg, const Args& args) 271 { 272 RequireValuesParsedToArg(values, arg.ExecArgType(), args); 273 } 274 275 // Description used for tests; doesn't need to be anything in particular. 276 static constexpr CLI::Resource::StringId DefaultDesc{ L""sv }; 277 278 TEST_CASE("ParseArguments_MultiplePositional", "[command]") 279 { 280 Args args; 281 TestCommand command({ 282 Argument{ "pos1", 'p', Args::Type::Channel, DefaultDesc, ArgumentType::Positional }, 283 Argument{ "std1", 's', Args::Type::Command, DefaultDesc, ArgumentType::Standard }, 284 Argument{ "pos2", 'q', Args::Type::Count, DefaultDesc, ArgumentType::Positional }, 285 }); 286 287 std::vector<std::string> values{ "val1", "val2" }; 288 Invocation inv{ std::vector<std::string>(values) }; 289 290 command.ParseArguments(inv, args); 291 292 RequireValueParsedToArg(values[0], command.m_args[0], args); 293 RequireValueParsedToArg(values[1], command.m_args[2], args); 294 } 295 296 TEST_CASE("ParseArguments_ForcePositional", "[command]") 297 { 298 Args args; 299 TestCommand command({ 300 Argument{ "pos1", 'p', Args::Type::Channel, DefaultDesc, ArgumentType::Positional }, 301 Argument{ "std1", 's', Args::Type::Command, DefaultDesc, ArgumentType::Standard }, 302 Argument{ "pos2", 'q', Args::Type::Count, DefaultDesc, ArgumentType::Positional }, 303 }); 304 305 std::vector<std::string> values{ "val1", "--", "-std1" }; 306 Invocation inv{ std::vector<std::string>(values) }; 307 308 command.ParseArguments(inv, args); 309 310 RequireValueParsedToArg(values[0], command.m_args[0], args); 311 RequireValueParsedToArg(values[2], command.m_args[2], args); 312 } 313 314 TEST_CASE("ParseArguments_TooManyPositional", "[command]") 315 { 316 Args args; 317 TestCommand command({ 318 Argument{ "pos1", 'p', Args::Type::Channel, DefaultDesc, ArgumentType::Positional }, 319 Argument{ "std1", 's', Args::Type::Command, DefaultDesc, ArgumentType::Standard }, 320 }); 321 322 std::vector<std::string> values{ "val1", "--", "-std1" }; 323 Invocation inv{ std::vector<std::string>(values) }; 324 325 REQUIRE_COMMAND_EXCEPTION(command.ParseArguments(inv, args), CLI::Resource::String::ExtraPositionalError(Utility::LocIndView{ values[2] })); 326 } 327 328 TEST_CASE("ParseArguments_InvalidChar", "[command]") 329 { 330 Args args; 331 TestCommand command({ 332 Argument{ "pos1", 'p', Args::Type::Channel, DefaultDesc, ArgumentType::Positional }, 333 Argument{ "std1", 's', Args::Type::Command, DefaultDesc, ArgumentType::Standard }, 334 Argument{ "pos2", 'q', Args::Type::Count, DefaultDesc, ArgumentType::Positional }, 335 }); 336 337 std::vector<std::string> values{ "val1", "-", "-std1" }; 338 Invocation inv{ std::vector<std::string>(values) }; 339 340 REQUIRE_COMMAND_EXCEPTION(command.ParseArguments(inv, args), CLI::Resource::String::InvalidArgumentSpecifierError(Utility::LocIndView{ values[1] })); 341 } 342 343 TEST_CASE("ParseArguments_InvalidAlias", "[command]") 344 { 345 Args args; 346 TestCommand command({ 347 Argument{ "pos1", 'p', Args::Type::Channel, DefaultDesc, ArgumentType::Positional }, 348 Argument{ "std1", 's', Args::Type::Command, DefaultDesc, ArgumentType::Standard }, 349 Argument{ "pos2", 'q', Args::Type::Count, DefaultDesc, ArgumentType::Positional }, 350 }); 351 352 std::vector<std::string> values{ "val1", "-b", "-std1" }; 353 Invocation inv{ std::vector<std::string>(values) }; 354 355 REQUIRE_COMMAND_EXCEPTION(command.ParseArguments(inv, args), CLI::Resource::String::InvalidAliasError(Utility::LocIndView{ values[1] })); 356 } 357 358 TEST_CASE("ParseArguments_MultiFlag", "[command]") 359 { 360 Args args; 361 TestCommand command({ 362 Argument{ "pos1", 'p', Args::Type::Channel, DefaultDesc, ArgumentType::Positional }, 363 Argument{ "flag1", 's', Args::Type::Command, DefaultDesc, ArgumentType::Flag }, 364 Argument{ "pos2", 'q', Args::Type::Count, DefaultDesc, ArgumentType::Positional }, 365 Argument{ "flag2", 't', Args::Type::Exact, DefaultDesc, ArgumentType::Flag }, 366 }); 367 368 std::vector<std::string> values{ "val1", "-st", "val2" }; 369 Invocation inv{ std::vector<std::string>(values) }; 370 371 command.ParseArguments(inv, args); 372 373 REQUIRE(args.Contains(command.m_args[1].ExecArgType())); 374 REQUIRE(args.Contains(command.m_args[3].ExecArgType())); 375 } 376 377 TEST_CASE("ParseArguments_FlagThenUnknown", "[command]") 378 { 379 Args args; 380 TestCommand command({ 381 Argument{ "pos1", 'p', Args::Type::Channel, DefaultDesc, ArgumentType::Positional }, 382 Argument{ "flag1", 's', Args::Type::Command, DefaultDesc, ArgumentType::Flag }, 383 Argument{ "pos2", 'q', Args::Type::Count, DefaultDesc, ArgumentType::Positional }, 384 Argument{ "flag2", 't', Args::Type::Exact, DefaultDesc, ArgumentType::Flag }, 385 }); 386 387 std::vector<std::string> values{ "val1", "-sr", "val2" }; 388 Invocation inv{ std::vector<std::string>(values) }; 389 390 REQUIRE_COMMAND_EXCEPTION(command.ParseArguments(inv, args), CLI::Resource::String::AdjoinedNotFoundError(Utility::LocIndView{ values[1] })); 391 } 392 393 TEST_CASE("ParseArguments_FlagThenNonFlag", "[command]") 394 { 395 Args args; 396 TestCommand command({ 397 Argument{ "pos1", 'p', Args::Type::Channel, DefaultDesc, ArgumentType::Positional }, 398 Argument{ "flag1", 's', Args::Type::Command, DefaultDesc, ArgumentType::Flag }, 399 Argument{ "pos2", 'q', Args::Type::Count, DefaultDesc, ArgumentType::Positional }, 400 Argument{ "flag2", 't', Args::Type::Exact, DefaultDesc, ArgumentType::Flag }, 401 }); 402 403 std::vector<std::string> values{ "val1", "-sp", "val2" }; 404 Invocation inv{ std::vector<std::string>(values) }; 405 406 REQUIRE_COMMAND_EXCEPTION(command.ParseArguments(inv, args), CLI::Resource::String::AdjoinedNotFlagError(Utility::LocIndView{ values[1] })); 407 } 408 409 TEST_CASE("ParseArguments_NameUsingAliasSpecifier", "[command]") 410 { 411 Args args; 412 TestCommand command({ 413 Argument{ "pos1", 'p', Args::Type::Channel, DefaultDesc, ArgumentType::Positional }, 414 Argument{ "std1", 's', Args::Type::Command, DefaultDesc, ArgumentType::Standard }, 415 Argument{ "pos2", 'q', Args::Type::Count, DefaultDesc, ArgumentType::Positional }, 416 Argument{ "flag1", 'f', Args::Type::Exact, DefaultDesc, ArgumentType::Flag }, 417 }); 418 419 std::vector<std::string> values{ "another", "-flag1" }; 420 Invocation inv{ std::vector<std::string>(values) }; 421 422 REQUIRE_COMMAND_EXCEPTION(command.ParseArguments(inv, args), CLI::Resource::String::AdjoinedNotFoundError(Utility::LocIndView{ values[1] })); 423 } 424 425 TEST_CASE("ParseArguments_AliasWithAdjoinedValue", "[command]") 426 { 427 Args args; 428 TestCommand command({ 429 Argument{ "pos1", 'p', Args::Type::Channel, DefaultDesc, ArgumentType::Positional }, 430 Argument{ "std1", 's', Args::Type::Command, DefaultDesc, ArgumentType::Standard }, 431 Argument{ "pos2", 'q', Args::Type::Count, DefaultDesc, ArgumentType::Positional }, 432 }); 433 434 std::vector<std::string> values{ "-s=Val1" }; 435 Invocation inv{ std::vector<std::string>(values) }; 436 437 command.ParseArguments(inv, args); 438 439 RequireValueParsedToArg(values[0].substr(3), command.m_args[1], args); 440 } 441 442 TEST_CASE("ParseArguments_AliasWithSeparatedValue", "[command]") 443 { 444 Args args; 445 TestCommand command({ 446 Argument{ "pos1", 'p', Args::Type::Channel, DefaultDesc, ArgumentType::Positional }, 447 Argument{ "std1", 's', Args::Type::Command, DefaultDesc, ArgumentType::Standard }, 448 Argument{ "pos2", 'q', Args::Type::Count, DefaultDesc, ArgumentType::Positional }, 449 }); 450 451 std::vector<std::string> values{ "-s", "Val1" }; 452 Invocation inv{ std::vector<std::string>(values) }; 453 454 command.ParseArguments(inv, args); 455 456 RequireValueParsedToArg(values[1], command.m_args[1], args); 457 } 458 459 TEST_CASE("ParseArguments_AliasWithSeparatedValueMissing", "[command]") 460 { 461 Args args; 462 TestCommand command({ 463 Argument{ "pos1", 'p', Args::Type::Channel, DefaultDesc, ArgumentType::Positional }, 464 Argument{ "std1", 's', Args::Type::Command, DefaultDesc, ArgumentType::Standard }, 465 Argument{ "pos2", 'q', Args::Type::Count, DefaultDesc, ArgumentType::Positional }, 466 }); 467 468 std::vector<std::string> values{ "-s" }; 469 Invocation inv{ std::vector<std::string>(values) }; 470 471 REQUIRE_COMMAND_EXCEPTION(command.ParseArguments(inv, args), CLI::Resource::String::MissingArgumentError(Utility::LocIndView{ values[0] })); 472 } 473 474 TEST_CASE("ParseArguments_NameWithAdjoinedValue", "[command]") 475 { 476 Args args; 477 TestCommand command({ 478 Argument{ "pos1", 'p', Args::Type::Channel, DefaultDesc, ArgumentType::Positional }, 479 Argument{ "std1", 's', Args::Type::Command, DefaultDesc, ArgumentType::Standard }, 480 Argument{ "pos2", 'q', Args::Type::Count, DefaultDesc, ArgumentType::Positional }, 481 }); 482 483 std::vector<std::string> values{ "--pos1=Val1" }; 484 Invocation inv{ std::vector<std::string>(values) }; 485 486 command.ParseArguments(inv, args); 487 488 RequireValueParsedToArg(values[0].substr(7), command.m_args[0], args); 489 } 490 491 TEST_CASE("ParseArguments_AlternateNameWithAdjoinedValue", "[command]") 492 { 493 Args args; 494 TestCommand command({ 495 Argument{ "pos1", 'p', "p1", Args::Type::Channel, DefaultDesc, ArgumentType::Positional}, 496 Argument{ "std1", 's', Args::Type::Command, DefaultDesc, ArgumentType::Standard }, 497 Argument{ "pos2", 'q', Args::Type::Count, DefaultDesc, ArgumentType::Positional }, 498 }); 499 500 std::vector<std::string> values{ "--p1=Val1" }; 501 Invocation inv{ std::vector<std::string>(values) }; 502 503 command.ParseArguments(inv, args); 504 505 RequireValueParsedToArg(values[0].substr(5), command.m_args[0], args); 506 } 507 508 TEST_CASE("ParseArguments_NameFlag", "[command]") 509 { 510 Args args; 511 TestCommand command({ 512 Argument{ "pos1", 'p', Args::Type::Channel, DefaultDesc, ArgumentType::Positional }, 513 Argument{ "std1", 's', Args::Type::Command, DefaultDesc, ArgumentType::Standard }, 514 Argument{ "pos2", 'q', Args::Type::Count, DefaultDesc, ArgumentType::Positional }, 515 Argument{ "flag1", 'f', Args::Type::Exact, DefaultDesc, ArgumentType::Flag }, 516 }); 517 518 std::vector<std::string> values{ "--flag1", "arbitrary" }; 519 Invocation inv{ std::vector<std::string>(values) }; 520 521 command.ParseArguments(inv, args); 522 523 RequireValueParsedToArg(values[1], command.m_args[0], args); 524 REQUIRE(args.Contains(command.m_args[3].ExecArgType())); 525 } 526 527 TEST_CASE("ParseArguments_NameFlagWithAdjoinedValue", "[command]") 528 { 529 Args args; 530 TestCommand command({ 531 Argument{ "pos1", 'p', Args::Type::Channel, DefaultDesc, ArgumentType::Positional }, 532 Argument{ "std1", 's', Args::Type::Command, DefaultDesc, ArgumentType::Standard }, 533 Argument{ "pos2", 'q', Args::Type::Count, DefaultDesc, ArgumentType::Positional }, 534 Argument{ "flag1", 'f', Args::Type::Exact, DefaultDesc, ArgumentType::Flag }, 535 }); 536 537 std::vector<std::string> values{ "another", "--flag1=arbitrary" }; 538 Invocation inv{ std::vector<std::string>(values) }; 539 540 REQUIRE_COMMAND_EXCEPTION(command.ParseArguments(inv, args), CLI::Resource::String::FlagContainAdjoinedError(Utility::LocIndView{ values[1] })); 541 } 542 543 TEST_CASE("ParseArguments_NameWithSeparatedValue", "[command]") 544 { 545 Args args; 546 TestCommand command({ 547 Argument{ "pos1", 'p', Args::Type::Channel, DefaultDesc, ArgumentType::Positional }, 548 Argument{ "std1", 's', Args::Type::Command, DefaultDesc, ArgumentType::Standard }, 549 Argument{ "pos2", 'q', Args::Type::Count, DefaultDesc, ArgumentType::Positional }, 550 Argument{ "flag1", 'f', Args::Type::Exact, DefaultDesc, ArgumentType::Flag }, 551 }); 552 553 std::vector<std::string> values{ "--pos2", "arbitrary" }; 554 Invocation inv{ std::vector<std::string>(values) }; 555 556 command.ParseArguments(inv, args); 557 558 RequireValueParsedToArg(values[1], command.m_args[2], args); 559 } 560 561 TEST_CASE("ParseArguments_NameWithSeparatedValueMissing", "[command]") 562 { 563 Args args; 564 TestCommand command({ 565 Argument{ "pos1", 'p', Args::Type::Channel, DefaultDesc, ArgumentType::Positional }, 566 Argument{ "std1", 's', Args::Type::Command, DefaultDesc, ArgumentType::Standard }, 567 Argument{ "pos2", 'q', Args::Type::Count, DefaultDesc, ArgumentType::Positional }, 568 Argument{ "flag1", 'f', Args::Type::Exact, DefaultDesc, ArgumentType::Flag }, 569 }); 570 571 std::vector<std::string> values{ "--pos2" }; 572 Invocation inv{ std::vector<std::string>(values) }; 573 574 REQUIRE_COMMAND_EXCEPTION(command.ParseArguments(inv, args), CLI::Resource::String::MissingArgumentError(Utility::LocIndView{ values[0] })); 575 } 576 577 TEST_CASE("ParseArguments_UnknownName", "[command]") 578 { 579 Args args; 580 TestCommand command({ 581 Argument{ "pos1", 'p', Args::Type::Channel, DefaultDesc, ArgumentType::Positional }, 582 Argument{ "std1", 's', Args::Type::Command, DefaultDesc, ArgumentType::Standard }, 583 Argument{ "pos2", 'q', Args::Type::Count, DefaultDesc, ArgumentType::Positional }, 584 Argument{ "flag1", 'f', Args::Type::Exact, DefaultDesc, ArgumentType::Flag }, 585 }); 586 587 std::vector<std::string> values{ "another", "--nope" }; 588 Invocation inv{ std::vector<std::string>(values) }; 589 590 REQUIRE_COMMAND_EXCEPTION(command.ParseArguments(inv, args), CLI::Resource::String::InvalidNameError(Utility::LocIndView{ values[1] })); 591 } 592 593 TEST_CASE("ParseArguments_PositionalWithMultipleValues", "[command]") 594 { 595 Args args; 596 TestCommand command({ 597 Argument{ "multi", 'm', Args::Type::MultiQuery, DefaultDesc, ArgumentType::Positional }.SetCountLimit(5), 598 }); 599 600 std::vector<std::string> values{ "value1" "value2", "value3" }; 601 Invocation inv{ std::vector<std::string>(values) }; 602 603 command.ParseArguments(inv, args); 604 RequireValuesParsedToArg(values, command.m_args[0], args); 605 } 606 607 TEST_CASE("ParseArguments_PositionalWithMultipleValuesAndOtherArgs", "[command]") 608 { 609 Args args; 610 TestCommand command({ 611 Argument{ "pos1", 'p', Args::Type::Source, DefaultDesc, ArgumentType::Positional }, 612 Argument{ "pos2", 'q', Args::Type::All, DefaultDesc, ArgumentType::Positional }, 613 Argument{ "multi", 'm', Args::Type::MultiQuery, DefaultDesc, ArgumentType::Positional }.SetCountLimit(5), 614 Argument{ "flag", 'f', Args::Type::BlockingPin, DefaultDesc, ArgumentType::Flag }, 615 }); 616 617 std::vector<std::string> values{ "positional", "-q", "anotherPos", "multiValue1", "multiValue2", "-f" }; 618 Invocation inv{ std::vector<std::string>(values) }; 619 620 command.ParseArguments(inv, args); 621 RequireValueParsedToArg(values[0], command.m_args[0], args); 622 RequireValueParsedToArg(values[2], command.m_args[1], args); 623 RequireValuesParsedToArg({ values[3], values[4] }, command.m_args[2], args); 624 REQUIRE(args.Contains(command.m_args[3].ExecArgType())); 625 } 626 627 TEST_CASE("ParseArguments_PositionalWithMultipleValuesAndName", "[command]") 628 { 629 Args args; 630 TestCommand command({ 631 Argument{ "multi", 'm', Args::Type::MultiQuery, DefaultDesc, ArgumentType::Positional }.SetCountLimit(5), 632 }); 633 634 std::vector<std::string> values{ "--multi", "one", "two", "three" }; 635 Invocation inv{ std::vector<std::string>(values) }; 636 637 command.ParseArguments(inv, args); 638 RequireValuesParsedToArg({ values[1], values[2], values[3] }, command.m_args[0], args); 639 } 640 641 TEST_CASE("ParseArguments_MultiQueryConvertedToSingleQuery", "[command]") 642 { 643 Args args; 644 TestCommand command({ 645 Argument{ "multi", 'm', Args::Type::MultiQuery, DefaultDesc, ArgumentType::Positional }.SetCountLimit(5), 646 }); 647 648 std::vector<std::string> values{ "singleValue" }; 649 Invocation inv{ std::vector<std::string>(values) }; 650 651 // ParseArguments converts MultiQuery args with a single value into Query args 652 command.ParseArguments(inv, args); 653 RequireValuesParsedToArg({ values[0] }, Args::Type::Query, args); 654 } 655 656 TEST_CASE("ParseArguments_PositionalWithTooManyValues", "[command]") 657 { 658 Args args; 659 TestCommand command({ 660 Argument{ "multi", 'm', Args::Type::MultiQuery, DefaultDesc, ArgumentType::Positional }.SetCountLimit(5), 661 }); 662 663 std::vector<std::string> values{ "1", "2", "3", "4", "5", "tooMany" }; 664 Invocation inv{ std::vector<std::string>(values) }; 665 666 REQUIRE_COMMAND_EXCEPTION(command.ParseArguments(inv, args), CLI::Resource::String::ExtraPositionalError(Utility::LocIndView{ values.back() })); 667 }