Example #1
0
 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 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
Example #3
0
def set_up_device(opt):
    if not opt.wifi_ssid or not opt.wifi_key or not opt.wifi_pass:
        raise ValueError('Missing --wifi options')

    mc = Marionette('localhost', opt.adb_port)
    for i in range(2):
        try:
            mc.start_session()
            break
        except socket.error:
            sh('adb forward tcp:%s tcp:%s' % (opt.adb_port, opt.adb_port))
    if opt.shell:
        from pdb import set_trace
        set_trace()
        return

    # watch out! This is how gaiatest does it.
    mc.__class__ = type('Marionette', (Marionette, MarionetteTouchMixin), {})
    device = GaiaDevice(mc)

    device.restart_b2g()

    apps = GaiaApps(mc)
    data_layer = GaiaData(mc)
    lockscreen = LockScreen(mc)
    mc.setup_touch()

    lockscreen.unlock()
    apps.kill_all()

    data_layer.enable_wifi()
    if opt.wifi_key == 'WPA-PSK':
        pass_key = 'psk'
    elif opt.wifi_key == 'WEP':
        pass_key = 'wep'
    else:
        assert 0, 'unknown key management'
    data = {'ssid': opt.wifi_ssid, 'keyManagement': opt.wifi_key,
            pass_key: opt.wifi_pass}
    data_layer.connect_to_wifi(data)

    mc.switch_to_frame()
    all_apps = set(a['manifest']['name'] for a in get_installed(apps))
    if 'Marketplace Dev' not in all_apps:
        mc.execute_script(
            'navigator.mozApps.install'
            '("https://marketplace-dev.allizom.org/manifest.webapp");')
        wait_for_element_displayed(mc, 'id', 'app-install-install-button')
        yes = mc.find_element('id', 'app-install-install-button')
        mc.tap(yes)
        wait_for_element_displayed(mc, 'id', 'system-banner')

    print 'Pushing payment prefs'
    sh('adb shell stop b2g')
    sh('adb push "%s" /data/local/user.js' % (
        os.path.join(os.path.dirname(__file__), 'payment-prefs.js')))
    sh('adb shell start b2g')

    print 'When your device reboots, Marketplace Dev will be installed'
Example #4
0
def set_up_device(args):
    mc = get_marionette(args)
    device = GaiaDevice(mc)
    try:
        device.restart_b2g()
    except Exception:
        print ' ** Check to make sure you don\'t have desktop B2G running'
        raise

    apps = GaiaApps(mc)
    data_layer = GaiaData(mc)
    lockscreen = LockScreen(mc)
    mc.setup_touch()

    lockscreen.unlock()
    apps.kill_all()

    if args.wifi_ssid:
        print 'Configuring WiFi'
        if not args.wifi_key or not args.wifi_pass:
            args.error('Missing --wifi_key or --wifi_pass option')
        args.wifi_key = args.wifi_key.upper()

        data_layer.enable_wifi()
        if args.wifi_key == 'WPA-PSK':
            pass_key = 'psk'
        elif args.wifi_key == 'WEP':
            pass_key = 'wep'
        else:
            args.error('not sure what key to use for %r' % args.wifi_key)

        data = {'ssid': args.wifi_ssid, 'keyManagement': args.wifi_key,
                pass_key: args.wifi_pass}
        data_layer.connect_to_wifi(data)

    for manifest in args.apps:
        # There is probably a way easier way to do this by adb pushing
        # something. Send me a patch!
        mc.switch_to_frame()
        try:
            data = requests.get(manifest).json()
            app_name = data['name']
            all_apps = set(a['manifest']['name'] for a in get_installed(apps))
            if app_name not in all_apps:
                print 'Installing %s from %s' % (app_name, manifest)
                mc.execute_script('navigator.mozApps.install("%s");' % manifest)
                wait_for_element_displayed(mc, 'id', 'app-install-install-button')
                yes = mc.find_element('id', 'app-install-install-button')
                mc.tap(yes)
                # This still works but the id check broke.
                # See https://bugzilla.mozilla.org/show_bug.cgi?id=853878
                wait_for_element_displayed(mc, 'id', 'system-banner')
        except Exception, exc:
            print ' ** installing manifest %s failed (maybe?)' % manifest
            print ' ** error: %s: %s' % (exc.__class__.__name__, exc)
            continue
