Introducing the "ReactOS Automatic Testing Utility", superseding our current syssetup/cmd/dbgprint hack for running automatic regression tests.

Without any parameters, it mostly works the same as our current solution, but all in a standalone application.
Adding the /w parameter will submit all results to the web service committed in my previous commit.

The application would also make it possible to run Wine Tests regularly on a Windows machine and submitting the results.
This would make sure that all Wine tests also pass under Windows.

svn path=/trunk/; revision=38580
This commit is contained in:
Colin Finck 2009-01-05 12:41:34 +00:00
parent 45661165b8
commit d2148478c6
8 changed files with 1325 additions and 0 deletions

View file

@ -13,6 +13,9 @@
<directory name="regtests_by_casper">
<xi:include href="regtests_by_casper/directory.rbuild" />
</directory>
<directory name="rosautotest">
<xi:include href="rosautotest/rosautotest.rbuild" />
</directory>
<directory name="tests">
<xi:include href="tests/directory.rbuild" />
</directory>

326
rostests/rosautotest/main.c Normal file
View file

@ -0,0 +1,326 @@
/*
* PROJECT: ReactOS Automatic Testing Utility
* LICENSE: GNU GPLv2 or any later version as published by the Free Software Foundation
* PURPOSE: Main implementation file
* COPYRIGHT: Copyright 2008-2009 Colin Finck <colin@reactos.org>
*/
#include "precomp.h"
typedef void (WINAPI *GETSYSINFO)(LPSYSTEM_INFO);
APP_OPTIONS AppOptions = {0};
HANDLE hProcessHeap;
PCHAR AuthenticationRequestString = NULL;
PCHAR SystemInfoRequestString = NULL;
/**
* Gets a value from a specified INI file and returns it converted to ASCII.
*
* @param AppName
* The INI section to look in (lpAppName parameter passed to GetPrivateProfileStringW)
*
* @param KeyName
* The key to look for in the specified section (lpKeyName parameter passed to GetPrivateProfileStringW)
*
* @param FileName
* The path to the INI file
*
* @param ReturnedValue
* Pointer to a CHAR pointer, which will receive the read and converted value.
* The caller needs to HeapFree that value manually.
*
* @return
* Returns the string length of the read value (in characters) or zero if we didn't get any value.
*/
static DWORD
IntGetINIValueA(PCWCH AppName, PCWCH KeyName, PCWCH FileName, char** ReturnedValue)
{
DWORD Length;
WCHAR Buffer[2048];
/* Load the value into a temporary Unicode buffer */
Length = GetPrivateProfileStringW(AppName, KeyName, NULL, Buffer, sizeof(Buffer) / sizeof(WCHAR), FileName);
if(!Length)
return 0;
/* Convert the string to ANSI charset */
*ReturnedValue = HeapAlloc(hProcessHeap, 0, Length + 1);
WideCharToMultiByte(CP_ACP, 0, Buffer, Length + 1, *ReturnedValue, Length + 1, NULL, NULL);
return Length;
}
/**
* Gets the username and password hash from the "rosautotest.ini" file if the user enabled submitting the results to the web service.
* The "rosautotest.ini" file should look like this:
*
* [Login]
* UserName=TestMan
* PasswordHash=1234567890abcdef1234567890abcdef
*/
static BOOL
IntGetConfigurationValues()
{
const CHAR PasswordHashProp[] = "&passwordhash=";
const CHAR UserNameProp[] = "&username=";
DWORD DataLength;
DWORD Length;
PCHAR PasswordHash;
PCHAR UserName;
WCHAR ConfigFile[MAX_PATH];
/* We only need this if the results are going to be submitted */
if(!AppOptions.Submit)
return TRUE;
/* Build the path to the configuration file */
Length = GetWindowsDirectoryW(ConfigFile, MAX_PATH);
wcscpy(&ConfigFile[Length], L"\\rosautotest.ini");
/* Check if it exists */
if(GetFileAttributesW(ConfigFile) == INVALID_FILE_ATTRIBUTES)
return FALSE;
/* Get the required length of the authentication request string */
DataLength = sizeof(UserNameProp) - 1;
Length = IntGetINIValueA(L"Login", L"UserName", ConfigFile, &UserName);
if(!Length)
{
StringOut("UserName is missing in the configuration file\n");
return FALSE;
}
/* Some characters might need to be escaped and an escaped character takes 3 bytes */
DataLength += 3 * Length;
DataLength += sizeof(PasswordHashProp) - 1;
Length = IntGetINIValueA(L"Login", L"PasswordHash", ConfigFile, &PasswordHash);
if(!Length)
{
StringOut("PasswordHash is missing in the configuration file\n");
return FALSE;
}
DataLength += 3 * Length;
/* Build the string */
AuthenticationRequestString = HeapAlloc(hProcessHeap, 0, DataLength + 1);
strcpy(AuthenticationRequestString, UserNameProp);
EscapeString(&AuthenticationRequestString[strlen(AuthenticationRequestString)], UserName);
strcat(AuthenticationRequestString, PasswordHashProp);
EscapeString(&AuthenticationRequestString[strlen(AuthenticationRequestString)], PasswordHash);
return TRUE;
}
/**
* Determines on which platform we're running on.
* Prepares the appropriate request strings needed if we want to submit test results to the web service.
*/
static BOOL
IntGetBuildAndPlatform()
{
const CHAR PlatformProp[] = "&platform=";
const CHAR RevisionProp[] = "&revision=";
CHAR BuildNo[BUILDNO_LENGTH];
CHAR Platform[PLATFORM_LENGTH];
CHAR PlatformArchitecture[3];
CHAR ProductType;
DWORD DataLength;
GETSYSINFO GetSysInfo;
HANDLE hKernel32;
OSVERSIONINFOEXW os;
SYSTEM_INFO si;
WCHAR WindowsDirectory[MAX_PATH];
/* Get the build from the define */
_ultoa(KERNEL_VERSION_BUILD_HEX, BuildNo, 10);
/* Check if we are running under ReactOS from the SystemRoot directory */
GetWindowsDirectoryW(WindowsDirectory, MAX_PATH);
if(!_wcsnicmp(&WindowsDirectory[3], L"reactos", 7))
{
/* Yes, we are most-probably under ReactOS */
strcpy(Platform, "reactos");
}
else
{
/* No, then use the info from GetVersionExW */
os.dwOSVersionInfoSize = sizeof(os);
if(!GetVersionExW((LPOSVERSIONINFOW)&os))
{
StringOut("GetVersionExW failed\n");
return FALSE;
}
if(os.dwMajorVersion < 5)
{
StringOut("Application requires at least Windows 2000!\n");
return FALSE;
}
if(os.wProductType == VER_NT_WORKSTATION)
ProductType = 'w';
else
ProductType = 's';
/* Print all necessary identification information into the Platform string */
sprintf(Platform, "%lu.%lu.%lu.%u.%u.%c", os.dwMajorVersion, os.dwMinorVersion, os.dwBuildNumber, os.wServicePackMajor, os.wServicePackMinor, ProductType);
}
/* We also need to know about the processor architecture.
To retrieve this information accurately, check whether "GetNativeSystemInfo" is exported and use it then, otherwise fall back to "GetSystemInfo". */
hKernel32 = GetModuleHandleW(L"KERNEL32.DLL");
GetSysInfo = (GETSYSINFO)GetProcAddress(hKernel32, "GetNativeSystemInfo");
if(!GetSysInfo)
GetSysInfo = (GETSYSINFO)GetProcAddress(hKernel32, "GetSystemInfo");
GetSysInfo(&si);
PlatformArchitecture[0] = '.';
_ultoa(si.wProcessorArchitecture, &PlatformArchitecture[1], 10);
PlatformArchitecture[2] = 0;
strcat(Platform, PlatformArchitecture);
/* Get the required length of the system info request string */
DataLength = sizeof(RevisionProp) - 1;
DataLength += strlen(BuildNo);
DataLength += sizeof(PlatformProp) - 1;
DataLength += strlen(Platform);
/* Now build the string */
SystemInfoRequestString = HeapAlloc(hProcessHeap, 0, DataLength + 1);
strcpy(SystemInfoRequestString, RevisionProp);
strcat(SystemInfoRequestString, BuildNo);
strcat(SystemInfoRequestString, PlatformProp);
strcat(SystemInfoRequestString, Platform);
return TRUE;
}
/**
* Prints the application usage.
*/
static VOID
IntPrintUsage()
{
printf("rosautotest - ReactOS Automatic Testing Utility\n");
printf("Usage: rosautotest [options] [module] [test]\n");
printf(" options:\n");
printf(" /? - Shows this help\n");
printf(" /s - Shut down the system after finishing the tests\n");
printf(" /w - Submit the results to the webservice\n");
printf(" Requires a \"rosautotest.ini\" with valid login data.\n");
printf("\n");
printf(" module:\n");
printf(" The module to be tested (i.e. \"advapi32\")\n");
printf(" If this parameter is specified without any test parameter,\n");
printf(" all tests of the specified module are run.\n");
printf("\n");
printf(" test:\n");
printf(" The test to be run. Needs to be a test of the specified module.\n");
}
/**
* Main entry point
*/
int
wmain(int argc, wchar_t* argv[])
{
int Result = 0;
UINT i;
hProcessHeap = GetProcessHeap();
/* Parse the command line arguments */
for(i = 1; i < (UINT)argc; i++)
{
if(argv[i][0] == '-' || argv[i][0] == '/')
{
switch(argv[i][1])
{
case 's':
AppOptions.Shutdown = TRUE;
break;
case 'w':
AppOptions.Submit = TRUE;
break;
default:
Result = 1;
/* Fall through */
case '?':
IntPrintUsage();
goto End;
}
}
else
{
size_t Length;
/* Which parameter is this? */
if(!AppOptions.Module)
{
/* Copy the parameter */
Length = (wcslen(argv[i]) + 1) * sizeof(WCHAR);
AppOptions.Module = HeapAlloc(hProcessHeap, 0, Length);
memcpy(AppOptions.Module, argv[i], Length);
}
else if(!AppOptions.Test)
{
/* Copy the parameter converted to ASCII */
Length = WideCharToMultiByte(CP_ACP, 0, argv[i], -1, NULL, 0, NULL, NULL);
AppOptions.Test = HeapAlloc(hProcessHeap, 0, Length);
WideCharToMultiByte(CP_ACP, 0, argv[i], -1, AppOptions.Test, Length, NULL, NULL);
}
else
{
Result = 1;
IntPrintUsage();
goto End;
}
}
}
if(!IntGetConfigurationValues() || !IntGetBuildAndPlatform() || !RunWineTests())
{
Result = 1;
goto End;
}
/* For sysreg */
OutputDebugStringA("SYSREG_CHECKPOINT:THIRDBOOT_COMPLETE\n");
End:
/* Cleanup */
if(AppOptions.Module)
HeapFree(hProcessHeap, 0, AppOptions.Module);
if(AppOptions.Test)
HeapFree(hProcessHeap, 0, AppOptions.Test);
if(AuthenticationRequestString)
HeapFree(hProcessHeap, 0, AuthenticationRequestString);
if(SystemInfoRequestString)
HeapFree(hProcessHeap, 0, SystemInfoRequestString);
/* Shut down the system if requested */
if(AppOptions.Shutdown && !ShutdownSystem())
Result = 1;
return Result;
}

