Planet
navi homePPSaboutscreenshotsdownloaddevelopmentforum

source: downloads/boost_1_33_1/libs/program_options/test/cmdline_test.cpp @ 13

Last change on this file since 13 was 12, checked in by landauf, 18 years ago

added boost

File size: 17.0 KB
Line 
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#include <boost/program_options/cmdline.hpp>
7#include <boost/program_options/options_description.hpp>
8#include <boost/program_options/detail/cmdline.hpp>
9using namespace boost::program_options;
10using boost::program_options::detail::cmdline;
11
12
13#include <boost/test/test_tools.hpp>
14
15#include <iostream>
16#include <sstream>
17#include <vector>
18#include <cassert>
19using namespace std;
20
21/* To facilitate testing, declare a number of error codes. Otherwise,
22   we'd have to specify the type of exception that should be thrown.
23*/
24
25const int s_success = 0;
26const int s_unknown_option = 1;
27const int s_ambiguous_option = 2;
28const int s_long_not_allowed = 3;
29const int s_long_adjacent_not_allowed = 4;
30const int s_short_adjacent_not_allowed = 5;
31const int s_empty_adjacent_parameter = 6;
32const int s_missing_parameter = 7;
33const int s_extra_parameter = 8;
34
35int translate_syntax_error_kind(invalid_command_line_syntax::kind_t k)
36{
37    invalid_command_line_syntax::kind_t table[] = {
38        invalid_command_line_syntax::long_not_allowed,
39        invalid_command_line_syntax::long_adjacent_not_allowed,
40        invalid_command_line_syntax::short_adjacent_not_allowed,
41        invalid_command_line_syntax::empty_adjacent_parameter,
42        invalid_command_line_syntax::missing_parameter,
43        invalid_command_line_syntax::extra_parameter,
44    };
45    invalid_command_line_syntax::kind_t *b, *e, *i;
46    b = table;
47    e = table + sizeof(table)/sizeof(table[0]);
48    i = std::find(b, e, k);
49    assert(i != e);
50    return std::distance(b, i) + 3;
51}
52
53struct test_case {
54    const char* input;
55    int expected_status;
56    const char* expected_result;
57};
58
59
60/* Parses the syntax description in 'syntax' and initialized
61   'cmd' accordingly'
62   The "boost::program_options" in parameter type is needed because CW9
63   has std::detail and it causes an ambiguity.
64*/
65void apply_syntax(options_description& desc, 
66                  const char* syntax)
67{
68   
69    string s;
70    stringstream ss;
71    ss << syntax;
72    while(ss >> s) {
73        value_semantic* v = 0;
74       
75        if (*(s.end()-1) == '=') {
76            v = value<string>();
77            s.resize(s.size()-1);
78        } else if (*(s.end()-1) == '?') {
79            //v = value<string>()->implicit();
80            v = value<string>();
81            s.resize(s.size()-1);
82        } else if (*(s.end()-1) == '*') {
83            v = value<vector<string> >()->multitoken();
84            s.resize(s.size()-1);
85        } else if (*(s.end()-1) == '+') {
86            v = value<vector<string> >()->multitoken();
87            s.resize(s.size()-1);
88        }
89        if (v) {
90            desc.add_options()
91                (s.c_str(), v, "");
92        } else {
93            desc.add_options()
94                (s.c_str(), "");
95        }
96    }
97}
98
99void test_cmdline(const char* syntax, 
100                  command_line_style::style_t style,
101                  const test_case* cases)
102{
103    for (int i = 0; cases[i].input; ++i) {
104        // Parse input
105        vector<string> xinput;
106        {
107            string s;
108            stringstream ss;
109            ss << cases[i].input;
110            while (ss >> s) {
111                xinput.push_back(s);
112            }
113        }
114        options_description desc;
115        apply_syntax(desc, syntax);
116
117        cmdline cmd(xinput);
118        cmd.style(style);
119        cmd.set_options_description(desc);
120
121
122        string result;
123        int status = 0;
124
125        try {
126            vector<option> options = cmd.run();
127
128            for(unsigned i = 0; i < options.size(); ++i)
129            {
130                option opt = options[i];
131
132                if (opt.position_key != -1) {
133                    if (!result.empty())
134                        result += " ";
135                    result += opt.value[0];
136                } else {
137                    if (!result.empty())
138                        result += " ";
139                    result += opt.string_key + ":";
140                    for (size_t j = 0; j < opt.value.size(); ++j) {
141                        if (j != 0)
142                            result += "-";
143                        result += opt.value[j];
144                    }                   
145                }
146            }
147        }
148        catch(unknown_option& e) {
149            status = s_unknown_option;
150        }
151        catch(ambiguous_option& e) {
152            status = s_ambiguous_option;
153        }
154        catch(invalid_command_line_syntax& e) {
155            status = translate_syntax_error_kind(e.kind());
156        }
157        BOOST_CHECK_EQUAL(status, cases[i].expected_status);
158        BOOST_CHECK_EQUAL(result, cases[i].expected_result);
159    }
160}
161
162void test_long_options()
163{
164    using namespace command_line_style;
165    cmdline::style_t style = cmdline::style_t(
166        allow_long | long_allow_adjacent);
167
168    test_case test_cases1[] = {
169        // Test that long options are recognized and everything else
170        // is treated like arguments
171        {"--foo foo -123 /asd", s_success, "foo: foo -123 /asd"},
172
173        // Unknown option
174        {"--unk", s_unknown_option, ""},
175
176        // Test that abbreviated names do not work
177        {"--fo", s_unknown_option, ""},
178
179        // Test for disallowed parameter
180        {"--foo=13", s_extra_parameter, ""},
181
182        // Test option with required parameter
183        {"--bar=", s_empty_adjacent_parameter, ""},
184        {"--bar", s_missing_parameter, ""},
185
186        {"--bar=123", s_success, "bar:123"},
187        {0}
188    };
189    test_cmdline("foo bar=", style, test_cases1);
190
191
192    style = cmdline::style_t(
193        allow_long | long_allow_next);
194
195    test_case test_cases2[] = {
196        {"--bar 10", s_success, "bar:10"},
197        {"--bar", s_missing_parameter,  ""},
198        // Since --bar accepts a parameter, --foo is
199        // considered a value, even though it looks like
200        // an option.
201        {"--bar --foo", s_success, "bar:--foo"},
202        {0}
203    };
204    test_cmdline("foo bar=", style, test_cases2);
205    style = cmdline::style_t(
206        allow_long | long_allow_adjacent
207        | long_allow_next);
208
209    test_case test_cases3[] = {
210        {"--bar=10", s_success, "bar:10"},
211        {"--bar 11", s_success, "bar:11"},
212        {0}
213    };
214    test_cmdline("foo bar=", style, test_cases3);
215
216    style = cmdline::style_t(
217        allow_long | long_allow_adjacent
218        | long_allow_next | case_insensitive);
219
220// FIXME: restore
221#if 0
222    // Test case insensitive style.
223    // Note that option names are normalized to lower case.
224    test_case test_cases4[] = {
225        {"--foo", s_success, "foo:"},
226        {"--Foo", s_success, "foo:"},
227        {"--bar=Ab", s_success, "bar:Ab"},
228        {"--Bar=ab", s_success, "bar:ab"},
229        {"--giz", s_success, "Giz:"},
230        {0}
231    };
232    test_cmdline("foo bar= baz? Giz", style, test_cases4);
233#endif
234}
235
236void test_short_options()
237{
238    using namespace command_line_style;
239    cmdline::style_t style;
240
241    style = cmdline::style_t(
242        allow_short | allow_dash_for_short
243        | short_allow_adjacent);
244
245    test_case test_cases1[] = {
246        {"-d d /bar", s_success, "-d: d /bar"},
247        // This is treated as error when long options are disabled
248        {"--foo", s_success, "--foo"},
249        {"-d13", s_extra_parameter, ""},
250        {"-f14", s_success, "-f:14"},
251        {"-g -f1", s_success, "-g: -f:1"},
252        {"-f", s_missing_parameter, ""},
253        {0}
254    };
255    test_cmdline(",d ,f= ,g", style, test_cases1);
256
257    style = cmdline::style_t(
258        allow_short | allow_dash_for_short
259        | short_allow_next);
260
261    test_case test_cases2[] = {
262        {"-f 13", s_success, "-f:13"},
263        {"-f -13", s_success, "-f:-13"},
264        {"-f", s_missing_parameter, ""},
265        {"-f /foo", s_success, "-f:/foo"},
266        {"-f -d", s_success, "-f:-d"},
267        {0}
268    };
269    test_cmdline(",d ,f=", style, test_cases2);
270
271    style = cmdline::style_t(
272        allow_short | short_allow_next
273        | allow_dash_for_short | short_allow_adjacent);
274
275    test_case test_cases3[] = {
276        {"-f10", s_success, "-f:10"},
277        {"-f 10", s_success, "-f:10"},
278        {"-f -d", s_success, "-f:-d"},
279        {0}
280    };
281    test_cmdline(",d ,f=", style, test_cases3);
282
283    style = cmdline::style_t(
284        allow_short | short_allow_next
285        | allow_dash_for_short
286        | short_allow_adjacent | allow_sticky);
287
288    test_case test_cases4[] = {
289        {"-de", s_success, "-d: -e:"},
290        {"-df10", s_success, "-d: -f:10"},
291        // FIXME: review
292        //{"-d12", s_extra_parameter, ""},
293        {"-f12", s_success, "-f:12"},
294        {"-fe", s_success, "-f:e"},
295        {0}
296    };
297    test_cmdline(",d ,f= ,e", style, test_cases4);
298
299}
300
301
302void test_dos_options()
303{
304    using namespace command_line_style;
305    cmdline::style_t style;
306
307    style = cmdline::style_t(
308        allow_short
309        | allow_slash_for_short | short_allow_adjacent);
310
311    test_case test_cases1[] = {
312        {"/d d -bar", s_success, "-d: d -bar"},
313        {"--foo", s_success, "--foo"},
314        {"/d13", s_extra_parameter, ""},
315        {"/f14", s_success, "-f:14"},
316        {"/f", s_missing_parameter, ""},
317        {0}
318    };
319    test_cmdline(",d ,f=", style, test_cases1);
320
321    style = cmdline::style_t(
322        allow_short
323        | allow_slash_for_short | short_allow_next
324        | short_allow_adjacent | allow_sticky);
325
326    test_case test_cases2[] = {
327        {"/de", s_extra_parameter, ""},
328        {"/fe", s_success, "-f:e"},
329        {0}
330    };
331    test_cmdline(",d ,f= ,e", style, test_cases2);
332
333}
334
335
336void test_disguised_long()
337{
338    using namespace command_line_style;
339    cmdline::style_t style;
340
341    style = cmdline::style_t(
342        allow_short | short_allow_adjacent
343        | allow_dash_for_short
344        | short_allow_next | allow_long_disguise
345        | long_allow_adjacent);
346
347    test_case test_cases1[] = {
348        {"-foo -f", s_success, "foo: foo:"},
349        {"-goo=x -gy", s_success, "goo:x goo:y"},
350        {"-bee=x -by", s_success, "bee:x bee:y"},
351        {0}
352    };
353    test_cmdline("foo,f goo,g= bee,b?", style, test_cases1);
354
355    style = cmdline::style_t(style | allow_slash_for_short);
356    test_case test_cases2[] = {
357        {"/foo -f", s_success, "foo: foo:"},
358        {"/goo=x", s_success, "goo:x"},
359        {0}
360    };
361    test_cmdline("foo,f goo,g= bee,b?", style, test_cases2);
362}
363
364void test_guessing()
365{
366    using namespace command_line_style;
367    cmdline::style_t style;
368
369    style = cmdline::style_t(
370        allow_short | short_allow_adjacent
371        | allow_dash_for_short       
372        | allow_long | long_allow_adjacent
373        | allow_guessing | allow_long_disguise);
374
375    test_case test_cases1[] = {
376        {"--opt1", s_success, "opt123:"},
377        {"--opt", s_ambiguous_option, ""},
378        {"--f=1", s_success, "foo:1"},
379        {"-far", s_success, "foo:ar"},
380        {0}
381    };
382    test_cmdline("opt123 opt56 foo,f=", style, test_cases1);
383}
384
385void test_arguments()
386{
387    using namespace command_line_style;
388    cmdline::style_t style;
389
390    style = cmdline::style_t(
391        allow_short | allow_long
392        | allow_dash_for_short
393        | short_allow_adjacent | long_allow_adjacent);
394
395    test_case test_cases1[] = {
396        {"-f file -gx file2", s_success, "-f: file -g:x file2"},
397        {"-f - -gx - -- -e", s_success, "-f: - -g:x - -e"},
398        {0}
399    };
400    test_cmdline(",f ,g= ,e", style, test_cases1);
401
402    // "--" should stop options regardless of whether long options are
403    // allowed or not.
404
405    style = cmdline::style_t(
406        allow_short | short_allow_adjacent
407        | allow_dash_for_short);
408
409    test_case test_cases2[] = {
410        {"-f - -gx - -- -e", s_success, "-f: - -g:x - -e"},
411        {0}
412    };
413    test_cmdline(",f ,g= ,e", style, test_cases2);
414}
415
416void test_prefix()
417{
418    using namespace command_line_style;
419    cmdline::style_t style;
420
421    style = cmdline::style_t(
422        allow_short | allow_long
423        | allow_dash_for_short
424        | short_allow_adjacent | long_allow_adjacent
425        );
426
427    test_case test_cases1[] = {
428        {"--foo.bar=12", s_success, "foo.bar:12"},
429        {0}
430    };
431
432    test_cmdline("foo*=", style, test_cases1);
433}
434
435
436pair<string, string> at_option_parser(string const&s)
437{
438    if ('@' == s[0])
439        return std::make_pair(string("response-file"), s.substr(1));
440    else
441        return pair<string, string>();
442}
443
444pair<string, string> at_option_parser_broken(string const&s)
445{
446    if ('@' == s[0])
447        return std::make_pair(string("some garbage"), s.substr(1));
448    else
449        return pair<string, string>();
450}
451
452
453
454void test_additional_parser()
455{
456    options_description desc;
457    desc.add_options()
458        ("response-file", value<string>(), "response file")
459        ("foo", value<int>(), "foo")
460        ;
461
462    vector<string> input;
463    input.push_back("@config");
464    input.push_back("--foo=1");
465
466    cmdline cmd(input);
467    cmd.set_options_description(desc);
468    cmd.set_additional_parser(at_option_parser);
469
470    vector<option> result = cmd.run();
471
472    BOOST_REQUIRE(result.size() == 2);
473    BOOST_CHECK_EQUAL(result[0].string_key, "response-file");
474    BOOST_CHECK_EQUAL(result[0].value[0], "config");
475    BOOST_CHECK_EQUAL(result[1].string_key, "foo");
476    BOOST_CHECK_EQUAL(result[1].value[0], "1");   
477
478    // Test that invalid options returned by additional style
479    // parser are detected.
480    cmdline cmd2(input);
481    cmd2.set_options_description(desc);
482    cmd2.set_additional_parser(at_option_parser_broken);
483
484    BOOST_CHECK_THROW(cmd2.run(), unknown_option);
485
486}
487
488vector<option> at_option_parser2(vector<string>& args)
489{
490    vector<option> result;
491    if ('@' == args[0][0]) {
492        // Simulate reading the response file.
493        result.push_back(option("foo", vector<string>(1, "1")));
494        result.push_back(option("bar", vector<string>(1, "1")));
495        args.erase(args.begin());
496    }
497    return result;
498}
499
500
501void test_style_parser()
502{
503    options_description desc;
504    desc.add_options()
505        ("foo", value<int>(), "foo")
506        ("bar", value<int>(), "bar")
507        ;
508
509    vector<string> input;
510    input.push_back("@config");
511
512    cmdline cmd(input);
513    cmd.set_options_description(desc);
514    cmd.extra_style_parser(at_option_parser2);
515
516    vector<option> result = cmd.run();
517
518    BOOST_REQUIRE(result.size() == 2);
519    BOOST_CHECK_EQUAL(result[0].string_key, "foo");
520    BOOST_CHECK_EQUAL(result[0].value[0], "1");   
521    BOOST_CHECK_EQUAL(result[1].string_key, "bar");
522    BOOST_CHECK_EQUAL(result[1].value[0], "1");   
523}
524
525void test_unregistered()
526{
527    // Check unregisted option when no options are registed at all.
528    options_description desc;
529
530    vector<string> input;
531    input.push_back("--foo=1");
532    input.push_back("--bar");
533    input.push_back("1");
534    input.push_back("-b");
535    input.push_back("-biz");
536
537    cmdline cmd(input);
538    cmd.set_options_description(desc);
539    cmd.allow_unregistered();
540   
541    vector<option> result = cmd.run();
542    BOOST_REQUIRE(result.size() == 5);
543    // --foo=1
544    BOOST_CHECK_EQUAL(result[0].string_key, "foo");
545    BOOST_CHECK_EQUAL(result[0].unregistered, true);
546    BOOST_CHECK_EQUAL(result[0].value[0], "1");
547    // --bar
548    BOOST_CHECK_EQUAL(result[1].string_key, "bar");
549    BOOST_CHECK_EQUAL(result[1].unregistered, true);
550    BOOST_CHECK(result[1].value.empty());
551    // '1' is considered a positional option, not a value to
552    // --bar
553    BOOST_CHECK(result[2].string_key.empty());
554    BOOST_CHECK(result[2].position_key == 0);
555    BOOST_CHECK_EQUAL(result[2].unregistered, false);
556    BOOST_CHECK_EQUAL(result[2].value[0], "1");
557    // -b
558    BOOST_CHECK_EQUAL(result[3].string_key, "-b");
559    BOOST_CHECK_EQUAL(result[3].unregistered, true);
560    BOOST_CHECK(result[3].value.empty());
561    // -biz
562    BOOST_CHECK_EQUAL(result[4].string_key, "-b");
563    BOOST_CHECK_EQUAL(result[4].unregistered, true);
564    BOOST_CHECK_EQUAL(result[4].value[0], "iz");
565
566    // Check sticky short options together with unregisted options.
567   
568    desc.add_options()
569        ("help,h", "")
570        ("magic,m", value<string>(), "")
571        ;
572
573    input.clear();
574    input.push_back("-hc");
575    input.push_back("-mc");
576
577
578    cmdline cmd2(input);
579    cmd2.set_options_description(desc);
580    cmd2.allow_unregistered();
581   
582    result = cmd2.run();
583
584    BOOST_REQUIRE(result.size() == 3);
585    BOOST_CHECK_EQUAL(result[0].string_key, "help");
586    BOOST_CHECK_EQUAL(result[0].unregistered, false);
587    BOOST_CHECK(result[0].value.empty());
588    BOOST_CHECK_EQUAL(result[1].string_key, "-c");
589    BOOST_CHECK_EQUAL(result[1].unregistered, true);
590    BOOST_CHECK(result[1].value.empty());
591    BOOST_CHECK_EQUAL(result[2].string_key, "magic");
592    BOOST_CHECK_EQUAL(result[2].unregistered, false);
593    BOOST_CHECK_EQUAL(result[2].value[0], "c");
594
595    // CONSIDER:
596    // There's a corner case:
597    //   -foo
598    // when 'allow_long_disguise' is set. Should this be considered
599    // disguised long option 'foo' or short option '-f' with value 'oo'?
600    // It's not clear yet, so I'm leaving the decision till later.
601}
602
603int test_main(int ac, char* av[])
604{
605    test_long_options();
606    test_short_options();
607    test_dos_options();
608    test_disguised_long();
609    test_guessing();
610    test_arguments();
611    test_prefix();
612    test_additional_parser();
613    test_style_parser();
614    test_unregistered();
615
616    return 0;
617}
Note: See TracBrowser for help on using the repository browser.