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.
cmd_mock_crash(void)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
run(cmdline::ui *,const cmdline::parsed_cmdline &,const config::tree &)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.
cmd_mock_error(const bool unhandled)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
run(cmdline::ui *,const cmdline::parsed_cmdline &,const config::tree &)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.
cmd_mock_write(void)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
run(cmdline::ui * ui,const cmdline::parsed_cmdline &,const config::tree &)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);
ATF_TEST_CASE_BODY(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);
ATF_TEST_CASE_BODY(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);
ATF_TEST_CASE_BODY(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);
ATF_TEST_CASE_BODY(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);
ATF_TEST_CASE_BODY(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);
ATF_TEST_CASE_BODY(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);
ATF_TEST_CASE_BODY(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);
ATF_TEST_CASE_BODY(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);
ATF_TEST_CASE_BODY(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);
ATF_TEST_CASE_BODY(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);
ATF_TEST_CASE_BODY(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);
ATF_TEST_CASE_BODY(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);
ATF_TEST_CASE_BODY(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);
ATF_TEST_CASE_BODY(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);
ATF_TEST_CASE_BODY(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
do_subcommand_crash(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);
ATF_TEST_CASE_BODY(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
ATF_INIT_TEST_CASES(tcs)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