|
1 # This Source Code Form is subject to the terms of the Mozilla Public |
|
2 # License, v. 2.0. If a copy of the MPL was not distributed with this |
|
3 # file, You can obtain one at http://mozilla.org/MPL/2.0/. |
|
4 |
|
5 import sys |
|
6 import os |
|
7 import time |
|
8 import tempfile |
|
9 import re |
|
10 import traceback |
|
11 import shutil |
|
12 import math |
|
13 import base64 |
|
14 |
|
15 sys.path.insert(0, os.path.abspath(os.path.realpath(os.path.dirname(__file__)))) |
|
16 |
|
17 from automation import Automation |
|
18 from remoteautomation import RemoteAutomation, fennecLogcatFilters |
|
19 from runtests import Mochitest |
|
20 from runtests import MochitestServer |
|
21 from mochitest_options import MochitestOptions |
|
22 |
|
23 import devicemanager |
|
24 import droid |
|
25 import manifestparser |
|
26 import mozinfo |
|
27 import mozlog |
|
28 import moznetwork |
|
29 |
|
30 SCRIPT_DIR = os.path.abspath(os.path.realpath(os.path.dirname(__file__))) |
|
31 log = mozlog.getLogger('Mochi-Remote') |
|
32 |
|
33 class RemoteOptions(MochitestOptions): |
|
34 |
|
35 def __init__(self, automation, **kwargs): |
|
36 defaults = {} |
|
37 self._automation = automation or Automation() |
|
38 MochitestOptions.__init__(self) |
|
39 |
|
40 self.add_option("--remote-app-path", action="store", |
|
41 type = "string", dest = "remoteAppPath", |
|
42 help = "Path to remote executable relative to device root using only forward slashes. Either this or app must be specified but not both") |
|
43 defaults["remoteAppPath"] = None |
|
44 |
|
45 self.add_option("--deviceIP", action="store", |
|
46 type = "string", dest = "deviceIP", |
|
47 help = "ip address of remote device to test") |
|
48 defaults["deviceIP"] = None |
|
49 |
|
50 self.add_option("--dm_trans", action="store", |
|
51 type = "string", dest = "dm_trans", |
|
52 help = "the transport to use to communicate with device: [adb|sut]; default=sut") |
|
53 defaults["dm_trans"] = "sut" |
|
54 |
|
55 self.add_option("--devicePort", action="store", |
|
56 type = "string", dest = "devicePort", |
|
57 help = "port of remote device to test") |
|
58 defaults["devicePort"] = 20701 |
|
59 |
|
60 self.add_option("--remote-product-name", action="store", |
|
61 type = "string", dest = "remoteProductName", |
|
62 help = "The executable's name of remote product to test - either fennec or firefox, defaults to fennec") |
|
63 defaults["remoteProductName"] = "fennec" |
|
64 |
|
65 self.add_option("--remote-logfile", action="store", |
|
66 type = "string", dest = "remoteLogFile", |
|
67 help = "Name of log file on the device relative to the device root. PLEASE ONLY USE A FILENAME.") |
|
68 defaults["remoteLogFile"] = None |
|
69 |
|
70 self.add_option("--remote-webserver", action = "store", |
|
71 type = "string", dest = "remoteWebServer", |
|
72 help = "ip address where the remote web server is hosted at") |
|
73 defaults["remoteWebServer"] = None |
|
74 |
|
75 self.add_option("--http-port", action = "store", |
|
76 type = "string", dest = "httpPort", |
|
77 help = "http port of the remote web server") |
|
78 defaults["httpPort"] = automation.DEFAULT_HTTP_PORT |
|
79 |
|
80 self.add_option("--ssl-port", action = "store", |
|
81 type = "string", dest = "sslPort", |
|
82 help = "ssl port of the remote web server") |
|
83 defaults["sslPort"] = automation.DEFAULT_SSL_PORT |
|
84 |
|
85 self.add_option("--robocop-ini", action = "store", |
|
86 type = "string", dest = "robocopIni", |
|
87 help = "name of the .ini file containing the list of tests to run") |
|
88 defaults["robocopIni"] = "" |
|
89 |
|
90 self.add_option("--robocop", action = "store", |
|
91 type = "string", dest = "robocop", |
|
92 help = "name of the .ini file containing the list of tests to run. [DEPRECATED- please use --robocop-ini") |
|
93 defaults["robocop"] = "" |
|
94 |
|
95 self.add_option("--robocop-apk", action = "store", |
|
96 type = "string", dest = "robocopApk", |
|
97 help = "name of the Robocop APK to use for ADB test running") |
|
98 defaults["robocopApk"] = "" |
|
99 |
|
100 self.add_option("--robocop-path", action = "store", |
|
101 type = "string", dest = "robocopPath", |
|
102 help = "Path to the folder where robocop.apk is located at. Primarily used for ADB test running. [DEPRECATED- please use --robocop-apk]") |
|
103 defaults["robocopPath"] = "" |
|
104 |
|
105 self.add_option("--robocop-ids", action = "store", |
|
106 type = "string", dest = "robocopIds", |
|
107 help = "name of the file containing the view ID map (fennec_ids.txt)") |
|
108 defaults["robocopIds"] = "" |
|
109 |
|
110 self.add_option("--remoteTestRoot", action = "store", |
|
111 type = "string", dest = "remoteTestRoot", |
|
112 help = "remote directory to use as test root (eg. /mnt/sdcard/tests or /data/local/tests)") |
|
113 defaults["remoteTestRoot"] = None |
|
114 |
|
115 defaults["logFile"] = "mochitest.log" |
|
116 defaults["autorun"] = True |
|
117 defaults["closeWhenDone"] = True |
|
118 defaults["testPath"] = "" |
|
119 defaults["app"] = None |
|
120 defaults["utilityPath"] = None |
|
121 |
|
122 self.set_defaults(**defaults) |
|
123 |
|
124 def verifyRemoteOptions(self, options, automation): |
|
125 if not options.remoteTestRoot: |
|
126 options.remoteTestRoot = automation._devicemanager.getDeviceRoot() |
|
127 |
|
128 if options.remoteWebServer == None: |
|
129 if os.name != "nt": |
|
130 options.remoteWebServer = moznetwork.get_ip() |
|
131 else: |
|
132 log.error("you must specify a --remote-webserver=<ip address>") |
|
133 return None |
|
134 |
|
135 options.webServer = options.remoteWebServer |
|
136 |
|
137 if (options.deviceIP == None): |
|
138 log.error("you must provide a device IP") |
|
139 return None |
|
140 |
|
141 if (options.remoteLogFile == None): |
|
142 options.remoteLogFile = options.remoteTestRoot + '/logs/mochitest.log' |
|
143 |
|
144 if (options.remoteLogFile.count('/') < 1): |
|
145 options.remoteLogFile = options.remoteTestRoot + '/' + options.remoteLogFile |
|
146 |
|
147 # remoteAppPath or app must be specified to find the product to launch |
|
148 if (options.remoteAppPath and options.app): |
|
149 log.error("You cannot specify both the remoteAppPath and the app setting") |
|
150 return None |
|
151 elif (options.remoteAppPath): |
|
152 options.app = options.remoteTestRoot + "/" + options.remoteAppPath |
|
153 elif (options.app == None): |
|
154 # Neither remoteAppPath nor app are set -- error |
|
155 log.error("You must specify either appPath or app") |
|
156 return None |
|
157 |
|
158 # Only reset the xrePath if it wasn't provided |
|
159 if (options.xrePath == None): |
|
160 options.xrePath = options.utilityPath |
|
161 |
|
162 if (options.pidFile != ""): |
|
163 f = open(options.pidFile, 'w') |
|
164 f.write("%s" % os.getpid()) |
|
165 f.close() |
|
166 |
|
167 # Robocop specific deprecated options. |
|
168 if options.robocop: |
|
169 if options.robocopIni: |
|
170 log.error("can not use deprecated --robocop and replacement --robocop-ini together") |
|
171 return None |
|
172 options.robocopIni = options.robocop |
|
173 del options.robocop |
|
174 |
|
175 if options.robocopPath: |
|
176 if options.robocopApk: |
|
177 log.error("can not use deprecated --robocop-path and replacement --robocop-apk together") |
|
178 return None |
|
179 options.robocopApk = os.path.join(options.robocopPath, 'robocop.apk') |
|
180 del options.robocopPath |
|
181 |
|
182 # Robocop specific options |
|
183 if options.robocopIni != "": |
|
184 if not os.path.exists(options.robocopIni): |
|
185 log.error("Unable to find specified robocop .ini manifest '%s'", options.robocopIni) |
|
186 return None |
|
187 options.robocopIni = os.path.abspath(options.robocopIni) |
|
188 |
|
189 if options.robocopApk != "": |
|
190 if not os.path.exists(options.robocopApk): |
|
191 log.error("Unable to find robocop APK '%s'", options.robocopApk) |
|
192 return None |
|
193 options.robocopApk = os.path.abspath(options.robocopApk) |
|
194 |
|
195 if options.robocopIds != "": |
|
196 if not os.path.exists(options.robocopIds): |
|
197 log.error("Unable to find specified robocop IDs file '%s'", options.robocopIds) |
|
198 return None |
|
199 options.robocopIds = os.path.abspath(options.robocopIds) |
|
200 |
|
201 # allow us to keep original application around for cleanup while running robocop via 'am' |
|
202 options.remoteappname = options.app |
|
203 return options |
|
204 |
|
205 def verifyOptions(self, options, mochitest): |
|
206 # since we are reusing verifyOptions, it will exit if App is not found |
|
207 temp = options.app |
|
208 options.app = __file__ |
|
209 tempPort = options.httpPort |
|
210 tempSSL = options.sslPort |
|
211 tempIP = options.webServer |
|
212 # We are going to override this option later anyway, just pretend |
|
213 # like it's not set for verification purposes. |
|
214 options.dumpOutputDirectory = None |
|
215 options = MochitestOptions.verifyOptions(self, options, mochitest) |
|
216 options.webServer = tempIP |
|
217 options.app = temp |
|
218 options.sslPort = tempSSL |
|
219 options.httpPort = tempPort |
|
220 |
|
221 return options |
|
222 |
|
223 class MochiRemote(Mochitest): |
|
224 |
|
225 _automation = None |
|
226 _dm = None |
|
227 localProfile = None |
|
228 logLines = [] |
|
229 |
|
230 def __init__(self, automation, devmgr, options): |
|
231 self._automation = automation |
|
232 Mochitest.__init__(self) |
|
233 self._dm = devmgr |
|
234 self.environment = self._automation.environment |
|
235 self.remoteProfile = options.remoteTestRoot + "/profile" |
|
236 self._automation.setRemoteProfile(self.remoteProfile) |
|
237 self.remoteLog = options.remoteLogFile |
|
238 self.localLog = options.logFile |
|
239 self._automation.deleteANRs() |
|
240 self.certdbNew = True |
|
241 |
|
242 def cleanup(self, manifest, options): |
|
243 if self._dm.fileExists(self.remoteLog): |
|
244 self._dm.getFile(self.remoteLog, self.localLog) |
|
245 self._dm.removeFile(self.remoteLog) |
|
246 else: |
|
247 log.warn("Unable to retrieve log file (%s) from remote device", |
|
248 self.remoteLog) |
|
249 self._dm.removeDir(self.remoteProfile) |
|
250 Mochitest.cleanup(self, manifest, options) |
|
251 |
|
252 def findPath(self, paths, filename = None): |
|
253 for path in paths: |
|
254 p = path |
|
255 if filename: |
|
256 p = os.path.join(p, filename) |
|
257 if os.path.exists(self.getFullPath(p)): |
|
258 return path |
|
259 return None |
|
260 |
|
261 def makeLocalAutomation(self): |
|
262 localAutomation = Automation() |
|
263 localAutomation.IS_WIN32 = False |
|
264 localAutomation.IS_LINUX = False |
|
265 localAutomation.IS_MAC = False |
|
266 localAutomation.UNIXISH = False |
|
267 hostos = sys.platform |
|
268 if (hostos == 'mac' or hostos == 'darwin'): |
|
269 localAutomation.IS_MAC = True |
|
270 elif (hostos == 'linux' or hostos == 'linux2'): |
|
271 localAutomation.IS_LINUX = True |
|
272 localAutomation.UNIXISH = True |
|
273 elif (hostos == 'win32' or hostos == 'win64'): |
|
274 localAutomation.BIN_SUFFIX = ".exe" |
|
275 localAutomation.IS_WIN32 = True |
|
276 return localAutomation |
|
277 |
|
278 # This seems kludgy, but this class uses paths from the remote host in the |
|
279 # options, except when calling up to the base class, which doesn't |
|
280 # understand the distinction. This switches out the remote values for local |
|
281 # ones that the base class understands. This is necessary for the web |
|
282 # server, SSL tunnel and profile building functions. |
|
283 def switchToLocalPaths(self, options): |
|
284 """ Set local paths in the options, return a function that will restore remote values """ |
|
285 remoteXrePath = options.xrePath |
|
286 remoteProfilePath = options.profilePath |
|
287 remoteUtilityPath = options.utilityPath |
|
288 |
|
289 localAutomation = self.makeLocalAutomation() |
|
290 paths = [ |
|
291 options.xrePath, |
|
292 localAutomation.DIST_BIN, |
|
293 self._automation._product, |
|
294 os.path.join('..', self._automation._product) |
|
295 ] |
|
296 options.xrePath = self.findPath(paths) |
|
297 if options.xrePath == None: |
|
298 log.error("unable to find xulrunner path for %s, please specify with --xre-path", os.name) |
|
299 sys.exit(1) |
|
300 |
|
301 xpcshell = "xpcshell" |
|
302 if (os.name == "nt"): |
|
303 xpcshell += ".exe" |
|
304 |
|
305 if options.utilityPath: |
|
306 paths = [options.utilityPath, options.xrePath] |
|
307 else: |
|
308 paths = [options.xrePath] |
|
309 options.utilityPath = self.findPath(paths, xpcshell) |
|
310 |
|
311 if options.utilityPath == None: |
|
312 log.error("unable to find utility path for %s, please specify with --utility-path", os.name) |
|
313 sys.exit(1) |
|
314 |
|
315 xpcshell_path = os.path.join(options.utilityPath, xpcshell) |
|
316 if localAutomation.elf_arm(xpcshell_path): |
|
317 log.error('xpcshell at %s is an ARM binary; please use ' |
|
318 'the --utility-path argument to specify the path ' |
|
319 'to a desktop version.' % xpcshell_path) |
|
320 sys.exit(1) |
|
321 |
|
322 if self.localProfile: |
|
323 options.profilePath = self.localProfile |
|
324 else: |
|
325 options.profilePath = tempfile.mkdtemp() |
|
326 |
|
327 def fixup(): |
|
328 options.xrePath = remoteXrePath |
|
329 options.utilityPath = remoteUtilityPath |
|
330 options.profilePath = remoteProfilePath |
|
331 |
|
332 return fixup |
|
333 |
|
334 def startServers(self, options, debuggerInfo): |
|
335 """ Create the servers on the host and start them up """ |
|
336 restoreRemotePaths = self.switchToLocalPaths(options) |
|
337 Mochitest.startServers(self, options, debuggerInfo) |
|
338 restoreRemotePaths() |
|
339 |
|
340 def buildProfile(self, options): |
|
341 restoreRemotePaths = self.switchToLocalPaths(options) |
|
342 manifest = Mochitest.buildProfile(self, options) |
|
343 self.localProfile = options.profilePath |
|
344 self._dm.removeDir(self.remoteProfile) |
|
345 |
|
346 # we do not need this for robotium based tests, lets save a LOT of time |
|
347 if options.robocopIni: |
|
348 shutil.rmtree(os.path.join(options.profilePath, 'webapps')) |
|
349 shutil.rmtree(os.path.join(options.profilePath, 'extensions', 'staged', 'mochikit@mozilla.org')) |
|
350 shutil.rmtree(os.path.join(options.profilePath, 'extensions', 'staged', 'worker-test@mozilla.org')) |
|
351 shutil.rmtree(os.path.join(options.profilePath, 'extensions', 'staged', 'workerbootstrap-test@mozilla.org')) |
|
352 os.remove(os.path.join(options.profilePath, 'userChrome.css')) |
|
353 |
|
354 try: |
|
355 self._dm.pushDir(options.profilePath, self.remoteProfile) |
|
356 except devicemanager.DMError: |
|
357 log.error("Automation Error: Unable to copy profile to device.") |
|
358 raise |
|
359 |
|
360 restoreRemotePaths() |
|
361 options.profilePath = self.remoteProfile |
|
362 return manifest |
|
363 |
|
364 def buildURLOptions(self, options, env): |
|
365 self.localLog = options.logFile |
|
366 options.logFile = self.remoteLog |
|
367 options.profilePath = self.localProfile |
|
368 env["MOZ_HIDE_RESULTS_TABLE"] = "1" |
|
369 retVal = Mochitest.buildURLOptions(self, options, env) |
|
370 |
|
371 if not options.robocopIni: |
|
372 #we really need testConfig.js (for browser chrome) |
|
373 try: |
|
374 self._dm.pushDir(options.profilePath, self.remoteProfile) |
|
375 except devicemanager.DMError: |
|
376 log.error("Automation Error: Unable to copy profile to device.") |
|
377 raise |
|
378 |
|
379 options.profilePath = self.remoteProfile |
|
380 options.logFile = self.localLog |
|
381 return retVal |
|
382 |
|
383 def buildTestPath(self, options): |
|
384 if options.robocopIni != "": |
|
385 # Skip over manifest building if we just want to run |
|
386 # robocop tests. |
|
387 return self.buildTestURL(options) |
|
388 else: |
|
389 return super(MochiRemote, self).buildTestPath(options) |
|
390 |
|
391 def installChromeFile(self, filename, options): |
|
392 parts = options.app.split('/') |
|
393 if (parts[0] == options.app): |
|
394 return "NO_CHROME_ON_DROID" |
|
395 path = '/'.join(parts[:-1]) |
|
396 manifest = path + "/chrome/" + os.path.basename(filename) |
|
397 try: |
|
398 self._dm.pushFile(filename, manifest) |
|
399 except devicemanager.DMError: |
|
400 log.error("Automation Error: Unable to install Chrome files on device.") |
|
401 raise |
|
402 |
|
403 return manifest |
|
404 |
|
405 def getLogFilePath(self, logFile): |
|
406 return logFile |
|
407 |
|
408 # In the future we could use LogParser: http://hg.mozilla.org/automation/logparser/ |
|
409 def addLogData(self): |
|
410 with open(self.localLog) as currentLog: |
|
411 data = currentLog.readlines() |
|
412 |
|
413 restart = re.compile('0 INFO SimpleTest START.*') |
|
414 reend = re.compile('([0-9]+) INFO TEST-START . Shutdown.*') |
|
415 refail = re.compile('([0-9]+) INFO TEST-UNEXPECTED-FAIL.*') |
|
416 start_found = False |
|
417 end_found = False |
|
418 fail_found = False |
|
419 for line in data: |
|
420 if reend.match(line): |
|
421 end_found = True |
|
422 start_found = False |
|
423 break |
|
424 |
|
425 if start_found and not end_found: |
|
426 # Append the line without the number to increment |
|
427 self.logLines.append(' '.join(line.split(' ')[1:])) |
|
428 |
|
429 if restart.match(line): |
|
430 start_found = True |
|
431 if refail.match(line): |
|
432 fail_found = True |
|
433 result = 0 |
|
434 if fail_found: |
|
435 result = 1 |
|
436 if not end_found: |
|
437 log.error("Automation Error: Missing end of test marker (process crashed?)") |
|
438 result = 1 |
|
439 return result |
|
440 |
|
441 def printLog(self): |
|
442 passed = 0 |
|
443 failed = 0 |
|
444 todo = 0 |
|
445 incr = 1 |
|
446 logFile = [] |
|
447 logFile.append("0 INFO SimpleTest START") |
|
448 for line in self.logLines: |
|
449 if line.startswith("INFO TEST-PASS"): |
|
450 passed += 1 |
|
451 elif line.startswith("INFO TEST-UNEXPECTED"): |
|
452 failed += 1 |
|
453 elif line.startswith("INFO TEST-KNOWN"): |
|
454 todo += 1 |
|
455 incr += 1 |
|
456 |
|
457 logFile.append("%s INFO TEST-START | Shutdown" % incr) |
|
458 incr += 1 |
|
459 logFile.append("%s INFO Passed: %s" % (incr, passed)) |
|
460 incr += 1 |
|
461 logFile.append("%s INFO Failed: %s" % (incr, failed)) |
|
462 incr += 1 |
|
463 logFile.append("%s INFO Todo: %s" % (incr, todo)) |
|
464 incr += 1 |
|
465 logFile.append("%s INFO SimpleTest FINISHED" % incr) |
|
466 |
|
467 # TODO: Consider not printing to stdout because we might be duplicating output |
|
468 print '\n'.join(logFile) |
|
469 with open(self.localLog, 'w') as localLog: |
|
470 localLog.write('\n'.join(logFile)) |
|
471 |
|
472 if failed > 0: |
|
473 return 1 |
|
474 return 0 |
|
475 |
|
476 def printScreenshots(self, screenShotDir): |
|
477 # TODO: This can be re-written after completion of bug 749421 |
|
478 if not self._dm.dirExists(screenShotDir): |
|
479 log.info("SCREENSHOT: No ScreenShots directory available: " + screenShotDir) |
|
480 return |
|
481 |
|
482 printed = 0 |
|
483 for name in self._dm.listFiles(screenShotDir): |
|
484 fullName = screenShotDir + "/" + name |
|
485 log.info("SCREENSHOT: FOUND: [%s]", fullName) |
|
486 try: |
|
487 image = self._dm.pullFile(fullName) |
|
488 encoded = base64.b64encode(image) |
|
489 log.info("SCREENSHOT: data:image/jpg;base64,%s", encoded) |
|
490 printed += 1 |
|
491 except: |
|
492 log.info("SCREENSHOT: Could not be parsed") |
|
493 pass |
|
494 |
|
495 log.info("SCREENSHOT: TOTAL PRINTED: [%s]", printed) |
|
496 |
|
497 def printDeviceInfo(self, printLogcat=False): |
|
498 try: |
|
499 if printLogcat: |
|
500 logcat = self._dm.getLogcat(filterOutRegexps=fennecLogcatFilters) |
|
501 log.info('\n'+(''.join(logcat))) |
|
502 log.info("Device info: %s", self._dm.getInfo()) |
|
503 log.info("Test root: %s", self._dm.getDeviceRoot()) |
|
504 except devicemanager.DMError: |
|
505 log.warn("Error getting device information") |
|
506 |
|
507 def buildRobotiumConfig(self, options, browserEnv): |
|
508 deviceRoot = self._dm.getDeviceRoot() |
|
509 fHandle = tempfile.NamedTemporaryFile(suffix='.config', |
|
510 prefix='robotium-', |
|
511 dir=os.getcwd(), |
|
512 delete=False) |
|
513 fHandle.write("profile=%s\n" % (self.remoteProfile)) |
|
514 fHandle.write("logfile=%s\n" % (options.remoteLogFile)) |
|
515 fHandle.write("host=http://mochi.test:8888/tests\n") |
|
516 fHandle.write("rawhost=http://%s:%s/tests\n" % (options.remoteWebServer, options.httpPort)) |
|
517 |
|
518 if browserEnv: |
|
519 envstr = "" |
|
520 delim = "" |
|
521 for key, value in browserEnv.items(): |
|
522 try: |
|
523 value.index(',') |
|
524 log.error("buildRobotiumConfig: browserEnv - Found a ',' in our value, unable to process value. key=%s,value=%s", key, value) |
|
525 log.error("browserEnv=%s", browserEnv) |
|
526 except ValueError, e: |
|
527 envstr += "%s%s=%s" % (delim, key, value) |
|
528 delim = "," |
|
529 |
|
530 fHandle.write("envvars=%s\n" % envstr) |
|
531 fHandle.close() |
|
532 |
|
533 self._dm.removeFile(os.path.join(deviceRoot, "robotium.config")) |
|
534 self._dm.pushFile(fHandle.name, os.path.join(deviceRoot, "robotium.config")) |
|
535 os.unlink(fHandle.name) |
|
536 |
|
537 def buildBrowserEnv(self, options, debugger=False): |
|
538 browserEnv = Mochitest.buildBrowserEnv(self, options, debugger=debugger) |
|
539 self.buildRobotiumConfig(options, browserEnv) |
|
540 return browserEnv |
|
541 |
|
542 def runApp(self, *args, **kwargs): |
|
543 """front-end automation.py's `runApp` functionality until FennecRunner is written""" |
|
544 |
|
545 # automation.py/remoteautomation `runApp` takes the profile path, |
|
546 # whereas runtest.py's `runApp` takes a mozprofile object. |
|
547 if 'profileDir' not in kwargs and 'profile' in kwargs: |
|
548 kwargs['profileDir'] = kwargs.pop('profile').profile |
|
549 |
|
550 # We're handling ssltunnel, so we should lie to automation.py to avoid |
|
551 # it trying to set up ssltunnel as well |
|
552 kwargs['runSSLTunnel'] = False |
|
553 |
|
554 return self._automation.runApp(*args, **kwargs) |
|
555 |
|
556 def main(): |
|
557 auto = RemoteAutomation(None, "fennec") |
|
558 parser = RemoteOptions(auto) |
|
559 options, args = parser.parse_args() |
|
560 |
|
561 if (options.dm_trans == "adb"): |
|
562 if (options.deviceIP): |
|
563 dm = droid.DroidADB(options.deviceIP, options.devicePort, deviceRoot=options.remoteTestRoot) |
|
564 else: |
|
565 dm = droid.DroidADB(deviceRoot=options.remoteTestRoot) |
|
566 else: |
|
567 dm = droid.DroidSUT(options.deviceIP, options.devicePort, deviceRoot=options.remoteTestRoot) |
|
568 auto.setDeviceManager(dm) |
|
569 options = parser.verifyRemoteOptions(options, auto) |
|
570 if (options == None): |
|
571 log.error("Invalid options specified, use --help for a list of valid options") |
|
572 sys.exit(1) |
|
573 |
|
574 productPieces = options.remoteProductName.split('.') |
|
575 if (productPieces != None): |
|
576 auto.setProduct(productPieces[0]) |
|
577 else: |
|
578 auto.setProduct(options.remoteProductName) |
|
579 auto.setAppName(options.remoteappname) |
|
580 |
|
581 mochitest = MochiRemote(auto, dm, options) |
|
582 |
|
583 options = parser.verifyOptions(options, mochitest) |
|
584 if (options == None): |
|
585 sys.exit(1) |
|
586 |
|
587 logParent = os.path.dirname(options.remoteLogFile) |
|
588 dm.mkDir(logParent); |
|
589 auto.setRemoteLog(options.remoteLogFile) |
|
590 auto.setServerInfo(options.webServer, options.httpPort, options.sslPort) |
|
591 |
|
592 mochitest.printDeviceInfo() |
|
593 |
|
594 # Add Android version (SDK level) to mozinfo so that manifest entries |
|
595 # can be conditional on android_version. |
|
596 androidVersion = dm.shellCheckOutput(['getprop', 'ro.build.version.sdk']) |
|
597 log.info("Android sdk version '%s'; will use this to filter manifests" % str(androidVersion)) |
|
598 mozinfo.info['android_version'] = androidVersion |
|
599 |
|
600 deviceRoot = dm.getDeviceRoot() |
|
601 if options.dmdPath: |
|
602 dmdLibrary = "libdmd.so" |
|
603 dmdPathOnDevice = os.path.join(deviceRoot, dmdLibrary) |
|
604 dm.removeFile(dmdPathOnDevice) |
|
605 dm.pushFile(os.path.join(options.dmdPath, dmdLibrary), dmdPathOnDevice) |
|
606 options.dmdPath = deviceRoot |
|
607 |
|
608 options.dumpOutputDirectory = deviceRoot |
|
609 |
|
610 procName = options.app.split('/')[-1] |
|
611 dm.killProcess(procName) |
|
612 |
|
613 if options.robocopIni != "": |
|
614 # sut may wait up to 300 s for a robocop am process before returning |
|
615 dm.default_timeout = 320 |
|
616 mp = manifestparser.TestManifest(strict=False) |
|
617 # TODO: pull this in dynamically |
|
618 mp.read(options.robocopIni) |
|
619 robocop_tests = mp.active_tests(exists=False, **mozinfo.info) |
|
620 tests = [] |
|
621 my_tests = tests |
|
622 for test in robocop_tests: |
|
623 tests.append(test['name']) |
|
624 |
|
625 if options.totalChunks: |
|
626 tests_per_chunk = math.ceil(len(tests) / (options.totalChunks * 1.0)) |
|
627 start = int(round((options.thisChunk-1) * tests_per_chunk)) |
|
628 end = int(round(options.thisChunk * tests_per_chunk)) |
|
629 if end > len(tests): |
|
630 end = len(tests) |
|
631 my_tests = tests[start:end] |
|
632 log.info("Running tests %d-%d/%d", start+1, end, len(tests)) |
|
633 |
|
634 dm.removeFile(os.path.join(deviceRoot, "fennec_ids.txt")) |
|
635 fennec_ids = os.path.abspath(os.path.join(SCRIPT_DIR, "fennec_ids.txt")) |
|
636 if not os.path.exists(fennec_ids) and options.robocopIds: |
|
637 fennec_ids = options.robocopIds |
|
638 dm.pushFile(fennec_ids, os.path.join(deviceRoot, "fennec_ids.txt")) |
|
639 options.extraPrefs.append('browser.search.suggest.enabled=true') |
|
640 options.extraPrefs.append('browser.search.suggest.prompted=true') |
|
641 options.extraPrefs.append('layout.css.devPixelsPerPx=1.0') |
|
642 options.extraPrefs.append('browser.chrome.dynamictoolbar=false') |
|
643 options.extraPrefs.append('browser.snippets.enabled=false') |
|
644 |
|
645 if (options.dm_trans == 'adb' and options.robocopApk): |
|
646 dm._checkCmd(["install", "-r", options.robocopApk]) |
|
647 |
|
648 retVal = None |
|
649 for test in robocop_tests: |
|
650 if options.testPath and options.testPath != test['name']: |
|
651 continue |
|
652 |
|
653 if not test['name'] in my_tests: |
|
654 continue |
|
655 |
|
656 if 'disabled' in test: |
|
657 log.info('TEST-INFO | skipping %s | %s' % (test['name'], test['disabled'])) |
|
658 continue |
|
659 |
|
660 # When running in a loop, we need to create a fresh profile for each cycle |
|
661 if mochitest.localProfile: |
|
662 options.profilePath = mochitest.localProfile |
|
663 os.system("rm -Rf %s" % options.profilePath) |
|
664 options.profilePath = tempfile.mkdtemp() |
|
665 mochitest.localProfile = options.profilePath |
|
666 |
|
667 options.app = "am" |
|
668 options.browserArgs = ["instrument", "-w", "-e", "deviceroot", deviceRoot, "-e", "class"] |
|
669 options.browserArgs.append("org.mozilla.gecko.tests.%s" % test['name']) |
|
670 options.browserArgs.append("org.mozilla.roboexample.test/org.mozilla.gecko.FennecInstrumentationTestRunner") |
|
671 |
|
672 # If the test is for checking the import from bookmarks then make sure there is data to import |
|
673 if test['name'] == "testImportFromAndroid": |
|
674 |
|
675 # Get the OS so we can run the insert in the apropriate database and following the correct table schema |
|
676 osInfo = dm.getInfo("os") |
|
677 devOS = " ".join(osInfo['os']) |
|
678 |
|
679 if ("pandaboard" in devOS): |
|
680 delete = ['execsu', 'sqlite3', "/data/data/com.android.browser/databases/browser2.db \'delete from bookmarks where _id > 14;\'"] |
|
681 else: |
|
682 delete = ['execsu', 'sqlite3', "/data/data/com.android.browser/databases/browser.db \'delete from bookmarks where _id > 14;\'"] |
|
683 if (options.dm_trans == "sut"): |
|
684 dm._runCmds([{"cmd": " ".join(delete)}]) |
|
685 |
|
686 # Insert the bookmarks |
|
687 log.info("Insert bookmarks in the default android browser database") |
|
688 for i in range(20): |
|
689 if ("pandaboard" in devOS): |
|
690 cmd = ['execsu', 'sqlite3', "/data/data/com.android.browser/databases/browser2.db 'insert or replace into bookmarks(_id,title,url,folder,parent,position) values (" + str(30 + i) + ",\"Bookmark"+ str(i) + "\",\"http://www.bookmark" + str(i) + ".com\",0,1," + str(100 + i) + ");'"] |
|
691 else: |
|
692 cmd = ['execsu', 'sqlite3', "/data/data/com.android.browser/databases/browser.db 'insert into bookmarks(title,url,bookmark) values (\"Bookmark"+ str(i) + "\",\"http://www.bookmark" + str(i) + ".com\",1);'"] |
|
693 if (options.dm_trans == "sut"): |
|
694 dm._runCmds([{"cmd": " ".join(cmd)}]) |
|
695 try: |
|
696 screenShotDir = "/mnt/sdcard/Robotium-Screenshots" |
|
697 dm.removeDir(screenShotDir) |
|
698 dm.recordLogcat() |
|
699 result = mochitest.runTests(options) |
|
700 if result != 0: |
|
701 log.error("runTests() exited with code %s", result) |
|
702 log_result = mochitest.addLogData() |
|
703 if result != 0 or log_result != 0: |
|
704 mochitest.printDeviceInfo(printLogcat=True) |
|
705 mochitest.printScreenshots(screenShotDir) |
|
706 # Ensure earlier failures aren't overwritten by success on this run |
|
707 if retVal is None or retVal == 0: |
|
708 retVal = result |
|
709 except: |
|
710 log.error("Automation Error: Exception caught while running tests") |
|
711 traceback.print_exc() |
|
712 mochitest.stopServers() |
|
713 try: |
|
714 mochitest.cleanup(None, options) |
|
715 except devicemanager.DMError: |
|
716 # device error cleaning up... oh well! |
|
717 pass |
|
718 retVal = 1 |
|
719 break |
|
720 finally: |
|
721 # Clean-up added bookmarks |
|
722 if test['name'] == "testImportFromAndroid": |
|
723 if ("pandaboard" in devOS): |
|
724 cmd_del = ['execsu', 'sqlite3', "/data/data/com.android.browser/databases/browser2.db \'delete from bookmarks where _id > 14;\'"] |
|
725 else: |
|
726 cmd_del = ['execsu', 'sqlite3', "/data/data/com.android.browser/databases/browser.db \'delete from bookmarks where _id > 14;\'"] |
|
727 if (options.dm_trans == "sut"): |
|
728 dm._runCmds([{"cmd": " ".join(cmd_del)}]) |
|
729 if retVal is None: |
|
730 log.warn("No tests run. Did you pass an invalid TEST_PATH?") |
|
731 retVal = 1 |
|
732 else: |
|
733 # if we didn't have some kind of error running the tests, make |
|
734 # sure the tests actually passed |
|
735 print "INFO | runtests.py | Test summary: start." |
|
736 overallResult = mochitest.printLog() |
|
737 print "INFO | runtests.py | Test summary: end." |
|
738 if retVal == 0: |
|
739 retVal = overallResult |
|
740 else: |
|
741 try: |
|
742 dm.recordLogcat() |
|
743 retVal = mochitest.runTests(options) |
|
744 except: |
|
745 log.error("Automation Error: Exception caught while running tests") |
|
746 traceback.print_exc() |
|
747 mochitest.stopServers() |
|
748 try: |
|
749 mochitest.cleanup(None, options) |
|
750 except devicemanager.DMError: |
|
751 # device error cleaning up... oh well! |
|
752 pass |
|
753 retVal = 1 |
|
754 |
|
755 mochitest.printDeviceInfo(printLogcat=True) |
|
756 |
|
757 sys.exit(retVal) |
|
758 |
|
759 if __name__ == "__main__": |
|
760 main() |