1 // Copyright 2010 The Kyua Authors. 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/config/tree.ipp" 46 #include "utils/defs.hpp" 47 #include "utils/sanity.hpp" 48 49 #if defined(HAVE_CONFIG_H) 50 # include "config.h" 51 #endif 52 53 namespace cmdline = utils::cmdline; 54 namespace config = utils::config; 55 56 using cli::cmd_help; 57 58 59 namespace { 60 61 62 /// Mock command with a simple definition (no options, no arguments). 63 /// 64 /// Attempting to run this command will result in a crash. It is only provided 65 /// to validate the generation of interactive help. 66 class cmd_mock_simple : public cli::cli_command { 67 public: 68 /// Constructs a new mock command. 69 /// 70 /// \param name_ The name of the command to create. 71 cmd_mock_simple(const char* name_) : cli::cli_command( 72 name_, "", 0, 0, "Simple command") 73 { 74 } 75 76 /// Runs the mock command. 77 /// 78 /// \return Nothing because this function is never called. 79 int 80 run(cmdline::ui* /* ui */, 81 const cmdline::parsed_cmdline& /* cmdline */, 82 const config::tree& /* 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 /// \return Nothing because this function is never called. 110 int 111 run(cmdline::ui* /* ui */, 112 const cmdline::parsed_cmdline& /* cmdline */, 113 const config::tree& /* user_config */) 114 { 115 UNREACHABLE; 116 } 117 }; 118 119 120 /// Initializes the cmdline library and generates the set of test commands. 121 /// 122 /// \param [out] commands A mapping that is updated to contain the commands to 123 /// use for testing. 124 static void 125 setup(cmdline::commands_map< cli::cli_command >& commands) 126 { 127 cmdline::init("progname"); 128 129 commands.insert(new cmd_mock_simple("mock_simple")); 130 commands.insert(new cmd_mock_complex("mock_complex")); 131 132 commands.insert(new cmd_mock_simple("mock_simple_2"), "First"); 133 commands.insert(new cmd_mock_complex("mock_complex_2"), "First"); 134 135 commands.insert(new cmd_mock_simple("mock_simple_3"), "Second"); 136 } 137 138 139 /// Performs a test on the global help (not that of a subcommand). 140 /// 141 /// \param general_options The genral options supported by the tool, if any. 142 /// \param expected_options Expected lines of help output documenting the 143 /// options in general_options. 144 /// \param ui The cmdline::mock_ui object to which to write the output. 145 static void 146 global_test(const cmdline::options_vector& general_options, 147 const std::vector< std::string >& expected_options, 148 cmdline::ui_mock& ui) 149 { 150 cmdline::commands_map< cli::cli_command > mock_commands; 151 setup(mock_commands); 152 153 cmdline::args_vector args; 154 args.push_back("help"); 155 156 cmd_help cmd(&general_options, &mock_commands); 157 ATF_REQUIRE_EQ(EXIT_SUCCESS, cmd.main(&ui, args, engine::default_config())); 158 159 std::vector< std::string > expected; 160 161 expected.push_back(PACKAGE " (" PACKAGE_NAME ") " PACKAGE_VERSION); 162 expected.push_back(""); 163 expected.push_back("Usage: progname [general_options] command " 164 "[command_options] [args]"); 165 if (!general_options.empty()) { 166 expected.push_back(""); 167 expected.push_back("Available general options:"); 168 std::copy(expected_options.begin(), expected_options.end(), 169 std::back_inserter(expected)); 170 } 171 expected.push_back(""); 172 expected.push_back("Generic commands:"); 173 expected.push_back(" mock_complex Complex command."); 174 expected.push_back(" mock_simple Simple command."); 175 expected.push_back(""); 176 expected.push_back("First commands:"); 177 expected.push_back(" mock_complex_2 Complex command."); 178 expected.push_back(" mock_simple_2 Simple command."); 179 expected.push_back(""); 180 expected.push_back("Second commands:"); 181 expected.push_back(" mock_simple_3 Simple command."); 182 expected.push_back(""); 183 expected.push_back("See kyua(1) for more details."); 184 185 ATF_REQUIRE(expected == ui.out_log()); 186 ATF_REQUIRE(ui.err_log().empty()); 187 } 188 189 190 } // anonymous namespace 191 192 193 ATF_TEST_CASE_WITHOUT_HEAD(global__no_options); 194 ATF_TEST_CASE_BODY(global__no_options) 195 { 196 cmdline::ui_mock ui; 197 198 cmdline::options_vector general_options; 199 200 global_test(general_options, std::vector< std::string >(), ui); 201 } 202 203 204 ATF_TEST_CASE_WITHOUT_HEAD(global__some_options); 205 ATF_TEST_CASE_BODY(global__some_options) 206 { 207 cmdline::ui_mock ui; 208 209 cmdline::options_vector general_options; 210 const cmdline::bool_option flag_a("flag_a", "Flag A"); 211 general_options.push_back(&flag_a); 212 const cmdline::string_option flag_c('c', "lc", "Flag C", "X"); 213 general_options.push_back(&flag_c); 214 215 std::vector< std::string > expected; 216 expected.push_back(" --flag_a Flag A."); 217 expected.push_back(" -c X, --lc=X Flag C."); 218 219 global_test(general_options, expected, ui); 220 } 221 222 223 ATF_TEST_CASE_WITHOUT_HEAD(subcommand__simple); 224 ATF_TEST_CASE_BODY(subcommand__simple) 225 { 226 cmdline::options_vector general_options; 227 228 cmdline::commands_map< cli::cli_command > mock_commands; 229 setup(mock_commands); 230 231 cmdline::args_vector args; 232 args.push_back("help"); 233 args.push_back("mock_simple"); 234 235 cmd_help cmd(&general_options, &mock_commands); 236 cmdline::ui_mock ui; 237 ATF_REQUIRE_EQ(EXIT_SUCCESS, cmd.main(&ui, args, engine::default_config())); 238 ATF_REQUIRE(atf::utils::grep_collection( 239 "^kyua.*" PACKAGE_VERSION, ui.out_log())); 240 ATF_REQUIRE(atf::utils::grep_collection( 241 "^Usage: progname \\[general_options\\] mock_simple$", ui.out_log())); 242 ATF_REQUIRE(!atf::utils::grep_collection( 243 "Available.*options", ui.out_log())); 244 ATF_REQUIRE(atf::utils::grep_collection( 245 "^See kyua-mock_simple\\(1\\) for more 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 "^kyua.*" PACKAGE_VERSION, ui.out_log())); 272 ATF_REQUIRE(atf::utils::grep_collection( 273 "^Usage: progname \\[general_options\\] mock_complex " 274 "\\[command_options\\] \\[arg1 .. argN\\]$", ui.out_log())); 275 ATF_REQUIRE(atf::utils::grep_collection("Available general options", 276 ui.out_log())); 277 ATF_REQUIRE(atf::utils::grep_collection("--global_a", ui.out_log())); 278 ATF_REQUIRE(atf::utils::grep_collection("--global_c=c_global", 279 ui.out_log())); 280 ATF_REQUIRE(atf::utils::grep_collection("Available command options", 281 ui.out_log())); 282 ATF_REQUIRE(atf::utils::grep_collection("--flag_a *Flag A", 283 ui.out_log())); 284 ATF_REQUIRE(atf::utils::grep_collection("-b.*--flag_b *Flag B", 285 ui.out_log())); 286 ATF_REQUIRE(atf::utils::grep_collection( 287 "-c c_arg.*--flag_c=c_arg *Flag C", ui.out_log())); 288 ATF_REQUIRE(atf::utils::grep_collection( 289 "--flag_d=d_arg *Flag D.*default.*foo", ui.out_log())); 290 ATF_REQUIRE(atf::utils::grep_collection( 291 "^See kyua-mock_complex\\(1\\) for more details.", ui.out_log())); 292 ATF_REQUIRE(ui.err_log().empty()); 293 } 294 295 296 ATF_TEST_CASE_WITHOUT_HEAD(subcommand__unknown); 297 ATF_TEST_CASE_BODY(subcommand__unknown) 298 { 299 cmdline::options_vector general_options; 300 301 cmdline::commands_map< cli::cli_command > mock_commands; 302 setup(mock_commands); 303 304 cmdline::args_vector args; 305 args.push_back("help"); 306 args.push_back("foobar"); 307 308 cmd_help cmd(&general_options, &mock_commands); 309 cmdline::ui_mock ui; 310 ATF_REQUIRE_THROW_RE(cmdline::usage_error, "command foobar.*not exist", 311 cmd.main(&ui, args, engine::default_config())); 312 ATF_REQUIRE(ui.out_log().empty()); 313 ATF_REQUIRE(ui.err_log().empty()); 314 } 315 316 317 ATF_TEST_CASE_WITHOUT_HEAD(invalid_args); 318 ATF_TEST_CASE_BODY(invalid_args) 319 { 320 cmdline::options_vector general_options; 321 322 cmdline::commands_map< cli::cli_command > mock_commands; 323 setup(mock_commands); 324 325 cmdline::args_vector args; 326 args.push_back("help"); 327 args.push_back("mock_simple"); 328 args.push_back("mock_complex"); 329 330 cmd_help cmd(&general_options, &mock_commands); 331 cmdline::ui_mock ui; 332 ATF_REQUIRE_THROW_RE(cmdline::usage_error, "Too many arguments", 333 cmd.main(&ui, args, engine::default_config())); 334 ATF_REQUIRE(ui.out_log().empty()); 335 ATF_REQUIRE(ui.err_log().empty()); 336 } 337 338 339 ATF_INIT_TEST_CASES(tcs) 340 { 341 ATF_ADD_TEST_CASE(tcs, global__no_options); 342 ATF_ADD_TEST_CASE(tcs, global__some_options); 343 ATF_ADD_TEST_CASE(tcs, subcommand__simple); 344 ATF_ADD_TEST_CASE(tcs, subcommand__complex); 345 ATF_ADD_TEST_CASE(tcs, subcommand__unknown); 346 ATF_ADD_TEST_CASE(tcs, invalid_args); 347 } 348