/* Copyright Joyent, Inc. and other Node contributors. All rights reserved. * * Permission is hereby granted, free of charge, to any person obtaining a copy * of this software and associated documentation files (the "Software"), to * deal in the Software without restriction, including without limitation the * rights to use, copy, modify, merge, publish, distribute, sublicense, and/or * sell copies of the Software, and to permit persons to whom the Software is * furnished to do so, subject to the following conditions: * * The above copyright notice and this permission notice shall be included in * all copies or substantial portions of the Software. * * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE * AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING * FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS * IN THE SOFTWARE. */ #include #include #include #include "runner.h" #include "task.h" #include "uv.h" char executable_path[sizeof(executable_path)]; static int compare_task(const void* va, const void* vb) { const task_entry_t* a = va; const task_entry_t* b = vb; return strcmp(a->task_name, b->task_name); } const char* fmt(double d) { static char buf[1024]; static char* p; uint64_t v; if (p == NULL) p = buf; p += 31; if (p >= buf + sizeof(buf)) return ""; v = (uint64_t) d; #if 0 /* works but we don't care about fractional precision */ if (d - v >= 0.01) { *--p = '0' + (uint64_t) (d * 100) % 10; *--p = '0' + (uint64_t) (d * 10) % 10; *--p = '.'; } #endif if (v == 0) *--p = '0'; while (v) { if (v) *--p = '0' + (v % 10), v /= 10; if (v) *--p = '0' + (v % 10), v /= 10; if (v) *--p = '0' + (v % 10), v /= 10; if (v) *--p = ','; } return p; } int run_tests(int benchmark_output) { int actual; int total; int passed; int failed; int skipped; int current; int test_result; int skip; task_entry_t* task; /* Count the number of tests. */ actual = 0; total = 0; for (task = TASKS; task->main; task++, actual++) { if (!task->is_helper) { total++; } } /* Keep platform_output first. */ skip = (actual > 0 && 0 == strcmp(TASKS[0].task_name, "platform_output")); qsort(TASKS + skip, actual - skip, sizeof(TASKS[0]), compare_task); fprintf(stderr, "1..%d\n", total); fflush(stderr); /* Run all tests. */ passed = 0; failed = 0; skipped = 0; current = 1; for (task = TASKS; task->main; task++) { if (task->is_helper) { continue; } test_result = run_test(task->task_name, benchmark_output, current); switch (test_result) { case TEST_OK: passed++; break; case TEST_SKIP: skipped++; break; default: failed++; } current++; } return failed; } void log_tap_result(int test_count, const char* test, int status, process_info_t* process) { const char* result; const char* directive; char reason[1024]; int reason_length; switch (status) { case TEST_OK: result = "ok"; directive = ""; break; case TEST_SKIP: result = "ok"; directive = " # SKIP "; break; default: result = "not ok"; directive = ""; } if (status == TEST_SKIP && process_output_size(process) > 0) { process_read_last_line(process, reason, sizeof reason); reason_length = strlen(reason); if (reason_length > 0 && reason[reason_length - 1] == '\n') reason[reason_length - 1] = '\0'; } else { reason[0] = '\0'; } fprintf(stderr, "%s %d - %s%s%s\n", result, test_count, test, directive, reason); fflush(stderr); } int run_test(const char* test, int benchmark_output, int test_count) { char errmsg[1024] = ""; process_info_t processes[1024]; process_info_t *main_proc; task_entry_t* task; int timeout_multiplier; int process_count; int result; int status; int i; status = 255; main_proc = NULL; process_count = 0; #ifndef _WIN32 /* Clean up stale socket from previous run. */ remove(TEST_PIPENAME); remove(TEST_PIPENAME_2); remove(TEST_PIPENAME_3); #endif /* If it's a helper the user asks for, start it directly. */ for (task = TASKS; task->main; task++) { if (task->is_helper && strcmp(test, task->process_name) == 0) { return task->main(); } } /* Start the helpers first. */ for (task = TASKS; task->main; task++) { if (strcmp(test, task->task_name) != 0) { continue; } /* Skip the test itself. */ if (!task->is_helper) { continue; } if (process_start(task->task_name, task->process_name, &processes[process_count], 1 /* is_helper */) == -1) { snprintf(errmsg, sizeof errmsg, "Process `%s` failed to start.", task->process_name); goto out; } process_count++; } /* Now start the test itself. */ for (task = TASKS; task->main; task++) { if (strcmp(test, task->task_name) != 0) { continue; } if (task->is_helper) { continue; } if (process_start(task->task_name, task->process_name, &processes[process_count], 0 /* !is_helper */) == -1) { snprintf(errmsg, sizeof errmsg, "Process `%s` failed to start.", task->process_name); goto out; } main_proc = &processes[process_count]; process_count++; break; } if (main_proc == NULL) { snprintf(errmsg, sizeof errmsg, "No test with that name: %s", test); goto out; } timeout_multiplier = 1; #ifndef _WIN32 do { const char* var; var = getenv("UV_TEST_TIMEOUT_MULTIPLIER"); if (var == NULL) break; timeout_multiplier = atoi(var); if (timeout_multiplier <= 0) timeout_multiplier = 1; } while (0); #endif result = process_wait(main_proc, 1, task->timeout * timeout_multiplier); if (result == -1) { FATAL("process_wait failed"); } else if (result == -2) { /* Don't have to clean up the process, process_wait() has killed it. */ snprintf(errmsg, sizeof errmsg, "timeout"); goto out; } status = process_reap(main_proc); if (status != TEST_OK) { snprintf(errmsg, sizeof errmsg, "exit code %d", status); goto out; } if (benchmark_output) { /* Give the helpers time to clean up their act. */ uv_sleep(1000); } out: /* Reap running processes except the main process, it's already dead. */ for (i = 0; i < process_count - 1; i++) { process_terminate(&processes[i]); } if (process_count > 0 && process_wait(processes, process_count - 1, -1) < 0) { FATAL("process_wait failed"); } log_tap_result(test_count, test, status, &processes[i]); /* Show error and output from processes if the test failed. */ if ((status != TEST_OK && status != TEST_SKIP) || task->show_output) { if (strlen(errmsg) > 0) fprintf(stderr, "# %s\n", errmsg); fprintf(stderr, "# "); fflush(stderr); for (i = 0; i < process_count; i++) { switch (process_output_size(&processes[i])) { case -1: fprintf(stderr, "Output from process `%s`: (unavailable)\n", process_get_name(&processes[i])); fflush(stderr); break; case 0: fprintf(stderr, "Output from process `%s`: (no output)\n", process_get_name(&processes[i])); fflush(stderr); break; default: fprintf(stderr, "Output from process `%s`:\n", process_get_name(&processes[i])); fflush(stderr); process_copy_output(&processes[i], stderr); break; } } /* In benchmark mode show concise output from the main process. */ } else if (benchmark_output) { switch (process_output_size(main_proc)) { case -1: fprintf(stderr, "%s: (unavailable)\n", test); fflush(stderr); break; case 0: fprintf(stderr, "%s: (no output)\n", test); fflush(stderr); break; default: for (i = 0; i < process_count; i++) { process_copy_output(&processes[i], stderr); } break; } } /* Clean up all process handles. */ for (i = 0; i < process_count; i++) { process_cleanup(&processes[i]); } return status; } /* Returns the status code of the task part * or 255 if no matching task was not found. */ int run_test_part(const char* test, const char* part) { task_entry_t* task; int r; for (task = TASKS; task->main; task++) { if (strcmp(test, task->task_name) == 0 && strcmp(part, task->process_name) == 0) { r = task->main(); return r; } } fprintf(stderr, "No test part with that name: %s:%s\n", test, part); fflush(stderr); return 255; } static int find_helpers(const task_entry_t* task, const task_entry_t** helpers) { const task_entry_t* helper; int n_helpers; for (n_helpers = 0, helper = TASKS; helper->main; helper++) { if (helper->is_helper && strcmp(helper->task_name, task->task_name) == 0) { *helpers++ = helper; n_helpers++; } } return n_helpers; } void print_tests(FILE* stream) { const task_entry_t* helpers[1024]; const task_entry_t* task; int n_helpers; int n_tasks; int i; for (n_tasks = 0, task = TASKS; task->main; n_tasks++, task++); qsort(TASKS, n_tasks, sizeof(TASKS[0]), compare_task); for (task = TASKS; task->main; task++) { if (task->is_helper) { continue; } n_helpers = find_helpers(task, helpers); if (n_helpers) { printf("%-25s (helpers:", task->task_name); for (i = 0; i < n_helpers; i++) { printf(" %s", helpers[i]->process_name); } printf(")\n"); } else { printf("%s\n", task->task_name); } } } void print_lines(const char* buffer, size_t size, FILE* stream) { const char* start; const char* end; start = buffer; while ((end = memchr(start, '\n', &buffer[size] - start))) { fputs("# ", stream); fwrite(start, 1, (int)(end - start), stream); fputs("\n", stream); fflush(stream); start = end + 1; } end = &buffer[size]; if (start < end) { fputs("# ", stream); fwrite(start, 1, (int)(end - start), stream); fputs("\n", stream); fflush(stream); } }