php-src/sapi/isapi/stresstest/stresstest.cpp
Shane Caraveo dd01542bad Multithreaded stress test program for isapi module now supports phpt files
still stuff to do before it's realy done, but does run the tests, just need
to get it to compare results right now.
2001-01-15 00:29:49 +00:00

737 lines
18 KiB
C++

/*
* ======================================================================= *
* File: stress .c *
* stress tester for isapi dll's *
* based on cgiwrap *
* ======================================================================= *
*
*/
#define WIN32_LEAN_AND_MEAN
#include <afx.h>
#include <afxtempl.h>
#include <winbase.h>
#include <winerror.h>
#include <httpext.h>
#include <stdio.h>
#include <stdlib.h>
// These are things that go out in the Response Header
//
#define HTTP_VER "HTTP/1.0"
#define SERVER_VERSION "Http-Srv-Beta2/1.0"
//
// Simple wrappers for the heap APIS
//
#define xmalloc(s) HeapAlloc(GetProcessHeap(),HEAP_ZERO_MEMORY,(s))
#define xfree(s) HeapFree(GetProcessHeap(),0,(s))
//
// The mandatory exports from the ISAPI DLL
//
#define NUM_THREADS 10
#define ITERATIONS 1
HANDLE terminate[NUM_THREADS];
HANDLE StartNow;
// quick and dirty environment
typedef CMapStringToString TEnvironment;
TEnvironment IsapiEnvironment;
CStringArray IsapiFileList; // list of filenames
CStringArray TestNames; // --TEST--
CStringArray IsapiGetData; // --GET--
CStringArray IsapiPostData; // --POST--
CStringArray IsapiMatchData; // --EXPECT--
typedef struct _TIsapiContext {
HANDLE in;
HANDLE out;
DWORD tid;
TEnvironment env;
} TIsapiContext;
//
// Prototypes of the functions this sample implements
//
extern "C" {
HINSTANCE hDll;
typedef BOOL (WINAPI *VersionProc)(HSE_VERSION_INFO *) ;
typedef DWORD (WINAPI *HttpExtProc)(EXTENSION_CONTROL_BLOCK *);
BOOL WINAPI FillExtensionControlBlock(EXTENSION_CONTROL_BLOCK *, TIsapiContext *) ;
BOOL WINAPI GetServerVariable(HCONN, LPSTR, LPVOID, LPDWORD );
BOOL WINAPI ReadClient(HCONN,LPVOID,LPDWORD);
BOOL WINAPI WriteClient(HCONN,LPVOID,LPDWORD,DWORD);
BOOL WINAPI ServerSupportFunction(HCONN,DWORD,LPVOID,LPDWORD,LPDWORD);
VersionProc IsapiGetExtensionVersion;
HttpExtProc IsapiHttpExtensionProc;
HSE_VERSION_INFO version_info;
}
char * MakeDateStr(VOID);
char * GetEnv(char *);
DWORD CALLBACK IsapiThread(void *);
int stress_main(const char *filename,
const char *arg,
const char *postfile,
const char *matchdata,
const char *testname);
BOOL test = FALSE;
char temppath[MAX_PATH];
void stripcrlf(char *line)
{
DWORD l = strlen(line)-1;
if (line[l]==10 || line[l]==13) line[l]=0;
l = strlen(line)-1;
if (line[l]==10 || line[l]==13) line[l]=0;
}
BOOL ReadGlobalEnvironment(const char *environment)
{
if (environment) {
FILE *fp = fopen(environment, "r");
DWORD i=0;
if (fp) {
char line[2048];
while (fgets(line,sizeof(line)-1,fp)) {
// file.php arg1 arg2 etc.
char *p = strchr(line, '=');
if (p) {
*p=0;
IsapiEnvironment[line]=p+1;
}
}
fclose(fp);
return IsapiEnvironment.GetCount() > 0;
}
}
return FALSE;
}
BOOL ReadFileList(const char *filelist)
{
FILE *fp = fopen(filelist, "r");
if (!fp) {
printf("Unable to open %s\r\n", filelist);
}
char line[2048];
int i=0;
while (fgets(line,sizeof(line)-1,fp)) {
// file.php arg1 arg2 etc.
stripcrlf(line);
if (strlen(line)>3) {
char *p = strchr(line, ' ');
if (p) {
*p = 0;
// get file
IsapiFileList.Add(line);
IsapiGetData.Add(p+1);
} else {
// just a filename is all
IsapiFileList.Add(line);
IsapiGetData.Add("");
}
}
// future use
IsapiPostData.Add("");
IsapiMatchData.Add("");
TestNames.Add("");
i++;
}
fclose(fp);
return IsapiFileList.GetSize() > 0;
}
void DoThreads() {
if (IsapiFileList.GetSize() == 0) {
printf("No Files to test\n");
return;
}
printf("Starting Threads...\n");
// loop creating threads
DWORD tid;
HANDLE threads[NUM_THREADS];
for (DWORD i=0; i< NUM_THREADS; i++) {
terminate[i] = CreateEvent(NULL, FALSE, FALSE, NULL);
if ((threads[i]=CreateThread(NULL, 0, IsapiThread, &terminate[i], CREATE_SUSPENDED, &tid))==NULL){
SetEvent(terminate[i]);
}
}
for (i=0; i< NUM_THREADS; i++) {
if (threads[i]) ResumeThread(threads[i]);
}
// wait for threads to finish
WaitForMultipleObjects(NUM_THREADS, terminate, TRUE, INFINITE);
}
void DoFileList(const char *filelist, const char *environment)
{
// read config files
if (!ReadFileList(filelist)) {
printf("No Files to test!\r\n");
return;
}
ReadGlobalEnvironment(environment);
DoThreads();
}
/**
* ParseTestFile
* parse a single phpt file and add it to the arrays
*/
BOOL ParseTestFile(const char *path, const char *fn)
{
// parse the test file
char filename[MAX_PATH];
_snprintf(filename, sizeof(filename)-1, "%s\\%s", path, fn);
char line[1024];
memset(line, 0, sizeof(line));
CString cTest, cSkipIf, cPost, cGet, cFile, cExpect;
printf("Reading %s\r\n", filename);
enum state {none, test, skipif, post, get, file, expect} parsestate = none;
FILE *fp = fopen(filename, "r");
if (fp) {
while (fgets(line,sizeof(line)-1,fp)) {
if (line[0]=='-') {
if (_strnicmp(line, "--TEST--", 8)==0) {
parsestate = test;
continue;
} else if (_strnicmp(line, "--SKIPIF--", 10)==0) {
parsestate = skipif;
continue;
} else if (_strnicmp(line, "--POST--", 8)==0) {
parsestate = post;
continue;
} else if (_strnicmp(line, "--GET--", 7)==0) {
parsestate = get;
continue;
} else if (_strnicmp(line, "--FILE--", 8)==0) {
parsestate = file;
continue;
} else if (_strnicmp(line, "--EXPECT--", 10)==0) {
parsestate = expect;
continue;
}
}
switch (parsestate) {
case test:
stripcrlf(line);
cTest = line;
break;
case skipif:
cSkipIf += line;
break;
case post:
cPost += line;
break;
case get:
cGet += line;
break;
case file:
cFile += line;
break;
case expect:
cExpect += line;
break;
}
}
fclose(fp);
if (!cTest.IsEmpty() && !cFile.IsEmpty() && !cExpect.IsEmpty()) {
BOOL created = FALSE;
char *fn = _tempnam(temppath,"pht.");
char *en = _tempnam(temppath,"exp.");
FILE *fp = fopen(fn, "w+");
FILE *fe = fopen(en, "w+");
if (fp && en) {
fwrite(cFile, cFile.GetLength(), 1, fp);
fwrite(cExpect, cExpect.GetLength(), 1, fe);
IsapiFileList.Add(fn);
TestNames.Add(cTest);
IsapiGetData.Add(cGet);
IsapiPostData.Add(cPost);
IsapiMatchData.Add(en);
created = TRUE;
}
if (fp) fclose(fp);
if (fe) fclose(fe);
return created;
}
}
return FALSE;
}
/**
* GetTestFiles
* Recurse through the path and subdirectories, parse each phpt file
*/
BOOL GetTestFiles(const char *path)
{
// find all files .phpt under testpath\tests
char FindPath[MAX_PATH];
WIN32_FIND_DATA fd;
memset(&fd, 0, sizeof(WIN32_FIND_DATA));
_snprintf(FindPath, sizeof(FindPath)-1, "%s\\*.*",path);
HANDLE fh = FindFirstFile(FindPath, &fd);
if (fh != INVALID_HANDLE_VALUE) {
do {
if ((fd.dwFileAttributes & FILE_ATTRIBUTE_DIRECTORY) &&
!strchr(fd.cFileName, '.')) {
// subdirectory, recurse into it
char NewFindPath[MAX_PATH];
_snprintf(NewFindPath, sizeof(NewFindPath)-1, "%s\\%s",path, fd.cFileName);
GetTestFiles(NewFindPath);
} else if (strstr(fd.cFileName, ".phpt")) {
// got test file, parse it now
if (ParseTestFile(path, fd.cFileName)) {
printf("Test File Added: %s\\%s\r\n", path, fd.cFileName);
}
}
memset(&fd, 0, sizeof(WIN32_FIND_DATA));
} while (FindNextFile(fh, &fd) != 0);
FindClose(fh);
}
return IsapiFileList.GetSize() > 0;
}
void DoTestFiles(const char *filelist, const char *environment)
{
if (!GetTestFiles(filelist)) {
printf("No Files to test!\r\n");
return;
}
ReadGlobalEnvironment(environment);
DoThreads();
}
int main(int argc, char* argv[]) {
LPVOID lpMsgBuf;
char *filelist=NULL, *environment=NULL;
if (argc < 3) {
// look for phpt files in tests
printf("USAGE: stresstest [L|T] filelist [environment]\r\n");
return 0;
} else {
if (argv[1][0]=='T') test = TRUE;
if (argc > 1) filelist = argv[2];
if (argc > 2) environment = argv[3];
}
GetTempPath(sizeof(temppath), temppath);
hDll = LoadLibrary("php4isapi.dll"); // Load our DLL
if (!hDll) {
FormatMessage(
FORMAT_MESSAGE_ALLOCATE_BUFFER | FORMAT_MESSAGE_FROM_SYSTEM,
NULL,
GetLastError(),
MAKELANGID(LANG_NEUTRAL, SUBLANG_DEFAULT), // Default language
(LPTSTR) &lpMsgBuf,
0,
NULL
);
fprintf(stderr,"Error: Dll 'php4isapi.dll' not found -%d\n%s\n", GetLastError(),lpMsgBuf);
LocalFree( lpMsgBuf );
return -1;
}
//
// Find the exported functions
IsapiGetExtensionVersion = (VersionProc)GetProcAddress(hDll,"GetExtensionVersion");
if (!IsapiGetExtensionVersion) {
fprintf(stderr,"Can't Get Extension Version %d\n",GetLastError());
return -1;
}
IsapiHttpExtensionProc = (HttpExtProc)GetProcAddress(hDll,"HttpExtensionProc");
if (!IsapiHttpExtensionProc) {
fprintf(stderr,"Can't Get Extension proc %d\n",GetLastError());
return -1;
}
// This should really check if the version information matches what we
// expect.
//
if (!IsapiGetExtensionVersion(&version_info) ) {
fprintf(stderr,"Fatal: GetExtensionVersion failed\n");
return -1;
}
if (test) {
char TestPath[MAX_PATH];
if (filelist != NULL)
_snprintf(TestPath, sizeof(TestPath)-1, "%s\\tests", filelist);
else strcpy(TestPath, "tests");
DoTestFiles(TestPath, environment);
} else
DoFileList(filelist, environment);
// cleanup
// We should really free memory (e.g., from GetEnv), but we'll be dead
// soon enough
FreeLibrary(hDll);
return 0;
}
DWORD CALLBACK IsapiThread(void *p)
{
HANDLE *terminate = (HANDLE *)p;
DWORD filecount = IsapiFileList.GetSize();
for (DWORD j=0; j<ITERATIONS; j++) {
for (DWORD i=0; i<filecount; i++) {
// execute each file
printf("Thread %d File %s\n", GetCurrentThreadId(), IsapiFileList.GetAt(i));
stress_main(IsapiFileList.GetAt(i),
IsapiGetData.GetAt(i),
IsapiPostData.GetAt(i),
IsapiMatchData.GetAt(i),
TestNames.GetAt(i));
Sleep(10);
}
}
SetEvent(*terminate);
printf("Thread ending...\n");
return 0;
}
/*
* ======================================================================= *
* In the startup of this program, we look at our executable name and *
* replace the ".EXE" with ".DLL" to find the ISAPI DLL we need to load. *
* This means that the executable need only be given the same "name" as *
* the DLL to load. There is no recompilation required. *
* ======================================================================= *
*/
int stress_main(const char *filename,
const char *arg,
const char *postdata,
const char *matchdata,
const char *testname)
{
EXTENSION_CONTROL_BLOCK ECB;
DWORD rc;
TIsapiContext context;
// open output and input files
context.tid = GetCurrentThreadId();
CString fname;
fname.Format("%08X.out", context.tid);
context.out = CreateFile(fname, GENERIC_WRITE, 0, NULL, CREATE_ALWAYS, 0, NULL);
if (context.out==INVALID_HANDLE_VALUE) {
printf("failed to open output file %s\n", fname);
return 0;
}
// not using post files
context.in = INVALID_HANDLE_VALUE;
//
// Fill the ECB with the necessary information
//
if (!FillExtensionControlBlock(&ECB, &context) ) {
fprintf(stderr,"Fill Ext Block Failed\n");
return -1;
}
// check for command line argument,
// first arg = filename
// this is added for testing php from command line
context.env.RemoveAll();
context.env["PATH_TRANSLATED"]= filename;
context.env["SCRIPT_MAP"]= filename;
context.env["CONTENT_TYPE"]= "";
context.env["CONTENT_LENGTH"]= "";
context.env["QUERY_STRING"]= arg;
context.env["METHOD"]="GET";
char buf[MAX_PATH];
if (postdata && *postdata !=0) {
ECB.cbAvailable = strlen(postdata);
ECB.cbTotalBytes = ECB.cbAvailable;
ECB.lpbData = (unsigned char *)postdata;
context.env["METHOD"]="POST";
_snprintf(buf, sizeof(buf)-1, "%d", ECB.cbTotalBytes);
context.env["CONTENT_LENGTH"]=buf;
context.env["CONTENT_TYPE"]="application/x-www-form-urlencoded";
}
ECB.lpszPathTranslated = strdup(filename);
ECB.lpszQueryString = strdup(arg);
// Call the DLL
//
rc = IsapiHttpExtensionProc(&ECB);
free(ECB.lpszPathTranslated);
free(ECB.lpszQueryString);
// compare the output with the EXPECT section
if (matchdata && *matchdata != 0) {
// wimpy I know, but I'm tired and lazy now
HANDLE md = CreateFile(matchdata, GENERIC_READ, FILE_SHARE_READ, NULL, OPEN_EXISTING, 0, NULL);
if (md) {
DWORD osz = 0, msz = 0;
GetFileSize(md, &msz);
GetFileSize(context.out, &osz);
if (osz == msz && osz != 0) {
printf("%s OK\r\n", testname);
} else {
printf("%s FAILED\r\n", testname);
}
}
}
if (context.out) CloseHandle(context.out);
//if (rc == HSE_STATUS_PENDING) // We will exit in ServerSupportFunction
// Sleep(INFINITE);
return 0;
}
//
// GetServerVariable() is how the DLL calls the main program to figure out
// the environment variables it needs. This is a required function.
//
BOOL WINAPI GetServerVariable(HCONN hConn, LPSTR lpszVariableName,
LPVOID lpBuffer, LPDWORD lpdwSize){
DWORD rc;
CString value;
TIsapiContext *c = (TIsapiContext *)hConn;
if (!c) return FALSE;
if (IsapiEnvironment.Lookup(lpszVariableName, value)) {
rc = value.GetLength();
strncpy((char *)lpBuffer, value, *lpdwSize-1);
} else if (c->env.Lookup(lpszVariableName, value)) {
rc = value.GetLength();
strncpy((char *)lpBuffer, value, *lpdwSize-1);
} else
rc = GetEnvironmentVariable(lpszVariableName,(char *)lpBuffer,*lpdwSize) ;
if (!rc) { // return of 0 indicates the variable was not found
SetLastError(ERROR_NO_DATA);
return FALSE;
}
if (rc > *lpdwSize) {
SetLastError(ERROR_INSUFFICIENT_BUFFER);
return FALSE;
}
*lpdwSize =rc + 1 ; // GetEnvironmentVariable does not count the NULL
return TRUE;
}
//
// Again, we don't have an HCONN, so we simply wrap ReadClient() to
// ReadFile on stdin. The semantics of the two functions are the same
//
BOOL WINAPI ReadClient(HCONN hConn, LPVOID lpBuffer, LPDWORD lpdwSize) {
TIsapiContext *c = (TIsapiContext *)hConn;
if (!c) return FALSE;
if (c->in != INVALID_HANDLE_VALUE)
return ReadFile(c->in,lpBuffer,(*lpdwSize), lpdwSize,NULL);
return FALSE;
}
//
// ditto for WriteClient()
//
BOOL WINAPI WriteClient(HCONN hConn, LPVOID lpBuffer, LPDWORD lpdwSize,
DWORD dwReserved) {
TIsapiContext *c = (TIsapiContext *)hConn;
if (!c) return FALSE;
if (c->out != INVALID_HANDLE_VALUE)
return WriteFile(c->out,lpBuffer,*lpdwSize, lpdwSize,NULL);
return FALSE;
}
//
// This is a special callback function used by the DLL for certain extra
// functionality. Look at the API help for details.
//
BOOL WINAPI ServerSupportFunction(HCONN hConn, DWORD dwHSERequest,
LPVOID lpvBuffer, LPDWORD lpdwSize, LPDWORD lpdwDataType){
char *lpszRespBuf;
char * temp = NULL;
DWORD dwBytes;
BOOL bRet;
switch(dwHSERequest) {
case (HSE_REQ_SEND_RESPONSE_HEADER) :
lpszRespBuf = (char *)xmalloc(*lpdwSize);//+ 80);//accomodate our header
if (!lpszRespBuf)
return FALSE;
wsprintf(lpszRespBuf,"%s",
//HTTP_VER,
/* Default response is 200 Ok */
//lpvBuffer?lpvBuffer:"200 Ok",
/* Create a string for the time. */
//temp=MakeDateStr(),
//SERVER_VERSION,
/* If this exists, it is a pointer to a data buffer to
be sent. */
lpdwDataType?(char *)lpdwDataType:NULL);
if (temp) xfree(temp);
dwBytes = strlen(lpszRespBuf);
bRet = WriteClient(0,lpszRespBuf,&dwBytes,0);
xfree(lpszRespBuf);
break;
//
// A real server would do cleanup here
case (HSE_REQ_DONE_WITH_SESSION):
//ExitThread(0);
break;
//
// This sends a redirect (temporary) to the client.
// The header construction is similar to RESPONSE_HEADER above.
//
case (HSE_REQ_SEND_URL_REDIRECT_RESP):
lpszRespBuf = (char *)xmalloc(*lpdwSize +80) ;
if (!lpszRespBuf)
return FALSE;
wsprintf(lpszRespBuf,"%s %s %s\r\n",
HTTP_VER,
"302 Moved Temporarily",
(lpdwSize > 0)?lpvBuffer:0);
xfree(temp);
dwBytes = strlen(lpszRespBuf);
bRet = WriteClient(0,lpszRespBuf,&dwBytes,0);
xfree(lpszRespBuf);
break;
default:
return FALSE;
break;
}
return bRet;
}
//
// Makes a string of the date and time from GetSystemTime().
// This is in UTC, as required by the HTTP spec.`
//
char * MakeDateStr(void){
SYSTEMTIME systime;
char *szDate= (char *)xmalloc(64);
char * DaysofWeek[] = {"Sun","Mon","Tue","Wed","Thurs","Fri","Sat"};
char * Months[] = {"NULL","Jan","Feb","Mar","Apr","May","Jun","Jul","Aug",
"Sep","Oct","Nov","Dec"};
GetSystemTime(&systime);
wsprintf(szDate,"%s, %d %s %d %d:%d.%d",DaysofWeek[systime.wDayOfWeek],
systime.wDay,
Months[systime.wMonth],
systime.wYear,
systime.wHour,systime.wMinute,
systime.wSecond );
return szDate;
}
//
// Fill the ECB up
//
BOOL WINAPI FillExtensionControlBlock(EXTENSION_CONTROL_BLOCK *ECB, TIsapiContext *context) {
char * temp;
ECB->cbSize = sizeof(EXTENSION_CONTROL_BLOCK);
ECB->dwVersion = MAKELONG(HSE_VERSION_MINOR,HSE_VERSION_MAJOR);
ECB->ConnID = (void *)context;
//
// Pointers to the functions the DLL will call.
//
ECB->GetServerVariable = GetServerVariable;
ECB->ReadClient = ReadClient;
ECB->WriteClient = WriteClient;
ECB->ServerSupportFunction = ServerSupportFunction;
//
// Fill in the standard CGI environment variables
//
ECB->lpszMethod = GetEnv("REQUEST_METHOD");
if (!ECB->lpszMethod) ECB->lpszMethod = "GET";
ECB->lpszQueryString = GetEnv("QUERY_STRING");
ECB->lpszPathInfo = GetEnv("PATH_INFO");
ECB->lpszPathTranslated = GetEnv("PATH_TRANSLATED");
ECB->cbTotalBytes=( (temp=GetEnv("CONTENT_LENGTH")) ? (atoi(temp)): 0);
ECB->cbAvailable = 0;
ECB->lpbData = (unsigned char *)"";
ECB->lpszContentType = GetEnv("CONTENT_TYPE");
return TRUE;
}
//
// Works like _getenv(), but uses win32 functions instead.
//
char * GetEnv(LPSTR lpszEnvVar) {
char *var,dummy;
DWORD dwLen;
if (!lpszEnvVar)
return "";
dwLen =GetEnvironmentVariable(lpszEnvVar,&dummy,1);
if (dwLen == 0)
return "";
var = (char *)xmalloc(dwLen);
if (!var)
return "";
(void)GetEnvironmentVariable(lpszEnvVar,var,dwLen);
return var;
}