def create_marionette(): """Returns current Marionette session, or creates one if one does not exist. """ m = TestCase.stored.marionette if m is None: m = Marionette() m.wait_for_port() m.start_session() TestCase.stored.marionette = m return TestCase.stored.marionette
class MtbfJobRunner(BaseActionRunner): serial = None marionette = None flash_params = { 'branch': 'mozilla-b2g34_v2_1-flame-kk-eng', 'build': '', 'build_id': '' } flashed = False def __init__(self, **kwargs): self.logger = logger BaseActionRunner.__init__(self) def setup(self): if not self.serial or not self.port: logger.error("Fail to get device") raise DMError self.config_raptor() self.marionette and self.marionette.session and self.marionette.cleanup( ) self.dm = mozdevice.DeviceManagerADB(deviceSerial=self.serial, port=self.port) self.marionette = Marionette(device_serial=self.serial, port=self.port) self.marionette.wait_for_port() self.marionette.start_session() self.device = GaiaDevice(marionette=self.marionette, manager=self.dm) self.apps = GaiaApps(self.marionette) self.data_layer = GaiaData(self.marionette) if self.flashed: self.device.wait_for_b2g_ready() def adb_test(self): if not hasattr(self, 'serial') or os.system("ANDROID_SERIAL=" + self.serial + " adb shell ls") != 0: logger.error("Device not found or can't be controlled") return False return True @action(enabled=False) def add_7mobile_action(self): # workaround for waiting for boot self.marionette.wait_for_port() self.marionette.start_session() self.data_layer = GaiaData(self.marionette) self.data_layer.set_setting('ril.data.apnSettings', [[{ "carrier": "(7-Mobile) (MMS)", "apn": "opentalk", "mmsc": "http://mms", "mmsproxy": "210.241.199.199", "mmsport": "9201", "types": ["mms"] }, { "carrier": "(7-Mobile) (Internet)", "apn": "opentalk", "types": ["default", "supl"] }]]) return True @action(enabled=False) def change_memory(self): # This function only work in flame # TODO: use native adb/fastboot command to change memory? # Make sure it's in fastboot mode, TODO: leverage all fastboot command in one task function memory = 512 # default set 512 if 'MEM' in os.environ: memory = os.environ['MEM'] elif self.settings['change_memory'][ 'enabled'] and 'memory' in self.settings['change_memory']: memory = self.settings['change_memory']['memory'] if self.adb_test(): os.system("adb reboot bootloader") memory = 512 mem_str = str(memory) os.system("fastboot oem mem " + mem_str) # Preventing from async timing of fastboot os.system("fastboot reboot") self.device_obj.create_adb_forward(self.port) return True logger.error("Can't find device") self.marionette.wait_for_port() self.device_obj.create_adb_forward(self.port) return False @action(enabled=False) def config_raptor(self): settings = self.settings if 'config_raptor' in settings and settings['config_raptor']['config']: with open(os.path.expandvars( settings['config_raptor']['config'])) as conf: self.raptor = json.load(conf) self.raptor['path'] = settings['config_raptor']['config'] self.raptor['monitorJobFolder'] = settings['config_raptor'][ 'monitorJobFolder'] @action(enabled=True) def collect_memory_report(self): zip_utils.collect_about_memory( "mtbf_driver") # TODO: give a correct path for about memory folder def get_free_device(self): do = device_pool.get_device(self.serial) if do: # Record device serial and store dp instance self.serial = do.serial self.device_obj = do if do.create_adb_forward(): self.port = do.adb_forwarded_port logger.info("Device found, ANDROID_SERIAL= " + self.serial) return do logger.error("Port forwarding failed") raise DMError logger.warning( "No available device. Please retry after device released") # TODO: more handling for no available device def validate_flash_params(self): ## Using system environment variable as temporary solution TODO: use other way for input params ## Check if package(files)/folder exists and return, else raise exception if not 'FLASH_BASEDIR' in os.environ: raise AttributeError("No FLASH_BASEDIR set") basedir = os.environ['FLASH_BASEDIR'] if not 'FLASH_BUILDID' in os.environ: ## TODO: if latest/ exists, use latest as default logging.info("No build id set. search in base dir") buildid = "" flash_dir = basedir else: buildid = os.environ['FLASH_BUILDID'] # re-format build id based on pvt folder structure if '-' in buildid: buildid = buildid.replace("-", "") year = buildid[:4] month = buildid[4:6] datetime = '-'.join( [year, month] + [buildid[i + 6:i + 8] for i in range(0, len(buildid[6:]), 2)]) flash_dir = os.path.join(basedir, year, month, datetime) if not os.path.isdir(flash_dir): raise AttributeError("Flash directory " + flash_dir + " not exist") flash_files = glob.glob(os.path.join(flash_dir, '*')) flash_src = {} for flash_file in flash_files: logger.debug("Flash source found: [" + flash_file + "]") if os.path.isdir(flash_file): continue elif re.match("^b2g-[0-9]*.*\.tar\.gz$", flash_file): flash_src['gecko'] = flash_file elif "gaia.zip" == flash_file: flash_src['gaia'] = flash_file elif "symbol" in flash_file: flash_src['symbol'] = flash_file elif "zip" in flash_file and not ("gaia.zip" in flash_file): flash_src['image'] = flash_file return flash_src @action(enabled=True) def full_flash(self): flash_src = self.validate_flash_params() if self.flashed: logger.warning("Flash performed; skip flashing") return True if not flash_src: logger.warning("Invalid build folder/build_id, skip flashing") return False if not 'image' in flash_src: logger.warning("No available image for flash, skip flashing") return False try: self.temp_dir = tempfile.mkdtemp() logger.info('Create temporary folder:' + self.temp_dir) Decompressor().unzip(flash_src['image'], self.temp_dir) # set the permissions to rwxrwxr-x (509 in python's os.chmod) os.chmod( self.temp_dir + '/b2g-distro/flash.sh', stat.S_IRUSR | stat.S_IWUSR | stat.S_IXUSR | stat.S_IRGRP | stat.S_IWGRP | stat.S_IXGRP | stat.S_IROTH | stat.S_IXOTH) os.chmod( self.temp_dir + '/b2g-distro/load-config.sh', stat.S_IRUSR | stat.S_IWUSR | stat.S_IXUSR | stat.S_IRGRP | stat.S_IWGRP | stat.S_IXGRP | stat.S_IROTH | stat.S_IXOTH) os.system('cd ' + self.temp_dir + '/b2g-distro; ./flash.sh -f') # support NO_FTU environment for skipping FTU (e.g. monkey test) if 'NO_FTU' in os.environ and os.environ['NO_FTU'] == 'true': logger.log('The [NO_FTU] is [true].') os.system( "ANDROID_SERIAL=" + self.serial + 'adb wait-for-device && adb shell stop b2g; (RET=$(adb root); if ! case ${RET} in *"cannot"*) true;; *) false;; esac; then adb remount && sleep 5; else exit 1; fi; ./disable_ftu.py) || (echo "No root permission, cannot setup NO_FTU."); adb reboot;' ) finally: try: shutil.rmtree(self.temp_dir) # delete directory except OSError: logger.error('Can not remove temporary folder:' + self.temp_dir, level=Logger._LEVEL_WARNING) self.flashed = True @action(enabled=False) def shallow_flash(self): flash_src = self.validate_flash_params() if self.flashed: logger.warning("Flash performed; skip flashing") return True if not flash_src: logger.warning("Invalid build folder/build_id, skip flashing") return False if not 'gaia' in flash_src or not 'gecko' in flash_src: logger.warning("No gaia or gecko archive, skip flashing") return False cmd = 'flash_tool/shallow_flash.sh -y --gecko="' + flash_src[ 'gecko'] + '" --gaia="' + flash_src['gaia'] + '"' if _platform == 'darwin': cmd = cmd.replace('=', ' ') ret = os.system(cmd) if ret != 0: logger.info("Shallow flash ended abnormally") return False self.flashed = True os.system("ANDROID_SERIAL=" + self.serial + " adb wait-for-device") @action(enabled=True) def enable_certified_apps_debug(self): if self.serial: os.system( "ANDROID_SERIAL=" + self.serial + " flash_tool/enable_certified_apps_for_devtools.sh && adb wait-for-device" ) logger.debug("Successfully enabling certified apps for debugging") return True return False def release(self): device_pool.release() def start_monitoring(self): job = { 'name': 'mtbf', 'type': 'moz_minions.kevin.MtbfToRaptorMinion', 'serial': self.serial, 'job_info': { 'pid': os.getpid(), 'program': sys.argv[0], } } if hasattr(self, 'raptor'): raptor = self.raptor job['job_info'].update(self.raptor) if "monitorJobFolder" in self.raptor: dirpath = os.path.expandvars(self.raptor['monitorJobFolder']) else: dirpath = "/tmp/mtbf" if not os.path.isdir(dirpath): os.makedirs(dirpath) timestamp = time.strftime('%Y-%m-%d-%H-%M-%S+0000', time.gmtime()) filename = job['name'] + "_" + timestamp + ".json" self.monitor_conf = os.path.join(dirpath, filename) job['job_info']['conf'] = self.monitor_conf with open(self.monitor_conf, 'w') as fh: fh.write(json.dumps(job, indent=2, sort_keys=True)) def stop_monitoring(self): if hasattr(self, 'raptor'): os.remove(self.monitor_conf) self.monitor_conf = None def check_version(self): # FIXME: fix check version to use package import cmd = "cd flash_tool/ && NO_COLOR=TRUE ./check_versions.py | sed -e 's| \{2,\}||g' -e 's|\[0m||g'" if self.serial: cmd = "ANDROID_SERIAL=" + self.serial + " " + cmd os.system(cmd) @action(enabled=False) def patch_marionette(self): os.system( "M_PATH=/mnt/mtbf_shared/paul/ /mnt/mtbf_shared/paul/marionette_update.sh" ) import time time.sleep(10) self.device_obj.create_adb_forward(self.port) def mtbf_options(self): ## load mtbf parameters if not 'MTBF_TIME' in os.environ: logger.warning("MTBF_TIME is not set") if not 'MTBF_CONF' in os.environ: logger.warning("MTBF_CONF is not set") parser = self.parser.parser parser.add_argument("--testvars", help="Test variables for b2g") self.parse_options() # FIXME: make rootdir of testvars could be customized mtbf_testvars_dir = "/mnt/mtbf_shared/testvars" if not hasattr(self.options, 'testvars') or not self.options.testvars: testvars = os.path.join(mtbf_testvars_dir, "testvars_" + self.serial + ".json") logger.info("testvar is [" + testvars + "]") if os.path.exists(testvars): self.options.testvars = parser.testvars = testvars logger.info("testvar [" + testvars + "] found") else: raise AttributeError("testvars[" + testvars + "] doesn't exist") def remove_settings_opt(self): for e in sys.argv[1:]: if '--settings' in e: idx = sys.argv.index(e) sys.argv.remove(e) if len(sys.argv) > idx and not '--' in sys.argv[idx]: del sys.argv[idx] break @action(enabled=False) def mtbf_daily(self): parser = GaiaTestArguments() opts = [] for k, v in self.kwargs.iteritems(): opts.append("--" + k) opts.append(v) options, tests = parser.parse_args(sys.argv[1:] + opts) structured.commandline.add_logging_group(parser) logger = structured.commandline.setup_logging(options.logger_name, options, {"tbpl": sys.stdout}) options.logger = logger options.testvars = [self.options.testvars] runner = GaiaTestRunner(**vars(options)) runner.run_tests(["tests"]) @action(enabled=True) def run_mtbf(self): mtbf.main(testvars=self.options.testvars, **self.kwargs) def execute(self): self.marionette.cleanup() self.marionette = Marionette(device_serial=self.serial, port=self.port) self.marionette.wait_for_port() # run test runner here self.remove_settings_opt() self.kwargs = {} if self.port: self.kwargs['address'] = "localhost:" + str(self.port) logger.info("Using address[localhost:" + str(self.port) + "]") self.start_monitoring() self.mtbf_daily() self.run_mtbf() self.stop_monitoring() def pre_flash(self): pass def flash(self): self.shallow_flash() self.full_flash() # workaround for waiting for boot def post_flash(self): self.setup() self.check_version() self.change_memory() self.add_7mobile_action() self.enable_certified_apps_debug() self.patch_marionette() def output_crash_report_no_to_log(self, serial): if serial in CrashScan.get_current_all_dev_serials(): crash_result = CrashScan.get_crash_no_by_serial(serial) if crash_result['crashNo'] > 0: logger.error("CrashReportFound: device " + serial + " has " + str(crash_result['crashNo']) + " crashes.") else: logger.info( "CrashReportNotFound: No crash report found in device " + serial) else: logger.error("CrashReportAdbError: Can't find device in ADB list") def collect_report(self, serial): self.output_crash_report_no_to_log(serial) def run(self): try: if self.get_free_device(): self.mtbf_options() self.pre_flash() self.flash() self.device_obj.create_adb_forward() self.port = self.device_obj.adb_forwarded_port self.post_flash() self.execute() self.collect_report(self.serial) finally: self.release()
class B2GMochitest(MochitestUtilsMixin): marionette = None def __init__(self, marionette_args, logger_options, out_of_process=True, profile_data_dir=None, locations=os.path.join(here, 'server-locations.txt')): super(B2GMochitest, self).__init__(logger_options) self.marionette_args = marionette_args self.out_of_process = out_of_process self.locations_file = locations self.preferences = [] self.webapps = None self.test_script = os.path.join(here, 'b2g_start_script.js') self.test_script_args = [self.out_of_process] self.product = 'b2g' self.remote_chrome_test_dir = None if profile_data_dir: self.preferences = [ os.path.join(profile_data_dir, f) for f in os.listdir(profile_data_dir) if f.startswith('pref') ] self.webapps = [ os.path.join(profile_data_dir, f) for f in os.listdir(profile_data_dir) if f.startswith('webapp') ] # mozinfo is populated by the parent class if mozinfo.info['debug']: self.SERVER_STARTUP_TIMEOUT = 180 else: self.SERVER_STARTUP_TIMEOUT = 90 def setup_common_options(self, options): test_url = self.buildTestPath(options) # For B2G emulators buildURLOptions has been called # without calling buildTestPath first and that # causes manifestFile not to be set if not "manifestFile=tests.json" in self.urlOpts: self.urlOpts.append("manifestFile=%s" % options.manifestFile) if len(self.urlOpts) > 0: test_url += "?" + "&".join(self.urlOpts) self.test_script_args.append(test_url) def buildTestPath(self, options, testsToFilter=None): if options.manifestFile != 'tests.json': super(B2GMochitest, self).buildTestPath(options, testsToFilter, disabled=False) return self.buildTestURL(options) def build_profile(self, options): # preferences prefs = {} for path in self.preferences: prefs.update(Preferences.read_prefs(path)) for v in options.extraPrefs: thispref = v.split("=", 1) if len(thispref) < 2: print "Error: syntax error in --setpref=" + v sys.exit(1) prefs[thispref[0]] = thispref[1] # interpolate the preferences interpolation = { "server": "%s:%s" % (options.webServer, options.httpPort), "OOP": "true" if self.out_of_process else "false" } prefs = json.loads(json.dumps(prefs) % interpolation) for pref in prefs: prefs[pref] = Preferences.cast(prefs[pref]) kwargs = { 'addons': self.getExtensionsToInstall(options), 'apps': self.webapps, 'locations': self.locations_file, 'preferences': prefs, 'proxy': { "remote": options.webServer } } if options.profile: self.profile = Profile.clone(options.profile, **kwargs) else: self.profile = Profile(**kwargs) options.profilePath = self.profile.profile # TODO bug 839108 - mozprofile should probably handle this manifest = self.addChromeToProfile(options) self.copyExtraFilesToProfile(options) return manifest def run_tests(self, options): """ Prepare, configure, run tests and cleanup """ self.setTestRoot(options) manifest = self.build_profile(options) self.logPreamble(self.getActiveTests(options)) # configuring the message logger's buffering self.message_logger.buffering = options.quiet if options.debugger or not options.autorun: timeout = None else: if not options.timeout: if mozinfo.info['debug']: options.timeout = 420 else: options.timeout = 300 timeout = options.timeout + 30.0 self.log.info("runtestsb2g.py | Running tests: start.") status = 0 try: def on_output(line): messages = self.message_logger.write(line) for message in messages: if message['action'] == 'test_start': self.runner.last_test = message['test'] # The logging will be handled by on_output, so we set the stream to # None process_args = {'processOutputLine': on_output, 'stream': None} self.marionette_args['process_args'] = process_args self.marionette_args['profile'] = self.profile self.marionette = Marionette(**self.marionette_args) self.runner = self.marionette.runner self.app_ctx = self.runner.app_ctx self.remote_log = posixpath.join(self.app_ctx.remote_test_root, 'log', 'mochitest.log') if not self.app_ctx.dm.dirExists(posixpath.dirname( self.remote_log)): self.app_ctx.dm.mkDirs(self.remote_log) if options.chrome: # Update chrome manifest file in profile with correct path. self.writeChromeManifest(options) self.leak_report_file = posixpath.join( self.app_ctx.remote_test_root, 'log', 'runtests_leaks.log') # We don't want to copy the host env onto the device, so pass in an # empty env. self.browserEnv = self.buildBrowserEnv(options, env={}) # B2G emulator debug tests still make external connections, so don't # pass MOZ_DISABLE_NONLOCAL_CONNECTIONS to them for now (bug # 1039019). if mozinfo.info[ 'debug'] and 'MOZ_DISABLE_NONLOCAL_CONNECTIONS' in self.browserEnv: del self.browserEnv['MOZ_DISABLE_NONLOCAL_CONNECTIONS'] self.runner.env.update(self.browserEnv) # Despite our efforts to clean up servers started by this script, in practice # we still see infrequent cases where a process is orphaned and interferes # with future tests, typically because the old server is keeping the port in use. # Try to avoid those failures by checking for and killing orphan servers before # trying to start new ones. self.killNamedOrphans('ssltunnel') self.killNamedOrphans('xpcshell') self.startServers(options, None) # In desktop mochitests buildTestPath is called before buildURLOptions. This # means options.manifestFile has already been converted to the proper json # style manifest. Not so with B2G, that conversion along with updating the URL # option will happen later. So backup and restore options.manifestFile to # prevent us from trying to pass in an instance of TestManifest via url param. manifestFile = options.manifestFile options.manifestFile = None self.buildURLOptions(options, {'MOZ_HIDE_RESULTS_TABLE': '1'}) options.manifestFile = manifestFile self.test_script_args.append(not options.emulator) self.test_script_args.append(options.wifi) self.test_script_args.append(options.chrome) self.runner.start(outputTimeout=timeout) self.marionette.wait_for_port() self.marionette.start_session() self.marionette.set_context(self.marionette.CONTEXT_CHROME) # Disable offline status management (bug 777145), otherwise the network # will be 'offline' when the mochitests start. Presumably, the network # won't be offline on a real device, so we only do this for # emulators. self.marionette.execute_script(""" Components.utils.import("resource://gre/modules/Services.jsm"); Services.io.manageOfflineStatus = false; Services.io.offline = false; """) self.marionette.execute_script(""" let SECURITY_PREF = "security.turn_off_all_security_so_that_viruses_can_take_over_this_computer"; Services.prefs.setBoolPref(SECURITY_PREF, true); if (!testUtils.hasOwnProperty("specialPowersObserver")) { let loader = Components.classes["@mozilla.org/moz/jssubscript-loader;1"] .getService(Components.interfaces.mozIJSSubScriptLoader); loader.loadSubScript("chrome://specialpowers/content/SpecialPowersObserver.js", testUtils); testUtils.specialPowersObserver = new testUtils.SpecialPowersObserver(); testUtils.specialPowersObserver.init(); testUtils.specialPowersObserver._loadFrameScript(); } """) if options.chrome: self.app_ctx.dm.removeDir(self.remote_chrome_test_dir) self.app_ctx.dm.mkDir(self.remote_chrome_test_dir) local = super(B2GMochitest, self).getChromeTestDir(options) local = os.path.join(local, "chrome") remote = self.remote_chrome_test_dir self.log.info("pushing %s to %s on device..." % (local, remote)) self.app_ctx.dm.pushDir(local, remote) if os.path.isfile(self.test_script): with open(self.test_script, 'r') as script: self.marionette.execute_script( script.read(), script_args=self.test_script_args) else: self.marionette.execute_script( self.test_script, script_args=self.test_script_args) status = self.runner.wait() if status is None: # the runner has timed out status = 124 local_leak_file = tempfile.NamedTemporaryFile() self.app_ctx.dm.getFile(self.leak_report_file, local_leak_file.name) self.app_ctx.dm.removeFile(self.leak_report_file) mozleak.process_leak_log( local_leak_file.name, leak_thresholds=options.leakThresholds, ignore_missing_leaks=options.ignoreMissingLeaks, log=self.log, ) except KeyboardInterrupt: self.log.info("runtests.py | Received keyboard interrupt.\n") status = -1 except: traceback.print_exc() self.log.error( "Automation Error: Received unexpected exception while running application\n" ) if hasattr(self, 'runner'): self.runner.check_for_crashes() status = 1 self.stopServers() self.log.info("runtestsb2g.py | Running tests: end.") if manifest is not None: self.cleanup(manifest, options) return status def getGMPPluginPath(self, options): if options.gmp_path: return options.gmp_path return '/system/b2g/gmp-clearkey/0.1' def getChromeTestDir(self, options): # The chrome test directory returned here is the remote location # of chrome test files. A reference to this directory is requested # when building the profile locally, before self.app_ctx is defined. # To get around this, return a dummy directory until self.app_ctx # is defined; the correct directory will be returned later, over- # writing the dummy. if hasattr(self, 'app_ctx'): self.remote_chrome_test_dir = posixpath.join( self.app_ctx.remote_test_root, 'chrome') return self.remote_chrome_test_dir return 'dummy-chrome-test-dir'
class B2GMochitest(MochitestUtilsMixin): marionette = None def __init__(self, marionette_args, logger_options, out_of_process=True, profile_data_dir=None, locations=os.path.join(here, 'server-locations.txt')): super(B2GMochitest, self).__init__(logger_options) self.marionette_args = marionette_args self.out_of_process = out_of_process self.locations_file = locations self.preferences = [] self.webapps = None self.test_script = os.path.join(here, 'b2g_start_script.js') self.test_script_args = [self.out_of_process] self.product = 'b2g' self.remote_chrome_test_dir = None if profile_data_dir: self.preferences = [ os.path.join(profile_data_dir, f) for f in os.listdir(profile_data_dir) if f.startswith('pref') ] self.webapps = [ os.path.join(profile_data_dir, f) for f in os.listdir(profile_data_dir) if f.startswith('webapp') ] # mozinfo is populated by the parent class if mozinfo.info['debug']: self.SERVER_STARTUP_TIMEOUT = 180 else: self.SERVER_STARTUP_TIMEOUT = 90 def setup_common_options(self, options): test_url = self.buildTestPath(options) # For B2G emulators buildURLOptions has been called # without calling buildTestPath first and that # causes manifestFile not to be set if not "manifestFile=tests.json" in self.urlOpts: self.urlOpts.append("manifestFile=%s" % options.manifestFile) if len(self.urlOpts) > 0: test_url += "?" + "&".join(self.urlOpts) self.test_script_args.append(test_url) def buildTestPath(self, options, testsToFilter=None): if options.manifestFile != 'tests.json': super(B2GMochitest, self).buildTestPath(options, testsToFilter, disabled=False) return self.buildTestURL(options) def build_profile(self, options): # preferences prefs = {} for path in self.preferences: prefs.update(Preferences.read_prefs(path)) for v in options.extraPrefs: thispref = v.split("=", 1) if len(thispref) < 2: print "Error: syntax error in --setpref=" + v sys.exit(1) prefs[thispref[0]] = thispref[1] # interpolate the preferences interpolation = { "server": "%s:%s" % (options.webServer, options.httpPort), "OOP": "true" if self.out_of_process else "false" } prefs = json.loads(json.dumps(prefs) % interpolation) for pref in prefs: prefs[pref] = Preferences.cast(prefs[pref]) kwargs = { 'addons': self.getExtensionsToInstall(options), 'apps': self.webapps, 'locations': self.locations_file, 'preferences': prefs, 'proxy': { "remote": options.webServer } } if options.profile: self.profile = Profile.clone(options.profile, **kwargs) else: self.profile = Profile(**kwargs) options.profilePath = self.profile.profile # TODO bug 839108 - mozprofile should probably handle this manifest = self.addChromeToProfile(options) self.copyExtraFilesToProfile(options) return manifest def run_tests(self, options): """ Prepare, configure, run tests and cleanup """ manifest = self.build_profile(options) self.logPreamble(self.getActiveTests(options)) # configuring the message logger's buffering self.message_logger.buffering = options.quiet if options.debugger or not options.autorun: timeout = None else: if not options.timeout: if mozinfo.info['debug']: options.timeout = 420 else: options.timeout = 300 timeout = options.timeout + 30.0 self.log.info("runtestsb2g.py | Running tests: start.") status = 0 try: def on_output(line): messages = self.message_logger.write(line) for message in messages: if message['action'] == 'test_start': self.runner.last_test = message['test'] # The logging will be handled by on_output, so we set the stream to None process_args = {'processOutputLine': on_output, 'stream': None} self.marionette_args['process_args'] = process_args self.marionette_args['profile'] = self.profile self.marionette = Marionette(**self.marionette_args) self.runner = self.marionette.runner self.app_ctx = self.runner.app_ctx self.remote_log = posixpath.join(self.app_ctx.remote_test_root, 'log', 'mochitest.log') if not self.app_ctx.dm.dirExists(posixpath.dirname( self.remote_log)): self.app_ctx.dm.mkDirs(self.remote_log) if options.chrome: # Update chrome manifest file in profile with correct path. self.writeChromeManifest(options) self.leak_report_file = posixpath.join( self.app_ctx.remote_test_root, 'log', 'runtests_leaks.log') # We don't want to copy the host env onto the device, so pass in an # empty env. self.browserEnv = self.buildBrowserEnv(options, env={}) # B2G emulator debug tests still make external connections, so don't # pass MOZ_DISABLE_NONLOCAL_CONNECTIONS to them for now (bug 1039019). if mozinfo.info[ 'debug'] and 'MOZ_DISABLE_NONLOCAL_CONNECTIONS' in self.browserEnv: del self.browserEnv['MOZ_DISABLE_NONLOCAL_CONNECTIONS'] self.runner.env.update(self.browserEnv) self.startServers(options, None) self.buildURLOptions(options, {'MOZ_HIDE_RESULTS_TABLE': '1'}) self.test_script_args.append(not options.emulator) self.test_script_args.append(options.wifi) self.test_script_args.append(options.chrome) self.runner.start(outputTimeout=timeout) self.marionette.wait_for_port() self.marionette.start_session() self.marionette.set_context(self.marionette.CONTEXT_CHROME) # Disable offline status management (bug 777145), otherwise the network # will be 'offline' when the mochitests start. Presumably, the network # won't be offline on a real device, so we only do this for emulators. self.marionette.execute_script(""" Components.utils.import("resource://gre/modules/Services.jsm"); Services.io.manageOfflineStatus = false; Services.io.offline = false; """) if options.chrome: self.app_ctx.dm.removeDir(self.remote_chrome_test_dir) self.app_ctx.dm.mkDir(self.remote_chrome_test_dir) local = super(B2GMochitest, self).getChromeTestDir(options) local = os.path.join(local, "chrome") remote = self.remote_chrome_test_dir self.log.info("pushing %s to %s on device..." % (local, remote)) self.app_ctx.dm.pushDir(local, remote) if os.path.isfile(self.test_script): with open(self.test_script, 'r') as script: self.marionette.execute_script( script.read(), script_args=self.test_script_args) else: self.marionette.execute_script( self.test_script, script_args=self.test_script_args) status = self.runner.wait() if status is None: # the runner has timed out status = 124 local_leak_file = tempfile.NamedTemporaryFile() self.app_ctx.dm.getFile(self.leak_report_file, local_leak_file.name) self.app_ctx.dm.removeFile(self.leak_report_file) processLeakLog(local_leak_file.name, options) except KeyboardInterrupt: self.log.info("runtests.py | Received keyboard interrupt.\n") status = -1 except: traceback.print_exc() self.log.error( "Automation Error: Received unexpected exception while running application\n" ) if hasattr(self, 'runner'): self.runner.check_for_crashes() status = 1 self.stopServers() self.log.info("runtestsb2g.py | Running tests: end.") if manifest is not None: self.cleanup(manifest, options) return status def getGMPPluginPath(self, options): # TODO: bug 1043403 return None def getChromeTestDir(self, options): # The chrome test directory returned here is the remote location # of chrome test files. A reference to this directory is requested # when building the profile locally, before self.app_ctx is defined. # To get around this, return a dummy directory until self.app_ctx # is defined; the correct directory will be returned later, over- # writing the dummy. if hasattr(self, 'app_ctx'): self.remote_chrome_test_dir = posixpath.join( self.app_ctx.remote_test_root, 'chrome') return self.remote_chrome_test_dir return 'dummy-chrome-test-dir'
class B2GMochitest(MochitestUtilsMixin): marionette = None def __init__(self, marionette_args, logger_options, out_of_process=True, profile_data_dir=None, locations=os.path.join(here, 'server-locations.txt')): super(B2GMochitest, self).__init__(logger_options) self.marionette_args = marionette_args self.out_of_process = out_of_process self.locations_file = locations self.preferences = [] self.webapps = None self.test_script = os.path.join(here, 'b2g_start_script.js') self.test_script_args = [self.out_of_process] self.product = 'b2g' self.remote_chrome_test_dir = None if profile_data_dir: self.preferences = [ os.path.join( profile_data_dir, f) for f in os.listdir(profile_data_dir) if f.startswith('pref')] self.webapps = [ os.path.join( profile_data_dir, f) for f in os.listdir(profile_data_dir) if f.startswith('webapp')] # mozinfo is populated by the parent class if mozinfo.info['debug']: self.SERVER_STARTUP_TIMEOUT = 180 else: self.SERVER_STARTUP_TIMEOUT = 90 def setup_common_options(self, options): test_url = self.buildTestPath(options) # For B2G emulators buildURLOptions has been called # without calling buildTestPath first and that # causes manifestFile not to be set if not "manifestFile=tests.json" in self.urlOpts: self.urlOpts.append("manifestFile=%s" % options.manifestFile) if len(self.urlOpts) > 0: test_url += "?" + "&".join(self.urlOpts) self.test_script_args.append(test_url) def buildTestPath(self, options, testsToFilter=None): if options.manifestFile != 'tests.json': super(B2GMochitest, self).buildTestPath(options, testsToFilter, disabled=False) return self.buildTestURL(options) def build_profile(self, options): # preferences prefs = {} for path in self.preferences: prefs.update(Preferences.read_prefs(path)) for v in options.extraPrefs: thispref = v.split("=", 1) if len(thispref) < 2: print "Error: syntax error in --setpref=" + v sys.exit(1) prefs[thispref[0]] = thispref[1] # interpolate the preferences interpolation = { "server": "%s:%s" % (options.webServer, options.httpPort), "OOP": "true" if self.out_of_process else "false"} prefs = json.loads(json.dumps(prefs) % interpolation) for pref in prefs: prefs[pref] = Preferences.cast(prefs[pref]) kwargs = { 'addons': self.getExtensionsToInstall(options), 'apps': self.webapps, 'locations': self.locations_file, 'preferences': prefs, 'proxy': {"remote": options.webServer} } if options.profile: self.profile = Profile.clone(options.profile, **kwargs) else: self.profile = Profile(**kwargs) options.profilePath = self.profile.profile # TODO bug 839108 - mozprofile should probably handle this manifest = self.addChromeToProfile(options) self.copyExtraFilesToProfile(options) return manifest def run_tests(self, options): """ Prepare, configure, run tests and cleanup """ self.setTestRoot(options) manifest = self.build_profile(options) self.logPreamble(self.getActiveTests(options)) # configuring the message logger's buffering self.message_logger.buffering = options.quiet if options.debugger or not options.autorun: timeout = None else: if not options.timeout: if mozinfo.info['debug']: options.timeout = 420 else: options.timeout = 300 timeout = options.timeout + 30.0 self.log.info("runtestsb2g.py | Running tests: start.") status = 0 try: def on_output(line): messages = self.message_logger.write(line) for message in messages: if message['action'] == 'test_start': self.runner.last_test = message['test'] # The logging will be handled by on_output, so we set the stream to # None process_args = {'processOutputLine': on_output, 'stream': None} self.marionette_args['process_args'] = process_args self.marionette_args['profile'] = self.profile self.marionette = Marionette(**self.marionette_args) self.runner = self.marionette.runner self.app_ctx = self.runner.app_ctx self.remote_log = posixpath.join(self.app_ctx.remote_test_root, 'log', 'mochitest.log') if not self.app_ctx.dm.dirExists( posixpath.dirname( self.remote_log)): self.app_ctx.dm.mkDirs(self.remote_log) if options.chrome: # Update chrome manifest file in profile with correct path. self.writeChromeManifest(options) self.leak_report_file = posixpath.join( self.app_ctx.remote_test_root, 'log', 'runtests_leaks.log') # We don't want to copy the host env onto the device, so pass in an # empty env. self.browserEnv = self.buildBrowserEnv(options, env={}) # B2G emulator debug tests still make external connections, so don't # pass MOZ_DISABLE_NONLOCAL_CONNECTIONS to them for now (bug # 1039019). if mozinfo.info[ 'debug'] and 'MOZ_DISABLE_NONLOCAL_CONNECTIONS' in self.browserEnv: del self.browserEnv['MOZ_DISABLE_NONLOCAL_CONNECTIONS'] self.runner.env.update(self.browserEnv) # Despite our efforts to clean up servers started by this script, in practice # we still see infrequent cases where a process is orphaned and interferes # with future tests, typically because the old server is keeping the port in use. # Try to avoid those failures by checking for and killing orphan servers before # trying to start new ones. self.killNamedOrphans('ssltunnel') self.killNamedOrphans('xpcshell') self.startServers(options, None) # In desktop mochitests buildTestPath is called before buildURLOptions. This # means options.manifestFile has already been converted to the proper json # style manifest. Not so with B2G, that conversion along with updating the URL # option will happen later. So backup and restore options.manifestFile to # prevent us from trying to pass in an instance of TestManifest via url param. manifestFile = options.manifestFile options.manifestFile = None self.buildURLOptions(options, {'MOZ_HIDE_RESULTS_TABLE': '1'}) options.manifestFile = manifestFile self.test_script_args.append(not options.emulator) self.test_script_args.append(options.wifi) self.test_script_args.append(options.chrome) self.runner.start(outputTimeout=timeout) self.marionette.wait_for_port() self.marionette.start_session() self.marionette.set_context(self.marionette.CONTEXT_CHROME) # Disable offline status management (bug 777145), otherwise the network # will be 'offline' when the mochitests start. Presumably, the network # won't be offline on a real device, so we only do this for # emulators. self.marionette.execute_script(""" Components.utils.import("resource://gre/modules/Services.jsm"); Services.io.manageOfflineStatus = false; Services.io.offline = false; """) self.marionette.execute_script(""" let SECURITY_PREF = "security.turn_off_all_security_so_that_viruses_can_take_over_this_computer"; Services.prefs.setBoolPref(SECURITY_PREF, true); if (!testUtils.hasOwnProperty("specialPowersObserver")) { let loader = Components.classes["@mozilla.org/moz/jssubscript-loader;1"] .getService(Components.interfaces.mozIJSSubScriptLoader); loader.loadSubScript("chrome://specialpowers/content/SpecialPowersObserver.js", testUtils); testUtils.specialPowersObserver = new testUtils.SpecialPowersObserver(); testUtils.specialPowersObserver.init(); testUtils.specialPowersObserver._loadFrameScript(); } """) if options.chrome: self.app_ctx.dm.removeDir(self.remote_chrome_test_dir) self.app_ctx.dm.mkDir(self.remote_chrome_test_dir) local = super(B2GMochitest, self).getChromeTestDir(options) local = os.path.join(local, "chrome") remote = self.remote_chrome_test_dir self.log.info( "pushing %s to %s on device..." % (local, remote)) self.app_ctx.dm.pushDir(local, remote) if os.path.isfile(self.test_script): with open(self.test_script, 'r') as script: self.marionette.execute_script( script.read(), script_args=self.test_script_args) else: self.marionette.execute_script( self.test_script, script_args=self.test_script_args) status = self.runner.wait() if status is None: # the runner has timed out status = 124 local_leak_file = tempfile.NamedTemporaryFile() self.app_ctx.dm.getFile( self.leak_report_file, local_leak_file.name) self.app_ctx.dm.removeFile(self.leak_report_file) mozleak.process_leak_log( local_leak_file.name, leak_thresholds=options.leakThresholds, ignore_missing_leaks=options.ignoreMissingLeaks, log=self.log, stack_fixer=get_stack_fixer_function(options.utilityPath, options.symbolsPath), ) except KeyboardInterrupt: self.log.info("runtests.py | Received keyboard interrupt.\n") status = -1 except: traceback.print_exc() self.log.error( "Automation Error: Received unexpected exception while running application\n") if hasattr(self, 'runner'): self.runner.check_for_crashes() status = 1 self.stopServers() self.log.info("runtestsb2g.py | Running tests: end.") if manifest is not None: self.cleanup(manifest, options) return status def getGMPPluginPath(self, options): if options.gmp_path: return options.gmp_path return '/system/b2g/gmp-clearkey/0.1' def getChromeTestDir(self, options): # The chrome test directory returned here is the remote location # of chrome test files. A reference to this directory is requested # when building the profile locally, before self.app_ctx is defined. # To get around this, return a dummy directory until self.app_ctx # is defined; the correct directory will be returned later, over- # writing the dummy. if hasattr(self, 'app_ctx'): self.remote_chrome_test_dir = posixpath.join( self.app_ctx.remote_test_root, 'chrome') return self.remote_chrome_test_dir return 'dummy-chrome-test-dir'
class B2GDesktopReftest(RefTest): marionette = None def __init__(self, marionette_args): RefTest.__init__(self) self.last_test = os.path.basename(__file__) self.marionette_args = marionette_args self.profile = None self.runner = None self.test_script = os.path.join(here, 'b2g_start_script.js') self.timeout = None def run_marionette_script(self): self.marionette = Marionette(**self.marionette_args) assert (self.marionette.wait_for_port()) self.marionette.start_session() self.marionette.set_context(self.marionette.CONTEXT_CHROME) if os.path.isfile(self.test_script): f = open(self.test_script, 'r') self.test_script = f.read() f.close() self.marionette.execute_script(self.test_script) def run_tests(self, test_path, options): reftestlist = self.getManifestPath(test_path) if not reftestlist.startswith('file://'): reftestlist = 'file://%s' % reftestlist self.profile = self.create_profile(options, reftestlist, profile_to_clone=options.profile) env = self.buildBrowserEnv(options, self.profile.profile) kp_kwargs = { 'processOutputLine': [self._on_output], 'onTimeout': [self._on_timeout], 'kill_on_timeout': False } if not options.debugger: if not options.timeout: if mozinfo.info['debug']: options.timeout = 420 else: options.timeout = 300 self.timeout = options.timeout + 30.0 log.info("%s | Running tests: start.", os.path.basename(__file__)) cmd, args = self.build_command_line( options.app, ignore_window_size=options.ignoreWindowSize, browser_arg=options.browser_arg) self.runner = FirefoxRunner(profile=self.profile, binary=cmd, cmdargs=args, env=env, process_class=ProcessHandler, process_args=kp_kwargs, symbols_path=options.symbolsPath) status = 0 try: self.runner.start(outputTimeout=self.timeout) log.info("%s | Application pid: %d", os.path.basename(__file__), self.runner.process_handler.pid) # kick starts the reftest harness self.run_marionette_script() status = self.runner.wait() finally: self.runner.check_for_crashes(test_name=self.last_test) self.runner.cleanup() if status > 0: log.testFail("%s | application terminated with exit code %s", self.last_test, status) elif status < 0: log.info("%s | application killed with signal %s", self.last_test, -status) log.info("%s | Running tests: end.", os.path.basename(__file__)) return status def create_profile(self, options, reftestlist, profile_to_clone=None): profile = RefTest.createReftestProfile( self, options, reftestlist, profile_to_clone=profile_to_clone) prefs = {} # Turn off the locale picker screen prefs["browser.firstrun.show.localepicker"] = False prefs[ "b2g.system_startup_url"] = "app://test-container.gaiamobile.org/index.html" prefs[ "b2g.system_manifest_url"] = "app://test-container.gaiamobile.org/manifest.webapp" prefs["browser.tabs.remote"] = False prefs["dom.ipc.tabs.disabled"] = False prefs["dom.mozBrowserFramesEnabled"] = True prefs["font.size.inflation.emPerLine"] = 0 prefs["font.size.inflation.minTwips"] = 0 prefs[ "network.dns.localDomains"] = "app://test-container.gaiamobile.org" prefs["reftest.browser.iframe.enabled"] = False prefs["reftest.remote"] = False prefs["reftest.uri"] = "%s" % reftestlist # Set a future policy version to avoid the telemetry prompt. prefs["toolkit.telemetry.prompted"] = 999 prefs["toolkit.telemetry.notifiedOptOut"] = 999 # Set the extra prefs. profile.set_preferences(prefs) return profile def build_command_line(self, app, ignore_window_size=False, browser_arg=None): cmd = os.path.abspath(app) args = ['-marionette'] if browser_arg: args += [browser_arg] if not ignore_window_size: args.extend(['--screen', '800x1000']) return cmd, args def _on_output(self, line): print(line) # TODO use structured logging if "TEST-START" in line and "|" in line: self.last_test = line.split("|")[1].strip() def _on_timeout(self): msg = "%s | application timed out after %s seconds with no output" log.testFail(msg % (self.last_test, self.timeout)) # kill process to get a stack self.runner.stop(sig=signal.SIGABRT)
class B2GMochitest(MochitestUtilsMixin): marionette = None def __init__(self, marionette_args, out_of_process=True, profile_data_dir=None, locations=os.path.join(here, 'server-locations.txt')): super(B2GMochitest, self).__init__() self.marionette_args = marionette_args self.out_of_process = out_of_process self.locations_file = locations self.preferences = [] self.webapps = None self.test_script = os.path.join(here, 'b2g_start_script.js') self.test_script_args = [self.out_of_process] self.product = 'b2g' # structured logging self.message_logger = MessageLogger(logger=log) if profile_data_dir: self.preferences = [os.path.join(profile_data_dir, f) for f in os.listdir(profile_data_dir) if f.startswith('pref')] self.webapps = [os.path.join(profile_data_dir, f) for f in os.listdir(profile_data_dir) if f.startswith('webapp')] # mozinfo is populated by the parent class if mozinfo.info['debug']: self.SERVER_STARTUP_TIMEOUT = 180 else: self.SERVER_STARTUP_TIMEOUT = 90 def setup_common_options(self, options): test_url = self.buildTestPath(options) # For B2G emulators buildURLOptions has been called # without calling buildTestPath first and that # causes manifestFile not to be set if not "manifestFile=tests.json" in self.urlOpts: self.urlOpts.append("manifestFile=%s" % options.manifestFile) if len(self.urlOpts) > 0: test_url += "?" + "&".join(self.urlOpts) self.test_script_args.append(test_url) def buildTestPath(self, options, testsToFilter=None): if options.manifestFile != 'tests.json': super(B2GMochitest, self).buildTestPath(options, testsToFilter, disabled=False) return self.buildTestURL(options) def build_profile(self, options): # preferences prefs = {} for path in self.preferences: prefs.update(Preferences.read_prefs(path)) for v in options.extraPrefs: thispref = v.split("=", 1) if len(thispref) < 2: print "Error: syntax error in --setpref=" + v sys.exit(1) prefs[thispref[0]] = thispref[1] # interpolate the preferences interpolation = { "server": "%s:%s" % (options.webServer, options.httpPort), "OOP": "true" if self.out_of_process else "false" } prefs = json.loads(json.dumps(prefs) % interpolation) for pref in prefs: prefs[pref] = Preferences.cast(prefs[pref]) kwargs = { 'addons': self.getExtensionsToInstall(options), 'apps': self.webapps, 'locations': self.locations_file, 'preferences': prefs, 'proxy': {"remote": options.webServer} } if options.profile: self.profile = Profile.clone(options.profile, **kwargs) else: self.profile = Profile(**kwargs) options.profilePath = self.profile.profile # TODO bug 839108 - mozprofile should probably handle this manifest = self.addChromeToProfile(options) self.copyExtraFilesToProfile(options) return manifest def run_tests(self, options): """ Prepare, configure, run tests and cleanup """ manifest = self.build_profile(options) self.leak_report_file = os.path.join(options.profilePath, "runtests_leaks.log") # configuring the message logger's buffering self.message_logger.buffering = options.quiet if options.debugger or not options.autorun: timeout = None else: if not options.timeout: if mozinfo.info['debug']: options.timeout = 420 else: options.timeout = 300 timeout = options.timeout + 30.0 log.info("runtestsb2g.py | Running tests: start.") status = 0 try: def on_output(line): messages = self.message_logger.write(line) for message in messages: if message['action'] == 'test_start': self.runner.last_test = message['test'] # The logging will be handled by on_output, so we set the stream to None process_args = {'processOutputLine': on_output, 'stream': None} self.marionette_args['process_args'] = process_args self.marionette_args['profile'] = self.profile self.marionette = Marionette(**self.marionette_args) self.runner = self.marionette.runner self.app_ctx = self.runner.app_ctx self.remote_log = posixpath.join(self.app_ctx.remote_test_root, 'log', 'mochitest.log') if not self.app_ctx.dm.dirExists(posixpath.dirname(self.remote_log)): self.app_ctx.dm.mkDirs(self.remote_log) self.startServers(options, None) self.buildURLOptions(options, {'MOZ_HIDE_RESULTS_TABLE': '1'}) self.test_script_args.append(not options.emulator) self.test_script_args.append(options.wifi) self.runner.start(outputTimeout=timeout) self.marionette.wait_for_port() self.marionette.start_session() self.marionette.set_context(self.marionette.CONTEXT_CHROME) # Disable offline status management (bug 777145), otherwise the network # will be 'offline' when the mochitests start. Presumably, the network # won't be offline on a real device, so we only do this for emulators. self.marionette.execute_script(""" Components.utils.import("resource://gre/modules/Services.jsm"); Services.io.manageOfflineStatus = false; Services.io.offline = false; """) if os.path.isfile(self.test_script): with open(self.test_script, 'r') as script: self.marionette.execute_script(script.read(), script_args=self.test_script_args) else: self.marionette.execute_script(self.test_script, script_args=self.test_script_args) status = self.runner.wait() if status is None: # the runner has timed out status = 124 except KeyboardInterrupt: log.info("runtests.py | Received keyboard interrupt.\n"); status = -1 except: traceback.print_exc() log.error("Automation Error: Received unexpected exception while running application\n") if hasattr(self, 'runner'): self.runner.check_for_crashes() status = 1 self.stopServers() log.info("runtestsb2g.py | Running tests: end.") if manifest is not None: self.cleanup(manifest, options) return status def getGMPPluginPath(self, options): # TODO: bug 1043403 return None
class MtbfJobRunner(BaseActionRunner): serial = None marionette = None flash_params = { 'branch': 'mozilla-b2g34_v2_1-flame-kk-eng', 'build': '', 'build_id': '' } flashed = False def __init__(self, **kwargs): self.logger = logger BaseActionRunner.__init__(self) def setup(self): if not self.serial or not self.port: logger.error("Fail to get device") raise DMError self.marionette and self.marionette.session and self.marionette.cleanup() self.dm = mozdevice.DeviceManagerADB(deviceSerial=self.serial, port=self.port) self.marionette = Marionette(device_serial=self.serial, port=self.port) self.marionette.start_session() self.device = GaiaDevice(marionette=self.marionette, manager=self.dm) self.apps = GaiaApps(self.marionette) self.data_layer = GaiaData(self.marionette) self.device.wait_for_b2g_ready() def adb_test(self): if not hasattr(self, 'serial') or os.system("ANDROID_SERIAL=" + self.serial + " adb shell ls") != 0: logger.error("Device not found or can't be controlled") return False return True @action(enabled=False) def add_7mobile_action(self): # workaround for waiting for boot self.marionette.start_session() self.data_layer = GaiaData(self.marionette) self.data_layer.set_setting('ril.data.apnSettings', [[ {"carrier": "(7-Mobile) (MMS)", "apn": "opentalk", "mmsc": "http://mms", "mmsproxy": "210.241.199.199", "mmsport": "9201", "types": ["mms"]}, {"carrier": "(7-Mobile) (Internet)", "apn": "opentalk", "types": ["default", "supl"]} ]]) return True @action(enabled=False) def change_memory(self): # This function only work in flame # TODO: use native adb/fastboot command to change memory? # Make sure it's in fastboot mode, TODO: leverage all fastboot command in one task function memory = 512 # default set 512 if 'MEM' in os.environ: memory = os.environ['MEM'] elif self.settings['change_memory']['enabled'] and 'memory' in self.settings['change_memory']: memory = self.settings['change_memory']['memory'] if self.adb_test(): os.system("adb reboot bootloader") memory = 512 mem_str = str(memory) os.system("fastboot oem mem " + mem_str) # Preventing from async timing of fastboot os.system("fastboot reboot") self.device_obj.create_adb_forward(self.port) return True logger.error("Can't find device") self.marionette.wait_for_port() self.device_obj.create_adb_forward(self.port) return False @action(enabled=True) def collect_memory_report(self): zip_utils.collect_about_memory("mtbf_driver") # TODO: give a correct path for about memory folder def get_free_device(self): do = device_pool.get_device(self.serial) if do: # Record device serial and store dp instance self.serial = do.serial self.device_obj = do if do.create_adb_forward(): self.port = do.adb_forwarded_port logger.info("Device found, ANDROID_SERIAL= " + self.serial) return do logger.error("Port forwarding failed") raise DMError logger.warning("No available device. Please retry after device released") # TODO: more handling for no available device def validate_flash_params(self): ## Using system environment variable as temporary solution TODO: use other way for input params ## Check if package(files)/folder exists and return, else raise exception if not 'FLASH_BASEDIR' in os.environ: raise AttributeError("No FLASH_BASEDIR set") basedir = os.environ['FLASH_BASEDIR'] if not 'FLASH_BUILDID' in os.environ: ## TODO: if latest/ exists, use latest as default logging.info("No build id set. search in base dir") buildid = "" flash_dir = basedir else: buildid = os.environ['FLASH_BUILDID'] # re-format build id based on pvt folder structure if '-' in buildid: buildid = buildid.replace("-", "") year = buildid[:4] month = buildid[4:6] datetime = '-'.join([year, month] + [buildid[i + 6:i + 8] for i in range(0, len(buildid[6:]), 2)]) flash_dir = os.path.join(basedir, year, month, datetime) if not os.path.isdir(flash_dir): raise AttributeError("Flash directory " + flash_dir + " not exist") flash_files = glob.glob(os.path.join(flash_dir, '*')) flash_src = {} for flash_file in flash_files: logger.debug("Flash source found: [" + flash_file + "]") if os.path.isdir(flash_file): continue elif re.match("^b2g-[0-9]*.*\.tar\.gz$", flash_file): flash_src['gecko'] = flash_file elif "gaia.zip" == flash_file: flash_src['gaia'] = flash_file elif "symbol" in flash_file: flash_src['symbol'] = flash_file elif "zip" in flash_file and not ("gaia.zip" in flash_file): flash_src['image'] = flash_file return flash_src @action(enabled=True) def full_flash(self): flash_src = self.validate_flash_params() if self.flashed: logger.warning("Flash performed; skip flashing") return True if not flash_src: logger.warning("Invalid build folder/build_id, skip flashing") return False if not 'image' in flash_src: logger.warning("No available image for flash, skip flashing") return False try: self.temp_dir = tempfile.mkdtemp() logger.info('Create temporary folder:' + self.temp_dir) Decompressor().unzip(flash_src['image'], self.temp_dir) # set the permissions to rwxrwxr-x (509 in python's os.chmod) os.chmod(self.temp_dir + '/b2g-distro/flash.sh', stat.S_IRUSR | stat.S_IWUSR | stat.S_IXUSR | stat.S_IRGRP | stat.S_IWGRP | stat.S_IXGRP | stat.S_IROTH | stat.S_IXOTH) os.chmod(self.temp_dir + '/b2g-distro/load-config.sh', stat.S_IRUSR | stat.S_IWUSR | stat.S_IXUSR | stat.S_IRGRP | stat.S_IWGRP | stat.S_IXGRP | stat.S_IROTH | stat.S_IXOTH) os.system('cd ' + self.temp_dir + '/b2g-distro; ./flash.sh -f') # support NO_FTU environment for skipping FTU (e.g. monkey test) if 'NO_FTU' in os.environ and os.environ['NO_FTU'] == 'true': logger.log('The [NO_FTU] is [true].') os.system("ANDROID_SERIAL=" + self.serial + 'adb wait-for-device && adb shell stop b2g; (RET=$(adb root); if ! case ${RET} in *"cannot"*) true;; *) false;; esac; then adb remount && sleep 5; else exit 1; fi; ./disable_ftu.py) || (echo "No root permission, cannot setup NO_FTU."); adb reboot;') finally: try: shutil.rmtree(self.temp_dir) # delete directory except OSError: logger.error('Can not remove temporary folder:' + self.temp_dir, level=Logger._LEVEL_WARNING) self.flashed = True @action(enabled=False) def shallow_flash(self): flash_src = self.validate_flash_params() if self.flashed: logger.warning("Flash performed; skip flashing") return True if not flash_src: logger.warning("Invalid build folder/build_id, skip flashing") return False if not 'gaia' in flash_src or not 'gecko' in flash_src: logger.warning("No gaia or gecko archive, skip flashing") return False cmd = 'flash_tool/shallow_flash.sh -y --gecko="' + flash_src['gecko'] + '" --gaia="' + flash_src['gaia'] + '"' if _platform == 'darwin': cmd = cmd.replace('=', ' ') ret = os.system(cmd) if ret != 0: logger.info("Shallow flash ended abnormally") return False self.flashed = True os.system("ANDROID_SERIAL=" + self.serial + " adb wait-for-device") @action(enabled=True) def enable_certified_apps_debug(self): if self.serial: os.system("ANDROID_SERIAL=" + self.serial + " flash_tool/enable_certified_apps_for_devtools.sh && adb wait-for-device") logger.debug("Successfully enabling certified apps for debugging") return True return False def release(self): device_pool.release() def check_version(self): # FIXME: fix check version to use package import cmd = "cd flash_tool/ && NO_COLOR=TRUE ./check_versions.py | sed -e 's| \{2,\}||g' -e 's|\[0m||g'" if self.serial: cmd = "ANDROID_SERIAL=" + self.serial + " " + cmd os.system(cmd) @action(enabled=False) def patch_marionette(self): os.system("M_PATH=/mnt/mtbf_shared/paul/ /mnt/mtbf_shared/paul/marionette_update.sh") import time time.sleep(10) self.device_obj.create_adb_forward(self.port) def mtbf_options(self): ## load mtbf parameters if not 'MTBF_TIME' in os.environ: logger.warning("MTBF_TIME is not set") if not 'MTBF_CONF' in os.environ: logger.warning("MTBF_CONF is not set") parser = self.parser.parser parser.add_argument("--testvars", help="Test variables for b2g") self.parse_options() # FIXME: make rootdir of testvars could be customized mtbf_testvars_dir = "/mnt/mtbf_shared/testvars" if not hasattr(self.options, 'testvars') or not self.options.testvars: testvars = os.path.join(mtbf_testvars_dir, "testvars_" + self.serial + ".json") logger.info("testvar is [" + testvars + "]") if os.path.exists(testvars): self.options.testvars = parser.testvars = testvars logger.info("testvar [" + testvars + "] found") else: raise AttributeError("testvars[" + testvars + "] doesn't exist") ## #TODO: finish parsing arguments for flashing ## parser.add_argument("--flashdir", help="directory for pulling build") ## parser.add_argument("--buildid", help="build id for pulling build") ## self.parse_options() ## if hasattr(self.parser.option, 'flashdir'): ## os.environ['FLASH_BASEDIR'] = self.parser.option.flashdir ## if hasattr(self.parser.option, 'buildid'): ## os.environ['FLASH_BUILDID'] = self.parser.option.buildid def remove_settings_opt(self): for e in sys.argv[1:]: if '--settings' in e: idx = sys.argv.index(e) sys.argv.remove(e) if len(sys.argv) > idx and not '--' in sys.argv[idx]: del sys.argv[idx] break @action(enabled=False) def mtbf_daily(self): parser = GaiaTestOptions() opts = [] for k, v in self.kwargs.iteritems(): opts.append("--" + k) opts.append(v) options, tests = parser.parse_args(sys.argv[1:] + opts) structured.commandline.add_logging_group(parser) logger = structured.commandline.setup_logging( options.logger_name, options, {"tbpl": sys.stdout}) options.logger = logger options.testvars = [self.options.testvars] runner = GaiaTestRunner(**vars(options)) runner.run_tests(["tests"]) @action(enabled=True) def run_mtbf(self): mtbf.main(testvars=self.options.testvars, **self.kwargs) def execute(self): self.marionette.cleanup() self.marionette = Marionette(device_serial=self.serial, port=self.port) self.marionette.wait_for_port() # run test runner here self.remove_settings_opt() self.kwargs = {} if self.port: self.kwargs['address'] = "localhost:" + str(self.port) logger.info("Using address[localhost:" + str(self.port) + "]") self.mtbf_daily() self.run_mtbf() def pre_flash(self): pass def flash(self): self.shallow_flash() self.full_flash() # workaround for waiting for boot def post_flash(self): self.setup() self.check_version() self.change_memory() self.add_7mobile_action() self.enable_certified_apps_debug() self.patch_marionette() def output_crash_report_no_to_log(self, serial): if serial in CrashScan.get_current_all_dev_serials(): crash_result = CrashScan.get_crash_no_by_serial(serial) if crash_result['crashNo'] > 0: logger.error("CrashReportFound: device " + serial + " has " + str(crash_result['crashNo']) + " crashes.") else: logger.info("CrashReportNotFound: No crash report found in device " + serial) else: logger.error("CrashReportAdbError: Can't find device in ADB list") def collect_report(self, serial): self.output_crash_report_no_to_log(serial) def run(self): try: if self.get_free_device(): self.mtbf_options() self.pre_flash() self.flash() self.device_obj.create_adb_forward() self.port = self.device_obj.adb_forwarded_port self.post_flash() self.execute() self.collect_report(self.serial) finally: self.release()
class B2GDesktopReftest(RefTest): marionette = None def __init__(self, marionette_args): RefTest.__init__(self) self.last_test = os.path.basename(__file__) self.marionette_args = marionette_args self.profile = None self.runner = None self.test_script = os.path.join(here, 'b2g_start_script.js') self.timeout = None def run_marionette_script(self): self.marionette = Marionette(**self.marionette_args) assert(self.marionette.wait_for_port()) self.marionette.start_session() self.marionette.set_context(self.marionette.CONTEXT_CHROME) if os.path.isfile(self.test_script): f = open(self.test_script, 'r') self.test_script = f.read() f.close() self.marionette.execute_script(self.test_script) def run_tests(self, test_path, options): reftestlist = self.getManifestPath(test_path) if not reftestlist.startswith('file://'): reftestlist = 'file://%s' % reftestlist self.profile = self.create_profile(options, reftestlist, profile_to_clone=options.profile) env = self.buildBrowserEnv(options, self.profile.profile) kp_kwargs = { 'processOutputLine': [self._on_output], 'onTimeout': [self._on_timeout], 'kill_on_timeout': False } if not options.debugger: if not options.timeout: if mozinfo.info['debug']: options.timeout = 420 else: options.timeout = 300 self.timeout = options.timeout + 30.0 log.info("%s | Running tests: start.", os.path.basename(__file__)) cmd, args = self.build_command_line(options.app, ignore_window_size=options.ignoreWindowSize, browser_arg=options.browser_arg) self.runner = FirefoxRunner(profile=self.profile, binary=cmd, cmdargs=args, env=env, process_class=ProcessHandler, process_args=kp_kwargs, symbols_path=options.symbolsPath) status = 0 try: self.runner.start(outputTimeout=self.timeout) log.info("%s | Application pid: %d", os.path.basename(__file__), self.runner.process_handler.pid) # kick starts the reftest harness self.run_marionette_script() status = self.runner.wait() finally: self.runner.check_for_crashes(test_name=self.last_test) self.runner.cleanup() if status > 0: log.testFail("%s | application terminated with exit code %s", self.last_test, status) elif status < 0: log.info("%s | application killed with signal %s", self.last_test, -status) log.info("%s | Running tests: end.", os.path.basename(__file__)) return status def create_profile(self, options, reftestlist, profile_to_clone=None): profile = RefTest.createReftestProfile(self, options, reftestlist, profile_to_clone=profile_to_clone) prefs = {} # Turn off the locale picker screen prefs["browser.firstrun.show.localepicker"] = False prefs["b2g.system_startup_url"] = "app://test-container.gaiamobile.org/index.html" prefs["b2g.system_manifest_url"] = "app://test-container.gaiamobile.org/manifest.webapp" prefs["dom.ipc.tabs.disabled"] = False prefs["dom.mozBrowserFramesEnabled"] = True prefs["font.size.inflation.emPerLine"] = 0 prefs["font.size.inflation.minTwips"] = 0 prefs["network.dns.localDomains"] = "app://test-container.gaiamobile.org" prefs["reftest.browser.iframe.enabled"] = False prefs["reftest.remote"] = False prefs["reftest.uri"] = "%s" % reftestlist # Set a future policy version to avoid the telemetry prompt. prefs["toolkit.telemetry.prompted"] = 999 prefs["toolkit.telemetry.notifiedOptOut"] = 999 # Set the extra prefs. profile.set_preferences(prefs) return profile def build_command_line(self, app, ignore_window_size=False, browser_arg=None): cmd = os.path.abspath(app) args = ['-marionette'] if browser_arg: args += [browser_arg] if not ignore_window_size: args.extend(['--screen', '800x1000']) return cmd, args def _on_output(self, line): print(line) # TODO use structured logging if "TEST-START" in line and "|" in line: self.last_test = line.split("|")[1].strip() def _on_timeout(self): msg = "%s | application timed out after %s seconds with no output" log.testFail(msg % (self.last_test, self.timeout)) # kill process to get a stack self.runner.stop(sig=signal.SIGABRT)
class B2GMochitest(MochitestUtilsMixin): marionette = None def __init__(self, marionette_args, out_of_process=True, profile_data_dir=None, locations=os.path.join(here, 'server-locations.txt')): super(B2GMochitest, self).__init__() self.marionette_args = marionette_args self.out_of_process = out_of_process self.locations_file = locations self.preferences = [] self.webapps = None self.test_script = os.path.join(here, 'b2g_start_script.js') self.test_script_args = [self.out_of_process] self.product = 'b2g' if profile_data_dir: self.preferences = [ os.path.join(profile_data_dir, f) for f in os.listdir(profile_data_dir) if f.startswith('pref') ] self.webapps = [ os.path.join(profile_data_dir, f) for f in os.listdir(profile_data_dir) if f.startswith('webapp') ] # mozinfo is populated by the parent class if mozinfo.info['debug']: self.SERVER_STARTUP_TIMEOUT = 180 else: self.SERVER_STARTUP_TIMEOUT = 90 def setup_common_options(self, options): test_url = self.buildTestPath(options) # For B2G emulators buildURLOptions has been called # without calling buildTestPath first and that # causes manifestFile not to be set if not "manifestFile=tests.json" in self.urlOpts: self.urlOpts.append("manifestFile=%s" % options.manifestFile) if len(self.urlOpts) > 0: test_url += "?" + "&".join(self.urlOpts) self.test_script_args.append(test_url) def buildTestPath(self, options): if options.manifestFile != 'tests.json': super(B2GMochitest, self).buildTestPath(options, disabled=False) return self.buildTestURL(options) def build_profile(self, options): # preferences prefs = {} for path in self.preferences: prefs.update(Preferences.read_prefs(path)) for v in options.extraPrefs: thispref = v.split("=", 1) if len(thispref) < 2: print "Error: syntax error in --setpref=" + v sys.exit(1) prefs[thispref[0]] = thispref[1] # interpolate the preferences interpolation = { "server": "%s:%s" % (options.webServer, options.httpPort), "OOP": "true" if self.out_of_process else "false" } prefs = json.loads(json.dumps(prefs) % interpolation) for pref in prefs: prefs[pref] = Preferences.cast(prefs[pref]) kwargs = { 'addons': self.getExtensionsToInstall(options), 'apps': self.webapps, 'locations': self.locations_file, 'preferences': prefs, 'proxy': { "remote": options.webServer } } if options.profile: self.profile = Profile.clone(options.profile, **kwargs) else: self.profile = Profile(**kwargs) options.profilePath = self.profile.profile # TODO bug 839108 - mozprofile should probably handle this manifest = self.addChromeToProfile(options) self.copyExtraFilesToProfile(options) return manifest def run_tests(self, options): """ Prepare, configure, run tests and cleanup """ manifest = self.build_profile(options) self.leak_report_file = os.path.join(options.profilePath, "runtests_leaks.log") if options.debugger or not options.autorun: timeout = None else: if not options.timeout: if mozinfo.info['debug']: options.timeout = 420 else: options.timeout = 300 timeout = options.timeout + 30.0 log.info("runtestsb2g.py | Running tests: start.") status = 0 try: self.marionette_args['profile'] = self.profile self.marionette = Marionette(**self.marionette_args) self.runner = self.marionette.runner self.app_ctx = self.runner.app_ctx self.remote_log = posixpath.join(self.app_ctx.remote_test_root, 'log', 'mochitest.log') if not self.app_ctx.dm.dirExists(posixpath.dirname( self.remote_log)): self.app_ctx.dm.mkDirs(self.remote_log) self.startServers(options, None) self.buildURLOptions(options, {'MOZ_HIDE_RESULTS_TABLE': '1'}) self.test_script_args.append(not options.emulator) self.test_script_args.append(options.wifi) self.runner.start(outputTimeout=timeout) self.marionette.wait_for_port() self.marionette.start_session() self.marionette.set_context(self.marionette.CONTEXT_CHROME) # Disable offline status management (bug 777145), otherwise the network # will be 'offline' when the mochitests start. Presumably, the network # won't be offline on a real device, so we only do this for emulators. self.marionette.execute_script(""" Components.utils.import("resource://gre/modules/Services.jsm"); Services.io.manageOfflineStatus = false; Services.io.offline = false; """) if os.path.isfile(self.test_script): with open(self.test_script, 'r') as script: self.marionette.execute_script( script.read(), script_args=self.test_script_args) else: self.marionette.execute_script( self.test_script, script_args=self.test_script_args) status = self.runner.wait() if status is None: # the runner has timed out status = 124 except KeyboardInterrupt: log.info("runtests.py | Received keyboard interrupt.\n") status = -1 except: traceback.print_exc() log.error( "Automation Error: Received unexpected exception while running application\n" ) if hasattr(self, 'runner'): self.runner.check_for_crashes() status = 1 self.stopServers() log.info("runtestsb2g.py | Running tests: end.") if manifest is not None: self.cleanup(manifest, options) return status
class TestRun(object): def __init__(self, adb="adb", serial=None): self.test_results = {} self.test_results_file = None self.m = None self.gaia_apps = None self.screenshot_path = None self.logcat_path = None self.app_name = None self.attempt = None self.num_apps = None self.device = None self.serial = serial self.port = None self.run_log = logging.getLogger('marketplace-test') if self.serial: self.dm = DeviceManagerADB(adbPath=adb, deviceSerial=serial) else: self.dm = DeviceManagerADB(adbPath=adb) def reset_marionette(self): try: self.m.delete_session() except Exception: pass self.m = None self.get_marionette() def get_marionette(self): if not self.m: self.m = Marionette(port=self.port) self.m.start_session() self.device = GaiaDevice(self.m) self.device.add_device_manager(self.dm) self.gaia_apps = GaiaApps(self.m) else: tries = 5 while tries > 0: try: self.m.get_url() break except MarionetteException as e: if "Please start a session" in str(e): time.sleep(5) self.m = Marionette(port=self.port) self.m.start_session() self.device = GaiaDevice(self.m) self.device.add_device_manager(self.dm) self.gaia_apps = GaiaApps(self.m) tries -= 1 else: raise e else: self.run_log.error("Can't connect to marionette, rebooting") self.restart_device() return self.m def write_to_file(self, data): with open("%s.tmp" % self.test_results_file, "w") as f: f.write(data) shutil.copyfile("%s.tmp" % self.test_results_file, self.test_results_file) def add_values(self, key, value): if self.serial: self.test_results["%s_%s" % (key, self.serial)] = value else: self.test_results["%s" % key] = value def add_result(self, passed=False, status=None, uninstalled_failure=False): values = {} if status: if not passed: values["status"] = "FAILED: %s" % status else: values["status"] = "PASS" if self.screenshot_path: values["screenshot"] = self.screenshot_path if self.logcat_path: values["logcat"] = self.logcat_path if uninstalled_failure: values["uninstalled_failure"] = uninstalled_failure entry = "%s_%s" % (self.app_name, self.attempt) self.test_results[entry] = values def launch_with_manifest(self, manifest): self.m.switch_to_frame() result = self.m.execute_async_script("GaiaApps.launchWithManifestURL('%s')" % manifest, script_timeout=30000) if result == False: raise Exception("launch timed out") app = GaiaApp(frame=result.get('frame'), src=result.get('src'), name=result.get('name'), origin=result.get('origin')) if app.frame_id is None: raise Exception("failed to launch; there is no app frame") self.m.switch_to_frame(app.frame_id) return app def uninstall_with_manifest(self, manifest): self.m.switch_to_frame() script = """ GaiaApps.locateWithManifestURL('%s', null, function uninstall(app) { navigator.mozApps.mgmt.uninstall(app); marionetteScriptFinished(true); }); """ result = self.m.execute_async_script(script % manifest, script_timeout=60000) if result != True: self.add_result(status="Failed to uninstall app with url '%s'" % manifest) return app def forward_port(self): # get unused port s = socket.socket(socket.AF_INET, socket.SOCK_STREAM) s.bind(('localhost', 0)) addr, port = s.getsockname() s.close() dm_tries = 0 self.run_log.info("using port %s" % port) while dm_tries < 20: if self.dm.forward("tcp:%d" % port, "tcp:2828") == 0: break dm_tries += 1 time.sleep(3) else: return False self.port = port return True def restart_device(self, restart_tries=0): self.run_log.info("rebooting") # TODO restarting b2g doesn't seem to work... reboot then while restart_tries < 3: restart_tries += 1 self.dm.reboot(wait=True) self.run_log.info("forwarding") if not self.forward_port(): self.run_log.error("couldn't forward port in time, rebooting") continue self.m = Marionette(port=self.port) if not self.m.wait_for_port(180): self.run_log.error("couldn't contact marionette in time, rebooting") continue time.sleep(1) self.m.start_session() try: Wait(self.m, timeout=240).until(lambda m: m.find_element("id", "lockscreen-container").is_displayed()) # It retuns a little early time.sleep(2) self.device = GaiaDevice(self.m) self.device.add_device_manager(self.dm) self.device.unlock() self.gaia_apps = GaiaApps(self.m) except (MarionetteException, IOError, socket.error) as e: self.run_log.error("got exception: %s, going to retry" % e) try: self.m.delete_session() except: # at least attempt to clear the session if possible pass continue break else: raise Exception("Couldn't restart the device in time, even after 3 tries") def readystate_wait(self, app): try: Wait(self.get_marionette(), timeout=30).until(lambda m: m.execute_script("return window.document.readyState;") == "complete") except ScriptTimeoutException as e: return False return True def record_icons(self): self.device.touch_home_button() icons = self.m.find_elements("class name", "icon") self.num_apps = len(icons) def check_if_app_installed(self, timeout=180): # TODO: Find a better way to do this than checking homescreen # I hope there is one... self.device.touch_home_button() icons = self.m.find_elements("class name", "icon") start = time.time() end = start + 180 found_icon = None claims_its_loaded = 0 # this is used in case 'loading' isn't applied immediately to the icon while time.time() < end: if not found_icon: icons = self.m.find_elements("class name", "icon") # We can't do set comparison b/c references change if len(icons) > self.num_apps: for icon in icons: if "loading" in icon.get_attribute("innerHTML"): found_icon = icon break else: claims_its_loaded += 1 if claims_its_loaded == 3: return True else: if "loading" not in found_icon.get_attribute("innerHTML"): return True time.sleep(2) return False
class TestRun(object): def __init__(self, adb="adb", serial=None): self.test_results = {} self.test_results_file = None self.m = None self.gaia_apps = None self.screenshot_path = None self.logcat_path = None self.app_name = None self.attempt = None self.num_apps = None self.device = None self.serial = serial self.port = None self.run_log = logging.getLogger('marketplace-test') if self.serial: self.dm = DeviceManagerADB(adbPath=adb, deviceSerial=serial) else: self.dm = DeviceManagerADB(adbPath=adb) def reset_marionette(self): try: self.m.delete_session() except Exception: pass self.m = None self.get_marionette() def get_marionette(self): if not self.m: self.m = Marionette(port=self.port) self.m.start_session() self.device = GaiaDevice(self.m) self.device.add_device_manager(self.dm) self.gaia_apps = GaiaApps(self.m) else: tries = 5 while tries > 0: try: self.m.get_url() break except MarionetteException as e: if "Please start a session" in str(e): time.sleep(5) self.m = Marionette(port=self.port) self.m.start_session() self.device = GaiaDevice(self.m) self.device.add_device_manager(self.dm) self.gaia_apps = GaiaApps(self.m) tries -= 1 else: raise e else: self.run_log.error("Can't connect to marionette, rebooting") self.restart_device() return self.m def write_to_file(self, data): with open("%s.tmp" % self.test_results_file, "w") as f: f.write(data) shutil.copyfile("%s.tmp" % self.test_results_file, self.test_results_file) def add_values(self, key, value): if self.serial: self.test_results["%s_%s" % (key, self.serial)] = value else: self.test_results["%s" % key] = value def add_result(self, passed=False, status=None, uninstalled_failure=False): values = {} if status: if not passed: values["status"] = "FAILED: %s" % status else: values["status"] = "PASS" if self.screenshot_path: values["screenshot"] = self.screenshot_path if self.logcat_path: values["logcat"] = self.logcat_path if uninstalled_failure: values["uninstalled_failure"] = uninstalled_failure entry = "%s_%s" % (self.app_name, self.attempt) self.test_results[entry] = values def launch_with_manifest(self, manifest): self.m.switch_to_frame() result = self.m.execute_async_script( "GaiaApps.launchWithManifestURL('%s')" % manifest, script_timeout=30000) if result == False: raise Exception("launch timed out") app = GaiaApp(frame=result.get('frame'), src=result.get('src'), name=result.get('name'), origin=result.get('origin')) if app.frame_id is None: raise Exception("failed to launch; there is no app frame") self.m.switch_to_frame(app.frame_id) return app def uninstall_with_manifest(self, manifest): self.m.switch_to_frame() script = """ GaiaApps.locateWithManifestURL('%s', null, function uninstall(app) { navigator.mozApps.mgmt.uninstall(app); marionetteScriptFinished(true); }); """ result = self.m.execute_async_script(script % manifest, script_timeout=60000) if result != True: self.add_result(status="Failed to uninstall app with url '%s'" % manifest) return app def forward_port(self): # get unused port s = socket.socket(socket.AF_INET, socket.SOCK_STREAM) s.bind(('localhost', 0)) addr, port = s.getsockname() s.close() dm_tries = 0 self.run_log.info("using port %s" % port) while dm_tries < 20: if self.dm.forward("tcp:%d" % port, "tcp:2828") == 0: break dm_tries += 1 time.sleep(3) else: return False self.port = port return True def restart_device(self, restart_tries=0): self.run_log.info("rebooting") # TODO restarting b2g doesn't seem to work... reboot then while restart_tries < 3: restart_tries += 1 self.dm.reboot(wait=True) self.run_log.info("forwarding") if not self.forward_port(): self.run_log.error("couldn't forward port in time, rebooting") continue self.m = Marionette(port=self.port) if not self.m.wait_for_port(180): self.run_log.error( "couldn't contact marionette in time, rebooting") continue time.sleep(1) self.m.start_session() try: Wait(self.m, timeout=240).until(lambda m: m.find_element( "id", "lockscreen-container").is_displayed()) # It retuns a little early time.sleep(2) self.device = GaiaDevice(self.m) self.device.add_device_manager(self.dm) self.device.unlock() self.gaia_apps = GaiaApps(self.m) except (MarionetteException, IOError, socket.error) as e: self.run_log.error("got exception: %s, going to retry" % e) try: self.m.delete_session() except: # at least attempt to clear the session if possible pass continue break else: raise Exception( "Couldn't restart the device in time, even after 3 tries") def readystate_wait(self, app): try: Wait(self.get_marionette(), timeout=30).until(lambda m: m.execute_script( "return window.document.readyState;") == "complete") except ScriptTimeoutException as e: return False return True def record_icons(self): self.device.touch_home_button() icons = self.m.find_elements("class name", "icon") self.num_apps = len(icons) def check_if_app_installed(self, timeout=180): # TODO: Find a better way to do this than checking homescreen # I hope there is one... self.device.touch_home_button() icons = self.m.find_elements("class name", "icon") start = time.time() end = start + 180 found_icon = None claims_its_loaded = 0 # this is used in case 'loading' isn't applied immediately to the icon while time.time() < end: if not found_icon: icons = self.m.find_elements("class name", "icon") # We can't do set comparison b/c references change if len(icons) > self.num_apps: for icon in icons: if "loading" in icon.get_attribute("innerHTML"): found_icon = icon break else: claims_its_loaded += 1 if claims_its_loaded == 3: return True else: if "loading" not in found_icon.get_attribute("innerHTML"): return True time.sleep(2) return False