stacktrace_test.c revision 1.1
1// Copyright 2012 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 "stacktrace.h"
30
31#include <sys/resource.h>
32#include <sys/stat.h>
33#include <sys/wait.h>
34
35#include <stdlib.h>
36#include <string.h>
37#include <unistd.h>
38
39#include <atf-c.h>
40
41#include "env.h"
42#include "error.h"
43#include "fs.h"
44#include "run.h"
45#include "text.h"
46
47
48/// Ensures that the given expression does not return a kyua_error_t.
49///
50/// \param expr Expression to evaluate.
51#define RE(expr) ATF_REQUIRE(!kyua_error_is_set(expr))
52
53
54/// Ensures that the given expression does not return a kyua_error_t.
55///
56/// \param expr Expression to evaluate.
57/// \param msg Failure message.
58#define RE_MSG(expr, msg) ATF_REQUIRE_MSG(!kyua_error_is_set(expr), msg)
59
60
61/// Generates a core dump.
62///
63/// Due to the complexity of this interface, you should probably use
64/// generate_core() instead.
65///
66/// \post If this fails to generate a core file, the test case is marked as
67/// skipped.  The caller therefore can rely that a core dump has been created on
68/// return.
69///
70/// \param tc Pointer to the caller test case.
71/// \param run_params Parameters for the execution of the helper.
72/// \param helper_path Path to the created helper.
73/// \param exec_path Name of the helper, prefixed with ./ so that it can be
74///     executed from within the work directory.
75/// \param helper_name Basename of the helper.
76///
77/// \return The PID of the crashed binary.
78static pid_t
79generate_core_aux(const atf_tc_t* tc, const kyua_run_params_t* run_params,
80                  const char* helper_path, const char* exec_path,
81                  const char* helper_name)
82{
83    const char* srcdir = atf_tc_get_config_var(tc, "srcdir");
84
85    char* src_helper;
86    RE(kyua_fs_concat(&src_helper, srcdir, "stacktrace_helper", NULL));
87    atf_utils_copy_file(src_helper, helper_path);
88    free(src_helper);
89
90    // We use kyua_run_fork for this to better simulate the final use case of
91    // the stacktrace gathering, as test programs are run through these
92    // functions.  Also, kyua_run_fork provides us with automatic unlimiting of
93    // resources so that core files can be generated.
94
95    pid_t pid;
96    const kyua_error_t error = kyua_run_fork(run_params, &pid);
97    if (!kyua_error_is_set(error) && pid == 0) {
98        const char* const args[] = { helper_name, NULL };
99        kyua_run_exec(exec_path, args);
100    }
101    RE(error);
102
103    int status; bool timed_out;
104    RE_MSG(kyua_run_wait(pid, &status, &timed_out),
105           "wait failed; unexpected problem during exec?");
106
107    ATF_REQUIRE(WIFSIGNALED(status));
108    if (!WCOREDUMP(status))
109        atf_tc_skip("Test failed to generate core dump");
110    return pid;
111}
112
113
114/// Creates a script.
115///
116/// \param script Path to the script to create.
117/// \param contents Contents of the script.
118static void
119create_script(const char* script, const char* contents)
120{
121    atf_utils_create_file(script, "#! /bin/sh\n\n%s\n", contents);
122    ATF_REQUIRE(chmod(script, 0755) != -1);
123}
124
125
126/// Generates a core file.
127///
128/// \param tc Pointer to the calling test case.
129/// \param work_directory Name of the directory in which to place the binary
130///     that will generate the stacktrace.
131/// \param program_name Basename of the binary that will crash.
132///
133/// \return PID of the process that generated the core file.
134static pid_t
135generate_core(const atf_tc_t* tc, const char* work_directory,
136              const char* program_name)
137{
138    kyua_run_params_t run_params;
139    kyua_run_params_init(&run_params);
140    if (strcmp(work_directory, ".") != 0) {
141        ATF_REQUIRE(mkdir(work_directory, 0755) != -1);
142        run_params.work_directory = work_directory;
143    }
144
145    char* copy_to; char* exec_path;
146    RE(kyua_text_printf(&copy_to, "%s/%s", work_directory, program_name));
147    RE(kyua_text_printf(&exec_path, "./%s", program_name));
148    const pid_t pid = generate_core_aux(tc, &run_params, copy_to, exec_path,
149                                        program_name);
150    free(exec_path);
151    free(copy_to);
152    return pid;
153}
154
155
156/// Prepares and runs kyua_stacktrace_dump().
157///
158/// \param tc Pointer to the calling test case.
159/// \param work_directory Name of the directory in which to place the binary
160///     that will generate the stacktrace.
161/// \param program_name Basename of the binary that will crash.
162/// \param output_name Name of the file to which to write the stacktrace.
163/// \param timeout_seconds Time to give GDB to complete.
164static void
165do_dump(const atf_tc_t* tc, const char* work_directory,
166        const char* program_name, const char* output_name,
167        const int timeout_seconds)
168{
169    const pid_t pid = generate_core(tc, work_directory, program_name);
170
171    kyua_run_params_t run_params;
172    kyua_run_params_init(&run_params);
173    run_params.timeout_seconds = timeout_seconds + 100;  // Some large value.
174    run_params.work_directory = work_directory;  // Created by generate_core.
175
176    kyua_stacktrace_gdb_timeout = timeout_seconds;
177
178    FILE* output = fopen(output_name, "w");
179    ATF_REQUIRE(output != NULL);
180    kyua_stacktrace_dump(program_name, pid, &run_params, output);
181    fclose(output);
182    atf_utils_cat_file(output_name, "dump output: ");
183}
184
185
186ATF_TC_WITHOUT_HEAD(find_core__found__short);
187ATF_TC_BODY(find_core__found__short, tc)
188{
189    const pid_t pid = generate_core(tc, "dir", "short");
190    const char* core_name = kyua_stacktrace_find_core("short", "dir", pid);
191    if (core_name == NULL)
192        atf_tc_fail("Core dumped, but no candidates found");
193    ATF_REQUIRE(strstr(core_name, "core") != NULL);
194    ATF_REQUIRE(access(core_name, F_OK) != -1);
195}
196
197
198ATF_TC_WITHOUT_HEAD(find_core__found__long);
199ATF_TC_BODY(find_core__found__long, tc)
200{
201    const pid_t pid = generate_core(
202        tc, "dir", "long-name-that-may-be-truncated-in-some-systems");
203    const char* core_name = kyua_stacktrace_find_core(
204        "long-name-that-may-be-truncated-in-some-systems", "dir", pid);
205    if (core_name == NULL)
206        atf_tc_fail("Core dumped, but no candidates found");
207    ATF_REQUIRE(strstr(core_name, "core") != NULL);
208    ATF_REQUIRE(access(core_name, F_OK) != -1);
209}
210
211
212ATF_TC_WITHOUT_HEAD(find_core__not_found);
213ATF_TC_BODY(find_core__not_found, tc)
214{
215    const char* core_name = kyua_stacktrace_find_core("missing", ".", 1);
216    if (core_name != NULL)
217        atf_tc_fail("Core not dumped, but candidate found: %s", core_name);
218}
219
220
221ATF_TC_WITHOUT_HEAD(dump__integration);
222ATF_TC_BODY(dump__integration, tc)
223{
224    do_dump(tc, "dir", "short", "stacktrace", 10);
225
226    // It is hard to validate the execution of an arbitrary GDB of which we know
227    // nothing anything.  Just assume that the backtrace, at the very least,
228    // prints a frame identifier.
229    ATF_REQUIRE(atf_utils_grep_file("#0", "stacktrace"));
230}
231
232
233ATF_TC_WITHOUT_HEAD(dump__ok);
234ATF_TC_BODY(dump__ok, tc)
235{
236    RE(kyua_env_set("PATH", "."));
237    create_script("fake-gdb", "echo 'frame 1'; echo 'frame 2'; "
238                  "echo 'some warning' 1>&2; exit 0");
239    kyua_stacktrace_gdb = "fake-gdb";
240
241    do_dump(tc, ".", "short", "stacktrace", 10);
242
243    ATF_REQUIRE(atf_utils_grep_file("dumped core; attempting to gather",
244                                    "stacktrace"));
245    ATF_REQUIRE(atf_utils_grep_file("frame 1", "stacktrace"));
246    ATF_REQUIRE(atf_utils_grep_file("frame 2", "stacktrace"));
247    ATF_REQUIRE(atf_utils_grep_file("some warning", "stacktrace"));
248    ATF_REQUIRE(atf_utils_grep_file("GDB exited successfully", "stacktrace"));
249}
250
251
252ATF_TC_WITHOUT_HEAD(dump__cannot_find_core);
253ATF_TC_BODY(dump__cannot_find_core, tc)
254{
255    kyua_run_params_t run_params;
256    kyua_run_params_init(&run_params);
257
258    FILE* output = fopen("stacktrace", "w");
259    ATF_REQUIRE(output != NULL);
260    // This assumes that init(8) has never core dumped.
261    kyua_stacktrace_dump("missing", 1, &run_params, output);
262    fclose(output);
263    atf_utils_cat_file("stacktrace", "dump output: ");
264
265    ATF_REQUIRE(atf_utils_grep_file("Cannot find any core file", "stacktrace"));
266}
267
268
269ATF_TC_WITHOUT_HEAD(dump__cannot_find_gdb);
270ATF_TC_BODY(dump__cannot_find_gdb, tc)
271{
272    RE(kyua_env_set("PATH", "."));
273    kyua_stacktrace_gdb = "missing-gdb";
274
275    do_dump(tc, ".", "dont-care", "stacktrace", 10);
276
277    ATF_REQUIRE(atf_utils_grep_file("execvp failed", "stacktrace"));
278}
279
280
281ATF_TC_WITHOUT_HEAD(dump__gdb_fail);
282ATF_TC_BODY(dump__gdb_fail, tc)
283{
284    RE(kyua_env_set("PATH", "."));
285    create_script("fake-gdb", "echo 'foo'; echo 'bar' 1>&2; exit 56");
286    kyua_stacktrace_gdb = "fake-gdb";
287
288    do_dump(tc, ".", "short", "stacktrace", 10);
289
290    ATF_REQUIRE(atf_utils_grep_file("foo", "stacktrace"));
291    ATF_REQUIRE(atf_utils_grep_file("bar", "stacktrace"));
292    ATF_REQUIRE(atf_utils_grep_file("GDB failed with code 56; see output above "
293                                    "for details", "stacktrace"));
294}
295
296
297ATF_TC_WITHOUT_HEAD(dump__gdb_times_out);
298ATF_TC_BODY(dump__gdb_times_out, tc)
299{
300    RE(kyua_env_set("PATH", "."));
301    create_script("fake-gdb", "echo 'foo'; echo 'bar' 1>&2; "
302                  "/bin/sleep 10; /usr/bin/sleep 10; exit 0");
303    kyua_stacktrace_gdb = "fake-gdb";
304
305    do_dump(tc, ".", "short", "stacktrace", 1);
306
307    ATF_REQUIRE(atf_utils_grep_file("foo", "stacktrace"));
308    ATF_REQUIRE(atf_utils_grep_file("bar", "stacktrace"));
309    ATF_REQUIRE(atf_utils_grep_file("GDB failed; timed out", "stacktrace"));
310}
311
312
313ATF_TP_ADD_TCS(tp)
314{
315    ATF_TP_ADD_TC(tp, find_core__found__short);
316    ATF_TP_ADD_TC(tp, find_core__found__long);
317    ATF_TP_ADD_TC(tp, find_core__not_found);
318
319    ATF_TP_ADD_TC(tp, dump__integration);
320    ATF_TP_ADD_TC(tp, dump__ok);
321    ATF_TP_ADD_TC(tp, dump__cannot_find_core);
322    ATF_TP_ADD_TC(tp, dump__cannot_find_gdb);
323    ATF_TP_ADD_TC(tp, dump__gdb_fail);
324    ATF_TP_ADD_TC(tp, dump__gdb_times_out);
325
326    return atf_no_error();
327}
328