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
3 # found in the LICENSE file.
5 """Environment setup and teardown for remote devices."""
7 import distutils
.version
14 from devil
.utils
import reraiser_thread
15 from devil
.utils
import timeout_retry
16 from pylib
.base
import environment
17 from pylib
.remote
.device
import appurify_sanitized
18 from pylib
.remote
.device
import remote_device_helper
20 class RemoteDeviceEnvironment(environment
.Environment
):
21 """An environment for running on remote devices."""
24 _DEVICE_KEY
= 'device'
27 def __init__(self
, args
, error_func
):
31 args: Command line arguments.
32 error_func: error to show when using bad command line arguments.
34 super(RemoteDeviceEnvironment
, self
).__init
__()
35 self
._access
_token
= None
37 self
._device
_type
= args
.device_type
38 self
._verbose
_count
= args
.verbose_count
41 'installing': 60 * 10,
42 'in-progress': 60 * 30,
45 # Example config file:
47 # "remote_device": ["Galaxy S4", "Galaxy S3"],
48 # "remote_device_os": ["4.4.2", "4.4.4"],
49 # "remote_device_minimum_os": "4.4.2",
50 # "api_address": "www.example.com",
52 # "api_protocol": "http",
53 # "api_secret": "apisecret",
54 # "api_key": "apikey",
58 # "in-progress": 1800,
62 if args
.remote_device_file
:
63 with
open(args
.remote_device_file
) as device_file
:
64 device_json
= json
.load(device_file
)
68 self
._api
_address
= device_json
.get('api_address', None)
69 self
._api
_key
= device_json
.get('api_key', None)
70 self
._api
_port
= device_json
.get('api_port', None)
71 self
._api
_protocol
= device_json
.get('api_protocol', None)
72 self
._api
_secret
= device_json
.get('api_secret', None)
73 self
._device
_oem
= device_json
.get('device_oem', None)
74 self
._device
_type
= device_json
.get('device_type', 'Android')
75 self
._network
_config
= device_json
.get('network_config', None)
76 self
._remote
_device
= device_json
.get('remote_device', None)
77 self
._remote
_device
_minimum
_os
= device_json
.get(
78 'remote_device_minimum_os', None)
79 self
._remote
_device
_os
= device_json
.get('remote_device_os', None)
80 self
._remote
_device
_timeout
= device_json
.get(
81 'remote_device_timeout', None)
82 self
._results
_path
= device_json
.get('results_path', None)
83 self
._runner
_package
= device_json
.get('runner_package', None)
84 self
._runner
_type
= device_json
.get('runner_type', None)
85 self
._timeouts
.update(device_json
.get('timeouts', {}))
87 def command_line_override(
88 file_value
, cmd_line_value
, desc
, print_value
=True):
90 if file_value
and file_value
!= cmd_line_value
:
92 logging
.info('Overriding %s from %s to %s',
93 desc
, file_value
, cmd_line_value
)
95 logging
.info('overriding %s', desc
)
99 self
._api
_address
= command_line_override(
100 self
._api
_address
, args
.api_address
, 'api_address')
101 self
._api
_port
= command_line_override(
102 self
._api
_port
, args
.api_port
, 'api_port')
103 self
._api
_protocol
= command_line_override(
104 self
._api
_protocol
, args
.api_protocol
, 'api_protocol')
105 self
._device
_oem
= command_line_override(
106 self
._device
_oem
, args
.device_oem
, 'device_oem')
107 self
._device
_type
= command_line_override(
108 self
._device
_type
, args
.device_type
, 'device_type')
109 self
._network
_config
= command_line_override(
110 self
._network
_config
, args
.network_config
, 'network_config')
111 self
._remote
_device
= command_line_override(
112 self
._remote
_device
, args
.remote_device
, 'remote_device')
113 self
._remote
_device
_minimum
_os
= command_line_override(
114 self
._remote
_device
_minimum
_os
, args
.remote_device_minimum_os
,
115 'remote_device_minimum_os')
116 self
._remote
_device
_os
= command_line_override(
117 self
._remote
_device
_os
, args
.remote_device_os
, 'remote_device_os')
118 self
._remote
_device
_timeout
= command_line_override(
119 self
._remote
_device
_timeout
, args
.remote_device_timeout
,
120 'remote_device_timeout')
121 self
._results
_path
= command_line_override(
122 self
._results
_path
, args
.results_path
, 'results_path')
123 self
._runner
_package
= command_line_override(
124 self
._runner
_package
, args
.runner_package
, 'runner_package')
125 self
._runner
_type
= command_line_override(
126 self
._runner
_type
, args
.runner_type
, 'runner_type')
128 if args
.api_key_file
:
129 with
open(args
.api_key_file
) as api_key_file
:
130 temp_key
= api_key_file
.read().strip()
131 self
._api
_key
= command_line_override(
132 self
._api
_key
, temp_key
, 'api_key', print_value
=False)
133 self
._api
_key
= command_line_override(
134 self
._api
_key
, args
.api_key
, 'api_key', print_value
=False)
136 if args
.api_secret_file
:
137 with
open(args
.api_secret_file
) as api_secret_file
:
138 temp_secret
= api_secret_file
.read().strip()
139 self
._api
_secret
= command_line_override(
140 self
._api
_secret
, temp_secret
, 'api_secret', print_value
=False)
141 self
._api
_secret
= command_line_override(
142 self
._api
_secret
, args
.api_secret
, 'api_secret', print_value
=False)
144 if not self
._api
_address
:
145 error_func('Must set api address with --api-address'
146 ' or in --remote-device-file.')
147 if not self
._api
_key
:
148 error_func('Must set api key with --api-key, --api-key-file'
149 ' or in --remote-device-file')
150 if not self
._api
_port
:
151 error_func('Must set api port with --api-port'
152 ' or in --remote-device-file')
153 if not self
._api
_protocol
:
154 error_func('Must set api protocol with --api-protocol'
155 ' or in --remote-device-file. Example: http')
156 if not self
._api
_secret
:
157 error_func('Must set api secret with --api-secret, --api-secret-file'
158 ' or in --remote-device-file')
160 logging
.info('Api address: %s', self
._api
_address
)
161 logging
.info('Api port: %s', self
._api
_port
)
162 logging
.info('Api protocol: %s', self
._api
_protocol
)
163 logging
.info('Remote device: %s', self
._remote
_device
)
164 logging
.info('Remote device minimum OS: %s',
165 self
._remote
_device
_minimum
_os
)
166 logging
.info('Remote device OS: %s', self
._remote
_device
_os
)
167 logging
.info('Remote device OEM: %s', self
._device
_oem
)
168 logging
.info('Remote device type: %s', self
._device
_type
)
169 logging
.info('Remote device timout: %s', self
._remote
_device
_timeout
)
170 logging
.info('Results Path: %s', self
._results
_path
)
171 logging
.info('Runner package: %s', self
._runner
_package
)
172 logging
.info('Runner type: %s', self
._runner
_type
)
173 logging
.info('Timeouts: %s', self
._timeouts
)
175 if not args
.trigger
and not args
.collect
:
179 self
._trigger
= args
.trigger
180 self
._collect
= args
.collect
183 """Set up the test environment."""
184 os
.environ
['APPURIFY_API_PROTO'] = self
._api
_protocol
185 os
.environ
['APPURIFY_API_HOST'] = self
._api
_address
186 os
.environ
['APPURIFY_API_PORT'] = self
._api
_port
187 os
.environ
['APPURIFY_STATUS_BASE_URL'] = 'none'
188 self
._GetAccessToken
()
193 """Teardown the test environment."""
194 self
._RevokeAccessToken
()
197 """Set up the test run when used as a context manager."""
202 self
.__exit
__(*sys
.exc_info())
205 def __exit__(self
, exc_type
, exc_val
, exc_tb
):
206 """Tears down the test run when used as a context manager."""
209 def DumpTo(self
, persisted_data
):
211 self
._DEVICE
_KEY
: self
._device
,
213 persisted_data
[self
._ENV
_KEY
] = env_data
215 def LoadFrom(self
, persisted_data
):
216 env_data
= persisted_data
[self
._ENV
_KEY
]
217 self
._device
= env_data
[self
._DEVICE
_KEY
]
219 def _GetAccessToken(self
):
220 """Generates access token for remote device service."""
221 logging
.info('Generating remote service access token')
222 with appurify_sanitized
.SanitizeLogging(self
._verbose
_count
,
224 access_token_results
= appurify_sanitized
.api
.access_token_generate(
225 self
._api
_key
, self
._api
_secret
)
226 remote_device_helper
.TestHttpResponse(access_token_results
,
227 'Unable to generate access token.')
228 self
._access
_token
= access_token_results
.json()['response']['access_token']
230 def _RevokeAccessToken(self
):
231 """Destroys access token for remote device service."""
232 logging
.info('Revoking remote service access token')
233 with appurify_sanitized
.SanitizeLogging(self
._verbose
_count
,
235 revoke_token_results
= appurify_sanitized
.api
.access_token_revoke(
237 remote_device_helper
.TestHttpResponse(revoke_token_results
,
238 'Unable to revoke access token.')
240 def _SelectDevice(self
):
241 if self
._remote
_device
_timeout
:
243 timeout_retry
.Run(self
._FindDeviceWithTimeout
,
244 self
._remote
_device
_timeout
, self
._DEFAULT
_RETRIES
)
245 except reraiser_thread
.TimeoutError
:
246 self
._NoDeviceFound
()
248 if not self
._FindDevice
():
249 self
._NoDeviceFound
()
251 def _FindDevice(self
):
252 """Find which device to use."""
253 logging
.info('Finding device to run tests on.')
254 device_list
= self
._GetDeviceList
()
255 random
.shuffle(device_list
)
256 for device
in device_list
:
257 if device
['os_name'] != self
._device
_type
:
259 if self
._remote
_device
and device
['name'] not in self
._remote
_device
:
261 if (self
._remote
_device
_os
262 and device
['os_version'] not in self
._remote
_device
_os
):
264 if self
._device
_oem
and device
['brand'] not in self
._device
_oem
:
266 if (self
._remote
_device
_minimum
_os
267 and distutils
.version
.LooseVersion(device
['os_version'])
268 < distutils
.version
.LooseVersion(self
._remote
_device
_minimum
_os
)):
270 if device
['has_available_device']:
271 logging
.info('Found device: %s %s',
272 device
['name'], device
['os_version'])
273 self
._device
= device
277 def _FindDeviceWithTimeout(self
):
278 """Find which device to use with timeout."""
279 timeout_retry
.WaitFor(self
._FindDevice
, wait_period
=1)
281 def _PrintAvailableDevices(self
, device_list
):
282 def compare_devices(a
, b
):
283 for key
in ('os_version', 'name'):
284 c
= cmp(a
[key
], b
[key
])
289 logging
.critical('Available %s Devices:', self
._device
_type
)
293 'Device Name'.ljust(30),
294 'Available'.ljust(10),
297 devices
= (d
for d
in device_list
if d
['os_name'] == self
._device
_type
)
298 for d
in sorted(devices
, compare_devices
):
301 d
['os_version'].ljust(10),
303 str(d
['available_devices_count']).ljust(10),
304 str(d
['busy_devices_count']).ljust(10),
305 str(d
['all_devices_count']).ljust(10))
307 def _GetDeviceList(self
):
308 with appurify_sanitized
.SanitizeLogging(self
._verbose
_count
,
310 dev_list_res
= appurify_sanitized
.api
.devices_list(self
._access
_token
)
311 remote_device_helper
.TestHttpResponse(dev_list_res
,
312 'Unable to generate access token.')
313 return dev_list_res
.json()['response']
315 def _NoDeviceFound(self
):
316 self
._PrintAvailableDevices
(self
._GetDeviceList
())
317 raise remote_device_helper
.RemoteDeviceError(
318 'No device found.', is_infra_error
=True)
325 def device_type_id(self
):
326 return self
._device
['device_type_id']
329 def network_config(self
):
330 return self
._network
_config
333 def only_output_failures(self
): # pylint: disable=no-self-use
334 # TODO(jbudorick): Remove this once b/18981674 is fixed.
338 def results_path(self
):
339 return self
._results
_path
342 def runner_package(self
):
343 return self
._runner
_package
346 def runner_type(self
):
347 return self
._runner
_type
351 return self
._timeouts
355 return self
._access
_token
362 def verbose_count(self
):
363 return self
._verbose
_count
366 def device_type(self
):
367 return self
._device
_type