1 //
2 // Copyright (c) 2017 Benjamin Kaufmann
3 //
4 // Permission is hereby granted, free of charge, to any person obtaining a copy
5 // of this software and associated documentation files (the "Software"), to
6 // deal in the Software without restriction, including without limitation the
7 // rights to use, copy, modify, merge, publish, distribute, sublicense, and/or
8 // sell copies of the Software, and to permit persons to whom the Software is
9 // furnished to do so, subject to the following conditions:
10 //
11 // The above copyright notice and this permission notice shall be included in
12 // all copies or substantial portions of the Software.
13 //
14 // THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
15 // IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
16 // FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
17 // AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
18 // LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING
19 // FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS
20 // IN THE SOFTWARE.
21 #include "catch.hpp"
22 #include <potassco/program_opts/program_options.h>
23 #include <potassco/program_opts/typed_value.h>
24 #include <potassco/program_opts/errors.h>
25 #include <potassco/program_opts/mapped_value.h>
26 namespace Potassco {
27 namespace ProgramOptions {
28 namespace Test {
29 namespace Po = ProgramOptions;
30 TEST_CASE("Test option default value", "[options]") {
31 int x;
32 SECTION("options don't have defaults by default") {
33 Po::Option o("other-int", 'i', "some other integer", Po::storeTo(x)->arg("<n>"));
34 REQUIRE(o.value()->defaultsTo() == static_cast<const char*>(0));
35 }
36 SECTION("options can have default values") {
37 Po::Option o("some-int", 'i', "some integer", Po::storeTo(x)->defaultsTo("123")->arg("<n>"));
38 REQUIRE(strcmp(o.value()->defaultsTo(), "123") == 0);
39 REQUIRE(strcmp(o.argName(), "<n>") == 0);
40 REQUIRE(o.assignDefault());
41 REQUIRE(x == 123);
42 REQUIRE(o.value()->state() == Po::Value::value_defaulted);
43 }
44 SECTION("careful with invalid default values") {
45 Po::Option o("other-int", 'i', "some other integer", Po::storeTo(x)->defaultsTo("123Hallo?")->arg("<n>"));
46 REQUIRE(!o.assignDefault());
47 REQUIRE(o.value()->state() == Po::Value::value_unassigned);
48 }
49 SECTION("parsing overwrites default value") {
50 Po::OptionGroup g;
51 g.addOptions()("int", Po::storeTo(x)->defaultsTo("10"), "An int");
52 Po::OptionContext ctx; ctx.add(g);
53 Po::ParsedOptions po;
54 ctx.assignDefaults(po);
55 REQUIRE(x == 10);
56 Po::ParsedValues pv(ctx);
57 pv.add("int", "2");
58 po.assign(pv);
59 REQUIRE(x == 2);
60 }
61 }
negatable_int(const std::string & s,int & out)62 static bool negatable_int(const std::string& s, int& out) {
63 if (s == "no") { out = 0; return true; }
64 return Potassco::string_cast(s, out);
65 }
66
67 TEST_CASE("Test negatable options", "[options]") {
68 bool b1, b2;
69 Po::OptionGroup g;
70 Po::OptionContext ctx;
71 SECTION("options are not negatable by default") {
72 Po::Option o("flag", 'f', "some flag", Po::flag(b1));
73 REQUIRE(o.value()->isNegatable() == false);
74 }
75 SECTION("exclamation mark ('!') in init helper makes option negatable") {
76 g.addOptions()("flag!,f", Po::flag(b1), "some flag");
77 REQUIRE((*g.begin())->value()->isNegatable() == true);
78 ctx.add(g);
79 REQUIRE(ctx.tryFind("flag!") == ctx.end());
80 }
81 SECTION("exclamation mark ('!') can be quoted") {
82 g.addOptions()("flag\\!,f", Po::flag(b1), "some flag");
83 REQUIRE((*g.begin())->value()->isNegatable() == false);
84 ctx.add(g);
85 REQUIRE(ctx.tryFind("flag!") != ctx.end());
86 }
87 SECTION("negatable options are shown in description") {
88 int i;
89 g.addOptions()
90 ("flag!,f", Po::flag(b1), "some negatable flag")
91 ("value!", Po::storeTo(i, &negatable_int)->arg("<n>"), "some negatable int")
92 ;
93 ctx.add(g);
94 std::string help;
95 Po::StringOut out(help);
96 ctx.description(out);
97 REQUIRE(help.find("[no-]flag") != std::string::npos);
98 REQUIRE(help.find("<n>|no") != std::string::npos);
99 }
100 SECTION("negatable options are correctly parsed") {
101 int i = 123;
102 g.addOptions()
103 ("flag!,f", Po::flag(b1), "some negatable flag")
104 ("flag\\!", Po::flag(b2), "some flag")
105 ("value!", Po::storeTo(i, &negatable_int)->arg("<n>"), "some negatable int")
106 ;
107 ctx.add(g);
108 Po::ParsedOptions po;
109 Po::ParsedValues pv = Po::parseCommandString("--flag! --no-flag --no-value", ctx);
110 REQUIRE(po.assign(pv) == true);
111 REQUIRE((b1 == false && b2 == true && i == 0));
112
113 REQUIRE_THROWS_AS(Po::parseCommandString("--no-value=2", ctx), Po::UnknownOption);
114 pv = Po::parseCommandString("--no-value --value=2", ctx);
115 REQUIRE_THROWS_AS(Po::ParsedOptions().assign(pv), Po::ValueError);
116 }
117 SECTION("negatable options should better not be a prefix of other option") {
118 b1 = true, b2 = false;
119 g.addOptions()
120 ("swi!", Po::flag(b1), "A negatable switch")
121 ("no-swi2", Po::flag(b2), "A switch")
122 ;
123 ctx.add(g);
124 Po::ParsedOptions po;
125 Po::ParsedValues pv = Po::parseCommandString("--no-swi", ctx);
126 REQUIRE((po.assign(pv) && b1 && b2));
127 }
128 }
129
130 TEST_CASE("Test parsed options", "[options]") {
131 Po::OptionGroup g;
132 int i1, i2;
133 g.addOptions()
134 ("int1", Po::storeTo(i1), "An int")
135 ("int2", Po::storeTo(i2)->defaultsTo("10"), "Another int")
136 ;
137 Po::OptionContext ctx;
138 ctx.add(g);
139 SECTION("assign parsed values") {
140 Po::ParsedOptions po;
141 Po::ParsedValues pv = Po::parseCommandString("--int1=2", ctx);
142 po.assign(pv);
143 ctx.assignDefaults(po);
144 REQUIRE(po.count("int1") != 0);
145 REQUIRE(po.count("int2") == 0);
146 REQUIRE(i2 == 10); // default value
147 REQUIRE(i1 == 2); // parsed value
148 pv.add("int2", "20");
149 po.assign(pv);
150 REQUIRE(po.count("int2") != 0);
151 REQUIRE(i2 == 20); // parsed value
152 }
153 SECTION("parsed options support exclude list") {
154 Po::ParsedOptions po, po2;
155 po.assign(Po::parseCommandString("--int1=1", ctx));
156 po2.assign(Po::parseCommandString("--int1=10 --int2=2", ctx), &po);
157 REQUIRE((i1 == 1 && i2 == 2));
158 }
159 SECTION("assign options from multiple sources") {
160 Po::OptionGroup g2;
161 bool b1;
162 Po::ValueMap vm;
163 g2.addOptions()
164 ("flag!", Po::flag(b1), "A switch")
165 ("int3", Po::store<int>(vm), "Yet another int")
166 ;
167 ctx.add(g2);
168 Po::ParsedOptions po;
169 po.assign(Po::parseCommandString("--int1=2 --flag --int3=3", ctx));
170 REQUIRE((i1 == 2 && b1 == true && Po::value_cast<int>(vm["int3"]) == 3));
171 Po::ParsedOptions p1(po), p2;
172 p1.assign(Po::parseCommandString("--int1=3 --no-flag --int2=4 --int3=5", ctx));
173 REQUIRE((i1 == 2 && b1 == true && i2 == 4 && Po::value_cast<int>(vm["int3"]) == 3));
174 p2.assign(Po::parseCommandString("--int1=3 --no-flag --int2=5 --int3=5", ctx));
175 REQUIRE((i1 == 3 && b1 == false && i2 == 5 && Po::value_cast<int>(vm["int3"]) == 5));
176 }
177 }
178
179 TEST_CASE("Test option groups", "[options]") {
180 int i1, i2;
181 Po::OptionGroup g1("Group1");
182 g1.addOptions()("int1", Po::storeTo(i1)->defaultsTo("10"), "An int");
183 Po::OptionGroup g2("Group2");
184 g2.addOptions()("int2", Po::storeTo(i2)->defaultsTo("10"), "An int");
185 Po::OptionContext ctx; ctx.add(g1); ctx.add(g2);
186 REQUIRE_THROWS_AS(ctx.findGroup("Foo"), Po::ContextError);
187 const Po::OptionGroup& x1 = ctx.findGroup(g1.caption());
188 REQUIRE(x1.size() == g1.size());
189 for (Po::OptionGroup::option_iterator gIt = g1.begin(), xIt = x1.begin(); gIt != g1.end(); ++gIt, ++xIt) {
190 REQUIRE(((*gIt)->name() == (*xIt)->name() && (*gIt)->value() == (*xIt)->value()));
191 }
192 }
193
194 TEST_CASE("Test context", "[options]") {
195 bool b1, b2;
196 Po::OptionGroup g;
197 Po::OptionContext ctx;
198 SECTION("option context supports find") {
199 g.addOptions()
200 ("help", Po::flag(b1), "")
201 ("help2", Po::flag(b2), "")
202 ;
203 ctx.add(g);
204 REQUIRE(ctx.tryFind("help") != ctx.end());
205 REQUIRE(ctx.tryFind("help", Po::OptionContext::find_name_or_prefix) != ctx.end());
206 REQUIRE(ctx.tryFind("help", Po::OptionContext::find_prefix) == ctx.end());
207
208 ctx.addAlias("Hilfe", ctx.find("help"));
209 REQUIRE(ctx.tryFind("Hilfe") != ctx.end());
210 }
211
212 SECTION("option description supports argument description placeholder '%A'") {
213 int x;
214 g.addOptions()("number", Po::storeTo(x)->arg("<n>"), "Some int %A in %%");
215 std::string ex;
216 Po::StringOut out(ex);
217 g.format(out, 20);
218 REQUIRE(ex.find("Some int <n> in %") != std::string::npos);
219 }
220 SECTION("option description supports default value placeholder '%D'") {
221 int x;
222 g.addOptions()("foo", Po::storeTo(x)->defaultsTo("99"), "Some int (Default: %D)");
223 std::string ex;
224 Po::StringOut out(ex);
225 g.format(out, 20);
226 REQUIRE(ex.find("Some int (Default: 99)") != std::string::npos);
227 }
228
229 SECTION("option parsing supports mapped values") {
230 Po::ValueMap vm;
231 g.addOptions()("foo", Po::store<std::vector<int> >(vm)->composing(), "");
232 ctx.add(g);
233 Po::ParsedValues pv(ctx);
234 pv.add("foo", "1");
235 pv.add("foo", "2");
236 Po::ParsedOptions po;
237 po.assign(pv);
238 const std::vector<int>& x = Po::value_cast<std::vector<int> >(vm["foo"]);
239 REQUIRE((x.size() == 2 && x[0] == 1 && x[1] == 2));
240 }
241 }
242
243 TEST_CASE("Test errors", "[options]") {
244 Po::OptionGroup g;
245 Po::OptionInitHelper x = g.addOptions();
246 Po::OptionContext ctx;
247 bool b;
248 SECTION("option name must not be empty") {
249 REQUIRE_THROWS_AS(x(0, Po::flag(b), ""), Po::Error);
250 REQUIRE_THROWS_AS(x("", Po::flag(b), ""), Po::Error);
251 }
252 SECTION("alias must be a single character") {
253 REQUIRE_THROWS_AS(x("foo,fo", Po::flag(b), ""), Po::Error);
254 }
255 SECTION("multiple occurrences are not allowed") {
256 g.addOptions()
257 ("help", Po::flag(b), "")
258 ("rand", Po::flag(b), "")
259 ;
260 ctx.add(g);
261 Po::ParsedValues pv(ctx);
262 pv.add("help", "1");
263 pv.add("help", "1");
264 REQUIRE_THROWS_AS(Po::ParsedOptions().assign(pv), Po::ValueError);
265 }
266 SECTION("unknown options are not allowed") {
267 REQUIRE_THROWS_AS(Po::parseCommandString("--help", ctx), Po::UnknownOption);
268 }
269 SECTION("options must not be ambiguous") {
270 g.addOptions()
271 ("help", Po::flag(b), "")
272 ("help-a", Po::flag(b), "")
273 ("help-b", Po::flag(b), "")
274 ("help-c", Po::flag(b), "")
275 ;
276 ctx.add(g);
277 REQUIRE_THROWS_AS(ctx.find("he", Po::OptionContext::find_prefix), Po::AmbiguousOption);
278 }
279 }
280
281 TEST_CASE("Test parse argv array", "[options]") {
282 const char* argv[] = {"-h", "-V3", "--int", "6"};
283 Po::OptionGroup g;
284 bool x;
285 int i1, i2;
286 g.addOptions()
287 ("help,h", Po::flag(x), "")
288 ("version,V", Po::storeTo(i1), "An int")
289 ("int", Po::storeTo(i2), "Another int")
290 ;
291 Po::OptionContext ctx; ctx.add(g);
292 Po::ParsedOptions po;
293 Po::ParsedValues pv = Po::parseCommandArray(argv, sizeof(argv)/sizeof(const char*), ctx);
294 po.assign(pv);
295 REQUIRE(x);
296 REQUIRE(i1 == 3);
297 REQUIRE(i2 == 6);
298 }
299
300 TEST_CASE("Test parser", "[options]") {
301 int i1, i2;
302 bool flag1 = true, flag2 = false;
303 Po::OptionGroup g;
304 g.addOptions()
305 ("int1", Po::storeTo(i1), "An int")
306 ("int2", Po::storeTo(i2), "Another int")
307 ("flag", Po::flag(flag1), "A flag")
308 ("foo,f", Po::flag(flag2), "A flag")
309 ;
310 SECTION("parser supports custom context") {
311 struct PC : public Po::ParseContext {
312 Po::OptionGroup *g;
PCPotassco::ProgramOptions::Test::PC313 PC(Po::OptionGroup& grp) : g(&grp) {}
getOptionPotassco::ProgramOptions::Test::PC314 Po::SharedOptPtr getOption(const char* name, FindType) {
315 for (Po::OptionGroup::option_iterator it = g->begin(), end = g->end(); it != end; ++it) {
316 if (it->get()->name() == name) { return *it; }
317 }
318 return Po::SharedOptPtr(0);
319 }
getOptionPotassco::ProgramOptions::Test::PC320 Po::SharedOptPtr getOption(int, const char*) { return Po::SharedOptPtr(0); }
addValuePotassco::ProgramOptions::Test::PC321 void addValue(const Po::SharedOptPtr& key, const std::string& value) {
322 if (!key->value()->parse(key->name(), value, Po::Value::value_unassigned)) {
323 throw std::logic_error("Invalid value");
324 }
325 }
326 } pc(g);
327 Po::parseCommandString("--int1=10 --int2 22", pc);
328 REQUIRE((i1 == 10 && i2 == 22));
329 }
330 SECTION("parser optionally supports flags with explicit value") {
331 Po::OptionContext ctx;
332 ctx.add(g);
333 std::string cmd = "--flag=false --foo=on";
334 REQUIRE_THROWS_AS(Po::parseCommandString(cmd, ctx, false, 0, 0), Po::SyntaxError);
335 Po::ParsedOptions().assign(Po::parseCommandString(cmd, ctx, false, 0, Po::command_line_allow_flag_value));
336 REQUIRE(flag1 == false);
337 REQUIRE(flag2 == true);
338 }
339 SECTION("parser supports quoting") {
340 std::vector<std::string> tok;
341 Po::OptionGroup g;
342 g.addOptions()("path", Po::storeTo(tok)->composing(), "An int");
343 Po::OptionContext ctx;
344 ctx.add(g);
345 struct P {
funcPotassco::ProgramOptions::Test::P346 static bool func(const std::string&, std::string& o) { o = "path"; return true; }
347 };
348 std::string cmd;
349 cmd.append("foo bar");
350 cmd.append(" \"foo bar\"");
351 cmd.append(" '\\foo bar'");
352 cmd.append(" \\");
353 cmd.append("\"");
354 cmd.append("foo bar");
355 cmd.append("\\");
356 cmd.append("\"");
357 Po::ParsedOptions().assign(Po::parseCommandString(cmd, ctx, false, &P::func));
358 REQUIRE(tok.size() == 6);
359 REQUIRE(tok[0] == "foo");
360 REQUIRE(tok[1] == "bar");
361 REQUIRE(tok[2] == "foo bar");
362 REQUIRE(tok[3] == "\\foo bar");
363 REQUIRE(tok[4] == "\"foo");
364 REQUIRE(tok[5] == "bar\"");
365 tok.clear();
366 cmd = "\\\\\"Hallo Welt\\\\\"";
367 Po::ParsedOptions().assign(Po::parseCommandString(cmd, ctx, false, &P::func));
368 REQUIRE(tok.size() == 1);
369 REQUIRE(tok[0] == "\\Hallo Welt\\");
370 }
371 }
372 }}}
373