xref: /netbsd-src/external/bsd/kyua-cli/dist/cli/cmd_help_test.cpp (revision 946379e7b37692fc43f68eb0d1c10daa0a7f3b6c)
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