[LOCALSPL_APITEST]
[reactos.git] / rostests / apitests / localspl / tests.c
1 /*
2 * PROJECT: ReactOS Local Spooler API Tests
3 * LICENSE: GNU GPLv2 or any later version as published by the Free Software Foundation
4 * PURPOSE: Test list
5 * COPYRIGHT: Copyright 2015-2016 Colin Finck <colin@reactos.org>
6 */
7
8 /*
9 * The original localspl.dll from Windows Server 2003 is not easily testable.
10 * It relies on a proper initialization inside spoolsv.exe, so we can't just load it in an API-Test as usual.
11 * See https://www.reactos.org/pipermail/ros-dev/2015-June/017395.html for more information.
12 *
13 * To make testing possible anyway, this program basically does four things:
14 * - Injecting our testing code into spoolsv.exe.
15 * - Registering and running us as a service in the SYSTEM security context like spoolsv.exe, so that injection is possible at all.
16 * - Sending the test name and receiving the console output over named pipes.
17 * - Redirecting the received console output to stdout again, so it looks and feels like a standard API-Test.
18 *
19 * To simplify debugging of the injected code, it is entirely separated into a DLL file localspl_apitest.dll.
20 * What we actually inject is a LoadLibraryW call, so that the DLL is loaded gracefully without any hacks.
21 * Therefore, you can just attach your debugger to the spoolsv.exe process and set breakpoints on the localspl_apitest.dll code.
22 */
23
24 #include <apitest.h>
25
26 #define WIN32_NO_STATUS
27 #include <stdio.h>
28 #include <stdlib.h>
29 #include <windef.h>
30 #include <winbase.h>
31 #include <wingdi.h>
32 #include <winreg.h>
33 #include <winsvc.h>
34 #include <winspool.h>
35 #include <winsplp.h>
36
37 #include "localspl_apitest.h"
38
39
40 static void
41 _RunRemoteTest(const char* szTestName)
42 {
43 BOOL bSuccessful = FALSE;
44 char szBuffer[1024];
45 DWORD cbRead;
46 DWORD cbWritten;
47 HANDLE hCommandPipe = INVALID_HANDLE_VALUE;
48 HANDLE hFind = INVALID_HANDLE_VALUE;
49 HANDLE hOutputPipe = INVALID_HANDLE_VALUE;
50 PWSTR p;
51 SC_HANDLE hSC = NULL;
52 SC_HANDLE hService = NULL;
53 SERVICE_STATUS ServiceStatus;
54 WCHAR wszFilePath[MAX_PATH + 20];
55 WIN32_FIND_DATAW fd;
56
57 // Do a dummy EnumPrintersW call.
58 // This guarantees that the Spooler Service has actually loaded localspl.dll, which is a requirement for our injected DLL to work properly.
59 EnumPrintersW(PRINTER_ENUM_LOCAL | PRINTER_ENUM_NAME, NULL, 1, NULL, 0, &cbRead, &cbWritten);
60
61 // Get the full path to our EXE file.
62 if (!GetModuleFileNameW(NULL, wszFilePath, MAX_PATH))
63 {
64 skip("GetModuleFileNameW failed with error %lu!\n", GetLastError());
65 goto Cleanup;
66 }
67
68 // Replace the extension.
69 p = wcsrchr(wszFilePath, L'.');
70 if (!p)
71 {
72 skip("File path has no file extension: %S\n", wszFilePath);
73 goto Cleanup;
74 }
75
76 wcscpy(p, L".dll");
77
78 // Check if the corresponding DLL file exists.
79 hFind = FindFirstFileW(wszFilePath, &fd);
80 if (hFind == INVALID_HANDLE_VALUE)
81 {
82 skip("My DLL file \"%S\" does not exist!\n", wszFilePath);
83 goto Cleanup;
84 }
85
86 // Change the extension back to .exe and add the parameters.
87 wcscpy(p, L".exe service dummy");
88
89 // Open a handle to the service manager.
90 hSC = OpenSCManagerW(NULL, NULL, SC_MANAGER_ALL_ACCESS);
91 if (!hSC)
92 {
93 skip("OpenSCManagerW failed with error %lu!\n", GetLastError());
94 goto Cleanup;
95 }
96
97 // Ensure that the spooler service is running.
98 hService = OpenServiceW(hSC, L"spooler", SERVICE_QUERY_STATUS);
99 if (!hService)
100 {
101 skip("OpenServiceW failed for the spooler service with error %lu!\n", GetLastError());
102 goto Cleanup;
103 }
104
105 if (!QueryServiceStatus(hService, &ServiceStatus))
106 {
107 skip("QueryServiceStatus failed for the spooler service with error %lu!\n", GetLastError());
108 goto Cleanup;
109 }
110
111 if (ServiceStatus.dwCurrentState != SERVICE_RUNNING)
112 {
113 skip("Spooler Service is not running!\n");
114 goto Cleanup;
115 }
116
117 CloseServiceHandle(hService);
118
119 // Try to open the service if we've created it in a previous run.
120 hService = OpenServiceW(hSC, SERVICE_NAME, SERVICE_ALL_ACCESS);
121 if (!hService)
122 {
123 if (GetLastError() == ERROR_SERVICE_DOES_NOT_EXIST)
124 {
125 // Create the service.
126 hService = CreateServiceW(hSC, SERVICE_NAME, NULL, SERVICE_ALL_ACCESS, SERVICE_WIN32_OWN_PROCESS, SERVICE_DEMAND_START, SERVICE_ERROR_IGNORE, wszFilePath, NULL, NULL, NULL, NULL, NULL);
127 if (!hService)
128 {
129 skip("CreateServiceW failed with error %lu!\n", GetLastError());
130 goto Cleanup;
131 }
132 }
133 else
134 {
135 skip("OpenServiceW failed with error %lu!\n", GetLastError());
136 goto Cleanup;
137 }
138 }
139
140 // Create pipes for the communication with the injected DLL.
141 hCommandPipe = CreateNamedPipeW(COMMAND_PIPE_NAME, PIPE_ACCESS_OUTBOUND, PIPE_TYPE_MESSAGE | PIPE_READMODE_MESSAGE | PIPE_WAIT, 1, 1024, 1024, 10000, NULL);
142 if (hCommandPipe == INVALID_HANDLE_VALUE)
143 {
144 skip("CreateNamedPipeW failed for the command pipe with error %lu!\n", GetLastError());
145 goto Cleanup;
146 }
147
148 hOutputPipe = CreateNamedPipeW(OUTPUT_PIPE_NAME, PIPE_ACCESS_INBOUND, PIPE_TYPE_BYTE | PIPE_READMODE_BYTE | PIPE_WAIT, 1, 1024, 1024, 10000, NULL);
149 if (hOutputPipe == INVALID_HANDLE_VALUE)
150 {
151 skip("CreateNamedPipeW failed for the output pipe with error %lu!\n", GetLastError());
152 goto Cleanup;
153 }
154
155 // Start the service with "service" and a dummy parameter (to distinguish it from a call by rosautotest to localspl_apitest:service)
156 if (!StartServiceW(hService, 0, NULL))
157 {
158 skip("StartServiceW failed with error %lu!\n", GetLastError());
159 goto Cleanup;
160 }
161
162 // Wait till it has injected the DLL and the DLL expects its test name.
163 if (!ConnectNamedPipe(hCommandPipe, NULL) && GetLastError() != ERROR_PIPE_CONNECTED)
164 {
165 skip("ConnectNamedPipe failed for the command pipe with error %lu!\n", GetLastError());
166 goto Cleanup;
167 }
168
169 // Send the test name.
170 if (!WriteFile(hCommandPipe, szTestName, strlen(szTestName) + sizeof(char), &cbWritten, NULL))
171 {
172 skip("WriteFile failed with error %lu!\n", GetLastError());
173 goto Cleanup;
174 }
175
176 // Now wait for the DLL to connect to the output pipe.
177 if (!ConnectNamedPipe(hOutputPipe, NULL))
178 {
179 skip("ConnectNamedPipe failed for the output pipe with error %lu!\n", GetLastError());
180 goto Cleanup;
181 }
182
183 // Get all testing messages from the pipe and output them on stdout.
184 while (ReadFile(hOutputPipe, szBuffer, sizeof(szBuffer), &cbRead, NULL) && cbRead)
185 fwrite(szBuffer, sizeof(char), cbRead, stdout);
186
187 bSuccessful = TRUE;
188
189 Cleanup:
190 if (hCommandPipe != INVALID_HANDLE_VALUE)
191 CloseHandle(hCommandPipe);
192
193 if (hOutputPipe != INVALID_HANDLE_VALUE)
194 CloseHandle(hOutputPipe);
195
196 if (hFind != INVALID_HANDLE_VALUE)
197 FindClose(hFind);
198
199 if (hService)
200 CloseServiceHandle(hService);
201
202 if (hSC)
203 CloseServiceHandle(hSC);
204
205 // If we successfully received test output through the named pipe, we have also output a summary line already.
206 // Prevent the testing framework from outputting another "0 tests executed" line in this case.
207 if (bSuccessful)
208 ExitProcess(0);
209 }
210
211 START_TEST(fpEnumPrinters)
212 {
213 _RunRemoteTest("fpEnumPrinters");
214 }