services.exe: Added tests.
diff --git a/configure b/configure
index 81d7390..4e0982f 100755
--- a/configure
+++ b/configure
@@ -15647,6 +15647,7 @@
wine_fn_config_program secedit enable_secedit install
wine_fn_config_program servicemodelreg enable_servicemodelreg install
wine_fn_config_program services enable_services install
+wine_fn_config_test programs/services/tests services.exe_test
wine_fn_config_program spoolsv enable_spoolsv install
wine_fn_config_program start enable_start install,po
wine_fn_config_program svchost enable_svchost install
diff --git a/configure.ac b/configure.ac
index 9688ba7..c9623ff 100644
--- a/configure.ac
+++ b/configure.ac
@@ -3063,6 +3063,7 @@
WINE_CONFIG_PROGRAM(secedit,,[install])
WINE_CONFIG_PROGRAM(servicemodelreg,,[install])
WINE_CONFIG_PROGRAM(services,,[install])
+WINE_CONFIG_TEST(programs/services/tests)
WINE_CONFIG_PROGRAM(spoolsv,,[install])
WINE_CONFIG_PROGRAM(start,,[install,po])
WINE_CONFIG_PROGRAM(svchost,,[install])
diff --git a/programs/services/tests/Makefile.in b/programs/services/tests/Makefile.in
new file mode 100644
index 0000000..8a49219
--- /dev/null
+++ b/programs/services/tests/Makefile.in
@@ -0,0 +1,7 @@
+TESTDLL = services.exe
+IMPORTS = advapi32
+
+C_SRCS = \
+ service.c
+
+@MAKE_TEST_RULES@
diff --git a/programs/services/tests/service.c b/programs/services/tests/service.c
new file mode 100644
index 0000000..5348bb9
--- /dev/null
+++ b/programs/services/tests/service.c
@@ -0,0 +1,329 @@
+/*
+ * Copyright 2012 Jacek Caban for CodeWeavers
+ *
+ * This library is free software; you can redistribute it and/or
+ * modify it under the terms of the GNU Lesser General Public
+ * License as published by the Free Software Foundation; either
+ * version 2.1 of the License, or (at your option) any later version.
+ *
+ * This library is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
+ * Lesser General Public License for more details.
+ *
+ * You should have received a copy of the GNU Lesser General Public
+ * License along with this library; if not, write to the Free Software
+ * Foundation, Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301, USA
+ */
+
+#include <windows.h>
+#include <stdio.h>
+
+#include "wine/test.h"
+
+static SERVICE_STATUS_HANDLE (WINAPI *pRegisterServiceCtrlHandlerExA)(LPCSTR,LPHANDLER_FUNCTION_EX,LPVOID);
+
+static HANDLE pipe_handle = INVALID_HANDLE_VALUE;
+static char service_name[100], named_pipe_name[100];
+static SERVICE_STATUS_HANDLE service_handle;
+
+/* Service process global variables */
+static HANDLE service_stop_event;
+
+static void send_msg(const char *type, const char *msg)
+{
+ DWORD written = 0;
+ char buf[512];
+
+ sprintf(buf, "%s:%s", type, msg);
+ WriteFile(pipe_handle, buf, strlen(buf)+1, &written, NULL);
+}
+
+static inline void service_trace(const char *msg)
+{
+ send_msg("TRACE", msg);
+}
+
+static inline void service_event(const char *event)
+{
+ send_msg("EVENT", event);
+}
+
+static void service_ok(int cnd, const char *msg, ...)
+{
+ va_list valist;
+ char buf[512];
+
+ va_start(valist, msg);
+ vsprintf(buf, msg, valist);
+ va_end(valist);
+
+ send_msg(cnd ? "OK" : "FAIL", buf);
+}
+
+static DWORD WINAPI service_handler(DWORD ctrl, DWORD event_type, void *event_data, void *context)
+{
+ SERVICE_STATUS status;
+
+ status.dwServiceType = SERVICE_WIN32;
+ status.dwControlsAccepted = SERVICE_ACCEPT_STOP;
+ status.dwWin32ExitCode = 0;
+ status.dwServiceSpecificExitCode = 0;
+ status.dwCheckPoint = 0;
+ status.dwWaitHint = 0;
+
+ switch(ctrl)
+ {
+ case SERVICE_CONTROL_STOP:
+ case SERVICE_CONTROL_SHUTDOWN:
+ service_event("STOP");
+ status.dwCurrentState = SERVICE_STOP_PENDING;
+ status.dwControlsAccepted = 0;
+ SetServiceStatus(service_handle, &status);
+ SetEvent(service_stop_event);
+ return NO_ERROR;
+ default:
+ status.dwCurrentState = SERVICE_RUNNING;
+ SetServiceStatus( service_handle, &status );
+ return NO_ERROR;
+ }
+}
+
+static void WINAPI service_main(DWORD argc, char **argv)
+{
+ SERVICE_STATUS status;
+ BOOL res;
+
+ service_handle = pRegisterServiceCtrlHandlerExA(service_name, service_handler, NULL);
+ service_ok(service_handle != NULL, "RegisterServiceCtrlHandlerEx failed: %u\n", GetLastError());
+ if(!service_handle)
+ return;
+
+ status.dwServiceType = SERVICE_WIN32;
+ status.dwCurrentState = SERVICE_RUNNING;
+ status.dwControlsAccepted = SERVICE_ACCEPT_STOP | SERVICE_ACCEPT_SHUTDOWN;
+ status.dwWin32ExitCode = 0;
+ status.dwServiceSpecificExitCode = 0;
+ status.dwCheckPoint = 0;
+ status.dwWaitHint = 10000;
+ res = SetServiceStatus(service_handle, &status);
+ service_ok(res, "SetServiceStatus(SERVICE_RUNNING) failed: %u", GetLastError());
+
+ service_event("RUNNING");
+
+ WaitForSingleObject(service_stop_event, INFINITE);
+
+ status.dwCurrentState = SERVICE_STOPPED;
+ status.dwControlsAccepted = 0;
+ res = SetServiceStatus(service_handle, &status);
+ service_ok(res, "SetServiceStatus(SERVICE_STOPPED) failed: %u", GetLastError());
+}
+
+static void service_process(void)
+{
+ BOOL res;
+
+ static const SERVICE_TABLE_ENTRYA servtbl[] = {
+ {service_name, service_main},
+ {NULL, NULL}
+ };
+
+ res = WaitNamedPipeA(named_pipe_name, NMPWAIT_USE_DEFAULT_WAIT);
+ if(!res)
+ return;
+
+ pipe_handle = CreateFileA(named_pipe_name, GENERIC_WRITE, 0, NULL, OPEN_EXISTING, FILE_ATTRIBUTE_NORMAL, NULL);
+ if(pipe_handle == INVALID_HANDLE_VALUE)
+ return;
+
+ service_trace("Starting...");
+
+ service_stop_event = CreateEventA(NULL, TRUE, FALSE, NULL);
+ service_ok(service_stop_event != NULL, "Could not create event: %u\n", GetLastError());
+ if(!service_stop_event)
+ return;
+
+ res = StartServiceCtrlDispatcherA(servtbl);
+ service_ok(res, "StartServiceCtrlDispatcher failed: %u\n", GetLastError());
+
+ CloseHandle(service_stop_event);
+}
+
+/* Test process global variables */
+static SC_HANDLE scm_handle;
+
+static char current_event[32];
+static HANDLE event_handle = INVALID_HANDLE_VALUE;
+static CRITICAL_SECTION event_cs;
+
+static SC_HANDLE register_service(void)
+{
+ char service_cmd[MAX_PATH+150], *ptr;
+ SC_HANDLE service;
+
+ ptr = service_cmd + GetModuleFileNameA(NULL, service_cmd, MAX_PATH);
+
+ /* If the file doesn't exist, assume we're using built-in exe and append .so to the path */
+ if(GetFileAttributesA(service_cmd) == INVALID_FILE_ATTRIBUTES) {
+ strcpy(ptr, ".so");
+ ptr += 3;
+ }
+
+ strcpy(ptr, " service ");
+ ptr += strlen(ptr);
+ strcpy(ptr, service_name);
+
+ trace("service_cmd \"%s\"\n", service_cmd);
+
+ service = CreateServiceA(scm_handle, service_name, service_name, GENERIC_ALL,
+ SERVICE_WIN32_OWN_PROCESS, SERVICE_DEMAND_START, SERVICE_ERROR_IGNORE,
+ service_cmd, NULL, NULL, NULL, NULL, NULL);
+ if(!service && GetLastError() == ERROR_ACCESS_DENIED) {
+ skip("Not enough access right to create service\n");
+ return NULL;
+ }
+
+ ok(service != NULL, "CreateService failed: %u\n", GetLastError());
+ return service;
+}
+
+static void expect_event(const char *event_name)
+{
+ char evt[32];
+ DWORD res;
+
+ trace("waiting for %s\n", event_name);
+
+ res = WaitForSingleObject(event_handle, 30000);
+ ok(res == WAIT_OBJECT_0, "WaitForSingleObject failed: %u\n", res);
+ if(res != WAIT_OBJECT_0)
+ return;
+
+ EnterCriticalSection(&event_cs);
+ strcpy(evt, current_event);
+ *current_event = 0;
+ LeaveCriticalSection(&event_cs);
+
+ ok(!strcmp(evt, event_name), "Unexpected event: %s, expected %s\n", evt, event_name);
+}
+
+static DWORD WINAPI pipe_thread(void *arg)
+{
+ char buf[257], *ptr;
+ DWORD read;
+ BOOL res;
+
+ res = ConnectNamedPipe(pipe_handle, NULL);
+ ok(res || GetLastError() == ERROR_PIPE_CONNECTED, "ConnectNamedPipe failed: %u\n", GetLastError());
+
+ while(1) {
+ res = ReadFile(pipe_handle, buf, sizeof(buf), &read, NULL);
+ if(!res) {
+ ok(GetLastError() == ERROR_BROKEN_PIPE || GetLastError() == ERROR_INVALID_HANDLE,
+ "ReadFile failed: %u\n", GetLastError());
+ break;
+ }
+
+ for(ptr = buf; ptr < buf+read; ptr += strlen(ptr)+1) {
+ if(!strncmp(ptr, "TRACE:", 6)) {
+ trace("service trace: %s\n", ptr+6);
+ }else if(!strncmp(ptr, "OK:", 3)) {
+ ok(1, "service: %s\n", ptr+3);
+ }else if(!strncmp(ptr, "FAIL:", 3)) {
+ ok(0, "service: %s\n", ptr+5);
+ }else if(!strncmp(ptr, "EVENT:", 6)) {
+ trace("service event: %s\n", ptr+6);
+ EnterCriticalSection(&event_cs);
+ ok(!current_event[0], "event %s still queued\n", current_event);
+ strcpy(current_event, ptr+6);
+ LeaveCriticalSection(&event_cs);
+ SetEvent(event_handle);
+ }else {
+ ok(0, "malformed service message: %s\n", ptr);
+ }
+ }
+ }
+
+ DisconnectNamedPipe(pipe_handle);
+ trace("pipe disconnected\n");
+ return 0;
+}
+
+static void test_process(void)
+{
+ SC_HANDLE service_handle;
+ SERVICE_STATUS status;
+ HANDLE thread;
+ BOOL res;
+
+ sprintf(service_name, "WineTestService%d", GetTickCount());
+ trace("service_name: %s\n", service_name);
+ sprintf(named_pipe_name, "\\\\.\\pipe\\%s_pipe", service_name);
+
+ pipe_handle = CreateNamedPipeA(named_pipe_name, PIPE_ACCESS_INBOUND,
+ PIPE_TYPE_BYTE|PIPE_READMODE_BYTE|PIPE_WAIT, 10, 2048, 2048, 10000, NULL);
+ ok(pipe_handle != INVALID_HANDLE_VALUE, "CreateNamedPipe failed: %u\n", GetLastError());
+ if(pipe_handle == INVALID_HANDLE_VALUE)
+ return;
+
+ InitializeCriticalSection(&event_cs);
+ event_handle = CreateEventA(NULL, FALSE, FALSE, NULL);
+ ok(event_handle != INVALID_HANDLE_VALUE, "CreateEvent failed: %u\n", GetLastError());
+ if(event_handle == INVALID_HANDLE_VALUE)
+ return;
+
+ thread = CreateThread(NULL, 0, pipe_thread, NULL, 0, NULL);
+ ok(thread != NULL, "CreateThread failed: %u\n", GetLastError());
+ if(!thread)
+ return;
+
+ service_handle = register_service();
+ if(!service_handle)
+ return;
+
+ trace("starting...\n");
+ res = StartServiceA(service_handle, 0, NULL);
+ ok(res, "StartService failed: %u\n", GetLastError());
+ if(!res)
+ return;
+ expect_event("RUNNING");
+
+ res = ControlService(service_handle, SERVICE_CONTROL_STOP, &status);
+ ok(res, "ControlService failed: %u\n", GetLastError());
+ expect_event("STOP");
+
+ res = DeleteService(service_handle);
+ ok(res, "DeleteService failed: %u\n", GetLastError());
+
+ CloseServiceHandle(service_handle);
+}
+
+START_TEST(service)
+{
+ char **argv;
+ int argc;
+
+ pRegisterServiceCtrlHandlerExA = (void*)GetProcAddress(GetModuleHandleA("advapi32.dll"), "RegisterServiceCtrlHandlerExA");
+ if(!pRegisterServiceCtrlHandlerExA) {
+ win_skip("RegisterServiceCtrlHandlerExA not available, skipping tests\n");
+ return;
+ }
+
+ argc = winetest_get_mainargs(&argv);
+
+ scm_handle = OpenSCManagerA(NULL, NULL, GENERIC_ALL);
+ ok(scm_handle != NULL, "OpenSCManager failed: %u\n", GetLastError());
+
+ if(argc < 3) {
+ test_process();
+ }else {
+ strcpy(service_name, argv[2]);
+ sprintf(named_pipe_name, "\\\\.\\pipe\\%s_pipe", service_name);
+
+ service_process();
+ }
+
+ CloseHandle(event_handle);
+ CloseHandle(pipe_handle);
+ CloseServiceHandle(scm_handle);
+}