Merge pull request #440 from adierking/windowstests
tests: port the test harness to Windows
diff --git a/tests/CMakeLists.txt b/tests/CMakeLists.txt
index 5909ad1..3a4684f 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 @@
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 @@
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_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 f7b6ea3..ca52d6e 100644
--- a/tests/bsdtestharness.c
+++ b/tests/bsdtestharness.c
@@ -20,33 +20,29 @@
#include <dispatch/dispatch.h>
#include <assert.h>
-#include <spawn.h>
#include <stdio.h>
#include <stdlib.h>
#if defined(__unix__) || (defined(__APPLE__) && defined(__MACH__))
+#include <spawn.h>
+#include <sys/resource.h>
+#include <sys/time.h>
+#include <sys/wait.h>
#include <unistd.h>
+#elif defined(_WIN32)
+#include <generic_win_port.h>
+#include <Psapi.h>
+#include <Windows.h>
#endif
#include <signal.h>
#ifdef __APPLE__
#include <mach/clock_types.h>
#include <mach-o/arch.h>
#endif
-#include <sys/resource.h>
-#include <sys/time.h>
-#if defined(__linux__) || defined(__FreeBSD__)
-#include <sys/wait.h>
-#endif
#include <bsdtests.h>
+#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 @@
_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 @@
//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 @@
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 @@
kill(pid, SIGCONT);
dispatch_main();
-#endif // SIMPLE_TEST_HARNESS
+#endif
return 0;
}
diff --git a/tests/bsdtests.c b/tests/bsdtests.c
index 09700fa..af71662 100644
--- a/tests/bsdtests.c
+++ b/tests/bsdtests.c
@@ -29,14 +29,16 @@
#include <unistd.h>
#endif
#include <errno.h>
-#include <sys/errno.h>
-#include <sys/wait.h>
#include <string.h>
#ifdef __APPLE__
#include <crt_externs.h>
#include <mach/mach_error.h>
-#endif
#include <spawn.h>
+#include <sys/wait.h>
+#endif
+#if defined(_WIN32)
+#include <generic_win_port.h>
+#endif
#include <inttypes.h>
#include "bsdtests.h"
@@ -454,18 +456,11 @@
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_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 e820823..e8e292e 100644
--- a/tests/bsdtests.h
+++ b/tests/bsdtests.h
@@ -63,18 +63,22 @@
}
#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 @@
#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 70cf90f..9809543 100644
--- a/tests/dispatch_test.c
+++ b/tests/dispatch_test.c
@@ -29,13 +29,16 @@
#include <stdio.h>
#if defined(__unix__) || (defined(__APPLE__) && defined(__MACH__))
#include <unistd.h>
-#endif
#if __has_include(<sys/event.h>)
#define HAS_SYS_EVENT_H 1
#include <sys/event.h>
#else
#include <sys/poll.h>
#endif
+#elif defined(_WIN32)
+#include <io.h>
+#include <Windows.h>
+#endif
#include <assert.h>
#include <dispatch/dispatch.h>
@@ -68,6 +71,20 @@
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 @@
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 @@
(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 0f5be8a..415e419 100644
--- a/tests/dispatch_test.h
+++ b/tests/dispatch_test.h
@@ -18,12 +18,13 @@
* @APPLE_APACHE_LICENSE_HEADER_END@
*/
-#include <sys/cdefs.h>
#include <stdbool.h>
#include <dispatch/dispatch.h>
#if defined(__linux__) || defined(__FreeBSD__)
#include <generic_unix_port.h>
+#elif defined(_WIN32)
+#include <generic_win_port.h>
#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 @@
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 0000000..d9a52f4
--- /dev/null
+++ b/tests/generic_win_port.c
@@ -0,0 +1,223 @@
+#include <generic_win_port.h>
+#include <stdarg.h>
+#include <stdbool.h>
+#include <stdio.h>
+#include <stdlib.h>
+#include <time.h>
+#include <wchar.h>
+#include <Windows.h>
+
+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:
+ // <https://docs.microsoft.com/en-us/windows/desktop/api/shellapi/nf-shellapi-commandlinetoargvw>
+ // <https://blogs.msdn.microsoft.com/twistylittlepassagesallalike/2011/04/23/everyone-quotes-command-line-arguments-the-wrong-way/>
+ 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 0000000..cf96a21
--- /dev/null
+++ b/tests/generic_win_port.h
@@ -0,0 +1,55 @@
+#pragma once
+
+#include <stdint.h>
+#include <Windows.h>
+
+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);