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
31extern "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
56namespace cmdline = utils::cmdline;
57namespace config = utils::config;
58namespace datetime = utils::datetime;
59namespace fs = utils::fs;
60namespace logging = utils::logging;
61namespace process = utils::process;
62
63
64namespace {
65
66
67/// Fake command implementation that crashes during its execution.
68class cmd_mock_crash : public cli::cli_command {
69public:
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.
92class 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
101public:
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.
133class cmd_mock_write : public cli::cli_command {
134public:
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
163ATF_TEST_CASE_WITHOUT_HEAD(detail__default_log_name__home);
164ATF_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
177ATF_TEST_CASE_WITHOUT_HEAD(detail__default_log_name__tmpdir);
178ATF_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
190ATF_TEST_CASE_WITHOUT_HEAD(detail__default_log_name__hardcoded);
191ATF_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
203ATF_TEST_CASE_WITHOUT_HEAD(main__no_args);
204ATF_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
222ATF_TEST_CASE_WITHOUT_HEAD(main__unknown_command);
223ATF_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
241ATF_TEST_CASE_WITHOUT_HEAD(main__logfile__default);
242ATF_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
260ATF_TEST_CASE_WITHOUT_HEAD(main__logfile__override);
261ATF_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
279ATF_TEST_CASE_WITHOUT_HEAD(main__loglevel__default);
280ATF_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
302ATF_TEST_CASE_WITHOUT_HEAD(main__loglevel__higher);
303ATF_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
326ATF_TEST_CASE_WITHOUT_HEAD(main__loglevel__lower);
327ATF_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
350ATF_TEST_CASE_WITHOUT_HEAD(main__loglevel__error);
351ATF_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
368ATF_TEST_CASE_WITHOUT_HEAD(main__subcommand__ok);
369ATF_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
388ATF_TEST_CASE_WITHOUT_HEAD(main__subcommand__invalid_args);
389ATF_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
410ATF_TEST_CASE_WITHOUT_HEAD(main__subcommand__runtime_error);
411ATF_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
428ATF_TEST_CASE_WITHOUT_HEAD(main__subcommand__unhandled_exception);
429ATF_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
443static void
444do_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
458ATF_TEST_CASE_WITHOUT_HEAD(main__subcommand__crash);
459ATF_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
470ATF_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