View file

@ -0,0 +1,81 @@
/* Includes */
#include <stdio.h>
#include <windows.h>
#include <reason.h>
#include <wininet.h>
#include <reactos/buildno.h>
/* Defines */
#define BUFFER_BLOCKSIZE 2048
#define BUILDNO_LENGTH 10
#define PLATFORM_LENGTH 25
#define SERVER_HOSTNAME L"localhost"
#define SERVER_FILE L"testman/webservice/"
/* Enums */
typedef enum _TESTTYPES
{
WineTest
}
TESTTYPES;
/* Structs */
typedef struct _APP_OPTIONS
{
BOOL Shutdown;
BOOL Submit;
PWSTR Module;
PCHAR Test;
}
APP_OPTIONS, *PAPP_OPTIONS;
typedef struct _WINE_GETSUITEID_DATA
{
PCHAR Module;
PCHAR Test;
}
WINE_GETSUITEID_DATA, *PWINE_GETSUITEID_DATA;
typedef struct _GENERAL_SUBMIT_DATA
{
PCHAR TestID;
PCHAR SuiteID;
}
GENERAL_SUBMIT_DATA, *PGENERAL_SUBMIT_DATA;
typedef struct _WINE_SUBMIT_DATA
{
GENERAL_SUBMIT_DATA General;
PCHAR Log;
}
WINE_SUBMIT_DATA, *PWINE_SUBMIT_DATA;
typedef struct _GENERAL_FINISH_DATA
{
PCHAR TestID;
}
GENERAL_FINISH_DATA, *PGENERAL_FINISH_DATA;
/* main.c */
extern APP_OPTIONS AppOptions;
extern HANDLE hProcessHeap;
extern PCHAR AuthenticationRequestString;
extern PCHAR SystemInfoRequestString;
/* shutdown.c */
BOOL ShutdownSystem();
/* tools.c */
VOID EscapeString(PCHAR Output, PCHAR Input);
VOID StringOut(PCHAR String);
/* webservice.c */
PCHAR GetTestID(TESTTYPES TestType);
PCHAR GetSuiteID(TESTTYPES TestType, const PVOID TestData);
BOOL Submit(TESTTYPES TestType, const PVOID TestData);
BOOL Finish(TESTTYPES TestType, const PVOID TestData);
/* winetests.c */
BOOL RunWineTests();