Example #5
0
 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")
Example #6
0
    def install_apps():
        mc = get_marionette(args)
        device = GaiaDevice(mc)
        try:
            device.restart_b2g()
            print 'Your device is rebooting.'
        except Exception:
            print ' ** Check to make sure you don\'t have desktop B2G running'
            raise

        apps = GaiaApps(mc)
        apps.kill_all()

        lockscreen = LockScreen(mc)
        lockscreen.unlock()

        if args.wifi_ssid:
            print 'Configuring WiFi'
            if not args.wifi_key or not args.wifi_pass:
                args.error('Missing --wifi_key or --wifi_pass option')
            args.wifi_key = args.wifi_key.upper()

            data_layer = GaiaData(mc)
            data_layer.enable_wifi()
            if args.wifi_key == 'WPA-PSK':
                pass_key = 'psk'
            elif args.wifi_key == 'WEP':
                pass_key = 'wep'
            else:
                args.error('not sure what key to use for %r' % args.wifi_key)

            data = {
                'ssid': args.wifi_ssid,
                'keyManagement': args.wifi_key,
                pass_key: args.wifi_pass
            }
            data_layer.connect_to_wifi(data)

        # disconnect marionette client because install_app would need it
        mc.client.close()

        # install apps one by one
        for manifest in args.apps:
            args.manifest = manifest
            args.app = None
            install_app(args)
    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()
Example #8
0
    def install_apps():
        mc = get_marionette(args)
        device = GaiaDevice(mc)
        try:
            device.restart_b2g()
            print 'Your device is rebooting.'
        except Exception:
            print ' ** Check to make sure you don\'t have desktop B2G running'
            raise

        apps = GaiaApps(mc)
        apps.kill_all()

        lockscreen = LockScreen(mc)
        lockscreen.unlock()

        if args.wifi_ssid:
            print 'Configuring WiFi'
            if not args.wifi_key or not args.wifi_pass:
                args.error('Missing --wifi_key or --wifi_pass option')
            args.wifi_key = args.wifi_key.upper()

            data_layer = GaiaData(mc)
            data_layer.enable_wifi()
            if args.wifi_key == 'WPA-PSK':
                pass_key = 'psk'
            elif args.wifi_key == 'WEP':
                pass_key = 'wep'
            else:
                args.error('not sure what key to use for %r' % args.wifi_key)

            data = {'ssid': args.wifi_ssid, 'keyManagement': args.wifi_key,
                    pass_key: args.wifi_pass}
            data_layer.connect_to_wifi(data)

        # disconnect marionette client because install_app would need it
        mc.client.close()

        # install apps one by one
        for manifest in args.apps:
            args.manifest = manifest
            args.app = None
            install_app(args)
Example #9
0
 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 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")
Example #11
0
    def __init__(self, marionette, log_level='INFO'):
        self.marionette = marionette
        self.data_layer = GaiaData(self.marionette)
        self.device = GaiaDevice(self.marionette)
        dm = mozdevice.DeviceManagerADB()
        self.device.add_device_manager(dm)

        self.logger.setLevel(getattr(mozlog, log_level.upper()))

        if self.device.is_android_build:
            self.idb_dir = 'idb'
            for candidate in self.device.manager.listFiles(
                    '/'.join([self.PERSISTENT_STORAGE_PATH, 'chrome'])):
                if re.match('\d.*idb', candidate):
                    self.idb_dir = candidate
                    break
