[tools 3/3] testbot/TestLauncher: Fix the detection of test executable dependencies.
Francois Gouget
fgouget at codeweavers.com
Mon Mar 15 13:15:39 CDT 2021
Detect the critical error dialog that pops up when the test executable
depends on a missing dll or entry point.
This improves the diagnostic messages and avoids long timeouts.
Wine-Bug: https://bugs.winehq.org/show_bug.cgi?id=31609
Wine-Bug: https://bugs.winehq.org/show_bug.cgi?id=32216
Signed-off-by: Francois Gouget <fgouget at codeweavers.com>
---
testbot/src/TestLauncher/TestLauncher.c | 341 +++++++-----------------
1 file changed, 92 insertions(+), 249 deletions(-)
diff --git a/testbot/src/TestLauncher/TestLauncher.c b/testbot/src/TestLauncher/TestLauncher.c
index bfc9f0fa4..7b58c9ad1 100644
--- a/testbot/src/TestLauncher/TestLauncher.c
+++ b/testbot/src/TestLauncher/TestLauncher.c
@@ -26,271 +26,91 @@
#define ARRAY_SIZE(x) (sizeof(x) / sizeof((x)[0]))
-static unsigned Failures = 0;
-static unsigned Skips = 0;
+/*
+ * Missing dll and entry point detection.
+ */
-#define ReportError (_SetErrorLocation(__FILE__, __LINE__), 0) ? (void)0 : _ReportError
+const char *Subtest;
+static unsigned Skips = 0;
-static const char *LocationFile;
-static unsigned LocationLine;
-static void _SetErrorLocation(const char* file, int line)
+BOOL CALLBACK DumpCriticalErrorDescription(HWND hWnd, LPARAM lParam)
{
- LocationFile = file;
- LocationLine = line;
-}
+ char Buffer[512];
+ int Count;
-#ifdef __GNUC__
-static void _ReportError(const char *Format, ...) __attribute__((format (printf,1,2) ));
-#endif
+ /* No nesting is expected */
+ if (GetParent(hWnd) != (HWND)lParam) return TRUE;
-static void _ReportError(const char *Format, ...)
-{
- va_list ArgList;
+ Count = GetClassNameA(hWnd, Buffer, ARRAY_SIZE(Buffer));
+ if (!Count || strcmp(Buffer, "Static")) return TRUE;
+
+ Count = GetWindowTextA(hWnd, Buffer, ARRAY_SIZE(Buffer));
+ if (!Count) return TRUE;
+ printf("| %s\n", Buffer);
- va_start(ArgList, Format);
- printf("%s:%d: Test failed: ", LocationFile, LocationLine);
- vprintf(Format, ArgList);
- Failures++;
+ return TRUE;
}
-static DWORD ConvertRVAToDiskOffset(DWORD RVA, DWORD SectionHeaderCount, const IMAGE_SECTION_HEADER *SectionHeaders)
+BOOL CALLBACK DetectCriticalErrorDialog(HWND TopWnd, LPARAM lParam)
{
- const IMAGE_SECTION_HEADER *SectionHeader;
-
- for (SectionHeader = SectionHeaders; SectionHeader < SectionHeaders + SectionHeaderCount; SectionHeader++)
+ const char* TestFileName = (char*)lParam;
+ int Count, TestFileLen;
+ char Buffer[512];
+ const char* Reason;
+
+ /* The window pid does not match the CreateProcess() one so it cannot be
+ * used for filtering. But do filter on top-level dialogs.
+ */
+ if (GetParent(TopWnd) || GetWindow(TopWnd, GW_OWNER)) return TRUE;
+
+ Count = GetClassNameA(TopWnd, Buffer, ARRAY_SIZE(Buffer));
+ if (!Count || strcmp(Buffer, "#32770")) return TRUE;
+
+ /* The dialog title always starts with the executable name */
+ TestFileLen = strlen(TestFileName);
+ Count = GetWindowTextA(TopWnd, Buffer, ARRAY_SIZE(Buffer));
+ if (!Count || strncmp(Buffer, TestFileName, TestFileLen)) return TRUE;
+
+ /* Followed by a reason */
+ Reason = NULL;
+ if (strcmp(Buffer + TestFileLen, " - System Error") == 0)
+ Reason = "missing dll";
+ else if (strcmp(Buffer + TestFileLen, " - Entry Point Not Found") == 0)
+ Reason = "missing entry point";
+ else if (strcmp(Buffer + TestFileLen, " - Ordinal Not Found") == 0)
+ Reason = "missing ordinal";
+ else if (strncmp(Buffer + TestFileLen, " - ", 3) == 0)
+ /* Old Windows version used to translate the dialog */
+ Reason = "unrecognized critical error";
+
+ if (Reason)
{
- if (SectionHeader->VirtualAddress <= RVA && RVA < SectionHeader->VirtualAddress + SectionHeader->Misc.VirtualSize)
- return SectionHeader->PointerToRawData + (RVA - SectionHeader->VirtualAddress);
+ Skips++;
+ printf("%s.c:0: Tests skipped: %s (details below)\n", Subtest, Reason);
+ printf("| %s\n", Buffer);
+ EnumChildWindows(TopWnd, DumpCriticalErrorDescription, (LPARAM)TopWnd);
+ /* Leave the dialog open so it appears on screenshots */
+ return FALSE;
}
- return 0;
+ return TRUE;
}
-static BOOL DllPresent(const char *DllName)
-{
- HMODULE DllModule;
-
- DllModule = LoadLibraryExA(DllName, NULL, LOAD_LIBRARY_AS_DATAFILE);
- if (DllModule != NULL)
- FreeLibrary(DllModule);
-
- return DllModule != NULL;
-}
/*
- * When launching an app that implicitly links against a DLL that's not present, a message box
- * will be shown "Unable to locate component". The child process waits until this message is
- * dismissed.
- * This can happen when running tests for a system DLL on a Windows version which does not include
- * that DLL. It messes up our testing because the child just hangs around until the timeout expires,
- * taking up testing time and generating a timeout error.
- * Since the message is produced by the child process before any application code is run, we can't
- * suppress it using SetErrorMode() or ProcessDefaultHardErrorMode. It is possible to suppress using
- * the registry value ErrorMode in HKEY_LOCAL_MACHINE\CurrentControlSet\Control\Windows but that has
- * a global effect.
- * So instead we just dive into the executable's import table, determine which modules are being
- * imported and check if they are present.
+ * Command line parsing and test running.
*/
-static BOOL AllImportedDllsPresent(const char *TestExeName, const char *Subtest)
-{
- HANDLE TestExe;
- IMAGE_DOS_HEADER DosHeader;
- IMAGE_NT_HEADERS NTHeaders;
- const IMAGE_DATA_DIRECTORY *DataDirectoryImportTable;
- IMAGE_SECTION_HEADER *SectionHeaders;
- IMAGE_IMPORT_DESCRIPTOR *ImportDescriptors;
- const IMAGE_IMPORT_DESCRIPTOR *ImportDescriptor;
- DWORD NR;
- DWORD NewPos;
- DWORD FileOffset;
- char ModuleName[MAX_PATH];
- BOOL Found;
- BOOL AllPresent;
-
- TestExe = CreateFileA(TestExeName, GENERIC_READ, FILE_SHARE_READ, NULL, OPEN_EXISTING, 0, NULL);
- if (TestExe == INVALID_HANDLE_VALUE)
- {
- ReportError("Can't open test executable %s, error %lu\n", TestExeName, GetLastError());
- return FALSE;
- }
-
- if (! ReadFile(TestExe, &DosHeader, sizeof(IMAGE_DOS_HEADER), &NR, NULL) || NR != sizeof(IMAGE_DOS_HEADER))
- {
- CloseHandle(TestExe);
- ReportError("Can't read DOS header from %s, error %lu\n", TestExeName, GetLastError());
- return FALSE;
- }
- if (DosHeader.e_magic != IMAGE_DOS_SIGNATURE)
- {
- CloseHandle(TestExe);
- ReportError("%s does not start with a valid DOS header\n", TestExeName);
- return FALSE;
- }
-
- NewPos = SetFilePointer(TestExe, DosHeader.e_lfanew, NULL, FILE_BEGIN);
- if (NewPos == INVALID_SET_FILE_POINTER && GetLastError() != ERROR_SUCCESS)
- {
- CloseHandle(TestExe);
- ReportError("Can't move to NT headers in %s, error %lu\n", TestExeName, GetLastError());
- return FALSE;
- }
-
- if (! ReadFile(TestExe, &NTHeaders, sizeof(IMAGE_NT_HEADERS), &NR, NULL) || NR != sizeof(IMAGE_NT_HEADERS))
- {
- CloseHandle(TestExe);
- ReportError("Can't read NT headers from %s, error %lu\n", TestExeName, GetLastError());
- return FALSE;
- }
- if (NTHeaders.Signature != IMAGE_NT_SIGNATURE || NTHeaders.OptionalHeader.Magic != IMAGE_NT_OPTIONAL_HDR_MAGIC)
- {
- CloseHandle(TestExe);
- ReportError("%s does not contain valid NT headers expected 0x%08x/0x%04x found 0x%08lx/0x%04x\n", TestExeName,
- IMAGE_NT_SIGNATURE, IMAGE_NT_OPTIONAL_HDR_MAGIC, NTHeaders.Signature, NTHeaders.OptionalHeader.Magic);
- return FALSE;
- }
- DataDirectoryImportTable = NTHeaders.OptionalHeader.DataDirectory + IMAGE_DIRECTORY_ENTRY_IMPORT;
- if (DataDirectoryImportTable->VirtualAddress == 0 || DataDirectoryImportTable->Size == 0)
- {
- CloseHandle(TestExe);
- ReportError("%s does not contain valid a valid import table (RVA 0x%lx size 0x%lx)\n", TestExeName,
- DataDirectoryImportTable->VirtualAddress, DataDirectoryImportTable->Size);
- return FALSE;
- }
-
- SectionHeaders = (IMAGE_SECTION_HEADER*) malloc(NTHeaders.FileHeader.NumberOfSections * sizeof(IMAGE_SECTION_HEADER));
- if (SectionHeaders == NULL)
- {
- CloseHandle(TestExe);
- ReportError("Unable to allocate memory for section headers\n");
- return FALSE;
- }
- if (! ReadFile(TestExe, SectionHeaders, NTHeaders.FileHeader.NumberOfSections * sizeof(IMAGE_SECTION_HEADER), &NR, NULL) ||
- NR != NTHeaders.FileHeader.NumberOfSections * sizeof(IMAGE_SECTION_HEADER))
- {
- free(SectionHeaders);
- CloseHandle(TestExe);
- ReportError("Can't read section headers from %s, error %lu\n", TestExeName, GetLastError());
- return FALSE;
- }
-
- ImportDescriptors = (IMAGE_IMPORT_DESCRIPTOR*) malloc(DataDirectoryImportTable->Size);
- if (ImportDescriptors == NULL)
- {
- free(SectionHeaders);
- CloseHandle(TestExe);
- ReportError("Unable to allocate memory for import directory\n");
- return FALSE;
- }
- FileOffset = ConvertRVAToDiskOffset(DataDirectoryImportTable->VirtualAddress,
- NTHeaders.FileHeader.NumberOfSections, SectionHeaders);
- if (FileOffset == 0)
- {
- free(ImportDescriptors);
- free(SectionHeaders);
- CloseHandle(TestExe);
- ReportError("Can't locate import directory in %s\n", TestExeName);
- return FALSE;
- }
- NewPos = SetFilePointer(TestExe, FileOffset, NULL, FILE_BEGIN);
- if (NewPos == INVALID_SET_FILE_POINTER && GetLastError() != ERROR_SUCCESS)
- {
- free(ImportDescriptors);
- free(SectionHeaders);
- CloseHandle(TestExe);
- ReportError("Can't move to import directory in %s, error %lu\n", TestExeName, GetLastError());
- return FALSE;
- }
-
- if (! ReadFile(TestExe, ImportDescriptors, DataDirectoryImportTable->Size, &NR, NULL) || NR != DataDirectoryImportTable->Size)
- {
- free(ImportDescriptors);
- free(SectionHeaders);
- CloseHandle(TestExe);
- ReportError("Can't read import directory from %s, error %lu\n", TestExeName, GetLastError());
- return FALSE;
- }
-
- AllPresent = TRUE;
- for (ImportDescriptor = ImportDescriptors;
- (char *) ImportDescriptor < (char *) ImportDescriptors + DataDirectoryImportTable->Size && ImportDescriptor->Name != 0;
- ImportDescriptor++)
- {
- FileOffset = ConvertRVAToDiskOffset(ImportDescriptor->Name, NTHeaders.FileHeader.NumberOfSections, SectionHeaders);
- if (FileOffset == 0)
- {
- free(ImportDescriptors);
- free(SectionHeaders);
- CloseHandle(TestExe);
- ReportError("Can't locate import module name in %s\n", TestExeName);
- return FALSE;
- }
- NewPos = SetFilePointer(TestExe, FileOffset, NULL, FILE_BEGIN);
- if (NewPos == INVALID_SET_FILE_POINTER && GetLastError() != ERROR_SUCCESS)
- {
- free(ImportDescriptors);
- free(SectionHeaders);
- CloseHandle(TestExe);
- ReportError("Can't move to import module name in %s, error %lu\n", TestExeName, GetLastError());
- return FALSE;
- }
-
- if (! ReadFile(TestExe, ModuleName, sizeof(ModuleName), &NR, NULL))
- {
- free(ImportDescriptors);
- free(SectionHeaders);
- CloseHandle(TestExe);
- ReportError("Can't read import directory from %s, error %lu\n", TestExeName, GetLastError());
- return FALSE;
- }
-
- Found = FALSE;
- for (NewPos = 0; ! Found && NewPos < NR; NewPos++)
- Found = ModuleName[NewPos] == '\0';
- if (! Found)
- {
- free(ImportDescriptors);
- free(SectionHeaders);
- CloseHandle(TestExe);
- ReportError("Import module name is too long in %s\n", TestExeName);
- return FALSE;
- }
-
- if (! DllPresent(ModuleName))
- {
- if (AllPresent)
- {
- printf("%s.c:0: Tests skipped: required DLL %s", Subtest, ModuleName);
- AllPresent = FALSE;
- }
- else
- printf(", %s", ModuleName);
- }
- }
-
- free(ImportDescriptors);
- free(SectionHeaders);
- CloseHandle(TestExe);
-
- if (! AllPresent)
- {
- printf(" is missing\n");
- Skips++;
- }
-
- return AllPresent;
-}
int main(int argc, char *argv[])
{
int Arg;
- DWORD Start, TimeOut;
+ DWORD Start, TimeOut, WaitTimeOut;
BOOL UsageError;
char TestExeFullName[MAX_PATH];
char *TestExeFileName;
const char *Suffix;
char TestName[MAX_PATH];
- const char *Subtest;
int TestArg;
char *CommandLine;
int CommandLen;
@@ -378,15 +198,6 @@ int main(int argc, char *argv[])
Start = GetTickCount();
printf("%s:%s start - -\n", TestName, Subtest);
-
- if (! AllImportedDllsPresent(TestExeFullName, Subtest))
- {
- printf("0000:%s: %u tests executed (0 marked as todo, %u failures), %u skipped.\n", Subtest, Failures, Failures, Skips);
- printf("%s:%s:0000 done (%u) in %lds\n", TestName, Subtest, Failures,
- (GetTickCount() - Start) / 1000);
- exit(0);
- }
-
fflush(stdout);
StartupInfo.cb = sizeof(STARTUPINFOA);
@@ -396,6 +207,11 @@ int main(int argc, char *argv[])
StartupInfo.hStdOutput = GetStdHandle(STD_OUTPUT_HANDLE);
StartupInfo.hStdError = GetStdHandle(STD_ERROR_HANDLE);
+ /* Unlike WineTest we do not have the luxury of first running the test with
+ * a --list argument. This means we cannot use SetErrorMode() to check
+ * whether there are missing dependencies as it could modify the test
+ * results...
+ */
if (! CreateProcessA(NULL, CommandLine, NULL, NULL, TRUE, CREATE_DEFAULT_ERROR_MODE, NULL, NULL, &StartupInfo, &ProcessInformation))
{
fprintf(stderr, "CreateProcess failed with error %lu\n", GetLastError());
@@ -403,7 +219,31 @@ int main(int argc, char *argv[])
}
CloseHandle(ProcessInformation.hThread);
- ExitCode = WaitForSingleObject(ProcessInformation.hProcess, TimeOut);
+ WaitTimeOut = 100;
+ ExitCode = WAIT_TIMEOUT;
+ while (ExitCode == WAIT_TIMEOUT)
+ {
+ DWORD Elapsed, Remaining;
+
+ ExitCode = WaitForSingleObject(ProcessInformation.hProcess, WaitTimeOut);
+ Elapsed = GetTickCount() - Start;
+ if (ExitCode != WAIT_TIMEOUT || Elapsed > TimeOut)
+ break;
+
+ /* ...instead detect the critical error dialog that pops up */
+ EnumWindows(DetectCriticalErrorDialog, (LPARAM)TestExeFileName);
+ if (Skips)
+ {
+ ExitCode = WAIT_OBJECT_0;
+ break;
+ }
+
+ Remaining = TimeOut == INFINITE ? TimeOut : TimeOut - Elapsed;
+ WaitTimeOut = (Elapsed > 3000) ? Remaining :
+ (2 * WaitTimeOut < Remaining) ? 2 * WaitTimeOut :
+ Remaining;
+ }
+
if (ExitCode != WAIT_OBJECT_0)
{
switch (ExitCode)
@@ -423,13 +263,16 @@ int main(int argc, char *argv[])
if (!TerminateProcess(ProcessInformation.hProcess, 257))
fprintf(stderr, "TerminateProcess failed, error %lu\n", GetLastError());
}
- else if (!GetExitCodeProcess(ProcessInformation.hProcess, &ExitCode))
+ else if (!Skips && !GetExitCodeProcess(ProcessInformation.hProcess, &ExitCode))
{
fprintf(stderr, "Can't get child exit code, error %lu\n", GetLastError());
ExitCode = 259;
}
CloseHandle(ProcessInformation.hProcess);
+ if (Skips)
+ printf("%04lx:%s: 0 tests executed (0 marked as todo, 0 failures), %u skipped.\n", ProcessInformation.dwProcessId, Subtest, Skips);
+
printf("%s:%s:%04lx done (%ld) in %lds\n", TestName, Subtest,
ProcessInformation.dwProcessId, ExitCode,
(GetTickCount() - Start) / 1000);
--
2.20.1
More information about the wine-devel
mailing list