View file

@ -0,0 +1,15 @@
<?xml version="1.0"?>
<!DOCTYPE module SYSTEM "../../../tools/rbuild/project.dtd">
<module name="rosautotest" type="win32cui" installbase="system32" installname="rosautotest.exe" unicode="yes">
<include base="rosautotest">.</include>
<library>advapi32</library>
<library>kernel32</library>
<library>user32</library>
<library>wininet</library>
<file>main.c</file>
<file>shutdown.c</file>
<file>tools.c</file>
<file>webservice.c</file>
<file>winetests.c</file>
<pch>precomp.h</pch>
</module>

View file

@ -0,0 +1,52 @@
/*
* PROJECT: ReactOS Automatic Testing Utility
* LICENSE: GNU GPLv2 or any later version as published by the Free Software Foundation
* PURPOSE: Helper function for shutting down the system
* COPYRIGHT: Copyright 2008-2009 Colin Finck <colin@reactos.org>
*/
#include "precomp.h"
/**
* Shuts down the system.
*
* @return
* TRUE if everything went well, FALSE if there was a problem while trying to shut down the system.
*/
BOOL ShutdownSystem()
{
HANDLE hToken;
TOKEN_PRIVILEGES Privileges;
if (!OpenProcessToken(GetCurrentProcess(), TOKEN_ADJUST_PRIVILEGES, &hToken))
{
StringOut("OpenProcessToken failed\n");
return FALSE;
}
/* Get the LUID for the Shutdown privilege */
if (!LookupPrivilegeValueW(NULL, SE_SHUTDOWN_NAME, &Privileges.Privileges[0].Luid))
{
StringOut("LookupPrivilegeValue failed\n");
return FALSE;
}
/* Assign the Shutdown privilege to our process */
Privileges.PrivilegeCount = 1;
Privileges.Privileges[0].Attributes = SE_PRIVILEGE_ENABLED;
if (!AdjustTokenPrivileges(hToken, FALSE, &Privileges, 0, NULL, NULL))
{
StringOut("AdjustTokenPrivileges failed\n");
return FALSE;
}
/* Finally shut down the system */
if(!ExitWindowsEx(EWX_POWEROFF, SHTDN_REASON_MAJOR_OTHER | SHTDN_REASON_MINOR_OTHER | SHTDN_REASON_FLAG_PLANNED))
{
StringOut("ExitWindowsEx failed\n");
return FALSE;
}
return TRUE;
}

