diff --git a/testing/mozbase/mozb2g/mozb2g/__init__.py b/testing/mozbase/mozb2g/mozb2g/__init__.py new file mode 100644 index 00000000000..21b67778913 --- /dev/null +++ b/testing/mozbase/mozb2g/mozb2g/__init__.py @@ -0,0 +1,5 @@ +# 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 b2gmixin import DeviceADB, DeviceSUT diff --git a/testing/mozbase/mozb2g/mozb2g/b2gmixin.py b/testing/mozbase/mozb2g/mozb2g/b2gmixin.py new file mode 100644 index 00000000000..b17a320e91f --- /dev/null +++ b/testing/mozbase/mozb2g/mozb2g/b2gmixin.py @@ -0,0 +1,171 @@ +# 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 datetime +import os +import re +import tempfile +import time +import shutil +import socket +import subprocess + +from marionette import Marionette +from mozdevice import DeviceManagerADB, DeviceManagerSUT, DMError + +class B2GMixin(object): + profileDir = None + userJS = "/data/local/user.js" + marionette = None + + def __init__(self, host=None, marionette_port=2828, **kwargs): + self.marionetteHost = host + self.marionettePort = marionette_port + + def cleanup(self): + if self.profileDir: + self.restoreProfile() + + def waitForPort(self, timeout): + """ + Wait for the marionette server to respond. + Timeout parameter is in seconds + """ + print "waiting for port" + starttime = datetime.datetime.now() + while datetime.datetime.now() - starttime < datetime.timedelta(seconds=timeout): + try: + sock = socket.socket(socket.AF_INET, socket.SOCK_STREAM) + print "trying %s %s" % (self.marionettePort, self.marionetteHost) + sock.connect((self.marionetteHost, self.marionettePort)) + data = sock.recv(16) + sock.close() + if '"from"' in data: + return True + except socket.error: + pass + except Exception as e: + raise DMError("Could not connect to marionette: %s" % e) + time.sleep(1) + raise DMError("Could not communicate with Marionette port") + + def setupMarionette(self): + """ + Start a marionette session. + If no host is given, then this will get the ip + of the device, and set up networking if needed. + """ + if not self.marionetteHost: + self.setupDHCP() + self.marionetteHost = self.getIP() + if not self.marionette: + self.marionette = Marionette(self.marionetteHost, self.marionettePort) + if not self.marionette.session: + self.waitForPort(30) + self.marionette.start_session() + + def restartB2G(self): + """ + Restarts the b2g process on the device + """ + #restart b2g so we start with a clean slate + if self.marionette and self.marionette.session: + self.marionette.delete_session() + self.shellCheckOutput(['stop', 'b2g']) + # Wait for a bit to make sure B2G has completely shut down. + tries = 10 + while "b2g" in self.shellCheckOutput(['ps', 'b2g']) and tries > 0: + tries -= 1 + time.sleep(1) + if tries == 0: + raise DMError("Could not kill b2g process") + self.shellCheckOutput(['start', 'b2g']) + + def setupProfile(self, prefs=None): + """ + Sets up the user profile on the device, + The 'prefs' is a string of user_prefs to add to the profile. + If it is not set, it will default to a standard b2g testing profile. + """ + if not prefs: + prefs = """ +user_pref("power.screen.timeout", 999999); +user_pref("devtools.debugger.force-local", false); + """ + #remove previous user.js if there is one + if not self.profileDir: + self.profileDir = tempfile.mkdtemp() + our_userJS = os.path.join(self.profileDir, "user.js") + if os.path.exists(our_userJS): + os.remove(our_userJS) + #copy profile + try: + output = self.getFile(self.userJS, our_userJS) + except subprocess.CalledProcessError: + pass + #if we successfully copied the profile, make a backup of the file + if os.path.exists(our_userJS): + self.shellCheckOutput(['dd', 'if=%s' % self.userJS, 'of=%s.orig' % self.userJS]) + with open(our_userJS, 'a') as user_file: + user_file.write("%s" % prefs) + self.pushFile(our_userJS, self.userJS) + self.restartB2G() + self.setupMarionette() + + def setupDHCP(self, conn_type='eth0'): + """ + Sets up networking. + + If conn_type is not set, it will assume eth0. + """ + tries = 5 + while tries > 0: + print "attempts left: %d" % tries + try: + self.shellCheckOutput(['netcfg', conn_type, 'dhcp'], timeout=10) + if self.getIP(): + return + except DMError: + pass + tries = tries - 1 + raise DMError("Could not set up network connection") + + def restoreProfile(self): + """ + Restores the original profile + """ + if not self.profileDir: + raise DMError("There is no profile to restore") + #if we successfully copied the profile, make a backup of the file + our_userJS = os.path.join(self.profileDir, "user.js") + if os.path.exists(our_userJS): + self.shellCheckOutput(['dd', 'if=%s.orig' % self.userJS, 'of=%s' % self.userJS]) + shutil.rmtree(self.profileDir) + self.profileDir = None + + def getAppInfo(self): + """ + Returns the appinfo, with an additional "date" key. + """ + if not self.marionette or not self.marionette.session: + self.setupMarionette() + self.marionette.set_context("chrome") + appinfo = self.marionette.execute_script(""" + var appInfo = Components.classes["@mozilla.org/xre/app-info;1"] + .getService(Components.interfaces.nsIXULAppInfo); + return appInfo; + """) + (year, month, day) = (appinfo["appBuildID"][0:4], appinfo["appBuildID"][4:6], appinfo["appBuildID"][6:8]) + appinfo['date'] = "%s-%s-%s" % (year, month, day) + return appinfo + +class DeviceADB(DeviceManagerADB, B2GMixin): + def __init__(self, **kwargs): + DeviceManagerADB.__init__(self, **kwargs) + B2GMixin.__init__(self, **kwargs) + +class DeviceSUT(DeviceManagerSUT, B2GMixin): + def __init__(self, **kwargs): + DeviceManagerSUT.__init__(self, **kwargs) + B2GMixin.__init__(self, **kwargs) diff --git a/testing/mozbase/mozb2g/setup.py b/testing/mozbase/mozb2g/setup.py new file mode 100644 index 00000000000..80155312cb5 --- /dev/null +++ b/testing/mozbase/mozb2g/setup.py @@ -0,0 +1,33 @@ +# 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/. + +import os +from setuptools import setup + +PACKAGE_VERSION = '0.1' + +# take description from README +here = os.path.dirname(os.path.abspath(__file__)) +try: + description = file(os.path.join(here, 'README.md')).read() +except (OSError, IOError): + description = '' + +deps = ['mozdevice', 'marionette_client'] + +setup(name='mozb2g', + version=PACKAGE_VERSION, + description="B2G specific code for device automation", + long_description=description, + classifiers=[], # Get strings from http://pypi.python.org/pypi?%3Aaction=list_classifiers + keywords='', + author='Mozilla Automation and Testing Team', + author_email='tools@lists.mozilla.org', + url='https://wiki.mozilla.org/Auto-tools/Projects/MozBase', + license='MPL', + packages=['mozb2g'], + include_package_data=True, + zip_safe=False, + install_requires=deps + ) diff --git a/testing/mozbase/mozdevice/mozdevice/devicemanagerADB.py b/testing/mozbase/mozdevice/mozdevice/devicemanagerADB.py index d5a257cc64a..46d2bed2422 100644 --- a/testing/mozbase/mozdevice/mozdevice/devicemanagerADB.py +++ b/testing/mozbase/mozdevice/mozdevice/devicemanagerADB.py @@ -762,6 +762,35 @@ class DeviceManagerADB(DeviceManager): return None return str(int(timestr)*1000) + def recordLogcat(self): + """ + Clears the logcat file making it easier to view specific events + """ + # this does not require root privileges with ADB + try: + self.shellCheckOutput(['/system/bin/logcat', '-c']) + except DMError, e: + print "DeviceManager: Error recording logcat '%s'" % e.msg + # to preserve compat with parent method, just ignore exceptions + pass + + def getLogcat(self): + """ + Returns the contents of the logcat file as a string + + returns: + success: contents of logcat, string + failure: None + """ + # this does not require root privileges with ADB + try: + output = self.shellCheckOutput(["/system/bin/logcat", "-d", "dalvikvm:S", "ConnectivityService:S", "WifiMonitor:S", "WifiStateTracker:S", "wpa_supplicant:S", "NetworkStateTracker:S"]) + return output.split('\r') + except DMError, e: + # to preserve compat with parent method, just ignore exceptions + print "DeviceManager: Error recording logcat '%s'" % e.msg + pass + def getInfo(self, directive=None): """ Returns information about the device: diff --git a/testing/mozbase/mozprocess/mozprocess/processhandler.py b/testing/mozbase/mozprocess/mozprocess/processhandler.py index 0f5e8cd37b7..85b34fa3bd0 100644 --- a/testing/mozbase/mozprocess/mozprocess/processhandler.py +++ b/testing/mozbase/mozprocess/mozprocess/processhandler.py @@ -290,7 +290,7 @@ falling back to not using job objects for managing child processes""" if MOZPROCESS_DEBUG: print "DBG::MOZPROC Self.pid value is: %s" % self.pid - + while True: msgid = c_ulong(0) compkey = c_ulong(0) @@ -311,8 +311,9 @@ falling back to not using job objects for managing child processes""" # don't want to mistake that situation for the situation of an unexpected # parent abort (which is what we're looking for here). if diff.seconds > self.MAX_IOCOMPLETION_PORT_NOTIFICATION_DELAY: - print >> sys.stderr, "Parent process exited without \ - killing children, attempting to kill children" + print >> sys.stderr, "Parent process %s exited with children alive:" % self.pid + print >> sys.stderr, "PIDS: %s" % ', '.join(self._spawned_procs.keys()) + print >> sys.stderr, "Attempting to kill them..." self.kill() self._process_events.put({self.pid: 'FINISHED'})