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