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