diff --git a/tests/CMakeLists.txt b/tests/CMakeLists.txt index 5909ad14c..3a4684f6c 100644 --- a/tests/CMakeLists.txt +++ b/tests/CMakeLists.txt @@ -1,7 +1,7 @@ if(CMAKE_SYSTEM_NAME STREQUAL Windows) execute_process(COMMAND - "${CMAKE_COMMAND}" -E copy "${PROJECT_SOURCE_DIR}/private" + "${CMAKE_COMMAND}" -E copy_directory "${PROJECT_SOURCE_DIR}/private" "${CMAKE_CURRENT_BINARY_DIR}/dispatch") execute_process(COMMAND "${CMAKE_COMMAND}" -E copy "${CMAKE_CURRENT_SOURCE_DIR}/leaks-wrapper.sh" @@ -36,6 +36,15 @@ if(BSD_OVERLAY_FOUND) PRIVATE ${BSD_OVERLAY_CFLAGS}) endif() +if (WIN32) + target_sources(bsdtests + PRIVATE + generic_win_port.c) + target_compile_definitions(bsdtests + PUBLIC + _CRT_NONSTDC_NO_WARNINGS + _CRT_SECURE_NO_WARNINGS) +endif () add_executable(bsdtestharness bsdtestharness.c) @@ -91,11 +100,11 @@ function(add_unit_test name) endif() if("${CMAKE_C_SIMULATE_ID}" STREQUAL "MSVC") target_compile_options(${name} PRIVATE -Xclang -fblocks) + target_compile_options(${name} PRIVATE /W3 -Wno-deprecated-declarations) else() target_compile_options(${name} PRIVATE -fblocks) + target_compile_options(${name} PRIVATE -Wall -Wno-deprecated-declarations) endif() - # TODO(compnerd) make this portable - target_compile_options(${name} PRIVATE -Wall -Wno-deprecated-declarations) dispatch_set_linker(${name}) target_link_libraries(${name} PRIVATE @@ -187,4 +196,4 @@ target_link_libraries(dispatch_group PRIVATE m) target_link_libraries(dispatch_timer_short PRIVATE m) # test-specific compile options -target_compile_options(dispatch_c99 PRIVATE -std=c99) +set_target_properties(dispatch_c99 PROPERTIES C_STANDARD 99) diff --git a/tests/bsdtestharness.c b/tests/bsdtestharness.c index f7b6ea31d..ca52d6e97 100644 --- a/tests/bsdtestharness.c +++ b/tests/bsdtestharness.c @@ -20,33 +20,29 @@ #include #include -#include #include #include #if defined(__unix__) || (defined(__APPLE__) && defined(__MACH__)) +#include +#include +#include +#include #include +#elif defined(_WIN32) +#include +#include +#include #endif #include #ifdef __APPLE__ #include #include #endif -#include -#include -#if defined(__linux__) || defined(__FreeBSD__) -#include -#endif #include +#if !defined(_WIN32) extern char **environ; - -#ifdef __linux__ -// Linux lacks the DISPATCH_SOURCE_TYPE_PROC functionality -// the real test harness needs. -#define SIMPLE_TEST_HARNESS 1 -#else -#define SIMPLE_TEST_HARNESS 0 #endif int @@ -131,6 +127,23 @@ main(int argc, char *argv[]) _Exit(EXIT_FAILURE); } } +#elif defined(_WIN32) + (void)res; + WCHAR *cmdline = argv_to_command_line(newargv); + if (!cmdline) { + fprintf(stderr, "argv_to_command_line() failed\n"); + exit(EXIT_FAILURE); + } + STARTUPINFOW si = {.cb = sizeof(si)}; + PROCESS_INFORMATION pi; + BOOL created = CreateProcessW(NULL, cmdline, NULL, NULL, FALSE, 0, NULL, NULL, &si, &pi); + DWORD error = GetLastError(); + free(cmdline); + if (!created) { + print_winapi_error("CreateProcessW", error); + exit(EXIT_FAILURE); + } + pid = (pid_t)pi.dwProcessId; #else #error "bsdtestharness not implemented on this platform" #endif @@ -138,18 +151,19 @@ main(int argc, char *argv[]) //fprintf(stderr, "pid = %d\n", pid); assert(pid > 0); -#if SIMPLE_TEST_HARNESS +#if defined(__linux__) int status; struct rusage usage; struct timeval tv_stop, tv_wall; + int res2 = wait4(pid, &status, 0, &usage); + (void)res2; + gettimeofday(&tv_stop, NULL); tv_wall.tv_sec = tv_stop.tv_sec - tv_start.tv_sec; tv_wall.tv_sec -= (tv_stop.tv_usec < tv_start.tv_usec); tv_wall.tv_usec = labs(tv_stop.tv_usec - tv_start.tv_usec); - int res2 = wait4(pid, &status, 0, &usage); - (void)res2; assert(res2 != -1); test_long("Process exited", (WIFEXITED(status) && WEXITSTATUS(status) && WEXITSTATUS(status) != 0xff) || WIFSIGNALED(status), 0); printf("[PERF]\twall time: %ld.%06ld\n", tv_wall.tv_sec, tv_wall.tv_usec); @@ -161,6 +175,46 @@ main(int argc, char *argv[]) printf("[PERF]\tvoluntary context switches: %ld\n", usage.ru_nvcsw); printf("[PERF]\tinvoluntary context switches: %ld\n", usage.ru_nivcsw); exit((WIFEXITED(status) && WEXITSTATUS(status)) || WIFSIGNALED(status)); +#elif defined(_WIN32) + if (WaitForSingleObject(pi.hProcess, INFINITE) != WAIT_OBJECT_0) { + print_winapi_error("WaitForSingleObject", GetLastError()); + exit(EXIT_FAILURE); + } + + struct timeval tv_stop, tv_wall; + gettimeofday(&tv_stop, NULL); + tv_wall.tv_sec = tv_stop.tv_sec - tv_start.tv_sec; + tv_wall.tv_sec -= (tv_stop.tv_usec < tv_start.tv_usec); + tv_wall.tv_usec = labs(tv_stop.tv_usec - tv_start.tv_usec); + + DWORD status; + if (!GetExitCodeProcess(pi.hProcess, &status)) { + print_winapi_error("GetExitCodeProcess", GetLastError()); + exit(EXIT_FAILURE); + } + + FILETIME create_time, exit_time, kernel_time, user_time; + if (!GetProcessTimes(pi.hProcess, &create_time, &exit_time, &kernel_time, &user_time)) { + print_winapi_error("GetProcessTimes", GetLastError()); + exit(EXIT_FAILURE); + } + struct timeval utime, stime; + filetime_to_timeval(&utime, &user_time); + filetime_to_timeval(&stime, &kernel_time); + + PROCESS_MEMORY_COUNTERS counters; + if (!GetProcessMemoryInfo(pi.hProcess, &counters, sizeof(counters))) { + print_winapi_error("GetProcessMemoryInfo", GetLastError()); + exit(EXIT_FAILURE); + } + + test_long("Process exited", status == 0 || status == 0xff, 1); + printf("[PERF]\twall time: %ld.%06ld\n", tv_wall.tv_sec, tv_wall.tv_usec); + printf("[PERF]\tuser time: %ld.%06ld\n", utime.tv_sec, utime.tv_usec); + printf("[PERF]\tsystem time: %ld.%06ld\n", stime.tv_sec, stime.tv_usec); + printf("[PERF]\tmax working set size: %zu\n", counters.PeakWorkingSetSize); + printf("[PERF]\tpage faults: %lu\n", counters.PageFaultCount); + exit(status ? EXIT_FAILURE : EXIT_SUCCESS); #else dispatch_queue_t main_q = dispatch_get_main_queue(); @@ -219,7 +273,7 @@ main(int argc, char *argv[]) kill(pid, SIGCONT); dispatch_main(); -#endif // SIMPLE_TEST_HARNESS +#endif return 0; } diff --git a/tests/bsdtests.c b/tests/bsdtests.c index 09700fae5..af716626b 100644 --- a/tests/bsdtests.c +++ b/tests/bsdtests.c @@ -29,14 +29,16 @@ #include #endif #include -#include -#include #include #ifdef __APPLE__ #include #include -#endif #include +#include +#endif +#if defined(_WIN32) +#include +#endif #include #include "bsdtests.h" @@ -454,18 +456,11 @@ test_start(const char* desc) usleep(100000); // give 'gdb --waitfor=' a chance to find this proc } -#if defined(__linux__) || defined(__FreeBSD__) -static char** get_environment(void) -{ - extern char **environ; - return environ; -} -#else +#if defined(__APPLE__) && defined(__MACH__) static char** get_environment(void) { return (* _NSGetEnviron()); } -#endif void test_leaks_pid(const char *name, pid_t pid) @@ -517,15 +512,18 @@ test_leaks(const char *name) { test_leaks_pid(name, getpid()); } +#endif void test_stop_after_delay(void *delay) { if (delay != NULL) { - sleep((uint)(intptr_t)delay); + sleep((unsigned int)(intptr_t)delay); } +#if defined(__APPLE__) && defined(__MACH__) test_leaks(NULL); +#endif fflush(stdout); _exit(_test_exit_code); diff --git a/tests/bsdtests.h b/tests/bsdtests.h index e82082337..e8e292e6e 100644 --- a/tests/bsdtests.h +++ b/tests/bsdtests.h @@ -63,18 +63,22 @@ __BASENAME__(const char *_str_) } #define __SOURCE_FILE__ __BASENAME__(__FILE__) -__BEGIN_DECLS +#if defined(__cplusplus) +extern "C" { +#endif /** * test_start() provides the TEST token. Use this once per test "tool" */ void test_start(const char* desc); +#if defined(__APPLE__) && defined(__MACH__) /** * Explicitly runs the 'leaks' test without stopping the process. */ void test_leaks_pid(const char *name, pid_t pid); void test_leaks(const char *name); +#endif /** * test_stop() checks for leaks during the tests using leaks-wrapper. Use this at the end of each "tool" @@ -179,6 +183,8 @@ void _test_skip(const char* file, long line, const char* desc); #define test_skip2(m) _test_skip("", 0, m) void test_skip_format(const char *format, ...) __printflike(1,2); -__END_DECLS +#if defined(__cplusplus) +} /* extern "C" */ +#endif #endif /* __BSD_TEST_H__ */ diff --git a/tests/dispatch_test.c b/tests/dispatch_test.c index 70cf90fef..9809543af 100644 --- a/tests/dispatch_test.c +++ b/tests/dispatch_test.c @@ -29,13 +29,16 @@ #include #if defined(__unix__) || (defined(__APPLE__) && defined(__MACH__)) #include -#endif #if __has_include() #define HAS_SYS_EVENT_H 1 #include #else #include #endif +#elif defined(_WIN32) +#include +#include +#endif #include #include @@ -68,6 +71,20 @@ dispatch_test_check_evfilt_read_for_fd(int fd) int r = kevent(kq, &ke, 1, &ke, 1, &t); close(kq); return r > 0; +#elif defined(_WIN32) + HANDLE handle = (HANDLE)_get_osfhandle(fd); + // A zero-distance move retrieves the file pointer + LARGE_INTEGER currentPosition; + LARGE_INTEGER distance = {.QuadPart = 0}; + if (!SetFilePointerEx(handle, distance, ¤tPosition, FILE_CURRENT)) { + return false; + } + // If we are not at the end, assume the file is readable + LARGE_INTEGER fileSize; + if (GetFileSizeEx(handle, &fileSize) == 0) { + return false; + } + return currentPosition.QuadPart < fileSize.QuadPart; #else struct pollfd pfd = { .fd = fd, @@ -141,6 +158,10 @@ dispatch_test_get_large_file(void) close(temp_fd); free(file_buf); return path; +#elif defined(_WIN32) + // TODO + fprintf(stderr, "dispatch_test_get_large_file() not implemented on Windows\n"); + abort(); #else #error "dispatch_test_get_large_file not implemented on this platform" #endif @@ -154,6 +175,8 @@ dispatch_test_release_large_file(const char *path) (void)path; #elif defined(__unix__) unlink(path); +#elif defined(_WIN32) + // TODO #else #error "dispatch_test_release_large_file not implemented on this platform" #endif diff --git a/tests/dispatch_test.h b/tests/dispatch_test.h index 0f5be8af6..415e41974 100644 --- a/tests/dispatch_test.h +++ b/tests/dispatch_test.h @@ -18,12 +18,13 @@ * @APPLE_APACHE_LICENSE_HEADER_END@ */ -#include #include #include #if defined(__linux__) || defined(__FreeBSD__) #include +#elif defined(_WIN32) +#include #endif #define test_group_wait(g) do { \ @@ -33,7 +34,9 @@ test_stop(); \ } } while (0) -__BEGIN_DECLS +#if defined(__cplusplus) +extern "C" { +#endif void dispatch_test_start(const char* desc); @@ -50,4 +53,6 @@ int sysctlbyname(const char *name, void *oldp, size_t *oldlenp, void *newp, size_t *newpl); #endif -__END_DECLS +#if defined(__cplusplus) +} /* extern "C" */ +#endif diff --git a/tests/generic_win_port.c b/tests/generic_win_port.c new file mode 100644 index 000000000..d9a52f477 --- /dev/null +++ b/tests/generic_win_port.c @@ -0,0 +1,223 @@ +#include +#include +#include +#include +#include +#include +#include +#include + +static bool +expand_wstr(WCHAR **str, size_t *capacity, size_t needed) +{ + if (*capacity >= needed) { + return true; + } + if (needed > UNICODE_STRING_MAX_CHARS) { + return false; + } + size_t new_capacity = *capacity ?: needed; + while (new_capacity < needed) { + new_capacity *= 2; + } + WCHAR *new_str = realloc(*str, new_capacity * sizeof(WCHAR)); + if (!new_str) { + return false; + } + *str = new_str; + *capacity = new_capacity; + return true; +} + +static bool +append_wstr(WCHAR **str, size_t *capacity, size_t *len, WCHAR *suffix) +{ + size_t suffix_len = wcslen(suffix); + if (!expand_wstr(str, capacity, *len + suffix_len)) { + return false; + } + memcpy(*str + *len, suffix, suffix_len * sizeof(WCHAR)); + *len += suffix_len; + return true; +} + +WCHAR * +argv_to_command_line(char **argv) +{ + // This is basically the reverse of CommandLineToArgvW(). We want to convert + // an argv array into a command-line compatible with CreateProcessW(). + // + // See also: + // + // + size_t len = 0, capacity = 0; + WCHAR *cmdline = NULL; + if (!expand_wstr(&cmdline, &capacity, 256)) { + goto error; + } + for (size_t i = 0; argv[i]; i++) { + // Separate arguments with spaces. + if (i > 0 && !append_wstr(&cmdline, &capacity, &len, L" ")) { + goto error; + } + // Surround the argument with quotes if it's empty or contains special + // characters. + char *cur = argv[i]; + bool quoted = (*cur == '\0' || cur[strcspn(cur, " \t\n\v\"")] != '\0'); + if (quoted && !append_wstr(&cmdline, &capacity, &len, L"\"")) { + goto error; + } + while (*cur != '\0') { + if (*cur == '"') { + // Quotes must be escaped with a backslash. + if (!append_wstr(&cmdline, &capacity, &len, L"\\\"")) { + goto error; + } + cur++; + } else if (*cur == '\\') { + // Windows treats backslashes differently depending on whether + // they're followed by a quote. If the backslashes aren't + // followed by a quote, then all slashes are copied into the + // argument string. Otherwise, only n/2 slashes are included. + // Count the number of slashes and double them if they're + // followed by a quote. + size_t backslashes = strspn(cur, "\\"); + cur += backslashes; + // If the argument needs to be surrounded with quotes, we must + // also check if the backslashes are at the end of the argument + // because the added quote will follow them. + if (*cur == '"' || (quoted && *cur == '\0')) { + backslashes *= 2; + } + if (!expand_wstr(&cmdline, &capacity, len + backslashes)) { + goto error; + } + wmemset(&cmdline[len], L'\\', backslashes); + len += backslashes; + } else { + // Widen as many characters as possible. + size_t mb_len = strcspn(cur, "\"\\"); + int wide_len = MultiByteToWideChar(CP_UTF8, 0, cur, mb_len, + NULL, 0); + if (wide_len == 0) { + goto error; + } + if (!expand_wstr(&cmdline, &capacity, len + wide_len)) { + goto error; + } + wide_len = MultiByteToWideChar(CP_UTF8, 0, cur, mb_len, + &cmdline[len], wide_len); + if (wide_len == 0) { + goto error; + } + cur += mb_len; + len += wide_len; + } + } + if (quoted && !append_wstr(&cmdline, &capacity, &len, L"\"")) { + goto error; + } + } + if (!expand_wstr(&cmdline, &capacity, len + 1)) { + goto error; + } + cmdline[len] = L'\0'; + return cmdline; +error: + free(cmdline); + return NULL; +} + +int +asprintf(char **strp, const char *format, ...) +{ + va_list arg1; + va_start(arg1, format); + int len = vsnprintf(NULL, 0, format, arg1); + va_end(arg1); + if (len >= 0) { + size_t size = (size_t)len + 1; + *strp = malloc(size); + if (!*strp) { + return -1; + } + va_list arg2; + va_start(arg2, format); + len = vsnprintf(*strp, size, format, arg2); + va_end(arg2); + } + return len; +} + +void +filetime_to_timeval(struct timeval *tp, const FILETIME *ft) +{ + int64_t ticks = ft->dwLowDateTime | (((int64_t)ft->dwHighDateTime) << 32); + static const int64_t ticks_per_sec = 10LL * 1000LL * 1000LL; + static const int64_t ticks_per_usec = 10LL; + if (ticks >= 0) { + tp->tv_sec = (long)(ticks / ticks_per_sec); + tp->tv_usec = (long)((ticks % ticks_per_sec) / ticks_per_usec); + } else { + tp->tv_sec = (long)((ticks + 1) / ticks_per_sec - 1); + tp->tv_usec = (long)((ticks_per_sec - 1 + (ticks + 1) % ticks_per_sec) / ticks_per_usec); + } +} + +pid_t +getpid(void) +{ + return (pid_t)GetCurrentProcessId(); +} + +int +gettimeofday(struct timeval *tp, void *tzp) +{ + (void)tzp; + FILETIME ft; + GetSystemTimePreciseAsFileTime(&ft); + int64_t ticks = ft.dwLowDateTime | (((int64_t)ft.dwHighDateTime) << 32); + ticks -= 116444736000000000LL; // Convert to Unix time + FILETIME unix_ft = {.dwLowDateTime = (DWORD)ticks, .dwHighDateTime = ticks >> 32}; + filetime_to_timeval(tp, &unix_ft); + return 0; +} + +void +print_winapi_error(const char *function_name, DWORD error) +{ + char *message = NULL; + DWORD len = FormatMessageA( + FORMAT_MESSAGE_ALLOCATE_BUFFER | FORMAT_MESSAGE_FROM_SYSTEM, + NULL, + error, + 0, + (LPSTR)&message, + 0, + NULL); + if (len > 0) { + // Note: FormatMessage includes a newline at the end of the message + fprintf(stderr, "%s: %s", function_name, message); + LocalFree(message); + } else { + fprintf(stderr, "%s: error %lu\n", function_name, error); + } +} + +unsigned int +sleep(unsigned int seconds) +{ + Sleep(seconds * 1000); + return 0; +} + +int +usleep(unsigned int usec) +{ + DWORD ms = usec / 1000; + if (ms == 0 && usec != 0) { + ms = 1; + } + Sleep(ms); + return 0; +} diff --git a/tests/generic_win_port.h b/tests/generic_win_port.h new file mode 100644 index 000000000..cf96a21a0 --- /dev/null +++ b/tests/generic_win_port.h @@ -0,0 +1,55 @@ +#pragma once + +#include +#include + +typedef int kern_return_t; +typedef int pid_t; + +#if defined(_WIN64) +typedef long long ssize_t; +#else +typedef long ssize_t; +#endif + +static inline int32_t +OSAtomicIncrement32(volatile int32_t *var) +{ + return __c11_atomic_fetch_add((_Atomic(int)*)var, 1, __ATOMIC_RELAXED)+1; +} + +static inline int32_t +OSAtomicIncrement32Barrier(volatile int32_t *var) +{ + return __c11_atomic_fetch_add((_Atomic(int)*)var, 1, __ATOMIC_SEQ_CST)+1; +} + +static inline int32_t +OSAtomicAdd32(int32_t val, volatile int32_t *var) +{ + return __c11_atomic_fetch_add((_Atomic(int)*)var, val, __ATOMIC_RELAXED)+val; +} + +WCHAR * +argv_to_command_line(char **argv); + +int +asprintf(char **strp, const char *format, ...); + +void +filetime_to_timeval(struct timeval *tp, const FILETIME *ft); + +pid_t +getpid(void); + +int +gettimeofday(struct timeval *tp, void *tzp); + +void +print_winapi_error(const char *function_name, DWORD error); + +unsigned int +sleep(unsigned int seconds); + +int +usleep(unsigned int usec);