Example #12
0
    def __init__(self, marionette, log_level='INFO', start_timeout=60,
                 device_serial=None):
        self.marionette = marionette
        self.data_layer = GaiaData(self.marionette)
        dm = mozdevice.DeviceManagerADB(deviceSerial=device_serial)
        self.device = GaiaDevice(self.marionette, manager=dm)

        self.logger.setLevel(getattr(mozlog, log_level.upper()))
        self.start_timeout = start_timeout

        if self.device.is_android_build:
            self.idb_dir = 'idb'
            for candidate in self.device.file_manager.list_items(
                    '/'.join([self.PERSISTENT_STORAGE_PATH, 'chrome'])):
                if re.match('\d.*idb', candidate):
                    self.idb_dir = candidate
                    break
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()
Example #14
0
class B2GPopulate(object):

    PERSISTENT_STORAGE_PATH = '/data/local/storage/persistent'

    handler = mozlog.StreamHandler()
    handler.setFormatter(mozlog.MozFormatter(include_timestamp=True))
    logger = mozlog.getLogger('B2GPopulate', handler)

    def __init__(self, marionette, log_level='INFO'):
        self.marionette = marionette
        self.data_layer = GaiaData(self.marionette)
        self.device = GaiaDevice(self.marionette)
        dm = mozdevice.DeviceManagerADB()
        self.device.add_device_manager(dm)

        self.logger.setLevel(getattr(mozlog, log_level.upper()))

        if self.device.is_android_build:
            self.idb_dir = 'idb'
            for candidate in self.device.manager.listFiles(
                    '/'.join([self.PERSISTENT_STORAGE_PATH, 'chrome'])):
                if re.match('\d.*idb', candidate):
                    self.idb_dir = candidate
                    break

    def populate(self, call_count=None, contact_count=None, message_count=None,
                 music_count=None, picture_count=None, video_count=None,
                 event_count=None):

        restart = any([call_count, contact_count, message_count])

        if restart:
            self.logger.debug('Stopping B2G')
            self.device.stop_b2g()

        if call_count is not None:
            self.populate_calls(call_count, restart=False)

        if contact_count is not None:
            self.populate_contacts(contact_count, restart=False)

        if event_count is not None:
            self.populate_events(event_count, restart=False)

        if message_count is not None:
            self.populate_messages(message_count, restart=False)

        if restart:
            self.start_b2g()

        if music_count > 0:
            self.populate_music(music_count)

        if picture_count > 0:
            self.populate_pictures(picture_count)

        if video_count > 0:
            self.populate_videos(video_count)

    def populate_calls(self, count, restart=True):
        # only allow preset db values for calls
        db_counts = DB_PRESET_COUNTS['call']
        if not count in db_counts:
            raise InvalidCountError('call')
        self.logger.info('Populating %d calls' % count)
        db_counts.sort(reverse=True)
        for marker in db_counts:
            if count >= marker:
                key = 'communications.gaiamobile.org'
                local_id = json.loads(self.device.manager.pullFile(
                    '/data/local/webapps/webapps.json'))[key]['localId']
                db_zip_name = pkg_resources.resource_filename(
                    __name__, os.path.sep.join(['resources', 'dialerDb.zip']))
                db_name = 'dialerDb-%d.sqlite' % marker
                self.logger.debug('Extracting %s from %s' % (
                    db_name, db_zip_name))
                db = ZipFile(db_zip_name).extract(db_name)
                if restart:
                    self.device.stop_b2g()
                destination = '/'.join([self.PERSISTENT_STORAGE_PATH,
                                        '%s+f+app+++%s' % (local_id, key),
                                        self.idb_dir,
                                        '2584670174dsitanleecreR.sqlite'])
                self.logger.debug('Pushing %s to %s' % (db, destination))
                self.device.push_file(db, destination=destination)
                self.logger.debug('Removing %s' % db)
                os.remove(db)
                if restart:
                    self.start_b2g()
                break

    def populate_contacts(self, count, restart=True, include_pictures=True):
        # only allow preset db values for contacts
        db_counts = DB_PRESET_COUNTS['contact']
        if not count in db_counts:
            raise InvalidCountError('contact')
        self.device.manager.removeDir('/'.join([
            self.PERSISTENT_STORAGE_PATH, 'chrome',
            self.idb_dir, '*csotncta*']))
        self.logger.info('Populating %d contacts' % count)
        db_counts.sort(reverse=True)
        for marker in db_counts:
            if count >= marker:
                db_zip_name = pkg_resources.resource_filename(
                    __name__, os.path.sep.join(['resources',
                                                'contactsDb.zip']))
                db_name = 'contactsDb-%d.sqlite' % marker
                self.logger.debug('Extracting %s from %s' % (
                    db_name, db_zip_name))
                db = ZipFile(db_zip_name).extract(db_name)
                if restart:
                    self.device.stop_b2g()
                destination = '/'.join([
                    self.PERSISTENT_STORAGE_PATH, 'chrome', self.idb_dir,
                    '3406066227csotncta.sqlite'])
                self.logger.debug('Pushing %s to %s' % (db, destination))
                self.device.push_file(db, destination=destination)
                self.logger.debug('Removing %s' % db)
                os.remove(db)
                if marker > 0 and include_pictures:
                    self.logger.debug('Adding contact pictures')
                    pictures_zip_name = pkg_resources.resource_filename(
                        __name__, os.path.sep.join(
                            ['resources', 'contactsPictures.zip']))
                    temp = tempfile.mkdtemp()
                    self.logger.debug('Extracting %s to %s' % (
                        pictures_zip_name, temp))
                    ZipFile(pictures_zip_name).extractall(temp)
                    destination = '/'.join([
                        self.PERSISTENT_STORAGE_PATH, 'chrome', self.idb_dir,
                        '3406066227csotncta'])
                    self.logger.debug('Pushing %s to %s' % (temp, destination))
                    self.device.manager.pushDir(temp, destination)
                    self.logger.debug('Removing %s' % temp)
                    shutil.rmtree(temp)
                if restart:
                    self.start_b2g()
                break

    def populate_events(self, count, restart=True):
        # only allow preset db values for events
        db_counts = DB_PRESET_COUNTS['event']
        if not count in db_counts:
            raise InvalidCountError('event')
        self.logger.info('Populating %d events' % count)
        db_counts.sort(reverse=True)
        for marker in db_counts:
            if count >= marker:
                key = 'calendar.gaiamobile.org'
                local_id = json.loads(self.device.manager.pullFile(
                    '/data/local/webapps/webapps.json'))[key]['localId']
                db_zip_name = pkg_resources.resource_filename(
                    __name__, os.path.sep.join(['resources',
                                                'calendarDb.zip']))
                db_name = 'calendarDb-%d.sqlite' % marker
                self.logger.debug('Extracting %s from %s' % (
                    db_name, db_zip_name))
                db = ZipFile(db_zip_name).extract(db_name)
                if restart:
                    self.device.stop_b2g()
                destination = '/'.join([self.PERSISTENT_STORAGE_PATH,
                                        '%s+f+app+++%s' % (local_id, key),
                                        self.idb_dir,
                                        '125582036br2agd-nceal.sqlite'])
                self.logger.debug('Pushing %s to %s' % (db, destination))
                self.device.push_file(db, destination=destination)
                self.logger.debug('Removing %s' % db)
                os.remove(db)
                if restart:
                    self.start_b2g()
                break

    def populate_messages(self, count, restart=True):
        # only allow preset db values for messages
        db_counts = DB_PRESET_COUNTS['message']
        if not count in db_counts:
            raise InvalidCountError('message')
        self.logger.info('Populating %d messages' % count)
        db_counts.sort(reverse=True)
        for marker in db_counts:
            if count >= marker:
                db_zip_name = pkg_resources.resource_filename(
                    __name__, os.path.sep.join(['resources', 'smsDb.zip']))
                db_name = 'smsDb-%d.sqlite' % marker
                self.logger.debug('Extracting %s from %s' % (
                    db_name, db_zip_name))
                db = ZipFile(db_zip_name).extract(db_name)
                if restart:
                    self.device.stop_b2g()
                destination = '/'.join([
                    self.PERSISTENT_STORAGE_PATH, 'chrome', self.idb_dir,
                    '226660312ssm.sqlite'])
                self.logger.debug('Pushing %s to %s' % (db, destination))
                self.device.push_file(db, destination=destination)
                os.remove(db)
                if marker > 0:
                    self.logger.debug('Adding message attachments')
                    all_attachments_zip_name = pkg_resources.resource_filename(
                        __name__, os.path.sep.join(
                            ['resources', 'smsAttachments.zip']))
                    attachments_zip_name = 'smsAttachments-%d.zip' % marker
                    self.logger.debug('Extracting %s from %s' % (
                        attachments_zip_name, all_attachments_zip_name))
                    attachments_zip = ZipFile(
                        all_attachments_zip_name).extract(attachments_zip_name)
                    temp = tempfile.mkdtemp()
                    self.logger.debug('Extracting %s to %s' % (
                        attachments_zip, temp))
                    ZipFile(attachments_zip).extractall(temp)
                    destination = '/'.join([
                        self.PERSISTENT_STORAGE_PATH, 'chrome', self.idb_dir,
                        '226660312ssm'])
                    self.logger.debug('Pushing %s to %s' % (temp, destination))
                    self.device.manager.pushDir(temp, destination)
                    self.logger.debug('Removing %s' % temp)
                    shutil.rmtree(temp)
                    self.logger.debug('Removing %s' % attachments_zip)
                    os.remove(attachments_zip)
                if restart:
                    self.start_b2g()
                break

    def populate_music(self, count, source='MUS_0001.mp3',
                       destination='sdcard', tracks_per_album=10):
        self.remove_media('music')

        import math
        from mutagen.easyid3 import EasyID3
        music_file = pkg_resources.resource_filename(
            __name__, os.path.sep.join(['resources', source]))
        local_filename = music_file.rpartition(os.path.sep)[-1]

        # copy the mp3 file into a temp location
        with tempfile.NamedTemporaryFile() as local_copy:
            self.logger.debug('Creating copy of %s at %s' % (
                music_file, local_copy.name))
            local_copy.write(open(music_file).read())
            music_file = local_copy.name

            mp3 = EasyID3(music_file)
            album_count = math.ceil(float(count) / tracks_per_album)
            self.logger.info('Populating %d music files (%d album%s)' % (
                count, album_count, 's' if album_count > 1 else ''))

            for i in range(1, count + 1):
                album = math.ceil(float(i) / float(tracks_per_album))
                track = i - ((album - 1) * tracks_per_album)
                mp3['title'] = 'Track %d' % track
                mp3['artist'] = 'Artist %d' % album
                mp3['album'] = 'Album %d' % album
                mp3['tracknumber'] = str(track)
                mp3.save()
                remote_filename = '_%s.'.join(
                    iter(local_filename.split('.'))) % i
                remote_destination = os.path.join(destination, remote_filename)
                self.logger.debug('Pushing %s to %s' % (
                    music_file, remote_destination))
                self.device.push_file(music_file, 1, remote_destination)

    def populate_pictures(self, count, source='IMG_0001.jpg',
                          destination='sdcard/DCIM/100MZLLA'):
        self.populate_files('picture', source, count, destination)

    def populate_videos(self, count, source='VID_0001.3gp',
                        destination='sdcard/DCIM/100MZLLA'):
        self.populate_files('video', source, count, destination)

    def populate_files(self, file_type, source, count, destination=''):
        self.remove_media(file_type)

        self.logger.info('Populating %d %s files' % (count, file_type))
        source_file = pkg_resources.resource_filename(
            __name__, os.path.sep.join(['resources', source]))
        self.logger.debug('Pushing %d copies of %s to %s' % (
            count, source_file, destination))
        self.device.push_file(source_file, count, destination)

    def remove_media(self, file_type):
        if self.device.is_android_build:
            files = getattr(self.data_layer, '%s_files' % file_type) or []
            if len(files) > 0:
                self.logger.info('Removing %d %s files' % (
                    len(files), file_type))
                for filename in files:
                    self.logger.debug('Removing %s' % filename)
                    self.device.manager.removeFile(filename)
                # TODO Wait for files to be deleted
                time.sleep(5)
                files = getattr(self.data_layer, '%s_files' % file_type) or []
            if not len(files) == 0:
                raise IncorrectCountError(
                    '%s files' % file_type, 0, len(files))

    def start_b2g(self):
        self.logger.debug('Starting B2G')
        self.device.start_b2g()
        self.data_layer = GaiaData(self.marionette)
