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