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