1 // Copyright 2018 The Fuchsia Authors. All rights reserved.
2 // Use of this source code is governed by a BSD-style license that can be
3 // found in the LICENSE file.
4 
5 #include <runtests-utils/posix-run-test.h>
6 
7 #include <errno.h>
8 #include <fcntl.h>
9 #include <spawn.h>
10 #include <stdio.h>
11 #include <stdlib.h>
12 #include <string>
13 #include <sys/stat.h>
14 #include <sys/wait.h>
15 #include <unistd.h>
16 
17 #include <fbl/algorithm.h>
18 #include <fbl/auto_call.h>
19 #include <unittest/unittest.h>
20 
21 namespace runtests {
22 namespace {
23 
24 // A whitelist of the names of environment variables names that we pass into the
25 // spawned test subprocess.
26 constexpr const char* const kEnvironmentWhitelist[] = {
27     "TMPDIR",
28     // Paths to the symbolizer for various sanitizers.
29     "ASAN_SYMBOLIZER_PATH",
30     "LSAN_SYMBOLIZER_PATH",
31     "MSAN_SYMBOLIZER_PATH",
32     "UBSAN_SYMBOLIZER_PATH",
33     // From unittest.h. Set by RunAllTests().
34     TEST_ENV_NAME,
35 };
36 
37 } // namespace
38 
PosixRunTest(const char * argv[],const char *,const char * output_filename)39 fbl::unique_ptr<Result> PosixRunTest(const char* argv[],
40                                      const char*, // output_dir
41                                      const char* output_filename) {
42     int status;
43     const char* path = argv[0];
44     FILE* output_file = nullptr;
45 
46     // Initialize |file_actions|, which dictate what I/O will be performed in the
47     // launched process, and ensure its destruction on function exit.
48     posix_spawn_file_actions_t file_actions;
49     if ((status = posix_spawn_file_actions_init(&file_actions))) {
50         fprintf(stderr, "FAILURE: posix_spawn_file_actions_init failed: %s\n",
51                strerror(status));
52         return fbl::make_unique<Result>(path, FAILED_TO_LAUNCH, 0);
53     }
54 
55     auto auto_tidy = fbl::MakeAutoCall([&] {
56         posix_spawn_file_actions_destroy(&file_actions);
57         if (output_file != nullptr) {
58             fclose(output_file);
59         }
60     });
61 
62     // Construct the array of whitelisted environment variable strings of the
63     // form "<name>=<value>".  The env_strings array just keeps the underlying
64     // std::string objects alive so the envp pointers remain valid.
65     std::string env_strings[fbl::count_of(kEnvironmentWhitelist)];
66     const char* envp[fbl::count_of(env_strings) + 1];  // +1 for null terminator.
67     size_t i = 0;
68     for (const char* var : kEnvironmentWhitelist) {
69         const char* val = getenv(var);
70         if (val) {
71             env_strings[i] = std::string(var) + "=" + val;
72             envp[i] = env_strings[i].c_str();
73             ++i;
74         }
75     }
76     envp[i] = nullptr;
77 
78     // Tee output.
79     if (output_filename != nullptr) {
80         output_file = fopen(output_filename, "w");
81         if (output_file == nullptr) {
82             return fbl::make_unique<Result>(path, FAILED_DURING_IO, 0);
83         }
84         if ((status = posix_spawn_file_actions_addopen(
85                  &file_actions, STDOUT_FILENO, output_filename,
86                  O_WRONLY | O_CREAT | O_TRUNC, 0644))) {
87             fprintf(stderr, "FAILURE: posix_spawn_file_actions_addopen failed: %s\n",
88                    strerror(status));
89             return fbl::make_unique<Result>(path, FAILED_TO_LAUNCH, 0);
90         }
91         if ((status = posix_spawn_file_actions_adddup2(&file_actions, STDOUT_FILENO,
92                                                        STDERR_FILENO))) {
93             fprintf(stderr, "FAILURE: posix_spawn_file_actions_adddup2 failed: %s\n",
94                    strerror(status));
95             return fbl::make_unique<Result>(path, FAILED_TO_LAUNCH, 0);
96         }
97     }
98 
99     // Launch the test subprocess.
100     pid_t test_pid;
101     if ((status = posix_spawn(&test_pid, path, &file_actions, nullptr,
102                               const_cast<char**>(argv),
103                               const_cast<char**>(envp)))) {
104         fprintf(stderr, "FAILURE: posix_spawn failed: %s\n", strerror(status));
105         return fbl::make_unique<Result>(path, FAILED_TO_LAUNCH, 0);
106     }
107 
108     if (waitpid(test_pid, &status, WUNTRACED | WCONTINUED) == -1) {
109         fprintf(stderr, "FAILURE: waitpid failed: %s\n", strerror(errno));
110         return fbl::make_unique<Result>(path, FAILED_TO_WAIT, 0);
111     }
112     if (WIFEXITED(status)) {
113         int return_code = WEXITSTATUS(status);
114         LaunchStatus launch_status =
115             return_code ? FAILED_NONZERO_RETURN_CODE : SUCCESS;
116         return fbl::make_unique<Result>(path, launch_status, return_code);
117     }
118     if (WIFSIGNALED(status)) {
119         fprintf(stderr, "FAILURE: test process killed by signal %d\n", WTERMSIG(status));
120         return fbl::make_unique<Result>(path, FAILED_NONZERO_RETURN_CODE, 1);
121     }
122     if (WIFSTOPPED(status)) {
123         fprintf(stderr, "FAILURE: test process stopped by signal %d\n", WSTOPSIG(status));
124         return fbl::make_unique<Result>(path, FAILED_NONZERO_RETURN_CODE, 1);
125     }
126 
127     fprintf(stderr, "FAILURE: test process with unexpected status: %#x", status);
128     return fbl::make_unique<Result>(path, FAILED_UNKNOWN, 0);
129 }
130 
131 } // namespace runtests
132