1 // Copyright 2010 Google Inc. 2 // All rights reserved. 3 // 4 // Redistribution and use in source and binary forms, with or without 5 // modification, are permitted provided that the following conditions are 6 // met: 7 // 8 // * Redistributions of source code must retain the above copyright 9 // notice, this list of conditions and the following disclaimer. 10 // * Redistributions in binary form must reproduce the above copyright 11 // notice, this list of conditions and the following disclaimer in the 12 // documentation and/or other materials provided with the distribution. 13 // * Neither the name of Google Inc. nor the names of its contributors 14 // may be used to endorse or promote products derived from this software 15 // without specific prior written permission. 16 // 17 // THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS 18 // "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT 19 // LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR 20 // A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT 21 // OWNER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, 22 // SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT 23 // LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, 24 // DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY 25 // THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT 26 // (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE 27 // OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. 28 29 #include "cli/cmd_help.hpp" 30 31 #include <algorithm> 32 #include <cstdlib> 33 #include <iterator> 34 35 #include <atf-c++.hpp> 36 37 #include "cli/common.ipp" 38 #include "engine/config.hpp" 39 #include "utils/cmdline/commands_map.ipp" 40 #include "utils/cmdline/exceptions.hpp" 41 #include "utils/cmdline/globals.hpp" 42 #include "utils/cmdline/options.hpp" 43 #include "utils/cmdline/parser.hpp" 44 #include "utils/cmdline/ui_mock.hpp" 45 #include "utils/defs.hpp" 46 #include "utils/sanity.hpp" 47 48 namespace cmdline = utils::cmdline; 49 namespace config = utils::config; 50 51 using cli::cmd_help; 52 53 54 namespace { 55 56 57 /// Mock command with a simple definition (no options, no arguments). 58 /// 59 /// Attempting to run this command will result in a crash. It is only provided 60 /// to validate the generation of interactive help. 61 class cmd_mock_simple : public cli::cli_command { 62 public: 63 /// Constructs a new mock command. 64 /// 65 /// \param name_ The name of the command to create. 66 cmd_mock_simple(const char* name_) : cli::cli_command( 67 name_, "", 0, 0, "Simple command") 68 { 69 } 70 71 /// Runs the mock command. 72 /// 73 /// \param unused_ui Object to interact with the I/O of the program. 74 /// \param unused_cmdline Representation of the command line to the 75 /// subcommand. 76 /// \param unused_user_config The runtime configuration of the program. 77 /// 78 /// \return Nothing because this function is never called. 79 int 80 run(cmdline::ui* UTILS_UNUSED_PARAM(ui), 81 const cmdline::parsed_cmdline& UTILS_UNUSED_PARAM(cmdline), 82 const config::tree& UTILS_UNUSED_PARAM(user_config)) 83 { 84 UNREACHABLE; 85 } 86 }; 87 88 89 /// Mock command with a complex definition (some options, some arguments). 90 /// 91 /// Attempting to run this command will result in a crash. It is only provided 92 /// to validate the generation of interactive help. 93 class cmd_mock_complex : public cli::cli_command { 94 public: 95 /// Constructs a new mock command. 96 /// 97 /// \param name_ The name of the command to create. 98 cmd_mock_complex(const char* name_) : cli::cli_command( 99 name_, "[arg1 .. argN]", 0, 2, "Complex command") 100 { 101 add_option(cmdline::bool_option("flag_a", "Flag A")); 102 add_option(cmdline::bool_option('b', "flag_b", "Flag B")); 103 add_option(cmdline::string_option('c', "flag_c", "Flag C", "c_arg")); 104 add_option(cmdline::string_option("flag_d", "Flag D", "d_arg", "foo")); 105 } 106 107 /// Runs the mock command. 108 /// 109 /// \param unused_ui Object to interact with the I/O of the program. 110 /// \param unused_cmdline Representation of the command line to the 111 /// subcommand. 112 /// \param unused_user_config The runtime configuration of the program. 113 /// 114 /// \return Nothing because this function is never called. 115 int 116 run(cmdline::ui* UTILS_UNUSED_PARAM(ui), 117 const cmdline::parsed_cmdline& UTILS_UNUSED_PARAM(cmdline), 118 const config::tree& UTILS_UNUSED_PARAM(user_config)) 119 { 120 UNREACHABLE; 121 } 122 }; 123 124 125 /// Initializes the cmdline library and generates the set of test commands. 126 /// 127 /// \param [out] commands A mapping that is updated to contain the commands to 128 /// use for testing. 129 static void 130 setup(cmdline::commands_map< cli::cli_command >& commands) 131 { 132 cmdline::init("progname"); 133 134 commands.insert(new cmd_mock_simple("mock_simple")); 135 commands.insert(new cmd_mock_complex("mock_complex")); 136 137 commands.insert(new cmd_mock_simple("mock_simple_2"), "First"); 138 commands.insert(new cmd_mock_complex("mock_complex_2"), "First"); 139 140 commands.insert(new cmd_mock_simple("mock_simple_3"), "Second"); 141 } 142 143 144 /// Performs a test on the global help (not that of a subcommand). 145 /// 146 /// \param general_options The genral options supported by the tool, if any. 147 /// \param expected_options Expected lines of help output documenting the 148 /// options in general_options. 149 /// \param ui The cmdline::mock_ui object to which to write the output. 150 static void 151 global_test(const cmdline::options_vector& general_options, 152 const std::vector< std::string >& expected_options, 153 cmdline::ui_mock& ui) 154 { 155 cmdline::commands_map< cli::cli_command > mock_commands; 156 setup(mock_commands); 157 158 cmdline::args_vector args; 159 args.push_back("help"); 160 161 cmd_help cmd(&general_options, &mock_commands); 162 ATF_REQUIRE_EQ(EXIT_SUCCESS, cmd.main(&ui, args, engine::default_config())); 163 164 std::vector< std::string > expected; 165 166 expected.push_back("Usage: progname [general_options] command " 167 "[command_options] [args]"); 168 if (!general_options.empty()) { 169 expected.push_back(""); 170 expected.push_back("Available general options:"); 171 std::copy(expected_options.begin(), expected_options.end(), 172 std::back_inserter(expected)); 173 } 174 expected.push_back(""); 175 expected.push_back("Generic commands:"); 176 expected.push_back(" mock_complex Complex command."); 177 expected.push_back(" mock_simple Simple command."); 178 expected.push_back(""); 179 expected.push_back("First commands:"); 180 expected.push_back(" mock_complex_2 Complex command."); 181 expected.push_back(" mock_simple_2 Simple command."); 182 expected.push_back(""); 183 expected.push_back("Second commands:"); 184 expected.push_back(" mock_simple_3 Simple command."); 185 expected.push_back(""); 186 expected.push_back("See kyua(1) for more details."); 187 188 ATF_REQUIRE(expected == ui.out_log()); 189 ATF_REQUIRE(ui.err_log().empty()); 190 } 191 192 193 } // anonymous namespace 194 195 196 ATF_TEST_CASE_WITHOUT_HEAD(global__no_options); 197 ATF_TEST_CASE_BODY(global__no_options) 198 { 199 cmdline::ui_mock ui; 200 201 cmdline::options_vector general_options; 202 203 global_test(general_options, std::vector< std::string >(), ui); 204 } 205 206 207 ATF_TEST_CASE_WITHOUT_HEAD(global__some_options); 208 ATF_TEST_CASE_BODY(global__some_options) 209 { 210 cmdline::ui_mock ui; 211 212 cmdline::options_vector general_options; 213 const cmdline::bool_option flag_a("flag_a", "Flag A"); 214 general_options.push_back(&flag_a); 215 const cmdline::string_option flag_c('c', "lc", "Flag C", "X"); 216 general_options.push_back(&flag_c); 217 218 std::vector< std::string > expected; 219 expected.push_back(" --flag_a Flag A."); 220 expected.push_back(" -c X, --lc=X Flag C."); 221 222 global_test(general_options, expected, ui); 223 } 224 225 226 ATF_TEST_CASE_WITHOUT_HEAD(subcommand__simple); 227 ATF_TEST_CASE_BODY(subcommand__simple) 228 { 229 cmdline::options_vector general_options; 230 231 cmdline::commands_map< cli::cli_command > mock_commands; 232 setup(mock_commands); 233 234 cmdline::args_vector args; 235 args.push_back("help"); 236 args.push_back("mock_simple"); 237 238 cmd_help cmd(&general_options, &mock_commands); 239 cmdline::ui_mock ui; 240 ATF_REQUIRE_EQ(EXIT_SUCCESS, cmd.main(&ui, args, engine::default_config())); 241 ATF_REQUIRE(atf::utils::grep_collection("^Usage: progname \\[general_options\\] " 242 "mock_simple$", ui.out_log())); 243 ATF_REQUIRE(!atf::utils::grep_collection("Available.*options", ui.out_log())); 244 ATF_REQUIRE(atf::utils::grep_collection("^See kyua-mock_simple\\(1\\) for more " 245 "details.", ui.out_log())); 246 ATF_REQUIRE(ui.err_log().empty()); 247 } 248 249 250 ATF_TEST_CASE_WITHOUT_HEAD(subcommand__complex); 251 ATF_TEST_CASE_BODY(subcommand__complex) 252 { 253 cmdline::options_vector general_options; 254 const cmdline::bool_option global_a("global_a", "Global A"); 255 general_options.push_back(&global_a); 256 const cmdline::string_option global_c('c', "global_c", "Global C", 257 "c_global"); 258 general_options.push_back(&global_c); 259 260 cmdline::commands_map< cli::cli_command > mock_commands; 261 setup(mock_commands); 262 263 cmdline::args_vector args; 264 args.push_back("help"); 265 args.push_back("mock_complex"); 266 267 cmd_help cmd(&general_options, &mock_commands); 268 cmdline::ui_mock ui; 269 ATF_REQUIRE_EQ(EXIT_SUCCESS, cmd.main(&ui, args, engine::default_config())); 270 ATF_REQUIRE(atf::utils::grep_collection( 271 "^Usage: progname \\[general_options\\] mock_complex " 272 "\\[command_options\\] \\[arg1 .. argN\\]$", ui.out_log())); 273 ATF_REQUIRE(atf::utils::grep_collection("Available general options", 274 ui.out_log())); 275 ATF_REQUIRE(atf::utils::grep_collection("--global_a", ui.out_log())); 276 ATF_REQUIRE(atf::utils::grep_collection("--global_c=c_global", 277 ui.out_log())); 278 ATF_REQUIRE(atf::utils::grep_collection("Available command options", 279 ui.out_log())); 280 ATF_REQUIRE(atf::utils::grep_collection("--flag_a *Flag A", 281 ui.out_log())); 282 ATF_REQUIRE(atf::utils::grep_collection("-b.*--flag_b *Flag B", 283 ui.out_log())); 284 ATF_REQUIRE(atf::utils::grep_collection( 285 "-c c_arg.*--flag_c=c_arg *Flag C", ui.out_log())); 286 ATF_REQUIRE(atf::utils::grep_collection( 287 "--flag_d=d_arg *Flag D.*default.*foo", ui.out_log())); 288 ATF_REQUIRE(atf::utils::grep_collection( 289 "^See kyua-mock_complex\\(1\\) for more details.", ui.out_log())); 290 ATF_REQUIRE(ui.err_log().empty()); 291 } 292 293 294 ATF_TEST_CASE_WITHOUT_HEAD(subcommand__unknown); 295 ATF_TEST_CASE_BODY(subcommand__unknown) 296 { 297 cmdline::options_vector general_options; 298 299 cmdline::commands_map< cli::cli_command > mock_commands; 300 setup(mock_commands); 301 302 cmdline::args_vector args; 303 args.push_back("help"); 304 args.push_back("foobar"); 305 306 cmd_help cmd(&general_options, &mock_commands); 307 cmdline::ui_mock ui; 308 ATF_REQUIRE_THROW_RE(cmdline::usage_error, "command foobar.*not exist", 309 cmd.main(&ui, args, engine::default_config())); 310 ATF_REQUIRE(ui.out_log().empty()); 311 ATF_REQUIRE(ui.err_log().empty()); 312 } 313 314 315 ATF_TEST_CASE_WITHOUT_HEAD(invalid_args); 316 ATF_TEST_CASE_BODY(invalid_args) 317 { 318 cmdline::options_vector general_options; 319 320 cmdline::commands_map< cli::cli_command > mock_commands; 321 setup(mock_commands); 322 323 cmdline::args_vector args; 324 args.push_back("help"); 325 args.push_back("mock_simple"); 326 args.push_back("mock_complex"); 327 328 cmd_help cmd(&general_options, &mock_commands); 329 cmdline::ui_mock ui; 330 ATF_REQUIRE_THROW_RE(cmdline::usage_error, "Too many arguments", 331 cmd.main(&ui, args, engine::default_config())); 332 ATF_REQUIRE(ui.out_log().empty()); 333 ATF_REQUIRE(ui.err_log().empty()); 334 } 335 336 337 ATF_INIT_TEST_CASES(tcs) 338 { 339 ATF_ADD_TEST_CASE(tcs, global__no_options); 340 ATF_ADD_TEST_CASE(tcs, global__some_options); 341 ATF_ADD_TEST_CASE(tcs, subcommand__simple); 342 ATF_ADD_TEST_CASE(tcs, subcommand__complex); 343 ATF_ADD_TEST_CASE(tcs, subcommand__unknown); 344 ATF_ADD_TEST_CASE(tcs, invalid_args); 345 } 346