xref: /netbsd-src/external/bsd/kyua-testers/dist/atf_main.c (revision 754f425fc237c181450c91977727274098801c74)
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 <sys/wait.h>
30 
31 #include <assert.h>
32 #include <err.h>
33 #include <errno.h>
34 #include <stdlib.h>
35 #include <string.h>
36 #include <unistd.h>
37 
38 #include "atf_list.h"
39 #include "atf_result.h"
40 #include "cli.h"
41 #include "defs.h"
42 #include "error.h"
43 #include "fs.h"
44 #include "run.h"
45 #include "stacktrace.h"
46 #include "text.h"
47 
48 
49 /// Template for the creation of the temporary work directories.
50 #define WORKDIR_TEMPLATE "kyua.atf-tester.XXXXXX"
51 
52 
53 static void run_list(const char*, const int[2]) KYUA_DEFS_NORETURN;
54 
55 
56 /// Executes the test program in list mode.
57 ///
58 /// \param test_program Path to the test program to execute; should be absolute.
59 /// \param stdout_fds Pipe to write the output of the test program to.
60 static void
run_list(const char * test_program,const int stdout_fds[2])61 run_list(const char* test_program, const int stdout_fds[2])
62 {
63     (void)close(stdout_fds[0]);
64 
65     if (stdout_fds[1] != STDOUT_FILENO) {
66         if (dup2(stdout_fds[1], STDOUT_FILENO) == -1)
67             err(EXIT_FAILURE, "dup2 failed");
68         (void)close(stdout_fds[1]);
69     }
70 
71     if (dup2(STDOUT_FILENO, STDERR_FILENO) == -1)
72         err(EXIT_FAILURE, "dup2 failed");
73 
74     const char* const program_args[] = { test_program, "-l", NULL };
75     kyua_run_exec(test_program, program_args);
76 }
77 
78 
79 /// Dumps the contents of the input file into the output.
80 ///
81 /// \param input File from which to read.
82 /// \param output File to which to write.
83 ///
84 /// \return An error if there is a problem.
85 static kyua_error_t
dump_file(FILE * input,FILE * output)86 dump_file(FILE* input, FILE* output)
87 {
88     char buffer[1024];
89     size_t length;
90 
91     while ((length = fread(buffer, 1, sizeof(buffer), input)) > 0) {
92         if (fwrite(buffer, 1, length, output) != length) {
93             return kyua_generic_error_new("Failed to write to output file");
94         }
95     }
96     if (ferror(input))
97         return kyua_libc_error_new(errno, "Failed to read test cases list");
98 
99     return kyua_error_ok();
100 }
101 
102 
103 /// Creates a file within the work directory.
104 ///
105 /// \param work_directory Path to the work directory.
106 /// \param name Name of the file to create.
107 /// \param mode Mode of the file, as specified by fopen(3).
108 /// \param [out] file Pointer to the created stream.
109 ///
110 /// \return An error if there is a problem.
111 static kyua_error_t
create_file_in_work_directory(const char * work_directory,const char * name,const char * mode,FILE ** file)112 create_file_in_work_directory(const char* work_directory, const char* name,
113                               const char* mode, FILE** file)
114 {
115     char* path;
116     kyua_error_t error = kyua_fs_concat(&path, work_directory, name, NULL);
117     if (kyua_error_is_set(error))
118         goto out;
119 
120     FILE* tmp_file = fopen(path, mode);
121     if (tmp_file == NULL) {
122         error = kyua_libc_error_new(errno, "Failed to create %s", path);
123         goto out_path;
124     }
125 
126     *file = tmp_file;
127 
128     assert(!kyua_error_is_set(error));
129 out_path:
130     free(path);
131 out:
132     return error;
133 }
134 
135 
136 /// Lists the test cases in a test program.
137 ///
138 /// \param test_program Path to the test program for which to list the test
139 ///     cases.  Should be absolute.
140 /// \param run_params Execution parameters to configure the test process.
141 ///
142 /// \return An error if the listing fails; OK otherwise.
143 static kyua_error_t
list_test_cases(const char * test_program,const kyua_run_params_t * run_params)144 list_test_cases(const char* test_program, const kyua_run_params_t* run_params)
145 {
146     kyua_error_t error;
147 
148     char* work_directory;
149     error = kyua_run_work_directory_enter(WORKDIR_TEMPLATE,
150                                           run_params->unprivileged_user,
151                                           run_params->unprivileged_group,
152                                           &work_directory);
153     if (kyua_error_is_set(error))
154         goto out;
155     kyua_run_params_t real_run_params = *run_params;
156     real_run_params.work_directory = work_directory;
157 
158     int stdout_fds[2];
159     if (pipe(stdout_fds) == -1) {
160         error = kyua_libc_error_new(errno, "pipe failed");
161         goto out_work_directory;
162     }
163 
164     pid_t pid;
165     error = kyua_run_fork(&real_run_params, &pid);
166     if (!kyua_error_is_set(error) && pid == 0) {
167         run_list(test_program, stdout_fds);
168     }
169     assert(pid != -1 && pid != 0);
170     if (kyua_error_is_set(error))
171         goto out_stdout_fds;
172 
173     FILE* tmp_output = NULL;  // Initialize to shut up gcc warning.
174     error = create_file_in_work_directory(real_run_params.work_directory,
175                                           "list.txt", "w+", &tmp_output);
176     if (kyua_error_is_set(error))
177         goto out_stdout_fds;
178 
179     close(stdout_fds[1]); stdout_fds[1] = -1;
180     kyua_error_t parse_error = atf_list_parse(stdout_fds[0], tmp_output);
181     stdout_fds[0] = -1;  // Guaranteed closed by atf_list_parse.
182     // Delay reporting of parse errors to later.  If we detect a problem while
183     // waiting for the test program, we know that the parsing has most likely
184     // failed and therefore the error with the program is more important for
185     // reporting purposes.
186 
187     int status; bool timed_out;
188     error = kyua_run_wait(pid, &status, &timed_out);
189     if (kyua_error_is_set(error))
190         goto out_tmp_output;
191     if (!WIFEXITED(status) || WEXITSTATUS(status) != EXIT_SUCCESS) {
192         error = kyua_generic_error_new("Test program list did not return "
193                                        "success");
194         goto out_tmp_output;
195     }
196 
197     error = kyua_error_subsume(error, parse_error);
198     if (!kyua_error_is_set(error)) {
199         rewind(tmp_output);
200         error = dump_file(tmp_output, stdout);
201     }
202 
203 out_tmp_output:
204     fclose(tmp_output);
205 out_stdout_fds:
206     if (stdout_fds[0] != -1)
207         close(stdout_fds[0]);
208     if (stdout_fds[1] != -1)
209         close(stdout_fds[1]);
210 out_work_directory:
211     error = kyua_error_subsume(error,
212         kyua_run_work_directory_leave(&work_directory));
213 out:
214     return error;
215 }
216 
217 
218 /// Counts the length of a user variables array.
219 ///
220 /// \param user_variables The array of elements to be counted.
221 ///
222 /// \return The length of the array.
223 static size_t
count_variables(const char * const user_variables[])224 count_variables(const char* const user_variables[])
225 {
226     size_t count = 0;
227     const char* const* iter;
228     for (iter = user_variables; *iter != NULL; ++iter)
229         count++;
230     return count;
231 }
232 
233 
234 static void exec_body(const char* test_program, const char* test_case,
235                       const char* result_file,
236                       const char* const user_variables[]) KYUA_DEFS_NORETURN;
237 
238 
239 /// Executes the body of a test case.
240 ///
241 /// \param test_program Path to the test program to execute.
242 /// \param test_case Name of the test case to run.
243 /// \param result_file Path to the ATF result file to be created.
244 /// \param user_variables Set of configuration variables to pass to the test.
245 static void
exec_body(const char * test_program,const char * test_case,const char * result_file,const char * const user_variables[])246 exec_body(const char* test_program, const char* test_case,
247           const char* result_file, const char* const user_variables[])
248 {
249     const size_t nargs =
250         1 /* test_program */ +
251         2 /* -r result_file */
252         + 2 * count_variables(user_variables) /* -v name=value */
253         + 1 /* test_case */ +
254         1 /* NULL */;
255 
256     const char** args = malloc(sizeof(const char*) * nargs);
257     if (args == NULL)
258         kyua_error_err(EXIT_FAILURE, kyua_oom_error_new(),
259                        "Failed to construct arguments list");
260 
261     size_t i = 0;
262     args[i++] = test_program;
263     args[i++] = "-r";
264     args[i++] = result_file;
265     const char* const* iter;
266     for (iter = user_variables; *iter != NULL; ++iter) {
267         args[i++] = "-v";
268         args[i++] = *iter;
269     }
270     args[i++] = test_case;
271     args[i++] = NULL;
272     assert(i == nargs);
273 
274     kyua_run_exec(test_program, args);
275 }
276 
277 
278 /// Forks and executes the body of a test case in a controlled manner.
279 ///
280 /// \param test_program Path to the test program to execute.
281 /// \param test_case Name of the test case to run.
282 /// \param result_file Path to the ATF result file to be created.
283 /// \param user_variables Set of configuration variables to pass to the test.
284 /// \param run_params Settings to control the subprocess.
285 /// \param [out] success Set to true if the test case runs properly and returns
286 ///     a result that is to be considered as successful.
287 ///
288 /// \return OK if all goes well, an error otherwise.  Note that a failed test
289 /// case is denoted by setting success to false on exit, not by returning an
290 /// error.
291 static kyua_error_t
run_body(const char * test_program,const char * test_case,const char * result_file,const char * const user_variables[],const kyua_run_params_t * run_params,bool * success)292 run_body(const char* test_program, const char* test_case,
293          const char* result_file, const char* const user_variables[],
294          const kyua_run_params_t* run_params, bool* success)
295 {
296     kyua_error_t error;
297 
298     char* tmp_result_file;
299     error = kyua_fs_concat(&tmp_result_file, run_params->work_directory,
300                            "result.txt", NULL);
301     if (kyua_error_is_set(error))
302         goto out;
303 
304     pid_t pid;
305     error = kyua_run_fork(run_params, &pid);
306     if (!kyua_error_is_set(error) && pid == 0) {
307         exec_body(test_program, test_case, tmp_result_file, user_variables);
308     }
309     assert(pid != -1 && pid != 0);
310     if (kyua_error_is_set(error))
311         goto out_tmp_result_file;
312 
313     int status; bool timed_out;
314     error = kyua_run_wait(pid, &status, &timed_out);
315     if (kyua_error_is_set(error))
316         goto out_tmp_result_file;
317 
318     if (WIFSIGNALED(status) && WCOREDUMP(status)) {
319         kyua_stacktrace_dump(test_program, pid, run_params, stderr);
320     }
321 
322     error = kyua_atf_result_rewrite(tmp_result_file, result_file, status,
323                                     timed_out, success);
324 
325 out_tmp_result_file:
326     free(tmp_result_file);
327 out:
328     return error;
329 }
330 
331 
332 static void exec_cleanup(const char* test_program, const char* test_case,
333                          const char* const user_variables[]) KYUA_DEFS_NORETURN;
334 
335 
336 /// Executes the cleanup of a test case.
337 ///
338 /// \param test_program Path to the test program to execute.
339 /// \param test_case Name of the test case to run.
340 /// \param user_variables Set of configuration variables to pass to the test.
341 static void
exec_cleanup(const char * test_program,const char * test_case,const char * const user_variables[])342 exec_cleanup(const char* test_program, const char* test_case,
343              const char* const user_variables[])
344 {
345     char* name;
346     kyua_error_t error = kyua_text_printf(&name, "%s:cleanup", test_case);
347     if (kyua_error_is_set(error))
348         kyua_error_err(EXIT_FAILURE, error,
349                        "Failed to construct argument list");
350 
351     const size_t nargs =
352         1 /* test_program */ +
353         + 2 * count_variables(user_variables) /* -v name=value */
354         + 1 /* test_case */ +
355         1 /* NULL */;
356 
357     const char** args = malloc(sizeof(const char*) * nargs);
358     if (args == NULL)
359         kyua_error_err(EXIT_FAILURE, kyua_oom_error_new(),
360                        "Failed to construct arguments list");
361 
362     size_t i = 0;
363     args[i++] = test_program;
364     const char* const* iter;
365     for (iter = user_variables; *iter != NULL; ++iter) {
366         args[i++] = "-v";
367         args[i++] = *iter;
368     }
369     args[i++] = name;
370     args[i++] = NULL;
371     assert(i == nargs);
372 
373     kyua_run_exec(test_program, args);
374 }
375 
376 
377 /// Forks and executes the cleanup of a test case in a controlled manner.
378 ///
379 /// \param test_program Path to the test program to execute.
380 /// \param test_case Name of the test case to run.
381 /// \param result_file Path to the ATF result file created by the body of the
382 ///     test case.  The cleanup may update such file if it fails.
383 /// \param user_variables Set of configuration variables to pass to the test.
384 /// \param run_params Settings to control the subprocess.
385 /// \param body_success The success value returned by run_body().
386 /// \param [out] success Set to true if the test case runs properly and returns
387 ///     a result that is to be considered as successful.
388 ///
389 /// \return OK if all goes well, an error otherwise.  Note that a failed test
390 /// case cleanup is denoted by setting success to false on exit, not by
391 /// returning an error.
392 static kyua_error_t
run_cleanup(const char * test_program,const char * test_case,const char * result_file,const char * const user_variables[],const kyua_run_params_t * run_params,const bool body_success,bool * success)393 run_cleanup(const char* test_program, const char* test_case,
394             const char* result_file, const char* const user_variables[],
395             const kyua_run_params_t* run_params, const bool body_success,
396             bool* success)
397 {
398     kyua_error_t error;
399 
400     pid_t pid;
401     error = kyua_run_fork(run_params, &pid);
402     if (!kyua_error_is_set(error) && pid == 0) {
403         exec_cleanup(test_program, test_case, user_variables);
404     }
405     assert(pid != -1 && pid != 0);
406     if (kyua_error_is_set(error))
407         goto out;
408 
409     int status; bool timed_out;
410     error = kyua_run_wait(pid, &status, &timed_out);
411     if (kyua_error_is_set(error))
412         goto out;
413 
414     if (WIFSIGNALED(status) && WCOREDUMP(status)) {
415         kyua_stacktrace_dump(test_program, pid, run_params, stderr);
416     }
417 
418     if (body_success) {
419         // If the body has reported a successful result, we inspect the status
420         // of the cleanup routine.  If the cleanup has failed, then we need to
421         // mark the test as broken.  However, if the body itself had failed, we
422         // don't do this to give preference to the original result, which is
423         // probably more informative.
424         error = kyua_atf_result_cleanup_rewrite(result_file, status,
425                                                 timed_out, success);
426     }
427 
428 out:
429     return error;
430 }
431 
432 
433 /// Checks if the user variables indicate that a test has a cleanup routine.
434 ///
435 /// This is an ugly hack to allow us to run the cleanup routine only when a test
436 /// case has it.  When Kyua invokes the tester to generate the test case list,
437 /// the tester tells Kyua which tests have a cleanup routine.  However, when the
438 /// tests are later run from here (as a different invocation) we cannot know if
439 /// the test had a cleanup routine or not.  We rely on Kyua telling us this fact
440 /// by specifying has.cleanup=true in the variables.
441 ///
442 /// \param user_variables Array of name=value pairs that describe the user
443 ///     configuration variables for the test case.
444 static bool
has_cleanup(const char * const * user_variables)445 has_cleanup(const char* const* user_variables)
446 {
447     const char* const* iter;
448     for (iter = user_variables; *iter != NULL; ++iter) {
449         if (strcmp(*iter, "has.cleanup=false") == 0)
450             return false;
451     }
452 
453     // The default is true because not running a cleanup routine when it exists
454     // is worse than running an empty routine when not told to do so.
455     return true;
456 }
457 
458 
459 /// Runs a single test cases of a test program.
460 ///
461 /// \param test_program Path to the test program for which to list the test
462 ///     cases.  Should be absolute.
463 /// \param test_case Name of the test case to run.
464 /// \param result_file Path to the file to which to write the result of the
465 ///     test.  Should be absolute.
466 /// \param user_variables Array of name=value pairs that describe the user
467 ///     configuration variables for the test case.
468 /// \param run_params Execution parameters to configure the test process.
469 /// \param [out] success Set to true if the test case reported a valid exit
470 ///     condition (like "passed" or "skipped"); false otherwise.  This is
471 ///     only updated if the method returns OK.
472 ///
473 /// \return An error if the listing fails; OK otherwise.
474 static kyua_error_t
run_test_case(const char * test_program,const char * test_case,const char * result_file,const char * const user_variables[],const kyua_run_params_t * run_params,bool * success)475 run_test_case(const char* test_program, const char* test_case,
476               const char* result_file, const char* const user_variables[],
477               const kyua_run_params_t* run_params, bool* success)
478 {
479     kyua_error_t error;
480 
481     char* work_directory;
482     error = kyua_run_work_directory_enter(WORKDIR_TEMPLATE,
483                                           run_params->unprivileged_user,
484                                           run_params->unprivileged_group,
485                                           &work_directory);
486     if (kyua_error_is_set(error))
487         goto out;
488     kyua_run_params_t real_run_params = *run_params;
489     real_run_params.work_directory = work_directory;
490 
491     error = run_body(test_program, test_case, result_file, user_variables,
492                      &real_run_params, success);
493     if (has_cleanup(user_variables)) {
494         error = run_cleanup(test_program, test_case, result_file,
495                             user_variables, &real_run_params, *success,
496                             success);
497     }
498 
499     error = kyua_error_subsume(error,
500         kyua_run_work_directory_leave(&work_directory));
501 out:
502     return error;
503 }
504 
505 
506 /// Definition of the tester.
507 static kyua_cli_tester_t atf_tester = {
508     .list_test_cases = list_test_cases,
509     .run_test_case = run_test_case,
510 };
511 
512 
513 /// Tester entry point.
514 ///
515 /// \param argc Number of command line arguments.
516 /// \param argv NULL-terminated array of command line arguments.
517 ///
518 /// \return An exit code.
519 int
main(const int argc,char * const * const argv)520 main(const int argc, char* const* const argv)
521 {
522     return kyua_cli_main(argc, argv, &atf_tester);
523 }
524