/*++ Copyright (c) 2000 Microsoft Corporation Module Name: vststprocess.cxx Abstract: Implementation of test message classes Brian Berkowitz [brianb] 05/24/2000 TBD: Revision History: Name Date Comments brianb 05/24/2000 Created --*/ #include #include #include #include void LogUnexpectedFailure(LPCWSTR, ...); CVsTstProcessList::CVsTstProcessList() : m_processId(0L), m_processList(NULL), m_bcsProcessListInitialized(false), m_bNoMoreProcessesAreCreated(false), m_bShuttingDown(true) { } CVsTstProcessList::~CVsTstProcessList() { m_bShuttingDown = true; if (WaitForSingleObject(m_hThreadMonitor, INFINITE) == WAIT_FAILED) { LogUnexpectedFailure(L"WaitForSingleObject failed for reason %d.\n", GetLastError()); // wait for 1 minute for everything to shut down Sleep(60000); } CloseHandle(m_hThreadMonitor); if (m_bcsProcessListInitialized) m_csProcessList.Term(); } void CVsTstProcessList::LinkIntoProcessList(CVsTstProcess *process) { m_csProcessList.Lock(); VSTST_ASSERT(m_processList == NULL || m_processList->m_prev == NULL); process->m_next = m_processList; process->m_prev = NULL; if (m_processList != NULL) m_processList->m_prev = process; m_processList = process; m_csProcessList.Unlock(); } void CVsTstProcessList::UnlinkFromProcessList(CVsTstProcess *process) { m_csProcessList.Lock(); VSTST_ASSERT(m_processList); VSTST_ASSERT(m_processList->m_prev == NULL); if (process == m_processList) { m_processList = process->m_next; if (m_processList) { VSTST_ASSERT(m_processList->m_prev == process); m_processList->m_prev = NULL; } } else { VSTST_ASSERT(process->m_prev != NULL); process->m_prev->m_next = process->m_next; if (process->m_next) { VSTST_ASSERT(process->m_next->m_prev == process); process->m_next->m_prev = process->m_prev; } } m_csProcessList.Unlock(); delete process; } HRESULT CVsTstProcessList::Initialize ( UINT maxLifetime, HANDLE hevtTermination ) { try { m_csProcessList.Init(); m_bcsProcessListInitialized = true; } catch(...) { return E_UNEXPECTED; } time_t timeCur; time(&timeCur); m_timeTerminateTest = timeCur + maxLifetime; m_hevtTermination = hevtTermination; DWORD tid; m_hThreadMonitor = CreateThread ( NULL, 256*1024, StartupMonitorThread, this, 0, &tid ); if (m_hThreadMonitor == NULL) return HRESULT_FROM_WIN32(GetLastError()); return S_OK; } DWORD CVsTstProcessList::StartupMonitorThread(void *pv) { CVsTstProcessList *processList = (CVsTstProcessList *) pv; try { processList->MonitorFunc(); if (processList->m_hevtTermination) { if (!SetEvent(processList->m_hevtTermination)) LogUnexpectedFailure(L"SetEvent failed with error %d", GetLastError()); } } catch(...) { } return 0; } // void CVsTstProcessList::MonitorFunc() { while(TRUE) { time_t timeNow; Sleep(5000); time(&timeNow); if (timeNow > m_timeTerminateTest) { StartTermination(); break; } else { m_csProcessList.Lock(); // check if there are no processes and no more proceses // are created if (m_processList == NULL && m_bNoMoreProcessesAreCreated) { m_csProcessList.Unlock(); break; } CVsTstProcess *process = m_processList; while(process != NULL) { CVsTstProcess *processNext = process->m_next; m_csProcessList.Unlock(); if (process->HasProcessExpired()) process->DoTerminateProcess(false); m_csProcessList.Lock(); process = processNext; } m_csProcessList.Unlock(); } } } // start process termination void CVsTstProcessList::StartTermination() { m_bNoMoreProcessesAreCreated = true; while(TRUE) { m_csProcessList.Lock(); CVsTstProcess *process = m_processList; if (process == NULL) { m_csProcessList.Unlock(); return; } while(process != NULL) { CVsTstProcess *processNext = process->m_next; m_csProcessList.Unlock(); process->DoTerminateProcess(m_bShuttingDown); m_csProcessList.Lock(); process = processNext; } // unlock list before going to sleep m_csProcessList.Unlock(); Sleep(5000); } } // constructor CVsTstProcess::CVsTstProcess(CVsTstProcessList *list) : m_hProcess(NULL), m_hevtGracefullyTerminate(NULL), m_hevtNotifyProcessTermination(NULL), m_pvPrivateData(NULL), m_next(NULL), m_prev(NULL), m_bLinked(false), m_processList(list), m_bGracefullyTerminated(false) { } // deastructor CVsTstProcess::~CVsTstProcess() { if (m_bLinked) m_processList->UnlinkFromProcessList(this); if (m_hevtGracefullyTerminate) CloseHandle(m_hevtGracefullyTerminate); } HRESULT CVsTstProcess::InitializeConformingExe ( ULONGLONG processId, VSTST_PROCESS_TYPE type, VSTST_ACCOUNT_TYPE account, LPCWSTR wszExecutableName, LPCWSTR wszScenarioFile, LPCWSTR wszSection, DWORD seed, UINT lifetime, bool bAbnormalTermination, HANDLE hevtNotify ) { WCHAR buf[256]; SECURITY_ATTRIBUTES attributes; m_processId = processId; m_type = type; m_account = account; m_bConforming = true; attributes.nLength = sizeof(SECURITY_ATTRIBUTES); attributes.lpSecurityDescriptor = NULL; attributes.bInheritHandle = true; m_hevtGracefullyTerminate = CreateEvent(&attributes, TRUE, FALSE, NULL); if (m_hevtGracefullyTerminate == NULL) return HRESULT_FROM_WIN32(GetLastError()); UINT pidLow = (UINT) (m_processId & 0xffffffff); UINT pidHigh = (UINT) (m_processId >> 32); swprintf ( buf, L"%s -pidLow=%d -pidHigh=%d -scenario={%s} -section={%s} -seed=%u -lifetime=%d -event=0x%08x", wszExecutableName, pidLow, pidHigh, wszScenarioFile, wszSection, seed, lifetime, m_hevtGracefullyTerminate ); PROCESS_INFORMATION info; STARTUPINFO startup; memset(&startup, 0, sizeof(STARTUPINFO)); startup.cb = sizeof(STARTUPINFO); if (!CreateProcess ( NULL, buf, NULL, NULL, TRUE, NORMAL_PRIORITY_CLASS, NULL, NULL, &startup, &info )) { DWORD dwErr = GetLastError(); CloseHandle(m_hevtGracefullyTerminate); m_hevtGracefullyTerminate = NULL; return HRESULT_FROM_WIN32(dwErr); } m_hProcess = info.hProcess; m_hevtNotifyProcessTermination = hevtNotify; m_bAbnormallyTerminate = bAbnormalTermination; time(&m_timeProcessStart); m_timeProcessTerminate = m_timeProcessStart + lifetime; m_processList->LinkIntoProcessList(this); return S_OK; } // startup a process associated with a non-conforming executable HRESULT CVsTstProcess::InitializeNonConformingExe ( ULONGLONG processId, VSTST_PROCESS_TYPE type, VSTST_ACCOUNT_TYPE account, LPCWSTR wszCommandLine, UINT lifetime, HANDLE hevtNotify ) { m_processId = processId; m_type = type; m_account = account; m_bConforming = false; PROCESS_INFORMATION info; STARTUPINFO startup; memset(&startup, 0, sizeof(STARTUPINFO)); startup.cb = sizeof(STARTUPINFO); if (!CreateProcess ( NULL, (LPWSTR) wszCommandLine, NULL, NULL, TRUE, NORMAL_PRIORITY_CLASS, NULL, NULL, &startup, &info )) return HRESULT_FROM_WIN32(GetLastError()); m_hProcess = info.hProcess; m_hevtNotifyProcessTermination = hevtNotify; m_bAbnormallyTerminate = true; time(&m_timeProcessStart); m_timeProcessTerminate = m_timeProcessStart + lifetime; m_processList->LinkIntoProcessList(this); return S_OK; } void CVsTstProcess::DoTerminateProcess(bool bAgressive) { if (m_bAbnormallyTerminate) ForceTerminateProcess(); else if (!m_bGracefullyTerminated) GracefullyTerminateProcess(bAgressive); else { // see if process is terminated DWORD dwErr = WaitForSingleObject(m_hProcess, 0); if (dwErr == WAIT_FAILED) ForceTerminateProcess(); else if (dwErr == WAIT_TIMEOUT) { time_t timeCur; time(&timeCur); if (m_timeProcessTerminationExpiration < timeCur) // forcefully terminate the process ForceTerminateProcess(); // if we are shutting down, give the process 10 seconds // to shut down before we forcefully terminate it if (bAgressive && m_timeProcessTerminationExpiration > timeCur + 10) m_timeProcessTerminationExpiration = timeCur + 10; } else CleanupProcess(); } } // has process expired bool CVsTstProcess::HasProcessExpired() { // see if process is terminated DWORD dwErr = WaitForSingleObject(m_hProcess, 0); if (dwErr == WAIT_OBJECT_0) { CleanupProcess(); return false; } time_t timeVal; time(&timeVal); return timeVal > m_timeProcessTerminate; } // cleanup process at termination void CVsTstProcess::CleanupProcess() { if (m_hevtNotifyProcessTermination) { if (!SetEvent(m_hevtNotifyProcessTermination)) LogUnexpectedFailure(L"SetEvent failed with error %d", GetLastError()); } m_processList->UnlinkFromProcessList(this); } // forceably terminate the process void CVsTstProcess::ForceTerminateProcess() { if (!TerminateProcess(m_hProcess, 0)) LogUnexpectedFailure(L"Terminate process failed with error %d", GetLastError()); CleanupProcess(); } // try terminating the process gracefully void CVsTstProcess::GracefullyTerminateProcess(bool bAgressive) { VSTST_ASSERT(m_hevtGracefullyTerminate); if (!SetEvent(m_hevtGracefullyTerminate)) LogUnexpectedFailure(L"SetEvent failed with error %d", GetLastError()); m_bGracefullyTerminated = true; time(&m_timeProcessStartTermination); // allow 2 minutes for the process to gracefully terminate, 10 seconds if // if we are agressive m_timeProcessTerminationExpiration = m_timeProcessStartTermination + bAgressive ? 10 : 120; } HRESULT CVsTstProcessList::CreateConformingExe ( VSTST_PROCESS_TYPE type, VSTST_ACCOUNT_TYPE account, LPCWSTR wszExecutableName, LPCWSTR wszScenarioFile, LPCWSTR wszSectionName, DWORD seed, UINT lifetime, bool bAbnormalTerminate, HANDLE hevtNotify, ULONGLONG &processId ) { processId = AllocateProcessId(); CVsTstProcess *process = new CVsTstProcess(this); if (process == NULL) return E_OUTOFMEMORY; HRESULT hr = process->InitializeConformingExe ( processId, type, account, wszExecutableName, wszScenarioFile, wszSectionName, seed, lifetime, bAbnormalTerminate, hevtNotify ); if (FAILED(hr)) delete process; return hr; } // create a process for a non-conforming executable HRESULT CVsTstProcessList::CreateNonConformingExe ( VSTST_PROCESS_TYPE type, VSTST_ACCOUNT_TYPE account, LPCWSTR wszCommandLine, UINT lifetime, HANDLE hevtNotify, ULONGLONG &processId ) { processId = AllocateProcessId(); CVsTstProcess *process = new CVsTstProcess(this); if (process == NULL) return E_OUTOFMEMORY; HRESULT hr = process->InitializeNonConformingExe ( processId, type, account, wszCommandLine, lifetime, hevtNotify ); if (FAILED(hr)) delete process; return hr; } // find a specific process CVsTstProcess *CVsTstProcessList::FindProcess(ULONGLONG processId) { m_csProcessList.Lock(); CVsTstProcess *process = m_processList; while(process != NULL) { if (process->m_processId == processId) { m_csProcessList.Unlock(); return process; } process = process->m_next; } return NULL; } // get private data associated with a process void *CVsTstProcessList::GetProcessPrivateData(LONGLONG processId) { CVsTstProcess *process = FindProcess(processId); if (process == NULL) return NULL; else return process->GetPrivateData(); } // set private data associated with a process void CVsTstProcessList::SetProcessPrivateData(LONGLONG processId, void *pv) { CVsTstProcess *process = FindProcess(processId); if (process != NULL) process->SetPrivateData(pv); } // indicate that no more processes will be created void CVsTstProcessList::EndOfProcessCreation() { m_bNoMoreProcessesAreCreated = true; } ULONGLONG CVsTstProcessList::AllocateProcessId() { m_csProcessList.Lock(); ULONGLONG processId = ++m_processId; m_csProcessList.Unlock(); return processId; } static VSTST_CMDDESC x_rgCommands[] = { { L"scenario", VSTST_CT_STRING, VSTST_CP_SCENARIOFILE }, { L"testseries", VSTST_CT_STRING, VSTST_CP_TESTSERIES }, { L"section", VSTST_CT_STRING, VSTST_CP_SECTIONNAME }, { L"seed", VSTST_CT_UINT, VSTST_CP_SEED }, { L"lifetime", VSTST_CT_UINT, VSTST_CP_LIFETIME, }, { L"event", VSTST_CT_HANDLE, VSTST_CP_TERMINATIONEVENT }, { L"pidLow", VSTST_CT_UINT, VSTST_CP_PIDLOW }, { L"pidHigh", VSTST_CT_UINT, VSTST_CP_PIDHIGH } }; static UINT x_cCommands = sizeof(x_rgCommands)/sizeof(x_rgCommands[0]); // constructor CVsTstParams::CVsTstParams() : m_wszScenarioFile(NULL), m_wszSectionName(NULL), m_wszTestSeries(NULL), m_supplied(0) { } // destructor CVsTstParams::~CVsTstParams() { delete m_wszScenarioFile; delete m_wszSectionName; } // parse the command line bool CVsTstParams::ParseCommandLine(WCHAR **argv, UINT argc) { for(UINT iarg = 1; iarg < argc; iarg++) { WCHAR *wsz = argv[iarg]; if (wsz[0] != '-') return false; wsz++; WCHAR *pwc = wsz; while(*pwc != L'\0' && *pwc != L'=') pwc++; if (*pwc != '=') return false; *pwc++ = '\0'; for(UINT icmd = 0; icmd < x_cCommands; icmd++) { if (wcscmp(wsz, x_rgCommands[icmd].wszParam) == 0) break; } if (icmd == x_cCommands) return false; VSTST_CMDDESC *pCommand = &x_rgCommands[icmd]; UINT ulVal; LPWSTR wszVal; HANDLE hVal; switch(pCommand->type) { default: VSTST_ASSERT(FALSE && "shouldn't get here"); return false; case VSTST_CT_UINT: if (!ParseUINT(pwc, &ulVal)) return false; switch(pCommand->param) { default: VSTST_ASSERT(FALSE && "shouldn't get here"); return false; case VSTST_CP_SEED: SetSeed(ulVal); break; case VSTST_CP_LIFETIME: SetLifetime(ulVal); break; case VSTST_CP_PIDLOW: SetPidLow(ulVal); break; case VSTST_CP_PIDHIGH: SetPidHigh(ulVal); break; } break; case VSTST_CT_HANDLE: if (!ParseHandle(pwc, &hVal)) return false; if (pCommand->param == VSTST_CP_TERMINATIONEVENT) SetTerminationEvent(hVal); else { VSTST_ASSERT(FALSE && "shouldn't get here"); return false; } break; case VSTST_CT_STRING: if (!ParseString(pwc, &wszVal)) return false; switch(pCommand->param) { default: VSTST_ASSERT(FALSE && "shouldn't get here"); return false; case VSTST_CP_SCENARIOFILE: SetScenarioFile(wszVal); break; case VSTST_CP_TESTSERIES: SetTestSeries(wszVal); break; case VSTST_CP_SECTIONNAME: SetSectionName(wszVal); break; } break; } } m_hTestTerminationEvent = CreateEvent(NULL, TRUE, FALSE, NULL); if (m_hTerminationEvent == NULL) return false; m_supplied |= VSTST_CP_TESTTERMINATIONEVENT; return true; } bool CVsTstParams::ParseUINT(LPCWSTR wsz, UINT *pulVal) { UINT ulVal = 0; if (*wsz == L'\0') return false; do { WCHAR wc = *wsz++; if (wc < L'0' || wc > L'9') return false; UINT ulValNew = ulVal * 10 + (wc - L'0'); if (ulValNew < ulVal) return false; ulVal = ulValNew; } while(*wsz != L'\0'); *pulVal = ulVal; return true; } // parse a string value of the form "..." bool CVsTstParams::ParseString(LPCWSTR wsz, LPWSTR *pwszVal) { if (*wsz != L'{') return false; wsz++; LPCWSTR wszStart = wsz; while(*wsz != L'\0' && *wsz != '}') wsz++; if (*wsz != L'}') return false; UINT cwc = (UINT)( wsz - wszStart ); LPWSTR wszRet = new WCHAR[cwc + 1]; memcpy(wszRet, wszStart, cwc * sizeof(WCHAR)); wszRet[cwc] = L'\0'; *pwszVal = wszRet; return true; } bool CVsTstParams::ParseHandle(LPCWSTR wsz, HANDLE *phVal) { UINT ulVal = 0; if (*wsz++ != L'0') return false; if (*wsz++ != L'x') return false; if (*wsz == L'\0') return false; do { WCHAR wc = *wsz++; UINT digit; if (wc >= L'0' && wc <= L'9') digit = wc - L'0'; else if (wc >= L'a' && wc <= L'f') digit = wc - L'a' + 10; else if (wc >= L'A' && wc <= L'F') digit = wc - L'A' + 10; else return false; if (ulVal & 0xf0000000) return false; ulVal = ulVal << 4 | digit; } while(*wsz != L'\0'); *phVal = (HANDLE)(ULONGLONG) ulVal; return true; } bool CVsTstParams::GetScenarioFileName(LPCWSTR *pwszScenarioFile) { if ((m_supplied & VSTST_CP_SCENARIOFILE) == 0) { *pwszScenarioFile = NULL; return false; } else { *pwszScenarioFile = m_wszScenarioFile; return true; } } bool CVsTstParams::GetTestSeries(LPCWSTR *pwszTestSeries) { if ((m_supplied & VSTST_CP_TESTSERIES) == 0) { *pwszTestSeries = NULL; return false; } else { *pwszTestSeries = m_wszTestSeries; return true; } } bool CVsTstParams::GetSectionName(LPCWSTR *pwszSectionName) { if ((m_supplied & VSTST_CP_SECTIONNAME) == 0) { *pwszSectionName = NULL; return false; } else { *pwszSectionName = m_wszSectionName; return true; } } bool CVsTstParams::GetRandomSeed(UINT *pSeed) { if ((m_supplied & VSTST_CP_SEED) == 0) return false; else { *pSeed = m_seed; return true; } } bool CVsTstParams::GetLifetime(UINT *pLifetime) { if ((m_supplied & VSTST_CP_LIFETIME) == 0) return false; else { *pLifetime = m_lifetime; return true; } } bool CVsTstParams::GetTerminationEvent(HANDLE *pHandle) { if ((m_supplied & VSTST_CP_TERMINATIONEVENT) == 0) { *pHandle = NULL; return false; } else { *pHandle = m_hTerminationEvent; return true; } } bool CVsTstParams::GetTestTerminationEvent(HANDLE *pHandle) { if ((m_supplied & VSTST_CP_TESTTERMINATIONEVENT) == 0) { *pHandle = NULL; return false; } else { *pHandle = m_hTestTerminationEvent; return true; } } bool CVsTstParams::GetProcessId(ULONGLONG *pid) { if ((m_supplied & VSTST_CP_PIDLOW) == 0) return false; else if ((m_supplied & VSTST_CP_PIDHIGH) == 0) { *pid = m_pidLow; return true; } else { *pid = (((ULONGLONG) m_pidHigh) << 32) + m_pidLow; return true; } } void CVsTstParams::SetSeed(UINT ulVal) { if (ulVal != 0) { m_supplied |= VSTST_CP_SEED; m_seed = ulVal; } } void CVsTstParams::SetLifetime(UINT ulVal) { if (ulVal != 0) { m_supplied |= VSTST_CP_LIFETIME; m_lifetime = ulVal; } } void CVsTstParams::SetPidLow(UINT ulVal) { m_supplied |= VSTST_CP_PIDLOW; m_pidLow = ulVal; } void CVsTstParams::SetPidHigh(UINT ulVal) { m_supplied |= VSTST_CP_PIDHIGH; m_pidHigh = ulVal; } void CVsTstParams::SetTerminationEvent(HANDLE hEvent) { if (hEvent != NULL) { m_supplied |= VSTST_CP_TERMINATIONEVENT; m_hTerminationEvent = hEvent; } } void CVsTstParams::SetScenarioFile(LPWSTR wsz) { m_supplied |= VSTST_CP_SCENARIOFILE; m_wszScenarioFile = wsz; } void CVsTstParams::SetTestSeries(LPWSTR wsz) { m_supplied |= VSTST_CP_TESTSERIES; m_wszTestSeries = wsz; } void CVsTstParams::SetSectionName(LPWSTR wsz) { m_supplied |= VSTST_CP_SECTIONNAME; m_wszSectionName = wsz; } // arguments to the test termination thread typedef struct _TerminationThreadParams { CVsTstParams *pParams; IVsTstRunningTest *pTest; } TerminationThreadParams; static LPCWSTR x_wszVssTestController = L"VssTestController."; static LPCWSTR x_wszVssTestRequestor = L"VssTestRequestor."; static LPCWSTR x_wszVssTestWriter = L"VssTestWriter."; // toplevel routine to run a test // it parses the parameters // creates a configuration file parser // creates a message pipe to the server // creates a thread to terminate the test // calls the supplied test // HRESULT CVsTstRunner::RunVsTest ( WCHAR **argv, UINT argc, IVsTstRunningTest *pTest, bool bCreateTerminationThread ) { CVsTstParams params; params.ParseCommandLine(argv, argc); LPCWSTR wszScenarioFile, wszSectionName, wszQualifier; if (!params.GetScenarioFileName(&wszScenarioFile) || !params.GetSectionName(&wszSectionName)) { VSTST_ASSERT(FALSE && "missing scenario"); return E_FAIL; } EVsTstINISectionType sectionType; if (memcmp(wszSectionName, x_wszVssTestWriter, wcslen(x_wszVssTestWriter) * sizeof(WCHAR)) == 0) { sectionType = eVsTstSectionType_TestWriter; wszQualifier = wszSectionName + wcslen(x_wszVssTestWriter); } else if (memcmp(wszSectionName, x_wszVssTestRequestor, wcslen(x_wszVssTestRequestor)) == 0) { sectionType = eVsTstSectionType_TestRequesterApp; wszQualifier = wszSectionName + wcslen(x_wszVssTestRequestor); } else if (memcmp(wszSectionName, x_wszVssTestController, wcslen(x_wszVssTestController)) == 0) { sectionType = eVsTstSectionType_TestCoordinator; wszQualifier = wszSectionName + wcslen(x_wszVssTestController); } else { VSTST_ASSERT(FALSE && "bad test type"); return E_FAIL; } HRESULT hr = S_OK; DWORD tid; TerminationThreadParams *pTTParm = NULL; HANDLE hTerminationThread = NULL; if (bCreateTerminationThread) { pTTParm = new TerminationThreadParams; if (pTTParm == NULL) { VSTST_ASSERT(FALSE && "Out of memory"); return E_OUTOFMEMORY; } pTTParm->pParams = ¶ms; pTTParm->pTest = pTest; hTerminationThread = CreateThread ( NULL, 32*1024, CVsTstRunner::StartupTerminationThread, pTTParm, 0, &tid ); if(hTerminationThread == NULL) { VSTST_ASSERT(FALSE && "Thread creation failed"); delete pTTParm; return HRESULT_FROM_WIN32(GetLastError()); } } try { InitMsgTypes(); unsigned cwc = (unsigned)wcslen(wszScenarioFile); WCHAR *wszFileName = new WCHAR[cwc * 2]; if (!ExpandEnvironmentStrings(wszScenarioFile, wszFileName, cwc * 2)) { VSTST_ASSERT(FALSE && "ExpandEnvironmentStrings failed"); LogUnexpectedFailure(L"ExpandEnvironmentStrings failed with error %d", GetLastError()); throw E_UNEXPECTED; } CVsTstINIConfig config(sectionType, wszQualifier, FALSE, wszFileName); CVsTstClientMsg client; ULONGLONG processId; if (!params.GetProcessId(&processId)) { VSTST_ASSERT(FALSE && "no process id"); throw(E_FAIL); } client.Init(processId, 1024, false); hr = pTest->RunTest(&config, &client, ¶ms); } catch(...) { VSTST_ASSERT(FALSE && "unexpected exception"); hr = E_FAIL; } HANDLE hTestTerminationEvent; if (!params.GetTestTerminationEvent(&hTestTerminationEvent)) LogUnexpectedFailure(L"Test Termination Event wasn't created"); else if (!SetEvent(hTestTerminationEvent)) LogUnexpectedFailure(L"SetEvent failed with error %d", GetLastError()); else { if (hTerminationThread != NULL && WaitForSingleObject(hTerminationThread, INFINITE) == WAIT_FAILED) LogUnexpectedFailure(L"WaitForSingleObject failed with error %d", GetLastError()); } if (hTerminationThread != NULL) CloseHandle(hTerminationThread); return hr; } // termination thread routine waits until either // test lifetime is exceeded // termination event is set by controller // test termination event is set when test completes // DWORD CVsTstRunner::StartupTerminationThread(void *pv) { TerminationThreadParams *pTTParms = (TerminationThreadParams *) pv; CVsTstParams *pParams = pTTParms->pParams; IVsTstRunningTest *pTest = pTTParms->pTest; delete pTTParms; HANDLE hTestTerminationEvent; HANDLE hTerminationEvent = NULL; if (!pParams->GetTestTerminationEvent(&hTestTerminationEvent)) { LogUnexpectedFailure(L"No Test Termination event was created."); return 0xffffffff; } pParams->GetTerminationEvent(&hTerminationEvent); UINT ulLifetime; bool bLifetime = pParams->GetLifetime(&ulLifetime); // more than one months worth of lifetime is infinite if (bLifetime && ulLifetime < 3600 * 24 * 30) ulLifetime = ulLifetime * 1000; else ulLifetime = INFINITE; HANDLE rghEvt[2]; unsigned cEvt = 1; rghEvt[0] = hTestTerminationEvent; if (hTerminationEvent != NULL) { rghEvt[1] = hTerminationEvent; cEvt++; } DWORD dwWait = WaitForMultipleObjects(cEvt, rghEvt, FALSE, ulLifetime); if (dwWait == WAIT_FAILED) LogUnexpectedFailure(L"Wait failed for reason %d", GetLastError()); else if (dwWait == WAIT_TIMEOUT) pTest->ShutdownTest(VSTST_SR_LIFETIME_EXCEEDED); else if (dwWait == WAIT_OBJECT_0 + 1) pTest->ShutdownTest(VSTST_SR_CONTROLLER_SIGNALLED); return 0; }