browser/metro/shell/testing/metrotestharness.cpp

Thu, 22 Jan 2015 13:21:57 +0100

author
Michael Schloh von Bennewitz <michael@schloh.com>
date
Thu, 22 Jan 2015 13:21:57 +0100
branch
TOR_BUG_9701
changeset 15
b8a032363ba2
permissions
-rw-r--r--

Incorporate requested changes from Mozilla in review:
https://bugzilla.mozilla.org/show_bug.cgi?id=1123480#c6

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

mercurial