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 48namespace cmdline = utils::cmdline; 49namespace config = utils::config; 50 51using cli::cmd_help; 52 53 54namespace { 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. 61class cmd_mock_simple : public cli::cli_command { 62public: 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. 93class cmd_mock_complex : public cli::cli_command { 94public: 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. 129static void 130setup(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. 150static void 151global_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 196ATF_TEST_CASE_WITHOUT_HEAD(global__no_options); 197ATF_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 207ATF_TEST_CASE_WITHOUT_HEAD(global__some_options); 208ATF_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 226ATF_TEST_CASE_WITHOUT_HEAD(subcommand__simple); 227ATF_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 250ATF_TEST_CASE_WITHOUT_HEAD(subcommand__complex); 251ATF_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 294ATF_TEST_CASE_WITHOUT_HEAD(subcommand__unknown); 295ATF_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 315ATF_TEST_CASE_WITHOUT_HEAD(invalid_args); 316ATF_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 337ATF_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