mirror of
https://github.com/RGBCube/serenity
synced 2025-07-27 16:57:46 +00:00
AK: Update OptionParser::m_arg_index by substracting skipped args
On argument swapping to put positional ones toward the end, m_arg_index was pointing at "last arg index" + "skipped args" + "consumed args" and thus was pointing ahead of the skipped ones. m_arg_index now points after the current parsed option arguments.
This commit is contained in:
parent
8320faf052
commit
a9df60ff1c
5 changed files with 382 additions and 2 deletions
|
@ -56,6 +56,7 @@ set(AK_TEST_SOURCES
|
|||
TestNonnullRefPtr.cpp
|
||||
TestNumberFormat.cpp
|
||||
TestOptional.cpp
|
||||
TestOptionParser.cpp
|
||||
TestOwnPtr.cpp
|
||||
TestPrint.cpp
|
||||
TestQueue.cpp
|
||||
|
|
174
Tests/AK/TestOptionParser.cpp
Normal file
174
Tests/AK/TestOptionParser.cpp
Normal file
|
@ -0,0 +1,174 @@
|
|||
/*
|
||||
* Copyright (c) 2021, the SerenityOS developers.
|
||||
*
|
||||
* SPDX-License-Identifier: BSD-2-Clause
|
||||
*/
|
||||
|
||||
#include <LibTest/TestCase.h>
|
||||
|
||||
#include <AK/Array.h>
|
||||
#include <AK/OptionParser.h>
|
||||
#include <AK/String.h>
|
||||
#include <AK/Vector.h>
|
||||
|
||||
TEST_CASE(string_option)
|
||||
{
|
||||
ByteString short_options = "";
|
||||
int index_of_found_long_option = -1;
|
||||
Vector<OptionParser::Option> long_options;
|
||||
long_options.append(
|
||||
{ "string_opt"sv,
|
||||
OptionParser::ArgumentRequirement::HasRequiredArgument,
|
||||
&index_of_found_long_option,
|
||||
0 });
|
||||
|
||||
Array<StringView, 3> argument_array({ "app"sv, "--string_opt"sv, "string_opt_value"sv });
|
||||
Span<StringView> arguments(argument_array);
|
||||
size_t next_argument_index = 1;
|
||||
|
||||
OptionParser parser;
|
||||
auto result = parser.getopt(arguments.slice(1), short_options, long_options, {});
|
||||
|
||||
// found a long option
|
||||
EXPECT_EQ(result.result, 0);
|
||||
// found long option at index 0
|
||||
EXPECT_EQ(index_of_found_long_option, 0);
|
||||
// 2 args consumed: option name and value
|
||||
EXPECT_EQ(result.consumed_args, static_cast<size_t>(2));
|
||||
// option has a value
|
||||
EXPECT_EQ(result.optarg_value, "string_opt_value");
|
||||
|
||||
next_argument_index += result.consumed_args;
|
||||
// we are past the end
|
||||
EXPECT_EQ(next_argument_index, static_cast<size_t>(3));
|
||||
}
|
||||
|
||||
TEST_CASE(string_option_then_positional)
|
||||
{
|
||||
ByteString short_options = "";
|
||||
int index_of_found_long_option = -1;
|
||||
Vector<OptionParser::Option> long_options;
|
||||
long_options.append(
|
||||
{ "string_opt"sv,
|
||||
OptionParser::ArgumentRequirement::HasRequiredArgument,
|
||||
&index_of_found_long_option,
|
||||
0 });
|
||||
|
||||
Array<StringView, 4> argument_array({ "app"sv, "--string_opt"sv, "string_opt_value"sv, "positional"sv });
|
||||
Span<StringView> arguments(argument_array);
|
||||
size_t next_argument_index = 1;
|
||||
|
||||
OptionParser parser;
|
||||
auto result = parser.getopt(arguments.slice(1), short_options, long_options, {});
|
||||
|
||||
// found a long option
|
||||
EXPECT_EQ(result.result, 0);
|
||||
// found long option at index 0
|
||||
EXPECT_EQ(index_of_found_long_option, 0);
|
||||
// 2 args consumed: option name and value
|
||||
EXPECT_EQ(result.consumed_args, static_cast<size_t>(2));
|
||||
// option has a value
|
||||
EXPECT_EQ(result.optarg_value, "string_opt_value");
|
||||
|
||||
next_argument_index += result.consumed_args;
|
||||
// we are at "positional" index of arguments vector
|
||||
EXPECT_EQ(next_argument_index, static_cast<size_t>(3));
|
||||
EXPECT_EQ(arguments[next_argument_index], "positional");
|
||||
|
||||
result = parser.getopt(arguments.slice(1), short_options, long_options, {});
|
||||
// there's no more options
|
||||
EXPECT_EQ(result.result, -1);
|
||||
}
|
||||
|
||||
TEST_CASE(positional_then_string_option)
|
||||
{
|
||||
ByteString short_options = "";
|
||||
int index_of_found_long_option = -1;
|
||||
Vector<OptionParser::Option> long_options;
|
||||
long_options.append(
|
||||
{ "string_opt"sv,
|
||||
OptionParser::ArgumentRequirement::HasRequiredArgument,
|
||||
&index_of_found_long_option,
|
||||
0 });
|
||||
|
||||
Array<StringView, 4> argument_array({ "app"sv, "positional"sv, "--string_opt"sv, "string_opt_value"sv });
|
||||
Span<StringView> arguments(argument_array);
|
||||
size_t next_argument_index = 1;
|
||||
|
||||
OptionParser parser;
|
||||
auto result = parser.getopt(arguments.slice(1), short_options, long_options, {});
|
||||
|
||||
// found a long option
|
||||
EXPECT_EQ(result.result, 0);
|
||||
// found long option at index 0
|
||||
EXPECT_EQ(index_of_found_long_option, 0);
|
||||
// 2 args consumed: option name and value
|
||||
EXPECT_EQ(result.consumed_args, static_cast<size_t>(2));
|
||||
// option has a value
|
||||
EXPECT_EQ(result.optarg_value, "string_opt_value");
|
||||
|
||||
next_argument_index += result.consumed_args;
|
||||
// we are at "positional" index of arguments vector
|
||||
EXPECT_EQ(next_argument_index, static_cast<size_t>(3));
|
||||
EXPECT_EQ(arguments[next_argument_index], "positional");
|
||||
|
||||
result = parser.getopt(arguments.slice(1), short_options, long_options, {});
|
||||
// there's no more options
|
||||
EXPECT_EQ(result.result, -1);
|
||||
}
|
||||
|
||||
TEST_CASE(positional_then_string_option_then_bool_option)
|
||||
{
|
||||
// #22759: Positional arguments were sometimes incorrectly not shifted, leading to an incorrect parse.
|
||||
|
||||
ByteString short_options = "";
|
||||
int index_of_found_long_option = -1;
|
||||
Vector<OptionParser::Option> long_options;
|
||||
long_options.append(
|
||||
{ "string_opt"sv,
|
||||
OptionParser::ArgumentRequirement::HasRequiredArgument,
|
||||
&index_of_found_long_option,
|
||||
0 });
|
||||
long_options.append(
|
||||
{ "bool_opt"sv,
|
||||
OptionParser::ArgumentRequirement::NoArgument,
|
||||
&index_of_found_long_option,
|
||||
1 });
|
||||
|
||||
Array<StringView, 5> argument_array({ "app"sv, "positional"sv, "--string_opt"sv, "string_opt_value"sv, "--bool_opt"sv });
|
||||
Span<StringView> arguments(argument_array);
|
||||
size_t next_argument_index = 1;
|
||||
|
||||
OptionParser parser;
|
||||
auto result = parser.getopt(arguments.slice(1), short_options, long_options, {});
|
||||
// found a long option
|
||||
EXPECT_EQ(result.result, 0);
|
||||
// found long option at index 0
|
||||
EXPECT_EQ(index_of_found_long_option, 0);
|
||||
// 2 args consumed: option name and value
|
||||
EXPECT_EQ(result.consumed_args, static_cast<size_t>(2));
|
||||
// option has a value
|
||||
EXPECT_EQ(result.optarg_value, "string_opt_value");
|
||||
|
||||
next_argument_index += result.consumed_args;
|
||||
EXPECT_EQ(next_argument_index, static_cast<size_t>(3));
|
||||
// positional argument has been shifted here
|
||||
EXPECT_EQ(arguments[next_argument_index], "positional");
|
||||
|
||||
result = parser.getopt(arguments.slice(1), short_options, long_options, {});
|
||||
// found another long option
|
||||
EXPECT_EQ(result.result, 0);
|
||||
// found long option at index 1
|
||||
EXPECT_EQ(index_of_found_long_option, 1);
|
||||
// 1 arg consumed: option name
|
||||
EXPECT_EQ(result.consumed_args, static_cast<size_t>(1));
|
||||
|
||||
next_argument_index += result.consumed_args;
|
||||
// "positional" argument has been shifted here
|
||||
EXPECT_EQ(next_argument_index, static_cast<size_t>(4));
|
||||
EXPECT_EQ(arguments[next_argument_index], "positional");
|
||||
|
||||
result = parser.getopt(arguments.slice(1), short_options, long_options, {});
|
||||
// there's no more options
|
||||
EXPECT_EQ(result.result, -1);
|
||||
}
|
|
@ -5,10 +5,10 @@
|
|||
*/
|
||||
|
||||
#include <AK/Function.h>
|
||||
#include <AK/String.h>
|
||||
#include <AK/Vector.h>
|
||||
#include <LibCore/ArgsParser.h>
|
||||
#include <LibTest/TestCase.h>
|
||||
#include <string.h>
|
||||
|
||||
class ParserResult {
|
||||
public:
|
||||
|
@ -112,6 +112,40 @@ TEST_CASE(bool_option)
|
|||
EXPECT_EQ(force, true);
|
||||
}
|
||||
|
||||
TEST_CASE(string_option)
|
||||
{
|
||||
ByteString string_option;
|
||||
|
||||
// short option
|
||||
auto parser_result = run_parser({ "app"sv, "-d"sv, "foo"sv }, [&](auto& parser) {
|
||||
parser.add_option(string_option, "dummy", nullptr, 'd', "DUMMY");
|
||||
});
|
||||
EXPECT_EQ(parser_result.result, true);
|
||||
EXPECT_EQ(string_option, "foo");
|
||||
|
||||
// short option, not given
|
||||
string_option = "";
|
||||
parser_result = run_parser({ "app"sv, "-d"sv }, [&](auto& parser) {
|
||||
parser.add_option(string_option, "dummy", nullptr, 'd', "DUMMY");
|
||||
});
|
||||
EXPECT_EQ(parser_result.result, false);
|
||||
|
||||
// long option
|
||||
string_option = "";
|
||||
parser_result = run_parser({ "app"sv, "--dummy"sv, "foo"sv }, [&](auto& parser) {
|
||||
parser.add_option(string_option, "dummy", "dummy", {}, "DUMMY");
|
||||
});
|
||||
EXPECT_EQ(parser_result.result, true);
|
||||
EXPECT_EQ(string_option, "foo");
|
||||
|
||||
// long option, not given
|
||||
string_option = "";
|
||||
parser_result = run_parser({ "app"sv, "--dummy"sv }, [&](auto& parser) {
|
||||
parser.add_option(string_option, "dummy", "dummy", {}, "DUMMY");
|
||||
});
|
||||
EXPECT_EQ(parser_result.result, false);
|
||||
}
|
||||
|
||||
TEST_CASE(positional_string_argument)
|
||||
{
|
||||
// Single required string argument
|
||||
|
@ -316,6 +350,169 @@ TEST_CASE(combination_of_bool_options_with_positional_vector_string)
|
|||
EXPECT_EQ(parser_result.result, false);
|
||||
}
|
||||
|
||||
TEST_CASE(combination_of_bool_and_string_short_options_with_positional_vector_string)
|
||||
{
|
||||
// #22759: Positional arguments were sometimes incorrectly not shifted, leading to an incorrect parse.
|
||||
|
||||
// Bool options (given), string options (given) and one positional argument (given)
|
||||
// Expected: all arguments fill as given
|
||||
bool bool_opt1 = false;
|
||||
bool bool_opt2 = false;
|
||||
ByteString string_opt1;
|
||||
ByteString string_opt2;
|
||||
Vector<StringView> positionals;
|
||||
|
||||
auto parser_result = run_parser({ "app"sv, "-b"sv, "-c"sv, "-d"sv, "foo"sv, "-e"sv, "bar"sv, "one"sv }, [&](auto& parser) {
|
||||
parser.add_option(bool_opt1, "bool_opt1", nullptr, 'b');
|
||||
parser.add_option(bool_opt2, "bool_opt2", nullptr, 'c');
|
||||
parser.add_option(string_opt1, "string_opt1", nullptr, 'd', "D");
|
||||
parser.add_option(string_opt2, "string_opt2", nullptr, 'e', "E");
|
||||
parser.add_positional_argument(positionals, "pos", "pos", Core::ArgsParser::Required::No);
|
||||
});
|
||||
EXPECT_EQ(parser_result.result, true);
|
||||
EXPECT_EQ(bool_opt1, true);
|
||||
EXPECT_EQ(bool_opt2, true);
|
||||
EXPECT_EQ(string_opt1, "foo");
|
||||
EXPECT_EQ(string_opt2, "bar");
|
||||
EXPECT_EQ(positionals.size(), 1u);
|
||||
if (positionals.size() == 1u) {
|
||||
EXPECT_EQ(positionals[0], "one");
|
||||
}
|
||||
|
||||
// Bool options (given), string options (given) and one positional argument (given)
|
||||
// one bool option after positional
|
||||
// Expected: all arguments fill as given
|
||||
bool_opt1 = false;
|
||||
bool_opt2 = false;
|
||||
string_opt1 = "";
|
||||
string_opt2 = "";
|
||||
positionals = {};
|
||||
|
||||
parser_result = run_parser({ "app"sv, "-c"sv, "-d"sv, "foo"sv, "-e"sv, "bar"sv, "one"sv, "-b"sv }, [&](auto& parser) {
|
||||
parser.add_option(bool_opt1, "bool_opt1", nullptr, 'b');
|
||||
parser.add_option(bool_opt2, "bool_opt2", nullptr, 'c');
|
||||
parser.add_option(string_opt1, "string_opt1", nullptr, 'd', "D");
|
||||
parser.add_option(string_opt2, "string_opt2", nullptr, 'e', "E");
|
||||
parser.add_positional_argument(positionals, "pos", "pos", Core::ArgsParser::Required::No);
|
||||
});
|
||||
EXPECT_EQ(parser_result.result, true);
|
||||
EXPECT_EQ(bool_opt1, true);
|
||||
EXPECT_EQ(bool_opt2, true);
|
||||
EXPECT_EQ(string_opt1, "foo");
|
||||
EXPECT_EQ(string_opt2, "bar");
|
||||
EXPECT_EQ(positionals.size(), 1u);
|
||||
if (positionals.size() == 1u) {
|
||||
EXPECT_EQ(positionals[0], "one");
|
||||
}
|
||||
|
||||
// Bool options (given), string options (given) and one positional argument (given)
|
||||
// one string and one bool option after positional
|
||||
// Expected: all arguments fill as given
|
||||
bool_opt1 = false;
|
||||
bool_opt2 = false;
|
||||
string_opt1 = "";
|
||||
string_opt2 = "";
|
||||
positionals = {};
|
||||
|
||||
parser_result = run_parser({ "app"sv, "-c"sv, "-e"sv, "bar"sv, "one"sv, "-d"sv, "foo"sv, "-b"sv }, [&](auto& parser) {
|
||||
parser.add_option(bool_opt1, "bool_opt1", nullptr, 'b');
|
||||
parser.add_option(bool_opt2, "bool_opt2", nullptr, 'c');
|
||||
parser.add_option(string_opt1, "string_opt1", nullptr, 'd', "D");
|
||||
parser.add_option(string_opt2, "string_opt2", nullptr, 'e', "E");
|
||||
parser.add_positional_argument(positionals, "pos", "pos", Core::ArgsParser::Required::No);
|
||||
});
|
||||
EXPECT_EQ(parser_result.result, true);
|
||||
EXPECT_EQ(bool_opt1, true);
|
||||
EXPECT_EQ(bool_opt2, true);
|
||||
EXPECT_EQ(string_opt1, "foo");
|
||||
EXPECT_EQ(string_opt2, "bar");
|
||||
EXPECT_EQ(positionals.size(), 1u);
|
||||
if (positionals.size() == 1u) {
|
||||
EXPECT_EQ(positionals[0], "one");
|
||||
}
|
||||
|
||||
// Bool options (given), string options (given) and two positional arguments (given)
|
||||
// positional arguments are separated by options
|
||||
// Expected: all arguments fill as given
|
||||
bool_opt1 = false;
|
||||
bool_opt2 = false;
|
||||
string_opt1 = "";
|
||||
string_opt2 = "";
|
||||
positionals = {};
|
||||
|
||||
parser_result = run_parser({ "app"sv, "-b"sv, "-d"sv, "foo"sv, "one"sv, "-c"sv, "-e"sv, "bar"sv, "two"sv }, [&](auto& parser) {
|
||||
parser.add_option(bool_opt1, "bool_opt1", nullptr, 'b');
|
||||
parser.add_option(bool_opt2, "bool_opt2", nullptr, 'c');
|
||||
parser.add_option(string_opt1, "string_opt1", nullptr, 'd', "D");
|
||||
parser.add_option(string_opt2, "string_opt2", nullptr, 'e', "E");
|
||||
parser.add_positional_argument(positionals, "pos", "pos", Core::ArgsParser::Required::No);
|
||||
});
|
||||
EXPECT_EQ(parser_result.result, true);
|
||||
EXPECT_EQ(bool_opt1, true);
|
||||
EXPECT_EQ(bool_opt2, true);
|
||||
EXPECT_EQ(string_opt1, "foo");
|
||||
EXPECT_EQ(string_opt2, "bar");
|
||||
EXPECT_EQ(positionals.size(), 2u);
|
||||
if (positionals.size() == 2u) {
|
||||
EXPECT_EQ(positionals[0], "one");
|
||||
EXPECT_EQ(positionals[1], "two");
|
||||
}
|
||||
|
||||
// Bool options (given), string options (given) and two positional arguments (given)
|
||||
// positional arguments are separated and followed by options
|
||||
// Expected: all arguments fill as given
|
||||
bool_opt1 = false;
|
||||
bool_opt2 = false;
|
||||
string_opt1 = "";
|
||||
string_opt2 = "";
|
||||
positionals = {};
|
||||
|
||||
parser_result = run_parser({ "app"sv, "one"sv, "-b"sv, "-d"sv, "foo"sv, "two"sv, "-c"sv, "-e"sv, "bar"sv }, [&](auto& parser) {
|
||||
parser.add_option(bool_opt1, "bool_opt1", nullptr, 'b');
|
||||
parser.add_option(bool_opt2, "bool_opt2", nullptr, 'c');
|
||||
parser.add_option(string_opt1, "string_opt1", nullptr, 'd', "D");
|
||||
parser.add_option(string_opt2, "string_opt2", nullptr, 'e', "E");
|
||||
parser.add_positional_argument(positionals, "pos", "pos", Core::ArgsParser::Required::No);
|
||||
});
|
||||
EXPECT_EQ(parser_result.result, true);
|
||||
EXPECT_EQ(bool_opt1, true);
|
||||
EXPECT_EQ(bool_opt2, true);
|
||||
EXPECT_EQ(string_opt1, "foo");
|
||||
EXPECT_EQ(string_opt2, "bar");
|
||||
EXPECT_EQ(positionals.size(), 2u);
|
||||
if (positionals.size() == 2u) {
|
||||
EXPECT_EQ(positionals[0], "one");
|
||||
EXPECT_EQ(positionals[1], "two");
|
||||
}
|
||||
|
||||
// Bool options (given), string options (given) and two positional arguments (given)
|
||||
// positional arguments are separated and followed by options, variation on options order
|
||||
// Expected: all arguments fill as given
|
||||
bool_opt1 = false;
|
||||
bool_opt2 = false;
|
||||
string_opt1 = "";
|
||||
string_opt2 = "";
|
||||
positionals = {};
|
||||
|
||||
parser_result = run_parser({ "app"sv, "one"sv, "-d"sv, "foo"sv, "-b"sv, "two"sv, "-e"sv, "bar"sv, "-c"sv }, [&](auto& parser) {
|
||||
parser.add_option(bool_opt1, "bool_opt1", nullptr, 'b');
|
||||
parser.add_option(bool_opt2, "bool_opt2", nullptr, 'c');
|
||||
parser.add_option(string_opt1, "string_opt1", nullptr, 'd', "D");
|
||||
parser.add_option(string_opt2, "string_opt2", nullptr, 'e', "E");
|
||||
parser.add_positional_argument(positionals, "pos", "pos", Core::ArgsParser::Required::No);
|
||||
});
|
||||
EXPECT_EQ(parser_result.result, true);
|
||||
EXPECT_EQ(bool_opt1, true);
|
||||
EXPECT_EQ(bool_opt2, true);
|
||||
EXPECT_EQ(string_opt1, "foo");
|
||||
EXPECT_EQ(string_opt2, "bar");
|
||||
EXPECT_EQ(positionals.size(), 2u);
|
||||
if (positionals.size() == 2u) {
|
||||
EXPECT_EQ(positionals[0], "one");
|
||||
EXPECT_EQ(positionals[1], "two");
|
||||
}
|
||||
}
|
||||
|
||||
TEST_CASE(stop_on_first_non_option)
|
||||
{
|
||||
// Do not stop on first non-option; arguments in correct order
|
||||
|
|
Loading…
Add table
Add a link
Reference in a new issue