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 pylib
import constants
15 from pylib
.base
import environment
16 from pylib
.remote
.device
import appurify_sanitized
17 from pylib
.remote
.device
import remote_device_helper
18 from pylib
.utils
import timeout_retry
19 from pylib
.utils
import reraiser_thread
21 class RemoteDeviceEnvironment(environment
.Environment
):
22 """An environment for running on remote devices."""
25 _DEVICE_KEY
= 'device'
28 def __init__(self
, args
, error_func
):
32 args: Command line arguments.
33 error_func: error to show when using bad command line arguments.
35 super(RemoteDeviceEnvironment
, self
).__init
__()
36 self
._access
_token
= None
38 self
._device
_type
= args
.device_type
39 self
._verbose
_count
= args
.verbose_count
42 'installing': 60 * 10,
43 'in-progress': 60 * 30,
46 # Example config file:
48 # "remote_device": ["Galaxy S4", "Galaxy S3"],
49 # "remote_device_os": ["4.4.2", "4.4.4"],
50 # "remote_device_minimum_os": "4.4.2",
51 # "api_address": "www.example.com",
53 # "api_protocol": "http",
54 # "api_secret": "apisecret",
55 # "api_key": "apikey",
59 # "in-progress": 1800,
63 if args
.remote_device_file
:
64 with
open(args
.remote_device_file
) as device_file
:
65 device_json
= json
.load(device_file
)
69 self
._api
_address
= device_json
.get('api_address', None)
70 self
._api
_key
= device_json
.get('api_key', None)
71 self
._api
_port
= device_json
.get('api_port', None)
72 self
._api
_protocol
= device_json
.get('api_protocol', None)
73 self
._api
_secret
= device_json
.get('api_secret', None)
74 self
._device
_oem
= device_json
.get('device_oem', None)
75 self
._device
_type
= device_json
.get('device_type', 'Android')
76 self
._network
_config
= device_json
.get('network_config', None)
77 self
._remote
_device
= device_json
.get('remote_device', None)
78 self
._remote
_device
_minimum
_os
= device_json
.get(
79 'remote_device_minimum_os', None)
80 self
._remote
_device
_os
= device_json
.get('remote_device_os', None)
81 self
._remote
_device
_timeout
= device_json
.get(
82 'remote_device_timeout', None)
83 self
._results
_path
= device_json
.get('results_path', None)
84 self
._runner
_package
= device_json
.get('runner_package', None)
85 self
._runner
_type
= device_json
.get('runner_type', None)
86 self
._timeouts
.update(device_json
.get('timeouts', {}))
88 def command_line_override(
89 file_value
, cmd_line_value
, desc
, print_value
=True):
91 if file_value
and file_value
!= cmd_line_value
:
93 logging
.info('Overriding %s from %s to %s',
94 desc
, file_value
, cmd_line_value
)
96 logging
.info('overriding %s', desc
)
100 self
._api
_address
= command_line_override(
101 self
._api
_address
, args
.api_address
, 'api_address')
102 self
._api
_port
= command_line_override(
103 self
._api
_port
, args
.api_port
, 'api_port')
104 self
._api
_protocol
= command_line_override(
105 self
._api
_protocol
, args
.api_protocol
, 'api_protocol')
106 self
._device
_oem
= command_line_override(
107 self
._device
_oem
, args
.device_oem
, 'device_oem')
108 self
._device
_type
= command_line_override(
109 self
._device
_type
, args
.device_type
, 'device_type')
110 self
._network
_config
= command_line_override(
111 self
._network
_config
, args
.network_config
, 'network_config')
112 self
._remote
_device
= command_line_override(
113 self
._remote
_device
, args
.remote_device
, 'remote_device')
114 self
._remote
_device
_minimum
_os
= command_line_override(
115 self
._remote
_device
_minimum
_os
, args
.remote_device_minimum_os
,
116 'remote_device_minimum_os')
117 self
._remote
_device
_os
= command_line_override(
118 self
._remote
_device
_os
, args
.remote_device_os
, 'remote_device_os')
119 self
._remote
_device
_timeout
= command_line_override(
120 self
._remote
_device
_timeout
, args
.remote_device_timeout
,
121 'remote_device_timeout')
122 self
._results
_path
= command_line_override(
123 self
._results
_path
, args
.results_path
, 'results_path')
124 self
._runner
_package
= command_line_override(
125 self
._runner
_package
, args
.runner_package
, 'runner_package')
126 self
._runner
_type
= command_line_override(
127 self
._runner
_type
, args
.runner_type
, 'runner_type')
129 if args
.api_key_file
:
130 with
open(args
.api_key_file
) as api_key_file
:
131 temp_key
= api_key_file
.read().strip()
132 self
._api
_key
= command_line_override(
133 self
._api
_key
, temp_key
, 'api_key', print_value
=False)
134 self
._api
_key
= command_line_override(
135 self
._api
_key
, args
.api_key
, 'api_key', print_value
=False)
137 if args
.api_secret_file
:
138 with
open(args
.api_secret_file
) as api_secret_file
:
139 temp_secret
= api_secret_file
.read().strip()
140 self
._api
_secret
= command_line_override(
141 self
._api
_secret
, temp_secret
, 'api_secret', print_value
=False)
142 self
._api
_secret
= command_line_override(
143 self
._api
_secret
, args
.api_secret
, 'api_secret', print_value
=False)
145 if not self
._api
_address
:
146 error_func('Must set api address with --api-address'
147 ' or in --remote-device-file.')
148 if not self
._api
_key
:
149 error_func('Must set api key with --api-key, --api-key-file'
150 ' or in --remote-device-file')
151 if not self
._api
_port
:
152 error_func('Must set api port with --api-port'
153 ' or in --remote-device-file')
154 if not self
._api
_protocol
:
155 error_func('Must set api protocol with --api-protocol'
156 ' or in --remote-device-file. Example: http')
157 if not self
._api
_secret
:
158 error_func('Must set api secret with --api-secret, --api-secret-file'
159 ' or in --remote-device-file')
161 logging
.info('Api address: %s', self
._api
_address
)
162 logging
.info('Api port: %s', self
._api
_port
)
163 logging
.info('Api protocol: %s', self
._api
_protocol
)
164 logging
.info('Remote device: %s', self
._remote
_device
)
165 logging
.info('Remote device minimum OS: %s',
166 self
._remote
_device
_minimum
_os
)
167 logging
.info('Remote device OS: %s', self
._remote
_device
_os
)
168 logging
.info('Remote device OEM: %s', self
._device
_oem
)
169 logging
.info('Remote device type: %s', self
._device
_type
)
170 logging
.info('Remote device timout: %s', self
._remote
_device
_timeout
)
171 logging
.info('Results Path: %s', self
._results
_path
)
172 logging
.info('Runner package: %s', self
._runner
_package
)
173 logging
.info('Runner type: %s', self
._runner
_type
)
174 logging
.info('Timeouts: %s', self
._timeouts
)
176 if not args
.trigger
and not args
.collect
:
180 self
._trigger
= args
.trigger
181 self
._collect
= args
.collect
184 """Set up the test environment."""
185 os
.environ
['APPURIFY_API_PROTO'] = self
._api
_protocol
186 os
.environ
['APPURIFY_API_HOST'] = self
._api
_address
187 os
.environ
['APPURIFY_API_PORT'] = self
._api
_port
188 os
.environ
['APPURIFY_STATUS_BASE_URL'] = 'none'
189 self
._GetAccessToken
()
194 """Teardown the test environment."""
195 self
._RevokeAccessToken
()
198 """Set up the test run when used as a context manager."""
203 self
.__exit
__(*sys
.exc_info())
206 def __exit__(self
, exc_type
, exc_val
, exc_tb
):
207 """Tears down the test run when used as a context manager."""
210 def DumpTo(self
, persisted_data
):
212 self
._DEVICE
_KEY
: self
._device
,
214 persisted_data
[self
._ENV
_KEY
] = env_data
216 def LoadFrom(self
, persisted_data
):
217 env_data
= persisted_data
[self
._ENV
_KEY
]
218 self
._device
= env_data
[self
._DEVICE
_KEY
]
220 def _GetAccessToken(self
):
221 """Generates access token for remote device service."""
222 logging
.info('Generating remote service access token')
223 with appurify_sanitized
.SanitizeLogging(self
._verbose
_count
,
225 access_token_results
= appurify_sanitized
.api
.access_token_generate(
226 self
._api
_key
, self
._api
_secret
)
227 remote_device_helper
.TestHttpResponse(access_token_results
,
228 'Unable to generate access token.')
229 self
._access
_token
= access_token_results
.json()['response']['access_token']
231 def _RevokeAccessToken(self
):
232 """Destroys access token for remote device service."""
233 logging
.info('Revoking remote service access token')
234 with appurify_sanitized
.SanitizeLogging(self
._verbose
_count
,
236 revoke_token_results
= appurify_sanitized
.api
.access_token_revoke(
238 remote_device_helper
.TestHttpResponse(revoke_token_results
,
239 'Unable to revoke access token.')
241 def _SelectDevice(self
):
242 if self
._remote
_device
_timeout
:
244 timeout_retry
.Run(self
._FindDeviceWithTimeout
,
245 self
._remote
_device
_timeout
, self
._DEFAULT
_RETRIES
)
246 except reraiser_thread
.TimeoutError
:
247 self
._NoDeviceFound
()
249 if not self
._FindDevice
():
250 self
._NoDeviceFound
()
252 def _FindDevice(self
):
253 """Find which device to use."""
254 logging
.info('Finding device to run tests on.')
255 device_list
= self
._GetDeviceList
()
256 random
.shuffle(device_list
)
257 for device
in device_list
:
258 if device
['os_name'] != self
._device
_type
:
260 if self
._remote
_device
and device
['name'] not in self
._remote
_device
:
262 if (self
._remote
_device
_os
263 and device
['os_version'] not in self
._remote
_device
_os
):
265 if self
._device
_oem
and device
['brand'] not in self
._device
_oem
:
267 if (self
._remote
_device
_minimum
_os
268 and distutils
.version
.LooseVersion(device
['os_version'])
269 < distutils
.version
.LooseVersion(self
._remote
_device
_minimum
_os
)):
271 if device
['has_available_device']:
272 logging
.info('Found device: %s %s',
273 device
['name'], device
['os_version'])
274 self
._device
= device
278 def _FindDeviceWithTimeout(self
):
279 """Find which device to use with timeout."""
280 timeout_retry
.WaitFor(self
._FindDevice
, wait_period
=1)
282 def _PrintAvailableDevices(self
, device_list
):
283 def compare_devices(a
,b
):
284 for key
in ('os_version', 'name'):
285 c
= cmp(a
[key
], b
[key
])
290 logging
.critical('Available %s Devices:', self
._device
_type
)
294 'Device Name'.ljust(30),
295 'Available'.ljust(10),
298 devices
= (d
for d
in device_list
if d
['os_name'] == self
._device
_type
)
299 for d
in sorted(devices
, compare_devices
):
302 d
['os_version'].ljust(10),
304 str(d
['available_devices_count']).ljust(10),
305 str(d
['busy_devices_count']).ljust(10),
306 str(d
['all_devices_count']).ljust(10))
308 def _GetDeviceList(self
):
309 with appurify_sanitized
.SanitizeLogging(self
._verbose
_count
,
311 dev_list_res
= appurify_sanitized
.api
.devices_list(self
._access
_token
)
312 remote_device_helper
.TestHttpResponse(dev_list_res
,
313 'Unable to generate access token.')
314 return dev_list_res
.json()['response']
316 def _NoDeviceFound(self
):
317 self
._PrintAvailableDevices
(self
._GetDeviceList
())
318 raise remote_device_helper
.RemoteDeviceError(
319 'No device found.', is_infra_error
=True)
326 def device_type_id(self
):
327 return self
._device
['device_type_id']
330 def network_config(self
):
331 return self
._network
_config
334 def only_output_failures(self
):
335 # TODO(jbudorick): Remove this once b/18981674 is fixed.
339 def results_path(self
):
340 return self
._results
_path
343 def runner_package(self
):
344 return self
._runner
_package
347 def runner_type(self
):
348 return self
._runner
_type
352 return self
._timeouts
356 return self
._access
_token
363 def verbose_count(self
):
364 return self
._verbose
_count
367 def device_type(self
):
368 return self
._device
_type