diff -r 000000000000 -r 6474c204b198 browser/metro/shell/testing/metrotestharness.cpp --- /dev/null Thu Jan 01 00:00:00 1970 +0000 +++ b/browser/metro/shell/testing/metrotestharness.cpp Wed Dec 31 06:09:35 2014 +0100 @@ -0,0 +1,398 @@ +/* -*- Mode: C++; tab-width: 2; indent-tabs-mode: nil; c-basic-offset: 2 -*- */ +/* This Source Code Form is subject to the terms of the Mozilla Public + * License, v. 2.0. If a copy of the MPL was not distributed with this file, + * You can obtain one at http://mozilla.org/MPL/2.0/. */ + +#undef WINVER +#undef _WIN32_WINNT +#define WINVER 0x602 +#define _WIN32_WINNT 0x602 + +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include + +static const WCHAR* kFirefoxExe = L"firefox.exe"; +static const WCHAR* kDefaultMetroBrowserIDPathKey = L"FirefoxURL"; + +// Logging pipe handle +HANDLE gTestOutputPipe = INVALID_HANDLE_VALUE; +// Logging pipe read buffer +#define PIPE_BUFFER_SIZE 4096 +char buffer[PIPE_BUFFER_SIZE + 1]; + +CString sAppParams; +CString sFirefoxPath; + +// The tests file we write out for firefox.exe which contains test +// startup command line paramters. +#define kMetroTestFile "tests.ini" + +// Process exit codes for buildbotcustom logic. These are currently ignored, but +// at some point releng expects to use these. +#define SUCCESS 0 +#define WARNINGS 1 +#define FAILURE 2 +#define EXCEPTION 3 +#define RETRY 4 + +static void Log(const wchar_t *fmt, ...) +{ + va_list a = nullptr; + wchar_t szDebugString[1024]; + if(!lstrlenW(fmt)) + return; + va_start(a,fmt); + vswprintf(szDebugString, 1024, fmt, a); + va_end(a); + if(!lstrlenW(szDebugString)) + return; + + wprintf(L"INFO | metrotestharness.exe | %s\n", szDebugString); + fflush(stdout); +} + +static void Fail(bool aRequestRetry, const wchar_t *fmt, ...) +{ + va_list a = nullptr; + wchar_t szDebugString[1024]; + if(!lstrlenW(fmt)) + return; + va_start(a,fmt); + vswprintf(szDebugString, 1024, fmt, a); + va_end(a); + if(!lstrlenW(szDebugString)) + return; + if (aRequestRetry) { + wprintf(L"FAIL-SHOULD-RETRY | metrotestharness.exe | %s\n", szDebugString); + } else { + wprintf(L"TEST-UNEXPECTED-FAIL | metrotestharness.exe | %s\n", szDebugString); + } + fflush(stdout); +} + +/* + * Retrieve our module dir path. + * + * @aPathBuffer Buffer to fill + */ +static bool GetModulePath(CStringW& aPathBuffer) +{ + WCHAR buffer[MAX_PATH]; + memset(buffer, 0, sizeof(buffer)); + + if (!GetModuleFileName(nullptr, buffer, MAX_PATH)) { + Fail(false, L"GetModuleFileName failed."); + return false; + } + + WCHAR* slash = wcsrchr(buffer, '\\'); + if (!slash) + return false; + *slash = '\0'; + + aPathBuffer = buffer; + return true; +} + +/* + * Retrieve 'module dir path\firefox.exe' + * + * @aPathBuffer Buffer to fill + */ +static bool GetDesktopBrowserPath(CStringW& aPathBuffer) +{ + if (!GetModulePath(aPathBuffer)) + return false; + + // ceh.exe sits in dist/bin root with the desktop browser. Since this + // is a firefox only component, this hardcoded filename is ok. + aPathBuffer.Append(L"\\"); + aPathBuffer.Append(kFirefoxExe); + return true; +} + +/* + * Retrieve the app model id of the firefox metro browser. + * + * @aPathBuffer Buffer to fill + * @aCharLength Length of buffer to fill in characters + */ +static bool GetDefaultBrowserAppModelID(WCHAR* aIDBuffer, + long aCharLength) +{ + if (!aIDBuffer || aCharLength <= 0) + return false; + + memset(aIDBuffer, 0, (sizeof(WCHAR)*aCharLength)); + + HKEY key; + if (RegOpenKeyExW(HKEY_CLASSES_ROOT, kDefaultMetroBrowserIDPathKey, + 0, KEY_READ, &key) != ERROR_SUCCESS) { + return false; + } + DWORD len = aCharLength * sizeof(WCHAR); + memset(aIDBuffer, 0, len); + if (RegQueryValueExW(key, L"AppUserModelID", nullptr, nullptr, + (LPBYTE)aIDBuffer, &len) != ERROR_SUCCESS || !len) { + RegCloseKey(key); + return false; + } + RegCloseKey(key); + return true; +} + +// Tests.ini file cleanup helper +class DeleteTestFileHelper +{ + CStringA mTestFile; +public: + DeleteTestFileHelper(CStringA& aTestFile) : + mTestFile(aTestFile) {} + ~DeleteTestFileHelper() { + if (mTestFile.GetLength()) { + Log(L"Deleting %s", CStringW(mTestFile)); + DeleteFileA(mTestFile); + } + } +}; + +static bool SetupTestOutputPipe() +{ + SECURITY_ATTRIBUTES saAttr; + saAttr.nLength = sizeof(SECURITY_ATTRIBUTES); + saAttr.bInheritHandle = TRUE; + saAttr.lpSecurityDescriptor = nullptr; + + gTestOutputPipe = + CreateNamedPipeW(L"\\\\.\\pipe\\metrotestharness", + PIPE_ACCESS_INBOUND, + PIPE_TYPE_BYTE|PIPE_WAIT, + 1, + PIPE_BUFFER_SIZE, + PIPE_BUFFER_SIZE, 0, nullptr); + + if (gTestOutputPipe == INVALID_HANDLE_VALUE) { + Log(L"Failed to create named logging pipe."); + return false; + } + return true; +} + +static void ReadPipe() +{ + DWORD numBytesRead; + while (ReadFile(gTestOutputPipe, buffer, PIPE_BUFFER_SIZE, + &numBytesRead, nullptr) && + numBytesRead) { + buffer[numBytesRead] = '\0'; + printf("%s", buffer); + fflush(stdout); + } +} + +static int Launch() +{ + Log(L"Launching browser..."); + + DWORD processID; + + // The interface that allows us to activate the browser + CComPtr activateMgr; + if (FAILED(CoCreateInstance(CLSID_ApplicationActivationManager, nullptr, + CLSCTX_LOCAL_SERVER, + IID_IApplicationActivationManager, + (void**)&activateMgr))) { + Fail(false, L"CoCreateInstance CLSID_ApplicationActivationManager failed."); + return FAILURE; + } + + HRESULT hr; + WCHAR appModelID[256]; + // Activation is based on the browser's registered app model id + if (!GetDefaultBrowserAppModelID(appModelID, (sizeof(appModelID)/sizeof(WCHAR)))) { + Fail(false, L"GetDefaultBrowserAppModelID failed."); + return FAILURE; + } + Log(L"App model id='%s'", appModelID); + + // Hand off focus rights if the terminal has focus to the out-of-process + // activation server (explorer.exe). Without this the metro interface + // won't launch. + hr = CoAllowSetForegroundWindow(activateMgr, nullptr); + if (FAILED(hr)) { + // Log but don't fail. This has happened on vms with certain terminals run by + // QA during mozmill testing. + Log(L"Windows focus rights hand off failed (HRESULT=0x%X). Ignoring.", hr); + } + + Log(L"Harness process id: %d", GetCurrentProcessId()); + + // If provided, validate the firefox path passed in. + int binLen = wcslen(kFirefoxExe); + if (sFirefoxPath.GetLength() && sFirefoxPath.Right(binLen) != kFirefoxExe) { + Log(L"firefoxpath is missing a valid bin name! Assuming '%s'.", kFirefoxExe); + if (sFirefoxPath.Right(1) != L"\\") { + sFirefoxPath += L"\\"; + } + sFirefoxPath += kFirefoxExe; + } + + // Because we can't pass command line args, we store params in a + // tests.ini file in dist/bin which the browser picks up on launch. + CStringA testFilePath; + if (sFirefoxPath.GetLength()) { + // Use the firefoxpath passed to us by the test harness + int index = sFirefoxPath.ReverseFind('\\'); + if (index == -1) { + Fail(false, L"Bad firefoxpath path"); + return FAILURE; + } + testFilePath = sFirefoxPath.Mid(0, index); + testFilePath += "\\"; + testFilePath += kMetroTestFile; + } else { + // Use the module path + char path[MAX_PATH]; + if (!GetModuleFileNameA(nullptr, path, MAX_PATH)) { + Fail(false, L"GetModuleFileNameA errorno=%d", GetLastError()); + return FAILURE; + } + char* slash = strrchr(path, '\\'); + if (!slash) + return FAILURE; + *slash = '\0'; // no trailing slash + testFilePath = path; + testFilePath += "\\"; + sFirefoxPath = testFilePath; + sFirefoxPath += kFirefoxExe; + testFilePath += kMetroTestFile; + } + + // Make sure the firefox bin exists + if (GetFileAttributesW(sFirefoxPath) == INVALID_FILE_ATTRIBUTES) { + Fail(false, L"Invalid bin path: '%s'", sFirefoxPath); + return FAILURE; + } + + Log(L"Using bin path: '%s'", sFirefoxPath); + + Log(L"Writing out tests.ini to: '%s'", CStringW(testFilePath)); + HANDLE hTestFile = CreateFileA(testFilePath, GENERIC_WRITE, + 0, nullptr, CREATE_ALWAYS, + FILE_ATTRIBUTE_NORMAL, + nullptr); + if (hTestFile == INVALID_HANDLE_VALUE) { + Fail(false, L"CreateFileA errorno=%d", GetLastError()); + return FAILURE; + } + + DeleteTestFileHelper dtf(testFilePath); + + // nsAppRunner expects the first param to be the bin path, just like a + // normal startup. So prepend our bin path to our param string we write. + CStringA asciiParams = sFirefoxPath; + asciiParams += " "; + asciiParams += sAppParams; + asciiParams.Trim(); + Log(L"Browser command line args: '%s'", CString(asciiParams)); + if (!WriteFile(hTestFile, asciiParams, asciiParams.GetLength(), + nullptr, 0)) { + CloseHandle(hTestFile); + Fail(false, L"WriteFile errorno=%d", GetLastError()); + return FAILURE; + } + FlushFileBuffers(hTestFile); + CloseHandle(hTestFile); + + // Create a named stdout pipe for the browser + if (!SetupTestOutputPipe()) { + Fail(false, L"SetupTestOutputPipe failed (errno=%d)", GetLastError()); + return FAILURE; + } + + // Launch firefox + hr = activateMgr->ActivateApplication(appModelID, L"", AO_NOERRORUI, &processID); + if (FAILED(hr)) { + Fail(true, L"ActivateApplication result %X", hr); + return RETRY; + } + + Log(L"Activation succeeded."); + + // automation.py picks up on this, don't mess with it. + Log(L"METRO_BROWSER_PROCESS=%d", processID); + + HANDLE child = OpenProcess(SYNCHRONIZE, FALSE, processID); + if (!child) { + Fail(false, L"Couldn't find child process. (%d)", GetLastError()); + return FAILURE; + } + + Log(L"Waiting on child process..."); + + MSG msg; + DWORD waitResult = WAIT_TIMEOUT; + HANDLE handles[2] = { child, gTestOutputPipe }; + while ((waitResult = MsgWaitForMultipleObjects(2, handles, FALSE, INFINITE, QS_ALLINPUT)) != WAIT_OBJECT_0) { + if (waitResult == WAIT_FAILED) { + Log(L"Wait failed (errno=%d)", GetLastError()); + break; + } else if (waitResult == WAIT_OBJECT_0 + 1) { + ReadPipe(); + } else if (waitResult == WAIT_OBJECT_0 + 2 && + PeekMessage(&msg, nullptr, 0, 0, PM_REMOVE)) { + TranslateMessage(&msg); + DispatchMessage(&msg); + } + } + + ReadPipe(); + CloseHandle(gTestOutputPipe); + CloseHandle(child); + + Log(L"Exiting."); + return SUCCESS; +} + +int wmain(int argc, WCHAR* argv[]) +{ + CoInitialize(nullptr); + + int idx; + bool firefoxParam = false; + for (idx = 1; idx < argc; idx++) { + CString param = argv[idx]; + param.Trim(); + + // Pickup the firefox path param and store it, we'll need this + // when we create the tests.ini file. + if (param == "-firefoxpath") { + firefoxParam = true; + continue; + } else if (firefoxParam) { + firefoxParam = false; + sFirefoxPath = param; + continue; + } + + sAppParams.Append(argv[idx]); + sAppParams.Append(L" "); + } + sAppParams.Trim(); + int res = Launch(); + CoUninitialize(); + return res; +}