Austin Schuh | f2a50ba | 2016-12-24 16:16:26 -0800 | [diff] [blame] | 1 | #include <libgen.h> |
Brian Silverman | 4c04efd | 2013-09-07 14:51:33 -0700 | [diff] [blame] | 2 | #include <sys/types.h> |
| 3 | #include <sys/wait.h> |
Austin Schuh | f2a50ba | 2016-12-24 16:16:26 -0800 | [diff] [blame] | 4 | #include <unistd.h> |
Brian Silverman | 4c04efd | 2013-09-07 14:51:33 -0700 | [diff] [blame] | 5 | |
Tyler Chatow | bf0609c | 2021-07-31 16:13:27 -0700 | [diff] [blame] | 6 | #include <cerrno> |
Austin Schuh | f2a50ba | 2016-12-24 16:16:26 -0800 | [diff] [blame] | 7 | #include <chrono> |
Tyler Chatow | bf0609c | 2021-07-31 16:13:27 -0700 | [diff] [blame] | 8 | #include <cstdio> |
| 9 | #include <cstring> |
Brian Silverman | 4c04efd | 2013-09-07 14:51:33 -0700 | [diff] [blame] | 10 | #include <string> |
| 11 | |
John Park | 33858a3 | 2018-09-28 23:05:48 -0700 | [diff] [blame] | 12 | #include "aos/die.h" |
Tyler Chatow | bf0609c | 2021-07-31 16:13:27 -0700 | [diff] [blame] | 13 | #include "aos/ipc_lib/core_lib.h" |
John Park | 33858a3 | 2018-09-28 23:05:48 -0700 | [diff] [blame] | 14 | #include "aos/libc/aos_strsignal.h" |
| 15 | #include "aos/libc/dirname.h" |
| 16 | #include "aos/logging/logging.h" |
| 17 | #include "aos/mutex/mutex.h" |
Tyler Chatow | bf0609c | 2021-07-31 16:13:27 -0700 | [diff] [blame] | 18 | #include "aos/testing/test_shm.h" |
John Park | 33858a3 | 2018-09-28 23:05:48 -0700 | [diff] [blame] | 19 | #include "aos/time/time.h" |
| 20 | #include "aos/type_traits/type_traits.h" |
Brian Silverman | 4c04efd | 2013-09-07 14:51:33 -0700 | [diff] [blame] | 21 | |
| 22 | // This runs all of the IPC-related tests in a bunch of parallel processes for a |
| 23 | // while and makes sure that they don't fail. It also captures the stdout and |
| 24 | // stderr output from each test run and only prints it out (not interleaved with |
| 25 | // the output from any other run) if the test fails. |
| 26 | // |
Brian Silverman | 5f8c492 | 2014-02-11 21:22:38 -0800 | [diff] [blame] | 27 | // They have to be run in separate processes because (in addition to various |
| 28 | // parts of our code not being thread-safe...) gtest does not like multiple |
| 29 | // threads. |
| 30 | // |
Brian Silverman | eeb62ca | 2013-09-11 15:08:03 -0700 | [diff] [blame] | 31 | // It's written in C++ for performance. We need actual OS-level parallelism for |
Brian Silverman | 4c04efd | 2013-09-07 14:51:33 -0700 | [diff] [blame] | 32 | // this to work, which means that Ruby's out because it doesn't have good |
| 33 | // support for doing that. My Python implementation ended up pretty heavily disk |
Brian Silverman | eeb62ca | 2013-09-11 15:08:03 -0700 | [diff] [blame] | 34 | // IO-bound, which is a bad way to test CPU contention. |
Brian Silverman | 4c04efd | 2013-09-07 14:51:33 -0700 | [diff] [blame] | 35 | |
| 36 | namespace aos { |
| 37 | |
Austin Schuh | f2a50ba | 2016-12-24 16:16:26 -0800 | [diff] [blame] | 38 | namespace chrono = ::std::chrono; |
| 39 | |
Brian Silverman | 4c04efd | 2013-09-07 14:51:33 -0700 | [diff] [blame] | 40 | // Each test is represented by the name of the test binary and then any |
| 41 | // arguments to pass to it. |
| 42 | // Using --gtest_filter is a bad idea because it seems to result in a lot of |
Brian Silverman | eeb62ca | 2013-09-11 15:08:03 -0700 | [diff] [blame] | 43 | // swapping which causes everything to be disk-bound (at least for me). |
Brian Silverman | ac5cd38 | 2014-02-12 14:50:23 -0800 | [diff] [blame] | 44 | static const size_t kTestMaxArgs = 10; |
Tyler Chatow | bf0609c | 2021-07-31 16:13:27 -0700 | [diff] [blame] | 45 | static const char *kTests[][kTestMaxArgs] = { |
| 46 | {"queue_test"}, |
| 47 | {"condition_test"}, |
| 48 | {"mutex_test"}, |
| 49 | {"raw_queue_test"}, |
Brian Silverman | 4c04efd | 2013-09-07 14:51:33 -0700 | [diff] [blame] | 50 | }; |
Brian Silverman | ac5cd38 | 2014-02-12 14:50:23 -0800 | [diff] [blame] | 51 | static const size_t kTestsLength = sizeof(kTests) / sizeof(kTests[0]); |
Brian Silverman | 4c04efd | 2013-09-07 14:51:33 -0700 | [diff] [blame] | 52 | // These arguments get inserted before any per-test arguments. |
Brian Silverman | 5f8c492 | 2014-02-11 21:22:38 -0800 | [diff] [blame] | 53 | static const char *kDefaultArgs[] = { |
Tyler Chatow | bf0609c | 2021-07-31 16:13:27 -0700 | [diff] [blame] | 54 | "--gtest_repeat=30", |
| 55 | "--gtest_shuffle", |
Brian Silverman | 4c04efd | 2013-09-07 14:51:33 -0700 | [diff] [blame] | 56 | }; |
| 57 | |
| 58 | // How many test processes to run at a time. |
Brian Silverman | eeb62ca | 2013-09-11 15:08:03 -0700 | [diff] [blame] | 59 | static const int kTesters = 100; |
Brian Silverman | 4c04efd | 2013-09-07 14:51:33 -0700 | [diff] [blame] | 60 | // How long to test for. |
Austin Schuh | f2a50ba | 2016-12-24 16:16:26 -0800 | [diff] [blame] | 61 | static constexpr monotonic_clock::duration kTestTime = chrono::seconds(30); |
Brian Silverman | 4c04efd | 2013-09-07 14:51:33 -0700 | [diff] [blame] | 62 | |
| 63 | // The structure that gets put into shared memory and then referenced by all of |
| 64 | // the child processes. |
| 65 | struct Shared { |
Austin Schuh | f2a50ba | 2016-12-24 16:16:26 -0800 | [diff] [blame] | 66 | Shared(const monotonic_clock::time_point stop_time) |
Tyler Chatow | bf0609c | 2021-07-31 16:13:27 -0700 | [diff] [blame] | 67 | : stop_time(stop_time), total_iterations(0) {} |
Brian Silverman | 4c04efd | 2013-09-07 14:51:33 -0700 | [diff] [blame] | 68 | |
| 69 | // Synchronizes access to stdout/stderr to avoid interleaving failure |
| 70 | // messages. |
| 71 | Mutex output_mutex; |
| 72 | |
| 73 | // When to stop. |
Austin Schuh | f2a50ba | 2016-12-24 16:16:26 -0800 | [diff] [blame] | 74 | monotonic_clock::time_point stop_time; |
Brian Silverman | 4c04efd | 2013-09-07 14:51:33 -0700 | [diff] [blame] | 75 | |
| 76 | // The total number of iterations. Updated by each child as it finishes. |
| 77 | int total_iterations; |
| 78 | // Sychronizes writes to total_iterations |
| 79 | Mutex total_iterations_mutex; |
| 80 | |
| 81 | const char *path; |
| 82 | }; |
| 83 | static_assert(shm_ok<Shared>::value, |
| 84 | "it's going to get shared between forked processes"); |
| 85 | |
| 86 | // Gets called after each child forks to run a test. |
Tyler Chatow | bf0609c | 2021-07-31 16:13:27 -0700 | [diff] [blame] | 87 | void __attribute__((noreturn)) |
| 88 | DoRunTest(Shared *shared, const char *(*test)[kTestMaxArgs], int pipes[2]) { |
Brian Silverman | 4c04efd | 2013-09-07 14:51:33 -0700 | [diff] [blame] | 89 | if (close(pipes[0]) == -1) { |
Brian Silverman | 01be000 | 2014-05-10 15:44:38 -0700 | [diff] [blame] | 90 | PDie("close(%d) of read end of pipe failed", pipes[0]); |
Brian Silverman | 4c04efd | 2013-09-07 14:51:33 -0700 | [diff] [blame] | 91 | } |
| 92 | if (close(STDIN_FILENO) == -1) { |
Brian Silverman | 01be000 | 2014-05-10 15:44:38 -0700 | [diff] [blame] | 93 | PDie("close(STDIN_FILENO(=%d)) failed", STDIN_FILENO); |
Brian Silverman | 4c04efd | 2013-09-07 14:51:33 -0700 | [diff] [blame] | 94 | } |
| 95 | if (dup2(pipes[1], STDOUT_FILENO) == -1) { |
Brian Silverman | 01be000 | 2014-05-10 15:44:38 -0700 | [diff] [blame] | 96 | PDie("dup2(%d, STDOUT_FILENO(=%d)) failed", pipes[1], STDOUT_FILENO); |
Brian Silverman | 4c04efd | 2013-09-07 14:51:33 -0700 | [diff] [blame] | 97 | } |
| 98 | if (dup2(pipes[1], STDERR_FILENO) == -1) { |
Brian Silverman | 01be000 | 2014-05-10 15:44:38 -0700 | [diff] [blame] | 99 | PDie("dup2(%d, STDERR_FILENO(=%d)) failed", pipes[1], STDERR_FILENO); |
Brian Silverman | 4c04efd | 2013-09-07 14:51:33 -0700 | [diff] [blame] | 100 | } |
| 101 | |
Brian Silverman | ac5cd38 | 2014-02-12 14:50:23 -0800 | [diff] [blame] | 102 | size_t size = kTestMaxArgs; |
| 103 | size_t default_size = sizeof(kDefaultArgs) / sizeof(kDefaultArgs[0]); |
Brian Silverman | 5f8c492 | 2014-02-11 21:22:38 -0800 | [diff] [blame] | 104 | // There's no chance to free this because we either exec or Die. |
Brian Silverman | 4c04efd | 2013-09-07 14:51:33 -0700 | [diff] [blame] | 105 | const char **args = new const char *[size + default_size + 1]; |
| 106 | // The actual executable to run. |
| 107 | ::std::string executable; |
| 108 | int i = 0; |
Brian Silverman | ac5cd38 | 2014-02-12 14:50:23 -0800 | [diff] [blame] | 109 | for (size_t test_i = 0; test_i < size; ++test_i) { |
| 110 | const char *c = (*test)[test_i]; |
Brian Silverman | 4c04efd | 2013-09-07 14:51:33 -0700 | [diff] [blame] | 111 | if (i == 0) { |
| 112 | executable = ::std::string(shared->path) + "/" + c; |
| 113 | args[0] = executable.c_str(); |
| 114 | for (const ::std::string &ci : kDefaultArgs) { |
Brian Silverman | eeb62ca | 2013-09-11 15:08:03 -0700 | [diff] [blame] | 115 | args[++i] = ci.c_str(); |
Brian Silverman | 4c04efd | 2013-09-07 14:51:33 -0700 | [diff] [blame] | 116 | } |
| 117 | } else { |
Brian Silverman | ac5cd38 | 2014-02-12 14:50:23 -0800 | [diff] [blame] | 118 | args[i] = c; |
Brian Silverman | 4c04efd | 2013-09-07 14:51:33 -0700 | [diff] [blame] | 119 | } |
| 120 | ++i; |
| 121 | } |
| 122 | args[size] = NULL; |
| 123 | execv(executable.c_str(), const_cast<char *const *>(args)); |
Brian Silverman | 01be000 | 2014-05-10 15:44:38 -0700 | [diff] [blame] | 124 | PDie("execv(%s, %p) failed", executable.c_str(), args); |
Brian Silverman | 4c04efd | 2013-09-07 14:51:33 -0700 | [diff] [blame] | 125 | } |
| 126 | |
| 127 | void DoRun(Shared *shared) { |
| 128 | int iterations = 0; |
| 129 | // An iterator pointing to a random one of the tests. |
Brian Silverman | 5f8c492 | 2014-02-11 21:22:38 -0800 | [diff] [blame] | 130 | // We randomize based on PID because otherwise they all end up running the |
| 131 | // same test at the same time for the whole test. |
Brian Silverman | ac5cd38 | 2014-02-12 14:50:23 -0800 | [diff] [blame] | 132 | const char *(*test)[kTestMaxArgs] = &kTests[getpid() % kTestsLength]; |
Brian Silverman | 4c04efd | 2013-09-07 14:51:33 -0700 | [diff] [blame] | 133 | int pipes[2]; |
Austin Schuh | f2a50ba | 2016-12-24 16:16:26 -0800 | [diff] [blame] | 134 | while (monotonic_clock::now() < shared->stop_time) { |
Brian Silverman | 4c04efd | 2013-09-07 14:51:33 -0700 | [diff] [blame] | 135 | if (pipe(pipes) == -1) { |
Brian Silverman | 01be000 | 2014-05-10 15:44:38 -0700 | [diff] [blame] | 136 | PDie("pipe(%p) failed", &pipes); |
Brian Silverman | 4c04efd | 2013-09-07 14:51:33 -0700 | [diff] [blame] | 137 | } |
| 138 | switch (fork()) { |
| 139 | case 0: // in runner |
Brian Silverman | ac5cd38 | 2014-02-12 14:50:23 -0800 | [diff] [blame] | 140 | DoRunTest(shared, test, pipes); |
Brian Silverman | 4c04efd | 2013-09-07 14:51:33 -0700 | [diff] [blame] | 141 | case -1: |
Brian Silverman | 01be000 | 2014-05-10 15:44:38 -0700 | [diff] [blame] | 142 | PDie("fork() failed"); |
Brian Silverman | 4c04efd | 2013-09-07 14:51:33 -0700 | [diff] [blame] | 143 | } |
| 144 | |
| 145 | if (close(pipes[1]) == -1) { |
Brian Silverman | 01be000 | 2014-05-10 15:44:38 -0700 | [diff] [blame] | 146 | PDie("close(%d) of write end of pipe failed", pipes[1]); |
Brian Silverman | 4c04efd | 2013-09-07 14:51:33 -0700 | [diff] [blame] | 147 | } |
| 148 | |
| 149 | ::std::string output; |
| 150 | char buffer[2048]; |
| 151 | while (true) { |
| 152 | ssize_t ret = read(pipes[0], &buffer, sizeof(buffer)); |
| 153 | if (ret == 0) { // EOF |
| 154 | if (close(pipes[0]) == -1) { |
Brian Silverman | 01be000 | 2014-05-10 15:44:38 -0700 | [diff] [blame] | 155 | PDie("close(%d) of pipe at EOF failed", pipes[0]); |
Brian Silverman | 4c04efd | 2013-09-07 14:51:33 -0700 | [diff] [blame] | 156 | } |
| 157 | break; |
| 158 | } else if (ret == -1) { |
Brian Silverman | 01be000 | 2014-05-10 15:44:38 -0700 | [diff] [blame] | 159 | PDie("read(%d, %p, %zd) failed", pipes[0], &buffer, sizeof(buffer)); |
Brian Silverman | 4c04efd | 2013-09-07 14:51:33 -0700 | [diff] [blame] | 160 | } |
| 161 | output += ::std::string(buffer, ret); |
| 162 | } |
| 163 | |
| 164 | int status; |
| 165 | while (true) { |
| 166 | if (wait(&status) == -1) { |
| 167 | if (errno == EINTR) continue; |
Brian Silverman | 01be000 | 2014-05-10 15:44:38 -0700 | [diff] [blame] | 168 | PDie("wait(%p) in child failed", &status); |
Brian Silverman | 4c04efd | 2013-09-07 14:51:33 -0700 | [diff] [blame] | 169 | } else { |
| 170 | break; |
| 171 | } |
| 172 | } |
| 173 | if (WIFEXITED(status)) { |
| 174 | if (WEXITSTATUS(status) != 0) { |
| 175 | MutexLocker sync(&shared->output_mutex); |
Tyler Chatow | bf0609c | 2021-07-31 16:13:27 -0700 | [diff] [blame] | 176 | fprintf(stderr, "Test %s exited with status %d. output:\n", (*test)[0], |
| 177 | WEXITSTATUS(status)); |
Brian Silverman | 4c04efd | 2013-09-07 14:51:33 -0700 | [diff] [blame] | 178 | fputs(output.c_str(), stderr); |
| 179 | } |
| 180 | } else if (WIFSIGNALED(status)) { |
| 181 | MutexLocker sync(&shared->output_mutex); |
Brian Silverman | ac5cd38 | 2014-02-12 14:50:23 -0800 | [diff] [blame] | 182 | fprintf(stderr, "Test %s terminated by signal %d: %s.\n", (*test)[0], |
Brian Silverman | af78486 | 2014-05-13 08:14:55 -0700 | [diff] [blame] | 183 | WTERMSIG(status), aos_strsignal(WTERMSIG(status))); |
Tyler Chatow | bf0609c | 2021-07-31 16:13:27 -0700 | [diff] [blame] | 184 | fputs(output.c_str(), stderr); |
Brian Silverman | 4c04efd | 2013-09-07 14:51:33 -0700 | [diff] [blame] | 185 | } else { |
Brian Silverman | fe457de | 2014-05-26 22:04:08 -0700 | [diff] [blame] | 186 | CHECK(WIFSTOPPED(status)); |
Brian Silverman | ac5cd38 | 2014-02-12 14:50:23 -0800 | [diff] [blame] | 187 | Die("Test %s was stopped.\n", (*test)[0]); |
Brian Silverman | 4c04efd | 2013-09-07 14:51:33 -0700 | [diff] [blame] | 188 | } |
| 189 | |
| 190 | ++test; |
Brian Silverman | ac5cd38 | 2014-02-12 14:50:23 -0800 | [diff] [blame] | 191 | if (test == kTests + 1) test = kTests; |
Brian Silverman | 4c04efd | 2013-09-07 14:51:33 -0700 | [diff] [blame] | 192 | ++iterations; |
| 193 | } |
| 194 | { |
| 195 | MutexLocker sync(&shared->total_iterations_mutex); |
| 196 | shared->total_iterations += iterations; |
| 197 | } |
| 198 | } |
| 199 | |
| 200 | void Run(Shared *shared) { |
| 201 | switch (fork()) { |
| 202 | case 0: // in child |
| 203 | DoRun(shared); |
| 204 | _exit(EXIT_SUCCESS); |
| 205 | case -1: |
Brian Silverman | 01be000 | 2014-05-10 15:44:38 -0700 | [diff] [blame] | 206 | PDie("fork() of child failed"); |
Brian Silverman | 4c04efd | 2013-09-07 14:51:33 -0700 | [diff] [blame] | 207 | } |
| 208 | } |
| 209 | |
| 210 | int Main(int argc, char **argv) { |
Brian Silverman | fe457de | 2014-05-26 22:04:08 -0700 | [diff] [blame] | 211 | if (argc < 1) { |
| 212 | fputs("need an argument\n", stderr); |
| 213 | return EXIT_FAILURE; |
| 214 | } |
Brian Silverman | 4c04efd | 2013-09-07 14:51:33 -0700 | [diff] [blame] | 215 | |
Brian Silverman | f5f8d8e | 2015-12-06 18:39:12 -0500 | [diff] [blame] | 216 | ::aos::testing::TestSharedMemory my_shm_; |
Brian Silverman | 4c04efd | 2013-09-07 14:51:33 -0700 | [diff] [blame] | 217 | |
| 218 | Shared *shared = static_cast<Shared *>(shm_malloc(sizeof(Shared))); |
Austin Schuh | f2a50ba | 2016-12-24 16:16:26 -0800 | [diff] [blame] | 219 | new (shared) Shared(monotonic_clock::now() + kTestTime); |
Brian Silverman | 4c04efd | 2013-09-07 14:51:33 -0700 | [diff] [blame] | 220 | |
Tyler Chatow | bf0609c | 2021-07-31 16:13:27 -0700 | [diff] [blame] | 221 | if (asprintf(const_cast<char **>(&shared->path), "%s/../tests", |
| 222 | ::aos::libc::Dirname(argv[0]).c_str()) == -1) { |
Brian Silverman | 01be000 | 2014-05-10 15:44:38 -0700 | [diff] [blame] | 223 | PDie("asprintf failed"); |
Brian Silverman | 5f8c492 | 2014-02-11 21:22:38 -0800 | [diff] [blame] | 224 | } |
Brian Silverman | 4c04efd | 2013-09-07 14:51:33 -0700 | [diff] [blame] | 225 | |
| 226 | for (int i = 0; i < kTesters; ++i) { |
| 227 | Run(shared); |
| 228 | } |
| 229 | |
| 230 | bool error = false; |
| 231 | for (int i = 0; i < kTesters; ++i) { |
| 232 | int status; |
| 233 | if (wait(&status) == -1) { |
| 234 | if (errno == EINTR) { |
| 235 | --i; |
| 236 | } else { |
Brian Silverman | 01be000 | 2014-05-10 15:44:38 -0700 | [diff] [blame] | 237 | PDie("wait(%p) failed", &status); |
Brian Silverman | 4c04efd | 2013-09-07 14:51:33 -0700 | [diff] [blame] | 238 | } |
| 239 | } |
| 240 | if (!WIFEXITED(status) || WEXITSTATUS(status) != 0) { |
| 241 | error = true; |
| 242 | } |
| 243 | } |
| 244 | |
| 245 | printf("Ran a total of %d tests.\n", shared->total_iterations); |
| 246 | if (error) { |
| 247 | printf("A child had a problem during the test.\n"); |
| 248 | } |
| 249 | return error ? EXIT_FAILURE : EXIT_SUCCESS; |
| 250 | } |
| 251 | |
| 252 | } // namespace aos |
| 253 | |
Tyler Chatow | bf0609c | 2021-07-31 16:13:27 -0700 | [diff] [blame] | 254 | int main(int argc, char **argv) { return ::aos::Main(argc, argv); } |