def test_verify_install_on_non_installed_app(self, device: Device, in_tmp_dir: Path): with pytest.raises(expected_exception=Exception) as excinfo: device._verify_install("fake/app/path", "com.linkedin.fake.app", "test_screenshots") assert "Failed to verify installation of app 'com.linkedin.fake.app'" in str( excinfo.value) assert (in_tmp_dir / "test_screenshots" / "install_failure-com.linkedin.fake.app.png").is_file()
def test_install_uninstall_app(self, device: Device, support_app: str): app = Application.from_apk(support_app, device) app.uninstall() assert app.package_name not in device.list_installed_packages() app = Application.from_apk(support_app, device) assert app.package_name in device.list_installed_packages() app.uninstall() assert app.package_name not in device.list_installed_packages()
def test_get_invalid_device_setting(self, device: Device): try: if int(device.get_system_property( "ro.product.first_api_level")) < 26: assert device.get_device_setting("invalid", "nosuchkey") is '' else: assert device.get_device_setting("invalid", "nosuchkey") is None except: assert device.get_device_setting("invalid", "nosuchkey") is None
def test_process_set_property_cmd(self, device: Device, listener: PropertyListener, test_setup: Tuple[ServiceApplication, Application]): butler_service, app = test_setup device.set_system_property(key="debug.mock_key", value="mock_value") assert device.get_system_property("debug.mock_key") == "mock_value" TestButlerCommandParser(butler_service, app_under_test=app, listener=listener).\ _process_set_property_cmd("debug.mock_key mock_new_value") if listener: assert listener.property_change_detected is True assert device.get_system_property("debug.mock_key") == "mock_new_value"
def test_get_device_datetime(self, device: Device): import time import datetime host_datetime = datetime.datetime.utcnow() dtime = device.get_device_datetime() host_delta = (host_datetime - dtime).total_seconds() time.sleep(1) host_datetime_delta = (datetime.datetime.utcnow() - device.get_device_datetime()).total_seconds() timediff = device.get_device_datetime() - dtime assert timediff.total_seconds() >= 0.99 assert host_datetime_delta - host_delta < 0.01
def test_reverse_port_forward(self, device: Device): device_network = DeviceConnectivity(device) device_network.reverse_port_forward(32451, 29323) completed = device.execute_remote_cmd("reverse", "--list", stdout=subprocess.PIPE) assert "29323" in completed.stdout device_network.remove_reverse_port_forward(32451) completed = device.execute_remote_cmd("reverse", "--list", stdout=subprocess.PIPE) assert "29323" not in completed.stdout assert "32451" not in completed.stdout
def test_process_set_device_setting_cmd(self, device: Device, listener: Optional[SettingListener], test_setup: Tuple[ServiceApplication, Application]): butler_service, app = test_setup # start with a known confirmed value device.set_device_setting(namespace="system", key="volume_music", value=SettingListener.START_VALUE) assert device.get_device_setting(namespace="system", key="volume_music") == SettingListener.START_VALUE # now set it through test butler to known different value TestButlerCommandParser(butler_service, app_under_test=app, listener=listener).\ _process_set_device_setting_cmd("system volume_music %s" % SettingListener.NEW_VALUE) # Listener will assert on keys and values and will set a flag to test here: if listener is not None: assert listener.setting_change_detected assert device.get_device_setting(namespace="system", key="volume_music") == "6"
def test_oneshot_cpu_mem(self, device: Device, support_app: str): app = Application.from_apk(support_app, device) app.monkey() time.sleep(1) cpu, mem = device.oneshot_cpu_mem(app.package_name) app.stop(force=True) assert cpu is not None assert mem is not None
def test_foreground_and_activity_detection(self, install_app, device: Device, support_app: str): app = install_app(Application, support_app) device_nav = DeviceInteraction(device) # By default, emulators should always start into the home screen assert device_nav.home_screen_active() # Start up an app and test home screen is no longer active, and foreground app is correct app.start(activity=".MainActivity") assert not device_nav.home_screen_active() assert device.foreground_activity() == app.package_name
def device_queue(): m = multiprocessing.Manager() AVD = "MTO_emulator" CONFIG = EmulatorBundleConfiguration( sdk=Path(support.find_sdk()), boot_timeout=10 * 60 # seconds ) ARGS = [ "-no-window", "-no-audio", "-wipe-data", "-gpu", "off", "-no-boot-anim", "-skin", "320x640", "-partition-size", "1024", ] support.ensure_avd(str(CONFIG.sdk), AVD) if TAG_MTO_DEVICE_ID in os.environ: queue = m.Queue(1) queue.put(Device(TAG_MTO_DEVICE_ID, adb_path=find_sdk())) else: if IS_CIRCLECI: Device.TIMEOUT_ADB_CMD *= 10 # slow machine ARGS.append("-no-accel") # on circleci, do build first to not take up too much # memory if emulator were started first count = 1 else: max_count = min(multiprocessing.cpu_count(), 6) count = int(os.environ.get("MTO_EMULATOR_COUNT", f"{max_count}")) queue = m.Queue(count) # launch emulators in parallel and wait for all to boot: async def launch(index: int): if index: await asyncio.sleep( index * 2 ) # stabilizes the launches spacing them out (otherwise, intermittend fail to boot) return await Emulator.launch(Emulator.PORTS[index], AVD, CONFIG, *ARGS) ems = asyncio.get_event_loop().run_until_complete( asyncio.gather(*[launch(index) for index in range(count)])) for em in ems: queue.put(em) try: yield queue finally: for em in ems: em.kill()
def test_invalid_output_path(self, fake_sdk, tmpdir): device = Device("fakeid", os.path.join(fake_sdk, "platform-tools", "adb")) tmpfile = os.path.join(str(tmpdir), "somefile") with open(tmpfile, 'w')as f: pass with pytest.raises(Exception) as exc_info: DeviceLog.LogCapture(device, tmpfile) assert "Path %s already exists; will not overwrite" % tmpfile in str(exc_info) with pytest.raises(Exception): logcap = DeviceLog.LogCapture(device, os.path.join(tmpdir, "newfile")) logcap.mark_end("proc_not_started_so_throw_exception")
def test_get_set_locale(self, device: Device, local_changer_apk): # noqa app = Application.from_apk(local_changer_apk, device) app.grant_permissions([" android.permission.CHANGE_CONFIGURATION"]) device.set_locale("en_US") assert device.get_locale() == "en_US" device.set_locale("fr_FR") assert device.get_locale() == "fr_FR"
def test_process_grant_permission_cmd(self, device: Device, test_setup: Tuple[ServiceApplication, Application]): butler_service, app = test_setup grant_permissions_invoked = False output = device.execute_remote_cmd("shell", "dumpsys", "package", app.package_name, capture_stdout=True) permission_to_grant = "android.permission.WRITE_EXTERNAL_STORAGE" # first make sure permission not already granted: for line in output.splitlines(): assert "%s: granted=true" % permission_to_grant not in line # process commdand through parser parser = TestButlerCommandParser(butler_service, app_under_test=app, listener=None) assert parser._process_grant_permission_cmd(cmd=json.dumps({'type': 'permission', 'package': app.package_name, 'permissions': [permission_to_grant] })) == (0, "Success") # ensure permission was indeed granted output = device.execute_remote_cmd("shell", "dumpsys", "package", app.package_name, capture_stdout=True) for line in output.splitlines(): if "%s: granted=true" % permission_to_grant in line: grant_permissions_invoked = True assert grant_permissions_invoked is True
def test_set_invalid_system_property(self, device: Device): try: api_is_old = int( device.get_system_property("ro.build.version.sdk")) < 26 except: api_is_old = False if api_is_old: device.set_system_property("nosuchkey", "value") assert device.get_system_property("nosuchkey") is "" else: with pytest.raises(Exception) as exc_info: device.set_system_property("nosuchkey", "value") assert f"setprop nosuchkey value' on device {device.device_id}" in str( exc_info.value)
def test_foreign_apk_install(self, device: Device, support_app: str, support_test_app: str): with EspressoTestPreparation(device=device, path_to_test_apk=support_test_app, path_to_apk=support_app) as prep, \ DevicePreparation(device) as device_prep: now = device.get_device_setting("system", "dim_screen") new = {"1": "0", "0": "1"}[now] prep.test_app.uninstall() assert prep.test_app.package_name not in device.list_installed_packages( ) prep.setup_foreign_apps(paths_to_foreign_apks=[support_test_app]) assert prep.test_app.package_name in device.list_installed_packages( ) device.set_system_property("debug.mock2", "\"\"\"\"") device_prep.configure_device(settings={'system:dim_screen': new}, properties={"debug.mock2": "5555"}) assert device.get_system_property("debug.mock2") == "5555" assert device.get_device_setting("system", "dim_screen") == new
def test_parse_line(self, device: Device, test_butler_service: str, support_test_app: str): butler_service = ServiceApplication.from_apk(test_butler_service, device) app = Application.from_apk(support_test_app, device) # start with a known confirmed value device.set_device_setting(namespace="system", key="volume_music", value=SettingAndPropertyListener.START_VALUE) assert device.get_device_setting(namespace="system", key="volume_music") == \ SettingAndPropertyListener.START_VALUE device.set_system_property(key="debug.mock", value="mock_value") assert device.get_system_property("debug.mock") == "mock_value" listener = SettingAndPropertyListener() parser = TestButlerCommandParser(butler_service, app_under_test=app, listener=listener) for line in [ "I/TestButler( ): 1 TEST_BUTLER_SETTING: system volume_music %s" % SettingAndPropertyListener.NEW_VALUE, "I/TestButler( ): 2 TEST_BUTLER_PROPERTY: debug.mock 42"]: parser.parse_line(line) assert listener.setting_change_detected assert device.get_device_setting("system", "volume_music") == "1" assert listener.property_change_detected assert device.get_system_property("debug.mock") == "42"
async def launch(cls, port: int, avd: str, config: EmulatorBundleConfiguration, *args: str) -> "Emulator": """ Launch an emulator on the given port, with named avd and configuration :param args: add'l arguments to pass to emulator command """ if port not in cls.PORTS: raise ValueError(f"Port must be one of {cls.PORTS}") device_id = f"emulator-{port}" device = Device(device_id, str(config.adb_path())) with suppress(Exception): device.execute_remote_cmd( "emu", "kill") # attempt to kill any existing emulator at this port await asyncio.sleep(2) emulator_cmd = config.sdk.joinpath("emulator").joinpath("emulator") if not emulator_cmd.is_file(): raise Exception( f"Could not find emulator cmd to launch emulator @ {emulator_cmd}" ) if not config.adb_path().is_file(): raise Exception(f"Could not find adb cmd @ {config.adb_path()}") cmd = [ str(emulator_cmd), "-avd", avd, "-port", str(port), "-read-only" ] if sys.platform.lower() == 'win32': cmd[0] += ".bat" if config.system_img: cmd += ["-system", str(config.system_img)] if config.kernel: cmd += ["-kernel", str(config.kernel)] if config.ramdisk: cmd += ["-ramdisk", str(config.ramdisk)] cmd += args environ = dict(os.environ) environ["ANDROID_AVD_HOME"] = str(config.avd_dir) environ["ANDROID_SDK_HOME"] = str(config.sdk) booted = False proc = subprocess.Popen(cmd, stderr=subprocess.STDOUT, stdout=subprocess.PIPE, env=environ) try: async def wait_for_boot() -> None: nonlocal booted nonlocal proc nonlocal device_id while device.get_state().strip() != 'device': await asyncio.sleep(1) if proc.poll() is not None: stdout, _ = proc.communicate() raise Emulator.FailedBootError(port, stdout.decode('utf-8')) start = time.time() while not booted: booted = device.get_system_property( "sys.boot_completed", ) == "1" await asyncio.sleep(1) duration = time.time() - start print( f">>> {device.device_id} [{duration}] Booted?: {booted}" ) await asyncio.wait_for(wait_for_boot(), config.boot_timeout) return Emulator(device_id, config=config, launch_cmd=cmd, env=environ) except Exception as e: raise Emulator.FailedBootError(port, str(e)) from e finally: if not booted: with suppress(Exception): proc.kill()
def test_take_screenshot(self, device: Device, mp_tmp_dir): path = os.path.join(str(mp_tmp_dir), "test_screenshot.png") device.take_screenshot(os.path.join(str(mp_tmp_dir), path)) assert os.path.isfile(path) assert os.stat(path).st_size != 0
def test_take_screenshot_file_already_exists(self, device: Device, mp_tmp_dir): path = os.path.join(str(mp_tmp_dir), "created_test_screenshot.png") open(path, 'w+b') # create the file with pytest.raises(FileExistsError): device.take_screenshot(os.path.join(str(mp_tmp_dir), path))
def test_list_packages(self, install_app, device: Device, support_app: str): app = install_app(Application, support_app) pkgs = device.list_installed_packages() assert app.package_name in pkgs
def test_list_packages(self, device: Device, support_app: str): app = Application.from_apk(support_app, device) pkgs = device.list_installed_packages() assert app.package_name in pkgs
def sole_emulator(emulator): # kicks off emulator launch android_sdk = support.find_sdk() return Device(adb_path=os.path.join(android_sdk, "platform-tools", add_ext("adb")), device_id=emulator)
def test_get_set_system_property(self, device: Device): device.set_system_property("debug.mock2", "5555") assert device.get_system_property("debug.mock2") == "5555" device.set_system_property("debug.mock2", "\"\"\"\"")
def test_set_invalid_system_property(self, device: Device): with pytest.raises(Exception) as exc_info: device.set_system_property("nosuchkey", "value") assert "setprop: failed to set property 'nosuchkey' to 'value'" in str( exc_info.value)
def test_get_invalid_decvice_setting(self, device: Device): assert device.get_device_setting("invalid", "nosuchkey") is None
def test_get_set_device_setting(self, device: Device): now = device.get_device_setting("system", "dim_screen") new = {"1": "0", "0": "1"}[now] device.set_device_setting("system", "dim_screen", new) assert device.get_device_setting("system", "dim_screen") == new
def test_take_screenshot(self, device: Device, tmpdir): path = os.path.join(str(tmpdir), "test_screenshot.png") device.take_screenshot(os.path.join(str(tmpdir), path)) assert os.path.isfile(path)
def test_check_network_connect(self, device: Device): assert device.check_network_connection("localhost", count=3) == 0
def test_get_locale(self, device: Device): locale = device.get_locale() assert locale == "en_US"
def test_add_background_task(self, device: Device, support_app: str, support_test_app: str, tmpdir: str): # ensure applications are not already installed as precursor to running tests with suppress(Exception): Application(device, {'package_name': support_app}).uninstall() with suppress(Exception): Application(device, {'package_name': support_test_app}).uninstall() def test_generator(): yield (TestSuite( name='test_suite1', arguments=[ "-e", "class", "com.linkedin.mtotestapp.InstrumentedTestAllSuccess#useAppContext" ])) # noinspection PyMissingOrEmptyDocstring class EmptyListener(TestRunListener): _call_count = {} def test_run_started(self, test_run_name: str, count: int = 0): EmptyListener._call_count.setdefault(test_run_name, 0) EmptyListener._call_count[test_run_name] += 1 def test_run_ended(self, duration: float, **kwargs): pass def test_run_failed(self, error_message: str): pass def test_failed(self, class_name: str, test_name: str, stack_trace: str): pass def test_ignored(self, class_name: str, test_name: str): pass def test_assumption_failure(self, class_name: str, test_name: str, stack_trace: str): pass def test_started(self, class_name: str, test_name: str): pass def test_ended(self, class_name: str, test_name: str, **kwargs): pass was_called = False async def some_task(orchestrator: AndroidTestOrchestrator): """ For testing that user-defined background task was indeed executed """ nonlocal was_called was_called = True with pytest.raises(Exception): orchestrator.add_logcat_monitor("BogusTag", None) test_vectors = os.path.join(str(tmpdir), "test_vectors") os.makedirs(test_vectors) with open(os.path.join(test_vectors, "file"), 'w') as f: f.write("TEST VECTOR DATA") with AndroidTestOrchestrator(artifact_dir=str(tmpdir)) as orchestrator, \ EspressoTestPreparation(device=device, path_to_apk=support_app, path_to_test_apk=support_test_app, grant_all_user_permissions=True) as test_prep, \ DevicePreparation(device) as device_prep: device_prep.verify_network_connection("localhost", 4) device_prep.port_forward(5748, 5749) completed = device.execute_remote_cmd("forward", "--list", stdout=subprocess.PIPE) forwarded_ports = completed.stdout assert "5748" in forwarded_ports and "5749" in forwarded_ports device_prep.reverse_port_forward(5432, 5431) completed = device.execute_remote_cmd("reverse", "--list", stdout=subprocess.PIPE) reverse_forwarded_ports = completed.stdout assert "5432" in reverse_forwarded_ports and "5431" in reverse_forwarded_ports test_prep.upload_test_vectors(test_vectors) orchestrator.add_test_suite_listeners( [EmptyListener(), EmptyListener()]) orchestrator.add_background_task(some_task(orchestrator)) orchestrator.execute_test_plan(test_plan=test_generator(), test_application=test_prep.test_app) assert was_called, "Failed to call user-define background task" # listener was added a second time, so expect call counts of 2 assert all([v == 2 for v in EmptyListener._call_count.values()]) completed = device.execute_remote_cmd("forward", "--list", stdout=subprocess.PIPE) forwarded_ports = completed.stdout assert forwarded_ports.strip() == "" completed = device.execute_remote_cmd("reverse", "--list", stdout=subprocess.PIPE) reverse_forwarded_ports = completed.stdout assert reverse_forwarded_ports.strip() == ""