[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 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 char szBuffer[1024];
44 DWORD cbRead;
45 DWORD cbWritten;
46 HANDLE hCommandPipe;
47 HANDLE hOutputPipe;
48 PWSTR p;
49 SC_HANDLE hSC;
50 SC_HANDLE hService;
51 WCHAR wszFilePath[MAX_PATH + 20];
52 WIN32_FIND_DATAW fd;
53
54 // Do a dummy EnumPrintersW call.
55 // This guarantees that the Spooler Service has actually loaded localspl.dll, which is a requirement for our injected DLL to work properly.
56 EnumPrintersW(PRINTER_ENUM_LOCAL | PRINTER_ENUM_NAME, NULL, 1, NULL, 0, &cbRead, &cbWritten);
57
58 // Get the full path to our EXE file.
59 if (!GetModuleFileNameW(NULL, wszFilePath, MAX_PATH))
60 {
61 skip("GetModuleFileNameW failed with error %lu!\n", GetLastError());
62 return;
63 }
64
65 // Replace the extension.
66 p = wcsrchr(wszFilePath, L'.');
67 if (!p)
68 {
69 skip("File path has no file extension: %S\n", wszFilePath);
70 return;
71 }
72
73 wcscpy(p, L".dll");
74
75 // Check if the corresponding DLL file exists.
76 if (!FindFirstFileW(wszFilePath, &fd))
77 {
78 skip("My DLL file \"%S\" does not exist!\n", wszFilePath);
79 return;
80 }
81
82 // Change the extension back to .exe and add the parameters.
83 wcscpy(p, L".exe service dummy");
84
85 // Open a handle to the service manager.
86 hSC = OpenSCManagerW(NULL, NULL, SC_MANAGER_ALL_ACCESS);
87 if (!hSC)
88 {
89 skip("OpenSCManagerW failed with error %lu!\n", GetLastError());
90 return;
91 }
92
93 // Try to open the service if we've created it in a previous run.
94 hService = OpenServiceW(hSC, SERVICE_NAME, SERVICE_ALL_ACCESS);
95 if (!hService)
96 {
97 if (GetLastError() == ERROR_SERVICE_DOES_NOT_EXIST)
98 {
99 // Create the service.
100 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);
101 if (!hService)
102 {
103 skip("CreateServiceW failed with error %lu!\n", GetLastError());
104 return;
105 }
106 }
107 else
108 {
109 skip("OpenServiceW failed with error %lu!\n", GetLastError());
110 return;
111 }
112 }
113
114 // Create pipes for the communication with the injected DLL.
115 hCommandPipe = CreateNamedPipeW(COMMAND_PIPE_NAME, PIPE_ACCESS_OUTBOUND, PIPE_TYPE_MESSAGE | PIPE_READMODE_MESSAGE | PIPE_WAIT, 1, 1024, 1024, 10000, NULL);
116 if (hCommandPipe == INVALID_HANDLE_VALUE)
117 {
118 skip("CreateNamedPipeW failed for the command pipe with error %lu!\n", GetLastError());
119 return;
120 }
121
122 hOutputPipe = CreateNamedPipeW(OUTPUT_PIPE_NAME, PIPE_ACCESS_INBOUND, PIPE_TYPE_BYTE | PIPE_READMODE_BYTE | PIPE_WAIT, 1, 1024, 1024, 10000, NULL);
123 if (hOutputPipe == INVALID_HANDLE_VALUE)
124 {
125 skip("CreateNamedPipeW failed for the output pipe with error %lu!\n", GetLastError());
126 return;
127 }
128
129 // Start the service with "service" and a dummy parameter (to distinguish it from a call by rosautotest to localspl_apitest:service)
130 if (!StartServiceW(hService, 0, NULL))
131 {
132 skip("StartServiceW failed with error %lu!\n", GetLastError());
133 return;
134 }
135
136 CloseServiceHandle(hService);
137 CloseServiceHandle(hSC);
138
139 // Wait till it has injected the DLL and the DLL expects its test name.
140 if (!ConnectNamedPipe(hCommandPipe, NULL) && GetLastError() != ERROR_PIPE_CONNECTED)
141 {
142 skip("ConnectNamedPipe failed for the command pipe with error %lu!\n", GetLastError());
143 return;
144 }
145
146 // Send the test name.
147 if (!WriteFile(hCommandPipe, szTestName, strlen(szTestName) + sizeof(char), &cbWritten, NULL))
148 {
149 skip("WriteFile failed with error %lu!\n", GetLastError());
150 return;
151 }
152
153 CloseHandle(hCommandPipe);
154
155 // Now wait for the DLL to connect to the output pipe.
156 if (!ConnectNamedPipe(hOutputPipe, NULL))
157 {
158 skip("ConnectNamedPipe failed for the output pipe with error %lu!\n", GetLastError());
159 return;
160 }
161
162 // Get all testing messages from the pipe and output them on stdout.
163 while (ReadFile(hOutputPipe, szBuffer, sizeof(szBuffer), &cbRead, NULL) && cbRead)
164 fwrite(szBuffer, sizeof(char), cbRead, stdout);
165
166 CloseHandle(hOutputPipe);
167
168 // Prevent the testing framework from outputting a "0 tests executed" line here.
169 ExitProcess(0);
170 }
171
172 START_TEST(fpEnumPrinters)
173 {
174 _RunRemoteTest("fpEnumPrinters");
175 }