browser/metro/shell/testing/metrotestharness.cpp

Wed, 31 Dec 2014 06:55:50 +0100

author
Michael Schloh von Bennewitz <michael@schloh.com>
date
Wed, 31 Dec 2014 06:55:50 +0100
changeset 2
7e26c7da4463
permissions
-rw-r--r--

Added tag UPSTREAM_283F7C6 for changeset ca08bd8f51b2

michael@0 1 /* -*- Mode: C++; tab-width: 2; indent-tabs-mode: nil; c-basic-offset: 2 -*- */
michael@0 2 /* This Source Code Form is subject to the terms of the Mozilla Public
michael@0 3 * License, v. 2.0. If a copy of the MPL was not distributed with this file,
michael@0 4 * You can obtain one at http://mozilla.org/MPL/2.0/. */
michael@0 5
michael@0 6 #undef WINVER
michael@0 7 #undef _WIN32_WINNT
michael@0 8 #define WINVER 0x602
michael@0 9 #define _WIN32_WINNT 0x602
michael@0 10
michael@0 11 #include <windows.h>
michael@0 12 #include <objbase.h>
michael@0 13 #include <combaseapi.h>
michael@0 14 #include <atlcore.h>
michael@0 15 #include <atlstr.h>
michael@0 16 #include <wininet.h>
michael@0 17 #include <shlobj.h>
michael@0 18 #include <shlwapi.h>
michael@0 19 #include <propkey.h>
michael@0 20 #include <propvarutil.h>
michael@0 21 #include <stdio.h>
michael@0 22 #include <stdlib.h>
michael@0 23 #include <strsafe.h>
michael@0 24 #include <io.h>
michael@0 25 #include <shellapi.h>
michael@0 26
michael@0 27 static const WCHAR* kFirefoxExe = L"firefox.exe";
michael@0 28 static const WCHAR* kDefaultMetroBrowserIDPathKey = L"FirefoxURL";
michael@0 29
michael@0 30 // Logging pipe handle
michael@0 31 HANDLE gTestOutputPipe = INVALID_HANDLE_VALUE;
michael@0 32 // Logging pipe read buffer
michael@0 33 #define PIPE_BUFFER_SIZE 4096
michael@0 34 char buffer[PIPE_BUFFER_SIZE + 1];
michael@0 35
michael@0 36 CString sAppParams;
michael@0 37 CString sFirefoxPath;
michael@0 38
michael@0 39 // The tests file we write out for firefox.exe which contains test
michael@0 40 // startup command line paramters.
michael@0 41 #define kMetroTestFile "tests.ini"
michael@0 42
michael@0 43 // Process exit codes for buildbotcustom logic. These are currently ignored, but
michael@0 44 // at some point releng expects to use these.
michael@0 45 #define SUCCESS 0
michael@0 46 #define WARNINGS 1
michael@0 47 #define FAILURE 2
michael@0 48 #define EXCEPTION 3
michael@0 49 #define RETRY 4
michael@0 50
michael@0 51 static void Log(const wchar_t *fmt, ...)
michael@0 52 {
michael@0 53 va_list a = nullptr;
michael@0 54 wchar_t szDebugString[1024];
michael@0 55 if(!lstrlenW(fmt))
michael@0 56 return;
michael@0 57 va_start(a,fmt);
michael@0 58 vswprintf(szDebugString, 1024, fmt, a);
michael@0 59 va_end(a);
michael@0 60 if(!lstrlenW(szDebugString))
michael@0 61 return;
michael@0 62
michael@0 63 wprintf(L"INFO | metrotestharness.exe | %s\n", szDebugString);
michael@0 64 fflush(stdout);
michael@0 65 }
michael@0 66
michael@0 67 static void Fail(bool aRequestRetry, const wchar_t *fmt, ...)
michael@0 68 {
michael@0 69 va_list a = nullptr;
michael@0 70 wchar_t szDebugString[1024];
michael@0 71 if(!lstrlenW(fmt))
michael@0 72 return;
michael@0 73 va_start(a,fmt);
michael@0 74 vswprintf(szDebugString, 1024, fmt, a);
michael@0 75 va_end(a);
michael@0 76 if(!lstrlenW(szDebugString))
michael@0 77 return;
michael@0 78 if (aRequestRetry) {
michael@0 79 wprintf(L"FAIL-SHOULD-RETRY | metrotestharness.exe | %s\n", szDebugString);
michael@0 80 } else {
michael@0 81 wprintf(L"TEST-UNEXPECTED-FAIL | metrotestharness.exe | %s\n", szDebugString);
michael@0 82 }
michael@0 83 fflush(stdout);
michael@0 84 }
michael@0 85
michael@0 86 /*
michael@0 87 * Retrieve our module dir path.
michael@0 88 *
michael@0 89 * @aPathBuffer Buffer to fill
michael@0 90 */
michael@0 91 static bool GetModulePath(CStringW& aPathBuffer)
michael@0 92 {
michael@0 93 WCHAR buffer[MAX_PATH];
michael@0 94 memset(buffer, 0, sizeof(buffer));
michael@0 95
michael@0 96 if (!GetModuleFileName(nullptr, buffer, MAX_PATH)) {
michael@0 97 Fail(false, L"GetModuleFileName failed.");
michael@0 98 return false;
michael@0 99 }
michael@0 100
michael@0 101 WCHAR* slash = wcsrchr(buffer, '\\');
michael@0 102 if (!slash)
michael@0 103 return false;
michael@0 104 *slash = '\0';
michael@0 105
michael@0 106 aPathBuffer = buffer;
michael@0 107 return true;
michael@0 108 }
michael@0 109
michael@0 110 /*
michael@0 111 * Retrieve 'module dir path\firefox.exe'
michael@0 112 *
michael@0 113 * @aPathBuffer Buffer to fill
michael@0 114 */
michael@0 115 static bool GetDesktopBrowserPath(CStringW& aPathBuffer)
michael@0 116 {
michael@0 117 if (!GetModulePath(aPathBuffer))
michael@0 118 return false;
michael@0 119
michael@0 120 // ceh.exe sits in dist/bin root with the desktop browser. Since this
michael@0 121 // is a firefox only component, this hardcoded filename is ok.
michael@0 122 aPathBuffer.Append(L"\\");
michael@0 123 aPathBuffer.Append(kFirefoxExe);
michael@0 124 return true;
michael@0 125 }
michael@0 126
michael@0 127 /*
michael@0 128 * Retrieve the app model id of the firefox metro browser.
michael@0 129 *
michael@0 130 * @aPathBuffer Buffer to fill
michael@0 131 * @aCharLength Length of buffer to fill in characters
michael@0 132 */
michael@0 133 static bool GetDefaultBrowserAppModelID(WCHAR* aIDBuffer,
michael@0 134 long aCharLength)
michael@0 135 {
michael@0 136 if (!aIDBuffer || aCharLength <= 0)
michael@0 137 return false;
michael@0 138
michael@0 139 memset(aIDBuffer, 0, (sizeof(WCHAR)*aCharLength));
michael@0 140
michael@0 141 HKEY key;
michael@0 142 if (RegOpenKeyExW(HKEY_CLASSES_ROOT, kDefaultMetroBrowserIDPathKey,
michael@0 143 0, KEY_READ, &key) != ERROR_SUCCESS) {
michael@0 144 return false;
michael@0 145 }
michael@0 146 DWORD len = aCharLength * sizeof(WCHAR);
michael@0 147 memset(aIDBuffer, 0, len);
michael@0 148 if (RegQueryValueExW(key, L"AppUserModelID", nullptr, nullptr,
michael@0 149 (LPBYTE)aIDBuffer, &len) != ERROR_SUCCESS || !len) {
michael@0 150 RegCloseKey(key);
michael@0 151 return false;
michael@0 152 }
michael@0 153 RegCloseKey(key);
michael@0 154 return true;
michael@0 155 }
michael@0 156
michael@0 157 // Tests.ini file cleanup helper
michael@0 158 class DeleteTestFileHelper
michael@0 159 {
michael@0 160 CStringA mTestFile;
michael@0 161 public:
michael@0 162 DeleteTestFileHelper(CStringA& aTestFile) :
michael@0 163 mTestFile(aTestFile) {}
michael@0 164 ~DeleteTestFileHelper() {
michael@0 165 if (mTestFile.GetLength()) {
michael@0 166 Log(L"Deleting %s", CStringW(mTestFile));
michael@0 167 DeleteFileA(mTestFile);
michael@0 168 }
michael@0 169 }
michael@0 170 };
michael@0 171
michael@0 172 static bool SetupTestOutputPipe()
michael@0 173 {
michael@0 174 SECURITY_ATTRIBUTES saAttr;
michael@0 175 saAttr.nLength = sizeof(SECURITY_ATTRIBUTES);
michael@0 176 saAttr.bInheritHandle = TRUE;
michael@0 177 saAttr.lpSecurityDescriptor = nullptr;
michael@0 178
michael@0 179 gTestOutputPipe =
michael@0 180 CreateNamedPipeW(L"\\\\.\\pipe\\metrotestharness",
michael@0 181 PIPE_ACCESS_INBOUND,
michael@0 182 PIPE_TYPE_BYTE|PIPE_WAIT,
michael@0 183 1,
michael@0 184 PIPE_BUFFER_SIZE,
michael@0 185 PIPE_BUFFER_SIZE, 0, nullptr);
michael@0 186
michael@0 187 if (gTestOutputPipe == INVALID_HANDLE_VALUE) {
michael@0 188 Log(L"Failed to create named logging pipe.");
michael@0 189 return false;
michael@0 190 }
michael@0 191 return true;
michael@0 192 }
michael@0 193
michael@0 194 static void ReadPipe()
michael@0 195 {
michael@0 196 DWORD numBytesRead;
michael@0 197 while (ReadFile(gTestOutputPipe, buffer, PIPE_BUFFER_SIZE,
michael@0 198 &numBytesRead, nullptr) &&
michael@0 199 numBytesRead) {
michael@0 200 buffer[numBytesRead] = '\0';
michael@0 201 printf("%s", buffer);
michael@0 202 fflush(stdout);
michael@0 203 }
michael@0 204 }
michael@0 205
michael@0 206 static int Launch()
michael@0 207 {
michael@0 208 Log(L"Launching browser...");
michael@0 209
michael@0 210 DWORD processID;
michael@0 211
michael@0 212 // The interface that allows us to activate the browser
michael@0 213 CComPtr<IApplicationActivationManager> activateMgr;
michael@0 214 if (FAILED(CoCreateInstance(CLSID_ApplicationActivationManager, nullptr,
michael@0 215 CLSCTX_LOCAL_SERVER,
michael@0 216 IID_IApplicationActivationManager,
michael@0 217 (void**)&activateMgr))) {
michael@0 218 Fail(false, L"CoCreateInstance CLSID_ApplicationActivationManager failed.");
michael@0 219 return FAILURE;
michael@0 220 }
michael@0 221
michael@0 222 HRESULT hr;
michael@0 223 WCHAR appModelID[256];
michael@0 224 // Activation is based on the browser's registered app model id
michael@0 225 if (!GetDefaultBrowserAppModelID(appModelID, (sizeof(appModelID)/sizeof(WCHAR)))) {
michael@0 226 Fail(false, L"GetDefaultBrowserAppModelID failed.");
michael@0 227 return FAILURE;
michael@0 228 }
michael@0 229 Log(L"App model id='%s'", appModelID);
michael@0 230
michael@0 231 // Hand off focus rights if the terminal has focus to the out-of-process
michael@0 232 // activation server (explorer.exe). Without this the metro interface
michael@0 233 // won't launch.
michael@0 234 hr = CoAllowSetForegroundWindow(activateMgr, nullptr);
michael@0 235 if (FAILED(hr)) {
michael@0 236 // Log but don't fail. This has happened on vms with certain terminals run by
michael@0 237 // QA during mozmill testing.
michael@0 238 Log(L"Windows focus rights hand off failed (HRESULT=0x%X). Ignoring.", hr);
michael@0 239 }
michael@0 240
michael@0 241 Log(L"Harness process id: %d", GetCurrentProcessId());
michael@0 242
michael@0 243 // If provided, validate the firefox path passed in.
michael@0 244 int binLen = wcslen(kFirefoxExe);
michael@0 245 if (sFirefoxPath.GetLength() && sFirefoxPath.Right(binLen) != kFirefoxExe) {
michael@0 246 Log(L"firefoxpath is missing a valid bin name! Assuming '%s'.", kFirefoxExe);
michael@0 247 if (sFirefoxPath.Right(1) != L"\\") {
michael@0 248 sFirefoxPath += L"\\";
michael@0 249 }
michael@0 250 sFirefoxPath += kFirefoxExe;
michael@0 251 }
michael@0 252
michael@0 253 // Because we can't pass command line args, we store params in a
michael@0 254 // tests.ini file in dist/bin which the browser picks up on launch.
michael@0 255 CStringA testFilePath;
michael@0 256 if (sFirefoxPath.GetLength()) {
michael@0 257 // Use the firefoxpath passed to us by the test harness
michael@0 258 int index = sFirefoxPath.ReverseFind('\\');
michael@0 259 if (index == -1) {
michael@0 260 Fail(false, L"Bad firefoxpath path");
michael@0 261 return FAILURE;
michael@0 262 }
michael@0 263 testFilePath = sFirefoxPath.Mid(0, index);
michael@0 264 testFilePath += "\\";
michael@0 265 testFilePath += kMetroTestFile;
michael@0 266 } else {
michael@0 267 // Use the module path
michael@0 268 char path[MAX_PATH];
michael@0 269 if (!GetModuleFileNameA(nullptr, path, MAX_PATH)) {
michael@0 270 Fail(false, L"GetModuleFileNameA errorno=%d", GetLastError());
michael@0 271 return FAILURE;
michael@0 272 }
michael@0 273 char* slash = strrchr(path, '\\');
michael@0 274 if (!slash)
michael@0 275 return FAILURE;
michael@0 276 *slash = '\0'; // no trailing slash
michael@0 277 testFilePath = path;
michael@0 278 testFilePath += "\\";
michael@0 279 sFirefoxPath = testFilePath;
michael@0 280 sFirefoxPath += kFirefoxExe;
michael@0 281 testFilePath += kMetroTestFile;
michael@0 282 }
michael@0 283
michael@0 284 // Make sure the firefox bin exists
michael@0 285 if (GetFileAttributesW(sFirefoxPath) == INVALID_FILE_ATTRIBUTES) {
michael@0 286 Fail(false, L"Invalid bin path: '%s'", sFirefoxPath);
michael@0 287 return FAILURE;
michael@0 288 }
michael@0 289
michael@0 290 Log(L"Using bin path: '%s'", sFirefoxPath);
michael@0 291
michael@0 292 Log(L"Writing out tests.ini to: '%s'", CStringW(testFilePath));
michael@0 293 HANDLE hTestFile = CreateFileA(testFilePath, GENERIC_WRITE,
michael@0 294 0, nullptr, CREATE_ALWAYS,
michael@0 295 FILE_ATTRIBUTE_NORMAL,
michael@0 296 nullptr);
michael@0 297 if (hTestFile == INVALID_HANDLE_VALUE) {
michael@0 298 Fail(false, L"CreateFileA errorno=%d", GetLastError());
michael@0 299 return FAILURE;
michael@0 300 }
michael@0 301
michael@0 302 DeleteTestFileHelper dtf(testFilePath);
michael@0 303
michael@0 304 // nsAppRunner expects the first param to be the bin path, just like a
michael@0 305 // normal startup. So prepend our bin path to our param string we write.
michael@0 306 CStringA asciiParams = sFirefoxPath;
michael@0 307 asciiParams += " ";
michael@0 308 asciiParams += sAppParams;
michael@0 309 asciiParams.Trim();
michael@0 310 Log(L"Browser command line args: '%s'", CString(asciiParams));
michael@0 311 if (!WriteFile(hTestFile, asciiParams, asciiParams.GetLength(),
michael@0 312 nullptr, 0)) {
michael@0 313 CloseHandle(hTestFile);
michael@0 314 Fail(false, L"WriteFile errorno=%d", GetLastError());
michael@0 315 return FAILURE;
michael@0 316 }
michael@0 317 FlushFileBuffers(hTestFile);
michael@0 318 CloseHandle(hTestFile);
michael@0 319
michael@0 320 // Create a named stdout pipe for the browser
michael@0 321 if (!SetupTestOutputPipe()) {
michael@0 322 Fail(false, L"SetupTestOutputPipe failed (errno=%d)", GetLastError());
michael@0 323 return FAILURE;
michael@0 324 }
michael@0 325
michael@0 326 // Launch firefox
michael@0 327 hr = activateMgr->ActivateApplication(appModelID, L"", AO_NOERRORUI, &processID);
michael@0 328 if (FAILED(hr)) {
michael@0 329 Fail(true, L"ActivateApplication result %X", hr);
michael@0 330 return RETRY;
michael@0 331 }
michael@0 332
michael@0 333 Log(L"Activation succeeded.");
michael@0 334
michael@0 335 // automation.py picks up on this, don't mess with it.
michael@0 336 Log(L"METRO_BROWSER_PROCESS=%d", processID);
michael@0 337
michael@0 338 HANDLE child = OpenProcess(SYNCHRONIZE, FALSE, processID);
michael@0 339 if (!child) {
michael@0 340 Fail(false, L"Couldn't find child process. (%d)", GetLastError());
michael@0 341 return FAILURE;
michael@0 342 }
michael@0 343
michael@0 344 Log(L"Waiting on child process...");
michael@0 345
michael@0 346 MSG msg;
michael@0 347 DWORD waitResult = WAIT_TIMEOUT;
michael@0 348 HANDLE handles[2] = { child, gTestOutputPipe };
michael@0 349 while ((waitResult = MsgWaitForMultipleObjects(2, handles, FALSE, INFINITE, QS_ALLINPUT)) != WAIT_OBJECT_0) {
michael@0 350 if (waitResult == WAIT_FAILED) {
michael@0 351 Log(L"Wait failed (errno=%d)", GetLastError());
michael@0 352 break;
michael@0 353 } else if (waitResult == WAIT_OBJECT_0 + 1) {
michael@0 354 ReadPipe();
michael@0 355 } else if (waitResult == WAIT_OBJECT_0 + 2 &&
michael@0 356 PeekMessage(&msg, nullptr, 0, 0, PM_REMOVE)) {
michael@0 357 TranslateMessage(&msg);
michael@0 358 DispatchMessage(&msg);
michael@0 359 }
michael@0 360 }
michael@0 361
michael@0 362 ReadPipe();
michael@0 363 CloseHandle(gTestOutputPipe);
michael@0 364 CloseHandle(child);
michael@0 365
michael@0 366 Log(L"Exiting.");
michael@0 367 return SUCCESS;
michael@0 368 }
michael@0 369
michael@0 370 int wmain(int argc, WCHAR* argv[])
michael@0 371 {
michael@0 372 CoInitialize(nullptr);
michael@0 373
michael@0 374 int idx;
michael@0 375 bool firefoxParam = false;
michael@0 376 for (idx = 1; idx < argc; idx++) {
michael@0 377 CString param = argv[idx];
michael@0 378 param.Trim();
michael@0 379
michael@0 380 // Pickup the firefox path param and store it, we'll need this
michael@0 381 // when we create the tests.ini file.
michael@0 382 if (param == "-firefoxpath") {
michael@0 383 firefoxParam = true;
michael@0 384 continue;
michael@0 385 } else if (firefoxParam) {
michael@0 386 firefoxParam = false;
michael@0 387 sFirefoxPath = param;
michael@0 388 continue;
michael@0 389 }
michael@0 390
michael@0 391 sAppParams.Append(argv[idx]);
michael@0 392 sAppParams.Append(L" ");
michael@0 393 }
michael@0 394 sAppParams.Trim();
michael@0 395 int res = Launch();
michael@0 396 CoUninitialize();
michael@0 397 return res;
michael@0 398 }

mercurial