cmdline_test.cpp 19 KB

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