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