summaryrefslogtreecommitdiffstats
path: root/build/automation.py.in
diff options
context:
space:
mode:
Diffstat (limited to 'build/automation.py.in')
-rw-r--r--build/automation.py.in609
1 files changed, 609 insertions, 0 deletions
diff --git a/build/automation.py.in b/build/automation.py.in
new file mode 100644
index 000000000..1c63977e8
--- /dev/null
+++ b/build/automation.py.in
@@ -0,0 +1,609 @@
+#
+# This Source Code Form is subject to the terms of the Mozilla Public
+# License, v. 2.0. If a copy of the MPL was not distributed with this
+# file, You can obtain one at http://mozilla.org/MPL/2.0/.
+
+from __future__ import with_statement
+import logging
+import os
+import re
+import select
+import signal
+import subprocess
+import sys
+import tempfile
+from datetime import datetime, timedelta
+
+SCRIPT_DIR = os.path.abspath(os.path.realpath(os.path.dirname(sys.argv[0])))
+sys.path.insert(0, SCRIPT_DIR)
+
+# --------------------------------------------------------------
+# TODO: this is a hack for mozbase without virtualenv, remove with bug 849900
+# These paths refer to relative locations to test.zip, not the OBJDIR or SRCDIR
+here = os.path.dirname(os.path.realpath(__file__))
+mozbase = os.path.realpath(os.path.join(os.path.dirname(here), 'mozbase'))
+
+if os.path.isdir(mozbase):
+ for package in os.listdir(mozbase):
+ package_path = os.path.join(mozbase, package)
+ if package_path not in sys.path:
+ sys.path.append(package_path)
+
+import mozcrash
+from mozscreenshot import printstatus, dump_screen
+
+
+# ---------------------------------------------------------------
+
+_DEFAULT_PREFERENCE_FILE = os.path.join(SCRIPT_DIR, 'prefs_general.js')
+_DEFAULT_APPS_FILE = os.path.join(SCRIPT_DIR, 'webapps_mochitest.json')
+
+_DEFAULT_WEB_SERVER = "127.0.0.1"
+_DEFAULT_HTTP_PORT = 8888
+_DEFAULT_SSL_PORT = 4443
+_DEFAULT_WEBSOCKET_PORT = 9988
+
+# from nsIPrincipal.idl
+_APP_STATUS_NOT_INSTALLED = 0
+_APP_STATUS_INSTALLED = 1
+_APP_STATUS_PRIVILEGED = 2
+_APP_STATUS_CERTIFIED = 3
+
+#expand _DIST_BIN = __XPC_BIN_PATH__
+#expand _IS_WIN32 = len("__WIN32__") != 0
+#expand _IS_MAC = __IS_MAC__ != 0
+#expand _IS_LINUX = __IS_LINUX__ != 0
+#ifdef IS_CYGWIN
+#expand _IS_CYGWIN = __IS_CYGWIN__ == 1
+#else
+_IS_CYGWIN = False
+#endif
+#expand _BIN_SUFFIX = __BIN_SUFFIX__
+
+#expand _DEFAULT_APP = "./" + __BROWSER_PATH__
+#expand _CERTS_SRC_DIR = __CERTS_SRC_DIR__
+#expand _IS_TEST_BUILD = __IS_TEST_BUILD__
+#expand _IS_DEBUG_BUILD = __IS_DEBUG_BUILD__
+#expand _CRASHREPORTER = __CRASHREPORTER__ == 1
+#expand _IS_ASAN = __IS_ASAN__ == 1
+
+
+if _IS_WIN32:
+ import ctypes, ctypes.wintypes, time, msvcrt
+else:
+ import errno
+
+def resetGlobalLog(log):
+ while _log.handlers:
+ _log.removeHandler(_log.handlers[0])
+ handler = logging.StreamHandler(log)
+ _log.setLevel(logging.INFO)
+ _log.addHandler(handler)
+
+# We use the logging system here primarily because it'll handle multiple
+# threads, which is needed to process the output of the server and application
+# processes simultaneously.
+_log = logging.getLogger()
+resetGlobalLog(sys.stdout)
+
+
+#################
+# PROFILE SETUP #
+#################
+
+class Automation(object):
+ """
+ Runs the browser from a script, and provides useful utilities
+ for setting up the browser environment.
+ """
+
+ DIST_BIN = _DIST_BIN
+ IS_WIN32 = _IS_WIN32
+ IS_MAC = _IS_MAC
+ IS_LINUX = _IS_LINUX
+ IS_CYGWIN = _IS_CYGWIN
+ BIN_SUFFIX = _BIN_SUFFIX
+
+ UNIXISH = not IS_WIN32 and not IS_MAC
+
+ DEFAULT_APP = _DEFAULT_APP
+ CERTS_SRC_DIR = _CERTS_SRC_DIR
+ IS_TEST_BUILD = _IS_TEST_BUILD
+ IS_DEBUG_BUILD = _IS_DEBUG_BUILD
+ CRASHREPORTER = _CRASHREPORTER
+ IS_ASAN = _IS_ASAN
+
+ # timeout, in seconds
+ DEFAULT_TIMEOUT = 60.0
+ DEFAULT_WEB_SERVER = _DEFAULT_WEB_SERVER
+ DEFAULT_HTTP_PORT = _DEFAULT_HTTP_PORT
+ DEFAULT_SSL_PORT = _DEFAULT_SSL_PORT
+ DEFAULT_WEBSOCKET_PORT = _DEFAULT_WEBSOCKET_PORT
+
+ def __init__(self):
+ self.log = _log
+ self.lastTestSeen = "automation.py"
+ self.haveDumpedScreen = False
+
+ def setServerInfo(self,
+ webServer = _DEFAULT_WEB_SERVER,
+ httpPort = _DEFAULT_HTTP_PORT,
+ sslPort = _DEFAULT_SSL_PORT,
+ webSocketPort = _DEFAULT_WEBSOCKET_PORT):
+ self.webServer = webServer
+ self.httpPort = httpPort
+ self.sslPort = sslPort
+ self.webSocketPort = webSocketPort
+
+ @property
+ def __all__(self):
+ return [
+ "UNIXISH",
+ "IS_WIN32",
+ "IS_MAC",
+ "log",
+ "runApp",
+ "Process",
+ "DIST_BIN",
+ "DEFAULT_APP",
+ "CERTS_SRC_DIR",
+ "environment",
+ "IS_TEST_BUILD",
+ "IS_DEBUG_BUILD",
+ "DEFAULT_TIMEOUT",
+ ]
+
+ class Process(subprocess.Popen):
+ """
+ Represents our view of a subprocess.
+ It adds a kill() method which allows it to be stopped explicitly.
+ """
+
+ def __init__(self,
+ args,
+ bufsize=0,
+ executable=None,
+ stdin=None,
+ stdout=None,
+ stderr=None,
+ preexec_fn=None,
+ close_fds=False,
+ shell=False,
+ cwd=None,
+ env=None,
+ universal_newlines=False,
+ startupinfo=None,
+ creationflags=0):
+ _log.info("INFO | automation.py | Launching: %s", subprocess.list2cmdline(args))
+ subprocess.Popen.__init__(self, args, bufsize, executable,
+ stdin, stdout, stderr,
+ preexec_fn, close_fds,
+ shell, cwd, env,
+ universal_newlines, startupinfo, creationflags)
+ self.log = _log
+
+ def kill(self):
+ if Automation().IS_WIN32:
+ import platform
+ pid = "%i" % self.pid
+ if platform.release() == "2000":
+ # Windows 2000 needs 'kill.exe' from the
+ #'Windows 2000 Resource Kit tools'. (See bug 475455.)
+ try:
+ subprocess.Popen(["kill", "-f", pid]).wait()
+ except:
+ self.log.info("TEST-UNEXPECTED-FAIL | automation.py | Missing 'kill' utility to kill process with pid=%s. Kill it manually!", pid)
+ else:
+ # Windows XP and later.
+ subprocess.Popen(["taskkill", "/F", "/PID", pid]).wait()
+ else:
+ os.kill(self.pid, signal.SIGKILL)
+
+ def environment(self, env=None, xrePath=None, crashreporter=True, debugger=False, dmdPath=None, lsanPath=None):
+ if xrePath == None:
+ xrePath = self.DIST_BIN
+ if env == None:
+ env = dict(os.environ)
+
+ ldLibraryPath = os.path.abspath(os.path.join(SCRIPT_DIR, xrePath))
+ dmdLibrary = None
+ preloadEnvVar = None
+ if self.UNIXISH or self.IS_MAC:
+ envVar = "LD_LIBRARY_PATH"
+ preloadEnvVar = "LD_PRELOAD"
+ if self.IS_MAC:
+ envVar = "DYLD_LIBRARY_PATH"
+ dmdLibrary = "libdmd.dylib"
+ else: # unixish
+ env['MOZILLA_FIVE_HOME'] = xrePath
+ dmdLibrary = "libdmd.so"
+ if envVar in env:
+ ldLibraryPath = ldLibraryPath + ":" + env[envVar]
+ env[envVar] = ldLibraryPath
+ elif self.IS_WIN32:
+ env["PATH"] = env["PATH"] + ";" + str(ldLibraryPath)
+ dmdLibrary = "dmd.dll"
+ preloadEnvVar = "MOZ_REPLACE_MALLOC_LIB"
+
+ if dmdPath and dmdLibrary and preloadEnvVar:
+ env[preloadEnvVar] = os.path.join(dmdPath, dmdLibrary)
+
+ if crashreporter and not debugger:
+ env['MOZ_CRASHREPORTER_NO_REPORT'] = '1'
+ env['MOZ_CRASHREPORTER'] = '1'
+ else:
+ env['MOZ_CRASHREPORTER_DISABLE'] = '1'
+
+ # Crash on non-local network connections by default.
+ # MOZ_DISABLE_NONLOCAL_CONNECTIONS can be set to "0" to temporarily
+ # enable non-local connections for the purposes of local testing. Don't
+ # override the user's choice here. See bug 1049688.
+ env.setdefault('MOZ_DISABLE_NONLOCAL_CONNECTIONS', '1')
+
+ env['GNOME_DISABLE_CRASH_DIALOG'] = '1'
+ env['XRE_NO_WINDOWS_CRASH_DIALOG'] = '1'
+
+ # Set WebRTC logging in case it is not set yet
+ env.setdefault('MOZ_LOG', 'signaling:3,mtransport:4,DataChannel:4,jsep:4,MediaPipelineFactory:4')
+ env.setdefault('R_LOG_LEVEL', '6')
+ env.setdefault('R_LOG_DESTINATION', 'stderr')
+ env.setdefault('R_LOG_VERBOSE', '1')
+
+ # ASan specific environment stuff
+ if self.IS_ASAN and (self.IS_LINUX or self.IS_MAC):
+ # Symbolizer support
+ llvmsym = os.path.join(xrePath, "llvm-symbolizer")
+ if os.path.isfile(llvmsym):
+ env["ASAN_SYMBOLIZER_PATH"] = llvmsym
+ self.log.info("INFO | automation.py | ASan using symbolizer at %s", llvmsym)
+ else:
+ self.log.info("TEST-UNEXPECTED-FAIL | automation.py | Failed to find ASan symbolizer at %s", llvmsym)
+
+ try:
+ totalMemory = int(os.popen("free").readlines()[1].split()[1])
+
+ # Only 4 GB RAM or less available? Use custom ASan options to reduce
+ # the amount of resources required to do the tests. Standard options
+ # will otherwise lead to OOM conditions on the current test slaves.
+ if totalMemory <= 1024 * 1024 * 4:
+ self.log.info("INFO | automation.py | ASan running in low-memory configuration")
+ env["ASAN_OPTIONS"] = "quarantine_size=50331648:malloc_context_size=5"
+ else:
+ self.log.info("INFO | automation.py | ASan running in default memory configuration")
+ except OSError,err:
+ self.log.info("Failed determine available memory, disabling ASan low-memory configuration: %s", err.strerror)
+ except:
+ self.log.info("Failed determine available memory, disabling ASan low-memory configuration")
+
+ return env
+
+ def killPid(self, pid):
+ try:
+ os.kill(pid, getattr(signal, "SIGKILL", signal.SIGTERM))
+ except WindowsError:
+ self.log.info("Failed to kill process %d." % pid)
+
+ if IS_WIN32:
+ PeekNamedPipe = ctypes.windll.kernel32.PeekNamedPipe
+ GetLastError = ctypes.windll.kernel32.GetLastError
+
+ def readWithTimeout(self, f, timeout):
+ """
+ Try to read a line of output from the file object |f|. |f| must be a
+ pipe, like the |stdout| member of a subprocess.Popen object created
+ with stdout=PIPE. Returns a tuple (line, did_timeout), where |did_timeout|
+ is True if the read timed out, and False otherwise. If no output is
+ received within |timeout| seconds, returns a blank line.
+ """
+
+ if timeout is None:
+ timeout = 0
+
+ x = msvcrt.get_osfhandle(f.fileno())
+ l = ctypes.c_long()
+ done = time.time() + timeout
+
+ buffer = ""
+ while timeout == 0 or time.time() < done:
+ if self.PeekNamedPipe(x, None, 0, None, ctypes.byref(l), None) == 0:
+ err = self.GetLastError()
+ if err == 38 or err == 109: # ERROR_HANDLE_EOF || ERROR_BROKEN_PIPE
+ return ('', False)
+ else:
+ self.log.error("readWithTimeout got error: %d", err)
+ # read a character at a time, checking for eol. Return once we get there.
+ index = 0
+ while index < l.value:
+ char = f.read(1)
+ buffer += char
+ if char == '\n':
+ return (buffer, False)
+ index = index + 1
+ time.sleep(0.01)
+ return (buffer, True)
+
+ def isPidAlive(self, pid):
+ STILL_ACTIVE = 259
+ PROCESS_QUERY_LIMITED_INFORMATION = 0x1000
+ pHandle = ctypes.windll.kernel32.OpenProcess(PROCESS_QUERY_LIMITED_INFORMATION, 0, pid)
+ if not pHandle:
+ return False
+ pExitCode = ctypes.wintypes.DWORD()
+ ctypes.windll.kernel32.GetExitCodeProcess(pHandle, ctypes.byref(pExitCode))
+ ctypes.windll.kernel32.CloseHandle(pHandle)
+ return pExitCode.value == STILL_ACTIVE
+
+ else:
+
+ def readWithTimeout(self, f, timeout):
+ """Try to read a line of output from the file object |f|. If no output
+ is received within |timeout| seconds, return a blank line.
+ Returns a tuple (line, did_timeout), where |did_timeout| is True
+ if the read timed out, and False otherwise."""
+ (r, w, e) = select.select([f], [], [], timeout)
+ if len(r) == 0:
+ return ('', True)
+ return (f.readline(), False)
+
+ def isPidAlive(self, pid):
+ try:
+ # kill(pid, 0) checks for a valid PID without actually sending a signal
+ # The method throws OSError if the PID is invalid, which we catch below.
+ os.kill(pid, 0)
+
+ # Wait on it to see if it's a zombie. This can throw OSError.ECHILD if
+ # the process terminates before we get to this point.
+ wpid, wstatus = os.waitpid(pid, os.WNOHANG)
+ return wpid == 0
+ except OSError, err:
+ # Catch the errors we might expect from os.kill/os.waitpid,
+ # and re-raise any others
+ if err.errno == errno.ESRCH or err.errno == errno.ECHILD:
+ return False
+ raise
+
+ def dumpScreen(self, utilityPath):
+ if self.haveDumpedScreen:
+ self.log.info("Not taking screenshot here: see the one that was previously logged")
+ return
+
+ self.haveDumpedScreen = True;
+ dump_screen(utilityPath, self.log)
+
+
+ def killAndGetStack(self, processPID, utilityPath, debuggerInfo):
+ """Kill the process, preferrably in a way that gets us a stack trace.
+ Also attempts to obtain a screenshot before killing the process."""
+ if not debuggerInfo:
+ self.dumpScreen(utilityPath)
+ self.killAndGetStackNoScreenshot(processPID, utilityPath, debuggerInfo)
+
+ def killAndGetStackNoScreenshot(self, processPID, utilityPath, debuggerInfo):
+ """Kill the process, preferrably in a way that gets us a stack trace."""
+ if self.CRASHREPORTER and not debuggerInfo:
+ if not self.IS_WIN32:
+ # ABRT will get picked up by Breakpad's signal handler
+ os.kill(processPID, signal.SIGABRT)
+ return
+ else:
+ # We should have a "crashinject" program in our utility path
+ crashinject = os.path.normpath(os.path.join(utilityPath, "crashinject.exe"))
+ if os.path.exists(crashinject):
+ status = subprocess.Popen([crashinject, str(processPID)]).wait()
+ printstatus("crashinject", status)
+ if status == 0:
+ return
+ self.log.info("Can't trigger Breakpad, just killing process")
+ self.killPid(processPID)
+
+ def waitForFinish(self, proc, utilityPath, timeout, maxTime, startTime, debuggerInfo, symbolsPath, outputHandler=None):
+ """ Look for timeout or crashes and return the status after the process terminates """
+ stackFixerFunction = None
+ didTimeout = False
+ hitMaxTime = False
+ if proc.stdout is None:
+ self.log.info("TEST-INFO: Not logging stdout or stderr due to debugger connection")
+ else:
+ logsource = proc.stdout
+
+ if self.IS_DEBUG_BUILD and symbolsPath and os.path.exists(symbolsPath):
+ # Run each line through a function in fix_stack_using_bpsyms.py (uses breakpad symbol files)
+ # This method is preferred for Tinderbox builds, since native symbols may have been stripped.
+ sys.path.insert(0, utilityPath)
+ import fix_stack_using_bpsyms as stackFixerModule
+ stackFixerFunction = lambda line: stackFixerModule.fixSymbols(line, symbolsPath)
+ del sys.path[0]
+ elif self.IS_DEBUG_BUILD and self.IS_MAC:
+ # Run each line through a function in fix_macosx_stack.py (uses atos)
+ sys.path.insert(0, utilityPath)
+ import fix_macosx_stack as stackFixerModule
+ stackFixerFunction = lambda line: stackFixerModule.fixSymbols(line)
+ del sys.path[0]
+ elif self.IS_DEBUG_BUILD and self.IS_LINUX:
+ # Run each line through a function in fix_linux_stack.py (uses addr2line)
+ # This method is preferred for developer machines, so we don't have to run "make buildsymbols".
+ sys.path.insert(0, utilityPath)
+ import fix_linux_stack as stackFixerModule
+ stackFixerFunction = lambda line: stackFixerModule.fixSymbols(line)
+ del sys.path[0]
+
+ # With metro browser runs this script launches the metro test harness which launches the browser.
+ # The metro test harness hands back the real browser process id via log output which we need to
+ # pick up on and parse out. This variable tracks the real browser process id if we find it.
+ browserProcessId = -1
+
+ (line, didTimeout) = self.readWithTimeout(logsource, timeout)
+ while line != "" and not didTimeout:
+ if stackFixerFunction:
+ line = stackFixerFunction(line)
+
+ if outputHandler is None:
+ self.log.info(line.rstrip().decode("UTF-8", "ignore"))
+ else:
+ outputHandler(line)
+
+ if "TEST-START" in line and "|" in line:
+ self.lastTestSeen = line.split("|")[1].strip()
+ if not debuggerInfo and "TEST-UNEXPECTED-FAIL" in line and "Test timed out" in line:
+ self.dumpScreen(utilityPath)
+
+ (line, didTimeout) = self.readWithTimeout(logsource, timeout)
+
+ if not hitMaxTime and maxTime and datetime.now() - startTime > timedelta(seconds = maxTime):
+ # Kill the application.
+ hitMaxTime = True
+ self.log.info("TEST-UNEXPECTED-FAIL | %s | application ran for longer than allowed maximum time of %d seconds", self.lastTestSeen, int(maxTime))
+ self.log.error("Force-terminating active process(es).");
+ self.killAndGetStack(proc.pid, utilityPath, debuggerInfo)
+ if didTimeout:
+ if line:
+ self.log.info(line.rstrip().decode("UTF-8", "ignore"))
+ self.log.info("TEST-UNEXPECTED-FAIL | %s | application timed out after %d seconds with no output", self.lastTestSeen, int(timeout))
+ self.log.error("Force-terminating active process(es).");
+ if browserProcessId == -1:
+ browserProcessId = proc.pid
+ self.killAndGetStack(browserProcessId, utilityPath, debuggerInfo)
+
+ status = proc.wait()
+ printstatus("Main app process", status)
+ if status == 0:
+ self.lastTestSeen = "Main app process exited normally"
+ if status != 0 and not didTimeout and not hitMaxTime:
+ self.log.info("TEST-UNEXPECTED-FAIL | %s | Exited with code %d during test run", self.lastTestSeen, status)
+ return status
+
+ def buildCommandLine(self, app, debuggerInfo, profileDir, testURL, extraArgs):
+ """ build the application command line """
+
+ cmd = os.path.abspath(app)
+ if self.IS_MAC and os.path.exists(cmd + "-bin"):
+ # Prefer 'app-bin' in case 'app' is a shell script.
+ # We can remove this hack once bug 673899 etc are fixed.
+ cmd += "-bin"
+
+ args = []
+
+ if debuggerInfo:
+ args.extend(debuggerInfo.args)
+ args.append(cmd)
+ cmd = os.path.abspath(debuggerInfo.path)
+
+ if self.IS_MAC:
+ args.append("-foreground")
+
+ if self.IS_CYGWIN:
+ profileDirectory = commands.getoutput("cygpath -w \"" + profileDir + "/\"")
+ else:
+ profileDirectory = profileDir + "/"
+
+ args.extend(("-no-remote", "-profile", profileDirectory))
+ if testURL is not None:
+ args.append((testURL))
+ args.extend(extraArgs)
+ return cmd, args
+
+ def checkForZombies(self, processLog, utilityPath, debuggerInfo):
+ """ Look for hung processes """
+ if not os.path.exists(processLog):
+ self.log.info('Automation Error: PID log not found: %s', processLog)
+ # Whilst no hung process was found, the run should still display as a failure
+ return True
+
+ foundZombie = False
+ self.log.info('INFO | zombiecheck | Reading PID log: %s', processLog)
+ processList = []
+ pidRE = re.compile(r'launched child process (\d+)$')
+ processLogFD = open(processLog)
+ for line in processLogFD:
+ self.log.info(line.rstrip())
+ m = pidRE.search(line)
+ if m:
+ processList.append(int(m.group(1)))
+ processLogFD.close()
+
+ for processPID in processList:
+ self.log.info("INFO | zombiecheck | Checking for orphan process with PID: %d", processPID)
+ if self.isPidAlive(processPID):
+ foundZombie = True
+ self.log.info("TEST-UNEXPECTED-FAIL | zombiecheck | child process %d still alive after shutdown", processPID)
+ self.killAndGetStack(processPID, utilityPath, debuggerInfo)
+ return foundZombie
+
+ def checkForCrashes(self, minidumpDir, symbolsPath):
+ return mozcrash.check_for_crashes(minidumpDir, symbolsPath, test_name=self.lastTestSeen)
+
+ def runApp(self, testURL, env, app, profileDir, extraArgs, utilityPath = None,
+ xrePath = None, certPath = None,
+ debuggerInfo = None, symbolsPath = None,
+ timeout = -1, maxTime = None, onLaunch = None,
+ detectShutdownLeaks = False, screenshotOnFail=False, testPath=None, bisectChunk=None,
+ valgrindPath=None, valgrindArgs=None, valgrindSuppFiles=None, outputHandler=None):
+ """
+ Run the app, log the duration it took to execute, return the status code.
+ Kills the app if it runs for longer than |maxTime| seconds, or outputs nothing for |timeout| seconds.
+ """
+
+ if utilityPath == None:
+ utilityPath = self.DIST_BIN
+ if xrePath == None:
+ xrePath = self.DIST_BIN
+ if certPath == None:
+ certPath = self.CERTS_SRC_DIR
+ if timeout == -1:
+ timeout = self.DEFAULT_TIMEOUT
+
+ # copy env so we don't munge the caller's environment
+ env = dict(env);
+ env["NO_EM_RESTART"] = "1"
+ tmpfd, processLog = tempfile.mkstemp(suffix='pidlog')
+ os.close(tmpfd)
+ env["MOZ_PROCESS_LOG"] = processLog
+
+
+ cmd, args = self.buildCommandLine(app, debuggerInfo, profileDir, testURL, extraArgs)
+ startTime = datetime.now()
+
+ if debuggerInfo and debuggerInfo.interactive:
+ # If an interactive debugger is attached, don't redirect output,
+ # don't use timeouts, and don't capture ctrl-c.
+ timeout = None
+ maxTime = None
+ outputPipe = None
+ signal.signal(signal.SIGINT, lambda sigid, frame: None)
+ else:
+ outputPipe = subprocess.PIPE
+
+ self.lastTestSeen = "automation.py"
+ proc = self.Process([cmd] + args,
+ env = self.environment(env, xrePath = xrePath,
+ crashreporter = not debuggerInfo),
+ stdout = outputPipe,
+ stderr = subprocess.STDOUT)
+ self.log.info("INFO | automation.py | Application pid: %d", proc.pid)
+
+ if onLaunch is not None:
+ # Allow callers to specify an onLaunch callback to be fired after the
+ # app is launched.
+ onLaunch()
+
+ status = self.waitForFinish(proc, utilityPath, timeout, maxTime, startTime, debuggerInfo, symbolsPath,
+ outputHandler=outputHandler)
+ self.log.info("INFO | automation.py | Application ran for: %s", str(datetime.now() - startTime))
+
+ # Do a final check for zombie child processes.
+ zombieProcesses = self.checkForZombies(processLog, utilityPath, debuggerInfo)
+
+ crashed = self.checkForCrashes(os.path.join(profileDir, "minidumps"), symbolsPath)
+
+ if crashed or zombieProcesses:
+ status = 1
+
+ if os.path.exists(processLog):
+ os.unlink(processLog)
+
+ return status
+
+ def elf_arm(self, filename):
+ data = open(filename, 'rb').read(20)
+ return data[:4] == "\x7fELF" and ord(data[18]) == 40 # EM_ARM
+