// Copyright Leo Goodstadt 2012 // Distributed under the Boost Software License, Version 1.0. // (See accompanying file LICENSE_1_0.txt // or copy at http://www.boost.org/LICENSE_1_0.txt) #include #include #include #include using namespace boost::program_options; #include #include #include #include using namespace std; #include "minitest.hpp" // // like BOOST_CHECK_EQUAL but with more descriptive error message // #define CHECK_EQUAL(description, a, b) if (a != b) {std::cerr << "\n\nError:\n<<" << \ description << ">>\n Expected text=\"" << b << "\"\n Actual text =\"" << a << "\"\n\n"; assert(a == b);} // Uncomment for Debugging, removes asserts so we can see more failures! //#define BOOST_ERROR(description) std::cerr << description; std::cerr << "\n"; //8888888888888888888888888888888888888888888888888888888888888888888888888888888888888888 // // Uncomment to print out the complete set of diagnostic messages for the different test cases /* #define CHECK_EQUAL(description, a, b) if (a != b) {std::cerr << "\n\nError: " << \ description << "\n Expecting\n" << b << "\n Found\n" << a << "\n\n"; } \ else {std::cout << description<< "\t" << b << "\n";} */ //8888888888888888888888888888888888888888888888888888888888888888888888888888888888888888 // // test exception for each specified command line style, e.g. short dash or config file // template void test_each_exception_message(const string& test_description, const vector& argv, options_description& desc, int style, string exception_msg, istream& is = cin) { if (exception_msg.length() == 0) return; variables_map vm; unsigned argc = argv.size(); try { if (style == -1) store(parse_config_file(is, desc), vm); else store(parse_command_line(argv.size(), &argv[0], desc, style), vm); notify(vm); } catch (EXCEPTION& e) { //cerr << "Correct:\n\t" << e.what() << "\n"; CHECK_EQUAL(test_description, e.what(), exception_msg); return; } catch (std::exception& e) { // concatenate argv without boost::algorithm::join string argv_txt; for (unsigned ii = 0; ii < argc - 1; ++ii) argv_txt += argv[ii] + string(" "); if (argc) argv_txt += argv[argc - 1]; BOOST_ERROR("\n<<" + test_description + string(">>\n Unexpected exception type!\n Actual text =\"") + e.what() + "\"\n argv =\"" + argv_txt + "\"\n Expected text=\"" + exception_msg + "\"\n"); return; } BOOST_ERROR(test_description + ": No exception thrown. "); } // // test exception messages for all command line styles (unix/long/short/slash/config file) // // try each command line style in turn const int unix_style = command_line_style::unix_style; const int short_dash = command_line_style::allow_dash_for_short | command_line_style::allow_short | command_line_style::short_allow_adjacent | command_line_style::allow_sticky; const int short_slash = command_line_style::allow_slash_for_short | command_line_style::allow_short | command_line_style::short_allow_adjacent; const int long_dash = command_line_style::allow_long | command_line_style::long_allow_adjacent | command_line_style::allow_guessing; template void test_exception_message(const vector >& argv, options_description& desc, const string& error_description, const char* expected_message_template[5]) { string expected_message; // unix expected_message = expected_message_template[0]; test_each_exception_message(error_description + " -- unix", argv[0], desc, unix_style, expected_message); // long dash only expected_message = expected_message_template[1]; test_each_exception_message(error_description + " -- long_dash", argv[1], desc, long_dash, expected_message); // short dash only expected_message = expected_message_template[2]; test_each_exception_message(error_description + " -- short_dash", argv[2], desc, short_dash, expected_message); // short slash only expected_message = expected_message_template[3]; test_each_exception_message(error_description + " -- short_slash", argv[3], desc, short_slash, expected_message); // config file only expected_message = expected_message_template[4]; if (expected_message.length()) { istringstream istrm(argv[4][0]); test_each_exception_message(error_description + " -- config_file", argv[4], desc, -1, expected_message, istrm); } } #define VEC_STR_PUSH_BACK(vec, c_array) \ vec.push_back(vector(c_array, c_array + sizeof(c_array) / sizeof(char*))); //________________________________________________________________________________________ // // invalid_option_value // //________________________________________________________________________________________ void test_invalid_option_value_exception_msg() { options_description desc; desc.add_options() ("int-option,d", value< int >(), "An option taking an integer") ; vector > argv; const char* argv0[] = { "program", "-d", "A_STRING"} ; VEC_STR_PUSH_BACK(argv, argv0); const char* argv1[] = { "program", "--int", "A_STRING"}; VEC_STR_PUSH_BACK(argv, argv1); const char* argv2[] = { "program", "-d", "A_STRING"} ; VEC_STR_PUSH_BACK(argv, argv2); const char* argv3[] = { "program", "/d", "A_STRING"} ; VEC_STR_PUSH_BACK(argv, argv3); const char* argv4[] = { "int-option=A_STRING"} ; VEC_STR_PUSH_BACK(argv, argv4); const char* expected_msg[5] = { "the argument ('A_STRING') for option '--int-option' is invalid", "the argument ('A_STRING') for option '--int-option' is invalid", "the argument ('A_STRING') for option '-d' is invalid", "the argument ('A_STRING') for option '/d' is invalid", "the argument ('A_STRING') for option 'int-option' is invalid", }; test_exception_message(argv, desc, "invalid_option_value", expected_msg); } //________________________________________________________________________________________ // // missing_value // //________________________________________________________________________________________ void test_missing_value_exception_msg() { options_description desc; desc.add_options() ("cfgfile,e", value(), "the config file") ("output,o", value(), "the output file") ; vector > argv; const char* argv0[] = { "program", "-e", "-e", "output.txt"} ; VEC_STR_PUSH_BACK(argv, argv0); const char* argv1[] = { "program", "--cfgfile"} ; VEC_STR_PUSH_BACK(argv, argv1); const char* argv2[] = { "program", "-e", "-e", "output.txt"} ; VEC_STR_PUSH_BACK(argv, argv2); const char* argv3[] = { "program", "/e", "/e", "output.txt"} ; VEC_STR_PUSH_BACK(argv, argv3); const char* argv4[] = { ""} ; VEC_STR_PUSH_BACK(argv, argv4); const char* expected_msg[5] = { "the required argument for option '--cfgfile' is missing", "the required argument for option '--cfgfile' is missing", "the required argument for option '-e' is missing", "", // Ignore probable bug in cmdline::finish_option //"the required argument for option '/e' is missing", "", }; test_exception_message(argv, desc, "invalid_syntax::missing_parameter", expected_msg); } //________________________________________________________________________________________ // // ambiguous_option // //________________________________________________________________________________________ void test_ambiguous_option_exception_msg() { options_description desc; desc.add_options() ("cfgfile1,c", value(), "the config file") ("cfgfile2,o", value(), "the config file") ("good,g", "good option") ("output,c", value(), "the output file") ("output", value(), "the output file") ; vector > argv; const char* argv0[] = {"program", "-ggc", "file", "-o", "anotherfile"} ; VEC_STR_PUSH_BACK(argv, argv0); const char* argv1[] = {"program", "--cfgfile", "file", "--cfgfile", "anotherfile"} ; VEC_STR_PUSH_BACK(argv, argv1); const char* argv2[] = {"program", "-ggc", "file", "-o", "anotherfile"} ; VEC_STR_PUSH_BACK(argv, argv2); const char* argv3[] = {"program", "/c", "file", "/o", "anotherfile"} ; VEC_STR_PUSH_BACK(argv, argv3); const char* argv4[] = { "output=output.txt\n"} ; VEC_STR_PUSH_BACK(argv, argv4); const char* expected_msg[5] = { "option '-c' is ambiguous and matches '--cfgfile1', and '--output'", "option '--cfgfile' is ambiguous and matches '--cfgfile1', and '--cfgfile2'", "option '-c' is ambiguous", "option '/c' is ambiguous", "option 'output' is ambiguous and matches different versions of 'output'", }; test_exception_message(argv, desc, "ambiguous_option", expected_msg); } //________________________________________________________________________________________ // // multiple_occurrences // //________________________________________________________________________________________ void test_multiple_occurrences_exception_msg() { options_description desc; desc.add_options() ("cfgfile,c", value(), "the configfile") ; vector > argv; const char* argv0[] = {"program", "-c", "file", "-c", "anotherfile"} ; VEC_STR_PUSH_BACK(argv, argv0); const char* argv1[] = {"program", "--cfgfi", "file", "--cfgfi", "anotherfile"} ; VEC_STR_PUSH_BACK(argv, argv1); const char* argv2[] = {"program", "-c", "file", "-c", "anotherfile"} ; VEC_STR_PUSH_BACK(argv, argv2); const char* argv3[] = {"program", "/c", "file", "/c", "anotherfile"} ; VEC_STR_PUSH_BACK(argv, argv3); const char* argv4[] = { "cfgfile=output.txt\ncfgfile=output.txt\n"} ; VEC_STR_PUSH_BACK(argv, argv4); const char* expected_msg[5] = { "option '--cfgfile' cannot be specified more than once", "option '--cfgfile' cannot be specified more than once", "option '-c' cannot be specified more than once", "option '/c' cannot be specified more than once", "option 'cfgfile' cannot be specified more than once", }; test_exception_message(argv, desc, "multiple_occurrences", expected_msg); } //________________________________________________________________________________________ // // unknown_option // //________________________________________________________________________________________ void test_unknown_option_exception_msg() { options_description desc; desc.add_options() ("good,g", "good option") ; vector > argv; const char* argv0[] = {"program", "-ggc", "file"} ; VEC_STR_PUSH_BACK(argv, argv0); const char* argv1[] = {"program", "--cfgfile", "file"} ; VEC_STR_PUSH_BACK(argv, argv1); const char* argv2[] = {"program", "-ggc", "file"} ; VEC_STR_PUSH_BACK(argv, argv2); const char* argv3[] = {"program", "/c", "file"} ; VEC_STR_PUSH_BACK(argv, argv3); const char* argv4[] = { "cfgfile=output.txt\n"} ; VEC_STR_PUSH_BACK(argv, argv4); const char* expected_msg[5] = { "unrecognised option '-ggc'", "unrecognised option '--cfgfile'", "unrecognised option '-ggc'", "unrecognised option '/c'", "unrecognised option 'cfgfile'", }; test_exception_message(argv, desc, "unknown_option", expected_msg); } //________________________________________________________________________________________ // // validation_error::invalid_bool_value // //________________________________________________________________________________________ void test_invalid_bool_value_exception_msg() { options_description desc; desc.add_options() ("bool_option,b", value< bool>(), "bool_option") ; vector > argv; const char* argv0[] = {"program", "-b", "file"} ; VEC_STR_PUSH_BACK(argv, argv0); const char* argv1[] = {"program", "--bool_optio", "file"} ; VEC_STR_PUSH_BACK(argv, argv1); const char* argv2[] = {"program", "-b", "file"} ; VEC_STR_PUSH_BACK(argv, argv2); const char* argv3[] = {"program", "/b", "file"} ; VEC_STR_PUSH_BACK(argv, argv3); const char* argv4[] = { "bool_option=output.txt\n"} ; VEC_STR_PUSH_BACK(argv, argv4); const char* expected_msg[5] = { "the argument ('file') for option '--bool_option' is invalid. Valid choices are 'on|off', 'yes|no', '1|0' and 'true|false'", "the argument ('file') for option '--bool_option' is invalid. Valid choices are 'on|off', 'yes|no', '1|0' and 'true|false'", "the argument ('file') for option '-b' is invalid. Valid choices are 'on|off', 'yes|no', '1|0' and 'true|false'", "the argument ('file') for option '/b' is invalid. Valid choices are 'on|off', 'yes|no', '1|0' and 'true|false'", "the argument ('output.txt') for option 'bool_option' is invalid. Valid choices are 'on|off', 'yes|no', '1|0' and 'true|false'", }; test_exception_message(argv, desc, "validation_error::invalid_bool_value", expected_msg); } //________________________________________________________________________________________ // // validation_error::multiple_values_not_allowed // //________________________________________________________________________________________ // // Strange exception: sole purpose seems to be catching multitoken() associated with a scalar // validation_error::multiple_values_not_allowed seems thus to be a programmer error // // void test_multiple_values_not_allowed_exception_msg() { options_description desc; desc.add_options() ("cfgfile,c", value()->multitoken(), "the config file") ("good,g", "good option") ("output,o", value(), "the output file") ; vector > argv; const char* argv0[] = { "program", "-c", "file", "c", "-o", "fritz", "hugo" } ; VEC_STR_PUSH_BACK(argv, argv0); const char* argv1[] = { "program", "--cfgfil", "file", "c", "--outpu", "fritz", "hugo" } ; VEC_STR_PUSH_BACK(argv, argv1); const char* argv2[] = { "program", "-c", "file", "c", "-o", "fritz", "hugo"} ; VEC_STR_PUSH_BACK(argv, argv2); const char* argv3[] = { "program", "/c", "file", "c", "/o", "fritz", "hugo"} ; VEC_STR_PUSH_BACK(argv, argv3); const char* argv4[] = { "" } ; VEC_STR_PUSH_BACK(argv, argv4); const char* expected_msg[5] = { "option '--cfgfile' only takes a single argument", "option '--cfgfile' only takes a single argument", "option '-c' only takes a single argument", "option '/c' only takes a single argument", "", }; test_exception_message(argv, desc, "validation_error::multiple_values_not_allowed", expected_msg); } //________________________________________________________________________________________ // // validation_error::at_least_one_value_required // //________________________________________________________________________________________ // // Strange exception: sole purpose seems to be catching zero_tokens() associated with a scalar // validation_error::multiple_values_not_allowed seems thus to be a programmer error // // void test_at_least_one_value_required_exception_msg() { options_description desc; desc.add_options() ("cfgfile,c", value()->zero_tokens(), "the config file") ("other,o", value(), "other") ; vector > argv; const char* argv0[] = { "program", "-c" } ; VEC_STR_PUSH_BACK(argv, argv0); const char* argv1[] = { "program", "--cfg", "--o", "name" } ; VEC_STR_PUSH_BACK(argv, argv1); const char* argv2[] = { "program", "-c" , "-o" , "name" } ; VEC_STR_PUSH_BACK(argv, argv2); const char* argv3[] = { "program", "/c" } ; VEC_STR_PUSH_BACK(argv, argv3); const char* argv4[] = { "" } ; VEC_STR_PUSH_BACK(argv, argv4); const char* expected_msg[5] = { "option '--cfgfile' requires at least one argument", "option '--cfgfile' requires at least one argument", "option '-c' requires at least one argument", "option '/c' requires at least one argument", "", }; test_exception_message(argv, desc, "validation_error::at_least_one_value_required", expected_msg); } //________________________________________________________________________________________ // // required_option // //________________________________________________________________________________________ void test_required_option_exception_msg() { options_description desc; desc.add_options() ("cfgfile,c", value()->required(), "the config file") ("good,g", "good option") ("output,o", value()->required(), "the output file") ; vector > argv; const char* argv0[] = { "program", "-g" } ; VEC_STR_PUSH_BACK(argv, argv0); const char* argv1[] = { "program", "--g" } ; VEC_STR_PUSH_BACK(argv, argv1); const char* argv2[] = { "program", "-g"} ; VEC_STR_PUSH_BACK(argv, argv2); const char* argv3[] = { "program", "/g"} ; VEC_STR_PUSH_BACK(argv, argv3); const char* argv4[] = { "" } ; VEC_STR_PUSH_BACK(argv, argv4); const char* expected_msg[5] = { "the option '--cfgfile' is required but missing", "the option '--cfgfile' is required but missing", "the option '-c' is required but missing", "the option '/c' is required but missing", "the option 'cfgfile' is required but missing", }; test_exception_message(argv, desc, "required_option", expected_msg); } /** * Check if this is the expected exception with the right message is being thrown inside * func */ template void test_exception(const string& test_name, const string& exception_txt, FUNC func) { try { options_description desc; variables_map vm; func(desc, vm); } catch (EXCEPTION& e) { CHECK_EQUAL(test_name, e.what(), exception_txt); return; } catch (std::exception& e) { BOOST_ERROR(string(test_name + ":\nUnexpected exception. ") + e.what() + "\nExpected text:\n" + exception_txt + "\n\n"); return; } BOOST_ERROR(test_name + ": No exception thrown. "); } //________________________________________________________________________________________ // // check_reading_file // //________________________________________________________________________________________ void check_reading_file(options_description& desc, variables_map& vm) { desc.add_options() ("output,o", value(), "the output file"); const char* file_name = "no_such_file"; store(parse_config_file(file_name, desc, true), vm); } //________________________________________________________________________________________ // // config_file_wildcard // //________________________________________________________________________________________ void config_file_wildcard(options_description& desc, variables_map& vm) { desc.add_options() ("outpu*", value(), "the output file1") ("outp*", value(), "the output file2") ; istringstream is("output1=whichone\noutput2=whichone\n"); store(parse_config_file(is, desc), vm); } //________________________________________________________________________________________ // // invalid_syntax::unrecognized_line // //________________________________________________________________________________________ void unrecognized_line(options_description& desc, variables_map& vm) { istringstream is("funny wierd line\n"); store(parse_config_file(is, desc), vm); } //________________________________________________________________________________________ // // abbreviated_options_in_config_file // //________________________________________________________________________________________ void abbreviated_options_in_config_file(options_description& desc, variables_map& vm) { desc.add_options()(",o", value(), "the output file"); istringstream is("o=output.txt\n"); store(parse_config_file(is, desc), vm); } //________________________________________________________________________________________ // // too_many_positional_options // //________________________________________________________________________________________ void too_many_positional_options(options_description& desc, variables_map& vm) { const char* argv[] = {"program", "1", "2", "3"}; positional_options_description positional_args; positional_args.add("two_positional_arguments", 2); store(command_line_parser(4, argv).options(desc).positional(positional_args).run(), vm); } //________________________________________________________________________________________ // // invalid_command_line_style // //________________________________________________________________________________________ void test_invalid_command_line_style_exception_msg() { string test_name = "invalid_command_line_style"; using namespace command_line_style; options_description desc; desc.add_options()("output,o", value(), "the output file"); vector invalid_styles; invalid_styles.push_back(allow_short | short_allow_adjacent); invalid_styles.push_back(allow_short | allow_dash_for_short); invalid_styles.push_back(allow_long); vector invalid_diagnostics; invalid_diagnostics.push_back("boost::program_options misconfiguration: choose one " "or other of 'command_line_style::allow_slash_for_short' " "(slashes) or 'command_line_style::allow_dash_for_short' " "(dashes) for short options."); invalid_diagnostics.push_back("boost::program_options misconfiguration: choose one " "or other of 'command_line_style::short_allow_next' " "(whitespace separated arguments) or " "'command_line_style::short_allow_adjacent' ('=' " "separated arguments) for short options."); invalid_diagnostics.push_back("boost::program_options misconfiguration: choose one " "or other of 'command_line_style::long_allow_next' " "(whitespace separated arguments) or " "'command_line_style::long_allow_adjacent' ('=' " "separated arguments) for long options."); const char* argv[] = {"program"}; variables_map vm; for (unsigned ii = 0; ii < 3; ++ii) { bool exception_thrown = false; try { store(parse_command_line(1, argv, desc, invalid_styles[ii]), vm); } catch (invalid_command_line_style& e) { string error_msg("arguments are not allowed for unabbreviated option names"); CHECK_EQUAL(test_name, e.what(), invalid_diagnostics[ii]); exception_thrown = true; } catch (std::exception& e) { BOOST_ERROR(string(test_name + ":\nUnexpected exception. ") + e.what() + "\nExpected text:\n" + invalid_diagnostics[ii] + "\n"); exception_thrown = true; } if (!exception_thrown) { BOOST_ERROR(test_name << ": No exception thrown. "); } } } void test_empty_value_inner(options_description &opts, variables_map& vm) { positional_options_description popts; opts.add_options()("foo", value()->value_name("