Skip to content

Commit 94adb69

Browse files
committed
[UTest] Add Unit Tests for ArgParser
1 parent 8e56f4f commit 94adb69

File tree

4 files changed

+173
-6
lines changed

4 files changed

+173
-6
lines changed

include/mutable/util/ArgParser.hpp

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -142,10 +142,12 @@ class M_EXPORT ArgParser
142142
const std::vector<const char*> & args() const { return args_; }
143143
};
144144

145+
M_LCOV_EXCL_START
145146
inline std::ostream & operator<<(std::ostream &out, const ArgParser &AP)
146147
{
147148
AP.print_args(out);
148149
return out;
149150
}
151+
M_LCOV_EXCL_STOP
150152

151153
}

src/util/ArgParser.cpp

Lines changed: 18 additions & 6 deletions
Original file line numberDiff line numberDiff line change
@@ -18,8 +18,8 @@ namespace {
1818
void check_next_arg(const char **&argv)
1919
{
2020
if (not *++argv) {
21-
std::cerr << "missing argument" << std::endl;
22-
std::exit(EXIT_FAILURE);
21+
std::cerr << "missing argument" << std::endl; //M_LCOV_EXCL_LINE
22+
std::exit(EXIT_FAILURE); //M_LCOV_EXCL_LINE
2323
}
2424
}
2525

@@ -43,20 +43,25 @@ void help_parse(const char **&argv, const std::function<void(T)> &callback)
4343
if constexpr (std::same_as<T, unsigned>) {
4444
const unsigned long v = std::stoul(*argv);
4545
if (v > std::numeric_limits<unsigned>::max())
46-
throw std::out_of_range("input exceeds range of type unsigned int");
46+
throw std::out_of_range("input exceeds range of type unsigned int"); //M_LCOV_EXCL_LINE
4747
i = unsigned(v);
4848
}
4949
if constexpr (std::same_as<T, unsigned long>)
5050
i = std::stoul(*argv);
5151
if constexpr (std::same_as<T, unsigned long long>)
5252
i = std::stoull(*argv);
53-
} catch(std::invalid_argument ex) {
53+
}
54+
55+
M_LCOV_EXCL_START
56+
catch(std::invalid_argument ex) {
5457
std::cerr << "not a valid integer" << std::endl;
5558
std::exit(EXIT_FAILURE);
5659
} catch (std::out_of_range ex) {
5760
std::cerr << "value out of range" << std::endl;
5861
std::exit(EXIT_FAILURE);
5962
}
63+
M_LCOV_EXCL_STOP
64+
6065
callback(i);
6166
}
6267

@@ -75,13 +80,18 @@ void help_parse(const char **&argv, const std::function<void(T)> &callback)
7580
fp = std::stod(*argv);
7681
if constexpr (std::same_as<T, long double>)
7782
fp = std::stold(*argv);
78-
} catch (std::invalid_argument) {
83+
}
84+
85+
M_LCOV_EXCL_START
86+
catch (std::invalid_argument) {
7987
std::cerr << "not a valid floating-point number" << std::endl;
8088
std::exit(EXIT_FAILURE);
8189
} catch (std::out_of_range) {
8290
std::cerr << "value out of range" << std::endl;
8391
std::exit(EXIT_FAILURE);
8492
}
93+
M_LCOV_EXCL_STOP
94+
8595
callback(fp);
8696
}
8797