Example #15
0
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()
Example #16
0
def do_login(args):
    mc = get_marionette(args)
    device = GaiaDevice(mc)
    apps = GaiaApps(mc)
    data_layer = GaiaData(mc)
    mc.setup_touch()

    _persona_frame_locator = ('css selector', "iframe")

    # Trusty UI on home screen
    _tui_container_locator = ('id', 'trustedui-frame-container')

    # Persona dialog
    _waiting_locator = ('css selector', 'body.waiting')
    _email_input_locator = ('id', 'authentication_email')
    _password_input_locator = ('id', 'authentication_password')
    _new_password = ('id', 'password')
    _verify_new_password = ('id', 'vpassword')
    _next_button_locator = ('css selector', 'button.start')
    _verify_start_button = ('css selector', 'button#verify_user')
    _returning_button_locator = ('css selector', 'button.returning')
    _sign_in_button_locator = ('id', 'signInButton')
    _this_session_only_button_locator = ('id', 'this_is_not_my_computer')

    # Switch to top level frame then Persona frame
    mc.switch_to_frame()
    wait_for_element_present(mc, *_tui_container_locator)
    trustyUI = mc.find_element(*_tui_container_locator)
    wait_for_condition(
        mc, lambda m: trustyUI.find_element(*_persona_frame_locator))
    personaDialog = trustyUI.find_element(*_persona_frame_locator)
    mc.switch_to_frame(personaDialog)

    try:
        ready = mc.find_element(*_email_input_locator).is_displayed()
    except NoSuchElementException:
        ready = False
    if not ready:
        print 'Persona email input is not present.'
        print 'Are you on a new login screen?'
        return

    done = False
    while not done:
        username = raw_input('Persona username: '******'password: '******'Not a new account? Trying to log in to existing account'
        # Logging into an exisiting account:
        password_field = mc.find_element(*_password_input_locator)
        password_field.send_keys(password)
        wait_for_element_displayed(mc, *_returning_button_locator)
        mc.tap(mc.find_element(*_returning_button_locator))  #.click()

    print 'You should be logged in now'
Example #17
0
def set_up_device(args):
    mc = get_marionette(args)
    device = GaiaDevice(mc)
    try:
        device.restart_b2g()
    except Exception:
        print ' ** Check to make sure you don\'t have desktop B2G running'
        raise

    apps = GaiaApps(mc)
    data_layer = GaiaData(mc)
    lockscreen = LockScreen(mc)

    lockscreen.unlock()
    apps.kill_all()

    if args.wifi_ssid:
        print 'Configuring WiFi'
        if not args.wifi_key or not args.wifi_pass:
            args.error('Missing --wifi_key or --wifi_pass option')
        args.wifi_key = args.wifi_key.upper()

        data_layer.enable_wifi()
        if args.wifi_key == 'WPA-PSK':
            pass_key = 'psk'
        elif args.wifi_key == 'WEP':
            pass_key = 'wep'
        else:
            args.error('not sure what key to use for %r' % args.wifi_key)

        data = {'ssid': args.wifi_ssid, 'keyManagement': args.wifi_key,
                pass_key: args.wifi_pass}
        data_layer.connect_to_wifi(data)

    for manifest in args.apps:
        # There is probably a way easier way to do this by adb pushing
        # something. Send me a patch!
        mc.switch_to_frame()
        try:
            data = requests.get(manifest).json()
            app_name = data['name']
            all_apps = set(a['manifest']['name'] for a in get_installed(apps))
            if app_name not in all_apps:
                print 'Installing %s from %s' % (app_name, manifest)
                mc.execute_script('navigator.mozApps.install("%s");' % manifest)
                wait_for_element_displayed(mc, 'id', 'app-install-install-button')
                mc.find_element('id', 'app-install-install-button').tap()
                wait_for_element_not_displayed(mc, 'id', 'app-install-install-button')

                # confirm that app got installed
                homescreen_frame = mc.find_element(*('css selector',
                                                   'div.homescreen iframe'))
                mc.switch_to_frame(homescreen_frame)
                _app_icon_locator = ('xpath', "//li[@class='icon']//span[text()='%s']" % app_name)
                try:
                    mc.find_element(*_app_icon_locator)
                except NoSuchElementException:
                    args.error('Error: app could not be installed.')

        except Exception, exc:
            print ' ** installing manifest %s failed (maybe?)' % manifest
            print ' ** error: %s: %s' % (exc.__class__.__name__, exc)
            continue
Example #18
0
    def run(self,
            script,
            address='localhost:2828',
            symbols=None,
            treeherder='https://treeherder.mozilla.org/',
            reset=False,
            **kwargs):
        try:
            host, port = address.split(':')
        except ValueError:
            raise ValueError('--address must be in the format host:port')

        # Check that Orangutan is installed
        self.adb_device = ADBDevice(self.device_serial)
        orng_path = posixpath.join('data', 'local', 'orng')
        if not self.adb_device.exists(orng_path):
            raise Exception('Orangutan not found! Please install it according '
                            'to the documentation.')

        self.runner = B2GDeviceRunner(serial=self.device_serial,
                                      process_args={'stream': None},
                                      symbols_path=symbols,
                                      logdir=self.temp_dir)

        if reset:
            self.runner.start()
        else:
            self.runner.device.connect()

        port = self.runner.device.setup_port_forwarding(remote_port=port)
        assert self.runner.device.wait_for_port(port), \
            'Timed out waiting for port!'

        marionette = Marionette(host=host, port=port)
        marionette.start_session()

        try:
            marionette.set_context(marionette.CONTEXT_CHROME)
            self.is_debug = marionette.execute_script(
                'return Components.classes["@mozilla.org/xpcom/debug;1"].'
                'getService(Components.interfaces.nsIDebug2).isDebugBuild;')
            marionette.set_context(marionette.CONTEXT_CONTENT)

            if reset:
                gaia_device = GaiaDevice(marionette)
                gaia_device.wait_for_b2g_ready(timeout=120)
                gaia_device.unlock()
                gaia_apps = GaiaApps(marionette)
                gaia_apps.kill_all()

            # TODO: Disable bluetooth, emergency calls, carrier, etc

            # Run Orangutan script
            remote_script = posixpath.join(self.adb_device.test_root,
                                           'orng.script')
            self.adb_device.push(script, remote_script)
            self.start_time = time.time()
            # TODO: Kill remote process on keyboard interrupt
            self.adb_device.shell(
                '%s %s %s' %
                (orng_path, self.device_properties['input'], remote_script))
            self.end_time = time.time()
            self.adb_device.rm(remote_script)
        except (MarionetteException, IOError):
            if self.runner.crashed:
                # Crash has been detected
                pass
            else:
                raise
        self.runner.check_for_crashes(test_name='b2gmonkey')

        # Report results to Treeherder
        required_envs = ['TREEHERDER_KEY', 'TREEHERDER_SECRET']
        if all([os.environ.get(v) for v in required_envs]):
            self.post_to_treeherder(script, treeherder)
        else:
            self._logger.info(
                'Results will not be posted to Treeherder. Please set the '
                'following environment variables to enable Treeherder '
                'reports: %s' %
                ', '.join([v for v in required_envs if not os.environ.get(v)]))
Example #19
0
def set_up_device(args):
    mc = get_marionette(args)
    device = GaiaDevice(mc)
    try:
        device.restart_b2g()
    except Exception:
        print ' ** Check to make sure you don\'t have desktop B2G running'
        raise

    apps = GaiaApps(mc)
    data_layer = GaiaData(mc)
    lockscreen = LockScreen(mc)
    mc.setup_touch()

    lockscreen.unlock()
    apps.kill_all()

    if args.wifi_ssid:
        print 'Configuring WiFi'
        if not args.wifi_key or not args.wifi_pass:
            args.error('Missing --wifi_key or --wifi_pass option')
        args.wifi_key = args.wifi_key.upper()

        data_layer.enable_wifi()
        if args.wifi_key == 'WPA-PSK':
            pass_key = 'psk'
        elif args.wifi_key == 'WEP':
            pass_key = 'wep'
        else:
            args.error('not sure what key to use for %r' % args.wifi_key)

        data = {
            'ssid': args.wifi_ssid,
            'keyManagement': args.wifi_key,
            pass_key: args.wifi_pass
        }
        data_layer.connect_to_wifi(data)

    for manifest in args.apps:
        # There is probably a way easier way to do this by adb pushing
        # something. Send me a patch!
        mc.switch_to_frame()
        try:
            data = requests.get(manifest).json()
            app_name = data['name']
            all_apps = set(a['manifest']['name'] for a in get_installed(apps))
            if app_name not in all_apps:
                print 'Installing %s from %s' % (app_name, manifest)
                mc.execute_script('navigator.mozApps.install("%s");' %
                                  manifest)
                wait_for_element_displayed(mc, 'id',
                                           'app-install-install-button')
                yes = mc.find_element('id', 'app-install-install-button')
                mc.tap(yes)
                # This still works but the id check broke.
                # See https://bugzilla.mozilla.org/show_bug.cgi?id=853878
                wait_for_element_displayed(mc, 'id', 'system-banner')
        except Exception, exc:
            print ' ** installing manifest %s failed (maybe?)' % manifest
            print ' ** error: %s: %s' % (exc.__class__.__name__, exc)
            continue
Example #20
0
    def run(self, script, address='localhost:2828', symbols=None,
            treeherder='https://treeherder.mozilla.org/', reset=False,
            **kwargs):
        try:
            host, port = address.split(':')
        except ValueError:
            raise ValueError('--address must be in the format host:port')

        # Check that Orangutan is installed
        self.adb_device = ADBDevice(self.device_serial)
        orng_path = posixpath.join('data', 'local', 'orng')
        if not self.adb_device.exists(orng_path):
            raise Exception('Orangutan not found! Please install it according '
                            'to the documentation.')

        self.runner = B2GDeviceRunner(
            serial=self.device_serial,
            process_args={'stream': None},
            symbols_path=symbols,
            logdir=self.temp_dir)

        if reset:
            self.runner.start()
        else:
            self.runner.device.connect()

        port = self.runner.device.setup_port_forwarding(remote_port=port)
        assert self.runner.device.wait_for_port(port), \
            'Timed out waiting for port!'

        marionette = Marionette(host=host, port=port)
        marionette.start_session()

        try:
            marionette.set_context(marionette.CONTEXT_CHROME)
            self.is_debug = marionette.execute_script(
                'return Components.classes["@mozilla.org/xpcom/debug;1"].'
                'getService(Components.interfaces.nsIDebug2).isDebugBuild;')
            marionette.set_context(marionette.CONTEXT_CONTENT)

            if reset:
                gaia_device = GaiaDevice(marionette)
                gaia_device.wait_for_b2g_ready(timeout=120)
                gaia_device.unlock()
                gaia_apps = GaiaApps(marionette)
                gaia_apps.kill_all()

            # TODO: Disable bluetooth, emergency calls, carrier, etc

            # Run Orangutan script
            remote_script = posixpath.join(self.adb_device.test_root,
                                           'orng.script')
            self.adb_device.push(script, remote_script)
            self.start_time = time.time()
            # TODO: Kill remote process on keyboard interrupt
            self.adb_device.shell('%s %s %s' % (orng_path,
                                                self.device_properties['input'],
                                                remote_script))
            self.end_time = time.time()
            self.adb_device.rm(remote_script)
        except (MarionetteException, IOError):
            if self.runner.crashed:
                # Crash has been detected
                pass
            else:
                raise
        self.runner.check_for_crashes(test_name='b2gmonkey')

        # Report results to Treeherder
        required_envs = ['TREEHERDER_KEY', 'TREEHERDER_SECRET']
        if all([os.environ.get(v) for v in required_envs]):
            self.post_to_treeherder(script, treeherder)
        else:
            self._logger.info(
                'Results will not be posted to Treeherder. Please set the '
                'following environment variables to enable Treeherder '
                'reports: %s' % ', '.join([
                    v for v in required_envs if not os.environ.get(v)]))
Example #21
0
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