xref: /minix3/external/bsd/kyua-cli/dist/cli/main_test.cpp (revision 11be35a165022172ed3cea20f2b5df0307540b0e)
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/main.hpp"
30 
31 extern "C" {
32 #include <signal.h>
33 }
34 
35 #include <cstdlib>
36 
37 #include <atf-c++.hpp>
38 
39 #include "utils/cmdline/base_command.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/datetime.hpp"
46 #include "utils/defs.hpp"
47 #include "utils/env.hpp"
48 #include "utils/fs/operations.hpp"
49 #include "utils/fs/path.hpp"
50 #include "utils/logging/macros.hpp"
51 #include "utils/logging/operations.hpp"
52 #include "utils/process/child.ipp"
53 #include "utils/process/status.hpp"
54 
55 namespace cmdline = utils::cmdline;
56 namespace config = utils::config;
57 namespace datetime = utils::datetime;
58 namespace fs = utils::fs;
59 namespace logging = utils::logging;
60 namespace process = utils::process;
61 
62 
63 namespace {
64 
65 
66 /// Fake command implementation that crashes during its execution.
67 class cmd_mock_crash : public cli::cli_command {
68 public:
69     /// Constructs a new mock command.
70     ///
71     /// All command parameters are set to irrelevant values.
cmd_mock_crash(void)72     cmd_mock_crash(void) :
73         cli::cli_command("mock_error", "", 0, 0, "Mock command that crashes")
74     {
75     }
76 
77     /// Runs the mock command.
78     ///
79     /// \param unused_ui Object to interact with the I/O of the program.
80     /// \param unused_cmdline Representation of the command line to the
81     ///     subcommand.
82     /// \param unused_user_config The runtime configuration of the program.
83     ///
84     /// \return Nothing because this function always aborts.
85     int
run(cmdline::ui * UTILS_UNUSED_PARAM (ui),const cmdline::parsed_cmdline & UTILS_UNUSED_PARAM (cmdline),const config::tree & UTILS_UNUSED_PARAM (user_config))86     run(cmdline::ui* UTILS_UNUSED_PARAM(ui),
87         const cmdline::parsed_cmdline& UTILS_UNUSED_PARAM(cmdline),
88         const config::tree& UTILS_UNUSED_PARAM(user_config))
89     {
90         std::abort();
91     }
92 };
93 
94 
95 /// Fake command implementation that throws an exception during its execution.
96 class cmd_mock_error : public cli::cli_command {
97     /// Whether the command raises an exception captured by the parent or not.
98     ///
99     /// If this is true, the command will raise a std::runtime_error exception
100     /// or a subclass of it.  The main program is in charge of capturing these
101     /// and reporting them appropriately.  If false, this raises another
102     /// exception that does not inherit from std::runtime_error.
103     bool _unhandled;
104 
105 public:
106     /// Constructs a new mock command.
107     ///
108     /// \param unhandled If true, make run raise an exception not catched by the
109     ///     main program.
cmd_mock_error(const bool unhandled)110     cmd_mock_error(const bool unhandled) :
111         cli::cli_command("mock_error", "", 0, 0,
112                          "Mock command that raises an error"),
113         _unhandled(unhandled)
114     {
115     }
116 
117     /// Runs the mock command.
118     ///
119     /// \param unused_ui Object to interact with the I/O of the program.
120     /// \param unused_cmdline Representation of the command line to the
121     ///     subcommand.
122     /// \param unused_user_config The runtime configuration of the program.
123     ///
124     /// \return Nothing because this function always aborts.
125     ///
126     /// \throw std::logic_error If _unhandled is true.
127     /// \throw std::runtime_error If _unhandled is false.
128     int
run(cmdline::ui * UTILS_UNUSED_PARAM (ui),const cmdline::parsed_cmdline & UTILS_UNUSED_PARAM (cmdline),const config::tree & UTILS_UNUSED_PARAM (user_config))129     run(cmdline::ui* UTILS_UNUSED_PARAM(ui),
130         const cmdline::parsed_cmdline& UTILS_UNUSED_PARAM(cmdline),
131         const config::tree& UTILS_UNUSED_PARAM(user_config))
132     {
133         if (_unhandled)
134             throw std::logic_error("This is unhandled");
135         else
136             throw std::runtime_error("Runtime error");
137     }
138 };
139 
140 
141 /// Fake command implementation that prints messages during its execution.
142 class cmd_mock_write : public cli::cli_command {
143 public:
144     /// Constructs a new mock command.
145     ///
146     /// All command parameters are set to irrelevant values.
cmd_mock_write(void)147     cmd_mock_write(void) : cli::cli_command(
148         "mock_write", "", 0, 0, "Mock command that prints output")
149     {
150     }
151 
152     /// Runs the mock command.
153     ///
154     /// \param ui Object to interact with the I/O of the program.
155     /// \param unused_cmdline Representation of the command line to the
156     ///     subcommand.
157     /// \param unused_user_config The runtime configuration of the program.
158     ///
159     /// \return Nothing because this function always aborts.
160     int
run(cmdline::ui * ui,const cmdline::parsed_cmdline & UTILS_UNUSED_PARAM (cmdline),const config::tree & UTILS_UNUSED_PARAM (user_config))161     run(cmdline::ui* ui,
162         const cmdline::parsed_cmdline& UTILS_UNUSED_PARAM(cmdline),
163         const config::tree& UTILS_UNUSED_PARAM(user_config))
164     {
165         ui->out("stdout message from subcommand");
166         ui->err("stderr message from subcommand");
167         return EXIT_FAILURE;
168     }
169 };
170 
171 
172 }  // anonymous namespace
173 
174 
175 ATF_TEST_CASE_WITHOUT_HEAD(detail__default_log_name__home);
ATF_TEST_CASE_BODY(detail__default_log_name__home)176 ATF_TEST_CASE_BODY(detail__default_log_name__home)
177 {
178     datetime::set_mock_now(2011, 2, 21, 21, 10, 30, 0);
179     cmdline::init("progname1");
180 
181     utils::setenv("HOME", "/home//fake");
182     utils::setenv("TMPDIR", "/do/not/use/this");
183     ATF_REQUIRE_EQ(
184         fs::path("/home/fake/.kyua/logs/progname1.20110221-211030.log"),
185         cli::detail::default_log_name());
186 }
187 
188 
189 ATF_TEST_CASE_WITHOUT_HEAD(detail__default_log_name__tmpdir);
ATF_TEST_CASE_BODY(detail__default_log_name__tmpdir)190 ATF_TEST_CASE_BODY(detail__default_log_name__tmpdir)
191 {
192     datetime::set_mock_now(2011, 2, 21, 21, 10, 50, 987);
193     cmdline::init("progname2");
194 
195     utils::unsetenv("HOME");
196     utils::setenv("TMPDIR", "/a/b//c");
197     ATF_REQUIRE_EQ(fs::path("/a/b/c/progname2.20110221-211050.log"),
198                    cli::detail::default_log_name());
199 }
200 
201 
202 ATF_TEST_CASE_WITHOUT_HEAD(detail__default_log_name__hardcoded);
ATF_TEST_CASE_BODY(detail__default_log_name__hardcoded)203 ATF_TEST_CASE_BODY(detail__default_log_name__hardcoded)
204 {
205     datetime::set_mock_now(2011, 2, 21, 21, 15, 00, 123456);
206     cmdline::init("progname3");
207 
208     utils::unsetenv("HOME");
209     utils::unsetenv("TMPDIR");
210     ATF_REQUIRE_EQ(fs::path("/tmp/progname3.20110221-211500.log"),
211                    cli::detail::default_log_name());
212 }
213 
214 
215 ATF_TEST_CASE_WITHOUT_HEAD(main__no_args);
ATF_TEST_CASE_BODY(main__no_args)216 ATF_TEST_CASE_BODY(main__no_args)
217 {
218     logging::set_inmemory();
219     cmdline::init("progname");
220 
221     const int argc = 1;
222     const char* const argv[] = {"progname", NULL};
223 
224     cmdline::ui_mock ui;
225     ATF_REQUIRE_EQ(3, cli::main(&ui, argc, argv));
226     ATF_REQUIRE(ui.out_log().empty());
227     ATF_REQUIRE(atf::utils::grep_collection("Usage error: No command provided",
228                                             ui.err_log()));
229     ATF_REQUIRE(atf::utils::grep_collection("Type.*progname help",
230                                             ui.err_log()));
231 }
232 
233 
234 ATF_TEST_CASE_WITHOUT_HEAD(main__unknown_command);
ATF_TEST_CASE_BODY(main__unknown_command)235 ATF_TEST_CASE_BODY(main__unknown_command)
236 {
237     logging::set_inmemory();
238     cmdline::init("progname");
239 
240     const int argc = 2;
241     const char* const argv[] = {"progname", "foo", NULL};
242 
243     cmdline::ui_mock ui;
244     ATF_REQUIRE_EQ(3, cli::main(&ui, argc, argv));
245     ATF_REQUIRE(ui.out_log().empty());
246     ATF_REQUIRE(atf::utils::grep_collection("Usage error: Unknown command.*foo",
247                                             ui.err_log()));
248     ATF_REQUIRE(atf::utils::grep_collection("Type.*progname help",
249                                             ui.err_log()));
250 }
251 
252 
253 ATF_TEST_CASE_WITHOUT_HEAD(main__logfile__default);
ATF_TEST_CASE_BODY(main__logfile__default)254 ATF_TEST_CASE_BODY(main__logfile__default)
255 {
256     logging::set_inmemory();
257     datetime::set_mock_now(2011, 2, 21, 21, 30, 00, 0);
258     cmdline::init("progname");
259 
260     const int argc = 1;
261     const char* const argv[] = {"progname", NULL};
262 
263     cmdline::ui_mock ui;
264     ATF_REQUIRE(!fs::exists(fs::path(
265         ".kyua/logs/progname.20110221-213000.log")));
266     ATF_REQUIRE_EQ(3, cli::main(&ui, argc, argv));
267     ATF_REQUIRE(fs::exists(fs::path(
268         ".kyua/logs/progname.20110221-213000.log")));
269 }
270 
271 
272 ATF_TEST_CASE_WITHOUT_HEAD(main__logfile__override);
ATF_TEST_CASE_BODY(main__logfile__override)273 ATF_TEST_CASE_BODY(main__logfile__override)
274 {
275     logging::set_inmemory();
276     datetime::set_mock_now(2011, 2, 21, 21, 30, 00, 321);
277     cmdline::init("progname");
278 
279     const int argc = 2;
280     const char* const argv[] = {"progname", "--logfile=test.log", NULL};
281 
282     cmdline::ui_mock ui;
283     ATF_REQUIRE(!fs::exists(fs::path("test.log")));
284     ATF_REQUIRE_EQ(3, cli::main(&ui, argc, argv));
285     ATF_REQUIRE(!fs::exists(fs::path(
286         ".kyua/logs/progname.20110221-213000.log")));
287     ATF_REQUIRE(fs::exists(fs::path("test.log")));
288 }
289 
290 
291 ATF_TEST_CASE_WITHOUT_HEAD(main__loglevel__default);
ATF_TEST_CASE_BODY(main__loglevel__default)292 ATF_TEST_CASE_BODY(main__loglevel__default)
293 {
294     logging::set_inmemory();
295     cmdline::init("progname");
296 
297     const int argc = 2;
298     const char* const argv[] = {"progname", "--logfile=test.log", NULL};
299 
300     LD("Mock debug message");
301     LE("Mock error message");
302     LI("Mock info message");
303     LW("Mock warning message");
304 
305     cmdline::ui_mock ui;
306     ATF_REQUIRE_EQ(3, cli::main(&ui, argc, argv));
307     ATF_REQUIRE(!atf::utils::grep_file("Mock debug message", "test.log"));
308     ATF_REQUIRE(atf::utils::grep_file("Mock error message", "test.log"));
309     ATF_REQUIRE(atf::utils::grep_file("Mock info message", "test.log"));
310     ATF_REQUIRE(atf::utils::grep_file("Mock warning message", "test.log"));
311 }
312 
313 
314 ATF_TEST_CASE_WITHOUT_HEAD(main__loglevel__higher);
ATF_TEST_CASE_BODY(main__loglevel__higher)315 ATF_TEST_CASE_BODY(main__loglevel__higher)
316 {
317     logging::set_inmemory();
318     cmdline::init("progname");
319 
320     const int argc = 3;
321     const char* const argv[] = {"progname", "--logfile=test.log",
322                                 "--loglevel=debug", NULL};
323 
324     LD("Mock debug message");
325     LE("Mock error message");
326     LI("Mock info message");
327     LW("Mock warning message");
328 
329     cmdline::ui_mock ui;
330     ATF_REQUIRE_EQ(3, cli::main(&ui, argc, argv));
331     ATF_REQUIRE(atf::utils::grep_file("Mock debug message", "test.log"));
332     ATF_REQUIRE(atf::utils::grep_file("Mock error message", "test.log"));
333     ATF_REQUIRE(atf::utils::grep_file("Mock info message", "test.log"));
334     ATF_REQUIRE(atf::utils::grep_file("Mock warning message", "test.log"));
335 }
336 
337 
338 ATF_TEST_CASE_WITHOUT_HEAD(main__loglevel__lower);
ATF_TEST_CASE_BODY(main__loglevel__lower)339 ATF_TEST_CASE_BODY(main__loglevel__lower)
340 {
341     logging::set_inmemory();
342     cmdline::init("progname");
343 
344     const int argc = 3;
345     const char* const argv[] = {"progname", "--logfile=test.log",
346                                 "--loglevel=warning", NULL};
347 
348     LD("Mock debug message");
349     LE("Mock error message");
350     LI("Mock info message");
351     LW("Mock warning message");
352 
353     cmdline::ui_mock ui;
354     ATF_REQUIRE_EQ(3, cli::main(&ui, argc, argv));
355     ATF_REQUIRE(!atf::utils::grep_file("Mock debug message", "test.log"));
356     ATF_REQUIRE(atf::utils::grep_file("Mock error message", "test.log"));
357     ATF_REQUIRE(!atf::utils::grep_file("Mock info message", "test.log"));
358     ATF_REQUIRE(atf::utils::grep_file("Mock warning message", "test.log"));
359 }
360 
361 
362 ATF_TEST_CASE_WITHOUT_HEAD(main__loglevel__error);
ATF_TEST_CASE_BODY(main__loglevel__error)363 ATF_TEST_CASE_BODY(main__loglevel__error)
364 {
365     logging::set_inmemory();
366     cmdline::init("progname");
367 
368     const int argc = 3;
369     const char* const argv[] = {"progname", "--logfile=test.log",
370                                 "--loglevel=i-am-invalid", NULL};
371 
372     cmdline::ui_mock ui;
373     ATF_REQUIRE_EQ(3, cli::main(&ui, argc, argv));
374     ATF_REQUIRE(atf::utils::grep_collection("Usage error.*i-am-invalid",
375                                             ui.err_log()));
376     ATF_REQUIRE(!fs::exists(fs::path("test.log")));
377 }
378 
379 
380 ATF_TEST_CASE_WITHOUT_HEAD(main__subcommand__ok);
ATF_TEST_CASE_BODY(main__subcommand__ok)381 ATF_TEST_CASE_BODY(main__subcommand__ok)
382 {
383     logging::set_inmemory();
384     cmdline::init("progname");
385 
386     const int argc = 2;
387     const char* const argv[] = {"progname", "mock_write", NULL};
388 
389     cmdline::ui_mock ui;
390     ATF_REQUIRE_EQ(EXIT_FAILURE,
391                    cli::main(&ui, argc, argv,
392                              cli::cli_command_ptr(new cmd_mock_write())));
393     ATF_REQUIRE_EQ(1, ui.out_log().size());
394     ATF_REQUIRE_EQ("stdout message from subcommand", ui.out_log()[0]);
395     ATF_REQUIRE_EQ(1, ui.err_log().size());
396     ATF_REQUIRE_EQ("stderr message from subcommand", ui.err_log()[0]);
397 }
398 
399 
400 ATF_TEST_CASE_WITHOUT_HEAD(main__subcommand__invalid_args);
ATF_TEST_CASE_BODY(main__subcommand__invalid_args)401 ATF_TEST_CASE_BODY(main__subcommand__invalid_args)
402 {
403     logging::set_inmemory();
404     cmdline::init("progname");
405 
406     const int argc = 3;
407     const char* const argv[] = {"progname", "mock_write", "bar", NULL};
408 
409     cmdline::ui_mock ui;
410     ATF_REQUIRE_EQ(3,
411                    cli::main(&ui, argc, argv,
412                              cli::cli_command_ptr(new cmd_mock_write())));
413     ATF_REQUIRE(ui.out_log().empty());
414     ATF_REQUIRE(atf::utils::grep_collection(
415         "Usage error for command mock_write: Too many arguments.",
416         ui.err_log()));
417     ATF_REQUIRE(atf::utils::grep_collection("Type.*progname help",
418                                             ui.err_log()));
419 }
420 
421 
422 ATF_TEST_CASE_WITHOUT_HEAD(main__subcommand__runtime_error);
ATF_TEST_CASE_BODY(main__subcommand__runtime_error)423 ATF_TEST_CASE_BODY(main__subcommand__runtime_error)
424 {
425     logging::set_inmemory();
426     cmdline::init("progname");
427 
428     const int argc = 2;
429     const char* const argv[] = {"progname", "mock_error", NULL};
430 
431     cmdline::ui_mock ui;
432     ATF_REQUIRE_EQ(2, cli::main(&ui, argc, argv,
433         cli::cli_command_ptr(new cmd_mock_error(false))));
434     ATF_REQUIRE(ui.out_log().empty());
435     ATF_REQUIRE(atf::utils::grep_collection("progname: E: Runtime error.",
436                                             ui.err_log()));
437 }
438 
439 
440 ATF_TEST_CASE_WITHOUT_HEAD(main__subcommand__unhandled_exception);
ATF_TEST_CASE_BODY(main__subcommand__unhandled_exception)441 ATF_TEST_CASE_BODY(main__subcommand__unhandled_exception)
442 {
443     logging::set_inmemory();
444     cmdline::init("progname");
445 
446     const int argc = 2;
447     const char* const argv[] = {"progname", "mock_error", NULL};
448 
449     cmdline::ui_mock ui;
450     ATF_REQUIRE_THROW(std::logic_error, cli::main(&ui, argc, argv,
451         cli::cli_command_ptr(new cmd_mock_error(true))));
452 }
453 
454 
455 static void
do_subcommand_crash(void)456 do_subcommand_crash(void)
457 {
458     logging::set_inmemory();
459     cmdline::init("progname");
460 
461     const int argc = 2;
462     const char* const argv[] = {"progname", "mock_error", NULL};
463 
464     cmdline::ui_mock ui;
465     cli::main(&ui, argc, argv,
466               cli::cli_command_ptr(new cmd_mock_crash()));
467 }
468 
469 
470 ATF_TEST_CASE_WITHOUT_HEAD(main__subcommand__crash);
ATF_TEST_CASE_BODY(main__subcommand__crash)471 ATF_TEST_CASE_BODY(main__subcommand__crash)
472 {
473     const process::status status = process::child::fork_files(
474         do_subcommand_crash, fs::path("stdout.txt"),
475         fs::path("stderr.txt"))->wait();
476     ATF_REQUIRE(status.signaled());
477     ATF_REQUIRE_EQ(SIGABRT, status.termsig());
478     ATF_REQUIRE(atf::utils::grep_file("Fatal signal", "stderr.txt"));
479 }
480 
481 
ATF_INIT_TEST_CASES(tcs)482 ATF_INIT_TEST_CASES(tcs)
483 {
484     ATF_ADD_TEST_CASE(tcs, detail__default_log_name__home);
485     ATF_ADD_TEST_CASE(tcs, detail__default_log_name__tmpdir);
486     ATF_ADD_TEST_CASE(tcs, detail__default_log_name__hardcoded);
487 
488     ATF_ADD_TEST_CASE(tcs, main__no_args);
489     ATF_ADD_TEST_CASE(tcs, main__unknown_command);
490     ATF_ADD_TEST_CASE(tcs, main__logfile__default);
491     ATF_ADD_TEST_CASE(tcs, main__logfile__override);
492     ATF_ADD_TEST_CASE(tcs, main__loglevel__default);
493     ATF_ADD_TEST_CASE(tcs, main__loglevel__higher);
494     ATF_ADD_TEST_CASE(tcs, main__loglevel__lower);
495     ATF_ADD_TEST_CASE(tcs, main__loglevel__error);
496     ATF_ADD_TEST_CASE(tcs, main__subcommand__ok);
497     ATF_ADD_TEST_CASE(tcs, main__subcommand__invalid_args);
498     ATF_ADD_TEST_CASE(tcs, main__subcommand__runtime_error);
499     ATF_ADD_TEST_CASE(tcs, main__subcommand__unhandled_exception);
500     ATF_ADD_TEST_CASE(tcs, main__subcommand__crash);
501 }
502