diff --git a/external/devlib/devlib/collector/dmesg.py b/external/devlib/devlib/collector/dmesg.py index 72529f53c667b71710d5c62e3444486d39b2e57a..799a965bb2b3aa0e41da1f67f18d6cccb63b9b6f 100644 --- a/external/devlib/devlib/collector/dmesg.py +++ b/external/devlib/devlib/collector/dmesg.py @@ -185,8 +185,13 @@ class DmesgCollector(CollectorBase): self.basic_dmesg = '--force-prefix' not in \ self.target.execute('dmesg -h', check_exit_code=False) self.facility = facility - self.needs_root = bool(target.config.typed_config.get( - 'CONFIG_SECURITY_DMESG_RESTRICT', KernelConfigTristate.NO)) + try: + needs_root = target.read_sysctl('kernel.dmesg_restrict') + except ValueError: + needs_root = True + else: + needs_root = bool(int(needs_root)) + self.needs_root = needs_root self._begin_timestamp = None self.empty_buffer = empty_buffer diff --git a/external/devlib/devlib/collector/ftrace.py b/external/devlib/devlib/collector/ftrace.py index 057d7322e6629e5b239ba63a349dbfa531aeed0b..d356154017eaff446ae2e2e987c627e5c26cb101 100644 --- a/external/devlib/devlib/collector/ftrace.py +++ b/external/devlib/devlib/collector/ftrace.py @@ -408,8 +408,7 @@ class FtraceCollector(CollectorBase): self.logger.debug(command) process = subprocess.Popen(command, stderr=subprocess.PIPE, shell=True) _, error = process.communicate() - if sys.version_info[0] == 3: - error = error.decode(sys.stdout.encoding or 'utf-8', 'replace') + error = error.decode(sys.stdout.encoding or 'utf-8', 'replace') if process.returncode: raise TargetStableError('trace-cmd returned non-zero exit code {}'.format(process.returncode)) if error: diff --git a/external/devlib/devlib/instrument/acmecape.py b/external/devlib/devlib/instrument/acmecape.py index ec0a77bf67b672735b5c03cc6786743f57a5d7ee..cfbcbe071188b90e47cd5a9b8586f82a8156edd5 100644 --- a/external/devlib/devlib/instrument/acmecape.py +++ b/external/devlib/devlib/instrument/acmecape.py @@ -116,10 +116,7 @@ class AcmeCapeInstrument(Instrument): msg = 'Could not terminate iio-capture:\n{}' raise HostError(msg.format(output)) if self.process.returncode != 15: # iio-capture exits with 15 when killed - if sys.version_info[0] == 3: - output += self.process.stdout.read().decode(sys.stdout.encoding or 'utf-8', 'replace') - else: - output += self.process.stdout.read() + output += self.process.stdout.read().decode(sys.stdout.encoding or 'utf-8', 'replace') self.logger.info('ACME instrument encountered an error, ' 'you may want to try rebooting the ACME device:\n' ' ssh root@{} reboot'.format(self.host)) diff --git a/external/devlib/devlib/instrument/energy_probe.py b/external/devlib/devlib/instrument/energy_probe.py index b0b51801a4f0cf58f86315617c579a34ff70d591..6a97c5bf2f150007a93b461222b43329e62e9f95 100644 --- a/external/devlib/devlib/instrument/energy_probe.py +++ b/external/devlib/devlib/instrument/energy_probe.py @@ -86,9 +86,8 @@ class EnergyProbeInstrument(Instrument): self.process.poll() if self.process.returncode is not None: stdout, stderr = self.process.communicate() - if sys.version_info[0] == 3: - stdout = stdout.decode(sys.stdout.encoding or 'utf-8', 'replace') - stderr = stderr.decode(sys.stdout.encoding or 'utf-8', 'replace') + stdout = stdout.decode(sys.stdout.encoding or 'utf-8', 'replace') + stderr = stderr.decode(sys.stdout.encoding or 'utf-8', 'replace') raise HostError( 'Energy Probe: Caiman exited unexpectedly with exit code {}.\n' 'stdout:\n{}\nstderr:\n{}'.format(self.process.returncode, diff --git a/external/devlib/devlib/instrument/monsoon.py b/external/devlib/devlib/instrument/monsoon.py index ac1741dbee98f9116a228b64798d08071fa752f1..97999177b18513f96b9196e292ad33dfe8f684cc 100644 --- a/external/devlib/devlib/instrument/monsoon.py +++ b/external/devlib/devlib/instrument/monsoon.py @@ -100,9 +100,8 @@ class MonsoonInstrument(Instrument): process.poll() if process.returncode is not None: stdout, stderr = process.communicate() - if sys.version_info[0] == 3: - stdout = stdout.encode(sys.stdout.encoding or 'utf-8') - stderr = stderr.encode(sys.stdout.encoding or 'utf-8') + stdout = stdout.encode(sys.stdout.encoding or 'utf-8') + stderr = stderr.encode(sys.stdout.encoding or 'utf-8') raise HostError( 'Monsoon script exited unexpectedly with exit code {}.\n' 'stdout:\n{}\nstderr:\n{}'.format(process.returncode, diff --git a/external/devlib/devlib/module/thermal.py b/external/devlib/devlib/module/thermal.py index 8220b2215dcb0ee598ae081f92c0641d9b65331a..d23739ea2ee7b580fd4fa096ae54e87c74e5a8a9 100644 --- a/external/devlib/devlib/module/thermal.py +++ b/external/devlib/devlib/module/thermal.py @@ -13,8 +13,11 @@ # limitations under the License. import re +import logging +import devlib.utils.asyn as asyn from devlib.module import Module +from devlib.exception import TargetStableCalledProcessError class TripPoint(object): def __init__(self, zone, _id): @@ -27,19 +30,22 @@ class TripPoint(object): def target(self): return self.zone.target - def get_temperature(self): + @asyn.asyncf + async def get_temperature(self): """Returns the currently configured temperature of the trip point""" temp_file = self.target.path.join(self.zone.path, self.temp_node) - return self.target.read_int(temp_file) + return await self.target.read_int.asyn(temp_file) - def set_temperature(self, temperature): + @asyn.asyncf + async def set_temperature(self, temperature): temp_file = self.target.path.join(self.zone.path, self.temp_node) - self.target.write_value(temp_file, temperature) + await self.target.write_value.asyn(temp_file, temperature) - def get_type(self): + @asyn.asyncf + async def get_type(self): """Returns the type of trip point""" type_file = self.target.path.join(self.zone.path, self.type_node) - return self.target.read_value(type_file) + return await self.target.read_value.asyn(type_file) class ThermalZone(object): def __init__(self, target, root, _id): @@ -47,28 +53,80 @@ class ThermalZone(object): self.name = 'thermal_zone' + _id self.path = target.path.join(root, self.name) self.trip_points = {} + self.type = self.target.read_value(self.target.path.join(self.path, 'type')) for entry in self.target.list_directory(self.path, as_root=target.is_rooted): re_match = re.match('^trip_point_([0-9]+)_temp', entry) if re_match is not None: - self.add_trip_point(re_match.group(1)) + self._add_trip_point(re_match.group(1)) - def add_trip_point(self, _id): + def _add_trip_point(self, _id): self.trip_points[int(_id)] = TripPoint(self, _id) - def is_enabled(self): + @asyn.asyncf + async def is_enabled(self): """Returns a boolean representing the 'mode' of the thermal zone""" - value = self.target.read_value(self.target.path.join(self.path, 'mode')) + value = await self.target.read_value.asyn(self.target.path.join(self.path, 'mode')) return value == 'enabled' - def set_enabled(self, enabled=True): + @asyn.asyncf + async def set_enabled(self, enabled=True): value = 'enabled' if enabled else 'disabled' - self.target.write_value(self.target.path.join(self.path, 'mode'), value) + await self.target.write_value.asyn(self.target.path.join(self.path, 'mode'), value) - def get_temperature(self): + @asyn.asyncf + async def get_temperature(self): """Returns the temperature of the thermal zone""" - temp_file = self.target.path.join(self.path, 'temp') - return self.target.read_int(temp_file) + sysfs_temperature_file = self.target.path.join(self.path, 'temp') + return await self.target.read_int.asyn(sysfs_temperature_file) + + @asyn.asyncf + async def get_policy(self): + """Returns the policy of the thermal zone""" + temp_file = self.target.path.join(self.path, 'policy') + return await self.target.read_value.asyn(temp_file) + + @asyn.asyncf + async def set_policy(self, policy): + """ + Sets the policy of the thermal zone + + :params policy: Thermal governor name + :type policy: str + """ + await self.target.write_value.asyn(self.target.path.join(self.path, 'policy'), policy) + + @asyn.asyncf + async def get_offset(self): + """Returns the temperature offset of the thermal zone""" + offset_file = self.target.path.join(self.path, 'offset') + return await self.target.read_value.asyn(offset_file) + + @asyn.asyncf + async def set_offset(self, offset): + """ + Sets the temperature offset in milli-degrees of the thermal zone + + :params offset: Temperature offset in milli-degrees + :type policy: int + """ + await self.target.write_value.asyn(self.target.path.join(self.path, 'offset'), policy) + + @asyn.asyncf + async def set_emul_temp(self, offset): + """ + Sets the emulated temperature in milli-degrees of the thermal zone + + :params offset: Emulated temperature in milli-degrees + :type policy: int + """ + await self.target.write_value.asyn(self.target.path.join(self.path, 'emul_temp'), policy) + + @asyn.asyncf + async def get_available_policies(self): + """Returns the policies available for the thermal zone""" + temp_file = self.target.path.join(self.path, 'available_policies') + return await self.target.read_value.asyn(temp_file) class ThermalModule(Module): name = 'thermal' @@ -83,6 +141,9 @@ class ThermalModule(Module): def __init__(self, target): super(ThermalModule, self).__init__(target) + self.logger = logging.getLogger(self.name) + self.logger.debug('Initialized [%s] module', self.name) + self.zones = {} self.cdevs = [] @@ -93,15 +154,44 @@ class ThermalModule(Module): continue if re_match.group(1) == 'thermal_zone': - self.add_thermal_zone(re_match.group(2)) + self._add_thermal_zone(re_match.group(2)) elif re_match.group(1) == 'cooling_device': # TODO pass - def add_thermal_zone(self, _id): + def _add_thermal_zone(self, _id): self.zones[int(_id)] = ThermalZone(self.target, self.thermal_root, _id) def disable_all_zones(self): """Disables all the thermal zones in the target""" for zone in self.zones.values(): zone.set_enabled(False) + + @asyn.asyncf + async def get_all_temperatures(self, error='raise'): + """ + Returns dictionary with current reading of all thermal zones. + + :params error: Sensor read error handling (raise or ignore) + :type error: str + + :returns: a dictionary in the form: {tz_type:temperature} + """ + + async def get_temperature_noexcep(item): + tzid, tz = item + try: + temperature = await tz.get_temperature.asyn() + except TargetStableCalledProcessError as e: + if error == 'raise': + raise e + elif error == 'ignore': + self.logger.warning(f'Skipping thermal_zone_id={tzid} thermal_zone_type={tz.type} error="{e}"') + return None + else: + raise ValueError(f'Unknown error parameter value: {error}') + return temperature + + tz_temps = await self.target.async_manager.map_concurrently(get_temperature_noexcep, self.zones.items()) + + return {tz.type: temperature for (tzid, tz), temperature in tz_temps.items() if temperature is not None} diff --git a/external/devlib/devlib/target.py b/external/devlib/devlib/target.py index f4c5ebd78e0c452b60dbb96bca63b7d9ac11972d..d69b74cd5189b8b9e7ded7d8043265abdf4842e7 100644 --- a/external/devlib/devlib/target.py +++ b/external/devlib/devlib/target.py @@ -68,14 +68,14 @@ import devlib.utils.asyn as asyn FSTAB_ENTRY_REGEX = re.compile(r'(\S+) on (.+) type (\S+) \((\S+)\)') -ANDROID_SCREEN_STATE_REGEX = re.compile('(?:mPowerState|mScreenOn|mWakefulness|Display Power: state)=([0-9]+|true|false|ON|OFF|DOZE|Asleep|Awake)', +ANDROID_SCREEN_STATE_REGEX = re.compile('(?:mPowerState|mScreenOn|mWakefulness|Display Power: state)=([0-9]+|true|false|ON|OFF|DOZE|Dozing|Asleep|Awake)', re.IGNORECASE) ANDROID_SCREEN_RESOLUTION_REGEX = re.compile(r'cur=(?P\d+)x(?P\d+)') ANDROID_SCREEN_ROTATION_REGEX = re.compile(r'orientation=(?P[0-3])') DEFAULT_SHELL_PROMPT = re.compile(r'^.*(shell|root|juno)@?.*:[/~]\S* *[#$] ', re.MULTILINE) KVERSION_REGEX = re.compile( - r'(?P\d+)(\.(?P\d+)(\.(?P\d+)(-rc(?P\d+))?)?)?(-(?P\d+)-g(?P[0-9a-fA-F]{7,}))?' + r'(?P\d+)(\.(?P\d+)(\.(?P\d+))?(-rc(?P\d+))?)?(-android(?P[0-9]+))?(-(?P\d+)-g(?P[0-9a-fA-F]{7,}))?(-ab(?P[0-9]+))?' ) GOOGLE_DNS_SERVER_ADDRESS = '8.8.8.8' @@ -1178,6 +1178,17 @@ fi await self.execute.asyn('rm -rf -- {}'.format(quote(path)), as_root=as_root) # misc + @asyn.asyncf + async def read_sysctl(self, parameter): + """ + Returns the value of the given sysctl parameter as a string. + """ + path = target.path.join('proc', 'sys', *parameter.split('.')) + try: + return await self.read_value.asyn(path) + except FileNotFoundError as e: + raise ValueError(f'systcl parameter {parameter} was not found: {e}') + def core_cpus(self, core): return [i for i, c in enumerate(self.core_names) if c == core] @@ -2018,10 +2029,7 @@ class AndroidTarget(Target): parsed_xml = xml.dom.minidom.parse(filepath) with open(filepath, 'w') as f: - if sys.version_info[0] == 3: - f.write(parsed_xml.toprettyxml()) - else: - f.write(parsed_xml.toprettyxml().encode('utf-8')) + f.write(parsed_xml.toprettyxml()) @asyn.asyncf async def is_installed(self, name): @@ -2099,6 +2107,8 @@ class AndroidTarget(Target): pass # Ignore if not requested elif 'Operation not allowed' in e.message: pass # Ignore if not allowed + elif 'is managed by role' in e.message: + pass # Ignore if cannot be granted else: raise @@ -2202,6 +2212,11 @@ class AndroidTarget(Target): def reboot_bootloader(self, timeout=30): self.conn.reboot_bootloader() + @asyn.asyncf + async def is_screen_locked(self): + screen_state = await self.execute.asyn('dumpsys window') + return 'mDreamingLockscreen=true' in screen_state + @asyn.asyncf async def is_screen_on(self): output = await self.execute.asyn('dumpsys power') @@ -2209,6 +2224,8 @@ class AndroidTarget(Target): if match: if 'DOZE' in match.group(1).upper(): return True + if match.group(1) == 'Dozing': + return False if match.group(1) == 'Asleep': return False if match.group(1) == 'Awake': @@ -2222,7 +2239,7 @@ class AndroidTarget(Target): if not await self.is_screen_on.asyn(): self.execute('input keyevent 26') if verify and not await self.is_screen_on.asyn(): - raise TargetStableError('Display cannot be turned on.') + raise TargetStableError('Display cannot be turned on.') @asyn.asyncf async def ensure_screen_is_on_and_stays(self, verify=True, mode=7): @@ -2527,6 +2544,10 @@ class KernelVersion(object): :type commits: int :ivar sha1: Kernel git revision hash, if available (otherwise None) :type sha1: str + :ivar android_version: Android version, if available (otherwise None) + :type android_version: int + :ivar gki_abi: GKI kernel abi, if available (otherwise None) + :type gki_abi: str :ivar parts: Tuple of version number components. Can be used for lexicographically comparing kernel versions. @@ -2550,6 +2571,8 @@ class KernelVersion(object): self.sha1 = None self.rc = None self.commits = None + self.gki_abi = None + self.android_version = None match = KVERSION_REGEX.match(version_string) if match: groups = match.groupdict() @@ -2563,6 +2586,10 @@ class KernelVersion(object): self.commits = int(groups['commits']) if groups['sha1'] is not None: self.sha1 = match.group('sha1') + if groups['gki_abi'] is not None: + self.gki_abi = match.group('gki_abi') + if groups['android_version'] is not None: + self.android_version = int(match.group('android_version')) self.parts = (self.version_number, self.major, self.minor) diff --git a/external/devlib/devlib/utils/android.py b/external/devlib/devlib/utils/android.py index 0b4b074522407734d63f555d3c6bf01ffb06aa4c..52fe4756c82d674a88cc989e61aea05414098efe 100755 --- a/external/devlib/devlib/utils/android.py +++ b/external/devlib/devlib/utils/android.py @@ -88,14 +88,16 @@ INTENT_FLAGS = { 'ACTIVITY_CLEAR_TASK' : 0x00008000 } +# Lazy init of some globals +def __getattr__(attr): + env = _AndroidEnvironment() -# Initialized in functions near the botton of the file -android_home = None -platform_tools = None -adb = None -aapt = None -aapt_version = None -fastboot = None + glob = globals() + glob.update(env.paths) + try: + return glob[attr] + except KeyError: + raise AttributeError(f"Module '{__name__}' has no attribute '{attr}'") class AndroidProperties(object): @@ -162,7 +164,6 @@ class ApkInfo(object): # pylint: disable=too-many-branches def parse(self, apk_path): - _check_env() output = self._run([aapt, 'dump', 'badging', apk_path]) for line in output.split('\n'): if line.startswith('application-label:'): @@ -231,20 +232,22 @@ class ApkInfo(object): parser = etree.XMLParser(encoding='utf-8', recover=True) xml_tree = etree.parse(StringIO(dump), parser) - package = next((i for i in xml_tree.iter('package') - if i.attrib['name'] == self.package), None) + package = [] + for i in xml_tree.iter('package'): + if i.attrib['name'] == self.package: + package.append(i) - self._methods = [(meth.attrib['name'], klass.attrib['name']) - for klass in package.iter('class') - for meth in klass.iter('method')] if package else [] + for elem in package: + self._methods.extend([(meth.attrib['name'], klass.attrib['name']) + for klass in elem.iter('class') + for meth in klass.iter('method')]) return self._methods def _run(self, command): logger.debug(' '.join(command)) try: output = subprocess.check_output(command, stderr=subprocess.STDOUT) - if sys.version_info[0] == 3: - output = output.decode(sys.stdout.encoding or 'utf-8', 'replace') + output = output.decode(sys.stdout.encoding or 'utf-8', 'replace') except subprocess.CalledProcessError as e: raise HostError('Error while running "{}":\n{}' .format(command, e.output)) @@ -284,6 +287,7 @@ class AdbConnection(ConnectionBase): timeout=None, platform=None, adb_server=None, + adb_port=None, adb_as_root=False, connection_attempts=MAX_ATTEMPTS, @@ -300,9 +304,10 @@ class AdbConnection(ConnectionBase): ) self.timeout = timeout if timeout is not None else self.default_timeout if device is None: - device = adb_get_device(timeout=timeout, adb_server=adb_server) + device = adb_get_device(timeout=timeout, adb_server=adb_server, adb_port=adb_port) self.device = device self.adb_server = adb_server + self.adb_port = adb_port self.adb_as_root = adb_as_root lock, nr_active = AdbConnection.active_connections with lock: @@ -317,7 +322,7 @@ class AdbConnection(ConnectionBase): # lead to commands hanging forever in some situations. except AdbRootError: pass - adb_connect(self.device, adb_server=self.adb_server, attempts=connection_attempts) + adb_connect(self.device, adb_server=self.adb_server, adb_port=self.adb_port, attempts=connection_attempts) self._setup_ls() self._setup_su() @@ -337,13 +342,14 @@ class AdbConnection(ConnectionBase): command = "{} {}".format(action, paths) if timeout: - adb_command(self.device, command, timeout=timeout, adb_server=self.adb_server) + adb_command(self.device, command, timeout=timeout, adb_server=self.adb_server, adb_port=self.adb_port) else: bg_cmd = adb_command_background( device=self.device, conn=self, command=command, - adb_server=self.adb_server + adb_server=self.adb_server, + adb_port=self.adb_port, ) handle = PopenTransferHandle( @@ -362,7 +368,7 @@ class AdbConnection(ConnectionBase): as_root = False try: return adb_shell(self.device, command, timeout, check_exit_code, - as_root, adb_server=self.adb_server, su_cmd=self.su_cmd) + as_root, adb_server=self.adb_server, adb_port=self.adb_port, su_cmd=self.su_cmd) except subprocess.CalledProcessError as e: cls = TargetTransientCalledProcessError if will_succeed else TargetStableCalledProcessError raise cls( @@ -404,7 +410,7 @@ class AdbConnection(ConnectionBase): if disconnect: if self.adb_as_root: self.adb_root(enable=False) - adb_disconnect(self.device, self.adb_server) + adb_disconnect(self.device, self.adb_server, self.adb_port) def cancel_running_command(self): # adbd multiplexes commands so that they don't interfer with each @@ -422,7 +428,7 @@ class AdbConnection(ConnectionBase): cmd = 'root' if enable else 'unroot' try: - output = adb_command(self.device, cmd, timeout=30, adb_server=self.adb_server) + output = adb_command(self.device, cmd, timeout=30, adb_server=self.adb_server, adb_port=self.adb_port) except subprocess.CalledProcessError as e: # Ignore if we're already root if 'adbd is already running as root' in e.output: @@ -436,10 +442,10 @@ class AdbConnection(ConnectionBase): AdbConnection._connected_as_root[self.device] = enable def wait_for_device(self, timeout=30): - adb_command(self.device, 'wait-for-device', timeout, self.adb_server) + adb_command(self.device, 'wait-for-device', timeout, self.adb_server, self.adb_port) def reboot_bootloader(self, timeout=30): - adb_command(self.device, 'reboot-bootloader', timeout, self.adb_server) + adb_command(self.device, 'reboot-bootloader', timeout, self.adb_server, self.adb_port) # Again, we need to handle boards where the default output format from ls is # single column *and* boards where the default output is multi-column. @@ -448,7 +454,7 @@ class AdbConnection(ConnectionBase): def _setup_ls(self): command = "shell '(ls -1); echo \"\n$?\"'" try: - output = adb_command(self.device, command, timeout=self.timeout, adb_server=self.adb_server) + output = adb_command(self.device, command, timeout=self.timeout, adb_server=self.adb_server, adb_port=self.adb_port) except subprocess.CalledProcessError as e: raise HostError( 'Failed to set up ls command on Android device. Output:\n' @@ -477,7 +483,6 @@ class AdbConnection(ConnectionBase): def fastboot_command(command, timeout=None, device=None): - _check_env() target = '-s {}'.format(quote(device)) if device else '' full_command = 'fastboot {} {}'.format(target, command) logger.debug(full_command) @@ -490,7 +495,7 @@ def fastboot_flash_partition(partition, path_to_image): fastboot_command(command) -def adb_get_device(timeout=None, adb_server=None): +def adb_get_device(timeout=None, adb_server=None, adb_port=None): """ Returns the serial number of a connected android device. @@ -501,7 +506,7 @@ def adb_get_device(timeout=None, adb_server=None): # Ensure server is started so the 'daemon started successfully' message # doesn't confuse the parsing below - adb_command(None, 'start-server', adb_server=adb_server) + adb_command(None, 'start-server', adb_server=adb_server, adb_port=adb_port) # The output of calling adb devices consists of a heading line then # a list of the devices sperated by new line @@ -509,7 +514,7 @@ def adb_get_device(timeout=None, adb_server=None): # then the output length is 2 + (1 for each device) start = time.time() while True: - output = adb_command(None, "devices", adb_server=adb_server).splitlines() # pylint: disable=E1103 + output = adb_command(None, "devices", adb_server=adb_server, adb_port=adb_port).splitlines() # pylint: disable=E1103 output_length = len(output) if output_length == 3: # output[1] is the 2nd line in the output which has the device name @@ -526,8 +531,7 @@ def adb_get_device(timeout=None, adb_server=None): time.sleep(1) -def adb_connect(device, timeout=None, attempts=MAX_ATTEMPTS, adb_server=None): - _check_env() +def adb_connect(device, timeout=None, attempts=MAX_ATTEMPTS, adb_server=None, adb_port=None): tries = 0 output = None while tries <= attempts: @@ -539,12 +543,12 @@ def adb_connect(device, timeout=None, attempts=MAX_ATTEMPTS, adb_server=None): # adb connection may have gone "stale", resulting in adb blocking # indefinitely when making calls to the device. To avoid this, # always disconnect first. - adb_disconnect(device, adb_server) - adb_cmd = get_adb_command(None, 'connect', adb_server) + adb_disconnect(device, adb_server, adb_port) + adb_cmd = get_adb_command(None, 'connect', adb_server, adb_port) command = '{} {}'.format(adb_cmd, quote(device)) logger.debug(command) output, _ = check_output(command, shell=True, timeout=timeout) - if _ping(device, adb_server): + if _ping(device, adb_server, adb_port): break time.sleep(10) else: # did not connect to the device @@ -554,12 +558,11 @@ def adb_connect(device, timeout=None, attempts=MAX_ATTEMPTS, adb_server=None): raise HostError(message) -def adb_disconnect(device, adb_server=None): - _check_env() +def adb_disconnect(device, adb_server=None, adb_port=None): if not device: return - if ":" in device and device in adb_list_devices(adb_server): - adb_cmd = get_adb_command(None, 'disconnect', adb_server) + if ":" in device and device in adb_list_devices(adb_server, adb_port): + adb_cmd = get_adb_command(None, 'disconnect', adb_server, adb_port) command = "{} {}".format(adb_cmd, device) logger.debug(command) retval = subprocess.call(command, stdout=open(os.devnull, 'wb'), shell=True) @@ -567,9 +570,8 @@ def adb_disconnect(device, adb_server=None): raise TargetTransientError('"{}" returned {}'.format(command, retval)) -def _ping(device, adb_server=None): - _check_env() - adb_cmd = get_adb_command(device, 'shell', adb_server) +def _ping(device, adb_server=None, adb_port=None): + adb_cmd = get_adb_command(device, 'shell', adb_server, adb_port) command = "{} {}".format(adb_cmd, quote('ls /data/local/tmp > /dev/null')) logger.debug(command) result = subprocess.call(command, stderr=subprocess.PIPE, shell=True) @@ -581,8 +583,7 @@ def _ping(device, adb_server=None): # pylint: disable=too-many-locals def adb_shell(device, command, timeout=None, check_exit_code=False, - as_root=False, adb_server=None, su_cmd='su -c {}'): # NOQA - _check_env() + as_root=False, adb_server=None, adb_port=None, su_cmd='su -c {}'): # NOQA # On older combinations of ADB/Android versions, the adb host command always # exits with 0 if it was able to run the command on the target, even if the @@ -590,18 +591,14 @@ def adb_shell(device, command, timeout=None, check_exit_code=False, # Homogenise this behaviour by running the command then echoing the exit # code of the executed command itself. command = r'({}); echo "\n$?"'.format(command) - - parts = ['adb'] - if adb_server is not None: - parts += ['-H', adb_server] - if device is not None: - parts += ['-s', device] - parts += ['shell', - command if not as_root else su_cmd.format(quote(command))] + command = su_cmd.format(quote(command)) if as_root else command + command = ('shell', command) + parts, env = _get_adb_parts(command, device, adb_server, adb_port, quote_adb=False) + env = {**os.environ, **env} logger.debug(' '.join(quote(part) for part in parts)) try: - raw_output, error = check_output(parts, timeout, shell=False) + raw_output, error = check_output(parts, timeout, shell=False, env=env) except subprocess.CalledProcessError as e: raise TargetStableError(str(e)) @@ -651,40 +648,72 @@ def adb_background_shell(conn, command, """Runs the specified command in a subprocess, returning the the Popen object.""" device = conn.device adb_server = conn.adb_server + adb_port = conn.adb_port + busybox = conn.busybox + orig_command = command - _check_env() stdout, stderr, command = redirect_streams(stdout, stderr, command) if as_root: - command = 'echo {} | su'.format(quote(command)) - - # Attach a unique UUID to the command line so it can be looked for without - # any ambiguity with ps - uuid_ = uuid.uuid4().hex - uuid_var = 'BACKGROUND_COMMAND_UUID={}'.format(uuid_) - command = "{} sh -c {}".format(uuid_var, quote(command)) - - adb_cmd = get_adb_command(device, 'shell', adb_server) - full_command = '{} {}'.format(adb_cmd, quote(command)) + command = f'{busybox} printf "%s" {quote(command)} | su' + + def with_uuid(cmd): + # Attach a unique UUID to the command line so it can be looked for + # without any ambiguity with ps + uuid_ = uuid.uuid4().hex + # Unset the var, since not all connection types set it. This will avoid + # anyone depending on that value. + cmd = f'DEVLIB_CMD_UUID={uuid_}; unset DEVLIB_CMD_UUID; {cmd}' + # Ensure we have an sh -c layer so that the UUID will appear on the + # command line parameters of at least one command. + cmd = f'exec {busybox} sh -c {quote(cmd)}' + return (uuid_, cmd) + + # Freeze the command with SIGSTOP to avoid racing with PID detection. + command = f"{busybox} kill -STOP $$ && exec {busybox} sh -c {quote(command)}" + command_uuid, command = with_uuid(command) + + adb_cmd = get_adb_command(device, 'shell', adb_server, adb_port) + full_command = f'{adb_cmd} {quote(command)}' logger.debug(full_command) p = subprocess.Popen(full_command, stdout=stdout, stderr=stderr, stdin=subprocess.PIPE, shell=True) # Out of band PID lookup, to avoid conflicting needs with stdout redirection - find_pid = '{} ps -A -o pid,args | grep {}'.format(conn.busybox, quote(uuid_var)) - ps_out = conn.execute(find_pid) - pids = [ - int(line.strip().split(' ', 1)[0]) - for line in ps_out.splitlines() - ] - # The line we are looking for is the first one, since it was started before - # any look up command - pid = sorted(pids)[0] + grep_cmd = f'{busybox} grep {quote(command_uuid)}' + # Find the PID and release the blocked background command with SIGCONT. + # We get multiple PIDs: + # * One from the grep command itself, but we remove it with another grep command. + # * One for each sh -c layer in the command itself. + # + # For each of the parent layer, we issue SIGCONT as it is harmless and + # avoids having to rely on PID ordering (which could be misleading if PIDs + # got recycled). + find_pid = f'''pids=$({busybox} ps -A -o pid,args | {grep_cmd} | {busybox} grep -v {quote(grep_cmd)} | {busybox} awk '{{print $1}}') && {busybox} printf "%s" "$pids" && {busybox} kill -CONT $pids''' + + excep = None + for _ in range(5): + try: + pids = conn.execute(find_pid, as_root=as_root) + # We choose the highest PID as the "control" PID. It actually does not + # really matter which one we pick, as they are all equivalent sh -c layers. + pid = max(map(int, pids.split())) + except TargetStableError: + raise + except Exception as e: + excep = e + time.sleep(10e-3) + continue + else: + break + else: + raise TargetTransientError(f'Could not detect PID of background command: {orig_command}') from excep + return (p, pid) -def adb_kill_server(timeout=30, adb_server=None): - adb_command(None, 'kill-server', timeout, adb_server) +def adb_kill_server(timeout=30, adb_server=None, adb_port=None): + adb_command(None, 'kill-server', timeout, adb_server, adb_port) -def adb_list_devices(adb_server=None): - output = adb_command(None, 'devices', adb_server=adb_server) +def adb_list_devices(adb_server=None, adb_port=None): + output = adb_command(None, 'devices', adb_server=adb_server, adb_port=adb_port) devices = [] for line in output.splitlines(): parts = [p.strip() for p in line.split()] @@ -693,24 +722,35 @@ def adb_list_devices(adb_server=None): return devices -def get_adb_command(device, command, adb_server=None): - _check_env() - device_string = "" - if adb_server != None: - device_string = ' -H {}'.format(adb_server) - device_string += ' -s {}'.format(device) if device else '' - return "LC_ALL=C adb{} {}".format(device_string, command) +def _get_adb_parts(command, device=None, adb_server=None, adb_port=None, quote_adb=True): + _quote = quote if quote_adb else lambda x: x + parts = ( + adb, + *(('-H', _quote(adb_server)) if adb_server is not None else ()), + *(('-P', _quote(str(adb_port))) if adb_port is not None else ()), + *(('-s', _quote(device)) if device is not None else ()), + *command, + ) + env = {'LC_ALL': 'C'} + return (parts, env) + + +def get_adb_command(device, command, adb_server=None, adb_port=None): + parts, env = _get_adb_parts((command,), device, adb_server, adb_port, quote_adb=True) + env = [quote(f'{name}={val}') for name, val in sorted(env.items())] + parts = [*env, *parts] + return ' '.join(parts) -def adb_command(device, command, timeout=None, adb_server=None): - full_command = get_adb_command(device, command, adb_server) +def adb_command(device, command, timeout=None, adb_server=None, adb_port=None): + full_command = get_adb_command(device, command, adb_server, adb_port) logger.debug(full_command) output, _ = check_output(full_command, timeout, shell=True) return output -def adb_command_background(device, conn, command, adb_server=None): - full_command = get_adb_command(device, command, adb_server) +def adb_command_background(device, conn, command, adb_server=None, adb_port=None): + full_command = get_adb_command(device, command, adb_server, adb_port) logger.debug(full_command) popen = get_subprocess(full_command, shell=True) cmd = PopenBackgroundCommand(conn=conn, popen=popen) @@ -739,120 +779,131 @@ def grant_app_permissions(target, package): # Messy environment initialisation stuff... -class _AndroidEnvironment(object): - +class _AndroidEnvironment: def __init__(self): - self.android_home = None - self.platform_tools = None - self.build_tools = None - self.adb = None - self.aapt = None - self.aapt_version = None - self.fastboot = None - - -def _initialize_with_android_home(env): - logger.debug('Using ANDROID_HOME from the environment.') - env.android_home = android_home - env.platform_tools = os.path.join(android_home, 'platform-tools') - os.environ['PATH'] = env.platform_tools + os.pathsep + os.environ['PATH'] - _init_common(env) - return env - - -def _initialize_without_android_home(env): - adb_full_path = which('adb') - if adb_full_path: - env.adb = 'adb' - else: - raise HostError('ANDROID_HOME is not set and adb is not in PATH. ' - 'Have you installed Android SDK?') - logger.debug('Discovering ANDROID_HOME from adb path.') - env.platform_tools = os.path.dirname(adb_full_path) - env.android_home = os.path.dirname(env.platform_tools) - _init_common(env) - return env - -def _init_common(env): - _discover_build_tools(env) - _discover_aapt(env) - -def _discover_build_tools(env): - logger.debug('ANDROID_HOME: {}'.format(env.android_home)) - build_tools_directory = os.path.join(env.android_home, 'build-tools') - if os.path.isdir(build_tools_directory): - env.build_tools = build_tools_directory - -def _check_supported_aapt2(binary): - # At time of writing the version argument of aapt2 is not helpful as - # the output is only a placeholder that does not distinguish between versions - # with and without support for badging. Unfortunately aapt has been - # deprecated and fails to parse some valid apks so we will try to favour - # aapt2 if possible else will fall back to aapt. - # Try to execute the badging command and check if we get an expected error - # message as opposed to an unknown command error to determine if we have a - # suitable version. - cmd = '{} dump badging'.format(binary) - result = subprocess.run(cmd.encode('utf-8'), shell=True, stderr=subprocess.PIPE) - supported = bool(AAPT_BADGING_OUTPUT.search(result.stderr.decode('utf-8'))) - msg = 'Found a {} aapt2 binary at: {}' - logger.debug(msg.format('supported' if supported else 'unsupported', binary)) - return supported - -def _discover_aapt(env): - if env.build_tools: - aapt_path = '' - aapt2_path = '' - versions = os.listdir(env.build_tools) - for version in reversed(sorted(versions)): - if not os.path.isfile(aapt2_path): - aapt2_path = os.path.join(env.build_tools, version, 'aapt2') - if not os.path.isfile(aapt_path): - aapt_path = os.path.join(env.build_tools, version, 'aapt') - aapt_version = 1 - # Use latest available version for aapt/appt2 but ensure at least one is valid. - if os.path.isfile(aapt2_path) or os.path.isfile(aapt_path): - break - - # Use aapt2 only if present and we have a suitable version - if aapt2_path and _check_supported_aapt2(aapt2_path): - aapt_path = aapt2_path - aapt_version = 2 - - # Use the aapt version discoverted from build tools. - if aapt_path: - logger.debug('Using {} for version {}'.format(aapt_path, version)) - env.aapt = aapt_path - env.aapt_version = aapt_version - return + android_home = os.getenv('ANDROID_HOME') + if android_home: + paths = self._from_android_home(android_home) + else: + paths = self._from_adb() + + self.paths = paths + + @classmethod + def _from_android_home(cls, android_home): + logger.debug('Using ANDROID_HOME from the environment.') + platform_tools = os.path.join(android_home, 'platform-tools') + + return { + 'android_home': android_home, + 'platform_tools': platform_tools, + 'adb': os.path.join(platform_tools, 'adb'), + 'fastboot': os.path.join(platform_tools, 'fastboot'), + **cls._init_common(android_home) + } + return paths + + @classmethod + def _from_adb(cls): + adb_path = which('adb') + if adb_path: + logger.debug('Discovering ANDROID_HOME from adb path.') + platform_tools = os.path.dirname(adb_path) + android_home = os.path.dirname(platform_tools) + + return { + 'android_home': android_home, + 'platform_tools': platform_tools, + 'adb': adb_path, + 'fastboot': which('fastboot'), + **cls._init_common(android_home) + } + else: + raise HostError('ANDROID_HOME is not set and adb is not in PATH. ' + 'Have you installed Android SDK?') + + @classmethod + def _init_common(cls, android_home): + logger.debug(f'ANDROID_HOME: {android_home}') + build_tools = cls._discover_build_tools(android_home) + return { + 'build_tools': build_tools, + **cls._discover_aapt(build_tools) + } + + @staticmethod + def _discover_build_tools(android_home): + build_tools = os.path.join(android_home, 'build-tools') + if os.path.isdir(build_tools): + return build_tools + else: + return None + + @staticmethod + def _check_supported_aapt2(binary): + # At time of writing the version argument of aapt2 is not helpful as + # the output is only a placeholder that does not distinguish between versions + # with and without support for badging. Unfortunately aapt has been + # deprecated and fails to parse some valid apks so we will try to favour + # aapt2 if possible else will fall back to aapt. + # Try to execute the badging command and check if we get an expected error + # message as opposed to an unknown command error to determine if we have a + # suitable version. + result = subprocess.run([str(binary), 'dump', 'badging'], stdout=subprocess.DEVNULL, stderr=subprocess.PIPE, universal_newlines=True) + supported = bool(AAPT_BADGING_OUTPUT.search(result.stderr)) + msg = 'Found a {} aapt2 binary at: {}' + logger.debug(msg.format('supported' if supported else 'unsupported', binary)) + return supported + + @classmethod + def _discover_aapt(cls, build_tools): + if build_tools: + + def find_aapt2(version): + path = os.path.join(build_tools, version, 'aapt2') + if os.path.isfile(path) and cls._check_supported_aapt2(path): + return (2, path) + else: + return (None, None) - # Try detecting aapt2 and aapt from PATH - if not env.aapt: - aapt2_path = which('aapt2') - if _check_supported_aapt2(aapt2_path): - env.aapt = aapt2_path - env.aapt_version = 2 - else: - env.aapt = which('aapt') - env.aapt_version = 1 + def find_aapt(version): + path = os.path.join(build_tools, version, 'aapt') + if os.path.isfile(path): + return (1, path) + else: + return (None, None) - if not env.aapt: - raise HostError('aapt/aapt2 not found. Please make sure it is avaliable in PATH' - ' or at least one Android platform is installed') + versions = os.listdir(build_tools) + found = ( + (version, finder(version)) + for version in reversed(sorted(versions)) + for finder in (find_aapt2, find_aapt) + ) -def _check_env(): - global android_home, platform_tools, adb, aapt, aapt_version # pylint: disable=W0603 - if not android_home: - android_home = os.getenv('ANDROID_HOME') - if android_home: - _env = _initialize_with_android_home(_AndroidEnvironment()) + for version, (aapt_version, aapt_path) in found: + if aapt_path: + logger.debug(f'Using {aapt_path} for version {version}') + return dict( + aapt=aapt_path, + aapt_version=aapt_version, + ) + + # Try detecting aapt2 and aapt from PATH + aapt2_path = which('aapt2') + aapt_path = which('aapt') + if aapt2_path and cls._check_supported_aapt2(aapt2_path): + return dict( + aapt=aapt2_path, + aapt_version=2, + ) + elif aapt_path: + return dict( + aapt=aapt_path, + aapt_version=1, + ) else: - _env = _initialize_without_android_home(_AndroidEnvironment()) - android_home = _env.android_home - platform_tools = _env.platform_tools - adb = _env.adb - aapt = _env.aapt - aapt_version = _env.aapt_version + raise HostError('aapt/aapt2 not found. Please make sure it is avaliable in PATH or at least one Android platform is installed') + class LogcatMonitor(object): """ @@ -911,7 +962,7 @@ class LogcatMonitor(object): if self._logcat_format: logcat_cmd = "{} -v {}".format(logcat_cmd, quote(self._logcat_format)) - logcat_cmd = get_adb_command(self.target.conn.device, logcat_cmd, self.target.adb_server) + logcat_cmd = get_adb_command(self.target.conn.device, logcat_cmd, self.target.adb_server, self.target.adb_port) logger.debug('logcat command ="{}"'.format(logcat_cmd)) self._logcat = pexpect.spawn(logcat_cmd, logfile=self._logfile, encoding='utf-8') diff --git a/external/devlib/devlib/utils/csvutil.py b/external/devlib/devlib/utils/csvutil.py index 2cb1212f947fdff466b8042393970896f948ac7e..b696449fa007648838f39234a63f39dd0a954011 100644 --- a/external/devlib/devlib/utils/csvutil.py +++ b/external/devlib/devlib/utils/csvutil.py @@ -60,10 +60,7 @@ from contextlib import contextmanager @contextmanager def csvwriter(filepath, *args, **kwargs): - if sys.version_info[0] == 3: - wfh = open(filepath, 'w', newline='') - else: - wfh = open(filepath, 'wb') + wfh = open(filepath, 'w', newline='') try: yield csv.writer(wfh, *args, **kwargs) @@ -73,10 +70,7 @@ def csvwriter(filepath, *args, **kwargs): @contextmanager def csvreader(filepath, *args, **kwargs): - if sys.version_info[0] == 3: - fh = open(filepath, 'r', newline='') - else: - fh = open(filepath, 'rb') + fh = open(filepath, 'r', newline='') try: yield csv.reader(fh, *args, **kwargs) @@ -85,16 +79,10 @@ def csvreader(filepath, *args, **kwargs): def create_writer(filepath, *args, **kwargs): - if sys.version_info[0] == 3: - wfh = open(filepath, 'w', newline='') - else: - wfh = open(filepath, 'wb') + wfh = open(filepath, 'w', newline='') return csv.writer(wfh, *args, **kwargs), wfh def create_reader(filepath, *args, **kwargs): - if sys.version_info[0] == 3: - fh = open(filepath, 'r', newline='') - else: - fh = open(filepath, 'rb') + fh = open(filepath, 'r', newline='') return csv.reader(fh, *args, **kwargs), fh diff --git a/external/devlib/devlib/utils/misc.py b/external/devlib/devlib/utils/misc.py index 47348927ff045e4696d32eeb0bb6d74b465bcabc..58cf6eca1270bf67cc9fc8ad72a6768ba09bc8c3 100644 --- a/external/devlib/devlib/utils/misc.py +++ b/external/devlib/devlib/utils/misc.py @@ -594,11 +594,7 @@ class LoadSyntaxError(Exception): RAND_MOD_NAME_LEN = 30 BAD_CHARS = string.punctuation + string.whitespace -# pylint: disable=no-member -if sys.version_info[0] == 3: - TRANS_TABLE = str.maketrans(BAD_CHARS, '_' * len(BAD_CHARS)) -else: - TRANS_TABLE = string.maketrans(BAD_CHARS, '_' * len(BAD_CHARS)) +TRANS_TABLE = str.maketrans(BAD_CHARS, '_' * len(BAD_CHARS)) def to_identifier(text): diff --git a/external/devlib/devlib/utils/rendering.py b/external/devlib/devlib/utils/rendering.py index 3df82d61c7579cbbee1657e8bdbb2199810d82b5..52d4f00dcedd7edfa10745e0b71f8ff4b2a48433 100644 --- a/external/devlib/devlib/utils/rendering.py +++ b/external/devlib/devlib/utils/rendering.py @@ -217,10 +217,7 @@ class GfxinfoFrameCollector(FrameCollector): def collect_frames(self, wfh): cmd = 'dumpsys gfxinfo {} framestats' result = self.target.execute(cmd.format(self.package)) - if sys.version_info[0] == 3: - wfh.write(result.encode('utf-8')) - else: - wfh.write(result) + wfh.write(result.encode('utf-8')) def clear(self): pass diff --git a/external/devlib/devlib/utils/ssh.py b/external/devlib/devlib/utils/ssh.py index 0feceaf668ecea0037fd55791dde2894c821c742..58b7659086ceed2f9e4c89fa4f7f7453f0a84af5 100644 --- a/external/devlib/devlib/utils/ssh.py +++ b/external/devlib/devlib/utils/ssh.py @@ -67,9 +67,17 @@ from devlib.connection import (ConnectionBase, ParamikoBackgroundCommand, PopenB DEFAULT_SSH_SUDO_COMMAND = "sudo -k -p ' ' -S -- sh -c {}" -ssh = None -scp = None -sshpass = None +# Lazy init of some globals +def __getattr__(attr): + if attr in {'ssh', 'scp', 'sshpass'}: + path = which(attr) + if path: + globals()[attr] = path + return path + else: + raise HostError(f'OpenSSH must be installed on the host: could not find {attr} command') + else: + raise AttributeError(f"Module '{__name__}' has no attribute '{attr}'") logger = logging.getLogger('ssh') @@ -176,7 +184,6 @@ def telnet_get_shell(host, port=None, timeout=10, original_prompt=None): - _check_env() start_time = time.time() while True: conn = TelnetPxssh(original_prompt=original_prompt) @@ -758,11 +765,7 @@ class SshConnection(SshConnectionBase): output_chunks, exit_code = _read_paramiko_streams(stdout, stderr, select_timeout, callback, []) # Join in one go to avoid O(N^2) concatenation output = b''.join(output_chunks) - - if sys.version_info[0] == 3: - output = output.decode(sys.stdout.encoding or 'utf-8', 'replace') - if strip_colors: - output = strip_bash_colors(output) + output = output.decode(sys.stdout.encoding or 'utf-8', 'replace') return (exit_code, output) @@ -796,7 +799,6 @@ class TelnetConnection(SshConnectionBase): strict_host_check=strict_host_check, ) - _check_env() self.options = self._get_default_options() self.lock = threading.Lock() @@ -970,10 +972,7 @@ class TelnetConnection(SshConnectionBase): logger.debug(command) self._sendline(command) timed_out = self._wait_for_prompt(timeout) - if sys.version_info[0] == 3: - output = process_backspaces(self.conn.before.decode(sys.stdout.encoding or 'utf-8', 'replace')) - else: - output = process_backspaces(self.conn.before) + output = process_backspaces(self.conn.before.decode(sys.stdout.encoding or 'utf-8', 'replace')) if timed_out: self.cancel_running_command() @@ -1605,23 +1604,15 @@ class AndroidGem5Connection(Gem5Connection): gem5_logger.info("Android booted") + def _give_password(password, command): - if not sshpass: + if sshpass: + pass_template = "{} -p {} " + pass_string = pass_template.format(quote(sshpass), quote(password)) + redacted_string = pass_template.format(quote(sshpass), quote('')) + return (pass_string + command, redacted_string + command) + else: raise HostError('Must have sshpass installed on the host in order to use password-based auth.') - pass_template = "sshpass -p {} " - pass_string = pass_template.format(quote(password)) - redacted_string = pass_template.format(quote('')) - return (pass_string + command, redacted_string + command) - - -def _check_env(): - global ssh, scp, sshpass # pylint: disable=global-statement - if not ssh: - ssh = which('ssh') - scp = which('scp') - sshpass = which('sshpass') - if not (ssh and scp): - raise HostError('OpenSSH must be installed on the host.') def process_backspaces(text): diff --git a/external/devlib/devlib/utils/types.py b/external/devlib/devlib/utils/types.py index f212930e277860a765e9f049c14244c617d1374e..d7c8864b059faa30432e5fd9d63ed1d1cdb71f68 100644 --- a/external/devlib/devlib/utils/types.py +++ b/external/devlib/devlib/utils/types.py @@ -136,35 +136,25 @@ def bitmask(value): regex_type = type(re.compile('')) -if sys.version_info[0] == 3: - def regex(value): - if isinstance(value, regex_type): - if isinstance(value.pattern, str): - return value - return re.compile(value.pattern.decode(), - value.flags | re.UNICODE) - else: - if isinstance(value, bytes): - value = value.decode() - return re.compile(value) - - - def bytes_regex(value): - if isinstance(value, regex_type): - if isinstance(value.pattern, bytes): - return value - return re.compile(value.pattern.encode(sys.stdout.encoding or 'utf-8'), - value.flags & ~re.UNICODE) - else: - if isinstance(value, str): - value = value.encode(sys.stdout.encoding or 'utf-8') - return re.compile(value) -else: - def regex(value): - if isinstance(value, regex_type): +def regex(value): + if isinstance(value, regex_type): + if isinstance(value.pattern, str): return value - else: - return re.compile(value) + return re.compile(value.pattern.decode(), + value.flags | re.UNICODE) + else: + if isinstance(value, bytes): + value = value.decode() + return re.compile(value) - bytes_regex = regex +def bytes_regex(value): + if isinstance(value, regex_type): + if isinstance(value.pattern, bytes): + return value + return re.compile(value.pattern.encode(sys.stdout.encoding or 'utf-8'), + value.flags & ~re.UNICODE) + else: + if isinstance(value, str): + value = value.encode(sys.stdout.encoding or 'utf-8') + return re.compile(value) diff --git a/external/devlib/devlib/utils/version.py b/external/devlib/devlib/utils/version.py index a44498b6574b973990b479fd0d913941ae58a589..ec6a3f1c53a1fe44ba613a5052a9b8e1d7a5ec2c 100644 --- a/external/devlib/devlib/utils/version.py +++ b/external/devlib/devlib/utils/version.py @@ -42,7 +42,7 @@ def get_commit(): p.wait() if p.returncode: return None - if sys.version_info[0] == 3 and isinstance(std, bytes): + if isinstance(std, bytes): return std[:8].decode(sys.stdout.encoding or 'utf-8', 'replace') else: return std[:8] diff --git a/external/devlib/doc/target.rst b/external/devlib/doc/target.rst index 17b2bbd3a9f8bcc5ab09667700393bb7ea2f7191..ad641b122405ee3a196d485076048e3f765841b4 100644 --- a/external/devlib/doc/target.rst +++ b/external/devlib/doc/target.rst @@ -747,10 +747,15 @@ Android Target .. method:: AndroidTarget.is_screen_on() - Returns ``True`` if the targets screen is currently on and ``False`` + Returns ``True`` if the target's screen is currently on and ``False`` otherwise. If the display is in a "Doze" mode or similar always on state, this will return ``True``. +.. method:: AndroidTarget.is_screen_locked() + + Returns ``True`` if the target's screen is currently locked and ``False`` + otherwise. + .. method:: AndroidTarget.wait_for_device(timeout=30) Returns when the devices becomes available withing the given timeout diff --git a/external/devlib/setup.py b/external/devlib/setup.py index bf1b408ff5f3b25c08c485877bdce17b13088dbe..06b20952ce54e61acfd96b69cbf8e27246d0682a 100644 --- a/external/devlib/setup.py +++ b/external/devlib/setup.py @@ -13,11 +13,11 @@ # limitations under the License. # -import imp import os import sys import warnings from itertools import chain +import types try: from setuptools import setup @@ -35,15 +35,25 @@ sys.path.insert(0, os.path.join(devlib_dir, 'core')) warnings.filterwarnings('ignore', "Unknown distribution option: 'install_requires'") warnings.filterwarnings('ignore', "Unknown distribution option: 'extras_require'") + try: os.remove('MANIFEST') except OSError: pass +def _load_path(filepath): + # Not a proper import in many, many ways but does the job for really basic + # needs + with open(filepath) as f: + globals_ = dict(__file__=filepath) + exec(f.read(), globals_) + return types.SimpleNamespace(**globals_) + + vh_path = os.path.join(devlib_dir, 'utils', 'version.py') # can load this, as it does not have any devlib imports -version_helper = imp.load_source('version_helper', vh_path) +version_helper = _load_path(vh_path) __version__ = version_helper.get_devlib_version() commit = version_helper.get_commit() if commit: @@ -94,6 +104,7 @@ params = dict( 'pandas', 'lxml', # More robust xml parsing 'nest_asyncio', # Allows running nested asyncio loops + 'future', # for the "past" Python package ], extras_require={ 'daq': ['daqpower>=2'], diff --git a/external/workload-automation/dev_scripts/pylint_plugins.py b/external/workload-automation/dev_scripts/pylint_plugins.py index 8d6f87cc6761e270645f019bb0c1dbe520b2796f..73611421f25f662d47a441ba6d8e3e96a67c776b 100644 --- a/external/workload-automation/dev_scripts/pylint_plugins.py +++ b/external/workload-automation/dev_scripts/pylint_plugins.py @@ -32,17 +32,11 @@ def transform(mod): if b'pylint:' in text[0]: msg = 'pylint directive found on the first line of {}; please move to below copyright header' raise RuntimeError(msg.format(mod.name)) - if sys.version_info[0] == 3: - char = chr(text[0][0]) - else: - char = text[0][0] + char = chr(text[0][0]) if text[0].strip() and char != '#': msg = 'first line of {} is not a comment; is the copyright header missing?' raise RuntimeError(msg.format(mod.name)) - if sys.version_info[0] == 3: - text[0] = '# pylint: disable={}'.format(','.join(errors)).encode('utf-8') - else: - text[0] = '# pylint: disable={}'.format(','.join(errors)) + text[0] = '# pylint: disable={}'.format(','.join(errors)).encode('utf-8') mod.file_bytes = b'\n'.join(text) # This is what *should* happen, but doesn't work. diff --git a/external/workload-automation/doc/source/user_information/user_reference/runtime_parameters.rst b/external/workload-automation/doc/source/user_information/user_reference/runtime_parameters.rst index 5e1472070cbf38dbf3a82c0cfbfde9cc53699449..f05373c46b9f137aedc21b17028eb4097b738181 100644 --- a/external/workload-automation/doc/source/user_information/user_reference/runtime_parameters.rst +++ b/external/workload-automation/doc/source/user_information/user_reference/runtime_parameters.rst @@ -33,6 +33,7 @@ states. iterations: 1 runtime_parameters: screen_on: false + unlock_screen: 'vertical' - name: benchmarkpi iterations: 1 sections: @@ -208,6 +209,13 @@ Android Specific Runtime Parameters :screen_on: A ``boolean`` to specify whether the devices screen should be turned on. Defaults to ``True``. +:unlock_screen: A ``String`` to specify how the devices screen should be + unlocked. Unlocking screen is disabled by default. ``vertical``, ``diagonal`` + and ``horizontal`` are the supported values (see :meth:`devlib.AndroidTarget.swipe_to_unlock`). + Note that unlocking succeeds when no passcode is set. Since unlocking screen + requires turning on the screen, this option overrides value of ``screen_on`` + option. + .. _setting-sysfiles: Setting Sysfiles diff --git a/external/workload-automation/requirements.txt b/external/workload-automation/requirements.txt index 99a342d118619d1168f9a4eab6d3fd1a4128f13e..ba6b3544415f18542f8af4b6face994c1e46f1f8 100644 --- a/external/workload-automation/requirements.txt +++ b/external/workload-automation/requirements.txt @@ -3,7 +3,7 @@ certifi==2023.7.22 cffi==1.15.1 charset-normalizer==3.1.0 colorama==0.4.6 -cryptography==41.0.3 +cryptography==41.0.6 devlib==1.3.4 future==0.18.3 idna==3.4 @@ -12,7 +12,7 @@ lxml==4.9.2 nose==1.3.7 numpy==1.24.3 pandas==2.0.1 -paramiko==3.1.0 +paramiko==3.4.0 pexpect==4.8.0 ptyprocess==0.7.0 pycparser==2.21 @@ -25,6 +25,6 @@ requests==2.31.0 scp==0.14.5 six==1.16.0 tzdata==2023.3 -urllib3==1.26.15 +urllib3==1.26.18 wlauto==3.3.1 wrapt==1.15.0 diff --git a/external/workload-automation/setup.py b/external/workload-automation/setup.py index 36c697c6a8c7899b15b1ca763f4e6e601ffb5fdf..3b92599bbe5c9a626db496de5fc427f818e7ec74 100755 --- a/external/workload-automation/setup.py +++ b/external/workload-automation/setup.py @@ -79,6 +79,7 @@ params = dict( license='Apache v2', maintainer='ARM Architecture & Technology Device Lab', maintainer_email='workload-automation@arm.com', + python_requires='>= 3.7', setup_requires=[ 'numpy<=1.16.4; python_version<"3"', 'numpy; python_version>="3"', diff --git a/external/workload-automation/wa/commands/revent.py b/external/workload-automation/wa/commands/revent.py index 87363122b693322462929b3545f554783cfa3a91..ab46f7e6d474410d5b4cbaea23c5238343df3ec8 100644 --- a/external/workload-automation/wa/commands/revent.py +++ b/external/workload-automation/wa/commands/revent.py @@ -25,10 +25,6 @@ from wa.framework.target.manager import TargetManager from wa.utils.revent import ReventRecorder -if sys.version_info[0] == 3: - raw_input = input # pylint: disable=redefined-builtin - - class RecordCommand(Command): name = 'record' @@ -137,11 +133,11 @@ class RecordCommand(Command): def record(self, revent_file, name, output_path): msg = 'Press Enter when you are ready to record {}...' self.logger.info(msg.format(name)) - raw_input('') + input('') self.revent_recorder.start_record(revent_file) msg = 'Press Enter when you have finished recording {}...' self.logger.info(msg.format(name)) - raw_input('') + input('') self.revent_recorder.stop_record() if not os.path.isdir(output_path): diff --git a/external/workload-automation/wa/commands/show.py b/external/workload-automation/wa/commands/show.py index 45531a4c4d38b733a018a83c294fedcdb9a30754..071b627e1b3b1ee0e3c1c38fbf9950c54dc0b3bf 100644 --- a/external/workload-automation/wa/commands/show.py +++ b/external/workload-automation/wa/commands/show.py @@ -73,11 +73,8 @@ class ShowCommand(Command): if which('pandoc'): p = Popen(['pandoc', '-f', 'rst', '-t', 'man'], stdin=PIPE, stdout=PIPE, stderr=PIPE) - if sys.version_info[0] == 3: - output, _ = p.communicate(rst_output.encode(sys.stdin.encoding)) - output = output.decode(sys.stdout.encoding) - else: - output, _ = p.communicate(rst_output) + output, _ = p.communicate(rst_output.encode(sys.stdin.encoding)) + output = output.decode(sys.stdout.encoding) # Make sure to double escape back slashes output = output.replace('\\', '\\\\\\') diff --git a/external/workload-automation/wa/framework/getters.py b/external/workload-automation/wa/framework/getters.py index 626bd03198d1371a5bd664ac9d7c421f422eecd1..9ca63bf7e83c294f5d9906d2faab84438d601429 100644 --- a/external/workload-automation/wa/framework/getters.py +++ b/external/workload-automation/wa/framework/getters.py @@ -244,10 +244,7 @@ class Http(ResourceGetter): response.status_code, response.reason)) return {} - if sys.version_info[0] == 3: - content = response.content.decode('utf-8') - else: - content = response.content + content = response.content.decode('utf-8') return json.loads(content) def download_asset(self, asset, owner_name): diff --git a/external/workload-automation/wa/framework/instrument.py b/external/workload-automation/wa/framework/instrument.py index 08da70251924b0f9b22177d26e2392a66df39162..663361662a78060ed68b9151e89a2b450f98f5f1 100644 --- a/external/workload-automation/wa/framework/instrument.py +++ b/external/workload-automation/wa/framework/instrument.py @@ -98,7 +98,6 @@ and the code to clear these file goes in teardown method. :: """ -import sys import logging import inspect from collections import OrderedDict @@ -325,10 +324,7 @@ def install(instrument, context): if not callable(attr): msg = 'Attribute {} not callable in {}.' raise ValueError(msg.format(attr_name, instrument)) - if sys.version_info[0] == 3: - argspec = inspect.getfullargspec(attr) - else: - argspec = inspect.getargspec(attr) # pylint: disable=deprecated-method + argspec = inspect.getfullargspec(attr) arg_num = len(argspec.args) # Instrument callbacks will be passed exactly two arguments: self # (the instrument instance to which the callback is bound) and diff --git a/external/workload-automation/wa/framework/plugin.py b/external/workload-automation/wa/framework/plugin.py index e2767782a4e2a89ede2f5f5ec5d349fb4fd6a85c..a59ded5e48b99aa7df1126c66782bf8e8593fa7b 100644 --- a/external/workload-automation/wa/framework/plugin.py +++ b/external/workload-automation/wa/framework/plugin.py @@ -18,8 +18,6 @@ import os import sys import inspect -import imp -import string import logging from collections import OrderedDict, defaultdict from itertools import chain @@ -32,16 +30,10 @@ from wa.framework.exception import (NotFoundError, PluginLoaderError, TargetErro ValidationError, ConfigError, HostError) from wa.utils import log from wa.utils.misc import (ensure_directory_exists as _d, walk_modules, load_class, - merge_dicts_simple, get_article) + merge_dicts_simple, get_article, import_path) from wa.utils.types import identifier -if sys.version_info[0] == 3: - MODNAME_TRANS = str.maketrans(':/\\.', '____') -else: - MODNAME_TRANS = string.maketrans(':/\\.', '____') - - class AttributeCollection(object): """ Accumulator for plugin attribute objects (such as Parameters or Artifacts). @@ -384,7 +376,7 @@ class TargetedPlugin(Plugin): """ - suppoted_targets = [] + supported_targets = [] parameters = [ Parameter('cleanup_assets', kind=bool, global_alias='cleanup_assets', @@ -398,8 +390,8 @@ class TargetedPlugin(Plugin): @classmethod def check_compatible(cls, target): - if cls.suppoted_targets: - if target.os not in cls.suppoted_targets: + if cls.supported_targets: + if target.os not in cls.supported_targets: msg = 'Incompatible target OS "{}" for {}' raise TargetError(msg.format(target.os, cls.name)) @@ -645,8 +637,7 @@ class PluginLoader(object): def _discover_from_file(self, filepath): try: - modname = os.path.splitext(filepath[1:])[0].translate(MODNAME_TRANS) - module = imp.load_source(modname, filepath) + module = import_path(filepath) self._discover_in_module(module) except (SystemExit, ImportError) as e: if self.keep_going: diff --git a/external/workload-automation/wa/framework/target/runtime_config.py b/external/workload-automation/wa/framework/target/runtime_config.py index bbbccee3c7bcba204f9511cc231f164501bf69a3..471c2ff400a50f25c30d8d90c44369a2ef87006b 100644 --- a/external/workload-automation/wa/framework/target/runtime_config.py +++ b/external/workload-automation/wa/framework/target/runtime_config.py @@ -878,6 +878,11 @@ class AndroidRuntimeConfig(RuntimeConfig): if value is not None: obj.config['screen_on'] = value + @staticmethod + def set_unlock_screen(obj, value): + if value is not None: + obj.config['unlock_screen'] = value + def __init__(self, target): self.config = defaultdict(dict) super(AndroidRuntimeConfig, self).__init__(target) @@ -930,6 +935,16 @@ class AndroidRuntimeConfig(RuntimeConfig): Specify whether the device screen should be on """) + param_name = 'unlock_screen' + self._runtime_params[param_name] = \ + RuntimeParameter( + param_name, kind=str, + default=None, + setter=self.set_unlock_screen, + description=""" + Specify how the device screen should be unlocked (e.g., vertical) + """) + def check_target(self): if self.target.os != 'android' and self.target.os != 'chromeos': raise ConfigError('Target does not appear to be running Android') @@ -940,6 +955,7 @@ class AndroidRuntimeConfig(RuntimeConfig): pass def commit(self): + # pylint: disable=too-many-branches if 'airplane_mode' in self.config: new_airplane_mode = self.config['airplane_mode'] old_airplane_mode = self.target.get_airplane_mode() @@ -964,13 +980,20 @@ class AndroidRuntimeConfig(RuntimeConfig): if 'brightness' in self.config: self.target.set_brightness(self.config['brightness']) + if 'rotation' in self.config: self.target.set_rotation(self.config['rotation']) + if 'screen_on' in self.config: if self.config['screen_on']: self.target.ensure_screen_is_on() else: self.target.ensure_screen_is_off() + if self.config.get('unlock_screen'): + self.target.ensure_screen_is_on() + if self.target.is_screen_locked(): + self.target.swipe_to_unlock(self.config['unlock_screen']) + def clear(self): self.config = {} diff --git a/external/workload-automation/wa/framework/version.py b/external/workload-automation/wa/framework/version.py index 50f0ec2d3d4ab733e2a1b066f1104c1756fa9a84..a329ce9cc9ecc2e10f8cfe4da729d4bdfa8651b6 100644 --- a/external/workload-automation/wa/framework/version.py +++ b/external/workload-automation/wa/framework/version.py @@ -57,7 +57,4 @@ def get_commit(): p.wait() if p.returncode: return None - if sys.version_info[0] == 3 and isinstance(std, bytes): - return std[:8].decode(sys.stdout.encoding or 'utf-8') - else: - return std[:8] + return std[:8].decode(sys.stdout.encoding or 'utf-8') diff --git a/external/workload-automation/wa/framework/workload.py b/external/workload-automation/wa/framework/workload.py index 645e2db87e6ab8c82dea68bdab11d6e57ca0b980..5abb90e3f668b8b900bbf685578b851c2f20f114 100644 --- a/external/workload-automation/wa/framework/workload.py +++ b/external/workload-automation/wa/framework/workload.py @@ -945,7 +945,7 @@ class TestPackageHandler(PackageHandler): def setup(self, context): self.initialize_package(context) - words = ['am', 'instrument'] + words = ['am', 'instrument', '--user', '0'] if self.raw: words.append('-r') if self.wait: diff --git a/external/workload-automation/wa/utils/misc.py b/external/workload-automation/wa/utils/misc.py index 810665e3bd8a7e5ae5c3c7e0c86755855da1df1e..3686914377e9e22745d46c13716cb93b1b5f516c 100644 --- a/external/workload-automation/wa/utils/misc.py +++ b/external/workload-automation/wa/utils/misc.py @@ -21,10 +21,12 @@ Miscellaneous functions that don't fit anywhere else. import errno import hashlib -import imp +import importlib +import inspect import logging import math import os +import pathlib import random import re import shutil @@ -39,10 +41,7 @@ from functools import reduce # pylint: disable=redefined-builtin from operator import mul from tempfile import gettempdir, NamedTemporaryFile from time import sleep -if sys.version_info[0] == 3: - from io import StringIO -else: - from io import BytesIO as StringIO +from io import StringIO # pylint: disable=wrong-import-position,unused-import from itertools import chain, cycle from distutils.spawn import find_executable # pylint: disable=no-name-in-module, import-error @@ -234,7 +233,12 @@ def load_class(classpath): """Loads the specified Python class. ``classpath`` must be a fully-qualified class name (i.e. namspaced under module/package).""" modname, clsname = classpath.rsplit('.', 1) - return getattr(__import__(modname), clsname) + mod = importlib.import_module(modname) + cls = getattr(mod, clsname) + if isinstance(cls, type): + return cls + else: + raise ValueError(f'The classpath "{classpath}" does not point at a class: {cls}') def get_pager(): @@ -285,7 +289,7 @@ def get_article(word): in all case; e.g. this will return ``"a hour"``. """ - return'an' if word[0] in 'aoeiu' else 'a' + return 'an' if word[0] in 'aoeiu' else 'a' def get_random_string(length): @@ -308,32 +312,57 @@ class LoadSyntaxError(Exception): RAND_MOD_NAME_LEN = 30 -def load_struct_from_python(filepath=None, text=None): +def import_path(filepath, module_name=None): + """ + Programmatically import the given Python source file under the name + ``module_name``. If ``module_name`` is not provided, a stable name based on + ``filepath`` will be created. Note that this module name cannot be relied + on, so don't make write import statements assuming this will be stable in + the future. + """ + if not module_name: + path = pathlib.Path(filepath).resolve() + id_ = to_identifier(str(path)) + module_name = f'wa._user_import.{id_}' + + try: + return sys.modules[module_name] + except KeyError: + spec = importlib.util.spec_from_file_location(module_name, filepath) + module = importlib.util.module_from_spec(spec) + try: + sys.modules[module_name] = module + spec.loader.exec_module(module) + except BaseException: + sys.modules.pop(module_name, None) + raise + else: + # We could return the "module" object, but that would not take into + # account any manipulation the module did on sys.modules when + # executing. To be consistent with the import statement, re-lookup + # the module name. + return sys.modules[module_name] + + +def load_struct_from_python(filepath): """Parses a config structure from a .py file. The structure should be composed of basic Python types (strings, ints, lists, dicts, etc.).""" - if not (filepath or text) or (filepath and text): - raise ValueError('Exactly one of filepath or text must be specified.') + try: - if filepath: - modname = to_identifier(filepath) - mod = imp.load_source(modname, filepath) - else: - modname = get_random_string(RAND_MOD_NAME_LEN) - while modname in sys.modules: # highly unlikely, but... - modname = get_random_string(RAND_MOD_NAME_LEN) - mod = imp.new_module(modname) - exec(text, mod.__dict__) # pylint: disable=exec-used - return dict((k, v) - for k, v in mod.__dict__.items() - if not k.startswith('_')) + mod = import_path(filepath) except SyntaxError as e: raise LoadSyntaxError(e.message, filepath, e.lineno) + else: + return { + k: v + for k, v in inspect.getmembers(mod) + if not k.startswith('_') + } def load_struct_from_yaml(filepath=None, text=None): """Parses a config structure from a .yaml file. The structure should be composed of basic Python types (strings, ints, lists, dicts, etc.).""" - # Import here to avoid circular imports # pylint: disable=wrong-import-position,cyclic-import, import-outside-toplevel from wa.utils.serializer import yaml diff --git a/external/workload-automation/wa/utils/types.py b/external/workload-automation/wa/utils/types.py index 5d7b60a50e0c14dd6a025ec76e4aa310d5014fd3..767b882bbefc7dd99ba5c8cee105a7e2552d1c0c 100644 --- a/external/workload-automation/wa/utils/types.py +++ b/external/workload-automation/wa/utils/types.py @@ -29,19 +29,14 @@ import os import re import numbers import shlex -import sys from bisect import insort -if sys.version_info[0] == 3: - from urllib.parse import quote, unquote # pylint: disable=no-name-in-module, import-error - from past.builtins import basestring # pylint: disable=redefined-builtin - long = int # pylint: disable=redefined-builtin -else: - from urllib import quote, unquote # pylint: disable=no-name-in-module +from urllib.parse import quote, unquote # pylint: disable=no-name-in-module, import-error # pylint: disable=wrong-import-position from collections import defaultdict from collections.abc import MutableMapping from functools import total_ordering +from past.builtins import basestring # pylint: disable=redefined-builtin from future.utils import with_metaclass from devlib.utils.types import identifier, boolean, integer, numeric, caseless_string @@ -710,8 +705,6 @@ class ParameterDict(dict): prefix = 'f' elif isinstance(obj, bool): prefix = 'b' - elif isinstance(obj, long): - prefix = 'i' elif isinstance(obj, int): prefix = 'i' elif obj is None: @@ -792,10 +785,7 @@ class ParameterDict(dict): return (key, self._decode(value)) def iter_encoded_items(self): - if sys.version_info[0] == 3: - return dict.items(self) - else: - return dict.iteritems(self) + return dict.items(self) def get_encoded_value(self, name): return dict.__getitem__(self, name) diff --git a/external/workload-automation/wa/workloads/jankbench/__init__.py b/external/workload-automation/wa/workloads/jankbench/__init__.py index 9b20bf1d9ee920c0748aac9580faa1860fbd5e95..4817fced7c4c0e3db8edf9ffc47782236d820a65 100644 --- a/external/workload-automation/wa/workloads/jankbench/__init__.py +++ b/external/workload-automation/wa/workloads/jankbench/__init__.py @@ -223,8 +223,7 @@ class JankbenchRunMonitor(threading.Thread): ready, _, _ = select.select([proc.stdout, proc.stderr], [], [], 2) if ready: line = ready[0].readline() - if sys.version_info[0] == 3: - line = line.decode(sys.stdout.encoding, 'replace') + line = line.decode(sys.stdout.encoding, 'replace') if self.regex.search(line): self.run_ended.set() proc.terminate() diff --git a/external/workload-automation/wa/workloads/meabo/__init__.py b/external/workload-automation/wa/workloads/meabo/__init__.py index 899fcfe3f848439512c2a4b431394c2b465bc3b6..faef07d41fbc23b7819fbb161860a106956401b6 100644 --- a/external/workload-automation/wa/workloads/meabo/__init__.py +++ b/external/workload-automation/wa/workloads/meabo/__init__.py @@ -256,10 +256,7 @@ class Meabo(Workload): outfile = os.path.join(context.output_directory, 'meabo-output.txt') with open(outfile, 'wb') as wfh: - if sys.version_info[0] == 3: - wfh.write(self.output.encode('utf-8')) - else: - wfh.write(self.output) + wfh.write(self.output.encode('utf-8')) context.add_artifact('meabo-output', outfile, kind='raw') cur_phase = 0 diff --git a/external/workload-automation/wa/workloads/pcmark/__init__.py b/external/workload-automation/wa/workloads/pcmark/__init__.py index d2f2279371549d6409cbffaccd32d3fa23298af6..a7020c41faab91ce279d0a60c7991a57510e7b23 100755 --- a/external/workload-automation/wa/workloads/pcmark/__init__.py +++ b/external/workload-automation/wa/workloads/pcmark/__init__.py @@ -89,8 +89,7 @@ class PcMark(ApkUiautoWorkload): def update_output(self, context): expected_results = len(self.regex_matches[self.major_version]) zf = zipfile.ZipFile(os.path.join(context.output_directory, self.result_file), 'r').read('Result.xml') - if sys.version_info[0] == 3: - zf = zf.decode(sys.stdout.encoding) + zf = zf.decode(sys.stdout.encoding) for line in zf.split('\n'): for regex in self.regex_matches[self.major_version]: match = regex.search(line)