| //------------------------------------------------------------------------------------------------------- |
| // Copyright (C) Microsoft. All rights reserved. |
| // Licensed under the MIT license. See LICENSE.txt file in the project root for full license information. |
| //------------------------------------------------------------------------------------------------------- |
| // rlrun.c |
| // |
| // executable regression worker for rl.c |
| |
| #include "rl.h" |
| |
| #define TMP_PREFIX "ex" // 2 characters |
| |
| #define POGO_PGD "rlpogo.pgd" |
| |
| // In the RL environment running Pogo tests, some warnings in the optimization |
| // compile should be errors, since we do the instrumentation and optimization |
| // compiles back-to-back. |
| // 4951 "'%s' has been edited since profile data was collected, function profile data not used" |
| // 4952 "'%s' : no profile data found in program database '%s'" |
| // 4953 "Inlinee '%s' has been edited since profile data was collected, profile data not used" |
| // 4961 "No profile data was merged into '%s', profile-guided optimizations disabled" |
| // 4962 "Profile-guided optimizations disabled because profile data became inconsistent" |
| // 4963 "'%s' : no profile data found; different compiler options were used in instrumented build" |
| |
| static const char *PogoForceErrors = "-we4951 -we4952 -we4953 -we4961 -we4962 -we4963"; |
| |
| // |
| // Global variables set before worker threads start, and only accessed |
| // (not set) by the worker threads. |
| // |
| // sets of options to iterate over |
| const char *OptFlags[MAXOPTIONS + 1], *PogoOptFlags[MAXOPTIONS + 1]; |
| |
| // use a big global array as scratch pad for passing the child process env vars |
| #define MAX_ENV_LEN 10000 |
| __declspec(thread) char EnvFlags[MAX_ENV_LEN]; |
| |
| // |
| // Global variables read and written by the worker threads: these need to |
| // either be protected by synchronization or use thread-local storage. |
| // |
| |
| LOCAL void __cdecl |
| RunCleanUp() |
| { |
| } |
| |
| void |
| RunInit() |
| { |
| char *opts; // value of EXEC_TESTS_FLAGS environment variable |
| int numOptions; |
| int numPogoOptions; |
| int i; |
| |
| atexit(RunCleanUp); |
| |
| // Break EXEC_TESTS up into different sets of flags. The sets should |
| // be separated by semi-colons. Options don't apply to Pogo testing |
| // unless prefixed with POGO_TEST_PREFIX. Those options _only_ apply |
| // to Pogo tests. |
| |
| opts = EXEC_TESTS_FLAGS; |
| ASSERTNR(opts); |
| |
| numOptions = numPogoOptions = 0; |
| |
| while (opts) { |
| while (isspace(*opts)) |
| opts++; |
| if (*opts == '\0') |
| break; |
| |
| if (!_strnicmp(opts, POGO_TEST_PREFIX, strlen(POGO_TEST_PREFIX))) { |
| opts += strlen(POGO_TEST_PREFIX); |
| PogoOptFlags[numPogoOptions] = opts; |
| ++numPogoOptions; |
| if (numPogoOptions == MAXOPTIONS) |
| Fatal("Too many options in EXEC_TESTS_FLAGS"); |
| } |
| else { |
| OptFlags[numOptions] = opts; |
| ++numOptions; |
| if (numOptions == MAXOPTIONS) |
| Fatal("Too many options in EXEC_TESTS_FLAGS"); |
| } |
| |
| opts = strchr(opts, ';'); |
| if (opts) |
| *opts++ = '\0'; |
| } |
| |
| for (i = 0; i < numPogoOptions; i++) { |
| if (strstr(PogoOptFlags[i], "GL") == NULL) { |
| Fatal("Pogo without LTCG is not supported"); |
| } |
| } |
| |
| OptFlags[numOptions] = NULL; |
| PogoOptFlags[numPogoOptions] = NULL; |
| |
| if (FVerbose) { |
| printf("(Normal) Exec flags:"); |
| for (i = 0; i < numOptions; i++) { |
| printf(" '%s'", OptFlags[i]); |
| } |
| printf("\nPogo Exec flags:"); |
| for (i = 0; i < numPogoOptions; i++) { |
| printf(" '%s'", PogoOptFlags[i]); |
| } |
| printf("\n"); |
| } |
| } |
| |
| BOOL |
| RunStartDir( |
| char * /*dir -- unused*/ |
| ) |
| { |
| return TRUE; |
| } |
| |
| void |
| DumpFileToLog( |
| char* path |
| ) |
| { |
| FILE* fp; |
| char buf[BUFFER_SIZE]; |
| char* p; |
| |
| fp = fopen_unsafe(path, "r"); |
| if (fp == NULL) { |
| LogError("ERROR: DumpFileToLog couldn't open file '%s' with error '%s'", path, strerror_unsafe(errno)); |
| } |
| else { |
| int fd = _fileno(fp); |
| struct _stat64 fileStats; |
| if (fd != -1 && _fstat64(fd, &fileStats) != -1) |
| { |
| char creationTime[256]; |
| char accessTime[256]; |
| char currTime[256]; |
| __time64_t now = _time64(NULL); |
| _ctime64_s(currTime, &now); |
| _ctime64_s(creationTime, &fileStats.st_ctime); |
| _ctime64_s(accessTime, &fileStats.st_atime); |
| auto stripNewline = [](char *buf) { |
| if (char *ptr = strchr(buf, '\n')) |
| *ptr = '\0'; |
| }; |
| stripNewline(creationTime); |
| stripNewline(accessTime); |
| stripNewline(currTime); |
| |
| LogOut("ERROR: name of output file: %s; size: %I64d; creation: %s, last access: %s, now: %s", path, fileStats.st_size, creationTime, accessTime, currTime); |
| } |
| if (!FNoProgramOutput) |
| { |
| bool printlines = !FOnlyAssertOutput; |
| if (printlines) |
| { |
| LogOut("ERROR: bad output file follows ============"); |
| } |
| while (fgets(buf, BUFFER_SIZE, fp) != NULL) { |
| // Strip the newline, since LogOut adds one |
| p = strchr(buf, '\n'); |
| if (p != NULL) { |
| *p = '\0'; |
| } |
| if (!printlines && strlen(buf) > 8 && buf[0] == 'A' && buf[1] == 'S' && buf[2] == 'S' && buf[3] == 'E' && buf[4] == 'R' && buf[5] == 'T') |
| { |
| printlines = true; |
| LogOut("ERROR: bad output file follows ============"); |
| } |
| if (printlines) |
| { |
| LogOut("%s", buf); |
| } |
| } |
| if (printlines) |
| { |
| LogOut("ERROR: end of bad output file ============"); |
| } |
| } |
| fclose(fp); |
| } |
| } |
| |
| // Use a state machine to recognize the word "pass" |
| BOOL |
| LookForPass( |
| char *p |
| ) |
| { |
| int state = 0; |
| |
| for(; *p != '\0'; p++) { |
| switch(tolower(*p)) { |
| case 'p': |
| state = 1; |
| break; |
| case 'a': |
| if (state == 1) |
| state = 2; |
| else |
| state = 0; |
| break; |
| case 's': |
| if (state == 2) |
| state = 3; |
| else if (state == 3) |
| return TRUE; |
| else |
| state = 0; |
| break; |
| default: |
| state = 0; |
| } |
| } |
| return FALSE; |
| } |
| |
| // Return TRUE if the specified test is Pogo-specific. |
| BOOL |
| IsPogoTest( |
| Test * pTest |
| ) |
| { |
| return HasInfo(pTest->defaultTestInfo.data[TIK_TAGS], XML_DELIM, "Pogo"); |
| } |
| |
| // Return TRUE if the specified test should NOT use nogpfnt.obj. |
| BOOL |
| SuppressNoGPF( |
| Test * pTest |
| ) |
| { |
| return HasInfo(pTest->defaultTestInfo.data[TIK_RL_DIRECTIVES], XML_DELIM, |
| "NoGPF"); |
| } |
| |
| template <size_t bufSize> |
| void |
| FillNoGPFFlags( |
| char (&nogpfFlags)[bufSize], |
| BOOL fSuppressNoGPF |
| ) |
| { |
| nogpfFlags[0] = '\0'; |
| if (FNogpfnt && TargetInfo[TargetMachine].fUseNoGPF) { |
| if (!fSuppressNoGPF) { |
| sprintf_s(nogpfFlags, |
| " %s\\bin\\%s\\nogpfnt.obj /entry:nogpfntStartup", |
| REGRESS, TargetInfo[TargetMachine].name); |
| } |
| } |
| } |
| |
| BOOL |
| CheckForPass(char * filename, char * optReportBuf, char * cmdbuf, BOOL fDumpOutputFile = TRUE) |
| { |
| FILE * fp; |
| char buf[BUFFER_SIZE]; |
| |
| // Check to see if the exe ran at all. |
| |
| fp = fopen_unsafe(filename, "r"); |
| if (fp == NULL) |
| { |
| LogOut("ERROR: Test failed to run. Unable to open file '%s', error '%s' (%s):", filename, strerror_unsafe(errno), optReportBuf); |
| LogOut(" %s", cmdbuf); |
| return FALSE; |
| } |
| |
| // Parse the output file and verify that all lines must be pass/passed, or empty lines |
| BOOL pass = FALSE; |
| while(fgets(buf, BUFFER_SIZE, fp) != NULL) |
| { |
| if(!_strcmpi(buf, "pass\n") || !_strcmpi(buf, "passed\n")) |
| { |
| // Passing strings were found - pass |
| pass = TRUE; |
| } |
| else if(_strcmpi(buf, "\n") != 0) |
| { |
| // Something else other than a newline was found - this is a failure. |
| pass = FALSE; |
| break; |
| } |
| } |
| |
| fclose(fp); |
| |
| if (!pass) |
| { |
| LogOut("ERROR: Test failed to run correctly: pass not found in output file (%s):", optReportBuf); |
| LogOut(" %s", cmdbuf); |
| if (fDumpOutputFile) |
| { |
| DumpFileToLog(filename); |
| } |
| } |
| |
| return pass; |
| } |
| |
| void CopyRebaseFile(PCSTR testout, PCSTR baseline) |
| { |
| if (FRebase) |
| { |
| char rebase[_MAX_PATH]; |
| sprintf_s(rebase, "%s.rebase", baseline); |
| CopyFile(testout, rebase, FALSE); |
| } |
| } |
| |
| // Handle external test scripts. We support three kinds, makefiles (rl.mak), |
| // command shell (dotest.cmd), and JScript (*.js). |
| // |
| // Standardized makefiles have the following targets: |
| // clean: delete all generated files |
| // build: build the test (OPT=compile options) |
| // run: run the test |
| // copy: copy the generated files to a subdirectory (COPYDIR=subdir) |
| // |
| int |
| DoOneExternalTest( |
| CDirectory* pDir, |
| TestVariant *pTestVariant, |
| const char *optFlags, |
| const char *inCCFlags, |
| const char *inLinkFlags, |
| const char *testCmd, |
| ExternalTestKind kind, |
| BOOL fSyncVariationWhenFinished, |
| BOOL fCleanBefore, |
| BOOL fCleanAfter, |
| BOOL fSuppressNoGPF, |
| DWORD millisecTimeout /* = INFINITE */, |
| uint32 timeoutRetries /* = 0 */, |
| void *envFlags /* = NULL */ |
| ) |
| { |
| #define NMAKE "nmake -nologo -R -f " |
| char full[MAX_PATH]; |
| char cmdbuf[BUFFER_SIZE]; |
| char buf[BUFFER_SIZE]; |
| char ccFlags[BUFFER_SIZE]; |
| char linkFlags[BUFFER_SIZE]; |
| char nogpfFlags[BUFFER_SIZE]; |
| char optReportBuf[BUFFER_SIZE]; |
| char nonZeroReturnBuf[BUFFER_SIZE]; |
| const char *reason = NULL; |
| time_t start_variation; |
| UINT elapsed_variation; |
| time_t start_build_variation; |
| UINT elapsed_build_variation; |
| LARGE_INTEGER start_run, end_run, frequency; |
| UINT elapsed_run; |
| BOOL fFailed = FALSE; |
| BOOL fDumpOutputFile = FVerbose; |
| BOOL fFileToDelete = FALSE; |
| int cmdResult; |
| static unsigned int testCount = 0; |
| unsigned int localTestCount = InterlockedIncrement(&testCount); |
| |
| // Avoid conditionals by copying/creating ccFlags appropriately. |
| |
| if (inCCFlags) |
| { |
| if (pDir->HasTestInfoData(TIK_SOURCE_PATH)) |
| { |
| sprintf_s(ccFlags, " %s -baselinePath:%s", inCCFlags, pDir->GetDirectoryPath()); |
| } |
| else |
| { |
| sprintf_s(ccFlags, " %s", inCCFlags); |
| } |
| } |
| else |
| { |
| ccFlags[0] = '\0'; |
| } |
| |
| switch (TargetMachine) |
| { |
| case TM_WVM: |
| case TM_WVMX86: |
| case TM_WVM64: |
| strcat_s(ccFlags, " /BC "); |
| break; |
| } |
| |
| if (inLinkFlags) |
| { |
| strcpy_s(linkFlags, inLinkFlags); |
| } |
| else |
| { |
| linkFlags[0] = '\0'; |
| } |
| |
| sprintf_s(optReportBuf, "%s%s%s", optFlags, *linkFlags ? ";" : "", linkFlags); |
| |
| // Update the status. |
| |
| sprintf_s(buf, " (%s)", optReportBuf); |
| ThreadInfo[ThreadId].SetCurrentTest(pDir->GetDirectoryName(), |
| buf, pDir->IsBaseline()); |
| UpdateTitleStatus(); |
| |
| // Make sure the file that will say pass or fail is not present. |
| |
| sprintf_s(full, "%s\\testout%d", pDir->GetDirectoryPath(), localTestCount); |
| DeleteFileIfFound(full); |
| |
| start_variation = time(NULL); |
| |
| if (kind == TK_MAKEFILE) |
| { |
| Message(""); // newline |
| Message("Processing %s with '%s' flags", testCmd, optReportBuf); |
| Message(""); // newline |
| |
| if (FTest) |
| { |
| return 0; |
| } |
| |
| if (fCleanBefore) |
| { |
| // Clean the directory. |
| sprintf_s(cmdbuf, NMAKE"%s clean", testCmd); |
| Message(cmdbuf); |
| ExecuteCommand(pDir->GetDirectoryPath(), cmdbuf); |
| } |
| |
| FillNoGPFFlags(nogpfFlags, fSuppressNoGPF); |
| |
| // Build the test. |
| |
| start_build_variation = time(NULL); |
| |
| sprintf_s(cmdbuf, NMAKE"%s build OPT=\"%s %s%s\" LINKFLAGS=\"%s %s %s\"", |
| testCmd, |
| optFlags, EXTRA_CC_FLAGS, ccFlags, |
| LINKFLAGS, linkFlags, nogpfFlags); |
| |
| if (strlen(cmdbuf) > BUFFER_SIZE - 1) |
| { |
| Fatal("Buffer overrun"); |
| } |
| |
| Message(cmdbuf); |
| fFailed = ExecuteCommand(pDir->GetDirectoryPath(), cmdbuf); |
| |
| elapsed_build_variation = (int)(time(NULL) - start_build_variation); |
| |
| if (Timing & TIME_VARIATION) |
| { |
| Message("RL: Variation elapsed time (build) (%s, %s, %s): %02d:%02d", |
| pDir->GetDirectoryName(), |
| "rl.mak", |
| optReportBuf, |
| elapsed_build_variation / 60, elapsed_build_variation % 60); |
| } |
| |
| if (fFailed) { |
| reason = "build failure"; |
| goto logFailure; |
| } |
| |
| // Run the test. |
| |
| QueryPerformanceCounter(&start_run); |
| |
| sprintf_s(cmdbuf, NMAKE"%s run", testCmd); |
| Message(cmdbuf); |
| cmdResult = ExecuteCommand(pDir->GetDirectoryPath(), cmdbuf, millisecTimeout); |
| |
| QueryPerformanceCounter(&end_run); |
| QueryPerformanceFrequency(&frequency); |
| elapsed_run = (int) (((end_run.QuadPart - start_run.QuadPart) * 1000UI64) / frequency.QuadPart); |
| |
| if (Timing & TIME_VARIATION) |
| { |
| Message("RL: Variation elapsed time (run) (%s, %s, %s): %02d:%02d.%03d", |
| pDir->GetDirectoryName(), |
| "rl.mak", |
| optReportBuf, |
| elapsed_run / 60000, (elapsed_run % 60000)/1000, elapsed_run % 1000); |
| } |
| } |
| else if (kind == TK_CMDSCRIPT) |
| { |
| |
| // Build up the test command string |
| |
| sprintf_s(cmdbuf, "%s %s %s%s >testout%d", testCmd, optFlags, EXTRA_CC_FLAGS, ccFlags, localTestCount); |
| |
| Message("Running '%s'", cmdbuf); |
| |
| if (FTest) |
| { |
| return 0; |
| } |
| cmdResult = ExecuteCommand(pDir->GetDirectoryPath(), cmdbuf, millisecTimeout, timeoutRetries, envFlags); |
| } |
| else if (kind == TK_JSCRIPT || kind==TK_HTML || kind == TK_COMMAND) |
| { |
| char tempExtraCCFlags[MAX_PATH*2] = {0}; |
| |
| // Only append when EXTRA_CC_FLAGS isn't empty. |
| if (EXTRA_CC_FLAGS[0]) |
| { |
| // Append test case unique identifier to the end of EXTRA_CC_FLAGS. |
| if (FAppendTestNameToExtraCCFlags) |
| { |
| sprintf_s(tempExtraCCFlags, "%s.%s", EXTRA_CC_FLAGS, pTestVariant->testInfo.data[TIK_FILES]); |
| } |
| else |
| { |
| strcpy_s(tempExtraCCFlags, EXTRA_CC_FLAGS); |
| } |
| } |
| |
| const char* cmd = JCBinary; |
| if (kind != TK_JSCRIPT && kind != TK_HTML) |
| { |
| cmd = pTestVariant->testInfo.data[TIK_COMMAND]; |
| } |
| |
| // |
| // If the test is a JS test and we've passed in a custom config file, |
| // ignore all of the other flags and just pass the config file in |
| // |
| if (kind == TK_JSCRIPT && pTestVariant->testInfo.data[TIK_CUSTOM_CONFIG_FILE] != nullptr) |
| { |
| sprintf_s(cmdbuf, "%s -CustomConfigFile:%s %s >%s 2>&1", cmd, pTestVariant->testInfo.data[TIK_CUSTOM_CONFIG_FILE], testCmd, full); |
| } |
| else |
| { |
| sprintf_s(cmdbuf, "%s %s %s %s %s >%s 2>&1", cmd, optFlags, tempExtraCCFlags, ccFlags, testCmd, full); |
| } |
| |
| Message("Running '%s'", cmdbuf); |
| |
| if (FTest) |
| { |
| DeleteFileIfFound(full); |
| return 0; |
| } |
| |
| cmdResult = ExecuteCommand(pDir->GetFullPathFromSourceOrDirectory(), cmdbuf, millisecTimeout, timeoutRetries, envFlags); |
| |
| if (cmdResult && cmdResult != WAIT_TIMEOUT && !pTestVariant->testInfo.data[TIK_BASELINE]) // failure code, not baseline diffing |
| { |
| fFailed = TRUE; |
| sprintf_s(nonZeroReturnBuf, "non-zero (%08X) return value from test command", cmdResult); |
| reason = nonZeroReturnBuf; |
| goto logFailure; |
| } |
| } |
| else |
| { |
| ASSERTNR(UNREACHED); |
| cmdResult = NOERROR; // calm compiler warning about uninitialized variable usage |
| } |
| |
| // Check for timeout. |
| |
| if (cmdResult == WAIT_TIMEOUT) |
| { |
| ASSERT(millisecTimeout != INFINITE); |
| sprintf_s(nonZeroReturnBuf, "timed out after %u second%s", millisecTimeout / 1000, millisecTimeout == 1000 ? "" : "s"); |
| reason = nonZeroReturnBuf; |
| fFailed = TRUE; |
| goto logFailure; |
| } |
| |
| // If we have a baseline test, we need to check the baseline file. |
| if (pTestVariant->testInfo.data[TIK_BASELINE]) |
| { |
| char baseline_file[_MAX_PATH]; |
| |
| sprintf_s(baseline_file, "%s\\%s", pDir->GetFullPathFromSourceOrDirectory(), |
| pTestVariant->testInfo.data[TIK_BASELINE]); |
| if (DoCompare(baseline_file, full, pTestVariant->testInfo.hasData[TIK_EOL_NORMALIZATION])) |
| { |
| reason = "diffs from baseline"; |
| sprintf_s(optReportBuf, "%s", baseline_file); |
| fFailed = TRUE; |
| CopyRebaseFile(full, baseline_file); |
| } |
| } |
| else if ((kind == TK_JSCRIPT || kind == TK_HTML || kind == TK_COMMAND) && !pTestVariant->testInfo.hasData[TIK_BASELINE]) |
| { |
| if (!CheckForPass(full, optReportBuf, cmdbuf, fDumpOutputFile)) |
| { |
| fFailed = TRUE; |
| goto SkipLogFailure; |
| } |
| } |
| |
| logFailure: |
| if (fFailed) |
| { |
| LogOut("ERROR: Test failed to run correctly: %s (%s):", reason, optReportBuf); |
| LogOut(" %s", cmdbuf); |
| if (fDumpOutputFile) { |
| DumpFileToLog(full); |
| } |
| if (FStopOnError) |
| { |
| GStopDueToError = TRUE; |
| } |
| } |
| |
| SkipLogFailure: |
| if (fFileToDelete && !FNoDelete) |
| { |
| DeleteFileRetryMsg(full); |
| } |
| |
| elapsed_variation = (int)(time(NULL) - start_variation); |
| if (Timing & TIME_VARIATION) |
| { |
| Message("RL: Variation elapsed time (%s, %s, %s): %02d:%02d", |
| pDir->GetDirectoryName(), |
| kind == TK_MAKEFILE ? "rl.mak" : "dotest.cmd", |
| optReportBuf, |
| elapsed_variation / 60, elapsed_variation % 60); |
| } |
| |
| if (kind == TK_MAKEFILE) |
| { |
| // If the test failed and we are asked to copy the failures, do so. |
| |
| if (fFailed && FCopyOnFail) |
| { |
| sprintf_s(cmdbuf, NMAKE"%s copy COPYDIR=\"fail.%s.%s\"", |
| testCmd, optFlags, linkFlags); |
| Message(cmdbuf); |
| ExecuteCommand(pDir->GetDirectoryPath(), cmdbuf); |
| } |
| |
| // Clean up after ourselves. |
| |
| if (!FNoDelete && (fFailed || fCleanAfter)) |
| { |
| sprintf_s(cmdbuf, NMAKE"%s clean", testCmd); |
| Message(cmdbuf); |
| ExecuteCommand(pDir->GetDirectoryPath(), cmdbuf); |
| } |
| } |
| |
| if (FSyncVariation) |
| { |
| if (FRLFE && fFailed) |
| { |
| RLFEAddLog(pDir, RLFES_FAILED, testCmd, |
| optReportBuf, ThreadOut->GetText()); |
| } |
| |
| if (fSyncVariationWhenFinished) |
| { |
| FlushOutput(); |
| } |
| } |
| DeleteFileIfFound(full); |
| |
| return fFailed ? -1 : 0; |
| } |
| |
| // null terminated string of null terminated strings |
| // name=data from testinfo and all of parent process env, arg for CreateProcess() |
| void * GetEnvFlags |
| ( |
| TestVariant * pTestVariant |
| ) |
| { |
| char temp[BUFFER_SIZE]; |
| size_t len = 0, totalEnvLen = 0; |
| char * envFlags = NULL; |
| Xml::Node *env = (Xml::Node *)pTestVariant->testInfo.data[TIK_ENV]; |
| if (env != NULL) { |
| // use a fixed global array for memory |
| memset(EnvFlags, '\0', MAX_ENV_LEN); |
| *temp = '\0'; |
| for ( Xml::Node * child = env->ChildList; child != NULL; child = child->Next) { |
| sprintf_s(temp, "%s=%s", child->Name, child->Data); |
| if (envFlags == NULL) { |
| sprintf_s(EnvFlags, "%s", temp); |
| envFlags = EnvFlags; |
| } else { |
| strcat_s(envFlags, REMAININGARRAYLEN(EnvFlags, envFlags), temp); |
| } |
| len = strlen(envFlags); |
| envFlags += len+1; |
| } |
| LPTSTR lpszParentEnv = GetEnvironmentStrings(); |
| totalEnvLen = len; |
| ASSERT(totalEnvLen < BUFFER_SIZE); |
| len = 0; |
| while(!((lpszParentEnv[len] == '\0') && (lpszParentEnv[len+1] == '\0'))) { |
| len++; |
| } |
| ASSERT(totalEnvLen+len+2 < MAX_ENV_LEN); |
| memcpy(envFlags, lpszParentEnv, len+2); |
| FreeEnvironmentStrings(lpszParentEnv); |
| envFlags = EnvFlags; |
| } |
| return (void*)envFlags; |
| } |
| |
| int |
| DoExternalTest( |
| CDirectory* pDir, |
| TestVariant * pTestVariant, |
| char *testCmd, |
| ExternalTestKind kind, |
| BOOL fSuppressNoGPF, |
| DWORD millisecTimeout, |
| uint32 timeoutRetries |
| ) |
| { |
| const char *ccFlags = pTestVariant->testInfo.data[TIK_COMPILE_FLAGS]; |
| void *envFlags = GetEnvFlags(pTestVariant); |
| return DoOneExternalTest(pDir, pTestVariant, pTestVariant->optFlags, ccFlags, NULL, |
| testCmd, kind, TRUE, TRUE, TRUE, fSuppressNoGPF, millisecTimeout, timeoutRetries, envFlags); |
| } |
| |
| int |
| DoPogoExternalTest( |
| CDirectory* pDir, |
| TestVariant * pTestVariant, |
| char *testCmd, |
| ExternalTestKind kind, |
| BOOL fSuppressNoGPF, |
| DWORD millisecTimeout, |
| uint32 timeoutRetries |
| ) |
| { |
| static const char *pgc = "*.pgc"; |
| static const char *pgd = POGO_PGD; |
| char pgdFull[MAX_PATH]; |
| char ccFlags[BUFFER_SIZE]; |
| char linkFlags[BUFFER_SIZE]; |
| char cmdbuf[BUFFER_SIZE]; |
| BOOL fFailed; |
| void *envFlags = GetEnvFlags(pTestVariant); |
| |
| sprintf_s(pgdFull, "%s\\%s", pDir->GetDirectoryPath(), pgd); |
| |
| const char * inCCFlags = pTestVariant->testInfo.data[TIK_COMPILE_FLAGS]; |
| const char * optFlags = pTestVariant->optFlags; |
| |
| DeleteFileIfFound(pgdFull); |
| DeleteMultipleFiles(pDir, pgc); |
| fFailed = FALSE; |
| |
| // Pogo requires LTCG |
| |
| ASSERT(strstr(optFlags, "GL") != NULL); |
| |
| if (!kind == TK_MAKEFILE) |
| { |
| Warning("'%s\\%s' is not a makefile test; Pogo almost certainly won't work", pDir->GetDirectoryPath(), testCmd); |
| } |
| |
| sprintf_s(ccFlags, "%s %s", PogoForceErrors, optFlags); |
| sprintf_s(linkFlags, "-ltcg:pgi -pgd:%s", pgd); |
| |
| if (DoOneExternalTest(pDir, pTestVariant, ccFlags, inCCFlags, linkFlags, |
| testCmd, kind, FALSE, TRUE, FALSE, fSuppressNoGPF, millisecTimeout, timeoutRetries, envFlags)) |
| { |
| fFailed = TRUE; |
| goto logFailure; |
| } |
| |
| sprintf_s(ccFlags, "%s %s", PogoForceErrors, optFlags); |
| sprintf_s(linkFlags, "-ltcg:pgo -pgd:%s", pgd); |
| |
| // Manually erase EXE and DLL files to get makefile to relink. |
| // Also erase ASM files because some makefiles try to rebuild from them. |
| |
| DeleteMultipleFiles(pDir, "*.exe"); |
| DeleteMultipleFiles(pDir, "*.dll"); |
| DeleteMultipleFiles(pDir, "*.asm"); |
| |
| if (DoOneExternalTest(pDir, pTestVariant, ccFlags, inCCFlags, linkFlags, |
| testCmd, kind, FALSE, FALSE, TRUE, fSuppressNoGPF, millisecTimeout, timeoutRetries, envFlags)) |
| { |
| fFailed = TRUE; |
| } |
| |
| logFailure: |
| if (FSyncVariation) |
| { |
| if (FRLFE && fFailed) |
| { |
| sprintf_s(cmdbuf, "%s%s%s", ccFlags, *linkFlags ? ";" : "", linkFlags); |
| RLFEAddLog(pDir, RLFES_FAILED, testCmd, cmdbuf, ThreadOut->GetText()); |
| } |
| |
| FlushOutput(); |
| } |
| |
| if (FRLFE) |
| { |
| RLFETestStatus(pDir); |
| } |
| |
| if (!FNoDelete) |
| { |
| DeleteFileRetryMsg(pgdFull); |
| DeleteMultipleFiles(pDir, pgc); |
| } |
| |
| return fFailed ? -1 : 0; |
| } |
| |
| BOOL |
| DoOneSimpleTest( |
| CDirectory *pDir, |
| Test * pTest, |
| TestVariant * pTestVariant, |
| const char *optFlags, |
| const char *inCCFlags, |
| const char *inLinkFlags, |
| BOOL fSyncVariationWhenFinished, |
| BOOL fCleanAfter, |
| BOOL fLinkOnly, // relink only |
| BOOL fSuppressNoGPF, |
| DWORD millisecTimeout, |
| uint32 timeoutRetries |
| ) |
| { |
| int rc; |
| char *p = NULL; |
| char cmdbuf[BUFFER_SIZE * 2]; |
| char ccFlags[BUFFER_SIZE]; |
| char linkFlags[BUFFER_SIZE]; |
| char nogpfFlags[BUFFER_SIZE]; |
| char optReportBuf[BUFFER_SIZE]; |
| char full[MAX_PATH]; |
| char exebuf[BUFFER_SIZE]; |
| char fullexebuf[BUFFER_SIZE]; |
| char buf[BUFFER_SIZE]; |
| char failDir[BUFFER_SIZE]; |
| char copyName[BUFFER_SIZE]; |
| char tmp_file1[MAX_PATH]; |
| char tmp_file2[MAX_PATH]; |
| time_t start_variation; |
| UINT elapsed_variation; |
| BOOL fFailed; |
| void *envFlags = GetEnvFlags(pTestVariant); |
| |
| // Avoid conditionals by copying/creating ccFlags appropriately. |
| |
| if (inCCFlags) |
| { |
| sprintf_s(ccFlags, " %s", inCCFlags); |
| } |
| else |
| { |
| ccFlags[0] = '\0'; |
| } |
| |
| switch (TargetMachine) |
| { |
| case TM_WVM: |
| case TM_WVMX86: |
| case TM_WVM64: |
| strcat_s(ccFlags, " /BC "); |
| break; |
| } |
| |
| if (inLinkFlags) |
| { |
| strcpy_s(linkFlags, inLinkFlags); |
| } |
| else |
| { |
| linkFlags[0] = '\0'; |
| } |
| |
| sprintf_s(optReportBuf, "%s%s%s", optFlags, *linkFlags ? ";" : "", linkFlags); |
| |
| // Figure out the exe name and path |
| |
| strcpy_s(exebuf, pTest->name); |
| p = strrchr(exebuf, '.'); |
| if (p != NULL) |
| { |
| strcpy_s(p + 1, REMAININGARRAYLEN(exebuf, p + 1), "exe"); |
| } |
| else |
| { |
| strcat_s(exebuf, ".exe"); |
| } |
| |
| sprintf_s(fullexebuf, "%s\\%s", pDir->GetDirectoryPath(), exebuf); |
| |
| start_variation = time(NULL); |
| |
| // Build up the compile command string. |
| |
| sprintf_s(cmdbuf, "%s %s%s %s", REGR_CL, optFlags, ccFlags, EXTRA_CC_FLAGS); |
| |
| for (StringList * pFile = pTest->files; pFile != NULL; pFile = pFile->next) |
| { |
| strcat_s(cmdbuf, " "); |
| strcat_s(cmdbuf, pFile->string); |
| |
| // If we're only relinking, hammer the extension to .obj |
| |
| if (fLinkOnly) |
| { |
| p = strrchr(cmdbuf, '.'); |
| sprintf_s(p, REMAININGARRAYLEN(cmdbuf, p), ".obj"); |
| } |
| } |
| |
| // Build the link option string. |
| |
| if (LINKFLAGS && LINKFLAGS[0] != '\0') |
| { |
| strcat_s(linkFlags, " "); |
| strcat_s(linkFlags, LINKFLAGS); |
| } |
| |
| FillNoGPFFlags(nogpfFlags, fSuppressNoGPF); |
| strcat_s(linkFlags, nogpfFlags); |
| |
| switch (TargetMachine) |
| { |
| case TM_X86: |
| case TM_IA64: |
| case TM_AMD64: |
| case TM_AMD64SYS: |
| case TM_AM33: |
| case TM_ARM: |
| case TM_ARM64: |
| case TM_THUMB: |
| case TM_M32R: |
| case TM_MIPS: |
| case TM_SH3: |
| case TM_SH4: |
| case TM_SH5M: |
| case TM_SH5C: |
| case TM_WVMX86: |
| if (*linkFlags) |
| { |
| strcat_s(cmdbuf, " /link "); |
| strcat_s(cmdbuf, linkFlags); |
| } |
| break; |
| case TM_WVM: |
| strcat_s(cmdbuf, " /c "); |
| break; |
| case TM_PPCWCE: |
| if (*linkFlags) |
| { |
| strcat_s(cmdbuf, " "); |
| strcat_s(cmdbuf, linkFlags); |
| } |
| break; |
| } |
| |
| sprintf_s(buf, "%s (%s)", pTest->name, optReportBuf); |
| ThreadInfo[ThreadId].SetCurrentTest(pDir->GetDirectoryName(), buf, pDir->IsBaseline()); |
| UpdateTitleStatus(); |
| |
| // Remove exe if it's already there. We have to keep trying to delete it |
| // until it's gone, or else the link will fail (if it is somehow still in |
| // use). |
| |
| DeleteFileRetryMsg(fullexebuf); |
| |
| if (FTest) |
| { |
| Message("%s", cmdbuf); |
| if (pTestVariant->testInfo.data[TIK_BASELINE]) |
| { |
| Message(" (baseline %s)", pTestVariant->testInfo.data[TIK_BASELINE]); |
| } |
| return 0; |
| } |
| |
| // Do the compile. |
| |
| Message("Compiling:"); |
| Message(" %s", cmdbuf); |
| |
| rc = ExecuteCommand(pDir->GetDirectoryPath(), cmdbuf); |
| |
| // Some machines require separate linking of the |
| // compiler and/or assembler output. |
| |
| if (rc == 0) |
| { |
| switch (TargetMachine) |
| { |
| case TM_WVM: |
| // Build up the linker command string. |
| |
| strcpy_s(cmdbuf, LINKER); |
| |
| for (StringList * pFile = pTest->files; |
| pFile != NULL; |
| pFile = pFile->next) |
| { |
| strcat_s(cmdbuf, " "); |
| strcat_s(cmdbuf, pFile->string); |
| p = strrchr(cmdbuf, '.'); |
| strcpy_s(p + 1, REMAININGARRAYLEN(cmdbuf, p + 1), "obj"); |
| } |
| |
| if (linkFlags) { |
| strcat_s(cmdbuf, " "); |
| strcat_s(cmdbuf, linkFlags); |
| } |
| |
| // Do the link. |
| |
| Message("Linking:"); |
| Message(" %s", cmdbuf); |
| ExecuteCommand(pDir->GetDirectoryPath(), cmdbuf); |
| break; |
| |
| default: |
| break; |
| } |
| } |
| |
| // See if the compile succeeded by checking for the existence |
| // of the executable. |
| |
| if ((rc != 0) || GetFileAttributes(fullexebuf) == INVALID_FILE_ATTRIBUTES) |
| { |
| LogOut("ERROR: Test failed to compile or link (%s):", optReportBuf); |
| LogOut(" %s", cmdbuf); |
| fFailed = TRUE; |
| goto logFailure; |
| } |
| |
| // Run the resulting exe. |
| |
| if (TargetVM) |
| { |
| strcpy_s(buf, TargetVM); |
| strcat_s(buf, " "); |
| strcat_s(buf, exebuf); |
| |
| // Copy the VM command to cmdbuf, so we get a useful error message |
| // in the log file if test fails. |
| |
| strcpy_s(cmdbuf, buf); |
| } |
| else |
| { |
| strcpy_s(buf, exebuf); |
| } |
| |
| // We need some temporary files. |
| // Note: these are full pathnames, not relative pathnames. Also, note that |
| // mytmpnam creates the file to be used. To avoid losing that file, and |
| // risking another program using it, don't delete the file before use. |
| // We currently delete the file before running the test, so we can see if |
| // the test ever creates it. We should probably create the temp files in |
| // the same directory as the test, since we guarantee that no other copies |
| // of RL are running in the same directory. |
| |
| if (mytmpnam(pDir->GetDirectoryPath(), TMP_PREFIX, tmp_file1) == NULL || |
| mytmpnam(pDir->GetDirectoryPath(), TMP_PREFIX, tmp_file2) == NULL) |
| { |
| Fatal("Unable to create temporary files"); |
| } |
| |
| ThreadInfo[ThreadId].AddToTmpFileList(tmp_file1); |
| ThreadInfo[ThreadId].AddToTmpFileList(tmp_file2); |
| |
| if (FVerbose) |
| { |
| Message("INFO: tmp file 1 = %s, tmp file 2 = %s", tmp_file1, tmp_file2); |
| } |
| |
| Message("Running the test (%s)", buf); |
| strcat_s(buf, " > "); |
| strcat_s(buf, tmp_file1); |
| |
| // Make sure the output file isn't there. |
| |
| DeleteFileIfFound(tmp_file1); |
| |
| fFailed = FALSE; |
| |
| // Check for timeout. |
| { |
| int retval = ExecuteCommand(pDir->GetDirectoryPath(), buf, millisecTimeout, timeoutRetries, envFlags); |
| if (retval == WAIT_TIMEOUT) |
| { |
| ASSERT(millisecTimeout != INFINITE); |
| LogOut("ERROR: Test timed out after %ul seconds", millisecTimeout / 1000); |
| fFailed = TRUE; |
| goto logFailure; |
| } |
| } |
| |
| // Check the output. |
| |
| if (pTestVariant->testInfo.data[TIK_BASELINE]) |
| { |
| int spiff_ret; |
| |
| // Check to see if the exe ran at all. |
| |
| if (GetFileAttributes(tmp_file1) == INVALID_FILE_ATTRIBUTES) |
| { |
| LogOut("ERROR: Test failed to run. Couldn't find file '%s' (%s):", tmp_file1, optReportBuf); |
| LogOut(" %s", cmdbuf); |
| fFailed = TRUE; |
| } |
| else |
| { |
| sprintf_s(full, "%s\\%s", pDir->GetFullPathFromSourceOrDirectory(), pTestVariant->testInfo.data[TIK_BASELINE]); |
| |
| if (DoCompare(tmp_file1, full, pTestVariant->testInfo.hasData[TIK_EOL_NORMALIZATION])) |
| { |
| // Output differs, run spiff to see if it's just minor |
| // floating point anomalies. |
| |
| DeleteFileIfFound(tmp_file2); |
| sprintf_s(buf, "spiff -m -n -s \"command spiff\" %s %s > %s", tmp_file1, full, tmp_file2); |
| spiff_ret = ExecuteCommand(pDir->GetDirectoryPath(), buf); |
| if (GetFileAttributes(tmp_file2) == INVALID_FILE_ATTRIBUTES) |
| { |
| LogError("ERROR: spiff failed to run"); |
| fFailed = TRUE; |
| } |
| else if (spiff_ret) |
| { |
| LogOut("ERROR: Test failed to run correctly. spiff returned %d (%s):", spiff_ret, optReportBuf); |
| LogOut(" %s", cmdbuf); |
| fFailed = TRUE; |
| } |
| } |
| } |
| } |
| else |
| { |
| if (!CheckForPass(tmp_file1, optReportBuf, cmdbuf)) |
| { |
| fFailed = TRUE; |
| } |
| } |
| |
| logFailure: |
| |
| if (fFailed) |
| { |
| if (FCopyOnFail) |
| { |
| if (FVerbose) |
| { |
| Message("INFO: Copying '%s' failure", optReportBuf); |
| } |
| |
| sprintf_s(failDir, "%s\\fail.%s", pDir->GetDirectoryPath(), optReportBuf); |
| |
| if ((GetFileAttributes(failDir) == INVALID_FILE_ATTRIBUTES) && |
| !CreateDirectory(failDir, NULL)) |
| { |
| Message("ERROR: Couldn't create directory '%s'", failDir); |
| } |
| else |
| { |
| for (StringList * pFile = pTest->files; |
| pFile != NULL; |
| pFile = pFile->next) |
| { |
| sprintf_s(copyName, "%s\\%s", failDir, pFile->string); |
| p = strrchr(copyName, '.') + 1; |
| strcpy_s(p, REMAININGARRAYLEN(copyName, p + 1), "obj"); |
| sprintf_s(buf, "%s\\%s", pDir->GetDirectoryPath(), pFile->string); |
| p = strrchr(buf, '.') + 1; |
| strcpy_s(p, REMAININGARRAYLEN(buf, p + 1), "obj"); |
| |
| if (!CopyFile(buf, copyName, FALSE)) |
| { |
| Message("ERROR: Couldn't copy '%s' to '%s'", buf, copyName); |
| } |
| } |
| |
| sprintf_s(copyName, "%s\\%s", failDir, exebuf); |
| if (!CopyFile(fullexebuf, copyName, FALSE)) |
| { |
| Message("ERROR: Couldn't copy '%s' to '%s'", fullexebuf, copyName); |
| } |
| } |
| } |
| } |
| |
| if (FRLFE) |
| { |
| RLFETestStatus(pDir); |
| } |
| |
| if (FVerbose) |
| { |
| Message("INFO: cleaning up test run"); |
| } |
| |
| // Remove the exe. |
| |
| if (!FNoDelete) |
| { |
| DeleteFileRetryMsg(fullexebuf); |
| } |
| |
| // Don't trash fullexebuf! |
| |
| strcpy_s(buf, fullexebuf); |
| |
| p = strrchr(buf, '.') + 1; |
| |
| // Remove the pdb(s) (if it exists). |
| |
| strcpy_s(p, REMAININGARRAYLEN(buf, p), "pdb"); |
| DeleteFileIfFound(buf); |
| DeleteMultipleFiles(pDir, "*.pdb"); |
| |
| // Remove the ilk (if it exists). |
| |
| strcpy_s(p, REMAININGARRAYLEN(buf, p), "ilk"); |
| DeleteFileIfFound(buf); |
| |
| // Remove the objs. |
| |
| if (!FNoDelete) |
| { |
| for (StringList * pFile = pTest->files; |
| pFile != NULL; |
| pFile = pFile->next) |
| { |
| sprintf_s(buf, "%s\\%s", pDir->GetDirectoryPath(), pFile->string); |
| p = strrchr(buf, '.') + 1; |
| |
| if (fCleanAfter) |
| { |
| strcpy_s(p, REMAININGARRAYLEN(buf, p), "obj"); |
| DeleteFileRetryMsg(buf); |
| } |
| |
| if (REGR_ASM) |
| { |
| strcpy_s(p, REMAININGARRAYLEN(buf, p), "asm"); |
| DeleteFileRetryMsg(buf); |
| } |
| } |
| } |
| |
| elapsed_variation = (int)(time(NULL) - start_variation); |
| if (Timing & TIME_VARIATION) |
| { |
| Message("RL: Variation elapsed time (%s, %s, %s): %02d:%02d", |
| pDir->GetDirectoryName(), pTest->name, optReportBuf, |
| elapsed_variation / 60, elapsed_variation % 60); |
| } |
| |
| if (FSyncVariation) |
| { |
| if (FRLFE && fFailed) |
| { |
| RLFEAddLog(pDir, RLFES_FAILED, pTest->name, optReportBuf, ThreadOut->GetText()); |
| } |
| |
| if (fSyncVariationWhenFinished) |
| { |
| FlushOutput(); |
| } |
| } |
| |
| ThreadInfo[ThreadId].DeleteTmpFileList(); |
| |
| return fFailed ? -1 : 0; |
| } |
| |
| int |
| DoSimpleTest( |
| CDirectory *pDir, |
| Test * pTest, |
| TestVariant * pTestVariant, |
| BOOL fSuppressNoGPF, |
| DWORD millisecTimeout, |
| uint32 timeoutRetries |
| ) |
| { |
| return DoOneSimpleTest(pDir, pTest, pTestVariant, pTestVariant->optFlags, |
| pTestVariant->testInfo.data[TIK_COMPILE_FLAGS], |
| pTestVariant->testInfo.data[TIK_LINK_FLAGS], |
| TRUE, TRUE, FALSE, fSuppressNoGPF, millisecTimeout, timeoutRetries); |
| } |
| |
| int |
| DoPogoSimpleTest( |
| CDirectory *pDir, |
| Test * pTest, |
| TestVariant * pTestVariant, |
| BOOL fSuppressNoGPF, |
| DWORD millisecTimeout, |
| uint32 timeoutRetries |
| ) |
| { |
| static const char *pgc = "*.pgc"; |
| static const char *pgd = POGO_PGD; |
| char pgdFull[MAX_PATH]; |
| char ccFlags[BUFFER_SIZE]; |
| char linkFlags[BUFFER_SIZE]; |
| BOOL fFailed; |
| |
| sprintf_s(pgdFull, "%s\\%s", pDir->GetDirectoryPath(), pgd); |
| |
| const char * inCCFlags = pTestVariant->testInfo.data[TIK_COMPILE_FLAGS]; |
| const char * optFlags = pTestVariant->optFlags; |
| |
| DeleteFileIfFound(pgdFull); |
| DeleteMultipleFiles(pDir, pgc); |
| fFailed = FALSE; |
| |
| // Pogo requires LTCG |
| |
| ASSERT(strstr(optFlags, "GL") != NULL); |
| |
| sprintf_s(ccFlags, "%s %s", PogoForceErrors, optFlags); |
| sprintf_s(linkFlags, "-ltcg:pgi -pgd:%s", pgd); |
| |
| if (DoOneSimpleTest(pDir, pTest, pTestVariant, ccFlags, inCCFlags, |
| linkFlags, FALSE, FALSE, FALSE, fSuppressNoGPF, millisecTimeout, timeoutRetries)) |
| { |
| fFailed = TRUE; |
| goto logFailure; |
| } |
| |
| if (FTest) |
| { |
| return 0; |
| } |
| |
| sprintf_s(ccFlags, "%s %s", PogoForceErrors, optFlags); |
| sprintf_s(linkFlags, "-ltcg:pgo -pgd:%s", pgd); |
| |
| if (DoOneSimpleTest(pDir, pTest, pTestVariant, ccFlags, inCCFlags, |
| linkFlags, FALSE, TRUE, TRUE, fSuppressNoGPF, millisecTimeout, timeoutRetries)) |
| { |
| fFailed = TRUE; |
| } |
| |
| logFailure: |
| |
| if (FSyncVariation) |
| { |
| #if 0 |
| if (FRLFE && fFailed) { |
| sprintf_s(cmdbuf, "%s%s%s", ccFlags, |
| *linkFlags ? ";" : "", linkFlags); |
| RLFEAddLog(pDir, RLFES_FAILED, testCmd, |
| cmdbuf, ThreadOut->GetText()); |
| } |
| #endif |
| |
| FlushOutput(); |
| } |
| |
| if (!FNoDelete) |
| { |
| DeleteFileRetryMsg(pgdFull); |
| DeleteMultipleFiles(pDir, pgc); |
| } |
| |
| return fFailed ? -1 : 0; |
| } |
| |
| int |
| ExecTest( |
| CDirectory* pDir, |
| Test * pTest, |
| TestVariant * pTestVariant |
| ) |
| { |
| char *p = NULL; |
| char full[MAX_PATH]; |
| DWORD millisecTimeout = DEFAULT_TEST_TIMEOUT; |
| uint32 timeoutRetries = DEFAULT_TEST_TIMEOUT_RETRIES; |
| const char *strTimeout = pTestVariant->testInfo.data[TIK_TIMEOUT]; |
| const char *strTimeoutRetries = pTestVariant->testInfo.data[TIK_TIMEOUT_RETRIES]; |
| |
| if (strTimeout) |
| { |
| char *end = nullptr; |
| _set_errno(0); |
| uint32 secTimeout = strtoul(strTimeout, &end, 10); |
| millisecTimeout = 1000 * secTimeout; |
| // Validation has already occurred so this string should |
| // parse fine and the value shouldn't overflow the DWORD. |
| ASSERT(errno == 0 && *end == '\0'); |
| ASSERT(millisecTimeout > secTimeout); |
| } |
| |
| if (strTimeoutRetries) |
| { |
| char *end = nullptr; |
| _set_errno(0); |
| timeoutRetries = strtoul(strTimeoutRetries, &end, 10); |
| // Validation has already occurred so this string should parse fine. |
| ASSERT(errno == 0 && *end == '\0'); |
| } |
| |
| // Check to see if all of the files exist. |
| |
| for (StringList * pFile = pTest->files; pFile != NULL && !GStopDueToError; pFile = pFile->next) |
| { |
| // Get a pointer to the filename sans path, if present. |
| |
| p = GetFilenamePtr(pFile->string); |
| |
| // If we have no pathname, use the current directory. |
| |
| if (p == pFile->string) |
| { |
| sprintf_s(full, "%s\\", pDir->GetFullPathFromSourceOrDirectory()); |
| } |
| else |
| { |
| // Look for %REGRESS% specifier. |
| |
| if (!_strnicmp(pFile->string, "%REGRESS%", strlen("%REGRESS%"))) |
| { |
| // Temporarily truncate the filename. |
| |
| ASSERT(p[-1] == '\\'); |
| p[-1] = '\0'; |
| sprintf_s(full, "%s%s\\", REGRESS, pFile->string + strlen("%REGRESS%")); |
| p[-1] = '\\'; |
| } |
| else |
| { |
| *p = '\0'; |
| } |
| } |
| |
| strcat_s(full, p); |
| |
| if (GetFileAttributes(full) == INVALID_FILE_ATTRIBUTES) |
| { |
| LogError("ERROR: '%s' does not exist", full); |
| return -1; |
| } |
| } |
| |
| const char* ext = GetFilenameExt(p); |
| |
| // Special case dotest.cmd |
| if (!_stricmp(p, "dotest.cmd")) |
| { |
| // We don't handle these yet. |
| |
| ASSERTNR(pTestVariant->testInfo.data[TIK_LINK_FLAGS] == NULL); |
| |
| if (IsPogoTest(pTest)) |
| { |
| return DoPogoExternalTest(pDir, pTestVariant, full, TK_CMDSCRIPT, TRUE, millisecTimeout, timeoutRetries); |
| } |
| else |
| { |
| return DoExternalTest(pDir, pTestVariant, full, TK_CMDSCRIPT, TRUE, millisecTimeout, timeoutRetries); |
| } |
| } |
| |
| // Special case for standardized RL makefiles. |
| else if (!_stricmp(p, "rl.mak")) |
| { |
| // We don't handle these yet. |
| |
| ASSERTNR(pTestVariant->testInfo.data[TIK_LINK_FLAGS] == NULL); |
| |
| if (IsPogoTest(pTest)) |
| { |
| return DoPogoExternalTest(pDir, pTestVariant, full, TK_MAKEFILE, FALSE, millisecTimeout, timeoutRetries); |
| } |
| else |
| { |
| return DoExternalTest(pDir, pTestVariant, full, TK_MAKEFILE, SuppressNoGPF(pTest), millisecTimeout, timeoutRetries); |
| } |
| } |
| |
| // Special case for files ending with ".js", ".html", ".htm" (<command> dealt with separately) |
| else if (pTestVariant->testInfo.data[TIK_COMMAND] == NULL |
| && !_stricmp(ext, ".js")) |
| { |
| return DoExternalTest(pDir, pTestVariant, full, TK_JSCRIPT, FALSE, millisecTimeout, timeoutRetries); |
| } |
| |
| else if (pTestVariant->testInfo.data[TIK_COMMAND] == NULL |
| && (!_stricmp(ext, ".html") || !_stricmp(ext, ".htm"))) |
| { |
| return DoExternalTest(pDir, pTestVariant, full, TK_HTML, FALSE, millisecTimeout, timeoutRetries); |
| } |
| |
| // Special case for tests with a <command> argument |
| else if (pTestVariant->testInfo.data[TIK_COMMAND] != NULL) |
| { |
| return DoExternalTest(pDir, pTestVariant, full, TK_COMMAND, FALSE, millisecTimeout, timeoutRetries); |
| } |
| |
| // Non-scripted test. |
| else |
| { |
| if (IsPogoTest(pTest)) |
| { |
| // We don't handle these yet. |
| |
| ASSERTNR(pTestVariant->testInfo.data[TIK_LINK_FLAGS] == NULL); |
| |
| return DoPogoSimpleTest(pDir, pTest, pTestVariant, FALSE, millisecTimeout, timeoutRetries); |
| } |
| else |
| { |
| return DoSimpleTest(pDir, pTest, pTestVariant, SuppressNoGPF(pTest), millisecTimeout, timeoutRetries); |
| } |
| } |
| } |