Wed, 31 Dec 2014 06:09:35 +0100
Cloned upstream origin tor-browser at tor-browser-31.3.0esr-4.5-1-build1
revision ID fc1c9ff7c1b2defdbc039f12214767608f46423f for hacking purpose.
michael@0 | 1 | #!/usr/bin/env python |
michael@0 | 2 | # |
michael@0 | 3 | # This Source Code Form is subject to the terms of the Mozilla Public |
michael@0 | 4 | # License, v. 2.0. If a copy of the MPL was not distributed with this |
michael@0 | 5 | # file, You can obtain one at http://mozilla.org/MPL/2.0/. |
michael@0 | 6 | |
michael@0 | 7 | import posixpath |
michael@0 | 8 | import sys, os |
michael@0 | 9 | import subprocess |
michael@0 | 10 | import runxpcshelltests as xpcshell |
michael@0 | 11 | import tempfile |
michael@0 | 12 | from automationutils import replaceBackSlashes |
michael@0 | 13 | from mozdevice import devicemanagerADB, devicemanagerSUT, devicemanager |
michael@0 | 14 | from zipfile import ZipFile |
michael@0 | 15 | import shutil |
michael@0 | 16 | import mozfile |
michael@0 | 17 | import mozinfo |
michael@0 | 18 | |
michael@0 | 19 | here = os.path.dirname(os.path.abspath(__file__)) |
michael@0 | 20 | |
michael@0 | 21 | def remoteJoin(path1, path2): |
michael@0 | 22 | return posixpath.join(path1, path2) |
michael@0 | 23 | |
michael@0 | 24 | class RemoteXPCShellTestThread(xpcshell.XPCShellTestThread): |
michael@0 | 25 | def __init__(self, *args, **kwargs): |
michael@0 | 26 | xpcshell.XPCShellTestThread.__init__(self, *args, **kwargs) |
michael@0 | 27 | |
michael@0 | 28 | # embed the mobile params from the harness into the TestThread |
michael@0 | 29 | mobileArgs = kwargs.get('mobileArgs') |
michael@0 | 30 | for key in mobileArgs: |
michael@0 | 31 | setattr(self, key, mobileArgs[key]) |
michael@0 | 32 | |
michael@0 | 33 | def buildCmdTestFile(self, name): |
michael@0 | 34 | remoteDir = self.remoteForLocal(os.path.dirname(name)) |
michael@0 | 35 | if remoteDir == self.remoteHere: |
michael@0 | 36 | remoteName = os.path.basename(name) |
michael@0 | 37 | else: |
michael@0 | 38 | remoteName = remoteJoin(remoteDir, os.path.basename(name)) |
michael@0 | 39 | return ['-e', 'const _TEST_FILE = ["%s"];' % |
michael@0 | 40 | replaceBackSlashes(remoteName)] |
michael@0 | 41 | |
michael@0 | 42 | def remoteForLocal(self, local): |
michael@0 | 43 | for mapping in self.pathMapping: |
michael@0 | 44 | if (os.path.abspath(mapping.local) == os.path.abspath(local)): |
michael@0 | 45 | return mapping.remote |
michael@0 | 46 | return local |
michael@0 | 47 | |
michael@0 | 48 | |
michael@0 | 49 | def setupTempDir(self): |
michael@0 | 50 | # make sure the temp dir exists |
michael@0 | 51 | if not self.device.dirExists(self.remoteTmpDir): |
michael@0 | 52 | self.device.mkDir(self.remoteTmpDir) |
michael@0 | 53 | # env var is set in buildEnvironment |
michael@0 | 54 | return self.remoteTmpDir |
michael@0 | 55 | |
michael@0 | 56 | def setupPluginsDir(self): |
michael@0 | 57 | if not os.path.isdir(self.pluginsPath): |
michael@0 | 58 | return None |
michael@0 | 59 | |
michael@0 | 60 | # making sure tmp dir is set up |
michael@0 | 61 | self.setupTempDir() |
michael@0 | 62 | |
michael@0 | 63 | pluginsDir = remoteJoin(self.remoteTmpDir, "plugins") |
michael@0 | 64 | self.device.pushDir(self.pluginsPath, pluginsDir) |
michael@0 | 65 | if self.interactive: |
michael@0 | 66 | self.log.info("TEST-INFO | plugins dir is %s" % pluginsDir) |
michael@0 | 67 | return pluginsDir |
michael@0 | 68 | |
michael@0 | 69 | def setupProfileDir(self): |
michael@0 | 70 | self.device.removeDir(self.profileDir) |
michael@0 | 71 | self.device.mkDir(self.profileDir) |
michael@0 | 72 | if self.interactive or self.singleFile: |
michael@0 | 73 | self.log.info("TEST-INFO | profile dir is %s" % self.profileDir) |
michael@0 | 74 | return self.profileDir |
michael@0 | 75 | |
michael@0 | 76 | def logCommand(self, name, completeCmd, testdir): |
michael@0 | 77 | self.log.info("TEST-INFO | %s | full command: %r" % (name, completeCmd)) |
michael@0 | 78 | self.log.info("TEST-INFO | %s | current directory: %r" % (name, self.remoteHere)) |
michael@0 | 79 | self.log.info("TEST-INFO | %s | environment: %s" % (name, self.env)) |
michael@0 | 80 | |
michael@0 | 81 | def getHeadAndTailFiles(self, test): |
michael@0 | 82 | """Override parent method to find files on remote device.""" |
michael@0 | 83 | def sanitize_list(s, kind): |
michael@0 | 84 | for f in s.strip().split(' '): |
michael@0 | 85 | f = f.strip() |
michael@0 | 86 | if len(f) < 1: |
michael@0 | 87 | continue |
michael@0 | 88 | |
michael@0 | 89 | path = remoteJoin(self.remoteHere, f) |
michael@0 | 90 | if not self.device.fileExists(path): |
michael@0 | 91 | raise Exception('%s file does not exist: %s' % ( kind, |
michael@0 | 92 | path)) |
michael@0 | 93 | |
michael@0 | 94 | yield path |
michael@0 | 95 | |
michael@0 | 96 | self.remoteHere = self.remoteForLocal(test['here']) |
michael@0 | 97 | |
michael@0 | 98 | return (list(sanitize_list(test['head'], 'head')), |
michael@0 | 99 | list(sanitize_list(test['tail'], 'tail'))) |
michael@0 | 100 | |
michael@0 | 101 | def buildXpcsCmd(self, testdir): |
michael@0 | 102 | # change base class' paths to remote paths and use base class to build command |
michael@0 | 103 | self.xpcshell = remoteJoin(self.remoteBinDir, "xpcw") |
michael@0 | 104 | self.headJSPath = remoteJoin(self.remoteScriptsDir, 'head.js') |
michael@0 | 105 | self.httpdJSPath = remoteJoin(self.remoteComponentsDir, 'httpd.js') |
michael@0 | 106 | self.httpdManifest = remoteJoin(self.remoteComponentsDir, 'httpd.manifest') |
michael@0 | 107 | self.testingModulesDir = self.remoteModulesDir |
michael@0 | 108 | self.testharnessdir = self.remoteScriptsDir |
michael@0 | 109 | xpcshell.XPCShellTestThread.buildXpcsCmd(self, testdir) |
michael@0 | 110 | # remove "-g <dir> -a <dir>" and add "--greomni <apk>" |
michael@0 | 111 | del(self.xpcsCmd[1:5]) |
michael@0 | 112 | if self.options.localAPK: |
michael@0 | 113 | self.xpcsCmd.insert(3, '--greomni') |
michael@0 | 114 | self.xpcsCmd.insert(4, self.remoteAPK) |
michael@0 | 115 | |
michael@0 | 116 | if self.remoteDebugger: |
michael@0 | 117 | # for example, "/data/local/gdbserver" "localhost:12345" |
michael@0 | 118 | self.xpcsCmd = [ |
michael@0 | 119 | self.remoteDebugger, |
michael@0 | 120 | self.remoteDebuggerArgs, |
michael@0 | 121 | self.xpcsCmd] |
michael@0 | 122 | |
michael@0 | 123 | def testTimeout(self, test_file, proc): |
michael@0 | 124 | self.timedout = True |
michael@0 | 125 | if not self.retry: |
michael@0 | 126 | self.log.error("TEST-UNEXPECTED-FAIL | %s | Test timed out" % test_file) |
michael@0 | 127 | self.kill(proc) |
michael@0 | 128 | |
michael@0 | 129 | def launchProcess(self, cmd, stdout, stderr, env, cwd): |
michael@0 | 130 | self.timedout = False |
michael@0 | 131 | cmd.insert(1, self.remoteHere) |
michael@0 | 132 | outputFile = "xpcshelloutput" |
michael@0 | 133 | with open(outputFile, 'w+') as f: |
michael@0 | 134 | try: |
michael@0 | 135 | self.shellReturnCode = self.device.shell(cmd, f) |
michael@0 | 136 | except devicemanager.DMError as e: |
michael@0 | 137 | if self.timedout: |
michael@0 | 138 | # If the test timed out, there is a good chance the SUTagent also |
michael@0 | 139 | # timed out and failed to return a return code, generating a |
michael@0 | 140 | # DMError. Ignore the DMError to simplify the error report. |
michael@0 | 141 | self.shellReturnCode = None |
michael@0 | 142 | pass |
michael@0 | 143 | else: |
michael@0 | 144 | raise e |
michael@0 | 145 | # The device manager may have timed out waiting for xpcshell. |
michael@0 | 146 | # Guard against an accumulation of hung processes by killing |
michael@0 | 147 | # them here. Note also that IPC tests may spawn new instances |
michael@0 | 148 | # of xpcshell. |
michael@0 | 149 | self.device.killProcess(cmd[0]) |
michael@0 | 150 | self.device.killProcess("xpcshell") |
michael@0 | 151 | return outputFile |
michael@0 | 152 | |
michael@0 | 153 | def checkForCrashes(self, |
michael@0 | 154 | dump_directory, |
michael@0 | 155 | symbols_path, |
michael@0 | 156 | test_name=None): |
michael@0 | 157 | if not self.device.dirExists(self.remoteMinidumpDir): |
michael@0 | 158 | # The minidumps directory is automatically created when Fennec |
michael@0 | 159 | # (first) starts, so its lack of presence is a hint that |
michael@0 | 160 | # something went wrong. |
michael@0 | 161 | print "Automation Error: No crash directory (%s) found on remote device" % self.remoteMinidumpDir |
michael@0 | 162 | # Whilst no crash was found, the run should still display as a failure |
michael@0 | 163 | return True |
michael@0 | 164 | with mozfile.TemporaryDirectory() as dumpDir: |
michael@0 | 165 | self.device.getDirectory(self.remoteMinidumpDir, dumpDir) |
michael@0 | 166 | crashed = xpcshell.XPCShellTestThread.checkForCrashes(self, dumpDir, symbols_path, test_name) |
michael@0 | 167 | self.device.removeDir(self.remoteMinidumpDir) |
michael@0 | 168 | self.device.mkDir(self.remoteMinidumpDir) |
michael@0 | 169 | return crashed |
michael@0 | 170 | |
michael@0 | 171 | def communicate(self, proc): |
michael@0 | 172 | f = open(proc, "r") |
michael@0 | 173 | contents = f.read() |
michael@0 | 174 | f.close() |
michael@0 | 175 | os.remove(proc) |
michael@0 | 176 | return contents, "" |
michael@0 | 177 | |
michael@0 | 178 | def poll(self, proc): |
michael@0 | 179 | if self.device.processExist("xpcshell") is None: |
michael@0 | 180 | return self.getReturnCode(proc) |
michael@0 | 181 | # Process is still running |
michael@0 | 182 | return None |
michael@0 | 183 | |
michael@0 | 184 | def kill(self, proc): |
michael@0 | 185 | return self.device.killProcess("xpcshell", True) |
michael@0 | 186 | |
michael@0 | 187 | def getReturnCode(self, proc): |
michael@0 | 188 | if self.shellReturnCode is not None: |
michael@0 | 189 | return self.shellReturnCode |
michael@0 | 190 | else: |
michael@0 | 191 | return -1 |
michael@0 | 192 | |
michael@0 | 193 | def removeDir(self, dirname): |
michael@0 | 194 | self.device.removeDir(dirname) |
michael@0 | 195 | |
michael@0 | 196 | #TODO: consider creating a separate log dir. We don't have the test file structure, |
michael@0 | 197 | # so we use filename.log. Would rather see ./logs/filename.log |
michael@0 | 198 | def createLogFile(self, test, stdout): |
michael@0 | 199 | try: |
michael@0 | 200 | f = None |
michael@0 | 201 | filename = test.replace('\\', '/').split('/')[-1] + ".log" |
michael@0 | 202 | f = open(filename, "w") |
michael@0 | 203 | f.write(stdout) |
michael@0 | 204 | |
michael@0 | 205 | finally: |
michael@0 | 206 | if f is not None: |
michael@0 | 207 | f.close() |
michael@0 | 208 | |
michael@0 | 209 | |
michael@0 | 210 | # A specialization of XPCShellTests that runs tests on an Android device |
michael@0 | 211 | # via devicemanager. |
michael@0 | 212 | class XPCShellRemote(xpcshell.XPCShellTests, object): |
michael@0 | 213 | |
michael@0 | 214 | def __init__(self, devmgr, options, args, log=None): |
michael@0 | 215 | xpcshell.XPCShellTests.__init__(self, log) |
michael@0 | 216 | |
michael@0 | 217 | # Add Android version (SDK level) to mozinfo so that manifest entries |
michael@0 | 218 | # can be conditional on android_version. |
michael@0 | 219 | androidVersion = devmgr.shellCheckOutput(['getprop', 'ro.build.version.sdk']) |
michael@0 | 220 | mozinfo.info['android_version'] = androidVersion |
michael@0 | 221 | |
michael@0 | 222 | self.localLib = options.localLib |
michael@0 | 223 | self.localBin = options.localBin |
michael@0 | 224 | self.options = options |
michael@0 | 225 | self.device = devmgr |
michael@0 | 226 | self.pathMapping = [] |
michael@0 | 227 | self.remoteTestRoot = self.device.getTestRoot("xpcshell") |
michael@0 | 228 | # remoteBinDir contains xpcshell and its wrapper script, both of which must |
michael@0 | 229 | # be executable. Since +x permissions cannot usually be set on /mnt/sdcard, |
michael@0 | 230 | # and the test root may be on /mnt/sdcard, remoteBinDir is set to be on |
michael@0 | 231 | # /data/local, always. |
michael@0 | 232 | self.remoteBinDir = "/data/local/xpcb" |
michael@0 | 233 | # Terse directory names are used here ("c" for the components directory) |
michael@0 | 234 | # to minimize the length of the command line used to execute |
michael@0 | 235 | # xpcshell on the remote device. adb has a limit to the number |
michael@0 | 236 | # of characters used in a shell command, and the xpcshell command |
michael@0 | 237 | # line can be quite complex. |
michael@0 | 238 | self.remoteTmpDir = remoteJoin(self.remoteTestRoot, "tmp") |
michael@0 | 239 | self.remoteScriptsDir = self.remoteTestRoot |
michael@0 | 240 | self.remoteComponentsDir = remoteJoin(self.remoteTestRoot, "c") |
michael@0 | 241 | self.remoteModulesDir = remoteJoin(self.remoteTestRoot, "m") |
michael@0 | 242 | self.remoteMinidumpDir = remoteJoin(self.remoteTestRoot, "minidumps") |
michael@0 | 243 | self.profileDir = remoteJoin(self.remoteTestRoot, "p") |
michael@0 | 244 | self.remoteDebugger = options.debugger |
michael@0 | 245 | self.remoteDebuggerArgs = options.debuggerArgs |
michael@0 | 246 | self.testingModulesDir = options.testingModulesDir |
michael@0 | 247 | |
michael@0 | 248 | self.env = {} |
michael@0 | 249 | |
michael@0 | 250 | if self.options.objdir: |
michael@0 | 251 | self.xpcDir = os.path.join(self.options.objdir, "_tests/xpcshell") |
michael@0 | 252 | elif os.path.isdir(os.path.join(here, 'tests')): |
michael@0 | 253 | self.xpcDir = os.path.join(here, 'tests') |
michael@0 | 254 | else: |
michael@0 | 255 | print >> sys.stderr, "Couldn't find local xpcshell test directory" |
michael@0 | 256 | sys.exit(1) |
michael@0 | 257 | |
michael@0 | 258 | if options.localAPK: |
michael@0 | 259 | self.localAPKContents = ZipFile(options.localAPK) |
michael@0 | 260 | if options.setup: |
michael@0 | 261 | self.setupUtilities() |
michael@0 | 262 | self.setupModules() |
michael@0 | 263 | self.setupTestDir() |
michael@0 | 264 | self.setupMinidumpDir() |
michael@0 | 265 | self.remoteAPK = None |
michael@0 | 266 | if options.localAPK: |
michael@0 | 267 | self.remoteAPK = remoteJoin(self.remoteBinDir, os.path.basename(options.localAPK)) |
michael@0 | 268 | self.setAppRoot() |
michael@0 | 269 | |
michael@0 | 270 | # data that needs to be passed to the RemoteXPCShellTestThread |
michael@0 | 271 | self.mobileArgs = { |
michael@0 | 272 | 'device': self.device, |
michael@0 | 273 | 'remoteBinDir': self.remoteBinDir, |
michael@0 | 274 | 'remoteScriptsDir': self.remoteScriptsDir, |
michael@0 | 275 | 'remoteComponentsDir': self.remoteComponentsDir, |
michael@0 | 276 | 'remoteModulesDir': self.remoteModulesDir, |
michael@0 | 277 | 'options': self.options, |
michael@0 | 278 | 'remoteDebugger': self.remoteDebugger, |
michael@0 | 279 | 'pathMapping': self.pathMapping, |
michael@0 | 280 | 'profileDir': self.profileDir, |
michael@0 | 281 | 'remoteTmpDir': self.remoteTmpDir, |
michael@0 | 282 | 'remoteMinidumpDir': self.remoteMinidumpDir, |
michael@0 | 283 | } |
michael@0 | 284 | if self.remoteAPK: |
michael@0 | 285 | self.mobileArgs['remoteAPK'] = self.remoteAPK |
michael@0 | 286 | |
michael@0 | 287 | def setLD_LIBRARY_PATH(self): |
michael@0 | 288 | self.env["LD_LIBRARY_PATH"] = self.remoteBinDir |
michael@0 | 289 | |
michael@0 | 290 | def pushWrapper(self): |
michael@0 | 291 | # Rather than executing xpcshell directly, this wrapper script is |
michael@0 | 292 | # used. By setting environment variables and the cwd in the script, |
michael@0 | 293 | # the length of the per-test command line is shortened. This is |
michael@0 | 294 | # often important when using ADB, as there is a limit to the length |
michael@0 | 295 | # of the ADB command line. |
michael@0 | 296 | localWrapper = tempfile.mktemp() |
michael@0 | 297 | f = open(localWrapper, "w") |
michael@0 | 298 | f.write("#!/system/bin/sh\n") |
michael@0 | 299 | for envkey, envval in self.env.iteritems(): |
michael@0 | 300 | f.write("export %s=%s\n" % (envkey, envval)) |
michael@0 | 301 | f.write("cd $1\n") |
michael@0 | 302 | f.write("echo xpcw: cd $1\n") |
michael@0 | 303 | f.write("shift\n") |
michael@0 | 304 | f.write("echo xpcw: xpcshell \"$@\"\n") |
michael@0 | 305 | f.write("%s/xpcshell \"$@\"\n" % self.remoteBinDir) |
michael@0 | 306 | f.close() |
michael@0 | 307 | remoteWrapper = remoteJoin(self.remoteBinDir, "xpcw") |
michael@0 | 308 | self.device.pushFile(localWrapper, remoteWrapper) |
michael@0 | 309 | os.remove(localWrapper) |
michael@0 | 310 | self.device.chmodDir(self.remoteBinDir) |
michael@0 | 311 | |
michael@0 | 312 | def buildEnvironment(self): |
michael@0 | 313 | self.buildCoreEnvironment() |
michael@0 | 314 | self.setLD_LIBRARY_PATH() |
michael@0 | 315 | self.env["MOZ_LINKER_CACHE"] = self.remoteBinDir |
michael@0 | 316 | if self.options.localAPK and self.appRoot: |
michael@0 | 317 | self.env["GRE_HOME"] = self.appRoot |
michael@0 | 318 | self.env["XPCSHELL_TEST_PROFILE_DIR"] = self.profileDir |
michael@0 | 319 | self.env["TMPDIR"] = self.remoteTmpDir |
michael@0 | 320 | self.env["HOME"] = self.profileDir |
michael@0 | 321 | self.env["XPCSHELL_TEST_TEMP_DIR"] = self.remoteTmpDir |
michael@0 | 322 | self.env["XPCSHELL_MINIDUMP_DIR"] = self.remoteMinidumpDir |
michael@0 | 323 | if self.options.setup: |
michael@0 | 324 | self.pushWrapper() |
michael@0 | 325 | |
michael@0 | 326 | def setAppRoot(self): |
michael@0 | 327 | # Determine the application root directory associated with the package |
michael@0 | 328 | # name used by the Fennec APK. |
michael@0 | 329 | self.appRoot = None |
michael@0 | 330 | packageName = None |
michael@0 | 331 | if self.options.localAPK: |
michael@0 | 332 | try: |
michael@0 | 333 | packageName = self.localAPKContents.read("package-name.txt") |
michael@0 | 334 | if packageName: |
michael@0 | 335 | self.appRoot = self.device.getAppRoot(packageName.strip()) |
michael@0 | 336 | except Exception as detail: |
michael@0 | 337 | print "unable to determine app root: " + str(detail) |
michael@0 | 338 | pass |
michael@0 | 339 | return None |
michael@0 | 340 | |
michael@0 | 341 | def setupUtilities(self): |
michael@0 | 342 | if (not self.device.dirExists(self.remoteBinDir)): |
michael@0 | 343 | # device.mkDir may fail here where shellCheckOutput may succeed -- see bug 817235 |
michael@0 | 344 | try: |
michael@0 | 345 | self.device.shellCheckOutput(["mkdir", self.remoteBinDir]); |
michael@0 | 346 | except devicemanager.DMError: |
michael@0 | 347 | # Might get a permission error; try again as root, if available |
michael@0 | 348 | self.device.shellCheckOutput(["mkdir", self.remoteBinDir], root=True); |
michael@0 | 349 | self.device.shellCheckOutput(["chmod", "777", self.remoteBinDir], root=True); |
michael@0 | 350 | |
michael@0 | 351 | remotePrefDir = remoteJoin(self.remoteBinDir, "defaults/pref") |
michael@0 | 352 | if (self.device.dirExists(self.remoteTmpDir)): |
michael@0 | 353 | self.device.removeDir(self.remoteTmpDir) |
michael@0 | 354 | self.device.mkDir(self.remoteTmpDir) |
michael@0 | 355 | if (not self.device.dirExists(remotePrefDir)): |
michael@0 | 356 | self.device.mkDirs(remoteJoin(remotePrefDir, "extra")) |
michael@0 | 357 | if (not self.device.dirExists(self.remoteScriptsDir)): |
michael@0 | 358 | self.device.mkDir(self.remoteScriptsDir) |
michael@0 | 359 | if (not self.device.dirExists(self.remoteComponentsDir)): |
michael@0 | 360 | self.device.mkDir(self.remoteComponentsDir) |
michael@0 | 361 | |
michael@0 | 362 | local = os.path.join(os.path.dirname(os.path.abspath(__file__)), 'head.js') |
michael@0 | 363 | remoteFile = remoteJoin(self.remoteScriptsDir, "head.js") |
michael@0 | 364 | self.device.pushFile(local, remoteFile) |
michael@0 | 365 | |
michael@0 | 366 | local = os.path.join(self.localBin, "xpcshell") |
michael@0 | 367 | remoteFile = remoteJoin(self.remoteBinDir, "xpcshell") |
michael@0 | 368 | self.device.pushFile(local, remoteFile) |
michael@0 | 369 | |
michael@0 | 370 | local = os.path.join(self.localBin, "components/httpd.js") |
michael@0 | 371 | remoteFile = remoteJoin(self.remoteComponentsDir, "httpd.js") |
michael@0 | 372 | self.device.pushFile(local, remoteFile) |
michael@0 | 373 | |
michael@0 | 374 | local = os.path.join(self.localBin, "components/httpd.manifest") |
michael@0 | 375 | remoteFile = remoteJoin(self.remoteComponentsDir, "httpd.manifest") |
michael@0 | 376 | self.device.pushFile(local, remoteFile) |
michael@0 | 377 | |
michael@0 | 378 | local = os.path.join(self.localBin, "components/test_necko.xpt") |
michael@0 | 379 | remoteFile = remoteJoin(self.remoteComponentsDir, "test_necko.xpt") |
michael@0 | 380 | self.device.pushFile(local, remoteFile) |
michael@0 | 381 | |
michael@0 | 382 | if self.options.localAPK: |
michael@0 | 383 | remoteFile = remoteJoin(self.remoteBinDir, os.path.basename(self.options.localAPK)) |
michael@0 | 384 | self.device.pushFile(self.options.localAPK, remoteFile) |
michael@0 | 385 | |
michael@0 | 386 | self.pushLibs() |
michael@0 | 387 | |
michael@0 | 388 | def pushLibs(self): |
michael@0 | 389 | pushed_libs_count = 0 |
michael@0 | 390 | if self.options.localAPK: |
michael@0 | 391 | try: |
michael@0 | 392 | dir = tempfile.mkdtemp() |
michael@0 | 393 | szip = os.path.join(self.localBin, '..', 'host', 'bin', 'szip') |
michael@0 | 394 | if not os.path.exists(szip): |
michael@0 | 395 | # Tinderbox builds must run szip from the test package |
michael@0 | 396 | szip = os.path.join(self.localBin, 'host', 'szip') |
michael@0 | 397 | if not os.path.exists(szip): |
michael@0 | 398 | # If the test package doesn't contain szip, it means files |
michael@0 | 399 | # are not szipped in the test package. |
michael@0 | 400 | szip = None |
michael@0 | 401 | for info in self.localAPKContents.infolist(): |
michael@0 | 402 | if info.filename.endswith(".so"): |
michael@0 | 403 | print >> sys.stderr, "Pushing %s.." % info.filename |
michael@0 | 404 | remoteFile = remoteJoin(self.remoteBinDir, os.path.basename(info.filename)) |
michael@0 | 405 | self.localAPKContents.extract(info, dir) |
michael@0 | 406 | file = os.path.join(dir, info.filename) |
michael@0 | 407 | if szip: |
michael@0 | 408 | out = subprocess.check_output([szip, '-d', file], stderr=subprocess.STDOUT) |
michael@0 | 409 | self.device.pushFile(os.path.join(dir, info.filename), remoteFile) |
michael@0 | 410 | pushed_libs_count += 1 |
michael@0 | 411 | finally: |
michael@0 | 412 | shutil.rmtree(dir) |
michael@0 | 413 | return pushed_libs_count |
michael@0 | 414 | |
michael@0 | 415 | for file in os.listdir(self.localLib): |
michael@0 | 416 | if (file.endswith(".so")): |
michael@0 | 417 | print >> sys.stderr, "Pushing %s.." % file |
michael@0 | 418 | if 'libxul' in file: |
michael@0 | 419 | print >> sys.stderr, "This is a big file, it could take a while." |
michael@0 | 420 | remoteFile = remoteJoin(self.remoteBinDir, file) |
michael@0 | 421 | self.device.pushFile(os.path.join(self.localLib, file), remoteFile) |
michael@0 | 422 | pushed_libs_count += 1 |
michael@0 | 423 | |
michael@0 | 424 | # Additional libraries may be found in a sub-directory such as "lib/armeabi-v7a" |
michael@0 | 425 | localArmLib = os.path.join(self.localLib, "lib") |
michael@0 | 426 | if os.path.exists(localArmLib): |
michael@0 | 427 | for root, dirs, files in os.walk(localArmLib): |
michael@0 | 428 | for file in files: |
michael@0 | 429 | if (file.endswith(".so")): |
michael@0 | 430 | print >> sys.stderr, "Pushing %s.." % file |
michael@0 | 431 | remoteFile = remoteJoin(self.remoteBinDir, file) |
michael@0 | 432 | self.device.pushFile(os.path.join(root, file), remoteFile) |
michael@0 | 433 | pushed_libs_count += 1 |
michael@0 | 434 | |
michael@0 | 435 | return pushed_libs_count |
michael@0 | 436 | |
michael@0 | 437 | def setupModules(self): |
michael@0 | 438 | if self.testingModulesDir: |
michael@0 | 439 | self.device.pushDir(self.testingModulesDir, self.remoteModulesDir) |
michael@0 | 440 | |
michael@0 | 441 | def setupTestDir(self): |
michael@0 | 442 | print 'pushing %s' % self.xpcDir |
michael@0 | 443 | try: |
michael@0 | 444 | self.device.pushDir(self.xpcDir, self.remoteScriptsDir, retryLimit=10) |
michael@0 | 445 | except TypeError: |
michael@0 | 446 | # Foopies have an older mozdevice ver without retryLimit |
michael@0 | 447 | self.device.pushDir(self.xpcDir, self.remoteScriptsDir) |
michael@0 | 448 | |
michael@0 | 449 | def setupMinidumpDir(self): |
michael@0 | 450 | if self.device.dirExists(self.remoteMinidumpDir): |
michael@0 | 451 | self.device.removeDir(self.remoteMinidumpDir) |
michael@0 | 452 | self.device.mkDir(self.remoteMinidumpDir) |
michael@0 | 453 | |
michael@0 | 454 | def buildTestList(self): |
michael@0 | 455 | xpcshell.XPCShellTests.buildTestList(self) |
michael@0 | 456 | uniqueTestPaths = set([]) |
michael@0 | 457 | for test in self.alltests: |
michael@0 | 458 | uniqueTestPaths.add(test['here']) |
michael@0 | 459 | for testdir in uniqueTestPaths: |
michael@0 | 460 | abbrevTestDir = os.path.relpath(testdir, self.xpcDir) |
michael@0 | 461 | remoteScriptDir = remoteJoin(self.remoteScriptsDir, abbrevTestDir) |
michael@0 | 462 | self.pathMapping.append(PathMapping(testdir, remoteScriptDir)) |
michael@0 | 463 | |
michael@0 | 464 | class RemoteXPCShellOptions(xpcshell.XPCShellOptions): |
michael@0 | 465 | |
michael@0 | 466 | def __init__(self): |
michael@0 | 467 | xpcshell.XPCShellOptions.__init__(self) |
michael@0 | 468 | defaults = {} |
michael@0 | 469 | |
michael@0 | 470 | self.add_option("--deviceIP", action="store", |
michael@0 | 471 | type = "string", dest = "deviceIP", |
michael@0 | 472 | help = "ip address of remote device to test") |
michael@0 | 473 | defaults["deviceIP"] = None |
michael@0 | 474 | |
michael@0 | 475 | self.add_option("--devicePort", action="store", |
michael@0 | 476 | type = "string", dest = "devicePort", |
michael@0 | 477 | help = "port of remote device to test") |
michael@0 | 478 | defaults["devicePort"] = 20701 |
michael@0 | 479 | |
michael@0 | 480 | self.add_option("--dm_trans", action="store", |
michael@0 | 481 | type = "string", dest = "dm_trans", |
michael@0 | 482 | help = "the transport to use to communicate with device: [adb|sut]; default=sut") |
michael@0 | 483 | defaults["dm_trans"] = "sut" |
michael@0 | 484 | |
michael@0 | 485 | self.add_option("--objdir", action="store", |
michael@0 | 486 | type = "string", dest = "objdir", |
michael@0 | 487 | help = "local objdir, containing xpcshell binaries") |
michael@0 | 488 | defaults["objdir"] = None |
michael@0 | 489 | |
michael@0 | 490 | self.add_option("--apk", action="store", |
michael@0 | 491 | type = "string", dest = "localAPK", |
michael@0 | 492 | help = "local path to Fennec APK") |
michael@0 | 493 | defaults["localAPK"] = None |
michael@0 | 494 | |
michael@0 | 495 | self.add_option("--noSetup", action="store_false", |
michael@0 | 496 | dest = "setup", |
michael@0 | 497 | help = "do not copy any files to device (to be used only if device is already setup)") |
michael@0 | 498 | defaults["setup"] = True |
michael@0 | 499 | |
michael@0 | 500 | self.add_option("--local-lib-dir", action="store", |
michael@0 | 501 | type = "string", dest = "localLib", |
michael@0 | 502 | help = "local path to library directory") |
michael@0 | 503 | defaults["localLib"] = None |
michael@0 | 504 | |
michael@0 | 505 | self.add_option("--local-bin-dir", action="store", |
michael@0 | 506 | type = "string", dest = "localBin", |
michael@0 | 507 | help = "local path to bin directory") |
michael@0 | 508 | defaults["localBin"] = None |
michael@0 | 509 | |
michael@0 | 510 | self.add_option("--remoteTestRoot", action = "store", |
michael@0 | 511 | type = "string", dest = "remoteTestRoot", |
michael@0 | 512 | help = "remote directory to use as test root (eg. /mnt/sdcard/tests or /data/local/tests)") |
michael@0 | 513 | defaults["remoteTestRoot"] = None |
michael@0 | 514 | |
michael@0 | 515 | self.set_defaults(**defaults) |
michael@0 | 516 | |
michael@0 | 517 | def verifyRemoteOptions(self, options): |
michael@0 | 518 | if options.localLib is None: |
michael@0 | 519 | if options.localAPK and options.objdir: |
michael@0 | 520 | for path in ['dist/fennec', 'fennec/lib']: |
michael@0 | 521 | options.localLib = os.path.join(options.objdir, path) |
michael@0 | 522 | if os.path.isdir(options.localLib): |
michael@0 | 523 | break |
michael@0 | 524 | else: |
michael@0 | 525 | self.error("Couldn't find local library dir, specify --local-lib-dir") |
michael@0 | 526 | elif options.objdir: |
michael@0 | 527 | options.localLib = os.path.join(options.objdir, 'dist/bin') |
michael@0 | 528 | elif os.path.isfile(os.path.join(here, '..', 'bin', 'xpcshell')): |
michael@0 | 529 | # assume tests are being run from a tests.zip |
michael@0 | 530 | options.localLib = os.path.abspath(os.path.join(here, '..', 'bin')) |
michael@0 | 531 | else: |
michael@0 | 532 | self.error("Couldn't find local library dir, specify --local-lib-dir") |
michael@0 | 533 | |
michael@0 | 534 | if options.localBin is None: |
michael@0 | 535 | if options.objdir: |
michael@0 | 536 | for path in ['dist/bin', 'bin']: |
michael@0 | 537 | options.localBin = os.path.join(options.objdir, path) |
michael@0 | 538 | if os.path.isdir(options.localBin): |
michael@0 | 539 | break |
michael@0 | 540 | else: |
michael@0 | 541 | self.error("Couldn't find local binary dir, specify --local-bin-dir") |
michael@0 | 542 | elif os.path.isfile(os.path.join(here, '..', 'bin', 'xpcshell')): |
michael@0 | 543 | # assume tests are being run from a tests.zip |
michael@0 | 544 | options.localBin = os.path.abspath(os.path.join(here, '..', 'bin')) |
michael@0 | 545 | else: |
michael@0 | 546 | self.error("Couldn't find local binary dir, specify --local-bin-dir") |
michael@0 | 547 | return options |
michael@0 | 548 | |
michael@0 | 549 | class PathMapping: |
michael@0 | 550 | |
michael@0 | 551 | def __init__(self, localDir, remoteDir): |
michael@0 | 552 | self.local = localDir |
michael@0 | 553 | self.remote = remoteDir |
michael@0 | 554 | |
michael@0 | 555 | def main(): |
michael@0 | 556 | |
michael@0 | 557 | if sys.version_info < (2,7): |
michael@0 | 558 | print >>sys.stderr, "Error: You must use python version 2.7 or newer but less than 3.0" |
michael@0 | 559 | sys.exit(1) |
michael@0 | 560 | |
michael@0 | 561 | parser = RemoteXPCShellOptions() |
michael@0 | 562 | options, args = parser.parse_args() |
michael@0 | 563 | if not options.localAPK: |
michael@0 | 564 | for file in os.listdir(os.path.join(options.objdir, "dist")): |
michael@0 | 565 | if (file.endswith(".apk") and file.startswith("fennec")): |
michael@0 | 566 | options.localAPK = os.path.join(options.objdir, "dist") |
michael@0 | 567 | options.localAPK = os.path.join(options.localAPK, file) |
michael@0 | 568 | print >>sys.stderr, "using APK: " + options.localAPK |
michael@0 | 569 | break |
michael@0 | 570 | else: |
michael@0 | 571 | print >>sys.stderr, "Error: please specify an APK" |
michael@0 | 572 | sys.exit(1) |
michael@0 | 573 | |
michael@0 | 574 | options = parser.verifyRemoteOptions(options) |
michael@0 | 575 | |
michael@0 | 576 | if len(args) < 1 and options.manifest is None: |
michael@0 | 577 | print >>sys.stderr, """Usage: %s <test dirs> |
michael@0 | 578 | or: %s --manifest=test.manifest """ % (sys.argv[0], sys.argv[0]) |
michael@0 | 579 | sys.exit(1) |
michael@0 | 580 | |
michael@0 | 581 | if (options.dm_trans == "adb"): |
michael@0 | 582 | if (options.deviceIP): |
michael@0 | 583 | dm = devicemanagerADB.DeviceManagerADB(options.deviceIP, options.devicePort, packageName=None, deviceRoot=options.remoteTestRoot) |
michael@0 | 584 | else: |
michael@0 | 585 | dm = devicemanagerADB.DeviceManagerADB(packageName=None, deviceRoot=options.remoteTestRoot) |
michael@0 | 586 | else: |
michael@0 | 587 | dm = devicemanagerSUT.DeviceManagerSUT(options.deviceIP, options.devicePort, deviceRoot=options.remoteTestRoot) |
michael@0 | 588 | if (options.deviceIP == None): |
michael@0 | 589 | print "Error: you must provide a device IP to connect to via the --device option" |
michael@0 | 590 | sys.exit(1) |
michael@0 | 591 | |
michael@0 | 592 | if options.interactive and not options.testPath: |
michael@0 | 593 | print >>sys.stderr, "Error: You must specify a test filename in interactive mode!" |
michael@0 | 594 | sys.exit(1) |
michael@0 | 595 | |
michael@0 | 596 | xpcsh = XPCShellRemote(dm, options, args) |
michael@0 | 597 | |
michael@0 | 598 | # we don't run concurrent tests on mobile |
michael@0 | 599 | options.sequential = True |
michael@0 | 600 | |
michael@0 | 601 | if not xpcsh.runTests(xpcshell='xpcshell', |
michael@0 | 602 | testClass=RemoteXPCShellTestThread, |
michael@0 | 603 | testdirs=args[0:], |
michael@0 | 604 | mobileArgs=xpcsh.mobileArgs, |
michael@0 | 605 | **options.__dict__): |
michael@0 | 606 | sys.exit(1) |
michael@0 | 607 | |
michael@0 | 608 | |
michael@0 | 609 | if __name__ == '__main__': |
michael@0 | 610 | main() |