diff options
Diffstat (limited to 'systrace/catapult/devil/devil/utils')
15 files changed, 212 insertions, 539 deletions
diff --git a/systrace/catapult/devil/devil/utils/__init__.py b/systrace/catapult/devil/devil/utils/__init__.py index ff84988..e69de29 100644 --- a/systrace/catapult/devil/devil/utils/__init__.py +++ b/systrace/catapult/devil/devil/utils/__init__.py @@ -1,23 +0,0 @@ -# Copyright 2015 The Chromium Authors. All rights reserved. -# Use of this source code is governed by a BSD-style license that can be -# found in the LICENSE file. - -import os -import sys - -def _JoinPath(*path_parts): - return os.path.abspath(os.path.join(*path_parts)) - - -def _AddDirToPythonPath(*path_parts): - path = _JoinPath(*path_parts) - if os.path.isdir(path) and path not in sys.path: - # Some call sites that use Telemetry assume that sys.path[0] is the - # directory containing the script, so we add these extra paths to right - # after sys.path[0]. - sys.path.insert(1, path) - -_CATAPULT_DIR = os.path.join(os.path.dirname(os.path.abspath(__file__)), - os.path.pardir, os.path.pardir, os.path.pardir) - -_AddDirToPythonPath(_CATAPULT_DIR, 'common', 'battor') diff --git a/systrace/catapult/devil/devil/utils/battor_device_mapping.py b/systrace/catapult/devil/devil/utils/battor_device_mapping.py deleted file mode 100755 index 8cabb83..0000000 --- a/systrace/catapult/devil/devil/utils/battor_device_mapping.py +++ /dev/null @@ -1,309 +0,0 @@ -#!/usr/bin/python -# Copyright 2016 The Chromium Authors. All rights reserved. -# Use of this source code is governed by a BSD-style license that can be -# found in the LICENSE file. - - -''' -This script provides tools to map BattOrs to phones. - -Phones are identified by the following string: - -"Phone serial number" - Serial number of the phone. This can be -obtained via 'adb devices' or 'usb-devices', and is not expected -to change for a given phone. - -BattOrs are identified by the following two strings: - -"BattOr serial number" - Serial number of the BattOr. This can be -obtained via 'usb-devices', and is not expected to change for -a given BattOr. - -"BattOr path" - The path of the form '/dev/ttyUSB*' that is used -to communicate with the BattOr (the battor_agent binary takes -this BattOr path as a parameter). The BattOr path is frequently -reassigned by the OS, most often when the device is disconnected -and then reconnected. Thus, the BattOr path cannot be expected -to be stable. - -In a typical application, the user will require the BattOr path -for the BattOr that is plugged into a given phone. For instance, -the user will be running tracing on a particular phone, and will -need to know which BattOr path to use to communicate with the BattOr -to get the corresponding power trace. - -Getting this mapping requires two steps: (1) determining the -mapping between phone serial numbers and BattOr serial numbers, and -(2) getting the BattOr path corresponding to a given BattOr serial -number. - -For step (1), we generate a JSON file giving this mapping. This -JSON file consists of a list of items of the following form: -[{'phone': <phone serial 1>, 'battor': <battor serial 1>}, -{'phone': <phone serial 2>, 'battor': <battor serial 2>}, ...] - -The default way to generate this JSON file is using the function -GenerateSerialMapFile, which generates a mapping based on assuming -that the system has two identical USB hubs connected to it, and -the phone plugged into physical port number 1 on one hub corresponds -to the BattOr plugged into physical port number 1 on the other hub, -and similarly with physical port numbers 2, 3, etc. This generates -the map file based on the structure at the time GenerateSerialMapFile called. -Note that after the map file is generated, port numbers are no longer used; -the user could move around the devices in the ports without affecting -which phone goes with which BattOr. (Thus, if the user wanted to update the -mapping to match the new port connections, the user would have to -re-generate this file.) - -The script update_mapping.py will do this updating from the command line. - -If the user wanted to specify a custom mapping, the user could instead -create the JSON file manually. (In this case, hubs would not be necessary -and the physical ports connected would be irrelevant.) - -Step (2) is conducted through the function GetBattOrPathFromPhoneSerial, -which takes a serial number mapping generated via step (1) and a phone -serial number, then gets the corresponding BattOr serial number from the -map and determines its BattOr path (e.g. /dev/ttyUSB0). Since BattOr paths -can change if devices are connected and disconnected (even if connected -or disconnected via the same port) this function should be called to -determine the BattOr path every time before connecting to the BattOr. - -Note that if there is only one BattOr connected to the system, then -GetBattOrPathFromPhoneSerial will always return that BattOr and will ignore -the mapping file. Thus, if the user never has more than one BattOr connected -to the system, the user will not need to generate mapping files. -''' - - -import json -import collections - -from battor import battor_error -from devil.utils import find_usb_devices -from devil.utils import usb_hubs - - -def GetBattOrList(device_tree_map): - return [x for x in find_usb_devices.GetTTYList() - if IsBattOr(x, device_tree_map)] - - -def IsBattOr(tty_string, device_tree_map): - (bus, device) = find_usb_devices.GetBusDeviceFromTTY(tty_string) - node = device_tree_map[bus].FindDeviceNumber(device) - return '0403:6001' in node.desc - - -def GetBattOrSerialNumbers(device_tree_map): - for x in find_usb_devices.GetTTYList(): - if IsBattOr(x, device_tree_map): - (bus, device) = find_usb_devices.GetBusDeviceFromTTY(x) - devnode = device_tree_map[bus].FindDeviceNumber(device) - yield devnode.serial - - -def ReadSerialMapFile(filename): - """Reads JSON file giving phone-to-battor serial number map. - - Parses a JSON file consisting of a list of items of the following form: - [{'phone': <phone serial 1>, 'battor': <battor serial 1>}, - {'phone': <phone serial 2>, 'battor': <battor serial 2>}, ...] - - indicating which phone serial numbers should be matched with - which BattOr serial numbers. Returns dictionary of the form: - - {<phone serial 1>: <BattOr serial 1>, - <phone serial 2>: <BattOr serial 2>} - - Args: - filename: Name of file to read. - """ - result = {} - with open(filename, 'r') as infile: - in_dict = json.load(infile) - for x in in_dict: - result[x['phone']] = x['battor'] - return result - -def WriteSerialMapFile(filename, serial_map): - """Writes a map of phone serial numbers to BattOr serial numbers to file. - - Writes a JSON file consisting of a list of items of the following form: - [{'phone': <phone serial 1>, 'battor': <battor serial 1>}, - {'phone': <phone serial 2>, 'battor': <battor serial 2>}, ...] - - indicating which phone serial numbers should be matched with - which BattOr serial numbers. Mapping is based on the physical port numbers - of the hubs that the BattOrs and phones are connected to. - - Args: - filename: Name of file to write. - serial_map: Serial map {phone: battor} - """ - result = [] - for (phone, battor) in serial_map.iteritems(): - result.append({'phone': phone, 'battor': battor}) - with open(filename, 'w') as outfile: - json.dump(result, outfile) - -def GenerateSerialMap(hub_types=None): - """Generates a map of phone serial numbers to BattOr serial numbers. - - Generates a dict of: - {<phone serial 1>: <battor serial 1>, - <phone serial 2>: <battor serial 2>} - indicating which phone serial numbers should be matched with - which BattOr serial numbers. Mapping is based on the physical port numbers - of the hubs that the BattOrs and phones are connected to. - - Args: - hub_types: List of hub types to check for. If not specified, checks - for all defined hub types. (see usb_hubs.py for details) - """ - if hub_types: - hub_types = [usb_hubs.GetHubType(x) for x in hub_types] - else: - hub_types = usb_hubs.ALL_HUBS - - devtree = find_usb_devices.GetBusNumberToDeviceTreeMap() - - # List of serial numbers in the system that represent BattOrs. - battor_serials = list(GetBattOrSerialNumbers(devtree)) - - # If there's only one BattOr in the system, then a serial number ma - # is not necessary. - if len(battor_serials) == 1: - return {} - - # List of dictionaries, one for each hub, that maps the physical - # port number to the serial number of that hub. For instance, in a 2 - # hub system, this could return [{1:'ab', 2:'cd'}, {1:'jkl', 2:'xyz'}] - # where 'ab' and 'cd' are the phone serial numbers and 'jkl' and 'xyz' - # are the BattOr serial numbers. - port_to_serial = find_usb_devices.GetAllPhysicalPortToSerialMaps( - hub_types, device_tree_map=devtree) - - class serials(object): - def __init__(self): - self.phone = None - self.battor = None - - # Map of {physical port number: [phone serial #, BattOr serial #]. This - # map is populated by executing the code below. For instance, in the above - # example, after the code below is executed, port_to_devices would equal - # {1: ['ab', 'jkl'], 2: ['cd', 'xyz']} - port_to_devices = collections.defaultdict(serials) - for hub in port_to_serial: - for (port, serial) in hub.iteritems(): - if serial in battor_serials: - if port_to_devices[port].battor is not None: - raise battor_error.BattOrError('Multiple BattOrs on same port number') - else: - port_to_devices[port].battor = serial - else: - if port_to_devices[port].phone is not None: - raise battor_error.BattOrError('Multiple phones on same port number') - else: - port_to_devices[port].phone = serial - - # Turn the port_to_devices map into a map of the form - # {phone serial number: BattOr serial number}. - result = {} - for pair in port_to_devices.values(): - if pair.phone is None: - continue - if pair.battor is None: - raise battor_error.BattOrError( - 'Phone detected with no corresponding BattOr') - result[pair.phone] = pair.battor - return result - -def GenerateSerialMapFile(filename, hub_types=None): - """Generates a serial map file and writes it.""" - WriteSerialMapFile(filename, GenerateSerialMap(hub_types)) - -def _PhoneToPathMap(serial, serial_map, devtree): - """Maps phone serial number to TTY path, assuming serial map is provided.""" - try: - battor_serial = serial_map[serial] - except KeyError: - raise battor_error.BattOrError('Serial number not found in serial map.') - for tree in devtree.values(): - for node in tree.AllNodes(): - if isinstance(node, find_usb_devices.USBDeviceNode): - if node.serial == battor_serial: - bus_device_to_tty = find_usb_devices.GetBusDeviceToTTYMap() - bus_device = (node.bus_num, node.device_num) - try: - return bus_device_to_tty[bus_device] - except KeyError: - raise battor_error.BattOrError( - 'Device with given serial number not a BattOr ' - '(does not have TTY path)') - - -def GetBattOrPathFromPhoneSerial(serial, serial_map=None, - serial_map_file=None): - """Gets the TTY path (e.g. '/dev/ttyUSB0') to communicate with the BattOr. - - (1) If serial_map is given, it is treated as a dictionary mapping - phone serial numbers to BattOr serial numbers. This function will get the - TTY path for the given BattOr serial number. - - (2) If serial_map_file is given, it is treated as the name of a - phone-to-BattOr mapping file (generated with GenerateSerialMapFile) - and this will be loaded and used as the dict to map port numbers to - BattOr serial numbers. - - You can only give one of serial_map and serial_map_file. - - Args: - serial: Serial number of phone connected on the same physical port that - the BattOr is connected to. - serial_map: Map of phone serial numbers to BattOr serial numbers, given - as a dictionary. - serial_map_file: Map of phone serial numbers to BattOr serial numbers, - given as a file. - hub_types: List of hub types to check for. Used only if serial_map_file - is None. - - Returns: - Device string used to communicate with device. - - Raises: - ValueError: If serial number is not given. - BattOrError: If BattOr not found or unexpected USB topology. - """ - # If there's only one BattOr connected to the system, just use that one. - # This allows for use on, e.g., a developer's workstation with no hubs. - devtree = find_usb_devices.GetBusNumberToDeviceTreeMap() - all_battors = GetBattOrList(devtree) - if len(all_battors) == 1: - return '/dev/' + all_battors[0] - - if not serial: - raise battor_error.BattOrError( - 'Two or more BattOrs connected, no serial provided') - - if serial_map and serial_map_file: - raise ValueError('Cannot specify both serial_map and serial_map_file') - - if serial_map_file: - serial_map = ReadSerialMapFile(serial_map_file) - - tty_string = _PhoneToPathMap(serial, serial_map, devtree) - - if not tty_string: - raise battor_error.BattOrError( - 'No device with given serial number detected.') - - if IsBattOr(tty_string, devtree): - return '/dev/' + tty_string - else: - raise battor_error.BattOrError( - 'Device with given serial number is not a BattOr.') - -if __name__ == '__main__': - # Main function for testing purposes - print GenerateSerialMap() diff --git a/systrace/catapult/devil/devil/utils/cmd_helper.py b/systrace/catapult/devil/devil/utils/cmd_helper.py index b477c70..3c4a06e 100644 --- a/systrace/catapult/devil/devil/utils/cmd_helper.py +++ b/systrace/catapult/devil/devil/utils/cmd_helper.py @@ -4,6 +4,7 @@ """A wrapper for subprocess to make calling shell commands easier.""" +import codecs import logging import os import pipes @@ -15,11 +16,16 @@ import subprocess import sys import time +from devil import base_error logger = logging.getLogger(__name__) _SafeShellChars = frozenset(string.ascii_letters + string.digits + '@%_-+=:,./') +# Cache the string-escape codec to ensure subprocess can find it +# later. Return value doesn't matter. +codecs.lookup('string-escape') + def SingleQuote(s): """Return an shell-escaped version of the string using single quotes. @@ -152,12 +158,12 @@ def _ValidateAndLogCommand(args, cwd, shell): else: if shell: raise Exception('array args must be run with shell=False') - args = ' '.join(SingleQuote(c) for c in args) + args = ' '.join(SingleQuote(str(c)) for c in args) if cwd is None: cwd = '' else: cwd = ':' + cwd - logger.info('[host]%s> %s', cwd, args) + logger.debug('[host]%s> %s', cwd, args) return args @@ -187,6 +193,27 @@ def GetCmdStatusAndOutput(args, cwd=None, shell=False, env=None): return (status, stdout) +def StartCmd(args, cwd=None, shell=False, env=None): + """Starts a subprocess and returns a handle to the process. + + Args: + args: A string or a sequence of program arguments. The program to execute is + the string or the first item in the args sequence. + cwd: If not None, the subprocess's current directory will be changed to + |cwd| before it's executed. + shell: Whether to execute args as a shell command. Must be True if args + is a string and False if args is a sequence. + env: If not None, a mapping that defines environment variables for the + subprocess. + + Returns: + A process handle from subprocess.Popen. + """ + _ValidateAndLogCommand(args, cwd, shell) + return Popen(args, stdout=subprocess.PIPE, stderr=subprocess.PIPE, + shell=shell, cwd=cwd, env=env) + + def GetCmdStatusOutputAndError(args, cwd=None, shell=False, env=None): """Executes a subprocess and returns its exit code, output, and errors. @@ -210,11 +237,11 @@ def GetCmdStatusOutputAndError(args, cwd=None, shell=False, env=None): return (pipe.returncode, stdout, stderr) -class TimeoutError(Exception): +class TimeoutError(base_error.BaseError): """Module-specific timeout exception.""" def __init__(self, output=None): - super(TimeoutError, self).__init__() + super(TimeoutError, self).__init__('Timeout') self._output = output @property @@ -226,6 +253,7 @@ def _IterProcessStdoutFcntl( process, iter_timeout=None, timeout=None, buffer_size=4096, poll_interval=1): """An fcntl-based implementation of _IterProcessStdout.""" + # pylint: disable=too-many-nested-blocks import fcntl try: # Enable non-blocking reads from the child's stdout. diff --git a/systrace/catapult/devil/devil/utils/find_usb_devices.py b/systrace/catapult/devil/devil/utils/find_usb_devices.py index 74b888d..b6dcc70 100755 --- a/systrace/catapult/devil/devil/utils/find_usb_devices.py +++ b/systrace/catapult/devil/devil/utils/find_usb_devices.py @@ -452,9 +452,9 @@ def GetBusDeviceFromTTY(tty_string): for line in _GetTtyUSBInfo(tty_string).splitlines(): bus_match = _BUS_NUM_REGEX.match(line) device_match = _DEVICE_NUM_REGEX.match(line) - if bus_match and bus_num == None: + if bus_match and bus_num is None: bus_num = int(bus_match.group(1)) - if device_match and device_num == None: + if device_match and device_num is None: device_num = int(device_match.group(1)) if bus_num is None or device_num is None: raise ValueError('Info not found') diff --git a/systrace/catapult/devil/devil/utils/find_usb_devices_test.py b/systrace/catapult/devil/devil/utils/find_usb_devices_test.py index e8b00c8..c22f21b 100755 --- a/systrace/catapult/devil/devil/utils/find_usb_devices_test.py +++ b/systrace/catapult/devil/devil/utils/find_usb_devices_test.py @@ -17,23 +17,21 @@ Bus 001: Bus 002: 1: Device 011 "quux" 2: Device 020 "My Test HUB" #hub 1 -2:1: Device 021 "battor_p7_h1_t0" #physical port 7 on hub 1, on ttyUSB0 -2:3: Device 022 "battor_p5_h1_t1" #physical port 5 on hub 1, on ttyUSB1 +2:1: Device 021 "usb_device_p7_h1_t0" #physical port 7 on hub 1, on ttyUSB0 +2:3: Device 022 "usb_device_p5_h1_t1" #physical port 5 on hub 1, on ttyUSB1 2:4: Device 023 "My Test Internal HUB" #internal section of hub 1 -2:4:2: Device 024 "battor_p3_h1_t2" #physical port 3 on hub 1, on ttyUSB2 +2:4:2: Device 024 "usb_device_p3_h1_t2" #physical port 3 on hub 1, on ttyUSB2 2:4:3: Device 026 "Not a Battery Monitor" #physical port 1 on hub 1, on ttyUSB3 -2:4:4: Device 025 "battor_p1_h1_t3" #physical port 1 on hub 1, on ttyUSB3 +2:4:4: Device 025 "usb_device_p1_h1_t3" #physical port 1 on hub 1, on ttyUSB3 3: Device 100 "My Test HUB" #hub 2 3:4: Device 101 "My Test Internal HUB" #internal section of hub 2 -3:4:4: Device 102 "battor_p1_h2_t4" #physical port 1 on hub 2, on ttyusb4 +3:4:4: Device 102 "usb_device_p1_h2_t4" #physical port 1 on hub 2, on ttyusb4 """ import logging -import os import unittest from devil import devil_env -from devil.utils import battor_device_mapping from devil.utils import find_usb_devices from devil.utils import lsusb from devil.utils import usb_hubs @@ -51,15 +49,15 @@ DEVLIST = [(1, 11, 'foo'), (1, 13, 'baz'), (2, 11, 'quux'), (2, 20, 'My Test HUB'), - (2, 21, 'ID 0403:6001 battor_p7_h1_t0'), - (2, 22, 'ID 0403:6001 battor_p5_h1_t1'), + (2, 21, 'ID 0403:6001 usb_device_p7_h1_t0'), + (2, 22, 'ID 0403:6001 usb_device_p5_h1_t1'), (2, 23, 'My Test Internal HUB'), - (2, 24, 'ID 0403:6001 battor_p3_h1_t2'), - (2, 25, 'ID 0403:6001 battor_p1_h1_t3'), + (2, 24, 'ID 0403:6001 usb_device_p3_h1_t2'), + (2, 25, 'ID 0403:6001 usb_device_p1_h1_t3'), (2, 26, 'Not a Battery Monitor'), (2, 100, 'My Test HUB'), (2, 101, 'My Test Internal HUB'), - (2, 102, 'ID 0403:6001 battor_p1_h1_t4')] + (2, 102, 'ID 0403:6001 usb_device_p1_h1_t4')] LSUSB_OUTPUT = [ {'bus': b, 'device': d, 'desc': t, 'id': (1000*b)+d} @@ -82,14 +80,14 @@ T: Bus=02 Lev=00 Prnt=00 Port=00 Cnt=00 Dev#= 11 Spd=000 MxCh=00 T: Bus=02 Lev=00 Prnt=00 Port=01 Cnt=00 Dev#= 20 Spd=000 MxCh=00 T: Bus=02 Lev=00 Prnt=20 Port=00 Cnt=00 Dev#= 21 Spd=000 MxCh=00 -S: SerialNumber=BattOr0 +S: SerialNumber=UsbDevice0 T: Bus=02 Lev=00 Prnt=20 Port=02 Cnt=00 Dev#= 22 Spd=000 MxCh=00 -S: SerialNumber=BattOr1 +S: SerialNumber=UsbDevice1 T: Bus=02 Lev=00 Prnt=20 Port=03 Cnt=00 Dev#= 23 Spd=000 MxCh=00 T: Bus=02 Lev=00 Prnt=23 Port=01 Cnt=00 Dev#= 24 Spd=000 MxCh=00 -S: SerialNumber=BattOr2 +S: SerialNumber=UsbDevice2 T: Bus=02 Lev=00 Prnt=23 Port=03 Cnt=00 Dev#= 25 Spd=000 MxCh=00 -S: SerialNumber=BattOr3 +S: SerialNumber=UsbDevice3 T: Bus=02 Lev=00 Prnt=23 Port=02 Cnt=00 Dev#= 26 Spd=000 MxCh=00 T: Bus=02 Lev=00 Prnt=00 Port=02 Cnt=00 Dev#=100 Spd=000 MxCh=00 @@ -103,15 +101,15 @@ Bus 001 Device 012: FAST bar Bus 001 Device 013: baz Bus 002 Device 011: quux Bus 002 Device 020: My Test HUB -Bus 002 Device 021: ID 0403:6001 battor_p7_h1_t0 -Bus 002 Device 022: ID 0403:6001 battor_p5_h1_t1 +Bus 002 Device 021: ID 0403:6001 usb_device_p7_h1_t0 +Bus 002 Device 022: ID 0403:6001 usb_device_p5_h1_t1 Bus 002 Device 023: My Test Internal HUB -Bus 002 Device 024: ID 0403:6001 battor_p3_h1_t2 -Bus 002 Device 025: ID 0403:6001 battor_p1_h1_t3 +Bus 002 Device 024: ID 0403:6001 usb_device_p3_h1_t2 +Bus 002 Device 025: ID 0403:6001 usb_device_p1_h1_t3 Bus 002 Device 026: Not a Battery Monitor Bus 002 Device 100: My Test HUB Bus 002 Device 101: My Test Internal HUB -Bus 002 Device 102: ID 0403:6001 battor_p1_h1_t4 +Bus 002 Device 102: ID 0403:6001 usb_device_p1_h1_t4 ''' LIST_TTY_OUTPUT = ''' @@ -213,17 +211,6 @@ class USBScriptTest(unittest.TestCase): lsusb.raw_lsusb = mock.Mock( return_value=RAW_LSUSB_OUTPUT) - def testIsBattOr(self): - bd = find_usb_devices.GetBusNumberToDeviceTreeMap() - self.assertTrue(battor_device_mapping.IsBattOr('ttyUSB3', bd)) - self.assertFalse(battor_device_mapping.IsBattOr('ttyUSB5', bd)) - - def testGetBattOrs(self): - bd = find_usb_devices.GetBusNumberToDeviceTreeMap() - self.assertEquals(battor_device_mapping.GetBattOrList(bd), - ['ttyUSB0', 'ttyUSB1', 'ttyUSB2', - 'ttyUSB3', 'ttyUSB4']) - def testGetTTYDevices(self): pp = find_usb_devices.GetAllPhysicalPortToTTYMaps([TEST_HUB]) result = list(pp) @@ -247,131 +234,49 @@ class USBScriptTest(unittest.TestCase): def testGetSerialMapping(self): pp = find_usb_devices.GetAllPhysicalPortToSerialMaps([TEST_HUB]) result = list(pp) - self.assertEquals(result[0], {7:'BattOr0', - 5:'BattOr1', - 3:'BattOr2', - 1:'BattOr3'}) + self.assertEquals(result[0], {7:'UsbDevice0', + 5:'UsbDevice1', + 3:'UsbDevice2', + 1:'UsbDevice3'}) self.assertEquals(result[1], {}) def testFastDeviceDescriptions(self): bd = find_usb_devices.GetBusNumberToDeviceTreeMap() dev_foo = bd[1].FindDeviceNumber(11) dev_bar = bd[1].FindDeviceNumber(12) - dev_battor_p7_h1_t0 = bd[2].FindDeviceNumber(21) + dev_usb_device_p7_h1_t0 = bd[2].FindDeviceNumber(21) self.assertEquals(dev_foo.desc, 'FAST foo') self.assertEquals(dev_bar.desc, 'FAST bar') - self.assertEquals(dev_battor_p7_h1_t0.desc, - 'ID 0403:6001 battor_p7_h1_t0') + self.assertEquals(dev_usb_device_p7_h1_t0.desc, + 'ID 0403:6001 usb_device_p7_h1_t0') def testDeviceDescriptions(self): bd = find_usb_devices.GetBusNumberToDeviceTreeMap(fast=False) dev_foo = bd[1].FindDeviceNumber(11) dev_bar = bd[1].FindDeviceNumber(12) - dev_battor_p7_h1_t0 = bd[2].FindDeviceNumber(21) + dev_usb_device_p7_h1_t0 = bd[2].FindDeviceNumber(21) self.assertEquals(dev_foo.desc, 'foo') self.assertEquals(dev_bar.desc, 'bar') - self.assertEquals(dev_battor_p7_h1_t0.desc, - 'ID 0403:6001 battor_p7_h1_t0') + self.assertEquals(dev_usb_device_p7_h1_t0.desc, + 'ID 0403:6001 usb_device_p7_h1_t0') def testDeviceInformation(self): bd = find_usb_devices.GetBusNumberToDeviceTreeMap(fast=False) dev_foo = bd[1].FindDeviceNumber(11) dev_bar = bd[1].FindDeviceNumber(12) - dev_battor_p7_h1_t0 = bd[2].FindDeviceNumber(21) + dev_usb_device_p7_h1_t0 = bd[2].FindDeviceNumber(21) self.assertEquals(dev_foo.info['id'], 1011) self.assertEquals(dev_bar.info['id'], 1012) - self.assertEquals(dev_battor_p7_h1_t0.info['id'], 2021) + self.assertEquals(dev_usb_device_p7_h1_t0.info['id'], 2021) def testSerialNumber(self): bd = find_usb_devices.GetBusNumberToDeviceTreeMap(fast=False) dev_foo = bd[1].FindDeviceNumber(11) dev_bar = bd[1].FindDeviceNumber(12) - dev_battor_p7_h1_t0 = bd[2].FindDeviceNumber(21) + dev_usb_device_p7_h1_t0 = bd[2].FindDeviceNumber(21) self.assertEquals(dev_foo.serial, 'FooSerial') self.assertEquals(dev_bar.serial, 'BarSerial') - self.assertEquals(dev_battor_p7_h1_t0.serial, 'BattOr0') - - def testBattOrDictMapping(self): - map_dict = {'Phone1':'BattOr1', 'Phone2':'BattOr2', 'Phone3':'BattOr3'} - a1 = battor_device_mapping.GetBattOrPathFromPhoneSerial( - 'Phone1', serial_map=map_dict) - a2 = battor_device_mapping.GetBattOrPathFromPhoneSerial( - 'Phone2', serial_map=map_dict) - a3 = battor_device_mapping.GetBattOrPathFromPhoneSerial( - 'Phone3', serial_map=map_dict) - self.assertEquals(a1, '/dev/ttyUSB1') - self.assertEquals(a2, '/dev/ttyUSB2') - self.assertEquals(a3, '/dev/ttyUSB3') - - def testBattOrDictFromFileMapping(self): - try: - map_dict = {'Phone1':'BattOr1', 'Phone2':'BattOr2', 'Phone3':'BattOr3'} - curr_dir = os.path.dirname(os.path.realpath(__file__)) - filename = os.path.join(curr_dir, 'test', 'data', 'test_write_map.json') - battor_device_mapping.WriteSerialMapFile(filename, map_dict) - a1 = battor_device_mapping.GetBattOrPathFromPhoneSerial( - 'Phone1', serial_map_file=filename) - a2 = battor_device_mapping.GetBattOrPathFromPhoneSerial( - 'Phone2', serial_map_file=filename) - a3 = battor_device_mapping.GetBattOrPathFromPhoneSerial( - 'Phone3', serial_map_file=filename) - finally: - os.remove(filename) - self.assertEquals(a1, '/dev/ttyUSB1') - self.assertEquals(a2, '/dev/ttyUSB2') - self.assertEquals(a3, '/dev/ttyUSB3') - - def testReadSerialMapFile(self): - curr_dir = os.path.dirname(os.path.realpath(__file__)) - map_dict = battor_device_mapping.ReadSerialMapFile( - os.path.join(curr_dir, 'test', 'data', 'test_serial_map.json')) - self.assertEquals(len(map_dict.keys()), 3) - self.assertEquals(map_dict['Phone1'], 'BattOr1') - self.assertEquals(map_dict['Phone2'], 'BattOr2') - self.assertEquals(map_dict['Phone3'], 'BattOr3') - -original_PPTSM = find_usb_devices.GetAllPhysicalPortToSerialMaps -original_PPTTM = find_usb_devices.GetAllPhysicalPortToTTYMaps -original_GBL = battor_device_mapping.GetBattOrList -original_GBNDM = find_usb_devices.GetBusNumberToDeviceTreeMap -original_IB = battor_device_mapping.IsBattOr -original_GBSM = battor_device_mapping.GetBattOrSerialNumbers - -def setup_battor_test(serial, tty, battor, bser=None): - serial_mapper = mock.Mock(return_value=serial) - tty_mapper = mock.Mock(return_value=tty) - battor_lister = mock.Mock(return_value=battor) - devtree = mock.Mock(return_value=None) - is_battor = mock.Mock(side_effect=lambda x, y: x in battor) - battor_serials = mock.Mock(return_value=bser) - find_usb_devices.GetAllPhysicalPortToSerialMaps = serial_mapper - find_usb_devices.GetAllPhysicalPortToTTYMaps = tty_mapper - battor_device_mapping.GetBattOrList = battor_lister - find_usb_devices.GetBusNumberToDeviceTreeMap = devtree - battor_device_mapping.IsBattOr = is_battor - battor_device_mapping.GetBattOrSerialNumbers = battor_serials - -class BattOrMappingTest(unittest.TestCase): - def tearDown(self): - find_usb_devices.GetAllPhysicalPortToSerialMaps = original_PPTSM - find_usb_devices.GetAllPhysicalPortToTTYMaps = original_PPTTM - battor_device_mapping.GetBattOrList = original_GBL - find_usb_devices.GetBusNumberToDeviceTreeMap = original_GBNDM - battor_device_mapping.IsBattOr = original_IB - battor_device_mapping.GetBattOrSerialNumbers = original_GBSM - - def test_generate_serial_map(self): - setup_battor_test([{1:'Phn1', 2:'Phn2', 3:'Phn3'}, - {1:'Bat1', 2:'Bat2', 3:'Bat3'}], - [{}, - {1:'ttyUSB0', 2:'ttyUSB1', 3:'ttyUSB2'}], - ['ttyUSB0', 'ttyUSB1', 'ttyUSB2'], - ['Bat1', 'Bat2', 'Bat3']) - result = battor_device_mapping.GenerateSerialMap() - self.assertEqual(len(result), 3) - self.assertEqual(result['Phn1'], 'Bat1') - self.assertEqual(result['Phn2'], 'Bat2') - self.assertEqual(result['Phn3'], 'Bat3') + self.assertEquals(dev_usb_device_p7_h1_t0.serial, 'UsbDevice0') if __name__ == "__main__": diff --git a/systrace/catapult/devil/devil/utils/lazy/weak_constant.py b/systrace/catapult/devil/devil/utils/lazy/weak_constant.py index 3558f29..24ad940 100644 --- a/systrace/catapult/devil/devil/utils/lazy/weak_constant.py +++ b/systrace/catapult/devil/devil/utils/lazy/weak_constant.py @@ -4,6 +4,9 @@ import threading +from devil.utils import reraiser_thread +from devil.utils import timeout_retry + class WeakConstant(object): """A thread-safe, lazily initialized object. @@ -13,17 +16,27 @@ class WeakConstant(object): """ def __init__(self, initializer): - self._initialized = False + self._initialized = threading.Event() self._initializer = initializer self._lock = threading.Lock() self._val = None def read(self): """Get the object, creating it if necessary.""" - if self._initialized: + if self._initialized.is_set(): return self._val with self._lock: - if not self._initialized: - self._val = self._initializer() - self._initialized = True + if not self._initialized.is_set(): + # We initialize the value on a separate thread to protect + # from holding self._lock indefinitely in the event that + # self._initializer hangs. + initializer_thread = reraiser_thread.ReraiserThread( + self._initializer) + initializer_thread.start() + timeout_retry.WaitFor( + lambda: initializer_thread.join(1) or not initializer_thread.isAlive(), + wait_period=0) + self._val = initializer_thread.GetReturnValue() + self._initialized.set() + return self._val diff --git a/systrace/catapult/devil/devil/utils/lazy/weak_constant_test.py b/systrace/catapult/devil/devil/utils/lazy/weak_constant_test.py new file mode 100644 index 0000000..643351d --- /dev/null +++ b/systrace/catapult/devil/devil/utils/lazy/weak_constant_test.py @@ -0,0 +1,70 @@ +#!/usr/bin/env python +# Copyright 2018 The Chromium Authors. All rights reserved. +# Use of this source code is governed by a BSD-style license that can be +# found in the LICENSE file. + +# pylint: disable=protected-access + +import time +import unittest + +from devil import devil_env +from devil.utils import lazy +from devil.utils import timeout_retry + +with devil_env.SysPath(devil_env.PYMOCK_PATH): + import mock + + +class DynamicSideEffect(object): + """A helper object for handling a sequence of single-use side effects.""" + + def __init__(self, side_effects): + self._side_effects = iter(side_effects or []) + + def __call__(self): + val = next(self._side_effects)() + if isinstance(val, Exception): + raise val + return val + + +class WeakConstantTest(unittest.TestCase): + + def testUninitialized(self): + """Ensure that the first read calls the initializer.""" + initializer = mock.Mock(return_value='initializer called') + test_constant = lazy.WeakConstant(initializer) + self.assertEquals( + 'initializer called', + test_constant.read()) + initializer.assert_called_once_with() + + def testInitialized(self): + """Ensure that reading doesn't reinitialize the value.""" + initializer = mock.Mock(return_value='initializer called') + test_constant = lazy.WeakConstant(initializer) + test_constant._initialized.set() + test_constant._val = 'initializer not called' + self.assertEquals( + 'initializer not called', + test_constant.read()) + self.assertFalse(initializer.mock_calls) # assert not called + + def testFirstCallHangs(self): + """Ensure that reading works even if the first initializer call hangs.""" + dyn = DynamicSideEffect([ + lambda: time.sleep(10), + lambda: 'second try worked!' + ]) + + initializer = mock.Mock(side_effect=dyn) + test_constant = lazy.WeakConstant(initializer) + self.assertEquals( + 'second try worked!', + timeout_retry.Run(test_constant.read, 1, 1)) + initializer.assert_has_calls([mock.call(), mock.call()]) + + +if __name__ == '__main__': + unittest.main() diff --git a/systrace/catapult/devil/devil/utils/logging_common.py b/systrace/catapult/devil/devil/utils/logging_common.py index 5aea3c6..ab364a2 100644 --- a/systrace/catapult/devil/devil/utils/logging_common.py +++ b/systrace/catapult/devil/devil/utils/logging_common.py @@ -8,13 +8,32 @@ import time def AddLoggingArguments(parser): - parser.add_argument( + """Adds standard logging flags to the parser. + + After parsing args, remember to invoke InitializeLogging() with the parsed + args, to configure the log level. + """ + group = parser.add_mutually_exclusive_group() + group.add_argument( '-v', '--verbose', action='count', default=0, help='Log more. Use multiple times for even more logging.') + group.add_argument( + '-q', '--quiet', action='count', default=0, + help=('Log less (suppress output). Use multiple times for even less ' + 'output.')) def InitializeLogging(args, handler=None): - if args.verbose == 0: + """Initialized the log level based on commandline flags. + + This expects to be given an "args" object with the options defined by + AddLoggingArguments(). + """ + if args.quiet >= 2: + log_level = logging.CRITICAL + elif args.quiet == 1: + log_level = logging.ERROR + elif args.verbose == 0: log_level = logging.WARNING elif args.verbose == 1: log_level = logging.INFO diff --git a/systrace/catapult/devil/devil/utils/markdown.py b/systrace/catapult/devil/devil/utils/markdown.py index 54e7ed5..ba66664 100755 --- a/systrace/catapult/devil/devil/utils/markdown.py +++ b/systrace/catapult/devil/devil/utils/markdown.py @@ -3,6 +3,8 @@ # Use of this source code is governed by a BSD-style license that can be # found in the LICENSE file. +from __future__ import print_function + import argparse import imp import os @@ -182,7 +184,7 @@ def md_module(module_obj, module_path=None, module_link=None): A list of markdown-formatted lines. """ def should_doc(name): - return (type(module_obj.__dict__[name]) != types.ModuleType + return (not isinstance(module_obj.__dict__[name], types.ModuleType) and not name.startswith('_')) stuff_to_doc = sorted( @@ -193,9 +195,9 @@ def md_module(module_obj, module_path=None, module_link=None): functions_to_doc = [] for s in stuff_to_doc: - if type(s) == types.TypeType: + if isinstance(s, types.TypeType): classes_to_doc.append(s) - elif type(s) == types.FunctionType: + elif isinstance(s, types.FunctionType): functions_to_doc.append(s) command = ['devil/utils/markdown.py'] @@ -221,7 +223,7 @@ def md_module(module_obj, module_path=None, module_link=None): for f in functions_to_doc: content += md_function(f) - print '\n'.join(content) + print('\n'.join(content)) return 0 @@ -243,7 +245,7 @@ def md_class(class_obj): content.extend(md_docstring(class_obj.__doc__)) def should_doc(name, obj): - return (type(obj) == types.FunctionType + return (isinstance(obj, types.FunctionType) and (name.startswith('__') or not name.startswith('_'))) methods_to_doc = sorted( diff --git a/systrace/catapult/devil/devil/utils/reraiser_thread.py b/systrace/catapult/devil/devil/utils/reraiser_thread.py index 56d95f3..6e6c810 100644 --- a/systrace/catapult/devil/devil/utils/reraiser_thread.py +++ b/systrace/catapult/devil/devil/utils/reraiser_thread.py @@ -11,12 +11,14 @@ import threading import time import traceback +from devil import base_error from devil.utils import watchdog_timer -class TimeoutError(Exception): +class TimeoutError(base_error.BaseError): """Module-specific timeout exception.""" - pass + def __init__(self, message): + super(TimeoutError, self).__init__(message) def LogThreadStack(thread, error_log_func=logging.critical): @@ -47,10 +49,13 @@ class ReraiserThread(threading.Thread): func: callable to call on a new thread. args: list of positional arguments for callable, defaults to empty. kwargs: dictionary of keyword arguments for callable, defaults to empty. - name: thread name, defaults to Thread-N. + name: thread name, defaults to the function name. """ - if not name and func.__name__ != '<lambda>': - name = func.__name__ + if not name: + if hasattr(func, '__name__') and func.__name__ != '<lambda>': + name = func.__name__ + else: + name = 'anonymous' super(ReraiserThread, self).__init__(name=name) if not args: args = [] @@ -64,10 +69,17 @@ class ReraiserThread(threading.Thread): self._exc_info = None self._thread_group = None - def ReraiseIfException(self): - """Reraise exception if an exception was raised in the thread.""" - if self._exc_info: - raise self._exc_info[0], self._exc_info[1], self._exc_info[2] + if sys.version_info < (3,): + # pylint: disable=exec-used + exec('''def ReraiseIfException(self): + """Reraise exception if an exception was raised in the thread.""" + if self._exc_info: + raise self._exc_info[0], self._exc_info[1], self._exc_info[2]''') + else: + def ReraiseIfException(self): + """Reraise exception if an exception was raised in the thread.""" + if self._exc_info: + raise self._exc_info[1] def GetReturnValue(self): """Reraise exception if present, otherwise get the return value.""" diff --git a/systrace/catapult/devil/devil/utils/reset_usb.py b/systrace/catapult/devil/devil/utils/reset_usb.py index 0335227..404a44c 100755 --- a/systrace/catapult/devil/devil/utils/reset_usb.py +++ b/systrace/catapult/devil/devil/utils/reset_usb.py @@ -3,12 +3,15 @@ # Use of this source code is governed by a BSD-style license that can be # found in the LICENSE file. +import sys +if sys.platform == 'win32': + raise ImportError('devil.utils.reset_usb only supported on unix systems.') + import argparse import fcntl import logging import os import re -import sys if __name__ == '__main__': sys.path.append( diff --git a/systrace/catapult/devil/devil/utils/run_tests_helper.py b/systrace/catapult/devil/devil/utils/run_tests_helper.py index 7f71b65..0b9dd47 100644 --- a/systrace/catapult/devil/devil/utils/run_tests_helper.py +++ b/systrace/catapult/devil/devil/utils/run_tests_helper.py @@ -14,7 +14,7 @@ CustomFormatter = logging_common.CustomFormatter _WrappedLoggingArgs = collections.namedtuple( - '_WrappedLoggingArgs', ['verbose']) + '_WrappedLoggingArgs', ['verbose', 'quiet']) def SetLogLevel(verbose_count, add_handler=True): @@ -25,5 +25,5 @@ def SetLogLevel(verbose_count, add_handler=True): add_handler: If true, adds a handler with |CustomFormatter|. """ logging_common.InitializeLogging( - _WrappedLoggingArgs(verbose_count), + _WrappedLoggingArgs(verbose_count, 0), handler=None if add_handler else logging.NullHandler()) diff --git a/systrace/catapult/devil/devil/utils/test/data/test_serial_map.json b/systrace/catapult/devil/devil/utils/test/data/test_serial_map.json deleted file mode 100644 index f068281..0000000 --- a/systrace/catapult/devil/devil/utils/test/data/test_serial_map.json +++ /dev/null @@ -1 +0,0 @@ -[{"phone": "Phone1", "battor": "BattOr1"}, {"phone": "Phone2", "battor": "BattOr2"}, {"phone": "Phone3", "battor": "BattOr3"}] diff --git a/systrace/catapult/devil/devil/utils/timeout_retry.py b/systrace/catapult/devil/devil/utils/timeout_retry.py index 2327b6b..d662c1d 100644 --- a/systrace/catapult/devil/devil/utils/timeout_retry.py +++ b/systrace/catapult/devil/devil/utils/timeout_retry.py @@ -109,7 +109,8 @@ def WaitFor(condition, wait_period=5, max_tries=None): # pylint: disable=no-member timeout_thread_group.GetRemainingTime(wait_period, suffix=' waiting for condition %r' % condition_name) - time.sleep(wait_period) + if wait_period: + time.sleep(wait_period) return None diff --git a/systrace/catapult/devil/devil/utils/update_mapping.py b/systrace/catapult/devil/devil/utils/update_mapping.py deleted file mode 100755 index 6666b9b..0000000 --- a/systrace/catapult/devil/devil/utils/update_mapping.py +++ /dev/null @@ -1,47 +0,0 @@ -#!/usr/bin/env python - -# Copyright 2016 The Chromium Authors. All rights reserved. -# Use of this source code is governed by a BSD-style license that can be -# found in the LICENSE file. - -import argparse -import sys - -from devil.utils import battor_device_mapping - -def parse_options(): - """Parses and checks the command-line options. - - Returns: - A tuple containing the options structure. - """ - usage = 'Usage: ./update_mapping.py [options]' - desc = ('Example: ./update_mapping.py -o mapping.json.\n' - 'This script generates and stores a file that gives the\n' - 'mapping between phone serial numbers and BattOr serial numbers\n' - 'Mapping is based on which physical ports on the USB hubs the\n' - 'devices are plugged in to. For instance, if there are two hubs,\n' - 'the phone connected to port N on the first hub is mapped to the\n' - 'BattOr connected to port N on the second hub, for each N.') - parser = argparse.ArgumentParser(usage=usage, description=desc) - parser.add_argument('-o', '--output', dest='out_file', - default='mapping.json', type=str, - action='store', help='mapping file name') - parser.add_argument('-u', '--hub', dest='hub_types', - action='append', choices=['plugable_7port', - 'plugable_7port_usb3_part2', - 'plugable_7port_usb3_part3'], - help='USB hub types.') - options = parser.parse_args() - if not options.hub_types: - options.hub_types = ['plugable_7port', 'plugable_7port_usb3_part2', - 'plugable_7port_usb3_part3'] - return options - -def main(): - options = parse_options() - battor_device_mapping.GenerateSerialMapFile(options.out_file, - options.hub_types) - -if __name__ == "__main__": - sys.exit(main()) |