@@ -144,6 +154,7 @@ void ArgParser::OptionImpl<std::vector<std::string_view>>::parse(const char **&a
144154

145155
//----------------------------------------------------------------------------------------------------------------------
146156

157+
M_LCOV_EXCL_START
147158
void ArgParser::print_args(std::ostream &out) const
148159
{
149160
auto print = [this, &out](const char *Short, const char *Long, const char *Descr) {
@@ -166,6 +177,7 @@ void ArgParser::print_args(std::ostream &out) const
166177
print(opt->short_name ? opt->short_name : "", opt->long_name ? opt->long_name : "", opt->description);
167178
}
168179
}
180+
M_LCOV_EXCL_STOP
169181

170182
void ArgParser::parse_args(int, const char **argv) {
171183
for (++argv; *argv; ++argv) {
@@ -176,7 +188,7 @@ void ArgParser::parse_args(int, const char **argv) {
176188
it->second.get().parse(argv); // option
177189
} else {
178190
if (strneq(*argv, "--", 2))
179-
std::cerr << "warning: ignore unknown option " << *argv << std::endl;
191+
std::cerr << "warning: ignore unknown option " << *argv << std::endl; //M_LCOV_EXCL_LINE
180192
else
181193
args_.emplace_back(*argv); // positional argument
182194
}

unittest/CMakeLists.txt

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -11,6 +11,7 @@ set(
1111
util/ADTTest.cpp
1212
util/AlgorithmsTest.cpp
1313
util/AllocatorTest.cpp
14+
util/ArgParserTest.cpp
1415
util/FnTest.cpp
1516
util/KmeansTest.cpp
1617
util/LinearModelTest.cpp

unittest/util/ArgParserTest.cpp

Lines changed: 152 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,152 @@
1+
#include "catch2/catch.hpp"
2+
#include <mutable/util/ArgParser.hpp>
3+
4+
using namespace m;
5+
6+
#define ADD(TYPE, SHORT, LONG, DESCR, CALLBACK) \
7+
{ \
8+
AP.add<TYPE>(SHORT, LONG, DESCR, CALLBACK); \
9+
}
10+
11+
12+
TEST_CASE("ArgParser", "[core][util][ArgParser]") {
13+
ArgParser AP;
14+
bool callback_called = false;
15+
16+
SECTION("Boolean option with short name only")
17+
{
18+
ADD(bool, "-h", nullptr, "help description",
19+
[&](bool) { callback_called = true; });
20+
21+
const char *argv[] = { "program", "-h", nullptr };
22+
AP(1, argv);
23+
}
24+
25+
SECTION("Boolean option with long name only")
26+
{
27+
ADD(bool, nullptr, "--help", "help description",
28+
[&](bool) { callback_called = true; });
29+
30+
const char *argv[] = { "program", "--help", nullptr };
31+
AP(1, argv);
32+
}
33+
34+
SECTION("String option with both short and long name")
35+
{
36+
ADD(const char *, "-s", "--string", "string option description",
37+
[&](const char * str) {
38+
REQUIRE(streq(str, "char * value"));
39+
callback_called = true;
40+
});
41+
42+
const char *argv[] = { "program", "-s", "char * value", nullptr };
43+
AP(2, argv);
44+
}
45+
46+
SECTION("Positional arguments only")
47+
{
48+
const char *argv[] = { "program", "pos_arg1", "--", "pos_arg2", "pos_arg3", nullptr };
49+
AP(2, argv);
50+
51+
auto &args = AP.args();
52+
53+
REQUIRE(args.size() == 3);
54+
REQUIRE(streq(args[0] ,"pos_arg1"));
55+
REQUIRE(streq(args[1], "pos_arg2"));
56+
REQUIRE(streq(args[2], "pos_arg3"));
57+
callback_called = true; // to silence the test
58+
}
59+
60+
SECTION("Combination of Options & Positional arguments")
61+
{
62+
ADD(bool, "-h", "--help", "help description",
63+
[&](bool) { callback_called = true; });
64+
65+
const char *argv[] = { "program", "-h", "pos_arg1", "pos_arg2", nullptr };
66+
AP(3, argv);
67+
68+
auto &args = AP.args();
69+
70+
REQUIRE(args.size() == 2);
71+
REQUIRE(streq(args[0] ,"pos_arg1"));
72+
REQUIRE(streq(args[1], "pos_arg2"));
73+
}
74+
75+
REQUIRE(callback_called);
76+
}
77+
78+
TEMPLATE_TEST_CASE("ArgParser with Integral types", "[core][util][ArgParser]",
79+
int, long, long long, unsigned, unsigned long, unsigned long long)
80+
{
81+
ArgParser AP;
82+
bool callback_called = false;
83+
84+
ADD(TestType, "-i", "--integral", "integral description",
85+
[&](TestType integral) {
86+
REQUIRE(integral == TestType(42));
87+
callback_called = true;
88+
});
89+
90+
const char *argv[] = { "program", "-i", "42", nullptr };
91+
AP(2, argv);
92+
93+
REQUIRE(callback_called);
94+
}
95+
96+
TEMPLATE_TEST_CASE("ArgParser with Floating-Point types", "[core][util][ArgParser]",
97+
float, double, long double)
98+
{
99+
ArgParser AP;
100+
bool callback_called = false;
101+
102+
ADD(TestType, "-f", "--float", "float description",
103+
[&](TestType floating_point) {
104+
REQUIRE(floating_point == TestType(42.0));
105+
callback_called = true;
106+
});
107+
108+
const char *argv[] = { "program", "-f", "42.0", nullptr };
109+
AP(2, argv);
110+
111+
REQUIRE(callback_called);
112+
}
113+
114+
TEST_CASE("ArgParser with List-of-Strings", "[core][util][ArgParser]")
115+
{
116+
ArgParser AP;
117+
bool callback_called = false;
118+
119+
SECTION("Empty list")
120+
{
121+
ADD(std::vector<std::string_view>, "-l", "--list-of-strings", "list description",
122+
[&](std::vector<std::string_view> list) {
123+
REQUIRE(list.empty());
124+
callback_called = true;
125+
});
126+
127+
const char *argv[] = { "program", "-l", "", nullptr };
128+
AP(2, argv);
129+
}
130+
131+
SECTION("Non-Empty list")
132+
{
133+
ADD(std::vector<std::string_view>, "-l", "--list-of-strings", "list description",
134+
[&](std::vector<std::string_view> list) {
135+
REQUIRE(list.size() == 4);
136+
REQUIRE(list[0] == "yellow");
137+
REQUIRE(list[1] == "green");
138+
REQUIRE(list[2] == "navy blue");
139+
REQUIRE(list[3] == "pink");
140+
141+
callback_called = true;
142+
});
143+
144+
const char *argv[] = { "program", "-l", "yellow,green,navy blue,pink", nullptr };
145+
AP(3, argv);
146+
}
147+
148+
REQUIRE(callback_called);
149+
}
150+
151+
152+
#undef ADD

0 commit comments

Comments
 (0)