testing/mochitest/runtestsremote.py

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

mercurial