--- /dev/null
+/*
+ * PROJECT: ReactOS api tests
+ * LICENSE: LGPLv2.1+ - See COPYING.LIB in the top level directory
+ * PURPOSE: Support helpers for embedded services inside api tests.
+ * PROGRAMMERS: Jacek Caban for CodeWeavers
+ * Thomas Faber <thomas.faber@reactos.org>
+ * Hermes Belusca-Maito
+ *
+ * NOTE: Room for improvements:
+ * - One test_runner managing 1 pipe for 1 service process that is shared
+ * by multiple services of type SERVICE_WIN32_SHARE_PROCESS.
+ * - Find a way to elegantly determine the registered service name inside
+ * the service process, without really passing it
+ */
+
+#include <apitest.h>
+#include <winnls.h>
+#include <winsvc.h>
+#include <strsafe.h>
+
+static HANDLE hClientPipe = INVALID_HANDLE_VALUE;
+static WCHAR named_pipe_name[100]; // Shared: FIXME!
+
+static CHAR service_nameA[100];
+static WCHAR service_nameW[100];
+
+
+/********** S E R V I C E ( C L I E N T ) M O D U L E S I D E *********/
+
+void send_msg(const char *type, const char *msg)
+{
+ DWORD written = 0;
+ char buf[512];
+
+ StringCbPrintfA(buf, sizeof(buf), "%s:%s", type, msg);
+ WriteFile(hClientPipe, buf, strlen(buf)+1, &written, NULL);
+}
+
+void service_trace(const char *msg, ...)
+{
+ va_list valist;
+ char buf[512];
+
+ va_start(valist, msg);
+ StringCbVPrintfA(buf, sizeof(buf), msg, valist);
+ va_end(valist);
+
+ send_msg("TRACE", buf);
+}
+
+void service_ok(int cnd, const char *msg, ...)
+{
+ va_list valist;
+ char buf[512];
+
+ va_start(valist, msg);
+ StringCbVPrintfA(buf, sizeof(buf), msg, valist);
+ va_end(valist);
+
+ send_msg(cnd ? "OK" : "FAIL", buf);
+}
+
+void service_process(BOOL (*start_service)(PCSTR, PCWSTR), int argc, char** argv)
+{
+ BOOL res;
+
+ StringCbCopyA(service_nameA, sizeof(service_nameA), argv[2]);
+ MultiByteToWideChar(CP_ACP, 0, service_nameA, -1, service_nameW, _countof(service_nameW));
+ StringCbPrintfW(named_pipe_name, sizeof(named_pipe_name), L"\\\\.\\pipe\\%ls_pipe", service_nameW);
+
+ res = WaitNamedPipeW(named_pipe_name, NMPWAIT_USE_DEFAULT_WAIT);
+ if (!res)
+ return;
+
+ hClientPipe = CreateFileW(named_pipe_name, GENERIC_WRITE, 0, NULL, OPEN_EXISTING, FILE_ATTRIBUTE_NORMAL, NULL);
+ if (hClientPipe == INVALID_HANDLE_VALUE)
+ return;
+
+ service_trace("Service process starting...\n");
+ res = start_service(service_nameA, service_nameW);
+ service_trace("Service process stopped.\n");
+
+ CloseHandle(hClientPipe);
+}
+
+
+/*********** T E S T E R ( S E R V E R ) M O D U L E S I D E **********/
+
+SC_HANDLE register_service_exA(
+ SC_HANDLE scm_handle,
+ PCSTR test_name,
+ PCSTR service_name, // LPCSTR lpServiceName,
+ PCSTR extra_args OPTIONAL,
+ DWORD dwDesiredAccess,
+ DWORD dwServiceType,
+ DWORD dwStartType,
+ DWORD dwErrorControl,
+ LPCSTR lpLoadOrderGroup OPTIONAL,
+ LPDWORD lpdwTagId OPTIONAL,
+ LPCSTR lpDependencies OPTIONAL,
+ LPCSTR lpServiceStartName OPTIONAL,
+ LPCSTR lpPassword OPTIONAL)
+{
+ SC_HANDLE service;
+ CHAR service_cmd[MAX_PATH+150];
+
+ /* Retrieve our full path */
+ if (!GetModuleFileNameA(NULL, service_cmd, MAX_PATH))
+ {
+ skip("GetModuleFileNameW failed with error %lu!\n", GetLastError());
+ return NULL;
+ }
+
+ /*
+ * Build up our custom command line. The first parameter is the test name,
+ * the second parameter is the flag used to decide whether we should start
+ * as a service.
+ */
+ StringCbCatA(service_cmd, sizeof(service_cmd), " ");
+ StringCbCatA(service_cmd, sizeof(service_cmd), test_name);
+ StringCbCatA(service_cmd, sizeof(service_cmd), " ");
+ StringCbCatA(service_cmd, sizeof(service_cmd), service_name);
+ if (extra_args)
+ {
+ StringCbCatA(service_cmd, sizeof(service_cmd), " ");
+ StringCbCatA(service_cmd, sizeof(service_cmd), extra_args);
+ }
+
+ trace("service_cmd \"%s\"\n", service_cmd);
+
+ service = CreateServiceA(scm_handle, service_name, service_name,
+ dwDesiredAccess, dwServiceType, dwStartType, dwErrorControl,
+ service_cmd, lpLoadOrderGroup, lpdwTagId, lpDependencies,
+ lpServiceStartName, lpPassword);
+ if (!service && GetLastError() == ERROR_ACCESS_DENIED)
+ {
+ skip("Not enough access right to create service.\n");
+ return NULL;
+ }
+
+ ok(service != NULL, "CreateService failed: %lu\n", GetLastError());
+ return service;
+}
+
+SC_HANDLE register_service_exW(
+ SC_HANDLE scm_handle,
+ PCWSTR test_name,
+ PCWSTR service_name, // LPCWSTR lpServiceName,
+ PCWSTR extra_args OPTIONAL,
+ DWORD dwDesiredAccess,
+ DWORD dwServiceType,
+ DWORD dwStartType,
+ DWORD dwErrorControl,
+ LPCWSTR lpLoadOrderGroup OPTIONAL,
+ LPDWORD lpdwTagId OPTIONAL,
+ LPCWSTR lpDependencies OPTIONAL,
+ LPCWSTR lpServiceStartName OPTIONAL,
+ LPCWSTR lpPassword OPTIONAL)
+{
+ SC_HANDLE service;
+ WCHAR service_cmd[MAX_PATH+150];
+
+ /* Retrieve our full path */
+ if (!GetModuleFileNameW(NULL, service_cmd, MAX_PATH))
+ {
+ skip("GetModuleFileNameW failed with error %lu!\n", GetLastError());
+ return NULL;
+ }
+
+ /*
+ * Build up our custom command line. The first parameter is the test name,
+ * the second parameter is the flag used to decide whether we should start
+ * as a service.
+ */
+ StringCbCatW(service_cmd, sizeof(service_cmd), L" ");
+ StringCbCatW(service_cmd, sizeof(service_cmd), test_name);
+ StringCbCatW(service_cmd, sizeof(service_cmd), L" ");
+ StringCbCatW(service_cmd, sizeof(service_cmd), service_name);
+ if (extra_args)
+ {
+ StringCbCatW(service_cmd, sizeof(service_cmd), L" ");
+ StringCbCatW(service_cmd, sizeof(service_cmd), extra_args);
+ }
+
+ trace("service_cmd \"%ls\"\n", service_cmd);
+
+ service = CreateServiceW(scm_handle, service_name, service_name,
+ dwDesiredAccess, dwServiceType, dwStartType, dwErrorControl,
+ service_cmd, lpLoadOrderGroup, lpdwTagId, lpDependencies,
+ lpServiceStartName, lpPassword);
+ if (!service && GetLastError() == ERROR_ACCESS_DENIED)
+ {
+ skip("Not enough access right to create service.\n");
+ return NULL;
+ }
+
+ ok(service != NULL, "CreateService failed: %lu\n", GetLastError());
+ return service;
+}
+
+SC_HANDLE register_serviceA(
+ SC_HANDLE scm_handle,
+ PCSTR test_name,
+ PCSTR service_name,
+ PCSTR extra_args OPTIONAL)
+{
+ return register_service_exA(scm_handle, test_name, service_name, extra_args,
+ SERVICE_ALL_ACCESS,
+ SERVICE_WIN32_OWN_PROCESS,
+ SERVICE_DEMAND_START,
+ SERVICE_ERROR_IGNORE,
+ NULL, NULL, NULL, NULL, NULL);
+}
+
+SC_HANDLE register_serviceW(
+ SC_HANDLE scm_handle,
+ PCWSTR test_name,
+ PCWSTR service_name,
+ PCWSTR extra_args OPTIONAL)
+{
+ return register_service_exW(scm_handle, test_name, service_name, extra_args,
+ SERVICE_ALL_ACCESS,
+ SERVICE_WIN32_OWN_PROCESS,
+ SERVICE_DEMAND_START,
+ SERVICE_ERROR_IGNORE,
+ NULL, NULL, NULL, NULL, NULL);
+}
+
+static DWORD WINAPI pipe_thread(void *param)
+{
+ HANDLE hServerPipe = (HANDLE)param;
+ DWORD read;
+ BOOL res;
+ char buf[512];
+
+ // printf("pipe_thread -- ConnectNamedPipe...\n");
+ res = ConnectNamedPipe(hServerPipe, NULL);
+ ok(res || GetLastError() == ERROR_PIPE_CONNECTED, "ConnectNamedPipe failed: %lu\n", GetLastError());
+ // printf("pipe_thread -- ConnectNamedPipe ok\n");
+
+ while (1)
+ {
+ res = ReadFile(hServerPipe, buf, sizeof(buf), &read, NULL);
+ if (!res)
+ {
+ ok(GetLastError() == ERROR_BROKEN_PIPE || GetLastError() == ERROR_INVALID_HANDLE,
+ "ReadFile failed: %lu\n", GetLastError());
+ // printf("pipe_thread -- break loop\n");
+ break;
+ }
+
+ if (!strncmp(buf, "TRACE:", 6))
+ {
+ trace("service trace: %s", buf+6);
+ }
+ else if (!strncmp(buf, "OK:", 3))
+ {
+ ok(1, "service: %s", buf+3);
+ }
+ else if (!strncmp(buf, "FAIL:", 5))
+ {
+ ok(0, "service: %s", buf+5);
+ }
+ else
+ {
+ ok(0, "malformed service message: %s\n", buf);
+ }
+ }
+
+ // printf("pipe_thread -- DisconnectNamedPipe\n");
+
+ /*
+ * Flush the pipe to allow the client to read
+ * the pipe's contents before disconnecting.
+ */
+ FlushFileBuffers(hServerPipe);
+
+ DisconnectNamedPipe(hServerPipe);
+ trace("pipe disconnected\n");
+ return 0;
+}
+
+void test_runner(void (*run_test)(PCSTR, PCWSTR, void*), void *param)
+{
+ HANDLE hServerPipe = INVALID_HANDLE_VALUE;
+ HANDLE hThread;
+
+ StringCbPrintfW(service_nameW, sizeof(service_nameW), L"WineTestService%lu", GetTickCount());
+ WideCharToMultiByte(CP_ACP, 0, service_nameW, -1, service_nameA, _countof(service_nameA), NULL, NULL);
+ //trace("service_name: %ls\n", service_nameW);
+ StringCbPrintfW(named_pipe_name, sizeof(named_pipe_name), L"\\\\.\\pipe\\%ls_pipe", service_nameW);
+
+ hServerPipe = CreateNamedPipeW(named_pipe_name, PIPE_ACCESS_INBOUND,
+ PIPE_TYPE_MESSAGE|PIPE_READMODE_MESSAGE|PIPE_WAIT, 10, 2048, 2048, 10000, NULL);
+ ok(hServerPipe != INVALID_HANDLE_VALUE, "CreateNamedPipe failed: %lu\n", GetLastError());
+ if (hServerPipe == INVALID_HANDLE_VALUE)
+ return;
+
+ hThread = CreateThread(NULL, 0, pipe_thread, (LPVOID)hServerPipe, 0, NULL);
+ ok(hThread != NULL, "CreateThread failed: %lu\n", GetLastError());
+ if (!hThread)
+ goto Quit;
+
+ run_test(service_nameA, service_nameW, param);
+
+ ok(WaitForSingleObject(hThread, 10000) == WAIT_OBJECT_0, "Timeout waiting for thread\n");
+
+Quit:
+ if (hThread)
+ {
+ /* Be sure to kill the thread if it did not already terminate */
+ TerminateThread(hThread, 0);
+ CloseHandle(hThread);
+ }
+
+ if (hServerPipe != INVALID_HANDLE_VALUE)
+ CloseHandle(hServerPipe);
+}
--- /dev/null
+/*
+ * PROJECT: ReactOS api tests
+ * LICENSE: LGPLv2.1+ - See COPYING.LIB in the top level directory
+ * PURPOSE: Support helpers for embedded services inside api tests.
+ * PROGRAMMERS: Jacek Caban for CodeWeavers
+ * Thomas Faber <thomas.faber@reactos.org>
+ * Hermes Belusca-Maito
+ */
+
+// #include <apitest.h>
+
+
+/********** S E R V I C E ( C L I E N T ) M O D U L E S I D E *********/
+
+void send_msg(const char *type, const char *msg);
+void service_trace(const char *msg, ...);
+void service_ok(int cnd, const char *msg, ...);
+void service_process(BOOL (*start_service)(PCSTR, PCWSTR), int argc, char** argv);
+
+
+/*********** T E S T E R ( S E R V E R ) M O D U L E S I D E **********/
+
+SC_HANDLE register_service_exA(
+ SC_HANDLE scm_handle,
+ PCSTR test_name,
+ PCSTR service_name, // LPCSTR lpServiceName,
+ PCSTR extra_args OPTIONAL,
+ DWORD dwDesiredAccess,
+ DWORD dwServiceType,
+ DWORD dwStartType,
+ DWORD dwErrorControl,
+ LPCSTR lpLoadOrderGroup OPTIONAL,
+ LPDWORD lpdwTagId OPTIONAL,
+ LPCSTR lpDependencies OPTIONAL,
+ LPCSTR lpServiceStartName OPTIONAL,
+ LPCSTR lpPassword OPTIONAL);
+
+SC_HANDLE register_service_exW(
+ SC_HANDLE scm_handle,
+ PCWSTR test_name,
+ PCWSTR service_name, // LPCWSTR lpServiceName,
+ PCWSTR extra_args OPTIONAL,
+ DWORD dwDesiredAccess,
+ DWORD dwServiceType,
+ DWORD dwStartType,
+ DWORD dwErrorControl,
+ LPCWSTR lpLoadOrderGroup OPTIONAL,
+ LPDWORD lpdwTagId OPTIONAL,
+ LPCWSTR lpDependencies OPTIONAL,
+ LPCWSTR lpServiceStartName OPTIONAL,
+ LPCWSTR lpPassword OPTIONAL);
+
+SC_HANDLE register_serviceA(
+ SC_HANDLE scm_handle,
+ PCSTR test_name,
+ PCSTR service_name,
+ PCSTR extra_args OPTIONAL);
+
+SC_HANDLE register_serviceW(
+ SC_HANDLE scm_handle,
+ PCWSTR test_name,
+ PCWSTR service_name,
+ PCWSTR extra_args OPTIONAL);
+
+#ifdef UNICODE
+#define register_service_ex register_service_exW
+#define register_service register_serviceW
+#else
+#define register_service_ex register_service_exA
+#define register_service register_serviceA
+#endif
+
+void test_runner(void (*run_test)(PCSTR, PCWSTR, void*), void *param);