Class-ify the viSer suite deployment methods
Clean-up any previous deployment before deploying the suite again.
Previous reports will be lost.
Issue: INFRA-236
Change-Id: I246af3c41140c9a936d0b5b561903cab34003be0
diff --git a/deploy.py b/deploy.py
index 57a9c7b..a24ca86 100755
--- a/deploy.py
+++ b/deploy.py
@@ -1,25 +1,19 @@
#!/usr/bin/env python3
+import dataclasses
import logging
import pathlib
import re
import subprocess
import sys
import time
+from typing import Sequence
import uiautomator
-VWS_CREDENTIALS = {"user": "fairphonetesting@gmail.com", "password": "aish3echi:uwaiSh"}
-
-
-PREBUILTS_PATH = "../../vendor/smartviser/viser/prebuilts/apk"
-
-PREBUILT_PROXY_APK_PATTERN = "com.lunarlabs.panda.proxy-latest-sdk{sdk}-{flavour}.apk"
-
-
_LOG = logging.getLogger(__name__)
-PREBUILT_APKS = ["com.smartviser.demogame-latest.apk", "com.lunarlabs.panda-latest.apk"]
+VWS_CREDENTIALS = {"user": "fairphonetesting@gmail.com", "password": "aish3echi:uwaiSh"}
ADB_DEVICES_PATTERN = re.compile(r"^([a-z0-9-]+)\s+device$", flags=re.M)
@@ -152,38 +146,6 @@
disable_privacy_impact_checkbox.click()
-def get_proxy_apk(android_sdk, flavour):
- if android_sdk >= 24:
- return PREBUILT_PROXY_APK_PATTERN.format(sdk=24, flavour=flavour)
- else:
- return PREBUILT_PROXY_APK_PATTERN.format(sdk=19, flavour=flavour)
-
-
-# Prepare the DUT
-def prepare_dut(device, scenarios_dir, data_dir, prebuilts_dir):
- flavour = "gms" if device.is_gms_device() else "sibon"
- proxy_apk = get_proxy_apk(device.sdk, flavour)
- prebuilts_dir = pathlib.Path(prebuilts_dir)
-
- # Uninstall the smartviser apps
- for app in PREBUILT_APKS + [proxy_apk]:
- print("Uninstalling `{}`…".format(app))
- device.uninstall(prebuilts_dir / app)
-
- # Copy the scenarios
- print("Pushing scenarios from `{}`…".format(scenarios_dir))
- device.push(scenarios_dir, pathlib.Path("/sdcard/Viser"))
-
- # Copy the scenarios data
- print("Pushing scenarios data from `{}`…".format(data_dir))
- device.push(data_dir, pathlib.Path("/sdcard/Viser/Data"))
-
- # Install the smartviser apps (starting with the proxy app)
- for app in [proxy_apk] + PREBUILT_APKS:
- print("Installing `{}`…".format(app))
- device.install(prebuilts_dir / app)
-
-
# Grant the permissions through the UI
def configure_perms(device):
# Input the credentials
@@ -231,6 +193,8 @@
serial: str
"""The device serial number (adb/fastboot)."""
+ os_flavour: str
+ """The Fairphone-specific OS flavour."""
sdk: int
"""The Android SDK version number."""
ui: uiautomator.Device
@@ -242,6 +206,8 @@
# Cache the Android SDK version
self.sdk = self.ui.info["sdkInt"]
+ # Cache the OS flavour
+ self.os_flavour = "gms" if self.is_gms_device() else "sibon"
def adb(self, *args) -> subprocess.CompletedProcess:
"""Execute an adb command on this device.
@@ -322,6 +288,13 @@
self.adb("uninstall", package)
+ def uninstall_package(self, package_name: str) -> None:
+ """Uninstall an app from its package name.
+
+ :raise DeviceCommandError: If the uninstall command failed.
+ """
+ self.adb("uninstall", package_name)
+
def install(self, apk: pathlib.Path) -> None:
"""Install an app from an APK file.
@@ -367,8 +340,155 @@
self.adb(*command)
+@dataclasses.dataclass(frozen=True)
+class AndroidApp:
+ """An installable Android app."""
+
+ package: str
+ """The app package name."""
+ apk: str
+ """The app package (APK) filename."""
+
+ def __str__(self) -> str:
+ return self.package
+
+
+@dataclasses.dataclass(frozen=True)
+class ViserProxyApp:
+ """A template for the viSer Proxy app."""
+
+ package: str
+ """The app package name."""
+ apk_filename_template: str
+ """The string template of the APK filename, in the `str.format()` style (`{}`).
+
+ The following place-holders can be used and will be replaced at runtime:
+ - `{sdk}`: The Android SDK number, e.g. `25` for Android 7.1.
+ - `{flavour}`: The Fairphone-specific Android build flavour, e.g. `gms`
+ (Fairphone OS) or `sibon` (Fairphone Open).
+ """
+
+ def resolve(self, *, sdk: int, flavour: str) -> AndroidApp:
+ """Resolve the app template into a Viser App."""
+ if sdk >= 24:
+ sdk = 24
+ else:
+ sdk = 19
+ return AndroidApp(
+ self.package, self.apk_filename_template.format(sdk=sdk, flavour=flavour)
+ )
+
+
+@dataclasses.dataclass(frozen=True)
+class ViserSuite:
+ """A SmartViser viSer app suite.
+
+ Example:
+
+ >>> device = DeviceUnderTest(...)
+ >>> suite = ViserSuite(...)
+ >>> suite.deploy(device)
+
+ """
+
+ VISER_PROXY_APP_TEMPLATE = ViserProxyApp(
+ "com.lunarlabs.panda.proxy",
+ "com.lunarlabs.panda.proxy-latest-sdk{sdk}-{flavour}.apk",
+ )
+ ANDROID_APPS = [
+ AndroidApp("com.smartviser.demogame", "com.smartviser.demogame-latest.apk"),
+ AndroidApp("com.lunarlabs.panda", "com.lunarlabs.panda-latest.apk"),
+ ]
+
+ prebuilts_path: pathlib.Path
+ scenarios_path: pathlib.Path
+ scenarios_data_path: pathlib.Path
+ target_path: pathlib.Path = pathlib.Path("/sdcard/Viser")
+
+ @property
+ def target_scenarios_path(self) -> pathlib.Path:
+ return self.target_path
+
+ @property
+ def target_scenarios_data_path(self) -> pathlib.Path:
+ return self.target_path / "Data"
+
+ def resolve_apps(self, device: DeviceUnderTest) -> Sequence[AndroidApp]:
+ """Resolve the apps based on the target device properties.
+
+ :param device: The device to target.
+ :returns: The sequence of apps suitable for the target device.
+ The sequence is ordered to satisfy the dependency graph
+ (i.e. required apps come first in the sequence).
+ """
+ return [
+ self.VISER_PROXY_APP_TEMPLATE.resolve(
+ sdk=device.sdk, flavour=device.os_flavour
+ )
+ ] + self.ANDROID_APPS
+
+ def deploy(self, device: DeviceUnderTest) -> None:
+ """Deploy the suite on a device.
+
+ Copy the test scenarios and their data, and install the
+ different apps composing the suite.
+
+ The previous configuration and data (i.e. reports) tied to the
+ app suite, if any, is deleted before hand.
+
+ :param device: The device to deploy the suite to.
+ """
+ self.cleanup_previous_deployment(device)
+ self.copy_scenarios(device)
+ self.install_suite(device)
+
+ def cleanup_previous_deployment(self, device: DeviceUnderTest) -> None:
+ """Clean-up a previous deployment of the suite on a device.
+
+ :param device: The device to clean-up.
+ """
+ # Uninstall the apps in the reverse order to cater for dependencies.
+ for app in reversed(self.resolve_apps(device)):
+ _LOG.info("Uninstall %s", app)
+ device.uninstall_package(app.package)
+
+ _LOG.info("Delete data from previous deployment")
+ device.remove(self.target_path, recurse=True)
+
+ def copy_scenarios(self, device: DeviceUnderTest) -> None:
+ """Copy the suite scenarios and their data on a device.
+
+ :param device: The device to copy the scenarios to.
+ """
+ _LOG.info(
+ "Copy scenarios: %s → %s", self.scenarios_path, self.target_scenarios_path
+ )
+ device.push(self.scenarios_path, self.target_scenarios_path)
+
+ _LOG.info(
+ "Copy scenarios data: %s → %s",
+ self.scenarios_data_path,
+ self.target_scenarios_data_path,
+ )
+ device.push(self.scenarios_path, self.target_scenarios_data_path)
+
+ def install_suite(self, device: DeviceUnderTest) -> None:
+ """Install the suite apps on a device.
+
+ :param device: The device to install the suite apps on.
+ """
+ for app in self.resolve_apps(device):
+ _LOG.info("Install %s", app)
+ device.install(self.prebuilts_path / app.apk)
+
+
def deploy():
serials = []
+ suite = ViserSuite(
+ pathlib.Path("../../vendor/smartviser/viser/prebuilts/apk"),
+ pathlib.Path("../scenarios"),
+ pathlib.Path("../scenarios-data"),
+ )
if len(sys.argv) > 1:
serials.append(sys.argv[1])
@@ -389,12 +509,7 @@
disable_privacy_impact_popup(device)
# Push the scenarios, their data, and install the apps
- prepare_dut(
- device,
- pathlib.Path("../scenarios"),
- pathlib.Path("../scenarios-data"),
- PREBUILTS_PATH,
- )
+ suite.deploy(device)
# Start the viser app
device.adb(