1 // Copyright 2012 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 "utils/stacktrace.hpp"
30 
31 extern "C" {
32 #include <sys/resource.h>
33 #include <sys/types.h>
34 #include <sys/stat.h>
35 
36 #include <signal.h>
37 #include <unistd.h>
38 }
39 
40 #include <iostream>
41 #include <sstream>
42 
43 #include <atf-c++.hpp>
44 
45 #include "utils/datetime.hpp"
46 #include "utils/env.hpp"
47 #include "utils/fs/operations.hpp"
48 #include "utils/fs/path.hpp"
49 #include "utils/optional.ipp"
50 #include "utils/process/executor.ipp"
51 #include "utils/process/child.ipp"
52 #include "utils/process/operations.hpp"
53 #include "utils/process/status.hpp"
54 #include "utils/sanity.hpp"
55 #include "utils/test_utils.ipp"
56 
57 namespace datetime = utils::datetime;
58 namespace executor = utils::process::executor;
59 namespace fs = utils::fs;
60 namespace process = utils::process;
61 
62 using utils::none;
63 using utils::optional;
64 
65 
66 namespace {
67 
68 
69 /// Functor to execute a binary in a subprocess.
70 ///
71 /// The provided binary is copied to the current work directory before being
72 /// executed and the copy is given the name chosen by the user.  The copy is
73 /// necessary so that we have a deterministic location for where core files may
74 /// be dumped (if they happen to be dumped in the current directory).
75 class crash_me {
76     /// Path to the binary to execute.
77     const fs::path _binary;
78 
79     /// Name of the binary after being copied.
80     const fs::path _copy_name;
81 
82 public:
83     /// Constructor.
84     ///
85     /// \param binary_ Path to binary to execute.
86     /// \param copy_name_ Name of the binary after being copied.  If empty,
87     ///     use the leaf name of binary_.
88     explicit crash_me(const fs::path& binary_,
89                       const std::string& copy_name_ = "") :
90         _binary(binary_),
91         _copy_name(copy_name_.empty() ? binary_.leaf_name() : copy_name_)
92     {
93     }
94 
95     /// Runs the binary.
96     void
97     operator()(void) const UTILS_NORETURN
98     {
99         atf::utils::copy_file(_binary.str(), _copy_name.str());
100 
101         const std::vector< std::string > args;
102         process::exec(_copy_name, args);
103     }
104 
105     /// Runs the binary.
106     ///
107     /// This interface is exposed to support passing crash_me to the executor.
108     void
109     operator()(const fs::path& /* control_directory */) const
110         UTILS_NORETURN
111     {
112         (*this)();  // Delegate to ensure the two entry points remain in sync.
113     }
114 };
115 
116 
117 static void child_exit(const fs::path&) UTILS_NORETURN;
118 
119 
120 /// Subprocess that exits cleanly.
121 static void
122 child_exit(const fs::path& /* control_directory */)
123 {
124     ::_exit(EXIT_SUCCESS);
125 }
126 
127 
128 static void child_pause(const fs::path&) UTILS_NORETURN;
129 
130 
131 /// Subprocess that just blocks.
132 static void
133 child_pause(const fs::path& /* control_directory */)
134 {
135     sigset_t mask;
136     sigemptyset(&mask);
137     for (;;) {
138         ::sigsuspend(&mask);
139     }
140     std::abort();
141 }
142 
143 
144 /// Generates a core dump, if possible.
145 ///
146 /// \post If this fails to generate a core file, the test case is marked as
147 /// skipped.  The caller can rely on this when attempting further checks on the
148 /// core dump by assuming that the core dump exists somewhere.
149 ///
150 /// \param test_case Pointer to the caller test case, needed to obtain the path
151 ///     to the source directory.
152 /// \param base_name Name of the binary to execute, which will be a copy of a
153 ///     helper binary that always crashes.  This name should later be part of
154 ///     the core filename.
155 ///
156 /// \return The status of the crashed binary.
157 static process::status
158 generate_core(const atf::tests::tc* test_case, const char* base_name)
159 {
160     utils::prepare_coredump_test(test_case);
161 
162     const fs::path helper = fs::path(test_case->get_config_var("srcdir")) /
163         "stacktrace_helper";
164 
165     const process::status status = process::child::fork_files(
166         crash_me(helper, base_name),
167         fs::path("unused.out"), fs::path("unused.err"))->wait();
168     ATF_REQUIRE(status.signaled());
169     if (!status.coredump())
170         ATF_SKIP("Test failed to generate core dump");
171     return status;
172 }
173 
174 
175 /// Generates a core dump, if possible.
176 ///
177 /// \post If this fails to generate a core file, the test case is marked as
178 /// skipped.  The caller can rely on this when attempting further checks on the
179 /// core dump by assuming that the core dump exists somewhere.
180 ///
181 /// \param test_case Pointer to the caller test case, needed to obtain the path
182 ///     to the source directory.
183 /// \param base_name Name of the binary to execute, which will be a copy of a
184 ///     helper binary that always crashes.  This name should later be part of
185 ///     the core filename.
186 /// \param executor_handle Executor to use to generate the core dump.
187 ///
188 /// \return The exit handle of the subprocess so that a stacktrace can be
189 /// executed reusing this context later on.
190 static executor::exit_handle
191 generate_core(const atf::tests::tc* test_case, const char* base_name,
192               executor::executor_handle& executor_handle)
193 {
194     utils::prepare_coredump_test(test_case);
195 
196     const fs::path helper = fs::path(test_case->get_config_var("srcdir")) /
197         "stacktrace_helper";
198 
199     const executor::exec_handle exec_handle = executor_handle.spawn(
200         crash_me(helper, base_name), datetime::delta(60, 0), none, none, none);
201     const executor::exit_handle exit_handle = executor_handle.wait(exec_handle);
202 
203     if (!exit_handle.status())
204         ATF_SKIP("Test failed to generate core dump (timed out)");
205     const process::status& status = exit_handle.status().get();
206     ATF_REQUIRE(status.signaled());
207     if (!status.coredump())
208         ATF_SKIP("Test failed to generate core dump");
209 
210     return exit_handle;
211 }
212 
213 
214 /// Creates a script.
215 ///
216 /// \param script Path to the script to create.
217 /// \param contents Contents of the script.
218 static void
219 create_script(const char* script, const std::string& contents)
220 {
221     atf::utils::create_file(script, "#! /bin/sh\n\n" + contents);
222     ATF_REQUIRE(::chmod(script, 0755) != -1);
223 }
224 
225 
226 }  // anonymous namespace
227 
228 
229 ATF_TEST_CASE_WITHOUT_HEAD(unlimit_core_size);
230 ATF_TEST_CASE_BODY(unlimit_core_size)
231 {
232     utils::require_run_coredump_tests(this);
233 
234     struct rlimit rl;
235     rl.rlim_cur = 0;
236     rl.rlim_max = RLIM_INFINITY;
237     if (::setrlimit(RLIMIT_CORE, &rl) == -1)
238         skip("Failed to lower the core size limit");
239 
240     ATF_REQUIRE(utils::unlimit_core_size());
241 
242     const fs::path helper = fs::path(get_config_var("srcdir")) /
243         "stacktrace_helper";
244     const process::status status = process::child::fork_files(
245         crash_me(helper),
246         fs::path("unused.out"), fs::path("unused.err"))->wait();
247     ATF_REQUIRE(status.signaled());
248     if (!status.coredump())
249         fail("Core not dumped as expected");
250 }
251 
252 
253 ATF_TEST_CASE_WITHOUT_HEAD(unlimit_core_size__hard_is_zero);
254 ATF_TEST_CASE_BODY(unlimit_core_size__hard_is_zero)
255 {
256     utils::require_run_coredump_tests(this);
257 
258     struct rlimit rl;
259     rl.rlim_cur = 0;
260     rl.rlim_max = 0;
261     if (::setrlimit(RLIMIT_CORE, &rl) == -1)
262         skip("Failed to lower the core size limit");
263 
264     ATF_REQUIRE(!utils::unlimit_core_size());
265 
266     const fs::path helper = fs::path(get_config_var("srcdir")) /
267         "stacktrace_helper";
268     const process::status status = process::child::fork_files(
269         crash_me(helper),
270         fs::path("unused.out"), fs::path("unused.err"))->wait();
271     ATF_REQUIRE(status.signaled());
272     ATF_REQUIRE(!status.coredump());
273 }
274 
275 
276 ATF_TEST_CASE_WITHOUT_HEAD(find_gdb__use_builtin);
277 ATF_TEST_CASE_BODY(find_gdb__use_builtin)
278 {
279     utils::builtin_gdb = "/path/to/gdb";
280     optional< fs::path > gdb = utils::find_gdb();
281     ATF_REQUIRE(gdb);
282     ATF_REQUIRE_EQ("/path/to/gdb", gdb.get().str());
283 }
284 
285 
286 ATF_TEST_CASE_WITHOUT_HEAD(find_gdb__search_builtin__ok);
287 ATF_TEST_CASE_BODY(find_gdb__search_builtin__ok)
288 {
289     atf::utils::create_file("custom-name", "");
290     ATF_REQUIRE(::chmod("custom-name", 0755) != -1);
291     const fs::path exp_gdb = fs::path("custom-name").to_absolute();
292 
293     utils::setenv("PATH", "/non-existent/location:.:/bin");
294 
295     utils::builtin_gdb = "custom-name";
296     optional< fs::path > gdb = utils::find_gdb();
297     ATF_REQUIRE(gdb);
298     ATF_REQUIRE_EQ(exp_gdb, gdb.get());
299 }
300 
301 
302 ATF_TEST_CASE_WITHOUT_HEAD(find_gdb__search_builtin__fail);
303 ATF_TEST_CASE_BODY(find_gdb__search_builtin__fail)
304 {
305     utils::setenv("PATH", ".");
306     utils::builtin_gdb = "foo";
307     optional< fs::path > gdb = utils::find_gdb();
308     ATF_REQUIRE(!gdb);
309 }
310 
311 
312 ATF_TEST_CASE_WITHOUT_HEAD(find_gdb__bogus_value);
313 ATF_TEST_CASE_BODY(find_gdb__bogus_value)
314 {
315     utils::builtin_gdb = "";
316     optional< fs::path > gdb = utils::find_gdb();
317     ATF_REQUIRE(!gdb);
318 }
319 
320 
321 ATF_TEST_CASE_WITHOUT_HEAD(find_core__found__short);
322 ATF_TEST_CASE_BODY(find_core__found__short)
323 {
324     const process::status status = generate_core(this, "short");
325     INV(status.coredump());
326     const optional< fs::path > core_name = utils::find_core(
327         fs::path("short"), status, fs::path("."));
328     if (!core_name)
329         fail("Core dumped, but no candidates found");
330     ATF_REQUIRE(core_name.get().str().find("core") != std::string::npos);
331     ATF_REQUIRE(fs::exists(core_name.get()));
332 }
333 
334 
335 ATF_TEST_CASE_WITHOUT_HEAD(find_core__found__long);
336 ATF_TEST_CASE_BODY(find_core__found__long)
337 {
338     const process::status status = generate_core(
339         this, "long-name-that-may-be-truncated-in-some-systems");
340     INV(status.coredump());
341     const optional< fs::path > core_name = utils::find_core(
342         fs::path("long-name-that-may-be-truncated-in-some-systems"),
343         status, fs::path("."));
344     if (!core_name)
345         fail("Core dumped, but no candidates found");
346     ATF_REQUIRE(core_name.get().str().find("core") != std::string::npos);
347     ATF_REQUIRE(fs::exists(core_name.get()));
348 }
349 
350 
351 ATF_TEST_CASE_WITHOUT_HEAD(find_core__not_found);
352 ATF_TEST_CASE_BODY(find_core__not_found)
353 {
354     const process::status status = process::status::fake_signaled(SIGILL, true);
355     const optional< fs::path > core_name = utils::find_core(
356         fs::path("missing"), status, fs::path("."));
357     if (core_name)
358         fail("Core not dumped, but candidate found: " + core_name.get().str());
359 }
360 
361 
362 ATF_TEST_CASE(dump_stacktrace__integration);
363 ATF_TEST_CASE_HEAD(dump_stacktrace__integration)
364 {
365     set_md_var("require.progs", utils::builtin_gdb);
366 }
367 ATF_TEST_CASE_BODY(dump_stacktrace__integration)
368 {
369     executor::executor_handle handle = executor::setup();
370 
371     executor::exit_handle exit_handle = generate_core(this, "short", handle);
372     INV(exit_handle.status());
373     INV(exit_handle.status().get().coredump());
374 
375     std::ostringstream output;
376     utils::dump_stacktrace(fs::path("short"), handle, exit_handle);
377 
378     // It is hard to validate the execution of an arbitrary GDB of which we do
379     // not know anything.  Just assume that the backtrace, at the very least,
380     // prints a couple of frame identifiers.
381     ATF_REQUIRE(!atf::utils::grep_file("#0", exit_handle.stdout_file().str()));
382     ATF_REQUIRE( atf::utils::grep_file("#0", exit_handle.stderr_file().str()));
383     ATF_REQUIRE(!atf::utils::grep_file("#1", exit_handle.stdout_file().str()));
384     ATF_REQUIRE( atf::utils::grep_file("#1", exit_handle.stderr_file().str()));
385 
386     exit_handle.cleanup();
387     handle.cleanup();
388 }
389 
390 
391 ATF_TEST_CASE_WITHOUT_HEAD(dump_stacktrace__ok);
392 ATF_TEST_CASE_BODY(dump_stacktrace__ok)
393 {
394     utils::setenv("PATH", ".");
395     create_script("fake-gdb", "echo 'frame 1'; echo 'frame 2'; "
396                   "echo 'some warning' 1>&2; exit 0");
397     utils::builtin_gdb = "fake-gdb";
398 
399     executor::executor_handle handle = executor::setup();
400     executor::exit_handle exit_handle = generate_core(this, "short", handle);
401     INV(exit_handle.status());
402     INV(exit_handle.status().get().coredump());
403 
404     utils::dump_stacktrace(fs::path("short"), handle, exit_handle);
405 
406     // Note how all output is expected on stderr even for the messages that the
407     // script decided to send to stdout.
408     ATF_REQUIRE(atf::utils::grep_file("exited with signal [0-9]* and dumped",
409                                       exit_handle.stderr_file().str()));
410     ATF_REQUIRE(atf::utils::grep_file("^frame 1$",
411                                       exit_handle.stderr_file().str()));
412     ATF_REQUIRE(atf::utils::grep_file("^frame 2$",
413                                       exit_handle.stderr_file().str()));
414     ATF_REQUIRE(atf::utils::grep_file("^some warning$",
415                                       exit_handle.stderr_file().str()));
416     ATF_REQUIRE(atf::utils::grep_file("GDB exited successfully",
417                                       exit_handle.stderr_file().str()));
418 
419     exit_handle.cleanup();
420     handle.cleanup();
421 }
422 
423 
424 ATF_TEST_CASE_WITHOUT_HEAD(dump_stacktrace__cannot_find_core);
425 ATF_TEST_CASE_BODY(dump_stacktrace__cannot_find_core)
426 {
427     // Make sure we can find a GDB binary so that we don't fail the test for
428     // the wrong reason.
429     utils::setenv("PATH", ".");
430     utils::builtin_gdb = "fake-gdb";
431     atf::utils::create_file("fake-gdb", "unused");
432 
433     executor::executor_handle handle = executor::setup();
434     executor::exit_handle exit_handle = generate_core(this, "short", handle);
435 
436     const optional< fs::path > core_name = utils::find_core(
437         fs::path("short"),
438         exit_handle.status().get(),
439         exit_handle.work_directory());
440     if (core_name) {
441         // This is needed even if we provide a different basename to
442         // dump_stacktrace below because the system policies may be generating
443         // core dumps by PID, not binary name.
444         std::cout << "Removing core dump: " << core_name << '\n';
445         fs::unlink(core_name.get());
446     }
447 
448     utils::dump_stacktrace(fs::path("fake"), handle, exit_handle);
449 
450     atf::utils::cat_file(exit_handle.stdout_file().str(), "stdout: ");
451     atf::utils::cat_file(exit_handle.stderr_file().str(), "stderr: ");
452     ATF_REQUIRE(atf::utils::grep_file("Cannot find any core file",
453                                       exit_handle.stderr_file().str()));
454 
455     exit_handle.cleanup();
456     handle.cleanup();
457 }
458 
459 
460 ATF_TEST_CASE_WITHOUT_HEAD(dump_stacktrace__cannot_find_gdb);
461 ATF_TEST_CASE_BODY(dump_stacktrace__cannot_find_gdb)
462 {
463     utils::setenv("PATH", ".");
464     utils::builtin_gdb = "missing-gdb";
465 
466     executor::executor_handle handle = executor::setup();
467     executor::exit_handle exit_handle = generate_core(this, "short", handle);
468 
469     utils::dump_stacktrace(fs::path("fake"), handle, exit_handle);
470 
471     ATF_REQUIRE(atf::utils::grep_file(
472                     "Cannot find GDB binary; builtin was 'missing-gdb'",
473                     exit_handle.stderr_file().str()));
474 
475     exit_handle.cleanup();
476     handle.cleanup();
477 }
478 
479 
480 ATF_TEST_CASE_WITHOUT_HEAD(dump_stacktrace__gdb_fail);
481 ATF_TEST_CASE_BODY(dump_stacktrace__gdb_fail)
482 {
483     utils::setenv("PATH", ".");
484     create_script("fake-gdb", "echo 'foo'; echo 'bar' 1>&2; exit 1");
485     const std::string gdb = (fs::current_path() / "fake-gdb").str();
486     utils::builtin_gdb = gdb.c_str();
487 
488     executor::executor_handle handle = executor::setup();
489     executor::exit_handle exit_handle = generate_core(this, "short", handle);
490 
491     atf::utils::create_file((exit_handle.work_directory() / "fake.core").str(),
492                             "Invalid core file, but not read");
493     utils::dump_stacktrace(fs::path("fake"), handle, exit_handle);
494 
495     ATF_REQUIRE(atf::utils::grep_file("^foo$",
496                                       exit_handle.stderr_file().str()));
497     ATF_REQUIRE(atf::utils::grep_file("^bar$",
498                                       exit_handle.stderr_file().str()));
499     ATF_REQUIRE(atf::utils::grep_file("GDB failed; see output above",
500                                       exit_handle.stderr_file().str()));
501 
502     exit_handle.cleanup();
503     handle.cleanup();
504 }
505 
506 
507 ATF_TEST_CASE_WITHOUT_HEAD(dump_stacktrace__gdb_timeout);
508 ATF_TEST_CASE_BODY(dump_stacktrace__gdb_timeout)
509 {
510     utils::setenv("PATH", ".");
511     create_script("fake-gdb", "while :; do sleep 1; done");
512     const std::string gdb = (fs::current_path() / "fake-gdb").str();
513     utils::builtin_gdb = gdb.c_str();
514     utils::gdb_timeout = datetime::delta(1, 0);
515 
516     executor::executor_handle handle = executor::setup();
517     executor::exit_handle exit_handle = generate_core(this, "short", handle);
518 
519     atf::utils::create_file((exit_handle.work_directory() / "fake.core").str(),
520                             "Invalid core file, but not read");
521     utils::dump_stacktrace(fs::path("fake"), handle, exit_handle);
522 
523     ATF_REQUIRE(atf::utils::grep_file("GDB timed out",
524                                       exit_handle.stderr_file().str()));
525 
526     exit_handle.cleanup();
527     handle.cleanup();
528 }
529 
530 
531 ATF_TEST_CASE_WITHOUT_HEAD(dump_stacktrace_if_available__append);
532 ATF_TEST_CASE_BODY(dump_stacktrace_if_available__append)
533 {
534     utils::setenv("PATH", ".");
535     create_script("fake-gdb", "echo 'frame 1'; exit 0");
536     utils::builtin_gdb = "fake-gdb";
537 
538     executor::executor_handle handle = executor::setup();
539     executor::exit_handle exit_handle = generate_core(this, "short", handle);
540 
541     atf::utils::create_file(exit_handle.stdout_file().str(), "Pre-stdout");
542     atf::utils::create_file(exit_handle.stderr_file().str(), "Pre-stderr");
543 
544     utils::dump_stacktrace_if_available(fs::path("short"), handle, exit_handle);
545 
546     ATF_REQUIRE(atf::utils::grep_file("Pre-stdout",
547                                       exit_handle.stdout_file().str()));
548     ATF_REQUIRE(atf::utils::grep_file("Pre-stderr",
549                                       exit_handle.stderr_file().str()));
550     ATF_REQUIRE(atf::utils::grep_file("frame 1",
551                                       exit_handle.stderr_file().str()));
552 
553     exit_handle.cleanup();
554     handle.cleanup();
555 }
556 
557 
558 ATF_TEST_CASE_WITHOUT_HEAD(dump_stacktrace_if_available__no_status);
559 ATF_TEST_CASE_BODY(dump_stacktrace_if_available__no_status)
560 {
561     executor::executor_handle handle = executor::setup();
562     const executor::exec_handle exec_handle = handle.spawn(
563         child_pause, datetime::delta(0, 100000), none, none, none);
564     executor::exit_handle exit_handle = handle.wait(exec_handle);
565     INV(!exit_handle.status());
566 
567     utils::dump_stacktrace_if_available(fs::path("short"), handle, exit_handle);
568     ATF_REQUIRE(atf::utils::compare_file(exit_handle.stdout_file().str(), ""));
569     ATF_REQUIRE(atf::utils::compare_file(exit_handle.stderr_file().str(), ""));
570 
571     exit_handle.cleanup();
572     handle.cleanup();
573 }
574 
575 
576 ATF_TEST_CASE_WITHOUT_HEAD(dump_stacktrace_if_available__no_coredump);
577 ATF_TEST_CASE_BODY(dump_stacktrace_if_available__no_coredump)
578 {
579     executor::executor_handle handle = executor::setup();
580     const executor::exec_handle exec_handle = handle.spawn(
581         child_exit, datetime::delta(60, 0), none, none, none);
582     executor::exit_handle exit_handle = handle.wait(exec_handle);
583     INV(exit_handle.status());
584     INV(exit_handle.status().get().exited());
585     INV(exit_handle.status().get().exitstatus() == EXIT_SUCCESS);
586 
587     utils::dump_stacktrace_if_available(fs::path("short"), handle, exit_handle);
588     ATF_REQUIRE(atf::utils::compare_file(exit_handle.stdout_file().str(), ""));
589     ATF_REQUIRE(atf::utils::compare_file(exit_handle.stderr_file().str(), ""));
590 
591     exit_handle.cleanup();
592     handle.cleanup();
593 }
594 
595 
596 ATF_INIT_TEST_CASES(tcs)
597 {
598     ATF_ADD_TEST_CASE(tcs, unlimit_core_size);
599     ATF_ADD_TEST_CASE(tcs, unlimit_core_size__hard_is_zero);
600 
601     ATF_ADD_TEST_CASE(tcs, find_gdb__use_builtin);
602     ATF_ADD_TEST_CASE(tcs, find_gdb__search_builtin__ok);
603     ATF_ADD_TEST_CASE(tcs, find_gdb__search_builtin__fail);
604     ATF_ADD_TEST_CASE(tcs, find_gdb__bogus_value);
605 
606     ATF_ADD_TEST_CASE(tcs, find_core__found__short);
607     ATF_ADD_TEST_CASE(tcs, find_core__found__long);
608     ATF_ADD_TEST_CASE(tcs, find_core__not_found);
609 
610     ATF_ADD_TEST_CASE(tcs, dump_stacktrace__integration);
611     ATF_ADD_TEST_CASE(tcs, dump_stacktrace__ok);
612     ATF_ADD_TEST_CASE(tcs, dump_stacktrace__cannot_find_core);
613     ATF_ADD_TEST_CASE(tcs, dump_stacktrace__cannot_find_gdb);
614     ATF_ADD_TEST_CASE(tcs, dump_stacktrace__gdb_fail);
615     ATF_ADD_TEST_CASE(tcs, dump_stacktrace__gdb_timeout);
616 
617     ATF_ADD_TEST_CASE(tcs, dump_stacktrace_if_available__append);
618     ATF_ADD_TEST_CASE(tcs, dump_stacktrace_if_available__no_status);
619     ATF_ADD_TEST_CASE(tcs, dump_stacktrace_if_available__no_coredump);
620 }
621