View file

@ -0,0 +1,57 @@
/*
* PROJECT: ReactOS Automatic Testing Utility
* LICENSE: GNU GPLv2 or any later version as published by the Free Software Foundation
* PURPOSE: Various helper functions
* COPYRIGHT: Copyright 2008-2009 Colin Finck <colin@reactos.org>
*/
#include "precomp.h"
/**
* Escapes a string according to RFC 1738.
* Required for passing parameters to the web service.
*
* @param Output
* Pointer to a CHAR array, which will receive the escaped string.
* The output buffer must be large enough to hold the full escaped string. You're on the safe side
* if you make the output buffer three times as large as the input buffer.
*
* @param Input
* Pointer to a CHAR array, which contains the input buffer to escape.
*/
VOID
EscapeString(PCHAR Output, PCHAR Input)
{
const CHAR HexCharacters[] = "0123456789ABCDEF";
do
{
if(strchr("ABCDEFGHIJKLMNOPQRSTUVWXYZabcdefghijklmnopqrstuvwxyz0123456789-_.~", *Input) )
{
/* It's a character we don't need to escape, just add it to the output string */
*Output++ = *Input;
}
else
{
/* We need to escape this character */
*Output++ = '%';
*Output++ = HexCharacters[((UCHAR)*Input >> 4) % 16];
*Output++ = HexCharacters[(UCHAR)*Input % 16];
}
} while(*++Input);
*Output = 0;
}
/**
* Outputs a string through the standard output and the debug output.
*
* @param String
* The string to output
*/
VOID
StringOut(PCHAR String)
{
printf(String);
OutputDebugStringA(String);
}

View file

