reactos/rostests/apitests/localspl/tests.c
Colin Finck f2a66788f8 [LOCALSPL_APITEST]
Write an API-Test for localspl.dll. As the original localspl.dll from Windows Server 2003 relies on proper initialization inside spoolsv.exe, we cannot test it standalone as usual.
To make testing possible anyway, this program basically does four things:

  - Injecting our testing code into spoolsv.exe.
  - Registering and running us as a service in the SYSTEM security context like spoolsv.exe, so that injection is possible at all.
  - Sending the test name and receiving the console output over named pipes.
  - Redirecting the received console output to stdout again, so it looks and feels like a standard API-Test.

Nevertheless, the testing code in fpEnumPrinters.c is still written like a usual test. The known ok(), skip(), etc. macros can be used as usual, their output is just redirected through the named pipes.
Thanks to Thomas for giving me the tip about injecting code into spoolsv! :)

svn path=/branches/colins-printing-for-freedom/; revision=68080
2015-06-08 17:15:44 +00:00

176 lines
6 KiB
C

/*
* PROJECT: ReactOS Local Spooler API Tests
* LICENSE: GNU GPLv2 or any later version as published by the Free Software Foundation
* PURPOSE: Test list
* COPYRIGHT: Copyright 2015 Colin Finck <colin@reactos.org>
*/
/*
* The original localspl.dll from Windows Server 2003 is not easily testable.
* It relies on a proper initialization inside spoolsv.exe, so we can't just load it in an API-Test as usual.
* See https://www.reactos.org/pipermail/ros-dev/2015-June/017395.html for more information.
*
* To make testing possible anyway, this program basically does four things:
* - Injecting our testing code into spoolsv.exe.
* - Registering and running us as a service in the SYSTEM security context like spoolsv.exe, so that injection is possible at all.
* - Sending the test name and receiving the console output over named pipes.
* - Redirecting the received console output to stdout again, so it looks and feels like a standard API-Test.
*
* To simplify debugging of the injected code, it is entirely separated into a DLL file localspl_apitest.dll.
* What we actually inject is a LoadLibraryW call, so that the DLL is loaded gracefully without any hacks.
* Therefore, you can just attach your debugger to the spoolsv.exe process and set breakpoints on the localspl_apitest.dll code.
*/
#include <apitest.h>
#define WIN32_NO_STATUS
#include <stdio.h>
#include <stdlib.h>
#include <windef.h>
#include <winbase.h>
#include <wingdi.h>
#include <winreg.h>
#include <winsvc.h>
#include <winspool.h>
#include <winsplp.h>
#include "localspl_apitest.h"
static void
_RunRemoteTest(const char* szTestName)
{
char szBuffer[1024];
DWORD cbRead;
DWORD cbWritten;
HANDLE hCommandPipe;
HANDLE hOutputPipe;
PWSTR p;
SC_HANDLE hSC;
SC_HANDLE hService;
WCHAR wszFilePath[MAX_PATH + 20];
WIN32_FIND_DATAW fd;
// Do a dummy EnumPrintersW call.
// This guarantees that the Spooler Service has actually loaded localspl.dll, which is a requirement for our injected DLL to work properly.
EnumPrintersW(PRINTER_ENUM_LOCAL | PRINTER_ENUM_NAME, NULL, 1, NULL, 0, &cbRead, &cbWritten);
// Get the full path to our EXE file.
if (!GetModuleFileNameW(NULL, wszFilePath, MAX_PATH))
{
skip("GetModuleFileNameW failed with error %lu!\n", GetLastError());
return;
}
// Replace the extension.
p = wcsrchr(wszFilePath, L'.');
if (!p)
{
skip("File path has no file extension: %S\n", wszFilePath);
return;
}
wcscpy(p, L".dll");
// Check if the corresponding DLL file exists.
if (!FindFirstFileW(wszFilePath, &fd))
{
skip("My DLL file \"%S\" does not exist!\n", wszFilePath);
return;
}
// Change the extension back to .exe and add the parameters.
wcscpy(p, L".exe service dummy");
// Open a handle to the service manager.
hSC = OpenSCManagerW(NULL, NULL, SC_MANAGER_ALL_ACCESS);
if (!hSC)
{
skip("OpenSCManagerW failed with error %lu!\n", GetLastError());
return;
}
// Try to open the service if we've created it in a previous run.
hService = OpenServiceW(hSC, SERVICE_NAME, SERVICE_ALL_ACCESS);
if (!hService)
{
if (GetLastError() == ERROR_SERVICE_DOES_NOT_EXIST)
{
// Create the service.
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);
if (!hService)
{
skip("CreateServiceW failed with error %lu!\n", GetLastError());
return;
}
}
else
{
skip("OpenServiceW failed with error %lu!\n", GetLastError());
return;
}
}
// Create pipes for the communication with the injected DLL.
hCommandPipe = CreateNamedPipeW(COMMAND_PIPE_NAME, PIPE_ACCESS_OUTBOUND, PIPE_TYPE_MESSAGE | PIPE_READMODE_MESSAGE | PIPE_WAIT, 1, 1024, 1024, 10000, NULL);
if (hCommandPipe == INVALID_HANDLE_VALUE)
{
skip("CreateNamedPipeW failed for the command pipe with error %lu!\n", GetLastError());
return;
}
hOutputPipe = CreateNamedPipeW(OUTPUT_PIPE_NAME, PIPE_ACCESS_INBOUND, PIPE_TYPE_BYTE | PIPE_READMODE_BYTE | PIPE_WAIT, 1, 1024, 1024, 10000, NULL);
if (hOutputPipe == INVALID_HANDLE_VALUE)
{
skip("CreateNamedPipeW failed for the output pipe with error %lu!\n", GetLastError());
return;
}
// Start the service with "service" and a dummy parameter (to distinguish it from a call by rosautotest to localspl_apitest:service)
if (!StartServiceW(hService, 0, NULL))
{
skip("StartServiceW failed with error %lu!\n", GetLastError());
return;
}
CloseServiceHandle(hService);
CloseServiceHandle(hSC);
// Wait till it has injected the DLL and the DLL expects its test name.
if (!ConnectNamedPipe(hCommandPipe, NULL) && GetLastError() != ERROR_PIPE_CONNECTED)
{
skip("ConnectNamedPipe failed for the command pipe with error %lu!\n", GetLastError());
return;
}
// Send the test name.
if (!WriteFile(hCommandPipe, szTestName, strlen(szTestName) + sizeof(char), &cbWritten, NULL))
{
skip("WriteFile failed with error %lu!\n", GetLastError());
return;
}
CloseHandle(hCommandPipe);
// Now wait for the DLL to connect to the output pipe.
if (!ConnectNamedPipe(hOutputPipe, NULL))
{
skip("ConnectNamedPipe failed for the output pipe with error %lu!\n", GetLastError());
return;
}
// Get all testing messages from the pipe and output them on stdout.
while (ReadFile(hOutputPipe, szBuffer, sizeof(szBuffer), &cbRead, NULL) && cbRead)
fwrite(szBuffer, sizeof(char), cbRead, stdout);
CloseHandle(hOutputPipe);
// Prevent the testing framework from outputting a "0 tests executed" line here.
ExitProcess(0);
}
START_TEST(fpEnumPrinters)
{
_RunRemoteTest("fpEnumPrinters");
}