1.1 --- /dev/null Thu Jan 01 00:00:00 1970 +0000 1.2 +++ b/browser/metro/shell/testing/metrotestharness.cpp Wed Dec 31 06:09:35 2014 +0100 1.3 @@ -0,0 +1,398 @@ 1.4 +/* -*- Mode: C++; tab-width: 2; indent-tabs-mode: nil; c-basic-offset: 2 -*- */ 1.5 +/* This Source Code Form is subject to the terms of the Mozilla Public 1.6 + * License, v. 2.0. If a copy of the MPL was not distributed with this file, 1.7 + * You can obtain one at http://mozilla.org/MPL/2.0/. */ 1.8 + 1.9 +#undef WINVER 1.10 +#undef _WIN32_WINNT 1.11 +#define WINVER 0x602 1.12 +#define _WIN32_WINNT 0x602 1.13 + 1.14 +#include <windows.h> 1.15 +#include <objbase.h> 1.16 +#include <combaseapi.h> 1.17 +#include <atlcore.h> 1.18 +#include <atlstr.h> 1.19 +#include <wininet.h> 1.20 +#include <shlobj.h> 1.21 +#include <shlwapi.h> 1.22 +#include <propkey.h> 1.23 +#include <propvarutil.h> 1.24 +#include <stdio.h> 1.25 +#include <stdlib.h> 1.26 +#include <strsafe.h> 1.27 +#include <io.h> 1.28 +#include <shellapi.h> 1.29 + 1.30 +static const WCHAR* kFirefoxExe = L"firefox.exe"; 1.31 +static const WCHAR* kDefaultMetroBrowserIDPathKey = L"FirefoxURL"; 1.32 + 1.33 +// Logging pipe handle 1.34 +HANDLE gTestOutputPipe = INVALID_HANDLE_VALUE; 1.35 +// Logging pipe read buffer 1.36 +#define PIPE_BUFFER_SIZE 4096 1.37 +char buffer[PIPE_BUFFER_SIZE + 1]; 1.38 + 1.39 +CString sAppParams; 1.40 +CString sFirefoxPath; 1.41 + 1.42 +// The tests file we write out for firefox.exe which contains test 1.43 +// startup command line paramters. 1.44 +#define kMetroTestFile "tests.ini" 1.45 + 1.46 +// Process exit codes for buildbotcustom logic. These are currently ignored, but 1.47 +// at some point releng expects to use these. 1.48 +#define SUCCESS 0 1.49 +#define WARNINGS 1 1.50 +#define FAILURE 2 1.51 +#define EXCEPTION 3 1.52 +#define RETRY 4 1.53 + 1.54 +static void Log(const wchar_t *fmt, ...) 1.55 +{ 1.56 + va_list a = nullptr; 1.57 + wchar_t szDebugString[1024]; 1.58 + if(!lstrlenW(fmt)) 1.59 + return; 1.60 + va_start(a,fmt); 1.61 + vswprintf(szDebugString, 1024, fmt, a); 1.62 + va_end(a); 1.63 + if(!lstrlenW(szDebugString)) 1.64 + return; 1.65 + 1.66 + wprintf(L"INFO | metrotestharness.exe | %s\n", szDebugString); 1.67 + fflush(stdout); 1.68 +} 1.69 + 1.70 +static void Fail(bool aRequestRetry, const wchar_t *fmt, ...) 1.71 +{ 1.72 + va_list a = nullptr; 1.73 + wchar_t szDebugString[1024]; 1.74 + if(!lstrlenW(fmt)) 1.75 + return; 1.76 + va_start(a,fmt); 1.77 + vswprintf(szDebugString, 1024, fmt, a); 1.78 + va_end(a); 1.79 + if(!lstrlenW(szDebugString)) 1.80 + return; 1.81 + if (aRequestRetry) { 1.82 + wprintf(L"FAIL-SHOULD-RETRY | metrotestharness.exe | %s\n", szDebugString); 1.83 + } else { 1.84 + wprintf(L"TEST-UNEXPECTED-FAIL | metrotestharness.exe | %s\n", szDebugString); 1.85 + } 1.86 + fflush(stdout); 1.87 +} 1.88 + 1.89 +/* 1.90 + * Retrieve our module dir path. 1.91 + * 1.92 + * @aPathBuffer Buffer to fill 1.93 + */ 1.94 +static bool GetModulePath(CStringW& aPathBuffer) 1.95 +{ 1.96 + WCHAR buffer[MAX_PATH]; 1.97 + memset(buffer, 0, sizeof(buffer)); 1.98 + 1.99 + if (!GetModuleFileName(nullptr, buffer, MAX_PATH)) { 1.100 + Fail(false, L"GetModuleFileName failed."); 1.101 + return false; 1.102 + } 1.103 + 1.104 + WCHAR* slash = wcsrchr(buffer, '\\'); 1.105 + if (!slash) 1.106 + return false; 1.107 + *slash = '\0'; 1.108 + 1.109 + aPathBuffer = buffer; 1.110 + return true; 1.111 +} 1.112 + 1.113 +/* 1.114 + * Retrieve 'module dir path\firefox.exe' 1.115 + * 1.116 + * @aPathBuffer Buffer to fill 1.117 + */ 1.118 +static bool GetDesktopBrowserPath(CStringW& aPathBuffer) 1.119 +{ 1.120 + if (!GetModulePath(aPathBuffer)) 1.121 + return false; 1.122 + 1.123 + // ceh.exe sits in dist/bin root with the desktop browser. Since this 1.124 + // is a firefox only component, this hardcoded filename is ok. 1.125 + aPathBuffer.Append(L"\\"); 1.126 + aPathBuffer.Append(kFirefoxExe); 1.127 + return true; 1.128 +} 1.129 + 1.130 +/* 1.131 + * Retrieve the app model id of the firefox metro browser. 1.132 + * 1.133 + * @aPathBuffer Buffer to fill 1.134 + * @aCharLength Length of buffer to fill in characters 1.135 + */ 1.136 +static bool GetDefaultBrowserAppModelID(WCHAR* aIDBuffer, 1.137 + long aCharLength) 1.138 +{ 1.139 + if (!aIDBuffer || aCharLength <= 0) 1.140 + return false; 1.141 + 1.142 + memset(aIDBuffer, 0, (sizeof(WCHAR)*aCharLength)); 1.143 + 1.144 + HKEY key; 1.145 + if (RegOpenKeyExW(HKEY_CLASSES_ROOT, kDefaultMetroBrowserIDPathKey, 1.146 + 0, KEY_READ, &key) != ERROR_SUCCESS) { 1.147 + return false; 1.148 + } 1.149 + DWORD len = aCharLength * sizeof(WCHAR); 1.150 + memset(aIDBuffer, 0, len); 1.151 + if (RegQueryValueExW(key, L"AppUserModelID", nullptr, nullptr, 1.152 + (LPBYTE)aIDBuffer, &len) != ERROR_SUCCESS || !len) { 1.153 + RegCloseKey(key); 1.154 + return false; 1.155 + } 1.156 + RegCloseKey(key); 1.157 + return true; 1.158 +} 1.159 + 1.160 +// Tests.ini file cleanup helper 1.161 +class DeleteTestFileHelper 1.162 +{ 1.163 + CStringA mTestFile; 1.164 +public: 1.165 + DeleteTestFileHelper(CStringA& aTestFile) : 1.166 + mTestFile(aTestFile) {} 1.167 + ~DeleteTestFileHelper() { 1.168 + if (mTestFile.GetLength()) { 1.169 + Log(L"Deleting %s", CStringW(mTestFile)); 1.170 + DeleteFileA(mTestFile); 1.171 + } 1.172 + } 1.173 +}; 1.174 + 1.175 +static bool SetupTestOutputPipe() 1.176 +{ 1.177 + SECURITY_ATTRIBUTES saAttr; 1.178 + saAttr.nLength = sizeof(SECURITY_ATTRIBUTES); 1.179 + saAttr.bInheritHandle = TRUE; 1.180 + saAttr.lpSecurityDescriptor = nullptr; 1.181 + 1.182 + gTestOutputPipe = 1.183 + CreateNamedPipeW(L"\\\\.\\pipe\\metrotestharness", 1.184 + PIPE_ACCESS_INBOUND, 1.185 + PIPE_TYPE_BYTE|PIPE_WAIT, 1.186 + 1, 1.187 + PIPE_BUFFER_SIZE, 1.188 + PIPE_BUFFER_SIZE, 0, nullptr); 1.189 + 1.190 + if (gTestOutputPipe == INVALID_HANDLE_VALUE) { 1.191 + Log(L"Failed to create named logging pipe."); 1.192 + return false; 1.193 + } 1.194 + return true; 1.195 +} 1.196 + 1.197 +static void ReadPipe() 1.198 +{ 1.199 + DWORD numBytesRead; 1.200 + while (ReadFile(gTestOutputPipe, buffer, PIPE_BUFFER_SIZE, 1.201 + &numBytesRead, nullptr) && 1.202 + numBytesRead) { 1.203 + buffer[numBytesRead] = '\0'; 1.204 + printf("%s", buffer); 1.205 + fflush(stdout); 1.206 + } 1.207 +} 1.208 + 1.209 +static int Launch() 1.210 +{ 1.211 + Log(L"Launching browser..."); 1.212 + 1.213 + DWORD processID; 1.214 + 1.215 + // The interface that allows us to activate the browser 1.216 + CComPtr<IApplicationActivationManager> activateMgr; 1.217 + if (FAILED(CoCreateInstance(CLSID_ApplicationActivationManager, nullptr, 1.218 + CLSCTX_LOCAL_SERVER, 1.219 + IID_IApplicationActivationManager, 1.220 + (void**)&activateMgr))) { 1.221 + Fail(false, L"CoCreateInstance CLSID_ApplicationActivationManager failed."); 1.222 + return FAILURE; 1.223 + } 1.224 + 1.225 + HRESULT hr; 1.226 + WCHAR appModelID[256]; 1.227 + // Activation is based on the browser's registered app model id 1.228 + if (!GetDefaultBrowserAppModelID(appModelID, (sizeof(appModelID)/sizeof(WCHAR)))) { 1.229 + Fail(false, L"GetDefaultBrowserAppModelID failed."); 1.230 + return FAILURE; 1.231 + } 1.232 + Log(L"App model id='%s'", appModelID); 1.233 + 1.234 + // Hand off focus rights if the terminal has focus to the out-of-process 1.235 + // activation server (explorer.exe). Without this the metro interface 1.236 + // won't launch. 1.237 + hr = CoAllowSetForegroundWindow(activateMgr, nullptr); 1.238 + if (FAILED(hr)) { 1.239 + // Log but don't fail. This has happened on vms with certain terminals run by 1.240 + // QA during mozmill testing. 1.241 + Log(L"Windows focus rights hand off failed (HRESULT=0x%X). Ignoring.", hr); 1.242 + } 1.243 + 1.244 + Log(L"Harness process id: %d", GetCurrentProcessId()); 1.245 + 1.246 + // If provided, validate the firefox path passed in. 1.247 + int binLen = wcslen(kFirefoxExe); 1.248 + if (sFirefoxPath.GetLength() && sFirefoxPath.Right(binLen) != kFirefoxExe) { 1.249 + Log(L"firefoxpath is missing a valid bin name! Assuming '%s'.", kFirefoxExe); 1.250 + if (sFirefoxPath.Right(1) != L"\\") { 1.251 + sFirefoxPath += L"\\"; 1.252 + } 1.253 + sFirefoxPath += kFirefoxExe; 1.254 + } 1.255 + 1.256 + // Because we can't pass command line args, we store params in a 1.257 + // tests.ini file in dist/bin which the browser picks up on launch. 1.258 + CStringA testFilePath; 1.259 + if (sFirefoxPath.GetLength()) { 1.260 + // Use the firefoxpath passed to us by the test harness 1.261 + int index = sFirefoxPath.ReverseFind('\\'); 1.262 + if (index == -1) { 1.263 + Fail(false, L"Bad firefoxpath path"); 1.264 + return FAILURE; 1.265 + } 1.266 + testFilePath = sFirefoxPath.Mid(0, index); 1.267 + testFilePath += "\\"; 1.268 + testFilePath += kMetroTestFile; 1.269 + } else { 1.270 + // Use the module path 1.271 + char path[MAX_PATH]; 1.272 + if (!GetModuleFileNameA(nullptr, path, MAX_PATH)) { 1.273 + Fail(false, L"GetModuleFileNameA errorno=%d", GetLastError()); 1.274 + return FAILURE; 1.275 + } 1.276 + char* slash = strrchr(path, '\\'); 1.277 + if (!slash) 1.278 + return FAILURE; 1.279 + *slash = '\0'; // no trailing slash 1.280 + testFilePath = path; 1.281 + testFilePath += "\\"; 1.282 + sFirefoxPath = testFilePath; 1.283 + sFirefoxPath += kFirefoxExe; 1.284 + testFilePath += kMetroTestFile; 1.285 + } 1.286 + 1.287 + // Make sure the firefox bin exists 1.288 + if (GetFileAttributesW(sFirefoxPath) == INVALID_FILE_ATTRIBUTES) { 1.289 + Fail(false, L"Invalid bin path: '%s'", sFirefoxPath); 1.290 + return FAILURE; 1.291 + } 1.292 + 1.293 + Log(L"Using bin path: '%s'", sFirefoxPath); 1.294 + 1.295 + Log(L"Writing out tests.ini to: '%s'", CStringW(testFilePath)); 1.296 + HANDLE hTestFile = CreateFileA(testFilePath, GENERIC_WRITE, 1.297 + 0, nullptr, CREATE_ALWAYS, 1.298 + FILE_ATTRIBUTE_NORMAL, 1.299 + nullptr); 1.300 + if (hTestFile == INVALID_HANDLE_VALUE) { 1.301 + Fail(false, L"CreateFileA errorno=%d", GetLastError()); 1.302 + return FAILURE; 1.303 + } 1.304 + 1.305 + DeleteTestFileHelper dtf(testFilePath); 1.306 + 1.307 + // nsAppRunner expects the first param to be the bin path, just like a 1.308 + // normal startup. So prepend our bin path to our param string we write. 1.309 + CStringA asciiParams = sFirefoxPath; 1.310 + asciiParams += " "; 1.311 + asciiParams += sAppParams; 1.312 + asciiParams.Trim(); 1.313 + Log(L"Browser command line args: '%s'", CString(asciiParams)); 1.314 + if (!WriteFile(hTestFile, asciiParams, asciiParams.GetLength(), 1.315 + nullptr, 0)) { 1.316 + CloseHandle(hTestFile); 1.317 + Fail(false, L"WriteFile errorno=%d", GetLastError()); 1.318 + return FAILURE; 1.319 + } 1.320 + FlushFileBuffers(hTestFile); 1.321 + CloseHandle(hTestFile); 1.322 + 1.323 + // Create a named stdout pipe for the browser 1.324 + if (!SetupTestOutputPipe()) { 1.325 + Fail(false, L"SetupTestOutputPipe failed (errno=%d)", GetLastError()); 1.326 + return FAILURE; 1.327 + } 1.328 + 1.329 + // Launch firefox 1.330 + hr = activateMgr->ActivateApplication(appModelID, L"", AO_NOERRORUI, &processID); 1.331 + if (FAILED(hr)) { 1.332 + Fail(true, L"ActivateApplication result %X", hr); 1.333 + return RETRY; 1.334 + } 1.335 + 1.336 + Log(L"Activation succeeded."); 1.337 + 1.338 + // automation.py picks up on this, don't mess with it. 1.339 + Log(L"METRO_BROWSER_PROCESS=%d", processID); 1.340 + 1.341 + HANDLE child = OpenProcess(SYNCHRONIZE, FALSE, processID); 1.342 + if (!child) { 1.343 + Fail(false, L"Couldn't find child process. (%d)", GetLastError()); 1.344 + return FAILURE; 1.345 + } 1.346 + 1.347 + Log(L"Waiting on child process..."); 1.348 + 1.349 + MSG msg; 1.350 + DWORD waitResult = WAIT_TIMEOUT; 1.351 + HANDLE handles[2] = { child, gTestOutputPipe }; 1.352 + while ((waitResult = MsgWaitForMultipleObjects(2, handles, FALSE, INFINITE, QS_ALLINPUT)) != WAIT_OBJECT_0) { 1.353 + if (waitResult == WAIT_FAILED) { 1.354 + Log(L"Wait failed (errno=%d)", GetLastError()); 1.355 + break; 1.356 + } else if (waitResult == WAIT_OBJECT_0 + 1) { 1.357 + ReadPipe(); 1.358 + } else if (waitResult == WAIT_OBJECT_0 + 2 && 1.359 + PeekMessage(&msg, nullptr, 0, 0, PM_REMOVE)) { 1.360 + TranslateMessage(&msg); 1.361 + DispatchMessage(&msg); 1.362 + } 1.363 + } 1.364 + 1.365 + ReadPipe(); 1.366 + CloseHandle(gTestOutputPipe); 1.367 + CloseHandle(child); 1.368 + 1.369 + Log(L"Exiting."); 1.370 + return SUCCESS; 1.371 +} 1.372 + 1.373 +int wmain(int argc, WCHAR* argv[]) 1.374 +{ 1.375 + CoInitialize(nullptr); 1.376 + 1.377 + int idx; 1.378 + bool firefoxParam = false; 1.379 + for (idx = 1; idx < argc; idx++) { 1.380 + CString param = argv[idx]; 1.381 + param.Trim(); 1.382 + 1.383 + // Pickup the firefox path param and store it, we'll need this 1.384 + // when we create the tests.ini file. 1.385 + if (param == "-firefoxpath") { 1.386 + firefoxParam = true; 1.387 + continue; 1.388 + } else if (firefoxParam) { 1.389 + firefoxParam = false; 1.390 + sFirefoxPath = param; 1.391 + continue; 1.392 + } 1.393 + 1.394 + sAppParams.Append(argv[idx]); 1.395 + sAppParams.Append(L" "); 1.396 + } 1.397 + sAppParams.Trim(); 1.398 + int res = Launch(); 1.399 + CoUninitialize(); 1.400 + return res; 1.401 +}