|
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/. */ |
|
5 |
|
6 #undef WINVER |
|
7 #undef _WIN32_WINNT |
|
8 #define WINVER 0x602 |
|
9 #define _WIN32_WINNT 0x602 |
|
10 |
|
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> |
|
26 |
|
27 static const WCHAR* kFirefoxExe = L"firefox.exe"; |
|
28 static const WCHAR* kDefaultMetroBrowserIDPathKey = L"FirefoxURL"; |
|
29 |
|
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]; |
|
35 |
|
36 CString sAppParams; |
|
37 CString sFirefoxPath; |
|
38 |
|
39 // The tests file we write out for firefox.exe which contains test |
|
40 // startup command line paramters. |
|
41 #define kMetroTestFile "tests.ini" |
|
42 |
|
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 |
|
50 |
|
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; |
|
62 |
|
63 wprintf(L"INFO | metrotestharness.exe | %s\n", szDebugString); |
|
64 fflush(stdout); |
|
65 } |
|
66 |
|
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 } |
|
85 |
|
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)); |
|
95 |
|
96 if (!GetModuleFileName(nullptr, buffer, MAX_PATH)) { |
|
97 Fail(false, L"GetModuleFileName failed."); |
|
98 return false; |
|
99 } |
|
100 |
|
101 WCHAR* slash = wcsrchr(buffer, '\\'); |
|
102 if (!slash) |
|
103 return false; |
|
104 *slash = '\0'; |
|
105 |
|
106 aPathBuffer = buffer; |
|
107 return true; |
|
108 } |
|
109 |
|
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; |
|
119 |
|
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 } |
|
126 |
|
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; |
|
138 |
|
139 memset(aIDBuffer, 0, (sizeof(WCHAR)*aCharLength)); |
|
140 |
|
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 } |
|
156 |
|
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 }; |
|
171 |
|
172 static bool SetupTestOutputPipe() |
|
173 { |
|
174 SECURITY_ATTRIBUTES saAttr; |
|
175 saAttr.nLength = sizeof(SECURITY_ATTRIBUTES); |
|
176 saAttr.bInheritHandle = TRUE; |
|
177 saAttr.lpSecurityDescriptor = nullptr; |
|
178 |
|
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); |
|
186 |
|
187 if (gTestOutputPipe == INVALID_HANDLE_VALUE) { |
|
188 Log(L"Failed to create named logging pipe."); |
|
189 return false; |
|
190 } |
|
191 return true; |
|
192 } |
|
193 |
|
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 } |
|
205 |
|
206 static int Launch() |
|
207 { |
|
208 Log(L"Launching browser..."); |
|
209 |
|
210 DWORD processID; |
|
211 |
|
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 } |
|
221 |
|
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); |
|
230 |
|
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 } |
|
240 |
|
241 Log(L"Harness process id: %d", GetCurrentProcessId()); |
|
242 |
|
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 } |
|
252 |
|
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 } |
|
283 |
|
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 } |
|
289 |
|
290 Log(L"Using bin path: '%s'", sFirefoxPath); |
|
291 |
|
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 } |
|
301 |
|
302 DeleteTestFileHelper dtf(testFilePath); |
|
303 |
|
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); |
|
319 |
|
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 } |
|
325 |
|
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 } |
|
332 |
|
333 Log(L"Activation succeeded."); |
|
334 |
|
335 // automation.py picks up on this, don't mess with it. |
|
336 Log(L"METRO_BROWSER_PROCESS=%d", processID); |
|
337 |
|
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 } |
|
343 |
|
344 Log(L"Waiting on child process..."); |
|
345 |
|
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 } |
|
361 |
|
362 ReadPipe(); |
|
363 CloseHandle(gTestOutputPipe); |
|
364 CloseHandle(child); |
|
365 |
|
366 Log(L"Exiting."); |
|
367 return SUCCESS; |
|
368 } |
|
369 |
|
370 int wmain(int argc, WCHAR* argv[]) |
|
371 { |
|
372 CoInitialize(nullptr); |
|
373 |
|
374 int idx; |
|
375 bool firefoxParam = false; |
|
376 for (idx = 1; idx < argc; idx++) { |
|
377 CString param = argv[idx]; |
|
378 param.Trim(); |
|
379 |
|
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 } |
|
390 |
|
391 sAppParams.Append(argv[idx]); |
|
392 sAppParams.Append(L" "); |
|
393 } |
|
394 sAppParams.Trim(); |
|
395 int res = Launch(); |
|
396 CoUninitialize(); |
|
397 return res; |
|
398 } |