@ -0,0 +1,417 @@
/*
* PROJECT: ReactOS Automatic Testing Utility
* LICENSE: GNU GPLv2 or any later version as published by the Free Software Foundation
* PURPOSE: Submitting test results to the Web Service
* COPYRIGHT: Copyright 2008-2009 Colin Finck <colin@reactos.org>
*/
#include "precomp.h"
static const CHAR ActionProp[] = "action=";
static const CHAR TestIDProp[] = "&testid=";
static const CHAR TestTypeProp[] = "&testtype=";
static const CHAR WineTestType[] = "wine";
/**
* Sends data to the ReactOS Web Test Manager web service.
*
* @param Data
* Pointer to a CHAR pointer, which contains the data to submit as HTTP POST data.
* The buffer behind this pointer had to be allocated with HeapAlloc.
* Returns the data received by the web service after the call.
*
* @param DataLength
* Pointer to a DWORD, which contains the length of the data to submit (in bytes).
* Returns the length of the data received by the web service after the call (in bytes).
*
* @return
* TRUE if everything went well, FALSE if an error occured while submitting the request.
* In case of an error, the function will output an appropriate error message through StringOut.
*/
static BOOL
IntDoRequest(char** Data, PDWORD DataLength)
{
const WCHAR Headers[] = L"Content-Type: application/x-www-form-urlencoded";
HINTERNET hHTTP;
HINTERNET hHTTPRequest;
HINTERNET hInet;
/* Establish an internet connection to the "testman" server */
hInet = InternetOpenW(L"rosautotest", INTERNET_OPEN_TYPE_PRECONFIG, NULL, NULL, 0);
if(!hInet)
{
StringOut("InternetOpenW failed\n");
return FALSE;
}
hHTTP = InternetConnectW(hInet, SERVER_HOSTNAME, INTERNET_DEFAULT_HTTP_PORT, NULL, NULL, INTERNET_SERVICE_HTTP, 0, 0);
if(!hHTTP)
{
StringOut("InternetConnectW failed\n");
return FALSE;
}
/* Post our test results to the web service */
hHTTPRequest = HttpOpenRequestW(hHTTP, L"POST", SERVER_FILE, NULL, NULL, NULL, INTERNET_FLAG_NO_COOKIES | INTERNET_FLAG_RELOAD | INTERNET_FLAG_NO_CACHE_WRITE, 0);
if(!hHTTPRequest)
{
StringOut("HttpOpenRequestW failed\n");
return FALSE;
}
if(!HttpSendRequestW(hHTTPRequest, Headers, wcslen(Headers), *Data, *DataLength))
{
StringOut("HttpSendRequestW failed\n");
return FALSE;
}
HeapFree(hProcessHeap, 0, *Data);
/* Get the response */
if(!InternetQueryDataAvailable(hHTTPRequest, DataLength, 0, 0))
{
StringOut("InternetQueryDataAvailable failed\n");
return FALSE;
}
*Data = HeapAlloc(hProcessHeap, 0, *DataLength + 1);
if(!InternetReadFile(hHTTPRequest, *Data, *DataLength, DataLength))
{
StringOut("InternetReadFile failed\n");
return FALSE;
}
(*Data)[*DataLength] = 0;
InternetCloseHandle(hHTTPRequest);
InternetCloseHandle(hHTTP);
InternetCloseHandle(hInet);
return TRUE;
}
/**
* Determines whether a string contains entirely numeric values.
*
* @param Input
* The string to check.
*
* @return
* TRUE if the string is entirely numeric, FALSE otherwise.
*/
static BOOL
IsNumber(PCHAR Input)
{
do
{
if(!isdigit(*Input))
return FALSE;
++Input;
}
while(*Input);
return TRUE;
}
/**
* Requests a Test ID from the web service for our test run.
*
* @param TestType
* Value from the TESTTYPES enum indicating the type of test we are about to submit.
*
* @return
* Returns the Test ID as a CHAR array if successful or NULL otherwise.
*/
PCHAR
GetTestID(TESTTYPES TestType)
{
const CHAR GetTestIDAction[] = "gettestid";
DWORD DataLength;
PCHAR Data;
/* Build the full request string */
DataLength = sizeof(ActionProp) - 1 + sizeof(GetTestIDAction) - 1;
DataLength += strlen(AuthenticationRequestString) + strlen(SystemInfoRequestString);
DataLength += sizeof(TestTypeProp) - 1;
switch(TestType)
{
case WineTest:
DataLength += sizeof(WineTestType) - 1;
break;
}
Data = HeapAlloc(hProcessHeap, 0, DataLength + 1);
strcpy(Data, ActionProp);
strcat(Data, GetTestIDAction);
strcat(Data, AuthenticationRequestString);
strcat(Data, SystemInfoRequestString);
strcat(Data, TestTypeProp);
switch(TestType)
{
case WineTest:
strcat(Data, WineTestType);
break;
}
if(!IntDoRequest(&Data, &DataLength))
return NULL;
/* Verify that this is really a number */
if(!IsNumber(Data))
{
StringOut("Expected Test ID, but received:\n");
StringOut(Data);
HeapFree(hProcessHeap, 0, Data);
return NULL;
}
return Data;
}
/**
* Requests a Suite ID from the web service for our module/test combination.
*
* @param TestType
* Value from the TESTTYPES enum indicating the type of test we are about to submit.
*
* @param TestData
* Pointer to a *_GETSUITEID_DATA structure appropriate for our selected test type.
* Contains other input information for this request.
*
* @return
* Returns the Suite ID as a CHAR array if successful or NULL otherwise.
*/
PCHAR
GetSuiteID(TESTTYPES TestType, const PVOID TestData)
{
const CHAR GetSuiteIDAction[] = "getsuiteid";
const CHAR ModuleProp[] = "&module=";
const CHAR TestProp[] = "&test=";
DWORD DataLength;
PCHAR Data;
PWINE_GETSUITEID_DATA WineData;
DataLength = sizeof(ActionProp) - 1 + sizeof(GetSuiteIDAction) - 1;
DataLength += strlen(AuthenticationRequestString);
DataLength += sizeof(TestTypeProp) - 1;
switch(TestType)
{
case WineTest:
DataLength += sizeof(WineTestType) - 1;
WineData = (PWINE_GETSUITEID_DATA)TestData;
DataLength += sizeof(ModuleProp) - 1;
DataLength += strlen(WineData->Module);
DataLength += sizeof(TestProp) - 1;
DataLength += strlen(WineData->Test);
break;
}
Data = HeapAlloc(hProcessHeap, 0, DataLength + 1);
strcpy(Data, ActionProp);
strcat(Data, GetSuiteIDAction);
strcat(Data, AuthenticationRequestString);
strcat(Data, TestTypeProp);
switch(TestType)
{
case WineTest:
strcat(Data, WineTestType);
/* Stupid GCC and MSVC: WineData is already initialized above, still it's reported as a potentially uninitialized variable :-( */
WineData = (PWINE_GETSUITEID_DATA)TestData;
strcat(Data, ModuleProp);
strcat(Data, WineData->Module);
strcat(Data, TestProp);
strcat(Data, WineData->Test);
break;
}
if(!IntDoRequest(&Data, &DataLength))
return NULL;
/* Verify that this is really a number */
if(!IsNumber(Data))
{
StringOut("Expected Suite ID, but received:\n");
StringOut(Data);
HeapFree(hProcessHeap, 0, Data);
return NULL;
}
return Data;
}
/**
* Submits the result of one test call to the web service.
*
* @param TestType
* Value from the TESTTYPES enum indicating the type of test we are about to submit.
*
* @param TestData
* Pointer to a *_SUBMIT_DATA structure appropriate for our selected test type.
* Contains other input information for this request.
*
* @return
* TRUE if everything went well, FALSE otherwise.
*/
BOOL
Submit(TESTTYPES TestType, const PVOID TestData)
{
const CHAR SubmitAction[] = "submit";
const CHAR SuiteIDProp[] = "&suiteid=";
const CHAR LogProp[] = "&log=";
DWORD DataLength;
PCHAR Data;
PCHAR pData;
PGENERAL_SUBMIT_DATA GeneralData;
PWINE_SUBMIT_DATA WineData;
/* Compute the full length of the POST data */
DataLength = sizeof(ActionProp) - 1 + sizeof(SubmitAction) - 1;
DataLength += strlen(AuthenticationRequestString);
GeneralData = (PGENERAL_SUBMIT_DATA)TestData;
DataLength += sizeof(TestIDProp) - 1;
DataLength += strlen(GeneralData->TestID);
DataLength += sizeof(SuiteIDProp) - 1;
DataLength += strlen(GeneralData->SuiteID);
/* The rest of the POST data depends on the test type */
DataLength += sizeof(TestTypeProp) - 1;
switch(TestType)
{
case WineTest:
DataLength += sizeof(WineTestType) - 1;
WineData = (PWINE_SUBMIT_DATA)TestData;
DataLength += sizeof(LogProp) - 1;
DataLength += 3 * strlen(WineData->Log);
break;
}
/* Now collect all the POST data */
Data = HeapAlloc(hProcessHeap, 0, DataLength + 1);
strcpy(Data, ActionProp);
strcat(Data, SubmitAction);
strcat(Data, AuthenticationRequestString);
strcat(Data, TestIDProp);
strcat(Data, GeneralData->TestID);
strcat(Data, SuiteIDProp);
strcat(Data, GeneralData->SuiteID);
strcat(Data, TestTypeProp);
switch(TestType)
{
case WineTest:
strcat(Data, WineTestType);
/* Stupid GCC and MSVC: WineData is already initialized above, still it's reported as a potentially uninitialized variable :-( */
WineData = (PWINE_SUBMIT_DATA)TestData;
strcat(Data, LogProp);
pData = Data + strlen(Data);
EscapeString(pData, WineData->Log);
break;
}
/* DataLength still contains the maximum length of the buffer, but not the actual data length we need for the request.
Determine that one now. */
DataLength = strlen(Data);
/* Send all the stuff */
if(!IntDoRequest(&Data, &DataLength))
return FALSE;
/* Output the response */
StringOut("The server responded:\n");
StringOut(Data);
StringOut("\n");
if(!strcmp(Data, "OK"))
return TRUE;
return FALSE;
}
/**
* Finishes a test run for the web service.
*
* @param TestType
* Value from the TESTTYPES enum indicating the type of test we are about to submit.
*
* @param TestData
* Pointer to a *_FINISH_DATA structure appropriate for our selected test type.
* Contains other input information for this request.
*
* @return
* TRUE if everything went well, FALSE otherwise.
*/
BOOL
Finish(TESTTYPES TestType, const PVOID TestData)
{
const CHAR FinishAction[] = "finish";
DWORD DataLength;
PCHAR Data;
PGENERAL_FINISH_DATA GeneralData;
/* Build the full request string */
DataLength = sizeof(ActionProp) - 1 + sizeof(FinishAction) - 1;
DataLength += strlen(AuthenticationRequestString);
GeneralData = (PGENERAL_FINISH_DATA)TestData;
DataLength += sizeof(TestIDProp) - 1;
DataLength += strlen(GeneralData->TestID);
DataLength += sizeof(TestTypeProp) - 1;
switch(TestType)
{
case WineTest:
DataLength += sizeof(WineTestType) - 1;
break;
}
Data = HeapAlloc(hProcessHeap, 0, DataLength + 1);
strcpy(Data, ActionProp);
strcat(Data, FinishAction);
strcat(Data, AuthenticationRequestString);
strcat(Data, TestIDProp);
strcat(Data, GeneralData->TestID);
strcat(Data, TestTypeProp);
switch(TestType)
{
case WineTest:
strcat(Data, WineTestType);
break;
}
if(!IntDoRequest(&Data, &DataLength))
return FALSE;
if(!strcmp(Data, "OK"))
return TRUE;
return FALSE;
}

