Chromium Code Reviews
chromiumcodereview-hr@appspot.gserviceaccount.com (chromiumcodereview-hr) | Please choose your nickname with Settings | Help | Chromium Project | Gerrit Changes | Sign out
(753)

Side by Side Diff: build/android/pylib/device/device_utils.py

Issue 290573004: [Android] Support generic parallel execution across devices. (Closed) Base URL: https://chromium.googlesource.com/chromium/src.git@master
Patch Set: Created 6 years, 7 months ago
Use n/p to move between diff chunks; N/P to move between comments. Draft comments are only viewable by you.
Jump to:
View unified diff | Download patch
OLDNEW
1 # Copyright 2014 The Chromium Authors. All rights reserved. 1 # Copyright 2014 The Chromium Authors. All rights reserved.
2 # Use of this source code is governed by a BSD-style license that can be 2 # Use of this source code is governed by a BSD-style license that can be
3 # found in the LICENSE file. 3 # found in the LICENSE file.
4 4
5 """ 5 """
6 Provides a variety of device interactions based on adb. 6 Provides a variety of device interactions based on adb.
7 7
8 Eventually, this will be based on adb_wrapper. 8 Eventually, this will be based on adb_wrapper.
9 """ 9 """
10 # pylint: disable=W0613 10 # pylint: disable=W0613
11 11
12 import multiprocessing
13 import os
14 import sys
15
16 import pylib.android_commands 12 import pylib.android_commands
17 from pylib.device import adb_wrapper 13 from pylib.device import adb_wrapper
18 from pylib.device import decorators 14 from pylib.device import decorators
19 15 from pylib.device import device_errors
20 CHROME_SRC_DIR = os.path.abspath( 16 from pylib.utils import reraiser_thread
21 os.path.join(os.path.dirname(__file__), '..', '..', '..', '..')) 17 from pylib.utils import watchdog_timer
22 sys.path.append(os.path.join(
23 CHROME_SRC_DIR, 'third_party', 'android_testrunner'))
24 import errors
25 18
26 _DEFAULT_TIMEOUT = 30 19 _DEFAULT_TIMEOUT = 30
27 _DEFAULT_RETRIES = 3 20 _DEFAULT_RETRIES = 3
28 21
29 22
30 # multiprocessing map_async requires a top-level function for pickle library.
31 def RebootDeviceSafe(device):
32 """Reboot a device, wait for it to start, and squelch timeout exceptions."""
33 try:
34 DeviceUtils(device).old_interface.Reboot(True)
35 except errors.DeviceUnresponsiveError as e:
36 return e
37
38
39 def RebootDevices():
40 """Reboot all attached and online devices."""
41 devices = pylib.android_commands.GetAttachedDevices()
42 print 'Rebooting: %s' % devices
43 if devices:
44 pool = multiprocessing.Pool(len(devices))
45 results = pool.map_async(RebootDeviceSafe, devices).get(99999)
46
47 for device, result in zip(devices, results):
48 if result:
49 print '%s failed to startup.' % device
50
51 if any(results):
52 print 'RebootDevices() Warning: %s' % results
53 else:
54 print 'Reboots complete.'
55
56
57 @decorators.WithExplicitTimeoutAndRetries( 23 @decorators.WithExplicitTimeoutAndRetries(
58 _DEFAULT_TIMEOUT, _DEFAULT_RETRIES) 24 _DEFAULT_TIMEOUT, _DEFAULT_RETRIES)
59 def GetAVDs(): 25 def GetAVDs():
60 """ Returns a list of Android Virtual Devices. 26 """ Returns a list of Android Virtual Devices.
61 27
62 Returns: 28 Returns:
63 A list containing the configured AVDs. 29 A list containing the configured AVDs.
64 """ 30 """
65 return pylib.android_commands.GetAVDs() 31 return pylib.android_commands.GetAVDs()
66 32
67 33
68 @decorators.WithExplicitTimeoutAndRetries( 34 @decorators.WithExplicitTimeoutAndRetries(
69 _DEFAULT_TIMEOUT, _DEFAULT_RETRIES) 35 _DEFAULT_TIMEOUT, _DEFAULT_RETRIES)
70 def RestartServer(): 36 def RestartServer():
71 """ Restarts the adb server. 37 """ Restarts the adb server.
72 38
73 Raises: 39 Raises:
74 CommandFailedError if we fail to kill or restart the server. 40 CommandFailedError if we fail to kill or restart the server.
75 """ 41 """
76 pylib.android_commands.AndroidCommands().RestartAdbServer() 42 pylib.android_commands.AndroidCommands().RestartAdbServer()
77 43
78 44
45 class _ParallelExecutor(object):
frankf 2014/05/16 17:47:26 this should be in its own module with tests
frankf 2014/05/16 17:47:26 Also think about how to organize various modes of
jbudorick 2014/05/16 17:52:50 I debated about the separate module thing. Opted n
craigdh 2014/05/16 21:59:35 This is probably worth sticking in utils/, probabl
jbudorick 2014/05/21 16:42:48 Done.
46 """ Allows parallel execution across a provided set of devices.
frankf 2014/05/16 17:47:26 """First line of docstinrg. Additional comments "
jbudorick 2014/05/16 17:52:50 In general -- what if there are no additional comm
frankf 2014/05/16 18:41:27 The former for single line docstring. On 2014/05
47 """
48
49 def __init__(self, device_list=None):
50 if not device_list:
51 self._devices = [
52 DeviceUtils(s) for s in
53 pylib.android_commands.GetAttachedDevices()]
54 else:
55 device_list_type = self._typeOfContents(device_list)
56 if issubclass(device_list_type, (basestring,
57 pylib.android_commands.AndroidCommands,
58 adb_wrapper.AdbWrapper)):
59 self._devices = [DeviceUtils(d) for d in device_list]
60 else:
61 self._devices = device_list
62 if not len(self._devices):
63 # TODO(jbudorick) Should this be legal?
64 raise ValueError('No devices connected.')
craigdh 2014/05/16 21:59:35 I don't think ValueError is the right exception to
jbudorick 2014/05/21 16:42:48 What would you suggest? IndexError? A separate exc
craigdh 2014/05/21 16:50:13 Maybe RuntimeError. Or just do an assert. ValueErr
65 self._objs = self._devices
66 self._emulated_type = self._typeOfContents(self._objs)
67
68 @staticmethod
69 def _typeOfContents(objs):
70 assert(all([type(objs[0]) == type(o) for o in objs]))
71 return type(objs[0]) if len(objs) else type(None)
72
73 def __getattr__(self, name):
74 """ Emulate getting the |name| attribute of |self|.
75
76 Args:
77 name: The name of the attribute to retrieve.
78 """
79 r = _ParallelExecutor(self._devices)
80 r._objs = [getattr(o, name) for o in self._objs]
81
82 # pylint: disable=W0212
83 emulated_type = self._typeOfContents(r._objs)
84 if all([callable(o) for o in r._objs]):
craigdh 2014/05/16 21:59:35 prefer generator expressions over list comprehensi
jbudorick 2014/05/21 16:42:48 Done.
85 r._emulated_type = self._emulated_type
86 else:
87 r._emulated_type = emulated_type
88 # pylint: enable=W0212
89 return r
90
91 def __getitem__(self, index):
92 """ Emulate getting the value of |self| at |index|.
93 """
94 r = _ParallelExecutor(self._devices)
95 r._objs = [o[index] for o in self._objs]
96 return r
97
98 def __call__(self, *args, **kwargs):
99 """ Emulate calling |self| with |args| and |kwargs|.
100 """
101 if not self._objs:
102 raise AttributeError('Nothing to call.')
103 for o in self._objs:
104 if not callable(o):
105 raise AttributeError("'%s' is not callable" % o.__name__)
106
107 r = _ParallelExecutor(self._devices)
108 r._objs = reraiser_thread.ReraiserThreadGroup(
109 [reraiser_thread.ReraiserThread(
110 o, args=args, kwargs=kwargs,
111 name='%s.%s' % (str(d), o.__name__))
112 for d, o in zip(self._devices, self._objs)])
113 r._objs.StartAll() # pylint: disable=W0212
114 return r
115
116 def get(self, timeout):
117 """ Get the return values from the most recent call.
118
119 Args:
120 timeout: The maximum number of seconds to wait for an individual
121 result to return.
122 Returns:
123 A list of the results, in order of the provided devices.
124 Raises:
125 Any exception raised by any of the called functions, or
126 CommandTimeoutError on timeout.
127 """
128 try:
129 if isinstance(self._objs, reraiser_thread.ReraiserThreadGroup):
130 self._objs.JoinAll()
131 self._objs = self._objs.Get(watchdog_timer.WatchdogTimer(timeout))
132 return self._objs
133 except reraiser_thread.TimeoutError as e:
134 raise device_errors.CommandTimeoutError(str(e))
135
136
79 class DeviceUtils(object): 137 class DeviceUtils(object):
80 138
139 parallel = _ParallelExecutor
140
81 def __init__(self, device): 141 def __init__(self, device):
82 self.old_interface = None 142 self.old_interface = None
83 if isinstance(device, basestring): 143 if isinstance(device, basestring):
84 self.old_interface = pylib.android_commands.AndroidCommands(device) 144 self.old_interface = pylib.android_commands.AndroidCommands(device)
85 elif isinstance(device, adb_wrapper.AdbWrapper): 145 elif isinstance(device, adb_wrapper.AdbWrapper):
86 self.old_interface = pylib.android_commands.AndroidCommands(str(device)) 146 self.old_interface = pylib.android_commands.AndroidCommands(str(device))
87 elif isinstance(device, pylib.android_commands.AndroidCommands): 147 elif isinstance(device, pylib.android_commands.AndroidCommands):
88 self.old_interface = device 148 self.old_interface = device
89 elif not device: 149 elif not device:
90 self.old_interface = pylib.android_commands.AndroidCommands() 150 self.old_interface = pylib.android_commands.AndroidCommands()
91 151
152 def __str__(self):
153 return self.old_interface.GetDevice()
154
OLDNEW

Powered by Google App Engine
This is Rietveld 408576698