| //------------------------------------------------------------------------------------------------------- |
| // Copyright (C) Microsoft. All rights reserved. |
| // Licensed under the MIT license. See LICENSE.txt file in the project root for full license information. |
| //------------------------------------------------------------------------------------------------------- |
| // rl.cpp |
| // To build: |
| // nmake RELEASE=1 rl.exe |
| // |
| // Julian Burger |
| // Microsoft Corporation |
| // 11/06/98 |
| |
| // Configuration files are XML |
| // |
| // rl{asm,exe}dirs.xml have directory lists |
| // rl{asm,exe}.xml have test lists |
| // |
| // General format: |
| // |
| // <?xml version="1.0"?> |
| // |
| // <regress-asm> // head node, tag unimportant |
| // |
| // <test> // test node, tag unimportant |
| // |
| // <default> // default test information |
| // |
| // test info // see below |
| // |
| // </default> |
| // |
| // <condition order="1" type="exclude"> // condition node |
| // |
| // <target>...</target> // list of targets this condition applies to |
| // |
| // </condition> |
| // |
| // </test> |
| // |
| // |
| // Test info (valid for all configuration files): |
| // |
| // <files>...</files> // list of comma-delimited files |
| // |
| // For asm regressions, files may be grouped in a single test node for |
| // convenience. For exe regressions, all the files comprising a single test |
| // should be group. |
| // |
| // <tags>...</tags> // list of comma-delimited (case-insensitive) tags |
| // |
| // Tags are intended as a proper of the test case itself. That is, they |
| // should be used to indicate features of a test (like C++ EH or x86 asm) |
| // rather than arbitrary nonsense. |
| // |
| // One tag is recognized specifically by RL: "Pogo". This indicates the test |
| // is a Pogo test and should be run specially. Any other tag is for explicit |
| // inclusion/exclusion of tests. E.g. |
| // |
| // <tags>SEH</tags> |
| // |
| // This marks a test with the SEH tag. If -tags:SEH is specified on the command |
| // line, only tests marked as such would be included. Similarly, -nottags:SEH |
| // would exclude tests so tagged. |
| // |
| // Test info (valid for asm and exe test configuration files): |
| // |
| // <compile-flags>...</compile-flags> // compilation flags for this test |
| // <rl>...</rl> // RL directives (case-insensitive) |
| // |
| // Test info (valid for exe test configuration files) |
| // |
| // <baseline>...</baseline> // baseline, expected test output |
| // <link-flags>...</link-flags> // link flags for this test |
| // <env> // extra env vars for this test |
| // <envvar1>value1</envvar1> // any number of child nodes with arbitrary data |
| // ... // <build>release</build> |
| // // will result in "set build=release" for the test task env |
| // </env> |
| // |
| // <condition> nodes have an explicit order of evaluation and type (action to |
| // take if the condition applies) which can be either "include" or "exclude". |
| // |
| // asm and exe test conditions may contain <override> nodes that allow default |
| // info to be overridden. E.g. |
| // |
| // <condition order="1" type="include"> |
| // |
| // <target>ia64</target> |
| // |
| // <override> |
| // |
| // <baseline>ia64-result.out</baseline> |
| // |
| // </override> |
| // |
| // </condition> |
| // |
| // The above would override the default baseline with "ia64-result.out" when |
| // the target is ia64. |
| // |
| // |
| // Currently, condition nodes may have only a single condition (target or |
| // compile-flags) and non-target conditions must appear after target |
| // conditions. |
| |
| |
| // Tags processing |
| // |
| // Tags specified in the same command line argument are treated as OR clauses. |
| // Multiple -tags (and -nottags) switches are permitted and are processed in |
| // command line order as AND clauses. E.g. |
| // |
| // -tags:Pogo -nottags:SEH,CPPEH -tags:asm |
| // |
| // The -tags declarations says: match all tests with "Pogo" tags. From that |
| // set, exclude those that have "SEH" or "CPPEH". The next -tags arg further |
| // restricts the set to those with "asm". (This is a contrived example.) |
| // |
| // Note that -tags/-nottags are only applied to files. Directories may not |
| // have tags because they are too error prone. |
| |
| |
| // Directives |
| // |
| // The following directives are recognized in the <rl>...</rl> section of a |
| // test's default info: |
| // |
| // PassFo |
| // NoAsm |
| // NoGPF |
| |
| // Log file format |
| // |
| // There are three output files generated by default |
| // 1. rl.log contains the summary of tests passed and failed in each dir |
| // 2. rl.results.log contains the passed/failed result of each test variation |
| // 3. rl.full.log contains all the output from all the tests, |
| // delimited by +++ <test variation> +++ |
| // Notes: |
| // 1. The test output in these files is not synchronized on MP, |
| // without the -sync option. |
| // 2. rl.results.log can be used as a baseline database to track regressions, |
| // if -time is not used. |
| |
| // -genlst mode and lst file format |
| // |
| // In -genlst -all mode, rl won't run tests but generate test.lst and env.lst |
| // containing the same info as in the rlexedirs.xml and rlexe.xml files |
| // These files are then read by the runall test harness to run the tests. |
| // Please refer to the runall documentation for the lst file format. |
| |
| // By default, rl spawns as many threads to execute tests as there are |
| // processors on the system. Only one thread works per directory because |
| // running multiple tests in a single directory may cause conflicts in the |
| // presence of startdir.cmd/enddir.cmd and precompiled header flags (e.g., |
| // /YX). The primary thread generates a work queue of directories and files to |
| // test in each directory. As soon as the tests in a directory are determined, |
| // the directory becomes available for worker threads. |
| // |
| // One complication is that in Windows there is only a single "current |
| // directory" per process. Thus, we need to synchronize as follows: |
| // |
| // 1. Worker threads grab the "directory" critical section immediately before |
| // spawning a process (e.g., compile, link, etc.), |
| // |
| // 2. The process is spawned with the correct current directory, which it |
| // inherits. |
| // |
| // 3. The lock is released and the next worker is free to change the directory. |
| // |
| // 4. The primary thread never changes the current directory, to avoid |
| // synchronization issues. |
| // |
| // 5. Any file access (fopen_unsafe, etc.) must use full pathnames. The only place |
| // relative paths may be used is in commands that are executed via |
| // ExecuteCommand(), which does a _chdir before creating a new process to |
| // execute the command. |
| // |
| // 6. Global variables used by worker threads must be thread-local or |
| // properly synchronized. Exceptions are: (a) variables only read, not |
| // written, by the worker threads, (b) bThreadStop, which is only written |
| // to TRUE by worker threads, so synchronization is not necessary. |
| // |
| // 7. All printf/fprintf output from the worker threads must go through |
| // the COutputBuffer class, to be properly synchronized. This class buffers |
| // up output per thread and flushes it at reasonable intervals, normally |
| // after a test is finished running. This includes any output generated |
| // by rl as well as any output captured by the pipe output filter on |
| // spawned processes. |
| // |
| // 8. Do not use _putenv() in the worker threads, as it sets process-wide |
| // state. |
| |
| |
| #include "rl.h" |
| #include "strsafe.h" |
| |
| #pragma warning(disable: 4474) // 'fprintf' : too many arguments passed for format string |
| |
| // Win64 headers (process.h) has this: |
| #ifndef _INTPTR_T_DEFINED |
| #ifdef _WIN64 |
| typedef __int64 intptr_t; |
| #else |
| typedef int intptr_t; |
| #endif |
| #define _INTPTR_T_DEFINED |
| #endif |
| |
| // Target machine information |
| // Any changes made here should have corresponding name added |
| // to TARGET_MACHINES in rl.h. NOTE: The TARGET_MACHINES enum is in exactly |
| // the same order as the TargetInfo array is initialized! |
| |
| TARGETINFO TargetInfo[] = { |
| "unknown", TRUE, FALSE, FALSE, NULL, NULL, NULL, |
| "x86", FALSE, FALSE, TRUE, NULL, NULL, NULL, |
| "ppcwce", FALSE, FALSE, FALSE, "comcli", NULL, "x86asm", |
| "wvm", FALSE, FALSE, FALSE, NULL, "vccoree.lib", "x86asm", |
| "wvmcee", FALSE, FALSE, FALSE, NULL, NULL, "x86asm", |
| "wvmx86", FALSE, FALSE, FALSE, NULL, NULL, "x86asm", |
| "mips", FALSE, FALSE, FALSE, NULL, NULL, "x86asm", |
| "arm", FALSE, FALSE, FALSE, "armsd", "libcnoce.lib", "x86asm", |
| "thumb", FALSE, FALSE, FALSE, "armsd", "libcnocet.lib", "x86asm", |
| "arm64", FALSE, FALSE, FALSE, "armsd", "libcnoce.lib", "x86asm", |
| "sh3", FALSE, FALSE, FALSE, "sh3sim", "libcsim.lib", "x86asm", |
| "sh4", FALSE, FALSE, FALSE, "sh3sim", "libcsim.lib", "x86asm", |
| "sh5c", FALSE, FALSE, FALSE, "sh5sim", "libcsim.lib", "x86asm", |
| "sh5m", FALSE, FALSE, FALSE, "sh5sim", "libcsim.lib", "x86asm", |
| "ia64", FALSE, TRUE, TRUE, NULL, NULL, "x86asm", |
| "amd64", FALSE, TRUE, TRUE, NULL, NULL, "x86asm", |
| "amd64sys", FALSE, FALSE, TRUE, "issuerun", NULL, "x86asm", |
| "wvm64", FALSE, FALSE, FALSE, NULL, NULL, "x86asm", |
| "am33", FALSE, FALSE, FALSE, NULL, NULL, "x86asm", |
| "m32r", FALSE, FALSE, FALSE, NULL, NULL, "x86asm", |
| "msil", FALSE, FALSE, FALSE, NULL, NULL, "x86asm", |
| NULL |
| }; |
| |
| // Target OS information |
| // Any changes made here should have corresponding name added |
| // to TARGET_OS in rl.h. NOTE: The TARGET_OS enum is in exactly |
| // the same order as the TargetOSNames array is initialized |
| const char* const TargetOSNames[] = { |
| "unknown", |
| "win7", |
| "win8", |
| "winBlue", |
| "win10", |
| "wp8", |
| NULL |
| }; |
| |
| #define IS_PATH_CHAR(c) ((c == '/') || (c == '\\')) |
| #define LOG_FULL_SUFFIX ".full" |
| |
| |
| const char * const ModeNames[] = { |
| "assembly", "executable", "shouldn't be printed (RM_DIR)" |
| }; |
| const char * const InternalModeCmd[] = { |
| "<asm regress>", "<exe regress>" |
| }; |
| |
| const char * const TestInfoKindName[] = |
| { |
| "files", |
| "baseline", |
| "compile-flags", |
| "link-flags", |
| "tags", |
| "rl", |
| "env", |
| "command", |
| "timeout", |
| "timeoutRetries", |
| "sourcepath", |
| "eol-normalization", |
| "custom-config-file", |
| NULL |
| }; |
| static_assert((sizeof(TestInfoKindName) / sizeof(TestInfoKindName[0])) - 1 == TestInfoKind::_TIK_COUNT, "Fix the buffer size"); |
| |
| const char * const DirectiveNames[] = |
| { |
| "PassFo", |
| "NoAsm", |
| "NoGPF", |
| NULL |
| }; |
| |
| |
| // |
| // Global variables set before worker threads start, and only accessed |
| // (not set) by the worker threads. |
| // |
| |
| TARGET_MACHINES TargetMachine = DEFAULT_TM; |
| TARGET_MACHINES RLMachine = DEFAULT_TM; |
| TARGET_OS TargetOS = DEFAULT_OS; |
| |
| Tags * TagsList = NULL; |
| Tags * TagsLast = NULL; |
| Tags* DirectoryTagsList = NULL; |
| Tags* DirectoryTagsLast = NULL; |
| |
| char SavedConsoleTitle[BUFFER_SIZE]; |
| const char *DIFF_DIR; |
| char *REGRESS = NULL, *MASTER_DIR, *TARGET_MACHINE, *RL_MACHINE, *TARGET_OS_NAME = NULL; |
| const char *REGR_CL, *REGR_DIFF; |
| char *REGR_ASM, *REGR_SHOWD; |
| const char *TARGET_VM; |
| char *EXTRA_CC_FLAGS, *EXEC_TESTS_FLAGS; |
| const char *LINKER, *LINKFLAGS; |
| char *CL, *_CL_; |
| const char *JCBinary = "jshost.exe"; |
| |
| BOOL FStatus = TRUE; |
| char *StatusPrefix = nullptr; |
| const char *StatusFormat = nullptr; |
| |
| BOOL FVerbose = FALSE; |
| BOOL FQuiet = FALSE; |
| BOOL FNoWarn = FALSE; |
| BOOL FTest = FALSE; |
| BOOL FStopOnError = FALSE; |
| BOOL GStopDueToError = FALSE; |
| BOOL FLow = FALSE; |
| BOOL FNoDelete = FALSE; |
| BOOL FCopyOnFail = FALSE; |
| BOOL FSummary = TRUE; |
| BOOL FMoveDiffs = FALSE; |
| BOOL FNoMoveDiffsSwitch = FALSE; |
| BOOL FRelativeLogPath = FALSE; |
| BOOL FNoDirName = TRUE; |
| BOOL FBaseline = FALSE; |
| BOOL FRebase = FALSE; |
| BOOL FDiff = FALSE; |
| BOOL FBaseDiff = FALSE; |
| BOOL FSyncEnumDirs = FALSE; |
| BOOL FNogpfnt = TRUE; |
| BOOL FAppend = FALSE; |
| BOOL FAppendTestNameToExtraCCFlags = FALSE; |
| |
| #ifndef NODEBUG |
| BOOL FDebug = FALSE; |
| #endif |
| |
| // Output synchronization options |
| BOOL FSyncImmediate = FALSE; // flush output immediately---no buffering |
| BOOL FSyncVariation = FALSE; // flush after every test variation (default) |
| BOOL FSyncTest = FALSE; // flush after all variations of a test |
| BOOL FSyncDir = FALSE; // flush after an entire directory |
| |
| BOOL FSingleThreadPerDir = FALSE; // Perform all tests within each directory on a single thread. |
| BOOL FSingleDir = FALSE; |
| BOOL FNoThreadId = FALSE; |
| |
| char* ExeCompiler = NULL; |
| char* BaseCompiler = NULL; |
| char* DiffCompiler = NULL; |
| |
| RLMODE Mode = DEFAULT_RLMODE; |
| |
| char *DCFGfile = NULL; |
| char const *CFGfile = NULL; |
| char const *CMDfile = NULL; |
| |
| #define MAX_ALLOWED_THREADS 10 // must be <= MAXIMUM_WAIT_OBJECTS (64) |
| unsigned NumberOfThreads = 0; |
| |
| TestList DirList, ExcludeDirList; |
| BOOL FUserSpecifiedFiles = FALSE; |
| BOOL FUserSpecifiedDirs = TRUE; |
| BOOL FNoProgramOutput = FALSE; |
| BOOL FOnlyAssertOutput = FALSE; |
| BOOL FExcludeDirs = FALSE; |
| BOOL FGenLst = FALSE; |
| char *ResumeDir = nullptr; |
| char *MatchDir = nullptr; |
| |
| TIME_OPTION Timing = TIME_DIR | TIME_TEST; // Default to report times at test and directory level |
| |
| static const char *ProgramName = nullptr; |
| static const char *LogName = nullptr; |
| static const char *FullLogName = nullptr; |
| static const char *ResultsLogName = nullptr; |
| static const char *TestTimeout = nullptr; // Stores timeout in seconds for all tests |
| static const char *TestTimeoutRetries = nullptr; // Number of timeout retries for all tests |
| #define MAX_ALLOWED_TIMEOUT_RETRIES 100 // Arbitrary max to avoid accidentally specifying too many retries |
| |
| // NOTE: this might be unused now |
| static char TempPath[MAX_PATH] = ""; // Path for temporary files |
| |
| // |
| // Global variables read and written by the worker threads: these need to |
| // either be protected by synchronization or use thread-local storage. |
| // |
| |
| // Ctrl-C or done? This is written by worker threads or the main thread, but |
| // only to TRUE. Once written to TRUE, it never changes again. Threads notice |
| // its new state by polling it periodically, and shutting down gracefully if |
| // it is TRUE. |
| BOOL bThreadStop = FALSE; |
| |
| char TitleStatus[BUFFER_SIZE]; // protected by csTitleBar |
| CRITICAL_SECTION csTitleBar; // used to serialize title bar |
| CRITICAL_SECTION csStdio; // printf serialization |
| CRITICAL_SECTION csCurrentDirectory; // used when changing current directory |
| |
| CProtectedLong NumVariationsRun[RLS_COUNT]; |
| CProtectedLong NumVariationsTotal[RLS_COUNT]; // run / total is displayed |
| CProtectedLong NumFailuresTotal[RLS_COUNT]; |
| CProtectedLong NumDiffsTotal[RLS_COUNT]; |
| |
| CProtectedLong MaxThreads; |
| |
| CProtectedLong CntDeleteFileFailures; |
| CProtectedLong CntDeleteFileFatals; |
| |
| // Under -syncdirs, enumerate all directories/files before allowing |
| // any work to be done. Do this by setting this event when done enumerating. |
| // This releases the worker threads. |
| CHandle heventDirsEnumerated; |
| |
| CDirectoryQueue DirectoryQueue; |
| CDirectoryQueue DirectoryTestQueue; |
| CDirectoryAndTestCaseQueue DirectoryAndTestCaseQueue; |
| CThreadInfo* ThreadInfo = NULL; |
| |
| // A per-thread id. ThreadId 0 is the primary thread, the directory/file |
| // enumeration thread. Other threads are numbered 1 to NumberOfThreads |
| |
| __declspec(thread) int ThreadId = 0; |
| __declspec(thread) char *TargetVM; |
| __declspec(thread) COutputBuffer* ThreadOut; // stdout |
| __declspec(thread) COutputBuffer* ThreadLog; // log file |
| __declspec(thread) COutputBuffer* ThreadFull; // full log file |
| __declspec(thread) COutputBuffer* ThreadRes; // results log file |
| |
| // Per-thread compare buffers, allocated once, deleted on thread destroy |
| #define CMPBUF_SIZE 65536 |
| __declspec(thread) char *cmpbuf1 = NULL; |
| __declspec(thread) char *cmpbuf2 = NULL; |
| |
| // Allow usage of select deprecated CRT APIs without disabling all deprecated CRT API warnings |
| #pragma warning (push) |
| #pragma warning (disable:4996) |
| char * getenv_unsafe(const char * varName) |
| { |
| // Use getenv instead of getenv_s or _dupenv_s to simplify calls to the API. |
| return getenv(varName); |
| } |
| |
| FILE * fopen_unsafe(const char * filename, const char * mode) |
| { |
| // Using fopen_s leads to EACCES error being returned occasionally, even when contentious |
| // fopen/fclose pairs are wrapped in a critical section. Unclear why this is happening. |
| // Use deprecated fopen instead. |
| _set_errno(0); |
| return fopen(filename, mode); |
| } |
| |
| char* strerror_unsafe(int errnum) |
| { |
| return strerror(errnum); |
| } |
| #pragma warning (pop) |
| |
| ////////////////////////////////////////////////////////////////////////////// |
| |
| void |
| CleanUp(BOOL fKilled) |
| { |
| if (FStatus) |
| SetConsoleTitle(SavedConsoleTitle); |
| |
| // Try to remove temporary files. The temp files may |
| // be in use, so we might not be able to. |
| |
| if (ThreadInfo != NULL) { // if we've gotten through parsing commands |
| for (unsigned int i = 0; i <= NumberOfThreads; i++) { |
| ThreadInfo[i].DeleteTmpFileList(); |
| } |
| } |
| |
| if (FRLFE) |
| RLFEDisconnect(fKilled); |
| } |
| |
| void __cdecl |
| NormalCleanUp(void) |
| { |
| CleanUp(FALSE); |
| } |
| |
| int __stdcall |
| NT_handling_function(unsigned long /* dummy -- unused */) |
| { |
| fprintf(stderr, "Exiting...\n"); |
| fflush(stdout); |
| fflush(stderr); |
| |
| CleanUp(TRUE); |
| |
| // For now, just exit ungracefully. This will probably cause bad |
| // things to happen with output filtering, since we've |
| // just killed the pipe. |
| |
| ExitProcess(1); |
| #if _MSC_VER<1300 |
| return 1; // avoid C4716 warning caused by no return on ExitProcess |
| #endif |
| } |
| |
| void |
| assert( |
| const char *file, |
| int line |
| ) |
| { |
| fprintf(stderr, "Assertion failed on line %d of %s\n", |
| line, file); |
| } |
| |
| ////////////////////////////////////////////////////////////////////////////// |
| // |
| // Output functions. All worker thread output must be generated using these |
| // functions, then flushed at appropriate times based on the -sync option. |
| // |
| |
| void |
| __cdecl Fatal(const char *fmt, ...) |
| { |
| va_list arg_ptr; |
| |
| va_start(arg_ptr, fmt); |
| fprintf(stderr, "Error: "); |
| vfprintf(stderr, fmt, arg_ptr); |
| fputc('\n', stderr); |
| |
| exit(1); |
| } |
| |
| void |
| __cdecl Warning(const char *fmt, ...) |
| { |
| va_list arg_ptr; |
| char buf[50], tempBuf[BUFFER_SIZE]; |
| |
| ASSERT(ThreadOut != NULL); |
| |
| buf[0] = '\0'; |
| if (!FNoThreadId && ThreadId != 0 && NumberOfThreads > 1) { |
| sprintf_s(buf, "%d>", ThreadId); |
| } |
| |
| if (!FNoWarn) { |
| va_start(arg_ptr, fmt); |
| vsprintf_s(tempBuf, fmt, arg_ptr); |
| ASSERT(strlen(tempBuf) < BUFFER_SIZE); |
| ThreadOut->Add("%sWarning: %s\n", buf, tempBuf); |
| ThreadFull->Add("%sWarning: %s\n", buf, tempBuf); |
| } |
| } |
| |
| void |
| __cdecl Message(const char *fmt, ...) |
| { |
| va_list arg_ptr; |
| char buf[50], tempBuf[BUFFER_SIZE]; |
| |
| ASSERT(ThreadOut != NULL); |
| |
| buf[0] = '\0'; |
| if (!FNoThreadId && ThreadId != 0 && NumberOfThreads > 1) { |
| sprintf_s(buf, "%d>", ThreadId); |
| } |
| |
| va_start(arg_ptr, fmt); |
| vsprintf_s(tempBuf, fmt, arg_ptr); |
| ASSERT(strlen(tempBuf) < BUFFER_SIZE); |
| ThreadOut->Add("%s%s\n", buf, tempBuf); |
| ThreadFull->Add("%s%s\n", buf, tempBuf); |
| } |
| |
| void |
| __cdecl WriteLog(const char *fmt, ...) |
| { |
| va_list arg_ptr; |
| char buf[50], tempBuf[BUFFER_SIZE]; |
| |
| ASSERT(ThreadLog != NULL); |
| |
| buf[0] = '\0'; |
| if (!FNoThreadId && ThreadId != 0 && NumberOfThreads > 1) { |
| sprintf_s(buf, "%d>", ThreadId); |
| } |
| |
| va_start(arg_ptr, fmt); |
| vsprintf_s(tempBuf, fmt, arg_ptr); |
| ASSERT(strlen(tempBuf) < BUFFER_SIZE); |
| ThreadLog->Add("%s%s\n", buf, tempBuf); |
| } |
| |
| void |
| __cdecl LogOut(const char *fmt, ...) |
| { |
| va_list arg_ptr; |
| char buf[50], tempBuf[BUFFER_SIZE]; |
| |
| ASSERT(ThreadOut != NULL); |
| ASSERT(ThreadLog != NULL); |
| |
| buf[0] = '\0'; |
| if (!FNoThreadId && ThreadId != 0 && NumberOfThreads > 1) { |
| sprintf_s(buf, "%d>", ThreadId); |
| } |
| |
| va_start(arg_ptr, fmt); |
| vsprintf_s(tempBuf, fmt, arg_ptr); |
| ASSERT(strlen(tempBuf) < BUFFER_SIZE); |
| ThreadOut->Add("%s%s\n", buf, tempBuf); |
| ThreadLog->Add("%s%s\n", buf, tempBuf); |
| ThreadFull->Add("%s%s\n", buf, tempBuf); |
| } |
| |
| void |
| __cdecl LogError(const char *fmt, ...) |
| { |
| va_list arg_ptr; |
| char buf[50], tempBuf[BUFFER_SIZE]; |
| |
| ASSERT(ThreadOut != NULL); |
| ASSERT(ThreadLog != NULL); |
| |
| buf[0] = '\0'; |
| if (!FNoThreadId && ThreadId != 0 && NumberOfThreads > 1) { |
| sprintf_s(buf, "%d>", ThreadId); |
| } |
| |
| va_start(arg_ptr, fmt); |
| vsprintf_s(tempBuf, fmt, arg_ptr); |
| ASSERT(strlen(tempBuf) < BUFFER_SIZE); |
| ThreadOut->Add("%sError: %s\n", buf, tempBuf); |
| ThreadLog->Add("%sError: %s\n", buf, tempBuf); |
| ThreadFull->Add("%sError: %s\n", buf, tempBuf); |
| } |
| |
| // Helper function to flush all thread output. |
| __inline void FlushOutput( |
| void |
| ) |
| { |
| ThreadOut->Flush(); |
| ThreadLog->Flush(); |
| ThreadFull->Flush(); |
| ThreadRes->Flush(); |
| } |
| |
| ////////////////////////////////////////////////////////////////////////////// |
| // |
| // Various DeleteFile() wrappers. We have had lots of problems where |
| // DeleteFile() fails with error 5 -- access denied. It's not clear why this |
| // is: it seems like an OS bug where the handle to a process is not released |
| // when the process handle becomes signaled. Perhaps there is a process that |
| // sits around grabbing handles. It seems to be worse with CLR testing and |
| // issuerun/readrun cross-compiler testing. To deal with this, loop and sleep |
| // between delete tries for some calls. Keep track of how many failures there |
| // were. Don't clog the log files with "error 5" unless the |
| // sleep doesn't fix the problem, or the user specifies verbose. |
| |
| BOOL |
| DeleteFileIfFoundInternal( |
| const char* filename |
| ) |
| { |
| BOOL ok; |
| |
| ASSERT(filename != NULL); |
| |
| if (FTest) |
| { |
| return TRUE; |
| } |
| |
| // We could see if it's there and then try to delete it. It's easier to |
| // just try deleting it and see what happens. |
| |
| ok = DeleteFile(filename); |
| if (!ok && (GetLastError() != ERROR_FILE_NOT_FOUND)) { |
| CntDeleteFileFailures++; |
| return FALSE; |
| } |
| |
| return TRUE; |
| } |
| |
| BOOL |
| DeleteFileIfFound( |
| const char* filename |
| ) |
| { |
| BOOL ok; |
| |
| ok = DeleteFileIfFoundInternal(filename); |
| if (!ok) { |
| LogError("DeleteFileIfFound: Unable to delete file %s - error %d", filename, GetLastError()); |
| return FALSE; |
| } |
| |
| return TRUE; |
| } |
| |
| // Call Win32's DeleteFile(), and print an error if it fails |
| void |
| DeleteFileMsg( |
| char *filename |
| ) |
| { |
| if (FTest) |
| { |
| return; |
| } |
| |
| BOOL ok; |
| |
| ok = DeleteFile(filename); |
| if (!ok) { |
| CntDeleteFileFailures++; |
| LogError("DeleteFileMsg: Unable to delete file %s - error %d", filename, GetLastError()); |
| } |
| } |
| |
| // Call Win32's DeleteFile(), and print an error if it fails. Retry a few |
| // times, just to improve robustness. NOTE: This function will fail (after |
| // retries) if the file does not exist! |
| |
| #define MAX_DELETE_FILE_TRIES 10 |
| #define DELETE_FILE_INITIAL_INTERVAL_IN_MS 100 |
| #define DELETE_FILE_DELTA_IN_MS 100 |
| |
| void |
| DeleteFileRetryMsg( |
| char *filename |
| ) |
| { |
| unsigned tries = 1; |
| BOOL ok; |
| DWORD dwWait = DELETE_FILE_INITIAL_INTERVAL_IN_MS; |
| |
| for (;;) { |
| ok = DeleteFileIfFoundInternal(filename); |
| if (ok) |
| break; |
| |
| if (FVerbose) { |
| LogError("DeleteFileRetryMsg: Unable to delete file %s - error %d", filename, GetLastError()); |
| } |
| |
| if (++tries > MAX_DELETE_FILE_TRIES) { |
| LogError("Giving up trying to delete file %s. Further related errors are likely", filename); |
| CntDeleteFileFatals++; |
| break; |
| } |
| |
| Sleep(dwWait); |
| dwWait += DELETE_FILE_DELTA_IN_MS; |
| } |
| } |
| |
| void |
| DeleteMultipleFiles( |
| CDirectory* pDir, |
| const char* pattern |
| ) |
| { |
| WIN32_FIND_DATA findData; |
| HANDLE h; |
| char full[MAX_PATH]; |
| |
| sprintf_s(full, "%s\\%s", pDir->GetDirectoryPath(), pattern); |
| |
| // Read filenames... |
| |
| if ((h = FindFirstFile(full, &findData)) == INVALID_HANDLE_VALUE) |
| return; |
| |
| do { |
| // FindFirstFile/FindNextFile set cFileName to a file name without a |
| // path. We need to prepend the directory and use a full path to do |
| // the delete. This is because multithreaded rl might have changed the |
| // current directory out from underneath us. |
| |
| sprintf_s(full, "%s\\%s", pDir->GetDirectoryPath(), findData.cFileName); |
| if (FVerbose) |
| Message("Deleting %s\n", full); |
| |
| DeleteFileRetryMsg(full); |
| } while (FindNextFile(h, &findData)); |
| |
| FindClose(h); |
| } |
| |
| char * |
| GetFilenamePtr( |
| char *path |
| ) |
| { |
| char *s; |
| |
| s = strrchr(path, '\\'); |
| if (s) |
| return s + 1; |
| return path; |
| } |
| |
| // Get extension of a filename, or "" if no extension found. |
| const char* GetFilenameExt(const char *path) |
| { |
| const char* ext = strrchr(path, '.'); |
| return ext ? ext : ""; |
| } |
| |
| char * |
| mytmpnam( |
| const char *directory, |
| const char *prefix, |
| char *filename |
| ) |
| { |
| UINT r; |
| char threadPrefix[MAX_PATH]; // make the prefix thread-specific |
| |
| // Note that GetTempFileName only uses the first 3 characters of the |
| // prefix. This should be okay, because it will still create a unique |
| // filename. |
| |
| sprintf_s(threadPrefix, "%s%X", prefix, ThreadId); |
| |
| // NOTE: GetTempFileName actually creates a file when it succeeds. |
| r = GetTempFileNameA(directory, threadPrefix, 0, filename); |
| |
| if (r == 0) { |
| return NULL; |
| } |
| |
| return filename; |
| } |
| |
| // |
| // It is possible antivirus program on dev box locked testout when we want to compare testout with baseline. |
| // If this happens, delay and try a few more times. |
| // |
| HANDLE OpenFileToCompare(char *file) |
| { |
| const int MAX_TRY = 1200; |
| const DWORD NEXT_TRY_SLEEP_MS = 250; |
| |
| HANDLE h; |
| |
| int i = 0; |
| for (;;) |
| { |
| // Open the files using exclusive access |
| h = CreateFile(file, GENERIC_READ, 0, NULL, OPEN_EXISTING, FILE_ATTRIBUTE_NORMAL | FILE_FLAG_SEQUENTIAL_SCAN, NULL); |
| if (h != INVALID_HANDLE_VALUE) |
| { |
| break; |
| } |
| |
| DWORD lastError = GetLastError(); |
| if (++i < MAX_TRY && lastError == ERROR_SHARING_VIOLATION) |
| { |
| Sleep(NEXT_TRY_SLEEP_MS); |
| continue; |
| } |
| |
| LogError("Unable to open file %s - error %d", file, lastError); |
| break; |
| } |
| |
| return h; |
| } |
| |
| struct MyFileWithoutCarriageReturn |
| { |
| CHandle* handle; |
| char* buf = nullptr; |
| size_t i = 0; |
| DWORD count = 0; |
| MyFileWithoutCarriageReturn(CHandle* handle, char* buf) : handle(handle), buf(buf) { Read(); } |
| bool readingError; |
| void Read() |
| { |
| i = 0; |
| readingError = !ReadFile(*handle, buf, CMPBUF_SIZE, &count, NULL); |
| } |
| bool HasNextChar() |
| { |
| if (count == 0) |
| { |
| return false; |
| } |
| if (i == count) |
| { |
| Read(); |
| if (readingError) |
| { |
| return false; |
| } |
| return HasNextChar(); |
| } |
| while (buf[i] == '\r') |
| { |
| ++i; |
| if (i == count) |
| { |
| Read(); |
| if (readingError) |
| { |
| return false; |
| } |
| return HasNextChar(); |
| } |
| } |
| return true; |
| } |
| char GetNextChar() |
| { |
| return buf[i++]; |
| } |
| }; |
| |
| // Do a quick file equality comparison using pure Win32 functions. (Avoid |
| // using CRT functions; the MT CRT seems to have locking/flushing problems on |
| // MP boxes.) |
| |
| int |
| DoCompare( |
| char *file1, |
| char *file2, |
| BOOL normalizeLineEndings |
| ) |
| { |
| CHandle h1, h2; // automatically closes open handles |
| DWORD count1, count2; |
| BOOL b1, b2; |
| DWORD size1, size2; |
| |
| // Allocate large buffers for doing quick file comparisons. |
| |
| if (cmpbuf1 == NULL) { |
| // initialize the buffers |
| cmpbuf1 = new char[CMPBUF_SIZE]; |
| cmpbuf2 = new char[CMPBUF_SIZE]; |
| if ((cmpbuf1 == NULL) || (cmpbuf2 == NULL)) |
| Fatal("new failed"); |
| } |
| |
| h1 = OpenFileToCompare(file1); |
| if (h1 == INVALID_HANDLE_VALUE) { |
| return -1; |
| } |
| h2 = OpenFileToCompare(file2); |
| if (h2 == INVALID_HANDLE_VALUE) { |
| return -1; |
| } |
| |
| if (normalizeLineEndings) |
| { |
| MyFileWithoutCarriageReturn f1(&h1, cmpbuf1); |
| MyFileWithoutCarriageReturn f2(&h2, cmpbuf2); |
| if (f1.readingError || f2.readingError) |
| { |
| LogError("ReadFile failed doing compare of %s and %s", file1, file2); |
| return -1; |
| } |
| while (f1.HasNextChar() && f2.HasNextChar()) |
| { |
| if (f1.readingError || f2.readingError) |
| { |
| LogError("ReadFile failed doing compare of %s and %s", file1, file2); |
| return -1; |
| } |
| |
| if (f1.GetNextChar() != f2.GetNextChar()) |
| { |
| #ifndef NODEBUG |
| if (FDebug) |
| printf("DoCompare shows %s and %s are NOT equal (contents differ)\n", file1, file2); |
| #endif |
| return 1; |
| } |
| } |
| if (f1.HasNextChar() != f2.HasNextChar()) |
| { |
| #ifndef NODEBUG |
| if (FDebug) |
| printf("DoCompare shows %s and %s are NOT equal (contents differ)\n", file1, file2); |
| #endif |
| return 1; |
| } |
| return 0; |
| } |
| |
| size1 = GetFileSize(h1, NULL); // assume < 4GB files |
| if (size1 == 0xFFFFFFFF) { |
| LogError("Unable to get file size for %s - error %d", file1, GetLastError()); |
| return -1; |
| } |
| size2 = GetFileSize(h2, NULL); |
| if (size2 == 0xFFFFFFFF) { |
| LogError("Unable to get file size for %s - error %d", file2, GetLastError()); |
| return -1; |
| } |
| if (size1 != size2) { // not equal; don't bother reading the files |
| |
| #ifndef NODEBUG |
| if (FDebug) |
| printf("DoCompare shows %s and %s are NOT equal (different size)\n", file1, file2); |
| #endif |
| |
| return 1; |
| } |
| do { |
| b1 = ReadFile(h1, cmpbuf1, CMPBUF_SIZE, &count1, NULL); |
| b2 = ReadFile(h2, cmpbuf2, CMPBUF_SIZE, &count2, NULL); |
| if (!b1 || !b2) { |
| LogError("ReadFile failed doing compare of %s and %s", file1, file2); |
| return -1; |
| } |
| |
| if (count1 != count2 || memcmp(cmpbuf1, cmpbuf2, count1) != 0) { |
| |
| #ifndef NODEBUG |
| if (FDebug) |
| printf("DoCompare shows %s and %s are NOT equal (contents differ)\n", file1, file2); |
| #endif |
| |
| return 1; |
| } |
| } while (count1 == CMPBUF_SIZE); |
| |
| #ifndef NODEBUG |
| if (FDebug) |
| printf("DoCompare shows %s and %s are equal\n", file1, file2); |
| #endif |
| |
| return 0; |
| } |
| |
| char * |
| FormatString( |
| const char *format |
| ) |
| { |
| static char buf[BUFFER_SIZE + 32]; // extra in case a sprintf_s goes over |
| int i; |
| |
| i = 0; |
| while (*format) { |
| if (*format != '%') { |
| buf[i++] = *format; |
| } |
| else { |
| switch (*++format) { |
| case 'd': |
| i += sprintf_s(&buf[i], BUFFER_SIZE + 32 - i, "%d", (int32)NumDiffsTotal[RLS_TOTAL]); |
| break; |
| case 'f': |
| i += sprintf_s(&buf[i], BUFFER_SIZE + 32 - i, "%d", (int32)NumFailuresTotal[RLS_TOTAL]); |
| break; |
| case 't': |
| i += sprintf_s(&buf[i], BUFFER_SIZE + 32 - i, "%d", (int32)NumVariationsRun[RLS_TOTAL]); |
| break; |
| case 'T': |
| i += sprintf_s(&buf[i], BUFFER_SIZE + 32 - i, "%d", (int32)NumVariationsTotal[RLS_TOTAL]); |
| break; |
| case 'p': |
| if ((int32)NumVariationsTotal[RLS_TOTAL]) { |
| i += sprintf_s(&buf[i], BUFFER_SIZE + 32 - i, "%d", |
| 100 * (int32)NumVariationsRun[RLS_TOTAL] / |
| (int32)NumVariationsTotal[RLS_TOTAL]); |
| } |
| else { |
| i += sprintf_s(&buf[i], BUFFER_SIZE + 32 - i, "--"); |
| } |
| break; |
| default: |
| buf[i++] = *format; |
| } |
| } |
| format++; |
| if (i > BUFFER_SIZE) |
| break; |
| } |
| |
| ASSERTNR(i < BUFFER_SIZE + 32); |
| buf[i] = '\0'; |
| |
| return buf; |
| } |
| |
| |
| void |
| Usage( |
| BOOL fExtended |
| ) |
| { |
| int i; |
| BOOL fFirst; |
| |
| printf( |
| "Usage: %s [options] [files...] [-args ...]\n" |
| "Specifying files on the command line overrides the configuration file parsing\n" |
| "\nOptions:\n" |
| " -h[x]: short/extended usage info\n" |
| "\n" |
| " -base[:backend] update MASTER_DIR assembly regression baselines\n" |
| " -diff[:backend] assembly regressions (default if no mode option)\n" |
| " -exe[:backend] run executable tests\n" |
| "\n" |
| " -all to run regressions on all directories in directory config file (log files go\n" |
| " in %%REGRESS%%\\logs directory)\n" |
| , ProgramName |
| ); |
| |
| if (fExtended) { |
| printf( |
| " -dirs:dir[,dir...] only regress the specified directories\n" |
| " -nodirs:dir[,dir...] exclude the specified directories\n" |
| " -resume:dir resume from the specified directory (implies -append)\n" |
| " -match:dir run regressions on directories with dir as a prefix\n" |
| "\n" |
| " -tags:tag[,tag...] only regress tests with any of the specified tags\n" |
| " -nottags:tag[,tag...] don't regress tests with any of the specified tags\n" |
| " -dirtags:tag[,tag...] only regress tests within directories with any of the specified tags\n" |
| " -dirnottags:tag[,tag...] don't regress tests within directories with any of the specified tags\n" |
| "\n" |
| " -exeflags:flag[;flag] specify exe flags to use in place of EXEC_TESTS_FLAGS\n" |
| " -target:arch specify the target architecture, instead of TARGET_MACHINE\n" |
| " -rltarget:arch specify the target architecture, instead of RL_MACHINE\n" |
| " -os:name specify the target os, instead of TARGET_OS\n" |
| " -regress:dir specify the root dir, instead of REGRESS\n" |
| "\n" |
| " -log:file specifies log file name (default: " DEFAULT_LOG_FILE ")\n" |
| " -full:file specifies full log file name (default: " DEFAULT_FULL_LOG_FILE ")\n" |
| " -results:file specifies runall style results log name (default: " DEFAULT_RESULTS_LOG_FILE ")\n" |
| " -rellog to allow a relative path log file\n" |
| "\n" |
| " -status[:prefix] to track diffs/failures in title\n" |
| " default prefix is \"{Diffs,Exec} [target] \"\n" |
| " -statusformat:format to specify display of statistics\n" |
| " default format is \"(%%d diffs, )%%f failures, %%t/%%T (%%p%%%%)\"\n" |
| " where %%d = # diffs, %%f = #failures,\n" |
| " %%t = num tests run, %%T = num tests total,\n" |
| " %%p = percentage of tests completed\n" |
| " -rlfe[:options] launch RLFE (graphical front end)\n" |
| "\n" |
| " -nosummary disables output of summary diff/failure info (implies -dirname)\n" |
| " -dirname enables output of '*** dirname ***'\n" |
| " -stoponerror stop on first test failure\n" |
| " -nomovediffs to not place assembly diffs in DIFF_DIR\n" |
| " -nodelete to not delete objs and exes when doing -exe\n" |
| " (only compatible with a single EXEC_TESTS_FLAGS flags)\n" |
| " -copyonfail to copy *.exe *.obj to fail.(OPT) subdirectory when rl.mak\n" |
| " tests fail\n" |
| " -allowpopup to allow popup screen when tests fail to run correctly\n" |
| " (do not link with nogpfnt.obj)\n" |
| " -append to append to the log files without deleting them first\n" |
| "\n" |
| " -quiet eliminates console display of progress\n" |
| " -verbose displays info about regressions\n" |
| " -nowarn turns off warnings\n" |
| " -low sets RL process to a lower priority\n" |
| " -test just displays regression commands, doesn't execute them\n" |
| "\n" |
| " -time:variation times each variation\n" |
| " -time:test times each test\n" |
| " -time:dir times each directory\n" |
| " -time:all enables all timing options\n" |
| "\n" |
| " -rebase:create rebase file if testout is different to baseline\n" |
| " -threads:# specifies # of worker threads to use (default==processor count)\n" |
| " -nothreadid to omit the thread id from multithreaded output\n" |
| " -sync:immediate to flush output immediately---no buffering\n" |
| " -sync:variation (default) to flush output after a test variation\n" |
| " -sync:test to flush output after all variations of a test\n" |
| " -sync:dir to flush output after an entire directory\n" |
| "\n" |
| " -dcfg:filename override directory config file\n" |
| " ASM default:" DEFAULT_ASM_DCFG " EXE default:" DEFAULT_EXE_DCFG "\n" |
| " -cfg:filename override file config file\n" |
| " ASM default:" DEFAULT_ASM_CFG " EXE default:" DEFAULT_EXE_CFG "\n" |
| "\n" |
| " -genlst to generate the runall test.lst and env.lst files, and don't run rl tests\n" |
| "\n" |
| ); |
| |
| #ifndef NODEBUG |
| printf( |
| "DEBUG ONLY:\n" |
| " -debug to show debug info (implies -verbose)\n" |
| " -syncdirs to enumerate all dirs/files before running tests\n" |
| "\n" |
| ); |
| #endif |
| |
| printf( |
| "Perform %s regressions by default.\n" |
| , ModeNames[DEFAULT_RLMODE] |
| ); |
| |
| printf( |
| "Current directory assumed if -all/-dirs/-resume/-match not specified\n" |
| "-status is the default; use -status:- to disable\n" |
| "Target machine defaults to %s (option -target or environment var\n" |
| " TARGET_MACHINE overrides)\n" |
| , TargetInfo[DEFAULT_TM].name |
| ); |
| printf( |
| " (Valid targets:"); |
| fFirst = TRUE; |
| for (i = 1; TargetInfo[i].name; i++) { |
| if (!TargetInfo[i].fRL_MACHINEonly) { |
| if (!fFirst) |
| putchar(','); |
| else |
| fFirst = FALSE; |
| printf(" %s", TargetInfo[i].name); |
| } |
| } |
| printf(")\n"); |
| |
| printf( |
| "Option -rltarget or environment var RL_MACHINE overrides TARGET_MACHINE for\n" |
| " configuration checking\n" |
| " (Additional configurations:"); |
| fFirst = TRUE; |
| for (i = 1; TargetInfo[i].name; i++) { |
| if (TargetInfo[i].fRL_MACHINEonly) { |
| if (!fFirst) |
| putchar(','); |
| else |
| fFirst = FALSE; |
| printf(" %s", TargetInfo[i].name); |
| } |
| } |
| printf(")\n"); |
| |
| printf( |
| "Target os defaults to %s (option -os or environment var TARGET_OS overrides)\n" |
| " (Valid os:" |
| , TargetOSNames[DEFAULT_OS]); |
| fFirst = TRUE; |
| for (i = 1; TargetOSNames[i]; i++) { |
| if (!fFirst) |
| putchar(','); |
| else |
| fFirst = FALSE; |
| printf(" %s", TargetOSNames[i]); |
| } |
| printf(")\n"); |
| |
| printf( |
| "MASTER_DIR env var defaults to master.<target machine>\n" |
| "DIFF_DIR defaults to diffs.<target machine>\n" |
| "\n" |
| "The following environment variables are recognized:\n" |
| "REGR_CL use the specified command instead of " DEFAULT_REGR_CL "\n" |
| "REGR_ASM generate assembly listings and use the specified command to\n" |
| " assemble them\n" |
| "REGR_DIFF use the specified command instead of " DEFAULT_REGR_DIFF " (asm only)\n" |
| "REGR_SHOWD use the specified executable instead of %s\\bin\\showd.cmd (asm only)\n" |
| "EXTRA_CC_FLAGS specifies additional flags to pass to REGR_CL\n" |
| " if EXTRA_CC_FLAGS is not specified, it is constructed from\n" |
| " RL_{ASM,EXE}_CFLAGS and RL_B2, if present\n" |
| "RL_ASM_CFLAGS specify ASM only flags (used to construct EXTRA_CC_FLAGS)\n" |
| "RL_EXE_CFLAGS specify EXE only flags (used to construct EXTRA_CC_FLAGS)\n" |
| "RL_B2 specify backend to use (appended to above to create EXTRA_CC_FLAGS)\n" |
| "EXEC_TESTS_FLAGS semicolon delimited list of testing options (exe only)\n" |
| " (overridden by -exeflags switch) (defaults to %s)\n" |
| , getenv_unsafe("REGRESS"), DEFAULT_EXEC_TESTS_FLAGS |
| ); |
| |
| printf( |
| "REGR_NOMASTERCMP specifies that the generated ASM files should not be compared\n" |
| "to the masters (useful if you don't care about diffs and want to save time.)\n" |
| "TARGET_VM specifies the program used to execute a test (exe only)\n" |
| ); |
| fFirst = TRUE; |
| for (i = 1; TargetInfo[i].name; i++) { |
| if (TargetInfo[i].TARGET_VM) { |
| if (fFirst) { |
| printf(" ("); |
| fFirst = FALSE; |
| } |
| else { |
| printf(", "); |
| } |
| printf("%s: %s", TargetInfo[i].name, TargetInfo[i].TARGET_VM); |
| } |
| } |
| if (!fFirst) |
| printf(")\n"); |
| |
| printf( |
| "LINKER use the specified command instead of " DEFAULT_LINKER " (exe only)\n" |
| "LINKFLAGS specifies additional flags to pass to LINKER (exe only)\n" |
| ); |
| fFirst = TRUE; |
| for (i = 1; TargetInfo[i].name; i++) { |
| if (TargetInfo[i].LINKFLAGS) { |
| if (fFirst) { |
| printf(" ("); |
| fFirst = FALSE; |
| } |
| else { |
| printf(", "); |
| } |
| printf("%s: %s", TargetInfo[i].name, TargetInfo[i].LINKFLAGS); |
| } |
| } |
| if (!fFirst) |
| printf(")\n"); |
| } |
| } |
| |
| TARGET_MACHINES |
| ParseMachine( |
| char *s |
| ) |
| { |
| int i; |
| |
| for (i = 1; TargetInfo[i].name; i++) { |
| if (!_stricmp(TargetInfo[i].name, s)) |
| return (TARGET_MACHINES)i; |
| } |
| |
| return TM_UNKNOWN; |
| } |
| |
| TARGET_OS |
| ParseOS( |
| char *s |
| ) |
| { |
| int i; |
| |
| for (i = 1; TargetOSNames[i]; i++) { |
| if (!_stricmp(TargetOSNames[i], s)) |
| return (TARGET_OS)i; |
| } |
| |
| return TO_UNKNOWN; |
| } |
| |
| // Does the config file specified target match with our regression target? |
| // TRUE if they are equal or regrTarget is a superset of fileTarget. |
| BOOL |
| MatchMachine( |
| TARGET_MACHINES fileTarget, |
| TARGET_MACHINES regrTarget |
| ) |
| { |
| if (fileTarget == regrTarget) |
| return TRUE; |
| |
| if (fileTarget == TM_WVM) { |
| if (regrTarget == TM_WVMCEE || regrTarget == TM_WVMX86 || regrTarget == TM_WVM64) |
| return TRUE; |
| } |
| |
| return FALSE; |
| } |
| |
| BOOL |
| MatchOS( |
| TARGET_OS fileTarget, |
| TARGET_OS regrTarget |
| ) |
| { |
| if (fileTarget == regrTarget) |
| { |
| return TRUE; |
| } |
| |
| // Apply win8 override for wp8 |
| if (fileTarget == TO_WIN8 && regrTarget == TO_WP8) |
| { |
| return TRUE; |
| } |
| |
| return FALSE; |
| } |
| |
| BOOL |
| HasInfoList |
| ( |
| const char * szInfoList1, |
| const char * delim1, |
| const char * szInfoList2, |
| const char * delim2, |
| bool allMustMatch |
| ) |
| { |
| if (szInfoList1 == NULL) |
| { |
| return FALSE; |
| } |
| |
| if (szInfoList2 == NULL) |
| { |
| return FALSE; |
| } |
| |
| const char * s = szInfoList2; |
| BOOL fHasInfo = TRUE; // start with TRUE on each entry |
| for (;;) |
| { |
| // Parse an item from szInfoList2. |
| |
| char * e; |
| for (e = const_cast<char *>(s); |
| (strchr(delim2, *e) == NULL) && (*e != '\0'); |
| e++) |
| { |
| } |
| |
| char c = *e; |
| *e = '\0'; |
| |
| if (allMustMatch) |
| { |
| if (delim2 && (*delim2 == ',')) |
| fHasInfo = TRUE; // new condition after ',' |
| const char *save; |
| save = strchr(s, *delim1); |
| |
| while(save != NULL) { |
| fHasInfo = fHasInfo && HasInfoList(szInfoList1, delim1, |
| s, delim1, allMustMatch); |
| s = save + 1; |
| save = strchr(s, *delim1); |
| } |
| |
| fHasInfo = fHasInfo && HasInfo(szInfoList1, delim1, s); |
| } |
| else |
| { |
| fHasInfo = HasInfo(szInfoList1, delim1, s); |
| } |
| |
| *e = c; |
| |
| if (fHasInfo) |
| { |
| return TRUE; |
| } |
| |
| if (c == '\0') |
| { |
| break; |
| } |
| |
| s = e + 1; |
| } |
| |
| return FALSE; |
| } |
| |
| const char * |
| stristr |
| ( |
| const char * str, |
| const char * sub |
| ) |
| { |
| size_t len = strlen(sub); |
| size_t i; |
| |
| while (*str) |
| { |
| |
| for (i = 0; i < len; i++) |
| { |
| if (tolower(str[i]) != tolower(sub[i])) |
| { |
| if ((str[i] != '/' && str[i] != '-') || (sub[i] != '-' && sub[i] != '/')) { |
| // if the mismatch is not between '/' and '-' |
| break; |
| } |
| } |
| } |
| if (i == len) |
| { |
| return str; |
| } |
| |
| str++; |
| } |
| |
| return NULL; |
| } |
| |
| BOOL |
| HasInfo |
| ( |
| const char * szInfoList, |
| const char * delim1, |
| const char * szInfo |
| ) |
| { |
| size_t len = strlen(szInfo); |
| const char * s = szInfoList; |
| |
| if (s != NULL) |
| { |
| for (;;) |
| { |
| s = stristr(s, szInfo); |
| if (s == NULL) |
| { |
| return FALSE; |
| } |
| |
| if (((s == szInfoList) || (strchr(delim1, s[-1]) != NULL)) |
| && ((s[len] == '\0') || (strchr(delim1, s[len]) != NULL))) |
| { |
| return TRUE; |
| } |
| |
| while (*s != '\0') |
| { |
| if (strchr(delim1, *s) != NULL) |
| { |
| break; |
| } |
| |
| s++; |
| } |
| |
| if (*s == '\0') |
| { |
| break; |
| } |
| |
| s++; |
| } |
| } |
| |
| return FALSE; |
| } |
| |
| template<typename ListType,typename String> |
| ListNode<ListType> * |
| AddToStringList |
| ( |
| ListNode<ListType> * list, |
| String string |
| ) |
| { |
| ListNode<ListType> * p = new ListNode<ListType>; |
| |
| p->string = string; // NOTE: we store the pointer; we don't copy the string |
| p->next = NULL; |
| |
| if (list == NULL) |
| { |
| return p; |
| } |
| |
| ListNode<ListType> * last = list; |
| |
| while (last->next != NULL) |
| { |
| last = last->next; |
| } |
| |
| last->next = p; |
| |
| return list; |
| } |
| |
| template<typename T> |
| void |
| FreeStringList |
| ( |
| ListNode<T> * list |
| ) |
| { |
| while (list) |
| { |
| ListNode<T> * pFree = list; |
| list = list->next; |
| |
| delete pFree; |
| } |
| } |
| |
| void |
| FreeVariants |
| ( |
| TestVariant * list |
| ) |
| { |
| while (list) |
| { |
| TestVariant * pFree = list; |
| list = list->next; |
| |
| delete pFree; |
| } |
| } |
| |
| StringList * |
| ParseStringList(const char* cp, const char* delim) |
| { |
| StringList * list = NULL; |
| |
| if (cp == NULL) |
| { |
| return list; |
| } |
| |
| char *p = _strdup(cp); // don't trash passed-in memory |
| |
| p = mystrtok(p, delim, delim); |
| |
| while (p != NULL) |
| { |
| list = AddToStringList(list, p); |
| p = mystrtok(NULL, delim, delim); |
| } |
| |
| return list; |
| } |
| |
| StringList * |
| AppendStringList |
| ( |
| StringList * list, |
| StringList * listToAppend |
| ) |
| { |
| StringList * p; |
| |
| if (list == NULL) { |
| return listToAppend; |
| } |
| |
| for (p = list; p->next != NULL; p = p->next) { |
| // nothing |
| } |
| |
| p->next = listToAppend; |
| return list; |
| } |
| |
| BOOL |
| CompareStringList |
| ( |
| StringList * list1, |
| StringList * list2 |
| ) |
| { |
| while ((list1 != NULL) && (list2 != NULL)) |
| { |
| if (_stricmp(list1->string, list2->string)) |
| { |
| return FALSE; |
| } |
| |
| list1 = list1->next; |
| list2 = list2->next; |
| } |
| |
| if (list1 == list2) |
| { |
| ASSERTNR(list1 == NULL); |
| |
| return TRUE; |
| } |
| |
| return FALSE; |
| } |
| |
| // Set three environment variables for use in suppressing GPF pop-ups: |
| // NOGPF -- for console-mode programs |
| // NOGPFWINEXE -- for Windows EXEs |
| // NOGPFWINDLL -- for Windows DLLs |
| // These are useful for either dotest.cmd tests, or rl.mak tests that don't |
| // use the default console-mode nogpfnt.obj. |
| |
| void |
| SetNOGPF() |
| { |
| char tempBuf[BUFFER_SIZE]; |
| |
| if (FNogpfnt && TargetInfo[TargetMachine].fUseNoGPF) { |
| sprintf_s(tempBuf, |
| "NOGPF=%s\\bin\\%s\\nogpfnt.obj /entry:nogpfntStartup", |
| REGRESS, TargetInfo[TargetMachine].name); |
| if (_putenv(tempBuf)) |
| Fatal("Couldn't set NOGPF"); |
| |
| sprintf_s(tempBuf, |
| "NOGPFWINEXE=%s\\bin\\%s\\nogpfntWinMain.obj /entry:nogpfntWinMainStartup", |
| REGRESS, TargetInfo[TargetMachine].name); |
| if (_putenv(tempBuf)) |
| Fatal("Couldn't set NOGPFWINEXE"); |
| |
| sprintf_s(tempBuf, |
| "NOGPFWINDLL=%s\\bin\\%s\\nogpfntDllMain.obj /entry:nogpfntDllMainStartup", |
| REGRESS, TargetInfo[TargetMachine].name); |
| if (_putenv(tempBuf)) |
| Fatal("Couldn't set NOGPFWINDLL"); |
| } else { |
| if (_putenv("NOGPF=")) |
| Fatal("Couldn't set NOGPF"); |
| if (_putenv("NOGPFWINEXE=")) |
| Fatal("Couldn't set NOGPFWINEXE"); |
| if (_putenv("NOGPFWINDLL=")) |
| Fatal("Couldn't set NOGPFWINDLL"); |
| } |
| } |
| |
| // Get the LINKFLAGS variable. If necessary, massage it and put it back into |
| // the environment for use by the regr386 scripts and makefiles. The |
| // manipulation includes: |
| // 1. add a "no access violation" object to avoid pop-ups |
| // 2. to make LTCG testing easier, if EXTRA_CC_FLAGS contains /B2c2.dll, we |
| // copy that to LINKFLAGS as -B2:c2.dll. We check that there doesn't already |
| // exist a -B2: on the LINKFLAGS, or that they don't conflict. If we don't |
| // do this manipulation, it is too easy to forget to add -B2: to LINKFLAGS |
| // on your own, and hence test some compiler besides the one you intend to, |
| // namely whatever c2.dll is on your path. |
| |
| void |
| GetLINKFLAGS() |
| { |
| char* readLINKFLAGS; |
| char* s; |
| char* b2; |
| char tmpLINKFLAGS[BUFFER_SIZE]; |
| char tmpstrtok[BUFFER_SIZE]; |
| char* tmpstrtoknext = NULL; |
| char tempBuf[BUFFER_SIZE]; |
| bool fPropagateB2; |
| |
| if ((readLINKFLAGS = getenv_unsafe("LINKFLAGS")) == NULL) { |
| if (NULL == TargetInfo[TargetMachine].LINKFLAGS) { |
| tmpLINKFLAGS[0] = '\0'; |
| } else { |
| strcpy_s(tmpLINKFLAGS, TargetInfo[TargetMachine].LINKFLAGS); |
| } |
| } else { |
| strcpy_s(tmpLINKFLAGS, readLINKFLAGS); |
| } |
| |
| // Look for -B2 or /B2 in EXTRA_CC_FLAGS |
| |
| fPropagateB2 = false; |
| b2 = NULL; |
| |
| strcpy_s(tmpstrtok, EXTRA_CC_FLAGS); |
| s = strtok_s(tmpstrtok, " \t", &tmpstrtoknext); |
| while (s) { |
| |
| #ifndef NODEBUG |
| if (FDebug) |
| printf("\tParsed '%s'\n", s); |
| #endif |
| |
| if ((0 == _strnicmp(s, "/B2", 3)) || (0 == _strnicmp(s, "-B2", 3))) { |
| if (b2 == NULL) { |
| b2 = s + 3; |
| if (*b2 == '\0') |
| b2++; |
| while (isspace(*b2)) |
| b2++; |
| b2 = _strdup(b2); |
| fPropagateB2 = true; |
| } else { |
| Warning("Two /B2 flags in EXTRA_CC_FLAGS (%s)", EXTRA_CC_FLAGS); |
| } |
| } |
| s = strtok_s(NULL, " \t", &tmpstrtoknext); |
| } |
| |
| // Now look for -B2: or /B2: in LINKFLAGS to check for consistency. |
| |
| strcpy_s(tmpstrtok, tmpLINKFLAGS); |
| tmpstrtoknext = NULL; |
| s = strtok_s(tmpstrtok, " \t", &tmpstrtoknext); |
| while (s) { |
| |
| #ifndef NODEBUG |
| if (FDebug) |
| printf("\tParsed '%s'\n", s); |
| #endif |
| |
| if ((0 == _strnicmp(s, "/B2:", 4)) || (0 == _strnicmp(s, "-B2:", 4))) { |
| fPropagateB2 = false; |
| if (b2 == NULL) { |
| Warning("/B2: in LINKFLAGS (%s) but not in EXTRA_CC_FLAGS (%s)", readLINKFLAGS, EXTRA_CC_FLAGS); |
| } else { |
| // Make sure they are the same |
| if (0 != _stricmp(s + 4, b2)) { |
| Warning("/B2 flags in EXTRA_CC_FLAGS (%s) conflict with -B2: in LINKFLAGS(%s)", EXTRA_CC_FLAGS, readLINKFLAGS); |
| } |
| } |
| } |
| s = strtok_s(NULL, " \t", &tmpstrtoknext); |
| } |
| |
| if (fPropagateB2) { |
| // We found -B2 in EXTRA_CC_FLAGS but not in LINKFLAGS, so propagate |
| // it to LINKFLAGS. |
| sprintf_s(tempBuf, " -B2:\"%s\"", b2); |
| free(b2); // came from _strdup() |
| strcat_s(tmpLINKFLAGS, tempBuf); |
| } |
| |
| // Now, put it back into the environment with all our changes |
| |
| sprintf_s(tempBuf, "LINKFLAGS=%s", tmpLINKFLAGS); |
| if (_putenv(tempBuf)) |
| Fatal("Couldn't set LINKFLAGS"); |
| |
| LINKFLAGS = _strdup(tmpLINKFLAGS); |
| } |
| |
| // WARNING: we are liberally using _putenv. As a result, it is dangerous to |
| // hold onto a pointer returned from getenv(), so always make a copy |
| |
| void |
| GetEnvironment( |
| void |
| ) |
| { |
| char *env; |
| FILE *showd_fp; |
| char tempBuf[BUFFER_SIZE]; |
| |
| // Get the TARGET_MACHINE environment variable. It may have already been |
| // specified on the command-line by the "-target" option, in which case |
| // we don't get the environment variable, but we do validate what the |
| // user passed. |
| |
| if ((TARGET_MACHINE != NULL) |
| || ((TARGET_MACHINE = getenv_unsafe("TARGET_MACHINE")) != NULL)) |
| { |
| TargetMachine = RLMachine = ParseMachine(TARGET_MACHINE); |
| if (TargetMachine == TM_UNKNOWN) { |
| Fatal("Unknown machine type specified by TARGET_MACHINE: %s", |
| TARGET_MACHINE); |
| } |
| if (TargetInfo[TargetMachine].fRL_MACHINEonly) { |
| Fatal("TARGET_MACHINE specified is only valid for RL_MACHINE: %s", |
| TARGET_MACHINE); |
| } |
| } |
| else { |
| // Set environment var in case regr scripts reference. |
| |
| sprintf_s(tempBuf, "TARGET_MACHINE=%s", TargetInfo[TargetMachine].name); |
| if (_putenv(tempBuf)) |
| Fatal("Couldn't set TARGET_MACHINE"); |
| } |
| |
| // Get the RL_MACHINE environment variable or option. |
| |
| if ((RL_MACHINE != NULL) |
| || ((RL_MACHINE = getenv_unsafe("RL_MACHINE")) != NULL)) |
| { |
| RLMachine = ParseMachine(RL_MACHINE); |
| if (RLMachine == TM_UNKNOWN) { |
| Fatal("Unknown machine type specified by RL_MACHINE: %s", RL_MACHINE); |
| } |
| } |
| else { |
| // Set environment var in case regr scripts reference. |
| |
| sprintf_s(tempBuf, "RL_MACHINE=%s", TargetInfo[RLMachine].name); |
| if (_putenv(tempBuf)) |
| Fatal("Couldn't set RL_MACHINE"); |
| } |
| |
| // Get the TARGET_OS environment variable or option. |
| if (TARGET_OS_NAME != NULL |
| || (TARGET_OS_NAME = getenv_unsafe("TARGET_OS")) != NULL) |
| { |
| TargetOS = ParseOS(TARGET_OS_NAME); |
| if (TargetOS == TO_UNKNOWN) { |
| Fatal("Unknown os type specified by TargetOS: %s", TARGET_OS_NAME); |
| } |
| } |
| |
| // Get the EXTRA_CC_FLAGS environment variable. |
| |
| if ((EXTRA_CC_FLAGS = getenv_unsafe("EXTRA_CC_FLAGS")) == NULL) { |
| char *cflags, *b2; |
| |
| // It doesn't exist, see if we can construct from RL_{ASM,EXE}_CFLAGS. |
| |
| if (Mode == RM_ASM) |
| cflags = getenv_unsafe("RL_ASM_CFLAGS"); |
| else |
| cflags = getenv_unsafe("RL_EXE_CFLAGS"); |
| |
| b2 = getenv_unsafe("RL_B2"); |
| if (BaseCompiler || DiffCompiler || ExeCompiler) { |
| if (FVerbose && b2) |
| puts("Command line specified compiler overrides RL_B2 environment variable"); |
| |
| if (ExeCompiler) |
| b2 = ExeCompiler; |
| else |
| b2 = NULL; |
| } |
| |
| sprintf_s(tempBuf, "%s%s%s", |
| cflags ? cflags : "", |
| b2 ? " -B2 " : "", |
| b2 ? b2 : ""); |
| EXTRA_CC_FLAGS = _strdup(tempBuf); |
| |
| sprintf_s(tempBuf, "EXTRA_CC_FLAGS=%s", EXTRA_CC_FLAGS); |
| if (_putenv(tempBuf)) |
| Fatal("Couldn't set EXTRA_CC_FLAGS"); |
| |
| if (FVerbose) |
| puts("Constructed EXTRA_CC_FLAGS from RL_{ASM,EXE}_CFLAGS and RL_B2"); |
| } else { |
| EXTRA_CC_FLAGS = _strdup(EXTRA_CC_FLAGS); |
| } |
| |
| // Get REGR_* overrides. |
| |
| if ((REGR_CL = getenv_unsafe("REGR_CL")) == NULL) { |
| REGR_CL = DEFAULT_REGR_CL; |
| } else { |
| REGR_CL = _strdup(REGR_CL); |
| } |
| |
| if ((REGR_DIFF = getenv_unsafe("REGR_DIFF")) == NULL) { |
| REGR_DIFF = DEFAULT_REGR_DIFF; |
| } else { |
| REGR_DIFF = _strdup(REGR_DIFF); |
| } |
| |
| REGR_ASM = getenv_unsafe("REGR_ASM"); |
| if (REGR_ASM != NULL) { |
| REGR_ASM = _strdup(REGR_ASM); |
| } |
| |
| // Get/generate {MASTER,DIFF}_DIR if doing assembly tests. |
| |
| if (Mode == RM_ASM) { |
| |
| // Get/generate the MASTER directory. |
| |
| if ((MASTER_DIR = getenv_unsafe("MASTER_DIR")) == NULL) { |
| if (FVerbose) { |
| // This should be the default, so don't warn |
| Warning("Generating MASTER_DIR environment variable"); |
| } |
| |
| sprintf_s(tempBuf, "master.%s", TargetInfo[RLMachine].name); |
| MASTER_DIR = _strdup(tempBuf); |
| |
| // Set environment var in case user overrides internal regr |
| // with -cmd. |
| |
| sprintf_s(tempBuf, "MASTER_DIR=%s", MASTER_DIR); |
| if (_putenv(tempBuf)) |
| Fatal("Couldn't set MASTER_DIR"); |
| } else { |
| MASTER_DIR = _strdup(MASTER_DIR); |
| } |
| |
| // Get/generate the DIFF directory. |
| |
| if (FMoveDiffs) { |
| if ((DIFF_DIR = getenv_unsafe("DIFF_DIR")) == NULL) { |
| if (FVerbose) { |
| // This should be the default, so don't warn |
| Warning("Generating DIFF_DIR environment variable"); |
| } |
| |
| sprintf_s(tempBuf, "diffs.%s", TargetInfo[RLMachine].name); |
| DIFF_DIR = _strdup(tempBuf); |
| |
| // Set environment var in case user overrides internal regr |
| // with -cmd. |
| |
| sprintf_s(tempBuf, "DIFF_DIR=%s", DIFF_DIR); |
| if (_putenv(tempBuf)) |
| Fatal("Couldn't set DIFF_DIR"); |
| } else { |
| DIFF_DIR = _strdup(DIFF_DIR); |
| } |
| } |
| else { |
| DIFF_DIR = "."; |
| if (_putenv("DIFF_DIR=")) |
| Fatal("Couldn't clear DIFF_DIR"); |
| } |
| |
| // Get the REGR_SHOWD directory. |
| |
| REGR_SHOWD = getenv_unsafe("REGR_SHOWD"); |
| if (REGR_SHOWD == NULL) { |
| sprintf_s(tempBuf, "%s\\bin\\showd.cmd", REGRESS); |
| REGR_SHOWD = _strdup(tempBuf); |
| } else { |
| REGR_SHOWD = _strdup(REGR_SHOWD); |
| } |
| showd_fp = fopen_unsafe(REGR_SHOWD, "rt"); |
| if (showd_fp == NULL) { |
| if (!FGenLst) |
| Fatal("couldn't open diff processing command file (%s) with error '%s'", REGR_SHOWD, strerror_unsafe(errno)); |
| } else |
| fclose(showd_fp); |
| } |
| |
| // Get executable test options. |
| |
| else { |
| if (EXEC_TESTS_FLAGS == NULL) { |
| if ((EXEC_TESTS_FLAGS = getenv_unsafe("EXEC_TESTS_FLAGS")) == NULL) |
| { |
| EXEC_TESTS_FLAGS = _strdup(DEFAULT_EXEC_TESTS_FLAGS); |
| } else { |
| // We edit EXEC_TESTS_FLAGS, so create a copy. |
| |
| EXEC_TESTS_FLAGS = _strdup(EXEC_TESTS_FLAGS); |
| } |
| } |
| |
| if ((TARGET_VM = getenv_unsafe("TARGET_VM")) == NULL) { |
| TARGET_VM = TargetInfo[TargetMachine].TARGET_VM; |
| |
| // Support automatic cross-compilation |
| |
| if ((TARGET_VM == NULL) && TargetInfo[TargetMachine].fAutoCrossCompilation) { |
| // If the host processor is not the same as the architecture to |
| // test, and we support automatic cross-compilation for this |
| // architecture, then create the default TARGET_VM. |
| |
| char* PROCESSOR_ARCHITECTURE; |
| |
| PROCESSOR_ARCHITECTURE = getenv_unsafe("PROCESSOR_ARCHITECTURE"); |
| if (PROCESSOR_ARCHITECTURE == NULL) { |
| Fatal("PROCESSOR_ARCHITECTURE environment variable not set (this should be set by the OS!)"); |
| } |
| |
| if (0 != _stricmp(TargetInfo[TargetMachine].name, PROCESSOR_ARCHITECTURE)) { |
| TARGET_VM = DEFAULT_CROSS_TARGET_VM; |
| } |
| } |
| } |
| |
| if (TARGET_VM != NULL) { |
| TARGET_VM = _strdup(TARGET_VM); |
| } |
| |
| if ((LINKER = getenv_unsafe("LINKER")) == NULL) { |
| LINKER = DEFAULT_LINKER; |
| } else { |
| LINKER = _strdup(LINKER); |
| } |
| |
| GetLINKFLAGS(); |
| |
| SetNOGPF(); |
| } |
| |
| // Grab CL and _CL_. |
| |
| CL = getenv_unsafe("CL"); |
| if (CL != NULL) |
| { |
| CL = _strdup(CL); |
| } |
| |
| _CL_ = getenv_unsafe("_CL_"); |
| if (_CL_ != NULL) |
| { |
| _CL_ = _strdup(_CL_); |
| } |
| |
| // Display all the settings. |
| |
| if (FVerbose) { |
| if ((env = getenv_unsafe(RL_PRE_ENV_VAR)) != NULL) |
| printf(RL_PRE_ENV_VAR"=%s\n", env); |
| if ((env = getenv_unsafe(RL_POST_ENV_VAR)) != NULL) |
| printf(RL_POST_ENV_VAR"=%s\n", env); |
| |
| printf("Target machine = %s", TargetInfo[RLMachine].name); |
| printf(", Target os = %s", TargetOSNames[TargetOS]); |
| if (Mode == RM_ASM) { |
| printf(", MASTER_DIR = %s", MASTER_DIR); |
| if (FMoveDiffs) |
| printf(", DIFF_DIR = %s", DIFF_DIR); |
| if (FBaseline) |
| printf(", creating baselines"); |
| if (FDiff) |
| printf(", checking diffs"); |
| } |
| else { |
| printf(", TARGET_VM = %s", TARGET_VM); |
| printf(", EXEC_TESTS_FLAGS = %s", EXEC_TESTS_FLAGS); |
| } |
| printf("\nEXTRA_CC_FLAGS = %s\n", EXTRA_CC_FLAGS); |
| |
| printf("REGR_CL = %s, REGR_ASM = %s", REGR_CL, REGR_ASM); |
| if (Mode == RM_ASM) { |
| printf(", REGR_DIFF = %s", REGR_DIFF); |
| } |
| else { |
| printf("\nLINKER = %s, LINKFLAGS = %s", LINKER, LINKFLAGS); |
| } |
| putchar('\n'); |
| |
| if (FLow) |
| puts("** Running with a low process priority"); |
| |
| printf("CL = %s, _CL_ = %s\n", CL, _CL_); |
| |
| PrintTagsList(TagsList); |
| PrintTagsList(DirectoryTagsList); |
| } |
| |
| if (FStatus && (GetConsoleTitle(SavedConsoleTitle, BUFFER_SIZE) == 0)) |
| Warning("Couldn't get console title; won't be correctly restored"); |
| } |
| |
| void |
| PrintTagsList( |
| Tags* pTagsList |
| ) |
| { |
| if (pTagsList != NULL) |
| { |
| for (Tags * tags = pTagsList; tags != NULL; tags = tags->next) |
| { |
| printf(" %s '%s'\n", tags->fInclude ? "include" : "exclude", tags->str); |
| } |
| } |
| else |
| { |
| printf(" None\n"); |
| } |
| } |
| |
| // Initialize a file list. |
| void |
| InitTestList( |
| TestList * pTestList |
| ) |
| { |
| pTestList->first = pTestList->last = NULL; |
| } |
| |
| // Free a file list. |
| void |
| FreeTestList( |
| TestList * pTestList |
| ) |
| { |
| Test * pFilename, * pFree; |
| |
| pFilename = pTestList->first; |
| |
| while (pFilename) { |
| pFree = pFilename; |
| pFilename = pFilename->next; |
| |
| FreeStringList(pFree->files); |
| FreeVariants(pFree->variants); |
| |
| free(pFree); |
| } |
| |
| InitTestList(pTestList); |
| } |
| |
| // Add one file to a file list. |
| Test * |
| AddToTestList( |
| TestList * pTestList, |
| StringList * fileList |
| ) |
| { |
| Test * pFilename; |
| |
| ASSERTNR(pTestList); |
| |
| pFilename = (Test *)malloc(sizeof(Test)); |
| memset(pFilename, 0, sizeof(Test)); |
| pFilename->files = fileList; |
| |
| if (pTestList->last) { |
| ASSERTNR(pTestList->first); |
| pTestList->last->next = pFilename; |
| } |
| else { |
| ASSERTNR(pTestList->first == NULL); |
| pTestList->first = pFilename; |
| } |
| |
| pTestList->last = pFilename; |
| |
| #ifndef NODEBUG |
| if (FDebug) |
| printf("Added %s\n", pFilename->name); |
| #endif |
| |
| return pFilename; |
| } |
| |
| // Add one file to a file list. |
| Test * |
| AddToTestList( |
| TestList * pTestList, |
| char * name |
| ) |
| { |
| Test * pTest = AddToTestList(pTestList, (StringList *)NULL); |
| pTest->name = name; |
| return pTest; |
| } |
| |
| // Add one dir to the dir list. |
| Test * |
| AddDir( |
| TestList * pTestList, |
| char *name |
| ) |
| { |
| return AddToTestList(pTestList, name); |
| } |
| |
| // Add the current directory to the directory list. |
| void |
| AddCurrentDir( |
| void |
| ) |
| { |
| char *dir; |
| char tempBuf[BUFFER_SIZE]; |
| |
| if (!GetCurrentDirectory(BUFFER_SIZE, tempBuf)) |
| Fatal("GetCurrentDirectory couldn't live up to its name"); |
| |
| // If REGRESS is not a prefix, emit a warning. |
| |
| if (_strnicmp(tempBuf, REGRESS, strlen(REGRESS))) { |
| Warning("Current directory '%s' is not in the '%s' tree", |
| tempBuf, REGRESS); |
| AddDir(&DirList, _strdup(tempBuf)); |
| } |
| else { |
| dir = tempBuf + strlen(REGRESS); |
| if (!IS_PATH_CHAR(*dir)) { |
| Fatal("Current directory '%s' is not a subdirectory of '%s'", |
| tempBuf, REGRESS); |
| } |
| else { |
| AddDir(&DirList, _strdup(dir + 1)); |
| } |
| } |
| } |
| |
| // Add a list of user-specified directories. |
| void |
| AddUserDirs( |
| TestList * pTestList, |
| char *s |
| ) |
| { |
| char *context = NULL; |
| s = strtok_s(s, XML_DELIM, &context); |
| while (s) { |
| AddDir(pTestList, s); |
| s = strtok_s(NULL, XML_DELIM, &context); |
| } |
| } |
| |
| #ifndef NODEBUG |
| |
| void |
| PrintStringList |
| ( |
| StringList *pList |
| ) |
| { |
| for (StringList *cur = pList; cur != NULL; cur = cur->next) { |
| printf("\t%s\n", cur->string); |
| } |
| } |
| |
| void |
| PrintTestInfo |
| ( |
| TestInfo *pTestInfo |
| ) |
| { |
| ConstStringList * GetNameDataPairs(Xml::Node * node); |
| |
| for(int i=0;i < _TIK_COUNT; i++) { |
| if ((i == TIK_ENV) && pTestInfo->data[TIK_ENV]) { |
| auto pStringList = GetNameDataPairs((Xml::Node*)pTestInfo->data[TIK_ENV]); |
| if (pStringList) { |
| for(; pStringList != NULL; pStringList = pStringList->next->next) { |
| ASSERT(pStringList->next); |
| if (pStringList->next->string[0]=='%') { |
| char tmpBuf[BUFFER_SIZE]; |
| strncpy_s(tmpBuf, pStringList->next->string+1, strlen(pStringList->next->string)-2); |
| tmpBuf[strlen(pStringList->next->string)-2]=0; |
| if (getenv_unsafe(tmpBuf)==NULL) { |
| char msgBuf[BUFFER_SIZE]; |
| sprintf_s(msgBuf, "%s environment variable used is not set\n", tmpBuf); |
| Fatal(msgBuf); |
| } else { |
| printf("\t%s=%s\n",pStringList->string, getenv_unsafe(tmpBuf)); |
| } |
| } else { |
| printf("\t%s=%s\n",pStringList->string, pStringList->next->string); |
| } |
| } |
| } |
| }else if (pTestInfo->data[i]) { |
| printf("\t%s\n", pTestInfo->data[i]); |
| } |
| } |
| } |
| |
| // Display the file list. |
| void |
| DumpTestList |
| ( |
| TestList * pTestList |
| ) |
| { |
| for (Test * pTest = pTestList->first; pTest != NULL; pTest = pTest->next) |
| { |
| printf("Files:\n"); |
| PrintStringList(pTest->files); |
| printf("\n"); |
| if (pTest->fullPath) |
| printf("fullpath (%s):\n", pTest->fullPath); |
| |
| for(ConditionNodeList * conditionNodeList = pTest->conditionNodeList; |
| conditionNodeList != NULL; conditionNodeList=conditionNodeList->next) |
| { |
| printf("Condition nodes\n"); |
| conditionNodeList->node->Dump(); |
| } |
| |
| printf("Default testinfo\n"); |
| PrintTestInfo(&pTest->defaultTestInfo); |
| int i=0; |
| for(TestVariant * variants=pTest->variants; variants!=nullptr; variants=variants->next) { |
| printf("variant %d\n", i++); |
| PrintTestInfo(&variants->testInfo); |
| printf("opt flag %s\n", variants->optFlags); |
| } |
| |
| printf("\n\n"); |
| } |
| } |
| |
| #endif |
| |
| |
| void |
| PadSpecialChars |
| ( |
| char * strOut, |
| const char * strIn |
| ) |
| { |
| int i = 0; |
| while (*strIn) { |
| strOut[i++] = *strIn++; |
| if (strOut[i-1] == '\\') |
| strOut[i++] = '\\'; |
| } |
| strOut[i] = '\0'; |
| } |
| |
| // given an xml node, returns the name-data StringList pairs for all children |
| ConstStringList * GetNameDataPairs |
| ( |
| Xml::Node * node |
| ) |
| { |
| ASSERT(node->ChildList != NULL); |
| ConstStringList *pStringList = NULL; |
| for (Xml::Node *ChildNode = node->ChildList; ChildNode != NULL; ChildNode = ChildNode->Next) { |
| pStringList = AddToStringList(pStringList, ChildNode->Name); |
| pStringList = AddToStringList(pStringList, ChildNode->Data); |
| } |
| return pStringList; |
| } |
| |
| // create the file env.lst |
| void |
| WriteEnvLst |
| ( |
| Test * pDir, TestList * pTestList |
| ) |
| { |
| char comments[BUFFER_SIZE]; |
| char envlst[BUFFER_SIZE]; |
| char noSpecialCharsBuf[BUFFER_SIZE]; |
| |
| ASSERT(pDir->fullPath); |
| |
| sprintf_s(envlst, "%s%s", pDir->fullPath, "\\" DEFAULT_ENVLST_CFG); |
| |
| DeleteFileIfFound(envlst); |
| COutputBuffer *LstFilesOut = new COutputBuffer(envlst, FSyncImmediate ? false : true); |
| ASSERT(LstFilesOut); |
| |
| for (Test * pTest = pTestList->first; pTest != NULL; pTest = pTest->next) |
| { |
| for(TestVariant * variants=pTest->variants; variants!=NULL; variants=variants->next) { |
| // print the tags first |
| if (variants->testInfo.data[TIK_TAGS]) { |
| LstFilesOut->Add("%s\t", variants->testInfo.data[TIK_TAGS]); |
| } else if (pTest->defaultTestInfo.data[TIK_TAGS]){ |
| LstFilesOut->Add("%s\t", pTest->defaultTestInfo.data[TIK_TAGS]); |
| } else { |
| LstFilesOut->Add("\t"); |
| } |
| LstFilesOut->Add("TESTNAME=%s", pTest->name); |
| strcpy_s(comments, pTest->name); |
| |
| PadSpecialChars(noSpecialCharsBuf, EXTRA_CC_FLAGS); |
| if (noSpecialCharsBuf[0] != '\0') { |
| LstFilesOut->Add(" EXTRA_CC_FLAGS=\"%s\"", noSpecialCharsBuf); |
| } |
| LstFilesOut->Add(" REGR_CL=%s", REGR_CL); |
| LstFilesOut->Add(" LINKER=%s", LINKER); |
| PadSpecialChars(noSpecialCharsBuf, LINKFLAGS); |
| if (noSpecialCharsBuf[0] != '\0') { |
| LstFilesOut->Add(" LINKFLAGS=\"%s\"", noSpecialCharsBuf); |
| } |
| |
| if (pTest->files) { |
| LstFilesOut->Add(" FILES=\""); |
| StringList* pStringList = pTest->files; |
| for(;pStringList != NULL;pStringList=pStringList->next) { |
| LstFilesOut->Add("%s ", pStringList->string); |
| } |
| LstFilesOut->Add("\""); |
| } |
| |
| // how we translate testinfo into env.lst specific stuffs |
| const char * const TestInfoEnvLstFmt[] = |
| { |
| " TESTFILE=\"%s\"", |
| " BASELINE=\"%s\"", |
| " CFLAGS=\"%s\"", |
| " LFLAGS=\"%s\"", |
| NULL, |
| NULL, |
| NULL, |
| NULL, |
| NULL, |
| NULL, |
| NULL, |
| NULL, |
| NULL |
| }; |
| |
| static_assert((sizeof(TestInfoEnvLstFmt) / sizeof(TestInfoEnvLstFmt[0])) == TestInfoKind::_TIK_COUNT, "Fix the buffer size"); |
| |
| // print the other TIK_* |
| for(int i=0;i < _TIK_COUNT; i++) { |
| if (variants->testInfo.data[i] && TestInfoEnvLstFmt[i]) { |
| LstFilesOut->Add(TestInfoEnvLstFmt[i], variants->testInfo.data[i]); |
| } else if (pTest->defaultTestInfo.data[TIK_RL_DIRECTIVES]) { |
| // we only handle NoGPF directive, passFo and NoAsm not used |
| if (SuppressNoGPF(pTest)) { |
| LstFilesOut->Add(" NOGPF=1"); |
| } |
| } |
| } |
| LstFilesOut->Add(" optFlags=\"%s\"", variants->optFlags); |
| strcat_s(comments, " "); strcat_s(comments, variants->optFlags); |
| |
| // print the env settings |
| if (variants->testInfo.data[TIK_ENV]) { |
| auto pStringList = GetNameDataPairs((Xml::Node*)variants->testInfo.data[TIK_ENV]); |
| if (pStringList) { |
| // assuming even number of elements |
| for(; pStringList != NULL; pStringList = pStringList->next->next) { |
| ASSERT(pStringList->next); |
| if (pStringList->next->string[0]=='%') { |
| // grab the env variable specified in the pStringList->next->string |
| char tmpBuf[BUFFER_SIZE]; |
| strncpy_s(tmpBuf, pStringList->next->string+1, strlen(pStringList->next->string)-2); |
| tmpBuf[strlen(pStringList->next->string)-2]=0; |
| if (getenv_unsafe(tmpBuf)==NULL) { |
| char msgBuf[BUFFER_SIZE]; |
| sprintf_s(msgBuf, "%s environment variable used in %s not set\n", tmpBuf, envlst); |
| Fatal(msgBuf); |
| } else { |
| LstFilesOut->Add(" %s=%s",pStringList->string, getenv_unsafe(tmpBuf)); |
| sprintf_s(comments, "%s %s=%s", comments, pStringList->string, getenv_unsafe(tmpBuf)); |
| } |
| } else { |
| LstFilesOut->Add(" %s=%s",pStringList->string, pStringList->next->string); |
| sprintf_s(comments, "%s %s=%s", comments, pStringList->string, pStringList->next->string); |
| } |
| } |
| FreeStringList(pStringList); |
| } |
| } |
| LstFilesOut->Add(" # %s\n", comments); |
| } |
| } |
| |
| LstFilesOut->Flush(); |
| delete LstFilesOut; |
| printf(" %s\n", envlst); |
| } |
| |
| BOOL |
| IsRelativePath( |
| const char *path |
| ) |
| { |
| char drive[MAX_PATH], dir[MAX_PATH]; |
| |
| _splitpath_s(path, drive, ARRAYLEN(drive), dir, ARRAYLEN(dir), NULL, 0, NULL, 0); |
| |
| // Path is relative if neither drive nor absolute directory are specified. |
| |
| if (!*drive && !IS_PATH_CHAR(dir[0])) |
| return TRUE; |
| |
| return FALSE; |
| } |
| |
| char* |
| MakeFullPath(const char* absFilePath, const char* relPath) |
| { |
| char drive[MAX_PATH], dir[MAX_PATH]; |
| _splitpath_s(absFilePath, drive, ARRAYLEN(drive), dir, ARRAYLEN(dir), NULL, 0, NULL, 0); |
| |
| char makepath[MAX_PATH + 1]; |
| makepath[MAX_PATH] = '\0'; |
| _makepath_s(makepath, drive, dir, relPath, NULL); |
| |
| char fullpath[MAX_PATH + 1]; |
| fullpath[MAX_PATH] = '\0'; |
| if (_fullpath(fullpath, makepath, MAX_PATH) == NULL) |
| { |
| return NULL; |
| } |
| |
| char* fullPathBuf = (char*)malloc(sizeof(fullpath)); |
| sprintf_s(fullPathBuf, sizeof(fullpath), "%s", fullpath); |
| |
| return fullPathBuf; |
| } |
| |
| BOOL |
| VerifyOrCreateDir( |
| char *name, |
| BOOL fCreate |
| ) |
| { |
| if (FTest) |
| { |
| return TRUE; |
| } |
| |
| DWORD attrib; |
| |
| attrib = GetFileAttributes(name); |
| |
| // Already exists? |
| |
| if (attrib != INVALID_FILE_ATTRIBUTES) { |
| |
| // Make sure it's a directory. |
| |
| if (!(attrib & FILE_ATTRIBUTE_DIRECTORY)) { |
| LogError("%s exists but is not a subdirectory", name); |
| return FALSE; |
| } |
| } |
| |
| // Are we allowed to create. |
| |
| else if (!fCreate) { |
| LogError("%s doesn't exist", name); |
| return FALSE; |
| } |
| |
| // Try to create it. |
| |
| else if (!CreateDirectory(name, NULL)) { |
| LogError("Unable to create %s", name); |
| return FALSE; |
| } |
| |
| else { |
| Warning("Created '%s' directory", name); |
| } |
| |
| return TRUE; |
| } |
| |
| // Create MASTER_DIR and DIFF_DIR if non-existent. |
| BOOL |
| CreateAsmDirs( |
| char* root, |
| BOOL fBaseline |
| ) |
| { |
| char path[_MAX_PATH]; |
| |
| ASSERTNR(!IsRelativePath(root)); |
| |
| sprintf_s(path, "%s\\%s", root, MASTER_DIR); |
| |
| if (!VerifyOrCreateDir(path, fBaseline)) |
| return FALSE; |
| |
| if (FMoveDiffs && FDiff) { |
| sprintf_s(path, "%s\\%s", root, DIFF_DIR); |
| if (!VerifyOrCreateDir(path, TRUE)) |
| return FALSE; |
| } |
| |
| return TRUE; |
| } |
| |
| void |
| PrintDirs( |
| void |
| ) |
| { |
| Test * pDir; |
| |
| printf("Regression directories:"); |
| for (pDir = DirList.first; pDir; pDir = pDir->next) { |
| printf(" %s", pDir->name); |
| } |
| printf("\n\n"); |
| |
| if (ExcludeDirList.first != NULL) { |
| printf("Regression exclude directories:"); |
| for (pDir = ExcludeDirList.first; pDir; pDir = pDir->next) { |
| printf(" %s", pDir->name); |
| } |
| printf("\n\n"); |
| } |
| } |
| |
| char * |
| GetNonEmptyArg( |
| char *arg |
| ) |
| { |
| if (arg && (*arg == '\0')) |
| return NULL; |
| |
| return arg; |
| } |
| |
| char * |
| ComplainIfNoArg( |
| char *opt, |
| char *arg |
| ) |
| { |
| if ((arg == NULL) || (*arg == '\0')) |
| Fatal("%s requires an argument", opt); |
| |
| return arg; |
| } |
| |
| // Parse a single command line argument. Returns 1 if -args processed. |
| // Returns -1 if a non-switch was processed and pTestList is NULL. |
| int |
| ParseArg( |
| char *arg, |
| TestList * pTestList |
| ) |
| { |
| char tempBuf[BUFFER_SIZE]; |
| char *s; |
| |
| switch (arg[0]) { |
| case '-': |
| case '/': |
| s = strchr(arg, ':'); |
| if (s) |
| *s++ = '\0'; |
| |
| if (!_stricmp(&arg[1], "exe")) { |
| Mode = RM_EXE; |
| ExeCompiler = GetNonEmptyArg(s); |
| break; |
| } |
| if (!_stricmp(&arg[1], "asm")) { |
| Mode = RM_ASM; |
| ExeCompiler = GetNonEmptyArg(s); |
| break; |
| } |
| if (!_stricmp(&arg[1], "log")) { |
| LogName = ComplainIfNoArg(arg, s); |
| break; |
| } |
| if (!_stricmp(&arg[1], "full")) { |
| FullLogName = ComplainIfNoArg(arg, s); |
| break; |
| } |
| if (!_stricmp(&arg[1], "results")) { |
| ResultsLogName = ComplainIfNoArg(arg, s); |
| break; |
| } |
| // For backward-compatibility, support -tee |
| if (!_stricmp(&arg[1], "tee")) { |
| Warning("-tee is deprecated; use -full instead"); |
| FullLogName = ComplainIfNoArg(arg, s); |
| break; |
| } |
| if (!_stricmp(&arg[1], "all")) { |
| FUserSpecifiedDirs = FALSE; |
| break; |
| } |
| if (!_stricmp(&arg[1], "dcfg")) { |
| DCFGfile = ComplainIfNoArg(arg, s); |
| break; |
| } |
| if (!_stricmp(&arg[1], "cfg")) { |
| CFGfile = ComplainIfNoArg(arg, s); |
| break; |
| } |
| if (!_stricmp(&arg[1], "genlst")) { |
| FGenLst = TRUE; |
| Mode = RM_EXE; |
| break; |
| } |
| // Note: we support "-dir:" for backwards-compatibility |
| if (!_stricmp(&arg[1], "dir") || !_stricmp(&arg[1], "dirs")) { |
| AddUserDirs(&DirList, ComplainIfNoArg(arg, s)); |
| break; |
| } |
| if (!_stricmp(&arg[1], "nodirs")) { |
| FExcludeDirs = TRUE; |
| AddUserDirs(&ExcludeDirList, ComplainIfNoArg(arg, s)); |
| break; |
| } |
| if (!_stricmp(&arg[1], "resume")) { |
| ResumeDir = ComplainIfNoArg(arg, s); |
| FUserSpecifiedDirs = FALSE; |
| FAppend = TRUE; |
| break; |
| } |
| if (!_stricmp(&arg[1], "match")) { |
| MatchDir = ComplainIfNoArg(arg, s); |
| FUserSpecifiedDirs = FALSE; |
| break; |
| } |
| if (!_stricmp(&arg[1], "status")) { |
| |
| // -status:- disables. |
| |
| if (s && !strcmp(s, "-")) { |
| FStatus = FALSE; |
| } |
| else { |
| FStatus = TRUE; |
| StatusPrefix = s; |
| } |
| break; |
| } |
| if (!_stricmp(&arg[1], "statusformat")) { |
| s = ComplainIfNoArg(arg, s); |
| StatusFormat = _strdup(s); |
| break; |
| } |
| if (!_stricmp(&arg[1], "rlfe")) { |
| FRLFE = TRUE; |
| if (s) |
| RLFEOpts = s; |
| break; |
| } |
| if (!_stricmp(&arg[1], "verbose")) { |
| FVerbose = TRUE; |
| break; |
| } |
| if (!_stricmp(&arg[1], "quiet")) { |
| FQuiet = TRUE; |
| break; |
| } |
| if (!_stricmp(&arg[1], "summary") || |
| !_stricmp(&arg[1], "nodirname") || |
| !_stricmp(&arg[1], "movediffs")) |
| { |
| Warning("%s is deprecated; it is now the default\n", arg); |
| break; |
| } |
| if (!_stricmp(&arg[1], "nosummary")) { |
| FSummary = FNoDirName = FALSE; |
| break; |
| } |
| if (!_stricmp(&arg[1], "allowpopup")) { |
| FNogpfnt = FALSE; |
| break; |
| } |
| if (!_stricmp(&arg[1], "append")) { |
| FAppend = TRUE; |
| break; |
| } |
| if (!_stricmp(&arg[1], "appendtestnametoextraccflags")) { |
| FAppendTestNameToExtraCCFlags = TRUE; |
| break; |
| } |
| if (!_stricmp(&arg[1], "singlethreadperdir")) { |
| FSingleThreadPerDir = TRUE; |
| break; |
| } |
| if (!_stricmp(&arg[1], "dirname")) { |
| FNoDirName = FALSE; |
| break; |
| } |
| if (!_stricmp(&arg[1], "nomovediffs")) { |
| FNoMoveDiffsSwitch = TRUE; |
| break; |
| } |
| if (!_stricmp(&arg[1], "nowarn")) { |
| FNoWarn = TRUE; |
| break; |
| } |
| if (!_stricmp(&arg[1], "low")) { |
| FLow = TRUE; |
| break; |
| } |
| if (!_stricmp(&arg[1], "test")) { |
| FTest = TRUE; |
| break; |
| } |
| if (!_stricmp(&arg[1], "stoponerror")) |
| { |
| FStopOnError = TRUE; |
| break; |
| } |
| if (!_stricmp(&arg[1], "exeflags")) { |
| EXEC_TESTS_FLAGS = ComplainIfNoArg(arg, s); |
| break; |
| } |
| if (!_stricmp(&arg[1], "target")) { |
| TARGET_MACHINE = ComplainIfNoArg(arg, s); |
| |
| // Some test scripts use TARGET_MACHINE, so put it into the |
| // environment. |
| |
| sprintf_s(tempBuf, "TARGET_MACHINE=%s", TARGET_MACHINE); |
| if (_putenv(tempBuf)) |
| Fatal("Couldn't set TARGET_MACHINE"); |
| break; |
| } |
| if (!_stricmp(&arg[1], "os")) { |
| TARGET_OS_NAME = ComplainIfNoArg(arg, s); |
| break; |
| } |
| if (!_stricmp(&arg[1], "rltarget")) { |
| RL_MACHINE = ComplainIfNoArg(arg, s); |
| break; |
| } |
| if (!_stricmp(&arg[1], "threads")) { |
| char* temp; |
| temp = ComplainIfNoArg(arg, s); |
| NumberOfThreads = atoi(temp); |
| if (NumberOfThreads == 0 || NumberOfThreads > MAX_ALLOWED_THREADS) { |
| Fatal("Illegal # of threads; must be between 1 and %d", MAX_ALLOWED_THREADS); |
| } |
| break; |
| } |
| if (!_stricmp(&arg[1], "regress")) { |
| REGRESS = ComplainIfNoArg(arg, s); |
| |
| // Some test scripts use REGRESS, so put it into the |
| // environment. |
| |
| sprintf_s(tempBuf, "REGRESS=%s", REGRESS); |
| if (_putenv(tempBuf)) |
| Fatal("Couldn't set REGRESS"); |
| break; |
| } |
| if (!_stricmp(&arg[1], "sync")) { |
| s = ComplainIfNoArg(arg, s); |
| if (!_stricmp(s, "immediate")) |
| FSyncImmediate = TRUE; |
| else if (!_stricmp(s, "variation")) |
| FSyncVariation = TRUE; |
| else if (!_stricmp(s, "test")) |
| FSyncTest = TRUE; |
| else if (!_stricmp(s, "dir")) |
| FSyncDir = TRUE; |
| else |
| Fatal("Unknown -sync option"); |
| break; |
| } |
| if (!_stricmp(&arg[1], "binary")) { |
| s = ComplainIfNoArg(arg, s); |
| JCBinary = _strdup(s); |
| break; |
| } |
| if (!_stricmp(&arg[1], "nothreadid")) { |
| FNoThreadId = TRUE; |
| break; |
| } |
| // We support "-baseline", but we only document "-base". Maybe we |
| // should deprecate it and emit a warning message... |
| if (!_stricmp(&arg[1], "baseline") || !_stricmp(&arg[1], "base")) { |
| FBaseline = TRUE; |
| BaseCompiler = GetNonEmptyArg(s); |
| break; |
| } |
| if (!_stricmp(&arg[1], "rebase")) { |
| FRebase = TRUE; |
| break; |
| } |
| if (!_stricmp(&arg[1], "diff")) { |
| DiffCompiler = GetNonEmptyArg(s); |
| FDiff = TRUE; |
| break; |
| } |
| if (!_stricmp(&arg[1], "rellog")) { |
| FRelativeLogPath = TRUE; |
| break; |
| } |
| if (!_stricmp(&arg[1], "nodelete")) { |
| FNoDelete = TRUE; |
| break; |
| } |
| if (!_stricmp(&arg[1], "copyonfail")) { |
| FCopyOnFail = TRUE; |
| break; |
| } |
| if (!_stricmp(&arg[1], "time")) { |
| s = ComplainIfNoArg(arg, s); |
| if (!_stricmp(s, "variation")) |
| Timing |= TIME_VARIATION; |
| else if (!_stricmp(s, "test")) |
| Timing |= TIME_TEST; |
| else if (!_stricmp(s, "dir")) |
| Timing |= TIME_DIR; |
| else if (!_stricmp(s, "all")) |
| Timing |= TIME_ALL; |
| else |
| Fatal("Unknown -time option"); |
| break; |
| } |
| |
| if ((!_stricmp(&arg[1], "nottags")) || (!_stricmp(&arg[1], "tags"))) { |
| s = ComplainIfNoArg(arg, s); |
| AddTagToTagsList(&TagsList, &TagsLast, s, strcmp(&arg[1], "tags") == 0); |
| |
| break; |
| } |
| |
| if ((!_stricmp(&arg[1], "dirnottags")) || (!_stricmp(&arg[1], "dirtags"))) { |
| s = ComplainIfNoArg(arg, s); |
| AddTagToTagsList(&DirectoryTagsList, &DirectoryTagsLast, s, strcmp(&arg[1], "dirtags") == 0); |
| |
| break; |
| } |
| |
| if (!_stricmp(&arg[1], "noprogramoutput")) { |
| FNoProgramOutput = TRUE; |
| break; |
| } |
| |
| if (!_stricmp(&arg[1], "onlyassertoutput")) { |
| FOnlyAssertOutput = TRUE; |
| break; |
| } |
| |
| if (!_stricmp(&arg[1], "timeout")) { |
| TestTimeout = ComplainIfNoArg(arg, s); |
| break; |
| } |
| |
| if (!_stricmp(&arg[1], "timeoutRetries")) { |
| TestTimeoutRetries = ComplainIfNoArg(arg, s); |
| break; |
| } |
| |
| #ifndef NODEBUG |
| if (!_stricmp(&arg[1], "debug")) { |
| FDebug = FVerbose = TRUE; |
| break; |
| } |
| if (!_stricmp(&arg[1], "syncdirs")) { |
| FSyncEnumDirs = TRUE; |
| break; |
| } |
| #endif |
| |
| switch (arg[1]) { |
| case '?': |
| case 'h': |
| case 'H': |
| switch (arg[2]) { |
| case '\0': |
| Usage(FALSE); |
| exit(0); |
| |
| case 'x': |
| Usage(TRUE); |
| exit(0); |
| } |
| |
| // FALL-THROUGH |
| |
| default: |
| Fatal("Unrecognized option: %s", arg); |
| } |
| |
| default: |
| if (pTestList == NULL) |
| return -1; |
| |
| FUserSpecifiedFiles = TRUE; |
| AddToTestList(pTestList, arg); |
| } |
| |
| return 0; |
| } |
| |
| void |
| AddTagToTagsList( |
| Tags** pTagsList, |
| Tags** pTagsLast, |
| const char* str, |
| BOOL fInclude |
| ) |
| { |
| if (pTagsList == NULL || pTagsLast == NULL) |
| { |
| return; |
| } |
| |
| Tags* tags = new Tags; |
| |
| tags->str = str; |
| tags->fInclude = fInclude; |
| tags->next = NULL; |
| |
| if (*pTagsLast) |
| { |
| (*pTagsLast)->next = tags; |
| } |
| else |
| { |
| (*pTagsList) = tags; |
| } |
| |
| (*pTagsLast) = tags; |
| } |
| |
| // Determines by tags if we should include a given test. |
| // |
| // Priority: |
| // 1. -nottags. Check the full tags list. If we have any exclude tag, reject the test. |
| // 2. -tags. If #1 didn't reject the test, we only accept the test if |
| // a. There is no include tag, |
| // b. Or we matched an include tag, or "mustMatchIncludeTag" is false. |
| // |
| bool |
| ShouldIncludeTest( |
| Tags* pTagsList, |
| TestInfo* testInfo, |
| bool mustMatchIncludeTag = true // false when matching "-tags" on a folder. (The folder may contain tests matching the tags, although itself doesn't.) |
| ) |
| { |
| // Automatically append tag "html" to any html tests |
| bool isHtmlTest = false; |
| if (testInfo->data[TIK_FILES]) |
| { |
| const char* ext = GetFilenameExt(testInfo->data[TIK_FILES]); |
| isHtmlTest = _stricmp(ext, ".html") == 0 || _stricmp(ext, ".htm") == 0; |
| } |
| |
| bool hasIncludeTag = false; // if we have "-tags:" filter |
| bool matchedIncludeTag = !mustMatchIncludeTag; // if the test has matched an include tag |
| |
| for (Tags* tags = pTagsList; tags != NULL; tags = tags->next) |
| { |
| if (matchedIncludeTag && tags->fInclude) |
| { |
| continue; // Already matched one include tag, skip further include tags. Continue to check if we have any exclude tag. |
| } |
| |
| bool hasTag = HasInfoList(testInfo->data[TIK_TAGS], XML_DELIM, tags->str, XML_DELIM, false) |
| || (isHtmlTest && HasInfo(tags->str, XML_DELIM, "html")); |
| |
| if (hasTag && !tags->fInclude) |
| { |
| return false; // #1: Reject, we have an exclude tag. |
| } |
| |
| if (tags->fInclude) |
| { |
| hasIncludeTag = true; |
| matchedIncludeTag |= hasTag; |
| } |
| } |
| |
| return !hasIncludeTag || matchedIncludeTag; |
| } |
| |
| void |
| ParseEnvVar( |
| const char *envVar |
| ) |
| { |
| char * s; |
| char * context; |
| |
| s = getenv_unsafe(envVar); |
| if (s == NULL) |
| return; |
| |
| s = _strdup(s); |
| |
| s = strtok_s(s, " \"", &context); |
| while (s) { |
| switch (ParseArg(s, NULL)) { |
| case 1: |
| Fatal("-args may not appear in %s environment variable", |
| envVar); |
| case -1: |
| Fatal("Only switches may appear in %s environment variable", |
| envVar); |
| } |
| s = strtok_s(NULL, " \"", &context); |
| } |
| } |
| |
| // Parse the command line and environment variable. |
| void |
| ParseCommandLine( |
| int argc, |
| char *argv[], |
| TestList * pTestList |
| ) |
| { |
| char *s; |
| int i; |
| char tempBuf[BUFFER_SIZE]; |
| |
| ProgramName = argv[0]; |
| |
| ParseEnvVar(RL_PRE_ENV_VAR); |
| |
| for (i = 1; i < argc; i++) { |
| ParseArg(argv[i], pTestList); |
| } |
| |
| ParseEnvVar(RL_POST_ENV_VAR); |
| |
| // Get the REGRESS environment variable. |
| |
| if (REGRESS == NULL) |
| REGRESS = getenv_unsafe("REGRESS"); |
| if (REGRESS == NULL) |
| Fatal("REGRESS environment variable not set"); |
| |
| // Use "rl.log" as the default log file, and rl.full.log as the default |
| // full log file. We make sure the log file is a full path. If the user |
| // specified "-all", then the logs go in %REGRESS%\logs. Otherwise, they |
| // go in the current directory. |
| |
| if (LogName == NULL) { |
| // The user didn't specify a log filename, so create one. |
| |
| if (!FUserSpecifiedDirs || (DirList.first != NULL)) { |
| strcpy_s(tempBuf, REGRESS); |
| strcat_s(tempBuf, "\\logs\\"); |
| strcat_s(tempBuf, DEFAULT_LOG_FILE); |
| LogName = _strdup(tempBuf); |
| } else { |
| LogName = DEFAULT_LOG_FILE; |
| } |
| } |
| |
| if (FullLogName == NULL) { |
| // The user didn't specify a full log filename, so create one. |
| |
| if (!FUserSpecifiedDirs || (DirList.first != NULL)) { |
| strcpy_s(tempBuf, REGRESS); |
| strcat_s(tempBuf, "\\logs\\"); |
| strcat_s(tempBuf, DEFAULT_FULL_LOG_FILE); |
| FullLogName = _strdup(tempBuf); |
| } else { |
| FullLogName = DEFAULT_FULL_LOG_FILE; |
| } |
| } |
| |
| if (ResultsLogName == NULL) { |
| // The user didn't specify a results log filename, so create one. |
| |
| if (!FUserSpecifiedDirs || (DirList.first != NULL)) { |
| strcpy_s(tempBuf, REGRESS); |
| strcat_s(tempBuf, "\\logs\\"); |
| strcat_s(tempBuf, DEFAULT_RESULTS_LOG_FILE); |
| ResultsLogName = _strdup(tempBuf); |
| } else { |
| ResultsLogName = DEFAULT_RESULTS_LOG_FILE; |
| } |
| } |
| |
| // If the files already exist, then delete them. Otherwise, we'll simply |
| // keep appending to them, which is not usually desired. |
| |
| if (!FAppend) { |
| DeleteFileIfFound(LogName); |
| DeleteFileIfFound(FullLogName); |
| DeleteFileIfFound(ResultsLogName); |
| } |
| |
| // Now we should have a log name, so regenerate the log output objects |
| delete ThreadOut; // flush what we've got... |
| ThreadOut = new COutputBuffer(FQuiet ? (FILE*)NULL : stdout, FSyncImmediate ? false : true); |
| ThreadLog = new COutputBuffer(LogName, FSyncImmediate ? false : true); |
| ThreadFull = new COutputBuffer(FullLogName, FSyncImmediate ? false : true); |
| ThreadRes = new COutputBuffer(ResultsLogName, FSyncImmediate ? false : true); |
| |
| // Check for missing/conflicting items. |
| |
| if (!FRelativeLogPath |
| && IsRelativePath(LogName) |
| && (!FUserSpecifiedDirs || (DirList.first != NULL))) |
| { |
| Fatal("Log file '%s' specifies a relative path name\n" |
| "If this was intended, add -rellog to the command line", |
| LogName); |
| } |
| |
| if (!FRelativeLogPath |
| && IsRelativePath(FullLogName) |
| && (!FUserSpecifiedDirs || (DirList.first != NULL))) |
| { |
| Fatal("Log file '%s' specifies a relative path name\n" |
| "If this was intended, add -rellog to the command line", |
| FullLogName); |
| } |
| |
| if (!FUserSpecifiedDirs && (DirList.first != NULL)) |
| Fatal("Specify all directories or explicit directories, not both"); |
| |
| if (FUserSpecifiedFiles && (DirList.first != NULL)) |
| Fatal("Specify either a directory or files, not both"); |
| |
| if (ResumeDir && (DirList.first != NULL)) |
| Fatal("Specify explicit directories or a resume point, not both"); |
| |
| if (MatchDir && (DirList.first != NULL)) |
| Fatal("Specify explicit directories or a matching dir, not both"); |
| |
| if ((ResumeDir != NULL) && (MatchDir != NULL)) |
| Fatal("Specify resume point or a matching dir, not both"); |
| |
| if (FBaseline) { |
| if (Mode != RM_ASM) |
| Fatal("-base only supported for asm tests"); |
| |
| if (FDiff) { |
| if ((BaseCompiler == NULL) || (DiffCompiler == NULL)) |
| Fatal("Must specify compilers for both -base and -diff"); |
| } |
| } |
| else if (FDiff) { |
| if (Mode != RM_ASM) |
| Fatal("-diff only supported for asm tests"); |
| } |
| else if (Mode == RM_ASM) { |
| FDiff = TRUE; |
| } |
| |
| if (FNoMoveDiffsSwitch && (Mode == RM_EXE)) |
| Fatal("-nomovediffs and -exe are incompatible options"); |
| |
| if (FNoMoveDiffsSwitch && !FDiff) |
| Fatal("-nomovediffs requires -diff"); |
| |
| FBaseDiff = FBaseline && FDiff; |
| FMoveDiffs = !FNoMoveDiffsSwitch; |
| |
| ASSERTNR((Mode != RM_ASM) || FBaseline || FDiff); |
| ASSERTNR(FBaseline || !BaseCompiler); |
| ASSERTNR(FDiff || !DiffCompiler); |
| ASSERTNR(!FBaseDiff || BaseCompiler); |
| ASSERTNR(!FBaseDiff || DiffCompiler); |
| |
| // Now that we have parsed a valid command line, get the environment |
| // settings. |
| |
| GetEnvironment(); |
| |
| // Prepend target-specific nottags, if any. |
| |
| if (TargetInfo[TargetMachine].NotTags != NULL) |
| { |
| AddTagToTagsList(&TagsList, &TagsLast, _strdup(TargetInfo[TargetMachine].NotTags), false); |
| } |
| |
| // -nodelete can only be used with EXE when one set of test options is |
| // specified. |
| |
| if (FNoDelete) { |
| if (Mode != RM_EXE) |
| Fatal("-nodelete may only be used with -exe"); |
| |
| if (Mode == RM_EXE) |
| { |
| int numTestOptions = 0; |
| |
| const char * env = EXEC_TESTS_FLAGS; |
| while (env) { |
| env = strchr(env, ';'); |
| if (env) |
| ++env; |
| numTestOptions++; |
| } |
| |
| if (numTestOptions != 1) |
| Fatal("-nodelete only allows one option in EXEC_TESTS_FLAGS"); |
| } |
| } |
| |
| // Generate unspecified options. |
| |
| if (StatusPrefix == NULL) { |
| s = tempBuf + sprintf_s(tempBuf, "%s [%s", |
| Mode == RM_ASM |
| ? FBaseDiff |
| ? "Base/Diff" |
| : FBaseline |
| ? "Baselines" |
| : "Diffs" |
| : "Exec", |
| TargetInfo[TargetMachine].name); |
| if (RLMachine != TargetMachine) |
| s += sprintf_s(s, REMAININGARRAYLEN(tempBuf, s), ":%s", TargetInfo[RLMachine].name); |
| sprintf_s(s, REMAININGARRAYLEN(tempBuf, s), "]"); |
| } |
| else { |
| strcpy_s(tempBuf, StatusPrefix); |
| } |
| StatusPrefix = _strdup(tempBuf); |
| |
| if (StatusFormat == NULL) { |
| if (FDiff) |
| StatusFormat = "%d diffs, %f failures, %t/%T (%p%%)"; |
| else |
| StatusFormat = "%f failures, %t/%T (%p%%)"; |
| } |
| s = tempBuf + strlen(tempBuf); |
| *s++ = ' '; |
| strcpy_s(s, REMAININGARRAYLEN(tempBuf, s), StatusFormat); |
| StatusFormat = _strdup(tempBuf); |
| |
| if (DCFGfile == NULL) { |
| sprintf_s(tempBuf, "%s\\%s", REGRESS, |
| Mode == RM_ASM ? DEFAULT_ASM_DCFG : DEFAULT_EXE_DCFG); |
| DCFGfile = _strdup(tempBuf); |
| } |
| |
| if (CFGfile == NULL) { |
| CFGfile = Mode == RM_ASM |
| ? DEFAULT_ASM_CFG |
| : DEFAULT_EXE_CFG; |
| } |
| |
| if (CMDfile == NULL) { |
| CMDfile = (Mode == RM_ASM) |
| ? DEFAULT_ASM_CMD |
| : DEFAULT_EXE_CMD; |
| } |
| |
| // Warn if doing multiple dirs and not outputting directory name or |
| // summary info. |
| |
| if (FNoDirName && !FSummary && |
| (!FUserSpecifiedDirs || (DirList.first != DirList.last))) { |
| Warning("-nodirname without -summary will make for a hard to read log file"); |
| } |
| |
| if (FVerbose) { |
| printf("Perform %s regression, appending output to %s\n", |
| ModeNames[Mode], LogName); |
| } |
| |
| // If no directories specified, use current directory. |
| |
| if (FUserSpecifiedDirs && (DirList.first == NULL)) |
| AddCurrentDir(); |
| |
| // Set process priority to LOW if requested. |
| |
| if (FLow) { |
| SetPriorityClass(GetCurrentProcess(), IDLE_PRIORITY_CLASS); |
| } |
| |
| // Initialize parallel data |
| |
| if (NumberOfThreads == 0) { |
| SYSTEM_INFO SystemInfo; |
| GetSystemInfo(&SystemInfo); |
| NumberOfThreads = SystemInfo.dwNumberOfProcessors; |
| } |
| |
| if (FVerbose) { |
| printf("Using %d threads\n", NumberOfThreads); |
| } |
| |
| // Test synchronization switches. Only one -sync variant can be specified. |
| // If none is specified, we use the default: FSyncVariation for |
| // multi-threaded operation, and FSyncImmediate for single-threaded. |
| |
| if (FSyncImmediate + FSyncVariation + FSyncTest + FSyncDir == 0) { |
| if (NumberOfThreads == 1) |
| FSyncImmediate = TRUE; |
| else |
| FSyncVariation = TRUE; |
| } |
| |
| if (FSyncImmediate + FSyncVariation + FSyncTest + FSyncDir != TRUE) |
| Fatal("-sync options are mutually exclusive"); |
| |
| if (FRLFE) { |
| FSyncEnumDirs = TRUE; |
| |
| if (!FSyncVariation) { |
| printf("-rlfe requires -sync:variation (overriding your command line)\n"); |
| FSyncImmediate = FSyncTest = FSyncDir = FALSE; |
| FSyncVariation = TRUE; |
| } |
| } |
| } |
| |
| // Find a file in the file list with compileFlags corresponding to the passed |
| // in ones. |
| Test * |
| FindTest( |
| TestList * pTestList, |
| const char * testName, |
| BOOL fUserSpecified, |
| TestInfo * testInfo |
| |
| ) |
| { |
| Test * pTest; |
| Test * pMatch; |
| |
| pTest = pTestList->first; |
| pMatch = NULL; |
| |
| while (pTest) |
| { |
| if (_stricmp(pTest->name, testName) == 0) |
| { |
| pMatch = pTest; |
| |
| // If we have a new user test, or a match on the compileFlags and |
| // tags, break. |
| |
| if ((fUserSpecified && (pTest->files == NULL)) || |
| (!mystrcmp(pTest->defaultTestInfo.data[TIK_COMPILE_FLAGS], |
| testInfo->data[TIK_COMPILE_FLAGS]) && |
| !mystrcmp(pTest->defaultTestInfo.data[TIK_BASELINE], |
| testInfo->data[TIK_BASELINE]) && |
| !mystrcmp(pTest->defaultTestInfo.data[TIK_TAGS], |
| testInfo->data[TIK_TAGS]))) |
| { |
| break; |
| } |
| } |
| |
| pTest = pTest->next; |
| } |
| |
| // If we saw a match but didn't accept it and the user is specifying which |
| // tests to run and this is a Pogo test, explicitly add it. |
| |
| if (fUserSpecified |
| && (Mode == RM_EXE) |
| && (pTest == NULL) |
| && (pMatch != NULL) |
| && HasInfo(testInfo->data[TIK_TAGS], XML_DELIM, "Pogo")) |
| { |
| StringList * dupList = NULL; |
| StringList * p; |
| |
| for (p = pMatch->files; p != NULL; p = p->next) |
| { |
| dupList = AddToStringList(dupList, p->string); |
| } |
| |
| pTest = AddToTestList(pTestList, dupList); |
| } |
| |
| return pTest; |
| } |
| |
| BOOL |
| IsTimeoutStringValid(const char *strTimeout) |
| { |
| char *end; |
| _set_errno(0); |
| |
| uint32 secTimeout = strtoul(strTimeout, &end, 10); |
| |
| if (errno != 0 || *end != 0) |
| { |
| return FALSE; |
| } |
| |
| // Check to see if the value is too large and would cause overflow |
| |
| // Do the multiplication using 64-bit unsigned math. |
| unsigned __int64 millisecTimeout = 1000ui64 * static_cast<unsigned __int64>(secTimeout); |
| |
| // Does the result fit in 32-bits? |
| if (millisecTimeout >= (1ui64 << 32)) |
| { |
| return FALSE; |
| } |
| |
| return TRUE; |
| } |
| |
| BOOL |
| IsTimeoutRetriesStringValid(const char *strTimeoutRetries) |
| { |
| char *end; |
| _set_errno(0); |
| |
| uint32 numRetries = strtoul(strTimeoutRetries, &end, 10); |
| |
| if (errno != 0 || *end != 0) |
| { |
| return FALSE; |
| } |
| |
| // We will not be doing any math with this value, so no need to check for overflow. |
| // However, large values will possibly result in an unacceptably long retry loop, |
| // (especially with the default timeout being multiple minutes long), |
| // so limit the number of retries to some arbitrary max. |
| |
| if (numRetries > MAX_ALLOWED_TIMEOUT_RETRIES) |
| { |
| return FALSE; |
| } |
| |
| return TRUE; |
| } |
| |
| uint32 GetTimeoutValue(const char *strTimeout) |
| { |
| if (strTimeout == nullptr) |
| { |
| return 0; |
| } |
| |
| char *end = nullptr; |
| _set_errno(0); |
| uint32 secTimeout = strtoul(strTimeout, &end, 10); |
| return secTimeout; |
| } |
| |
| BOOL |
| GetTestInfoFromNode |
| ( |
| const char * fileName, |
| Xml::Node * node, |
| TestInfo * testInfo |
| ) |
| { |
| if (node == nullptr) |
| { |
| return TRUE; |
| } |
| |
| for (int i = 0; i < _TIK_COUNT; i++) |
| { |
| Xml::Node * childNode = node->GetChild(TestInfoKindName[i]); |
| if (childNode != nullptr) |
| { |
| testInfo->hasData[i] = TRUE; |
| if (i == TIK_ENV) |
| { |
| ASSERT(childNode->ChildList != nullptr); |
| testInfo->data[i] = (char*)childNode; |
| } |
| else |
| { |
| if (childNode->ChildList != nullptr) |
| { |
| CFG_ERROR_EX(fileName, node->LineNumber, |
| "Expected data, not child list\n", nullptr); |
| childNode->Dump(); |
| return FALSE; |
| } |
| |
| if (childNode->Data != nullptr && childNode->Data[0] != '\0') |
| { |
| char * data = childNode->Data; |
| if (i == TIK_SOURCE_PATH && IsRelativePath(childNode->Data)) |
| { |
| // Make sure sourcepath is not relative, if relative make it full path |
| data = MakeFullPath(fileName, data); |
| ASSERT(data != nullptr); |
| } |
| testInfo->data[i] = data; |
| } |
| else |
| { |
| testInfo->data[i] = nullptr; |
| } |
| |
| if (i == TIK_TIMEOUT) |
| { |
| // Validate the timeout string now to fail early so we don't run any tests when there is an error. |
| if (!IsTimeoutStringValid(testInfo->data[i])) |
| { |
| CFG_ERROR_EX(fileName, node->LineNumber, |
| "Invalid timeout specified. Cannot parse or too large.\n", nullptr); |
| childNode->Dump(); |
| return FALSE; |
| } |
| } |
| |
| if (i == TIK_TIMEOUT_RETRIES) |
| { |
| // Validate the timeoutRetries string now to fail early so we don't run any tests when there is an error. |
| if (!IsTimeoutRetriesStringValid(testInfo->data[i])) |
| { |
| CFG_ERROR_EX(fileName, node->LineNumber, |
| "Invalid number of timeout retries specified. Value must be numeric and <= %d.\n", MAX_ALLOWED_TIMEOUT_RETRIES); |
| childNode->Dump(); |
| return FALSE; |
| } |
| } |
| } |
| } |
| |
| if (i == TIK_TIMEOUT && TestTimeout != nullptr) |
| { |
| // Overriding the timeout value with the command line value (if the command line value is larger) |
| uint32 xmlTimeoutValue = GetTimeoutValue(testInfo->data[i]); |
| uint32 testTimeoutValue = GetTimeoutValue(TestTimeout); |
| if (xmlTimeoutValue < testTimeoutValue) |
| { |
| testInfo->data[i] = TestTimeout; |
| } |
| } |
| |
| if (i == TIK_TIMEOUT_RETRIES && TestTimeoutRetries != nullptr) |
| { |
| // Overriding the timeoutRetries value with the command line value (if the command line value is larger) |
| uint32 xmlTimeoutRetriesValue = GetTimeoutValue(testInfo->data[i]); |
| uint32 testTimeoutRetriesValue = GetTimeoutValue(TestTimeoutRetries); |
| if (xmlTimeoutRetriesValue < testTimeoutRetriesValue) |
| { |
| testInfo->data[i] = TestTimeoutRetries; |
| } |
| } |
| } |
| |
| return TRUE; |
| } |
| |
| BOOL |
| AddAsmVariants |
| ( |
| Test * pTest, |
| TestInfo * defaultInfo, |
| ConditionNodeList * cnl |
| ) |
| { |
| // Asm configurations are simple; we simply apply all the conditions to |
| // the variant info. |
| |
| // Create a variant and inherit the default info. |
| |
| TestInfo variantInfo; |
| memcpy(&variantInfo, defaultInfo, sizeof(TestInfo)); |
| |
| while (cnl != NULL) |
| { |
| ConditionNodeList * next = cnl->next; |
| |
| if (!GetTestInfoFromNode(CFGfile, cnl->node, &variantInfo)) |
| { |
| return FALSE; |
| } |
| |
| delete cnl; |
| |
| cnl = next; |
| } |
| |
| ASSERTNR(pTest->variants == NULL); |
| |
| TestVariant * pTestVariant = new TestVariant; |
| memcpy(&pTestVariant->testInfo, &variantInfo, sizeof(TestInfo)); |
| |
| pTest->variants = pTestVariant; |
| |
| return TRUE; |
| } |
| |
| BOOL |
| AddExeVariants |
| ( |
| Test * pTest, |
| TestInfo * defaultInfo, |
| ConditionNodeList * cnl |
| ) |
| { |
| // Exe configurations require more work since we have a list of optimization |
| // flags to deal with. |
| |
| TestVariant ** ppLastVariant = &pTest->variants; |
| |
| while (*ppLastVariant != NULL) |
| { |
| ppLastVariant = &(*ppLastVariant)->next; |
| } |
| |
| const char ** optFlagsArray; |
| |
| // Decide which list to use depending on the tag. |
| optFlagsArray = IsPogoTest(pTest) |
| ? PogoOptFlags |
| : OptFlags |
| ; |
| |
| // For each optimization flag set, see if the conditional applies. |
| for (int i = 0; optFlagsArray[i] != NULL; i++) |
| { |
| // Create a variant and inherit the default info. |
| TestInfo variantInfo; |
| memcpy(&variantInfo, defaultInfo, sizeof(TestInfo)); |
| |
| // Check the conditions. |
| ConditionNodeList * cn = cnl; |
| while (cn != NULL) |
| { |
| Xml::Node * cflagsNode = cn->node->GetChild("compile-flags"); |
| ASSERTNR(cflagsNode != NULL); |
| |
| // Check the optimization flags, EXTRA_CC_FLAGS, CL and _CL_ |
| // for matches. |
| |
| if (HasInfoList(optFlagsArray[i], OPT_DELIM, cflagsNode->Data, XML_DELIM, true) |
| || HasInfoList(EXTRA_CC_FLAGS, OPT_DELIM, cflagsNode->Data, XML_DELIM, true) |
| || HasInfoList(CL, OPT_DELIM, cflagsNode->Data, XML_DELIM, true) |
| || HasInfoList(_CL_, OPT_DELIM, cflagsNode->Data, XML_DELIM, true)) |
| { |
| const char * type = cn->node->GetAttributeValue("type"); |
| if (strcmp(type, "exclude") == 0) |
| { |
| break; |
| } |
| |
| Xml::Node * overrideNode = cn->node->GetChild("override"); |
| |
| if (!GetTestInfoFromNode(CFGfile, overrideNode, &variantInfo)) |
| { |
| return FALSE; |
| } |
| } |
| |
| cn = cn->next; |
| } |
| |
| // If we terminated early, exclude. |
| |
| if (cn != NULL) |
| { |
| continue; |
| } |
| |
| TestVariant * pTestVariant = new TestVariant; |
| memcpy(&pTestVariant->testInfo, &variantInfo, sizeof(TestInfo)); |
| pTestVariant->optFlags = optFlagsArray[i]; |
| |
| *ppLastVariant = pTestVariant; |
| ppLastVariant = &(*ppLastVariant)->next; |
| } |
| |
| return TRUE; |
| } |
| |
| // Parse one or more files from the file list. |
| BOOL |
| ParseFiles |
| ( |
| TestList * pTestList, |
| const char * testName, |
| RLMODE cfg, |
| TestInfo * defaultInfo, |
| ConditionNodeList * cnl |
| ) |
| { |
| Test * pTest; |
| |
| // Break into list of strings. |
| |
| StringList * fileList = |
| ParseStringList(defaultInfo->data[TIK_FILES], XML_DELIM); |
| defaultInfo->data[TIK_FILES] = NULL; |
| |
| if (fileList == NULL) |
| { |
| if (cnl != NULL) |
| { |
| CFG_ERROR_EX(CFGfile, cnl->node->LineNumber, |
| "No files specified\n", NULL); |
| cnl->node->Dump(); |
| } |
| return FALSE; |
| } |
| |
| if (cfg == RM_DIR) |
| { |
| if (fileList->next != NULL) |
| { |
| CFG_ERROR_EX(CFGfile, cnl->node->LineNumber, |
| "Specify exactly one file (dir) for directory nodes\n", NULL); |
| cnl->node->Dump(); |
| return FALSE; |
| } |
| } |
| |
| BOOL fUserSpecified = FALSE; |
| |
| if (((cfg == RM_DIR) && FUserSpecifiedDirs) |
| || ((cfg == RM_ASM) && FUserSpecifiedFiles) |
| || ((cfg == RM_EXE) && FUserSpecifiedFiles)) |
| { |
| fUserSpecified = TRUE; |
| } |
| |
| // For asm and dir, we loop over the entire file list processing each file |
| // individually. (Recall that dir has a single file.) For exe, we |
| // process all files at once. |
| |
| StringList * next = fileList; |
| |
| while (next != NULL) |
| { |
| fileList = next; |
| |
| if (cfg == RM_EXE) |
| { |
| next = NULL; |
| } |
| else |
| { |
| next = fileList->next; |
| fileList->next = NULL; |
| |
| testName = fileList->string; |
| } |
| |
| if (cfg == RM_DIR) |
| { |
| pTest = FindTest(pTestList, testName, fUserSpecified, defaultInfo); |
| } |
| else |
| { |
| // Test names are unique for files, so there's no need to do a lookup. |
| // If this uniqueness constraint changes, be aware that the RL setup |
| // time would be quite long when there are lots of files given that |
| // "FindTest" does a linear search. |
| pTest = AddToTestList(pTestList, fileList); |
| } |
| |
| // If the filename doesn't exist yet, we may want to create it. |
| |
| if (pTest == NULL) |
| { |
| // If the user has specified the files to use, skip this one. |
| |
| if (fUserSpecified) |
| { |
| continue; |
| } |
| |
| pTest = AddToTestList(pTestList, fileList); |
| } |
| else |
| { |
| if (pTest->files == NULL) |
| { |
| ASSERTNR(fUserSpecified); |
| |
| pTest->files = fileList; |
| } |
| } |
| |
| // While this may happen multiple times for the same test, it is safe |
| // to do so. |
| |
| memcpy(&pTest->defaultTestInfo, defaultInfo, sizeof(TestInfo)); |
| pTest->name = testName; |
| |
| if (cfg == RM_DIR) |
| { |
| if (cnl != NULL) |
| { |
| CFG_ERROR_EX(CFGfile, cnl->node->LineNumber, |
| "Directory node can only be conditional on target\n", NULL); |
| cnl->node->Dump(); |
| return FALSE; |
| } |
| } |
| else if (cfg == RM_ASM) |
| { |
| if (!AddAsmVariants(pTest, defaultInfo, cnl)) |
| { |
| return FALSE; |
| } |
| } |
| else |
| { |
| if (!AddExeVariants(pTest, defaultInfo, cnl)) |
| { |
| return FALSE; |
| } |
| } |
| } |
| |
| return TRUE; |
| } |
| |
| // mystrcmp is a frontend to strcmp that doesn't have a problem with NULL |
| // parameters. |
| int |
| mystrcmp( |
| const char *a, |
| const char *b |
| ) |
| { |
| if (a == b) |
| return 0; |
| |
| if (a == NULL) |
| return -1; |
| |
| if (b == NULL) |
| return 1; |
| |
| return strcmp(a,b); |
| } |
| |
| // mystrtok is similar to strtok_s, but takes two delimiter parameters: one for |
| // skipping entirely and one for skipping and terminating. |
| char * |
| mystrtok( |
| char *s, |
| const char *delim, |
| const char *term |
| ) |
| { |
| static char *str = NULL; |
| char *ret, *lastNonDelim; |
| |
| if (s) |
| str = s; |
| |
| if (str == NULL) |
| return NULL; |
| |
| // Skip leading delimiter (and term for first call). |
| while (*str && |
| (strchr(delim, *str) || |
| (s && strchr(term, *str)))) { |
| ++str; |
| } |
| |
| // Parse non-delimiter/non-term. |
| ret = lastNonDelim = str; |
| while (*str && !strchr(term, *str)) { |
| if (!strchr(delim, *str)) |
| lastNonDelim = str; |
| ++str; |
| } |
| |
| // EOS? |
| if (!*str) { |
| |
| // If we reached EOS because there are no more tokens, return NULL now. |
| if (ret == str) |
| return nullptr; |
| |
| // Otherwise, set up for NULL return on next call. |
| str = nullptr; |
| } |
| // Skip terminator. |
| else { |
| str++; |
| } |
| |
| // Trim trailing space. |
| if (!strchr(term, *lastNonDelim)) |
| ++lastNonDelim; |
| *lastNonDelim = '\0'; |
| |
| return ret; |
| } |
| |
| BOOL |
| AppliesToTarget |
| ( |
| const char * fileName, |
| int lineNumber, |
| char * targetList |
| ) |
| { |
| // Special case for target list of "*" |
| |
| if (strcmp(targetList, "*") == 0) |
| { |
| return TRUE; |
| } |
| |
| TARGET_MACHINES fileMach; |
| |
| // Parse the target list looking for our target machine. |
| // Using mystrtok here because strtok_s doesn't appear to initialize |
| // its static pointer to NULL. |
| |
| targetList = mystrtok(targetList, XML_DELIM, XML_DELIM); |
| while (targetList) |
| { |
| |
| #ifndef NODEBUG |
| if (FDebug) |
| { |
| printf("\t\tparsing %s\n", targetList); |
| } |
| #endif |
| |
| // Does the config specified target match our target machine? |
| |
| fileMach = ParseMachine(targetList); |
| if (fileMach == TM_UNKNOWN) |
| { |
| CFG_WARNING_EX(fileName, lineNumber, "Specified machine '%s' is unknown", targetList); |
| } |
| else if (MatchMachine(fileMach, RLMachine)) |
| { |
| return TRUE; |
| } |
| |
| targetList = mystrtok(NULL, XML_DELIM, XML_DELIM); |
| } |
| |
| return FALSE; |
| } |
| |
| BOOL |
| AppliesToTargetOS |
| ( |
| const char * fileName, |
| int lineNumber, |
| char * osList |
| ) |
| { |
| osList = mystrtok(osList, XML_DELIM, XML_DELIM); |
| while (osList) |
| { |
| TARGET_OS os = ParseOS(osList); |
| if (os == TO_UNKNOWN) |
| { |
| CFG_WARNING_EX(fileName, lineNumber, "Specified os '%s' is unknown", osList); |
| } |
| else if (MatchOS(os, TargetOS)) |
| { |
| return TRUE; |
| } |
| |
| osList = mystrtok(NULL, XML_DELIM, XML_DELIM); |
| } |
| |
| return FALSE; |
| } |
| |
| BOOL |
| DirectoryExcluded( |
| char *filename |
| ) |
| { |
| Test * pDir; |
| |
| for (pDir = ExcludeDirList.first; pDir; pDir = pDir->next) { |
| if (!_stricmp(pDir->name, filename)) { |
| return TRUE; |
| } |
| } |
| |
| return FALSE; |
| } |
| |
| // Parse the configuration file and modify the file list appropriately. |
| PROCESS_CONFIG_STATUS |
| ProcessConfig |
| ( |
| TestList * pTestList, |
| char *CfgFile, |
| RLMODE cfg |
| ) |
| { |
| static int unnamedCount = 0; |
| const char * szOrder; |
| |
| ASSERT(!IsRelativePath(CfgFile)); // must be full path |
| |
| Xml::Node * topNode = Xml::ReadFile(CfgFile); |
| |
| if (topNode == NULL) |
| { |
| return PCS_FILE_NOT_FOUND; |
| } |
| |
| Xml::Node * defaultNode; |
| Xml::Node * conditionNode; |
| Xml::Node * testNode; |
| Xml::Node * applyNode; |
| |
| ConditionNodeList * conditionNodeList = NULL; |
| ConditionNodeList * conditionNodeLast = NULL; |
| |
| // Parser doesn't return the XML declaration node, so topNode is the RL root node. |
| |
| ASSERTNR(topNode->Next == NULL); |
| |
| for (testNode = topNode->ChildList; |
| testNode != NULL; |
| testNode = testNode->Next) |
| { |
| if (_stricmp(testNode->Name, "#comment") == 0) |
| { |
| continue; |
| } |
| |
| char *testName = new char[20]; |
| sprintf_s(testName, 20, "UnnamedTest%d", unnamedCount); |
| ++unnamedCount; |
| |
| #ifndef NODEBUG |
| |
| if (FDebug) |
| { |
| printf("Processing node '%s'\n", testName); |
| } |
| |
| #endif |
| |
| // If we are processing the directory configuration, check for resume |
| // and match points. |
| |
| if (cfg == RM_DIR) |
| { |
| // If we have a resume point, check for matching prefix. |
| |
| if (ResumeDir) |
| { |
| if (_strnicmp(testName, ResumeDir, strlen(ResumeDir))) |
| { |
| continue; |
| } |
| |
| printf("Resuming from %s\n", testName); |
| ResumeDir = NULL; |
| } |
| |
| // If we have a matching directory, check for matching prefix. |
| |
| if (MatchDir) |
| { |
| if (_strnicmp(testName, MatchDir, strlen(MatchDir))) |
| { |
| continue; |
| } |
| } |
| |
| // If the directory has been explicitly excluded, then skip it |
| if ((cfg == RM_DIR) && FExcludeDirs) { |
| if (DirectoryExcluded(testName)) { |
| if (FVerbose) |
| Message("Excluding %s\n", testName); |
| continue; |
| } |
| } |
| } |
| |
| // We should sort the children in condition order since we can't trust |
| // that Xml nodes are read in the proper order. Since I know that |
| // xmlreader.cpp does the right thing, I'm going to enforce the |
| // ordering here rather than correct it. |
| |
| if (testNode->ChildList == NULL) |
| { |
| CFG_ERROR_EX(CfgFile, testNode->LineNumber, "test has no information", NULL); |
| testNode->Dump(); |
| goto Label_Error; |
| } |
| |
| defaultNode = testNode->ChildList; |
| if (strcmp(defaultNode->Name, "default") != 0) |
| { |
| CFG_ERROR_EX(CfgFile, defaultNode->LineNumber, "first node is not default", NULL); |
| testNode->Dump(); |
| goto Label_Error; |
| } |
| |
| int lastOrder = 0; |
| |
| for (applyNode = testNode->ChildList->Next; |
| applyNode != NULL; |
| applyNode = applyNode->Next) |
| { |
| if (strcmp(applyNode->Name, "default") == 0) |
| { |
| CFG_ERROR_EX(CfgFile, applyNode->LineNumber, "multiple default nodes", NULL); |
| testNode->Dump(); |
| goto Label_Error; |
| } |
| |
| if (strcmp(applyNode->Name, "condition") != 0) |
| { |
| CFG_ERROR_EX(CfgFile, applyNode->LineNumber, "unknown node", NULL); |
| applyNode->Dump(); |
| goto Label_Error; |
| } |
| |
| szOrder = applyNode->GetAttributeValue("order"); |
| if (szOrder == NULL) |
| { |
| CFG_ERROR_EX(CfgFile, applyNode->LineNumber, "condition node has no order", NULL); |
| applyNode->Dump(); |
| goto Label_Error; |
| } |
| |
| int order = atoi(szOrder); |
| if (order < 1) |
| { |
| CFG_ERROR_EX(CfgFile, applyNode->LineNumber, "illegal order value '%s'", szOrder); |
| applyNode->Dump(); |
| goto Label_Error; |
| } |
| |
| if (order <= lastOrder) |
| { |
| CFG_ERROR_EX(CfgFile, applyNode->LineNumber, "condition node is out-of-order", NULL); |
| applyNode->Dump(); |
| goto Label_Error; |
| } |
| |
| lastOrder = order; |
| |
| // Check for valid conditions. |
| |
| int numCond = 0; |
| BOOL fHasOverride = FALSE; |
| BOOL fHasNonTargetCond = FALSE; |
| |
| for (Xml::Node * condNode = applyNode->ChildList; |
| condNode != NULL; |
| condNode = condNode->Next) |
| { |
| if (strcmp(condNode->Name, "override") == 0) |
| { |
| if (!fHasOverride) |
| { |
| fHasOverride = TRUE; |
| } |
| else |
| { |
| CFG_ERROR_EX(CfgFile, applyNode->LineNumber, |
| "Too many override nodes", NULL); |
| } |
| } |
| else if (strcmp(condNode->Name, "#comment") == 0) |
| { |
| } |
| else if (strcmp(condNode->Name, "target") == 0 || strcmp(condNode->Name, "os") == 0) |
| { |
| if (fHasNonTargetCond) |
| { |
| CFG_ERROR_EX(CfgFile, applyNode->LineNumber, |
| "Target conditions must come before non-target conditions", |
| NULL); |
| applyNode->Dump(); |
| goto Label_Error; |
| } |
| |
| numCond++; |
| } |
| else if (strcmp(condNode->Name, "compile-flags") == 0) |
| { |
| fHasNonTargetCond = TRUE; |
| numCond++; |
| } |
| else |
| { |
| CFG_ERROR_EX(CfgFile, applyNode->LineNumber, |
| "Can't be conditional on %s", condNode->Name); |
| applyNode->Dump(); |
| goto Label_Error; |
| } |
| } |
| |
| if (numCond == 0) |
| { |
| CFG_ERROR_EX(CfgFile, applyNode->LineNumber, |
| "Missing condition", NULL); |
| } |
| } |
| |
| // Get the defaults from the default node. |
| |
| TestInfo testInfo; |
| memset(&testInfo, 0, sizeof(TestInfo)); |
| |
| if (!GetTestInfoFromNode(CfgFile, defaultNode, &testInfo)) |
| { |
| goto Label_Error; |
| } |
| |
| // Process the tags before checking for condition nodes. |
| |
| // Check for any directory tags only if we are in directory mode |
| if (cfg == RM_DIR) |
| { |
| if (ShouldIncludeTest(DirectoryTagsList, &testInfo) == false) |
| { |
| goto Label_Skip; |
| } |
| } |
| |
| if (ShouldIncludeTest(TagsList, &testInfo, /*mustMatchIncludeTag*/cfg != RM_DIR) == false) |
| { |
| goto Label_Skip; |
| } |
| |
| // Walk the condition nodes looking for applicability. |
| |
| conditionNodeList = NULL; |
| conditionNodeLast = NULL; |
| conditionNode = NULL; |
| |
| for (applyNode = testNode->ChildList->Next; |
| applyNode != NULL; |
| applyNode = applyNode->Next) |
| { |
| // Get the type of condition. |
| |
| const char * szType = applyNode->GetAttributeValue("type"); |
| if (strcmp(szType, "include") == 0) |
| { |
| } |
| else if (strcmp(szType, "exclude") == 0) |
| { |
| } |
| else |
| { |
| CFG_ERROR_EX(CfgFile, applyNode->LineNumber, "unknown condition type", NULL); |
| applyNode->Dump(); |
| goto Label_Error; |
| } |
| |
| // See if the condition applies to this target. |
| |
| Xml::Node * targetNode = applyNode->GetChild("target"); |
| Xml::Node * osNode = applyNode->GetChild("os"); |
| if (targetNode != NULL || osNode != NULL) |
| { |
| if (targetNode != NULL) |
| { |
| if (targetNode->ChildList != NULL) |
| { |
| CFG_ERROR_EX(CfgFile, applyNode->LineNumber, |
| "expected data, not child node list", NULL); |
| targetNode->Dump(); |
| goto Label_Error; |
| } |
| |
| char * targetList = targetNode->Data; |
| if ((targetList != NULL) |
| && !AppliesToTarget(CfgFile, targetNode->LineNumber, targetList)) |
| { |
| continue; |
| } |
| } |
| |
| if (osNode != NULL) |
| { |
| char * osList = osNode->Data; |
| if (osList != NULL |
| && !AppliesToTargetOS(CfgFile, osNode->LineNumber, osList)) |
| { |
| continue; |
| } |
| } |
| |
| // Apply any overrides. |
| |
| Xml::Node * overrideNode = applyNode->GetChild("override"); |
| |
| if (!GetTestInfoFromNode(CfgFile, overrideNode, &testInfo)) |
| { |
| goto Label_Error; |
| } |
| } |
| else if (cfg != RM_EXE) |
| { |
| CFG_ERROR_EX(CfgFile, applyNode->LineNumber, |
| "non-target condition node NYI for non-exe", NULL); |
| applyNode->Dump(); |
| goto Label_Error; |
| } |
| |
| // We have a potentially applicable condition. Add it to the list |
| // of condition nodes if it is not a simple target condition. |
| |
| if (targetNode == NULL && osNode == NULL) |
| { |
| ConditionNodeList * cnl = new ConditionNodeList; |
| |
| cnl->node = applyNode; |
| cnl->next = NULL; |
| |
| if (conditionNodeLast != NULL) |
| { |
| conditionNodeLast->next = cnl; |
| } |
| else |
| { |
| conditionNodeList = cnl; |
| } |
| |
| conditionNodeLast = cnl; |
| } |
| else |
| { |
| |
| // Save the last applicable target condition. |
| |
| conditionNode = applyNode; |
| } |
| } |
| |
| // If we have an exclude condition node, then skip this node. |
| |
| if ((conditionNode != NULL) |
| && (strcmp(conditionNode->GetAttributeValue("type"), "exclude") == 0)) |
| { |
| while (conditionNodeList != NULL) |
| { |
| ConditionNodeList * cnl = conditionNodeList->next; |
| delete conditionNodeList; |
| conditionNodeList = cnl; |
| } |
| |
| continue; |
| } |
| |
| // Stash the test info into the file list. |
| |
| if (!ParseFiles(pTestList, testName, cfg, &testInfo, conditionNodeList)) |
| { |
| // TODO: Figure out where it came from (default or condition.) |
| |
| CFG_ERROR_EX(CfgFile, defaultNode->LineNumber, |
| "bad file list", NULL); |
| |
| goto Label_Error; |
| } |
| |
| Label_Skip:; |
| } |
| |
| // If the user specified files, check for ones that didn't match. |
| |
| if (FUserSpecifiedFiles && (cfg != RM_DIR)) |
| { |
| for (Test * pTest = pTestList->first; pTest != NULL; pTest = pTest->next) |
| { |
| if ((pTest->variants == NULL) |
| && !IsPogoTest(pTest)) |
| { |
| Warning("'%s' not found", pTest->name); |
| } |
| } |
| } |
| |
| return PCS_OK; |
| |
| Label_Error: |
| |
| return PCS_ERROR; |
| } |
| |
| void |
| WriteTestLst |
| ( |
| TestList * pTestList, |
| char *cfgFile |
| ) |
| { |
| char tempBuf[BUFFER_SIZE], drive[MAX_PATH], dir[MAX_PATH]; |
| |
| _splitpath_s(cfgFile, drive, ARRAYLEN(drive), dir, ARRAYLEN(dir), NULL, 0, NULL, 0); |
| sprintf_s(tempBuf, "%s%s%s", drive, dir, DEFAULT_TESTLST_DCFG); |
| DeleteFileIfFound(tempBuf); |
| COutputBuffer *LstFilesOut = new COutputBuffer(tempBuf, FSyncImmediate ? false : true); |
| ASSERT(LstFilesOut); |
| for(Test * pDir = pTestList->first; pDir; pDir=pDir->next) { |
| LstFilesOut->Add("%s\t%s\n", pDir->defaultTestInfo.data[TIK_TAGS], pDir->name); |
| } |
| LstFilesOut->Flush(); |
| printf("\nCreated: %s\n", tempBuf); |
| delete LstFilesOut; |
| } |
| |
| // Read the directory configuration file. |
| void |
| BuildDirList( |
| void |
| ) |
| { |
| Test * pDir; |
| PROCESS_CONFIG_STATUS status; |
| char tempBuf[BUFFER_SIZE], drive[MAX_PATH], dir[MAX_PATH]; |
| |
| status = ProcessConfig(&DirList, DCFGfile, RM_DIR); |
| switch (status) { |
| case PCS_ERROR: |
| exit(1); |
| |
| case PCS_FILE_NOT_FOUND: |
| if (!FUserSpecifiedDirs) { |
| Warning("-dcfg file %s not found; " |
| "only regressing the current directory", |
| DCFGfile); |
| AddCurrentDir(); |
| } |
| break; |
| } |
| |
| if (ResumeDir) |
| Fatal("-resume directory '%s' not found", ResumeDir); |
| if (MatchDir && (DirList.first == NULL)) |
| Fatal("-match prefix '%s' did not match any directory", MatchDir); |
| |
| // Get the full path for each. Grab the directory of the DCFG file and |
| // append to that each subdirectory. |
| |
| _splitpath_s(DCFGfile, drive, ARRAYLEN(drive), dir, ARRAYLEN(dir), NULL, 0, NULL, 0); |
| |
| for (pDir = DirList.first; pDir; pDir = pDir->next) { |
| sprintf_s(tempBuf, "%s%s%s", drive, dir, pDir->name); |
| pDir->fullPath = _strdup(tempBuf); |
| } |
| |
| #ifndef NODEBUG |
| if (FDebug) { |
| printf("\nDirectories:\n"); |
| DumpTestList(&DirList); |
| } |
| #endif |
| |
| if (FGenLst) |
| WriteTestLst(&DirList, DCFGfile); |
| } |
| |
| // Update the status in the title. |
| void |
| UpdateTitleStatus() |
| { |
| char* s; |
| char tempBuf[BUFFER_SIZE]; |
| unsigned int i; |
| |
| // Don't bother if we're not going to display it. |
| if (!FStatus) |
| return; |
| |
| EnterCriticalSection(&csTitleBar); |
| |
| s = FormatString(StatusFormat); |
| strcpy_s(TitleStatus, s); |
| s = strchr(TitleStatus, '\0'); |
| |
| // start at 1: skip primary thread 0 (unless we decide to let it do real work) |
| for (i = 1; i <= NumberOfThreads; i++) { |
| ThreadInfo[i].GetCurrentTest(tempBuf); |
| size_t remainingCount = REMAININGARRAYLEN(TitleStatus, s); |
| size_t testLen = strnlen_s(tempBuf, BUFFER_SIZE); |
| // Accounting for formatting string and endofbuffer char. |
| if ((testLen + 3) >= remainingCount) { |
| break; |
| } |
| s += sprintf_s(s, remainingCount, "; %s", tempBuf); |
| } |
| |
| LeaveCriticalSection(&csTitleBar); |
| } |
| |
| void |
| WriteSummary( |
| const char *name, |
| BOOL fBaseline, |
| int tests, |
| int diffs, |
| int failures |
| ) |
| { |
| char tempBuf[BUFFER_SIZE]; |
| |
| if (FSummary || FVerbose) { |
| if (Mode == RM_ASM) { |
| if (fBaseline) { |
| sprintf_s(tempBuf, |
| "Summary: %s (baselines) has %d tests; %d failures", |
| name, tests, failures); |
| } |
| else { |
| sprintf_s(tempBuf, |
| "Summary: %s (diffs) has %d tests; %d diffs and %d failures", |
| name, tests, diffs, failures); |
| } |
| |
| LogOut("%s", tempBuf); |
| } |
| else { |
| sprintf_s(tempBuf, |
| "Summary: %s had %d tests; %d failures", |
| name, tests, failures); |
| LogOut("%s", tempBuf); |
| } |
| } |
| } |
| |
| void |
| __cdecl WriteResults(const char *fmt, ...) |
| { |
| va_list arg_ptr; |
| char tempBuf[BUFFER_SIZE]; |
| |
| ASSERT(ThreadRes != NULL); |
| |
| va_start(arg_ptr, fmt); |
| vsprintf_s(tempBuf, fmt, arg_ptr); |
| ASSERT(strlen(tempBuf) < BUFFER_SIZE); |
| ThreadRes->Add("%s\n", tempBuf); |
| } |
| |
| void |
| __cdecl WriteRunpl(const char *fmt, ...) |
| { |
| va_list arg_ptr; |
| char tempBuf[BUFFER_SIZE], buf[50]; |
| |
| ASSERT(ThreadFull != NULL); |
| |
| buf[0] = '\0'; |
| if (!FNoThreadId && ThreadId != 0 && NumberOfThreads > 1) { |
| sprintf_s(buf, "%d>", ThreadId); |
| } |
| va_start(arg_ptr, fmt); |
| vsprintf_s(tempBuf, fmt, arg_ptr); |
| ASSERT(strlen(tempBuf) < BUFFER_SIZE); |
| ThreadFull->Add("%s%s\n", buf, tempBuf); |
| } |
| |
| // Execute a single regression test. |
| void |
| PerformSingleRegression( |
| CDirectory* pDir, |
| Test * pTest |
| ) |
| { |
| char testNameBuf[BUFFER_SIZE]; |
| char tempBuf[BUFFER_SIZE]; |
| const char* ccFlags; |
| time_t start_test, elapsed_test; |
| RLFE_STATUS rlfeStatus; |
| |
| // Before executing, make sure directory is started. |
| pDir->TryBeginDirectory(); |
| |
| for (TestVariant * pTestVariant = pTest->variants; pTestVariant != NULL && !GStopDueToError; pTestVariant = pTestVariant->next) |
| { |
| ThreadInfo[ThreadId].SetCurrentTest(pDir->GetDirectoryName(), pTest->name, pDir->IsBaseline()); |
| |
| pTestVariant->testInfo.data[TIK_FILES] = pTest->name; |
| |
| if (FStatus) |
| UpdateTitleStatus(); |
| |
| if (strcmp(pTest->files->string, "dotest.cmd") == 0) { |
| sprintf_s(tempBuf, "(%s (", pTest->name); |
| } else |
| sprintf_s(tempBuf, "(%s (", pTest->files->string); |
| |
| if (pTestVariant->optFlags != NULL) |
| strcat_s(tempBuf, pTestVariant->optFlags); |
| |
| ccFlags = pTestVariant->testInfo.data[TIK_COMPILE_FLAGS]; |
| if(ccFlags != NULL) { |
| if(pTestVariant->optFlags != NULL) |
| strcat_s(tempBuf, " "); |
| |
| strcat_s(tempBuf, ccFlags); |
| } |
| |
| if(!pTestVariant->optFlags && !ccFlags) |
| tempBuf[0] = '\0'; |
| else |
| strcat_s(tempBuf,") "); |
| |
| strcat_s(tempBuf, Mode == RM_ASM ? "asm" : "exe"); |
| strcat_s(tempBuf, (Mode == RM_ASM) ? (pDir->IsBaseline() ? " base)" : " diffs)") : ")"); |
| sprintf_s(testNameBuf, "%s %s", pDir->GetDirectoryName(), tempBuf); |
| WriteRunpl("+++ %s +++", testNameBuf); |
| |
| start_test = time(NULL); |
| |
| // Are we doing regressions internally? |
| rlfeStatus = RLFES_PASSED; |
| |
| int result; |
| |
| // Assembly regressions? |
| if (Mode == RM_ASM) |
| { |
| result = RegrFile(pDir, pTest, pTestVariant); |
| } |
| else |
| { |
| result = ExecTest(pDir, pTest, pTestVariant); |
| } |
| |
| switch (result) |
| { |
| case -1: |
| pDir->IncFailures(); |
| if (Timing & TIME_TEST) |
| WriteResults("%s -- failed : exec time=%I64d", testNameBuf, time(NULL) - start_test); |
| else |
| WriteResults("%s -- failed", testNameBuf); |
| rlfeStatus = RLFES_FAILED; |
| break; |
| case 1: |
| pDir->IncDiffs(); |
| |
| if (Timing & TIME_TEST) |
| WriteResults("%s -- diffs : exec time=%I64d", testNameBuf, time(NULL) - start_test); |
| else |
| WriteResults("%s -- diffs", testNameBuf); |
| rlfeStatus = RLFES_DIFFED; |
| break; |
| case 0: |
| if (Timing & TIME_TEST) |
| WriteResults("%s -- passed : exec time=%I64d", testNameBuf, time(NULL) - start_test); |
| else |
| WriteResults("%s -- passed", testNameBuf); |
| break; |
| default: |
| ASSERTNR(UNREACHED); |
| } |
| |
| pDir->IncRun(); |
| |
| if (FRLFE) |
| RLFETestStatus(pDir); |
| |
| elapsed_test = (int)(time(NULL) - start_test); |
| |
| if (Timing & TIME_TEST) { |
| Message("RL: Test elapsed time (%s, %s): %02d:%02d", pDir->GetDirectoryName(), pTest->name, elapsed_test / 60, elapsed_test % 60); |
| } |
| |
| if (FSyncTest || (Mode == RM_ASM && FSyncVariation)) { |
| if (FRLFE && rlfeStatus) |
| RLFEAddLog(pDir, rlfeStatus, pTest->name, NULL, ThreadOut->GetText()); |
| |
| FlushOutput(); |
| } |
| } |
| |
| // Test finished, notify directory in case we are done with it. |
| pDir->UpdateState(); |
| } |
| |
| // Execute the regressions on a single directory. |
| void |
| RegressDirectory( |
| CDirectory* pDir |
| ) |
| { |
| TestList * pTestList; |
| Test * pTest; |
| char* path; |
| const char* dir; |
| |
| #ifndef NODEBUG |
| if (FDebug) |
| pDir->Dump(); |
| #endif |
| |
| path = pDir->GetDirectoryPath(); |
| dir = pDir->GetDirectoryName(); |
| pTestList = pDir->GetTestList(); |
| |
| // If we are doing baselines and diffs simultaneously and we are now |
| // performing the diffs, reinitialize the directory stats. |
| |
| if (FBaseDiff && !pDir->IsBaseline()) |
| pDir->InitStats(0); |
| |
| ASSERTNR((int32)pDir->NumVariationsRun == 0); |
| ASSERTNR((int32)pDir->NumDiffs == 0); |
| ASSERTNR((int32)pDir->NumFailures == 0); |
| |
| if (!FNoDirName) |
| WriteLog("*** %s ***", dir); |
| |
| if ((Mode == RM_ASM) && !CreateAsmDirs(path, pDir->IsBaseline())) { |
| pDir->IncFailures(pDir->NumVariations); |
| if (FRLFE) |
| RLFETestStatus(pDir); |
| return; |
| } |
| |
| if (FRLFE) { |
| RLFETestStatus(pDir); |
| RLFEThreadDir(pDir, BYTE(ThreadId)); |
| } |
| |
| if (FSyncTest || FSyncVariation) |
| FlushOutput(); |
| |
| // Start a new directory. |
| |
| if (((Mode == RM_ASM) && !RegrStartDir(path)) || |
| ((Mode == RM_EXE) && !RunStartDir(path))) { |
| pDir->IncRun(pDir->NumVariations); |
| pDir->IncFailures(pDir->NumVariations); |
| if (FRLFE) { |
| RLFETestStatus(pDir); |
| RLFEAddLog(pDir, RLFES_FAILED, "Start", |
| NULL, "Start dir command failed"); |
| } |
| return; |
| } |
| |
| // Loop over the test list, invoking the regression command once per test |
| // variation. We do not need to write any directory summary here, the CDirectory |
| // object itself will take care of that when status is updated during regression |
| // execution. |
| for (pTest = pTestList->first; pTest && !GStopDueToError; pTest = pTest->next) |
| { |
| PerformSingleRegression(pDir, pTest); |
| } |
| |
| // End the directory. |
| |
| if (Mode == RM_ASM) { |
| if (!RegrEndDir(path)) |
| return; |
| } |
| |
| if (FSyncDir) |
| FlushOutput(); |
| } |
| |
| DWORD __stdcall |
| StatusWorker( void *arg ) |
| { |
| char oldTitleStatus[BUFFER_SIZE]; |
| |
| ASSERT(FStatus); |
| |
| oldTitleStatus[0] = '\0'; |
| |
| while (!bThreadStop) { |
| EnterCriticalSection(&csTitleBar); |
| if (0 != strcmp(oldTitleStatus, TitleStatus)) { |
| // only if it changed do we set it... |
| SetConsoleTitle(TitleStatus); |
| strcpy_s(oldTitleStatus, TitleStatus); |
| } |
| LeaveCriticalSection(&csTitleBar); |
| Sleep(500); // don't change it too often |
| } |
| |
| arg = arg; |
| return 0; |
| } |
| |
| DWORD __stdcall |
| ThreadWorker( void *arg ) |
| { |
| ThreadId = (int)(intptr_t)(arg); // thread-local global variable for thread id |
| MaxThreads++; |
| ThreadOut = new COutputBuffer(FQuiet ? (FILE*)NULL : stdout, FSyncImmediate ? false : true); |
| ThreadLog = new COutputBuffer(LogName, FSyncImmediate ? false : true); |
| ThreadFull = new COutputBuffer(FullLogName, FSyncImmediate ? false : true); |
| ThreadRes = new COutputBuffer(ResultsLogName, FSyncImmediate ? false : true); |
| |
| CDirectory* pDir = NULL; |
| CDirectoryAndTestCase* pDirAndTest = NULL; |
| |
| if (FSyncEnumDirs) |
| WaitForSingleObject(heventDirsEnumerated, INFINITE); |
| |
| // Create thread-specific target VM command. |
| |
| if (TARGET_VM) { |
| size_t bufSize = strlen(TARGET_VM) + 1; |
| TargetVM = (char *)malloc(bufSize); |
| sprintf_s(TargetVM, bufSize, TARGET_VM, ThreadId); |
| } |
| else { |
| TargetVM = NULL; |
| } |
| |
| while(TRUE) { |
| if(bThreadStop || GStopDueToError) |
| break; |
| |
| // Poll for new stuff. If the thread is set to stop, then go away. |
| |
| // Use DirectoryQueue and schedule each directory on a single thread. |
| pDir = DirectoryTestQueue.GetNextItem_NoBlock(1000); |
| if(pDir == NULL) |
| break; |
| |
| while(TRUE) { |
| RegressDirectory(pDir); |
| |
| // We're done with this directory. If we are doing both |
| // baselines and diffs, and it was a baseline directory, |
| // changed to diffs and run again. |
| |
| if(FBaseDiff && pDir->IsBaseline()) |
| pDir->SetDiffFlag(); |
| else |
| break; |
| } |
| |
| delete pDir; |
| pDir = NULL; |
| } |
| |
| while(TRUE) { |
| if(bThreadStop || GStopDueToError) |
| break; |
| |
| // Poll for new stuff. If the thread is set to stop, then go away. |
| |
| // Use DirectoryAndTestCaseQueue and schedule each test on it's own thread. |
| pDirAndTest = DirectoryAndTestCaseQueue.GetNextItem_NoBlock(1000); |
| if(pDirAndTest == NULL) |
| break; |
| |
| PerformSingleRegression(pDirAndTest->_pDir, pDirAndTest->_pTest); |
| |
| delete pDirAndTest; |
| pDirAndTest = NULL; |
| } |
| |
| // Make sure we've flushed all the output, especially if we're stopping |
| // early because of a ctrl-c. |
| FlushOutput(); |
| delete ThreadOut; |
| delete ThreadLog; |
| delete ThreadFull; |
| delete ThreadRes; |
| ThreadOut = NULL; |
| ThreadLog = NULL; |
| ThreadFull = NULL; |
| ThreadRes = NULL; |
| |
| // Do a little per-thread cleanup |
| delete[] cmpbuf1; |
| delete[] cmpbuf2; |
| |
| // Mark us as done so the title bar shows that. |
| ThreadInfo[ThreadId].Done(); |
| |
| MaxThreads--; |
| return 0; |
| } |
| |
| int __cdecl |
| main(int argc, char *argv[]) |
| { |
| Test * pDir, * pTest; |
| CDirectory *pDirectory; |
| TestList TestList; |
| PROCESS_CONFIG_STATUS status; |
| DWORD dwThreadId; |
| HANDLE newThread; |
| HANDLE* workerThreads; // for waiting to finish |
| unsigned int i; |
| int num, dirNum; |
| char fullCfg[_MAX_PATH]; |
| DWORD err; |
| time_t start_total; |
| UINT elapsed_total; |
| char flagBuff[64]; |
| |
| start_total = time(NULL); |
| |
| SetConsoleCtrlHandler(NT_handling_function, TRUE); |
| |
| InitializeCriticalSection(&csTitleBar); |
| InitializeCriticalSection(&csStdio); |
| InitializeCriticalSection(&csCurrentDirectory); |
| |
| // Set up output for primary thread. |
| ThreadOut = new COutputBuffer(stdout, true); |
| |
| // WARNING: while parsing flags, these are aliased to ThreadOut |
| ThreadLog = ThreadOut; // no LogName yet |
| ThreadFull = ThreadOut; |
| ThreadRes = ThreadOut; |
| |
| InitTestList(&TestList); |
| |
| ASSERTNR(TestList.first == NULL); |
| ASSERTNR(DirList.first == NULL); |
| |
| // Get the path for temporary files |
| |
| err = GetTempPathA(MAX_PATH, TempPath); |
| if (err == 0) { |
| strcpy_s(TempPath, "\\"); |
| } else { |
| err = GetFileAttributesA(TempPath); |
| if (err == 0xffffffff || !(err & FILE_ATTRIBUTE_DIRECTORY)) { |
| strcpy_s(TempPath, "\\"); |
| } |
| } |
| |
| ParseCommandLine(argc, argv, &TestList); |
| |
| if (FSyncEnumDirs) { |
| // The event is manual reset, so once triggered all threads |
| // will wake up. It is unsignaled by default; we signal it |
| // after all dirs/files have been enumerated. |
| heventDirsEnumerated = CreateEvent(NULL, TRUE, FALSE, NULL); |
| } |
| |
| // If we're using internal code for regressions, initialize. |
| |
| Mode == RM_ASM ? RegrInit() : RunInit(); |
| |
| if (FRLFE) { |
| if (!RLFEConnect(StatusPrefix)) |
| Fatal("Couldn't connect to RLFE"); |
| } |
| |
| atexit(NormalCleanUp); |
| |
| BuildDirList(); |
| |
| if (FVerbose) |
| PrintDirs(); |
| |
| if (DirList.first == nullptr) |
| { |
| Message("No directories specified for regression - exiting.\n"); |
| FlushOutput(); |
| return 0; |
| } |
| |
| NumDiffsTotal[RLS_TOTAL] = NumFailuresTotal[RLS_TOTAL] = 0; |
| |
| if (FRLFE) { |
| dirNum = 0; |
| for (pDir = DirList.first; pDir; pDir = pDir->next) |
| dirNum++; |
| |
| RLFEInit((BYTE)NumberOfThreads, dirNum); |
| |
| if (Mode == RM_ASM) { |
| if (FBaseline) |
| RLFEAddRoot(RLS_BASELINES, 0); |
| if (FDiff) |
| RLFEAddRoot(RLS_DIFFS, 0); |
| } |
| else { |
| RLFEAddRoot(RLS_EXE, 0); |
| } |
| } |
| |
| /* |
| * Create an array of info about each thread, accessed by the status |
| * update thread. |
| */ |
| ThreadInfo = new CThreadInfo[NumberOfThreads + 1]; |
| |
| /* |
| * Create the thread pool to do the tests |
| */ |
| workerThreads = new HANDLE[NumberOfThreads]; // don't store primary thread |
| for( i=1; i <= NumberOfThreads; i++ ) { |
| workerThreads[i-1] = newThread = CreateThread( (LPSECURITY_ATTRIBUTES)NULL, 0, |
| ThreadWorker, (LPVOID *)i, 0, &dwThreadId ); |
| if (newThread == INVALID_HANDLE_VALUE) |
| Fatal("Couldn't create thread %d", i); |
| } |
| |
| /* |
| * Create the 'update status' thread only for FStatus |
| */ |
| if (FStatus) { |
| newThread = CreateThread( (LPSECURITY_ATTRIBUTES)NULL, 0, |
| StatusWorker,(LPVOID *)0,0,&dwThreadId ); |
| if (newThread == INVALID_HANDLE_VALUE) |
| Fatal("Couldn't create status thread"); |
| CloseHandle(newThread); |
| } |
| |
| // flush all output for the primary thread |
| FlushOutput(); |
| |
| dirNum = 0; |
| for (pDir = DirList.first; pDir; pDir = pDir->next) { |
| |
| // Number the directories. |
| |
| pDir->num = ++dirNum; |
| |
| // Build a file list. |
| |
| sprintf_s(fullCfg, "%s\\%s", pDir->fullPath, CFGfile); |
| status = ProcessConfig(&TestList, fullCfg, Mode); |
| |
| if (status == PCS_ERROR) |
| { |
| exit(1); |
| } |
| else |
| { |
| |
| #ifndef NODEBUG |
| if (FDebug) { |
| printf("\nAfter processing config:\n"); |
| DumpTestList(&TestList); |
| } |
| #endif |
| num = 0; |
| for (pTest = TestList.first; pTest; pTest = pTest->next) |
| { |
| for (TestVariant * variant = pTest->variants; |
| variant != NULL; |
| variant = variant->next) |
| { |
| num++; |
| } |
| |
| if (!FNoWarn && IsPogoTest(pTest) && (pTest->variants == NULL)) |
| { |
| LogOut("Warning: %s::%s is a Pogo test, " |
| "but no Pogo-specific options are set", |
| pDir->name, pTest->name); |
| } |
| } |
| |
| if (FGenLst) { |
| // if generate runall's env.lst config, print the env lst here for the dir |
| WriteEnvLst(pDir, &TestList); |
| goto nextdir; |
| } |
| |
| if (num) |
| { |
| // Queue the tests |
| |
| pDirectory = new CDirectory(pDir, TestList); |
| pDirectory->InitStats(num); |
| if (!FBaseline) |
| pDirectory->SetDiffFlag(); |
| |
| if (FRLFE) { |
| if (Mode == RM_EXE) { |
| RLFEAddTest(RLS_EXE, pDirectory); |
| } |
| else { |
| if (FBaseline) |
| RLFEAddTest(RLS_BASELINES, pDirectory); |
| if (FDiff) |
| RLFEAddTest(RLS_DIFFS, pDirectory); |
| } |
| } |
| |
| strcpy_s(flagBuff, "sequential"); |
| BOOL isSerialDirectory = HasInfoList(pDir->defaultTestInfo.data[TIK_TAGS], XML_DELIM, flagBuff, XML_DELIM, false); |
| |
| if(isSerialDirectory) |
| { |
| DirectoryTestQueue.Append(pDirectory); |
| } |
| else |
| { |
| // Always put the directory into the DirectoryQueue. |
| // We will use this queue to clean up the CDirectory objects if FSingleThreadPerDir is |
| // turned off. |
| DirectoryQueue.Append(pDirectory); |
| |
| // Keep track of tests as a flat list so we can split tests |
| // in the same directory across multiple threads. |
| |
| for (pTest = TestList.first; pTest; pTest = pTest->next) |
| { |
| CDirectoryAndTestCase* pDirAndTest = new CDirectoryAndTestCase(pDirectory, pTest); |
| |
| DirectoryAndTestCaseQueue.Append(pDirAndTest); |
| } |
| } |
| } |
| } |
| |
| nextdir: |
| InitTestList(&TestList); // get ready for next directory |
| ASSERTNR(TestList.first == NULL); |
| } |
| |
| if (FGenLst) { |
| return 0; |
| } |
| // We can now set the totals. |
| |
| if (FRLFE) { |
| if (Mode == RM_EXE) { |
| RLFEAddRoot(RLS_EXE, (int32)NumVariationsTotal[RLS_EXE]); |
| } |
| else { |
| if (FBaseline) |
| RLFEAddRoot(RLS_BASELINES, (int32)NumVariationsTotal[RLS_BASELINES]); |
| if (FDiff) |
| RLFEAddRoot(RLS_DIFFS, (int32)NumVariationsTotal[RLS_DIFFS]); |
| } |
| |
| RLFEAddRoot(RLS_TOTAL, (int32)NumVariationsTotal[RLS_TOTAL]); |
| } |
| |
| DirectoryQueue.AdjustWaitForThreadCount(); |
| DirectoryAndTestCaseQueue.AdjustWaitForThreadCount(); |
| |
| #ifndef NODEBUG |
| if (FDebug) { |
| DirectoryQueue.Dump(); |
| } |
| #endif |
| |
| if (FSyncEnumDirs) |
| SetEvent(heventDirsEnumerated); |
| |
| // Wait for all the threads to die |
| WaitForMultipleObjectsEx(NumberOfThreads, workerThreads, TRUE, INFINITE, false); |
| for (i = 0; i < NumberOfThreads; i++) |
| CloseHandle(workerThreads[i]); |
| bThreadStop = TRUE; // make status thread go away |
| |
| if (FBaseDiff) { |
| WriteSummary(REGRESS, TRUE, |
| NumVariationsTotal[RLS_BASELINES], |
| NumDiffsTotal[RLS_BASELINES], |
| NumFailuresTotal[RLS_BASELINES]); |
| WriteSummary(REGRESS, FALSE, |
| NumVariationsTotal[RLS_DIFFS], |
| NumDiffsTotal[RLS_DIFFS], |
| NumFailuresTotal[RLS_DIFFS]); |
| } |
| else { |
| WriteSummary(REGRESS, FBaseline, |
| NumVariationsTotal[RLS_TOTAL], |
| NumDiffsTotal[RLS_TOTAL], |
| NumFailuresTotal[RLS_TOTAL]); |
| } |
| |
| elapsed_total = (int)(time(NULL) - start_total); |
| if (Timing != TIME_NOTHING) { |
| Message("RL: Total elapsed time: %02d:%02d", |
| elapsed_total / 60, elapsed_total % 60); |
| } |
| |
| if (((int)CntDeleteFileFailures > 0) || ((int)CntDeleteFileFatals > 0)) { |
| Message("RL: Total DeleteFile failures: %d, DeleteFile abandoned: %d", |
| (int)CntDeleteFileFailures, |
| (int)CntDeleteFileFatals); |
| } |
| |
| // flush all output for the primary thread |
| FlushOutput(); |
| |
| // The return code from RL is 0 for complete success, 1 for any failure. |
| // Note that diffs don't count as failures. |
| |
| return ((int32)NumFailuresTotal[RLS_TOTAL] == 0L) ? 0 : 1; |
| } |