View file

@ -0,0 +1,374 @@
/*
* PROJECT: ReactOS Automatic Testing Utility
* LICENSE: GNU GPLv2 or any later version as published by the Free Software Foundation
* PURPOSE: Running Wine Tests automatically
* COPYRIGHT: Copyright 2008-2009 Colin Finck <colin@reactos.org>
*/
#include "precomp.h"
/**
* Runs a specific test for a specific module.
* If we get results for a test, they are submitted to the Web Service.
*
* @param CommandLine
* Command line to run (should be the path to a module's test suite together with a parameter for the specified test)
*
* @param hReadPipe
* Handle to the Read Pipe set up in RunWineTests.
*
* @param StartupInfo
* Pointer to the StartupInfo structure set up in RunWineTests.
*
* @param GetSuiteIDData
* Pointer to the GetSuiteIDData structure set up in IntRunModuleTests.
*
* @param SubmitData
* Pointer to the SubmitData structure set up in RunWineTests.
*
* @return
* TRUE if everything went well, FALSE otherwise.
*/
static BOOL
IntRunTest(PWSTR CommandLine, HANDLE hReadPipe, LPSTARTUPINFOW StartupInfo, PWINE_GETSUITEID_DATA GetSuiteIDData, PWINE_SUBMIT_DATA SubmitData)
{
BOOL BreakLoop = FALSE;
DWORD BytesAvailable;
DWORD LogAvailable = 0;
DWORD LogLength = 0;
DWORD LogPosition = 0;
DWORD Temp;
PCHAR Buffer;
PROCESS_INFORMATION ProcessInfo;
if(AppOptions.Submit)
{
/* Allocate one block for the log */
SubmitData->Log = HeapAlloc(hProcessHeap, 0, BUFFER_BLOCKSIZE);
LogAvailable = BUFFER_BLOCKSIZE;
LogLength = BUFFER_BLOCKSIZE;
}
/* Execute the test */
StringOut("Running Wine Test, Module: ");
StringOut(GetSuiteIDData->Module);
StringOut(", Test: ");
StringOut(GetSuiteIDData->Test);
StringOut("\n");
if(!CreateProcessW(NULL, CommandLine, NULL, NULL, TRUE, NORMAL_PRIORITY_CLASS, NULL, NULL, StartupInfo, &ProcessInfo))
{
StringOut("CreateProcessW for running the test failed\n");
return FALSE;
}
/* Receive all the data from the pipe */
do
{
/* When the application finished, make sure that we peek the pipe one more time, so that we get all data.
If the following condition would be the while() condition, we might hit a race condition:
- We check for data with PeekNamedPipe -> no data available
- The application outputs its data and finishes
- WaitForSingleObject reports that the application has finished and we break the loop without receiving any data
*/
if(WaitForSingleObject(ProcessInfo.hProcess, 0) == WAIT_OBJECT_0)
BreakLoop = TRUE;
if(!PeekNamedPipe(hReadPipe, NULL, 0, NULL, &BytesAvailable, NULL))
{
StringOut("PeekNamedPipe failed for the test run\n");
return FALSE;
}
if(BytesAvailable)
{
/* There is data, so get it and output it */
Buffer = HeapAlloc(hProcessHeap, 0, BytesAvailable + 1);
if(!ReadFile(hReadPipe, Buffer, BytesAvailable, &Temp, NULL))
{
StringOut("ReadFile failed for the test run\n");
return FALSE;
}
/* Output all test output through StringOut, even while the test is still running */
Buffer[BytesAvailable] = 0;
StringOut(Buffer);
if(AppOptions.Submit)
{
/* Also store it in the buffer */
if(BytesAvailable > LogAvailable)
{
/* Allocate enough new blocks to hold all available data */
Temp = ((BytesAvailable - LogAvailable) / BUFFER_BLOCKSIZE + 1) * BUFFER_BLOCKSIZE;
LogAvailable += Temp;
LogLength += Temp;
SubmitData->Log = HeapReAlloc(hProcessHeap, 0, SubmitData->Log, LogLength);
}
memcpy(&SubmitData->Log[LogPosition], Buffer, BytesAvailable);
LogPosition += BytesAvailable;
LogAvailable -= BytesAvailable;
}
HeapFree(hProcessHeap, 0, Buffer);
}
}
while(!BreakLoop);
if(AppOptions.Submit)
{
SubmitData->Log[LogLength - LogAvailable] = 0;
/* If we got any output, submit it to the web service */
if(*SubmitData->Log)
{
/* We don't want to waste any ID's, so only request them if we can be sure that we have results to submit. */
/* Get a Test ID if we don't have one yet */
if(!SubmitData->General.TestID)
{
SubmitData->General.TestID = GetTestID(WineTest);
if(!SubmitData->General.TestID)
return FALSE;
}
/* Get a Suite ID for this combination */
SubmitData->General.SuiteID = GetSuiteID(WineTest, GetSuiteIDData);
if(!SubmitData->General.SuiteID)
return FALSE;
/* Submit the stuff */
Submit(WineTest, SubmitData);
/* Cleanup */
HeapFree(hProcessHeap, 0, SubmitData->General.SuiteID);
}
/* Cleanup */
HeapFree(hProcessHeap, 0, SubmitData->Log);
}
StringOut("\n\n");
return TRUE;
}
/**
* Runs the desired tests for a specified module.
*
* @param File
* The file name of the module's test suite.
*
* @param FilePath
* The full path to the file of the module's test suite.
*
* @param hReadPipe
* Handle to the Read Pipe set up in RunWineTests.
*
* @param StartupInfo
* Pointer to the StartupInfo structure set up in RunWineTests.
*
* @param SubmitData
* Pointer to the SubmitData structure set up in RunWineTests.
*
* @return
* TRUE if everything went well, FALSE otherwise.
*/
static BOOL
IntRunModuleTests(PWSTR File, PWSTR FilePath, HANDLE hReadPipe, LPSTARTUPINFOW StartupInfo, PWINE_SUBMIT_DATA SubmitData)
{
DWORD BytesAvailable;
DWORD Length;
DWORD Temp;
PCHAR Buffer;
PCHAR pStart;
PCHAR pEnd;
PROCESS_INFORMATION ProcessInfo;
size_t FilePosition;
WINE_GETSUITEID_DATA GetSuiteIDData;
/* Build the full command line */
FilePosition = wcslen(FilePath);
FilePath[FilePosition++] = ' ';
FilePath[FilePosition] = 0;
wcscat(FilePath, L"--list");
/* Store the tested module name */
Length = wcschr(File, L'_') - File;
GetSuiteIDData.Module = HeapAlloc(hProcessHeap, 0, Length + 1);
WideCharToMultiByte(CP_ACP, 0, File, Length, GetSuiteIDData.Module, Length, NULL, NULL);
GetSuiteIDData.Module[Length] = 0;
/* Start the process for getting all available tests */
if(!CreateProcessW(NULL, FilePath, NULL, NULL, TRUE, NORMAL_PRIORITY_CLASS, NULL, NULL, StartupInfo, &ProcessInfo))
{
StringOut("CreateProcessW for getting the available tests failed\n");
return FALSE;
}
/* Wait till this process ended */
if(WaitForSingleObject(ProcessInfo.hProcess, INFINITE) == WAIT_FAILED)
{
StringOut("WaitForSingleObject failed for the test list\n");
return FALSE;
}
/* Read the output data into a buffer */
if(!PeekNamedPipe(hReadPipe, NULL, 0, NULL, &BytesAvailable, NULL))
{
StringOut("PeekNamedPipe failed for the test list\n");
return FALSE;
}
Buffer = HeapAlloc(hProcessHeap, 0, BytesAvailable);
if(!ReadFile(hReadPipe, Buffer, BytesAvailable, &Temp, NULL))
{
StringOut("ReadFile failed\n");
return FALSE;
}
/* Jump to the first available test */
pStart = strchr(Buffer, '\n');
pStart += 5;
while(pStart < (Buffer + BytesAvailable))
{
/* Get start and end of this test name */
pEnd = pStart;
while(*pEnd != '\r')
++pEnd;
/* Store the test name */
GetSuiteIDData.Test = HeapAlloc(hProcessHeap, 0, pEnd - pStart + 1);
memcpy(GetSuiteIDData.Test, pStart, pEnd - pStart);
GetSuiteIDData.Test[pEnd - pStart] = 0;
/* If the user gave us a test to run, we check whether the module's test suite really provides this test. */
if(!AppOptions.Test || (AppOptions.Test && !strcmp(AppOptions.Test, GetSuiteIDData.Test)))
{
/* Build the command line for this test */
Length = MultiByteToWideChar(CP_ACP, 0, pStart, pEnd - pStart, NULL, 0);
MultiByteToWideChar(CP_ACP, 0, pStart, pEnd - pStart, &FilePath[FilePosition], Length * sizeof(WCHAR));
FilePath[FilePosition + Length] = 0;
if(!IntRunTest(FilePath, hReadPipe, StartupInfo, &GetSuiteIDData, SubmitData))
return FALSE;
}
/* Cleanup */
HeapFree(hProcessHeap, 0, GetSuiteIDData.Test);
/* Move to the next test */
pStart = pEnd + 6;
}
/* Cleanup */
HeapFree(hProcessHeap, 0, GetSuiteIDData.Module);
HeapFree(hProcessHeap, 0, Buffer);
return TRUE;
}
/**
* Runs the Wine tests according to the options specified by the parameters.
*
* @return
* TRUE if everything went well, FALSE otherwise.
*/
BOOL
RunWineTests()
{
GENERAL_FINISH_DATA FinishData;
HANDLE hFind;
HANDLE hReadPipe;
HANDLE hWritePipe;
SECURITY_ATTRIBUTES SecurityAttributes;
STARTUPINFOW StartupInfo = {0};
size_t PathPosition;
WCHAR FilePath[MAX_PATH];
WIN32_FIND_DATAW fd;
WINE_SUBMIT_DATA SubmitData = { {0} };
/* Create a pipe for getting the output of the tests */
SecurityAttributes.nLength = sizeof(SecurityAttributes);
SecurityAttributes.bInheritHandle = TRUE;
SecurityAttributes.lpSecurityDescriptor = NULL;
if(!CreatePipe(&hReadPipe, &hWritePipe, &SecurityAttributes, 0))
{
StringOut("CreatePipe failed\n");
return FALSE;
}
StartupInfo.cb = sizeof(StartupInfo);
StartupInfo.dwFlags = STARTF_USESTDHANDLES;
StartupInfo.hStdOutput = hWritePipe;
/* Build the path for finding the tests */
if(GetWindowsDirectoryW(FilePath, MAX_PATH) > MAX_PATH - 60)
{
StringOut("Windows directory path is too long\n");
return FALSE;
}
wcscat(FilePath, L"\\bin\\");
PathPosition = wcslen(FilePath);
if(AppOptions.Module)
{
/* Find a test belonging to this module */
wcscat(FilePath, AppOptions.Module);
wcscat(FilePath, L"_*.exe");
}
else
{
/* Find all tests */
wcscat(FilePath, L"*.exe");
}
hFind = FindFirstFileW(FilePath, &fd);
if(hFind == INVALID_HANDLE_VALUE)
{
StringOut("FindFirstFileW failed\n");
return FALSE;
}
/* Run the tests */
do
{
/* Build the full path to the test suite */
wcscpy(&FilePath[PathPosition], fd.cFileName);
/* Run it */
if(!IntRunModuleTests(fd.cFileName, FilePath, hReadPipe, &StartupInfo, &SubmitData))
return FALSE;
}
while(FindNextFileW(hFind, &fd));
/* Cleanup */
FindClose(hFind);
if(AppOptions.Submit && SubmitData.General.TestID)
{
/* We're done with the tests, so close this test run */
FinishData.TestID = SubmitData.General.TestID;
if(!Finish(WineTest, &FinishData))
return FALSE;
/* Cleanup */
HeapFree(hProcessHeap, 0, FinishData.TestID);
}
CloseHandle(hReadPipe);
CloseHandle(hWritePipe);
return TRUE;
}