xref: /freebsd/contrib/kyua/cli/cmd_help_test.cpp (revision f126890a)
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