1 | // Copyright Vladimir Prus 2002-2004. |
2 | // Distributed under the Boost Software License, Version 1.0. |
3 | // (See accompanying file LICENSE_1_0.txt |
4 | // or copy at http://www.boost.org/LICENSE_1_0.txt) |
5 | |
6 | |
7 | #include <boost/program_options/parsers.hpp> |
8 | #include <boost/program_options/options_description.hpp> |
9 | #include <boost/program_options/variables_map.hpp> |
10 | using namespace boost::program_options; |
11 | // We'll use po::value everywhere to workaround vc6 bug. |
12 | namespace po = boost::program_options; |
13 | |
14 | #include <boost/function.hpp> |
15 | using namespace boost; |
16 | |
17 | #include <sstream> |
18 | #include <iostream> |
19 | #include <iomanip> |
20 | using namespace std; |
21 | |
22 | #if defined(__sun) |
23 | #include <stdlib.h> // for putenv on solaris |
24 | #else |
25 | #include <cstdlib> // for putenv |
26 | #endif |
27 | |
28 | #include "minitest.hpp" |
29 | |
30 | #define TEST_CHECK_THROW(expression, exception, description) \ |
31 | try \ |
32 | { \ |
33 | expression; \ |
34 | BOOST_ERROR(description);\ |
35 | throw 10; \ |
36 | } \ |
37 | catch(exception &) \ |
38 | { \ |
39 | } |
40 | |
41 | pair<string, vector< vector<string> > > msp(const string& s1) |
42 | { |
43 | return std::make_pair(x: s1, y: vector< vector<string> >()); |
44 | } |
45 | |
46 | |
47 | pair<string, vector< vector<string> > > msp(const string& s1, const string& s2) |
48 | { |
49 | vector< vector<string> > v(1); |
50 | v[0].push_back(x: s2); |
51 | return std::make_pair(x: s1, y&: v); |
52 | } |
53 | |
54 | void check_value(const option& option, const char* name, const char* value) |
55 | { |
56 | BOOST_CHECK(option.string_key == name); |
57 | BOOST_REQUIRE(option.value.size() == 1); |
58 | BOOST_CHECK(option.value.front() == value); |
59 | } |
60 | |
61 | vector<string> sv(const char* array[], unsigned size) |
62 | { |
63 | vector<string> r; |
64 | for (unsigned i = 0; i < size; ++i) |
65 | r.push_back(x: array[i]); |
66 | return r; |
67 | } |
68 | |
69 | pair<string, string> additional_parser(const std::string&) |
70 | { |
71 | return pair<string, string>(); |
72 | } |
73 | |
74 | namespace command_line { |
75 | |
76 | #if 0 |
77 | // The following commented out blocks used to test parsing |
78 | // command line without syntax specification behaviour. |
79 | // It is disabled now and probably will never be enabled again: |
80 | // it is not possible to figure out what command line means without |
81 | // user's help. |
82 | void test_parsing_without_specifying_options() { |
83 | char* cmdline1[] = { "--a" , "--b=12" , "-f" , "-g4" , "-" , "file" }; |
84 | options_and_arguments a1 = parse_command_line(cmdline1, |
85 | cmdline1 + sizeof(cmdline1) / sizeof(cmdline1[0])); |
86 | BOOST_REQUIRE(a1.options().size() == 4); |
87 | BOOST_CHECK(a1.options()[0] == msp("a" , "" )); |
88 | BOOST_CHECK(a1.options()[1] == msp("b" , "12" )); |
89 | BOOST_CHECK(a1.options()[2] == msp("-f" , "" )); |
90 | BOOST_CHECK(a1.options()[3] == msp("-g" , "4" )); |
91 | BOOST_REQUIRE(a1.arguments().size() == 2); |
92 | BOOST_CHECK(a1.arguments()[0] == "-" ); |
93 | BOOST_CHECK(a1.arguments()[1] == "file" ); |
94 | char* cmdline2[] = { "--a" , "--" , "file" }; |
95 | options_and_arguments a2 = parse_command_line(cmdline2, |
96 | cmdline2 + sizeof(cmdline2) / sizeof(cmdline2[0])); |
97 | BOOST_REQUIRE(a2.options().size() == 1); |
98 | BOOST_CHECK(a2.options()[0] == msp("a" , "" )); |
99 | BOOST_CHECK(a2.arguments().size() == 1); |
100 | BOOST_CHECK(a2.arguments()[0] == "file" ); |
101 | } |
102 | #endif |
103 | |
104 | void test_many_different_options() { |
105 | options_description desc; |
106 | desc.add_options() |
107 | ("foo,f" , new untyped_value(), "" ) |
108 | ( // Explicit qualification is a workaround for vc6 |
109 | "bar,b" , po::value<std::string>(), "" ) |
110 | ("car,voiture" , new untyped_value()) |
111 | ("dog,dawg" , new untyped_value()) |
112 | ("baz" , new untyped_value()) |
113 | ("plug*" , new untyped_value()); |
114 | const char* cmdline3_[] = { "--foo=12" , "-f4" , "--bar=11" , "-b4" , |
115 | "--voiture=15" , "--dawg=16" , "--dog=17" , "--plug3=10" }; |
116 | vector<string> cmdline3 = sv(array: cmdline3_, |
117 | size: sizeof(cmdline3_) / sizeof(const char*)); |
118 | vector<option> a3 = |
119 | command_line_parser(cmdline3).options(desc).run().options; |
120 | BOOST_CHECK_EQUAL(a3.size(), 8u); |
121 | check_value(option: a3[0], name: "foo" , value: "12" ); |
122 | check_value(option: a3[1], name: "foo" , value: "4" ); |
123 | check_value(option: a3[2], name: "bar" , value: "11" ); |
124 | check_value(option: a3[3], name: "bar" , value: "4" ); |
125 | check_value(option: a3[4], name: "car" , value: "15" ); |
126 | check_value(option: a3[5], name: "dog" , value: "16" ); |
127 | check_value(option: a3[6], name: "dog" , value: "17" ); |
128 | check_value(option: a3[7], name: "plug3" , value: "10" ); |
129 | |
130 | // Regression test: check that '0' as style is interpreted as |
131 | // 'default_style' |
132 | vector<option> a4 = parse_command_line( |
133 | argc: sizeof(cmdline3_) / sizeof(const char*), argv: cmdline3_, desc, style: 0, |
134 | ext: additional_parser).options; |
135 | // The default style is unix-style, where the first argument on the command-line |
136 | // is the name of a binary, not an option value, so that should be ignored |
137 | BOOST_CHECK_EQUAL(a4.size(), 7u); |
138 | check_value(option: a4[0], name: "foo" , value: "4" ); |
139 | check_value(option: a4[1], name: "bar" , value: "11" ); |
140 | check_value(option: a4[2], name: "bar" , value: "4" ); |
141 | check_value(option: a4[3], name: "car" , value: "15" ); |
142 | check_value(option: a4[4], name: "dog" , value: "16" ); |
143 | check_value(option: a4[5], name: "dog" , value: "17" ); |
144 | check_value(option: a4[6], name: "plug3" , value: "10" ); |
145 | } |
146 | |
147 | void test_not_crashing_with_empty_string_values() { |
148 | // Check that we don't crash on empty values of type 'string' |
149 | const char* cmdline4[] = { "" , "--open" , "" }; |
150 | options_description desc2; |
151 | desc2.add_options()("open" , po::value<string>()); |
152 | variables_map vm; |
153 | po::store( |
154 | options: po::parse_command_line(argc: sizeof(cmdline4) / sizeof(const char*), |
155 | argv: const_cast<char**>(cmdline4), desc: desc2), m&: vm); |
156 | } |
157 | |
158 | void test_multitoken() { |
159 | const char* cmdline5[] = { "" , "-p7" , "-o" , "1" , "2" , "3" , "-x8" }; |
160 | options_description desc3; |
161 | desc3.add_options() |
162 | (",p" , po::value<string>()) |
163 | (",o" , po::value<string>()->multitoken()) |
164 | (",x" , po::value<string>()); |
165 | vector<option> a5 = parse_command_line( |
166 | argc: sizeof(cmdline5) / sizeof(const char*), |
167 | argv: const_cast<char**>(cmdline5), desc: desc3, style: 0, ext: additional_parser).options; |
168 | BOOST_CHECK_EQUAL(a5.size(), 3u); |
169 | check_value(option: a5[0], name: "-p" , value: "7" ); |
170 | BOOST_REQUIRE(a5[1].value.size() == 3); |
171 | BOOST_CHECK_EQUAL(a5[1].string_key, "-o" ); |
172 | BOOST_CHECK_EQUAL(a5[1].value[0], "1" ); |
173 | BOOST_CHECK_EQUAL(a5[1].value[1], "2" ); |
174 | BOOST_CHECK_EQUAL(a5[1].value[2], "3" ); |
175 | check_value(option: a5[2], name: "-x" , value: "8" ); |
176 | } |
177 | |
178 | void test_multitoken_and_multiname() { |
179 | const char* cmdline[] = { "program" , "-fone" , "-b" , "two" , "--foo" , "three" , "four" , "-zfive" , "--fee" , "six" }; |
180 | options_description desc; |
181 | desc.add_options() |
182 | ("bar,b" , po::value<string>()) |
183 | ("foo,fee,f" , po::value<string>()->multitoken()) |
184 | ("fizbaz,baz,z" , po::value<string>()); |
185 | |
186 | vector<option> parsed_options = parse_command_line( |
187 | argc: sizeof(cmdline) / sizeof(const char*), |
188 | argv: const_cast<char**>(cmdline), desc, style: 0, ext: additional_parser).options; |
189 | |
190 | BOOST_CHECK_EQUAL(parsed_options.size(), 5u); |
191 | check_value(option: parsed_options[0], name: "foo" , value: "one" ); |
192 | check_value(option: parsed_options[1], name: "bar" , value: "two" ); |
193 | BOOST_CHECK_EQUAL(parsed_options[2].string_key, "foo" ); |
194 | BOOST_REQUIRE(parsed_options[2].value.size() == 2); |
195 | BOOST_CHECK_EQUAL(parsed_options[2].value[0], "three" ); |
196 | BOOST_CHECK_EQUAL(parsed_options[2].value[1], "four" ); |
197 | check_value(option: parsed_options[3], name: "fizbaz" , value: "five" ); |
198 | check_value(option: parsed_options[4], name: "foo" , value: "six" ); |
199 | |
200 | const char* cmdline_2[] = { "program" , "-fone" , "-b" , "two" , "--fee" , "three" , "four" , "-zfive" , "--foo" , "six" }; |
201 | |
202 | parsed_options = parse_command_line( |
203 | argc: sizeof(cmdline_2) / sizeof(const char*), |
204 | argv: const_cast<char**>(cmdline_2), desc, style: 0, ext: additional_parser).options; |
205 | |
206 | BOOST_CHECK_EQUAL(parsed_options.size(), 5u); |
207 | check_value(option: parsed_options[0], name: "foo" , value: "one" ); |
208 | check_value(option: parsed_options[1], name: "bar" , value: "two" ); |
209 | BOOST_CHECK_EQUAL(parsed_options[2].string_key, "foo" ); |
210 | BOOST_REQUIRE(parsed_options[2].value.size() == 2); |
211 | BOOST_CHECK_EQUAL(parsed_options[2].value[0], "three" ); |
212 | BOOST_CHECK_EQUAL(parsed_options[2].value[1], "four" ); |
213 | check_value(option: parsed_options[3], name: "fizbaz" , value: "five" ); |
214 | check_value(option: parsed_options[4], name: "foo" , value: "six" ); |
215 | } |
216 | |
217 | |
218 | void test_multitoken_vector_option() { |
219 | po::options_description desc4("" ); |
220 | desc4.add_options() |
221 | ("multitoken,multi-token,m" , po::value<std::vector<std::string> >()->multitoken(), "values" ) |
222 | ("file" , po::value<std::string>(), "the file to process" ); |
223 | po::positional_options_description p; |
224 | p.add(name: "file" , max_count: 1); |
225 | const char* cmdline6[] = { "" , "-m" , "token1" , "token2" , "--" , "some_file" }; |
226 | vector<option> a6 = |
227 | command_line_parser(sizeof(cmdline6) / sizeof(const char*), |
228 | const_cast<char**>(cmdline6)).options(desc: desc4).positional(desc: p).run().options; |
229 | BOOST_CHECK_EQUAL(a6.size(), 2u); |
230 | BOOST_REQUIRE(a6[0].value.size() == 2); |
231 | BOOST_CHECK_EQUAL(a6[0].string_key, "multitoken" ); |
232 | BOOST_CHECK_EQUAL(a6[0].value[0], "token1" ); |
233 | BOOST_CHECK_EQUAL(a6[0].value[1], "token2" ); |
234 | BOOST_CHECK_EQUAL(a6[1].string_key, "file" ); |
235 | BOOST_REQUIRE(a6[1].value.size() == 1); |
236 | BOOST_CHECK_EQUAL(a6[1].value[0], "some_file" ); |
237 | } |
238 | |
239 | } // namespace command_line |
240 | |
241 | void test_command_line() |
242 | { |
243 | #if 0 |
244 | command_line::test_parsing_without_specifying_options(); |
245 | #endif |
246 | command_line::test_many_different_options(); |
247 | // Check that we don't crash on empty values of type 'string' |
248 | command_line::test_not_crashing_with_empty_string_values(); |
249 | command_line::test_multitoken(); |
250 | command_line::test_multitoken_vector_option(); |
251 | command_line::test_multitoken_and_multiname(); |
252 | } |
253 | |
254 | void test_config_file(const char* config_file) |
255 | { |
256 | options_description desc; |
257 | desc.add_options() |
258 | ("gv1" , new untyped_value) |
259 | ("gv2" , new untyped_value) |
260 | ("empty_value" , new untyped_value) |
261 | ("plug*" , new untyped_value) |
262 | ("m1.v1" , new untyped_value) |
263 | ("m1.v2" , new untyped_value) |
264 | ("m1.v3,alias3" , new untyped_value) |
265 | ("b" , bool_switch()) |
266 | ; |
267 | |
268 | const char content1[] = |
269 | " gv1 = 0#asd\n" |
270 | "empty_value = \n" |
271 | "plug3 = 7\n" |
272 | "b = true\n" |
273 | "[m1]\n" |
274 | "v1 = 1\n" |
275 | "\n" |
276 | "v2 = 2\n" |
277 | "v3 = 3\n" |
278 | ; |
279 | |
280 | stringstream ss(content1); |
281 | vector<option> a1 = parse_config_file(ss, desc).options; |
282 | BOOST_REQUIRE(a1.size() == 7); |
283 | check_value(option: a1[0], name: "gv1" , value: "0" ); |
284 | check_value(option: a1[1], name: "empty_value" , value: "" ); |
285 | check_value(option: a1[2], name: "plug3" , value: "7" ); |
286 | check_value(option: a1[3], name: "b" , value: "true" ); |
287 | check_value(option: a1[4], name: "m1.v1" , value: "1" ); |
288 | check_value(option: a1[5], name: "m1.v2" , value: "2" ); |
289 | check_value(option: a1[6], name: "m1.v3" , value: "3" ); |
290 | |
291 | // same test, but now options come from file |
292 | vector<option> a2 = parse_config_file<char>(filename: config_file, desc).options; |
293 | BOOST_REQUIRE(a2.size() == 7); |
294 | check_value(option: a2[0], name: "gv1" , value: "0" ); |
295 | check_value(option: a2[1], name: "empty_value" , value: "" ); |
296 | check_value(option: a2[2], name: "plug3" , value: "7" ); |
297 | check_value(option: a2[3], name: "b" , value: "true" ); |
298 | check_value(option: a2[4], name: "m1.v1" , value: "1" ); |
299 | check_value(option: a2[5], name: "m1.v2" , value: "2" ); |
300 | check_value(option: a2[6], name: "m1.v3" , value: "3" ); |
301 | } |
302 | |
303 | void test_environment() |
304 | { |
305 | options_description desc; |
306 | desc.add_options() |
307 | ("foo" , new untyped_value, "" ) |
308 | ("bar" , new untyped_value, "" ) |
309 | ; |
310 | |
311 | #if (defined(_WIN32) && ! defined(BOOST_BORLANDC) && ! defined(BOOST_EMBTC)) || (defined(__CYGWIN__)) |
312 | _putenv("PO_TEST_FOO=1" ); |
313 | #else |
314 | putenv(string: const_cast<char*>("PO_TEST_FOO=1" )); |
315 | #endif |
316 | parsed_options p = parse_environment(desc, prefix: "PO_TEST_" ); |
317 | |
318 | BOOST_REQUIRE(p.options.size() == 1); |
319 | BOOST_CHECK (p.options[0].string_key == "foo" ); |
320 | BOOST_REQUIRE(p.options[0].value.size() == 1); |
321 | BOOST_CHECK (p.options[0].value[0] == "1" ); |
322 | |
323 | //TODO: since 'bar' does not allow a value, it cannot appear in environemt, |
324 | // which already has a value. |
325 | } |
326 | |
327 | void test_unregistered() |
328 | { |
329 | options_description desc; |
330 | |
331 | const char* cmdline1_[] = { "--foo=12" , "--bar" , "1" }; |
332 | vector<string> cmdline1 = sv(array: cmdline1_, |
333 | size: sizeof(cmdline1_)/sizeof(const char*)); |
334 | vector<option> a1 = |
335 | command_line_parser(cmdline1).options(desc).allow_unregistered().run() |
336 | .options; |
337 | |
338 | BOOST_REQUIRE(a1.size() == 3); |
339 | BOOST_CHECK(a1[0].string_key == "foo" ); |
340 | BOOST_CHECK(a1[0].unregistered == true); |
341 | BOOST_REQUIRE(a1[0].value.size() == 1); |
342 | BOOST_CHECK(a1[0].value[0] == "12" ); |
343 | BOOST_CHECK(a1[1].string_key == "bar" ); |
344 | BOOST_CHECK(a1[1].unregistered == true); |
345 | BOOST_CHECK(a1[2].string_key == "" ); |
346 | BOOST_CHECK(a1[2].unregistered == false); |
347 | |
348 | |
349 | vector<string> a2 = collect_unrecognized(options: a1, mode: include_positional); |
350 | BOOST_CHECK(a2[0] == "--foo=12" ); |
351 | BOOST_CHECK(a2[1] == "--bar" ); |
352 | BOOST_CHECK(a2[2] == "1" ); |
353 | |
354 | // Test that storing unregisted options has no effect |
355 | variables_map vm; |
356 | |
357 | store(options: command_line_parser(cmdline1).options(desc). |
358 | allow_unregistered().run(), |
359 | m&: vm); |
360 | |
361 | BOOST_CHECK_EQUAL(vm.size(), 0u); |
362 | |
363 | |
364 | const char content1[] = |
365 | "gv1 = 0\n" |
366 | "[m1]\n" |
367 | "v1 = 1\n" |
368 | ; |
369 | |
370 | stringstream ss(content1); |
371 | vector<option> a3 = parse_config_file(ss, desc, allow_unregistered: true).options; |
372 | BOOST_REQUIRE(a3.size() == 2); |
373 | cout << "XXX" << a3[0].value.front() << "\n" ; |
374 | check_value(option: a3[0], name: "gv1" , value: "0" ); |
375 | check_value(option: a3[1], name: "m1.v1" , value: "1" ); |
376 | } |
377 | |
378 | |
379 | |
380 | int main(int, char* av[]) |
381 | { |
382 | test_command_line(); |
383 | test_config_file(config_file: av[1]); |
384 | test_environment(); |
385 | test_unregistered(); |
386 | return 0; |
387 | } |
388 | |
389 | |