blob: 51826fb6a50c4856db9f7a80e04bc416ed5850c0 [file] [log] [blame]
Sergei Trofimov4e6afe92015-10-09 09:30:04 +01001import os
2import re
3import time
4import logging
5import posixpath
6import subprocess
Anouk Van Laer0b7ab6a2017-05-17 17:13:33 +01007import tarfile
Sergei Trofimov4e6afe92015-10-09 09:30:04 +01008import tempfile
9import threading
10from collections import namedtuple
11
12from devlib.host import LocalConnection, PACKAGE_BIN_DIRECTORY
13from devlib.module import get_module
14from devlib.platform import Platform
15from devlib.exception import TargetError, TargetNotRespondingError, TimeoutError
16from devlib.utils.ssh import SshConnection
Valentin Schneider7c2fd872017-09-11 16:58:24 +010017from devlib.utils.android import AdbConnection, AndroidProperties, LogcatMonitor, adb_command, adb_disconnect
Sergei Trofimov4e6afe92015-10-09 09:30:04 +010018from devlib.utils.misc import memoized, isiterable, convert_new_lines, merge_lists
19from devlib.utils.misc import ABI_MAP, get_cpu_name, ranges_to_list, escape_double_quotes
20from devlib.utils.types import integer, boolean, bitmask, identifier, caseless_string
21
22
Sebastian Goscik040daab2016-02-23 10:27:45 +000023FSTAB_ENTRY_REGEX = re.compile(r'(\S+) on (.+) type (\S+) \((\S+)\)')
Sebastian Goscik1890db72016-02-15 14:43:30 +000024ANDROID_SCREEN_STATE_REGEX = re.compile('(?:mPowerState|mScreenOn|Display Power: state)=([0-9]+|true|false|ON|OFF)',
Sergei Trofimov4e6afe92015-10-09 09:30:04 +010025 re.IGNORECASE)
26ANDROID_SCREEN_RESOLUTION_REGEX = re.compile(r'mUnrestrictedScreen=\(\d+,\d+\)'
27 r'\s+(?P<width>\d+)x(?P<height>\d+)')
28DEFAULT_SHELL_PROMPT = re.compile(r'^.*(shell|root)@.*:/\S* [#$] ',
29 re.MULTILINE)
Patrick Bellasi9a8d5392017-02-17 15:28:07 +000030KVERSION_REGEX =re.compile(
Brendan Jackman66656932017-02-20 18:29:49 +000031 r'(?P<version>\d+)(\.(?P<major>\d+)(\.(?P<minor>\d+)(-rc(?P<rc>\d+))?)?)?(.*-g(?P<sha1>[0-9a-fA-F]{7,}))?'
Patrick Bellasi9a8d5392017-02-17 15:28:07 +000032)
Sergei Trofimov4e6afe92015-10-09 09:30:04 +010033
34
35class Target(object):
36
Sergei Trofimov4e6afe92015-10-09 09:30:04 +010037 path = None
38 os = None
39
40 default_modules = [
41 'hotplug',
42 'cpufreq',
43 'cpuidle',
44 'cgroups',
45 'hwmon',
46 ]
47
48 @property
49 def core_names(self):
50 return self.platform.core_names
51
52 @property
53 def core_clusters(self):
54 return self.platform.core_clusters
55
56 @property
57 def big_core(self):
58 return self.platform.big_core
59
60 @property
61 def little_core(self):
62 return self.platform.little_core
63
64 @property
65 def is_connected(self):
66 return self.conn is not None
67
68 @property
Sergei Trofimov4e6afe92015-10-09 09:30:04 +010069 def connected_as_root(self):
Patrick Bellasi9ce57c02017-02-16 13:11:02 +000070 if self._connected_as_root is None:
71 result = self.execute('id')
72 self._connected_as_root = 'uid=0(' in result
73 return self._connected_as_root
Sergei Trofimov4e6afe92015-10-09 09:30:04 +010074
75 @property
76 @memoized
77 def is_rooted(self):
78 if self.connected_as_root:
79 return True
80 try:
81 self.execute('ls /', timeout=2, as_root=True)
82 return True
83 except (TargetError, TimeoutError):
84 return False
85
86 @property
87 @memoized
Javi Merino16d87c62016-06-23 14:55:19 +010088 def needs_su(self):
89 return not self.connected_as_root and self.is_rooted
90
91 @property
92 @memoized
Sergei Trofimov4e6afe92015-10-09 09:30:04 +010093 def kernel_version(self):
Sebastian Goscikbdbf4742016-02-24 14:26:18 +000094 return KernelVersion(self.execute('{} uname -r -v'.format(self.busybox)).strip())
Sergei Trofimov4e6afe92015-10-09 09:30:04 +010095
96 @property
97 def os_version(self): # pylint: disable=no-self-use
98 return {}
99
100 @property
101 def abi(self): # pylint: disable=no-self-use
102 return None
103
104 @property
Marc Bonnici98fb2e22017-07-14 17:41:16 +0100105 def supported_abi(self):
106 return [self.abi]
107
108 @property
Sergei Trofimov4e6afe92015-10-09 09:30:04 +0100109 @memoized
110 def cpuinfo(self):
111 return Cpuinfo(self.execute('cat /proc/cpuinfo'))
112
113 @property
114 @memoized
115 def number_of_cpus(self):
116 num_cpus = 0
117 corere = re.compile(r'^\s*cpu\d+\s*$')
118 output = self.execute('ls /sys/devices/system/cpu')
119 for entry in output.split():
120 if corere.match(entry):
121 num_cpus += 1
122 return num_cpus
123
124 @property
125 @memoized
126 def config(self):
127 try:
128 return KernelConfig(self.execute('zcat /proc/config.gz'))
129 except TargetError:
130 for path in ['/boot/config', '/boot/config-$(uname -r)']:
131 try:
132 return KernelConfig(self.execute('cat {}'.format(path)))
133 except TargetError:
134 pass
135 return KernelConfig('')
136
137 @property
138 @memoized
139 def user(self):
140 return self.getenv('USER')
141
142 @property
143 def conn(self):
144 if self._connections:
145 tid = id(threading.current_thread())
146 if tid not in self._connections:
147 self._connections[tid] = self.get_connection()
148 return self._connections[tid]
149 else:
150 return None
151
152 def __init__(self,
153 connection_settings=None,
154 platform=None,
155 working_directory=None,
156 executables_directory=None,
157 connect=True,
158 modules=None,
159 load_default_modules=True,
160 shell_prompt=DEFAULT_SHELL_PROMPT,
Sergei Trofimovbeaf8d42016-12-07 15:11:32 +0000161 conn_cls=None,
Sergei Trofimov4e6afe92015-10-09 09:30:04 +0100162 ):
Patrick Bellasi9ce57c02017-02-16 13:11:02 +0000163 self._connected_as_root = None
Sergei Trofimov4e6afe92015-10-09 09:30:04 +0100164 self.connection_settings = connection_settings or {}
Anouk Van Laer21f40032017-01-31 13:11:03 +0000165 # Set self.platform: either it's given directly (by platform argument)
166 # or it's given in the connection_settings argument
167 # If neither, create default Platform()
168 if platform is None:
169 self.platform = self.connection_settings.get('platform', Platform())
170 else:
171 self.platform = platform
172 # Check if the user hasn't given two different platforms
173 if 'platform' in self.connection_settings:
174 if connection_settings['platform'] is not platform:
175 raise TargetError('Platform specified in connection_settings '
176 '({}) differs from that directly passed '
177 '({})!)'
178 .format(connection_settings['platform'],
179 self.platform))
180 self.connection_settings['platform'] = self.platform
Sergei Trofimov4e6afe92015-10-09 09:30:04 +0100181 self.working_directory = working_directory
182 self.executables_directory = executables_directory
183 self.modules = modules or []
184 self.load_default_modules = load_default_modules
185 self.shell_prompt = shell_prompt
Sergei Trofimovbeaf8d42016-12-07 15:11:32 +0000186 self.conn_cls = conn_cls
Sergei Trofimov4e6afe92015-10-09 09:30:04 +0100187 self.logger = logging.getLogger(self.__class__.__name__)
188 self._installed_binaries = {}
189 self._installed_modules = {}
190 self._cache = {}
191 self._connections = {}
192 self.busybox = None
193
194 if load_default_modules:
195 module_lists = [self.default_modules]
196 else:
197 module_lists = []
198 module_lists += [self.modules, self.platform.modules]
199 self.modules = merge_lists(*module_lists, duplicates='first')
200 self._update_modules('early')
201 if connect:
202 self.connect()
203
204 # connection and initialization
205
206 def connect(self, timeout=None):
207 self.platform.init_target_connection(self)
208 tid = id(threading.current_thread())
209 self._connections[tid] = self.get_connection(timeout=timeout)
Sergei Trofimov961f9572015-11-18 17:32:26 +0000210 self._resolve_paths()
Sergei Trofimov4e6afe92015-10-09 09:30:04 +0100211 self.busybox = self.get_installed('busybox')
Sergei Trofimov4e6afe92015-10-09 09:30:04 +0100212 self.platform.update_from_target(self)
Patrick Bellasib83e5182015-10-12 12:37:11 +0100213 self._update_modules('connected')
Sergei Trofimov4e6afe92015-10-09 09:30:04 +0100214 if self.platform.big_core and self.load_default_modules:
215 self._install_module(get_module('bl'))
216
217 def disconnect(self):
218 for conn in self._connections.itervalues():
219 conn.close()
220 self._connections = {}
221
222 def get_connection(self, timeout=None):
Sergei Trofimovbeaf8d42016-12-07 15:11:32 +0000223 if self.conn_cls == None:
224 raise ValueError('Connection class not specified on Target creation.')
Sergei Trofimov4e6afe92015-10-09 09:30:04 +0100225 return self.conn_cls(timeout=timeout, **self.connection_settings) # pylint: disable=not-callable
226
227 def setup(self, executables=None):
228 self.execute('mkdir -p {}'.format(self.working_directory))
229 self.execute('mkdir -p {}'.format(self.executables_directory))
230 self.busybox = self.install(os.path.join(PACKAGE_BIN_DIRECTORY, self.abi, 'busybox'))
Patrick Bellasif2eac512015-11-27 16:35:57 +0000231
232 # Setup shutils script for the target
233 shutils_ifile = os.path.join(PACKAGE_BIN_DIRECTORY, 'scripts', 'shutils.in')
234 shutils_ofile = os.path.join(PACKAGE_BIN_DIRECTORY, 'scripts', 'shutils')
235 shell_path = '/bin/sh'
236 if self.os == 'android':
237 shell_path = '/system/bin/sh'
238 with open(shutils_ifile) as fh:
239 lines = fh.readlines()
240 with open(shutils_ofile, 'w') as ofile:
241 for line in lines:
242 line = line.replace("__DEVLIB_SHELL__", shell_path)
243 line = line.replace("__DEVLIB_BUSYBOX__", self.busybox)
244 ofile.write(line)
245 self.shutils = self.install(os.path.join(PACKAGE_BIN_DIRECTORY, 'scripts', 'shutils'))
246
Sergei Trofimov4e6afe92015-10-09 09:30:04 +0100247 for host_exe in (executables or []): # pylint: disable=superfluous-parens
248 self.install(host_exe)
249
Anouk Van Laer21f40032017-01-31 13:11:03 +0000250 # Check for platform dependent setup procedures
251 self.platform.setup(self)
252
Patrick Bellasic4e46b72016-05-13 18:15:51 +0100253 # Initialize modules which requires Buxybox (e.g. shutil dependent tasks)
254 self._update_modules('setup')
255
Sergei Trofimov4e6afe92015-10-09 09:30:04 +0100256 def reboot(self, hard=False, connect=True, timeout=180):
257 if hard:
258 if not self.has('hard_reset'):
259 raise TargetError('Hard reset not supported for this target.')
260 self.hard_reset() # pylint: disable=no-member
261 else:
262 if not self.is_connected:
263 message = 'Cannot reboot target becuase it is disconnected. ' +\
264 'Either connect() first, or specify hard=True ' +\
265 '(in which case, a hard_reset module must be installed)'
266 raise TargetError(message)
267 self.reset()
Sergei Trofimovebe3a8a2016-02-25 10:28:45 +0000268 # Wait a fixed delay before starting polling to give the target time to
269 # shut down, otherwise, might create the connection while it's still shutting
270 # down resulting in subsequenct connection failing.
271 self.logger.debug('Waiting for target to power down...')
272 reset_delay = 20
273 time.sleep(reset_delay)
274 timeout = max(timeout - reset_delay, 10)
Sergei Trofimov4e6afe92015-10-09 09:30:04 +0100275 if self.has('boot'):
276 self.boot() # pylint: disable=no-member
Marc Bonnici0687dac2017-02-28 13:48:10 +0000277 self._connected_as_root = None
Sergei Trofimov4e6afe92015-10-09 09:30:04 +0100278 if connect:
279 self.connect(timeout=timeout)
280
281 # file transfer
282
283 def push(self, source, dest, timeout=None):
284 return self.conn.push(source, dest, timeout=timeout)
285
286 def pull(self, source, dest, timeout=None):
287 return self.conn.pull(source, dest, timeout=timeout)
288
Anouk Van Laer0b7ab6a2017-05-17 17:13:33 +0100289 def get_directory(self, source_dir, dest):
290 """ Pull a directory from the device, after compressing dir """
291 # Create all file names
292 tar_file_name = source_dir.lstrip(self.path.sep).replace(self.path.sep, '.')
293 # Host location of dir
294 outdir = os.path.join(dest, tar_file_name)
295 # Host location of archive
296 tar_file_name = '{}.tar'.format(tar_file_name)
297 tempfile = os.path.join(dest, tar_file_name)
298
299 # Does the folder exist?
300 self.execute('ls -la {}'.format(source_dir))
301 # Try compressing the folder
302 try:
303 self.execute('{} tar -cvf {} {}'.format(self.busybox, tar_file_name,
304 source_dir))
305 except TargetError:
306 self.logger.debug('Failed to run tar command on target! ' \
307 'Not pulling directory {}'.format(source_dir))
308 # Pull the file
309 os.mkdir(outdir)
310 self.pull(tar_file_name, tempfile )
311 # Decompress
312 f = tarfile.open(tempfile, 'r')
313 f.extractall(outdir)
314 os.remove(tempfile)
315
Sergei Trofimov4e6afe92015-10-09 09:30:04 +0100316 # execution
317
318 def execute(self, command, timeout=None, check_exit_code=True, as_root=False):
setrofimb7386552017-05-23 17:39:12 +0100319 return self.conn.execute(command, timeout, check_exit_code, as_root)
Sergei Trofimov4e6afe92015-10-09 09:30:04 +0100320
321 def background(self, command, stdout=subprocess.PIPE, stderr=subprocess.PIPE, as_root=False):
322 return self.conn.background(command, stdout, stderr, as_root)
323
324 def invoke(self, binary, args=None, in_directory=None, on_cpus=None,
325 as_root=False, timeout=30):
326 """
327 Executes the specified binary under the specified conditions.
328
329 :binary: binary to execute. Must be present and executable on the device.
330 :args: arguments to be passed to the binary. The can be either a list or
331 a string.
332 :in_directory: execute the binary in the specified directory. This must
333 be an absolute path.
334 :on_cpus: taskset the binary to these CPUs. This may be a single ``int`` (in which
335 case, it will be interpreted as the mask), a list of ``ints``, in which
336 case this will be interpreted as the list of cpus, or string, which
337 will be interpreted as a comma-separated list of cpu ranges, e.g.
338 ``"0,4-7"``.
339 :as_root: Specify whether the command should be run as root
340 :timeout: If the invocation does not terminate within this number of seconds,
341 a ``TimeoutError`` exception will be raised. Set to ``None`` if the
342 invocation should not timeout.
343
Brendan Jackman27f545f2016-11-15 16:58:57 +0000344 :returns: output of command.
Sergei Trofimov4e6afe92015-10-09 09:30:04 +0100345 """
346 command = binary
347 if args:
348 if isiterable(args):
349 args = ' '.join(args)
350 command = '{} {}'.format(command, args)
351 if on_cpus:
352 on_cpus = bitmask(on_cpus)
353 command = '{} taskset 0x{:x} {}'.format(self.busybox, on_cpus, command)
354 if in_directory:
355 command = 'cd {} && {}'.format(in_directory, command)
356 return self.execute(command, as_root=as_root, timeout=timeout)
357
Valentin Schneider92b0c252017-07-06 16:01:15 +0100358 def background_invoke(self, binary, args=None, in_directory=None,
359 on_cpus=None, as_root=False):
360 """
361 Executes the specified binary as a background task under the
362 specified conditions.
363
364 :binary: binary to execute. Must be present and executable on the device.
365 :args: arguments to be passed to the binary. The can be either a list or
366 a string.
367 :in_directory: execute the binary in the specified directory. This must
368 be an absolute path.
369 :on_cpus: taskset the binary to these CPUs. This may be a single ``int`` (in which
370 case, it will be interpreted as the mask), a list of ``ints``, in which
371 case this will be interpreted as the list of cpus, or string, which
372 will be interpreted as a comma-separated list of cpu ranges, e.g.
373 ``"0,4-7"``.
374 :as_root: Specify whether the command should be run as root
375
376 :returns: the subprocess instance handling that command
377 """
378 command = binary
379 if args:
380 if isiterable(args):
381 args = ' '.join(args)
382 command = '{} {}'.format(command, args)
383 if on_cpus:
384 on_cpus = bitmask(on_cpus)
385 command = '{} taskset 0x{:x} {}'.format(self.busybox, on_cpus, command)
386 if in_directory:
387 command = 'cd {} && {}'.format(in_directory, command)
388 return self.background(command, as_root=as_root)
389
Sergei Trofimov4e6afe92015-10-09 09:30:04 +0100390 def kick_off(self, command, as_root=False):
391 raise NotImplementedError()
392
393 # sysfs interaction
394
395 def read_value(self, path, kind=None):
Javi Merino16d87c62016-06-23 14:55:19 +0100396 output = self.execute('cat \'{}\''.format(path), as_root=self.needs_su).strip() # pylint: disable=E1103
Sergei Trofimov4e6afe92015-10-09 09:30:04 +0100397 if kind:
398 return kind(output)
399 else:
400 return output
401
402 def read_int(self, path):
403 return self.read_value(path, kind=integer)
404
405 def read_bool(self, path):
406 return self.read_value(path, kind=boolean)
407
408 def write_value(self, path, value, verify=True):
409 value = str(value)
410 self.execute('echo {} > \'{}\''.format(value, path), check_exit_code=False, as_root=True)
411 if verify:
412 output = self.read_value(path)
413 if not output == value:
414 message = 'Could not set the value of {} to "{}" (read "{}")'.format(path, value, output)
415 raise TargetError(message)
416
417 def reset(self):
418 try:
Javi Merino16d87c62016-06-23 14:55:19 +0100419 self.execute('reboot', as_root=self.needs_su, timeout=2)
Sergei Trofimov4e6afe92015-10-09 09:30:04 +0100420 except (TargetError, TimeoutError, subprocess.CalledProcessError):
421 # on some targets "reboot" doesn't return gracefully
422 pass
Marc Bonnici0687dac2017-02-28 13:48:10 +0000423 self._connected_as_root = None
Sergei Trofimov4e6afe92015-10-09 09:30:04 +0100424
425 def check_responsive(self):
426 try:
427 self.conn.execute('ls /', timeout=5)
428 except (TimeoutError, subprocess.CalledProcessError):
429 raise TargetNotRespondingError(self.conn.name)
430
431 # process management
432
433 def kill(self, pid, signal=None, as_root=False):
434 signal_string = '-s {}'.format(signal) if signal else ''
435 self.execute('kill {} {}'.format(signal_string, pid), as_root=as_root)
436
437 def killall(self, process_name, signal=None, as_root=False):
438 for pid in self.get_pids_of(process_name):
Sergei Trofimov6351a3b2017-01-30 11:14:36 +0000439 try:
440 self.kill(pid, signal=signal, as_root=as_root)
441 except TargetError:
442 pass
Sergei Trofimov4e6afe92015-10-09 09:30:04 +0100443
444 def get_pids_of(self, process_name):
445 raise NotImplementedError()
446
447 def ps(self, **kwargs):
448 raise NotImplementedError()
449
450 # files
451
452 def file_exists(self, filepath):
453 command = 'if [ -e \'{}\' ]; then echo 1; else echo 0; fi'
Brendan Jackmanc1b51522016-11-23 13:44:00 +0000454 output = self.execute(command.format(filepath), as_root=self.is_rooted)
455 return boolean(output.strip())
Sergei Trofimov4e6afe92015-10-09 09:30:04 +0100456
Sebastian Goscik33603c62016-02-15 15:07:19 +0000457 def directory_exists(self, filepath):
458 output = self.execute('if [ -d \'{}\' ]; then echo 1; else echo 0; fi'.format(filepath))
459 # output from ssh my contain part of the expression in the buffer,
460 # split out everything except the last word.
461 return boolean(output.split()[-1]) # pylint: disable=maybe-no-member
462
Sergei Trofimov4e6afe92015-10-09 09:30:04 +0100463 def list_file_systems(self):
464 output = self.execute('mount')
465 fstab = []
466 for line in output.split('\n'):
467 line = line.strip()
468 if not line:
469 continue
470 match = FSTAB_ENTRY_REGEX.search(line)
471 if match:
472 fstab.append(FstabEntry(match.group(1), match.group(2),
473 match.group(3), match.group(4),
474 None, None))
475 else: # assume pre-M Android
476 fstab.append(FstabEntry(*line.split()))
477 return fstab
478
479 def list_directory(self, path, as_root=False):
480 raise NotImplementedError()
481
482 def get_workpath(self, name):
483 return self.path.join(self.working_directory, name)
484
485 def tempfile(self, prefix='', suffix=''):
486 names = tempfile._get_candidate_names() # pylint: disable=W0212
487 for _ in xrange(tempfile.TMP_MAX):
488 name = names.next()
489 path = self.get_workpath(prefix + name + suffix)
490 if not self.file_exists(path):
491 return path
492 raise IOError('No usable temporary filename found')
493
494 def remove(self, path, as_root=False):
495 self.execute('rm -rf {}'.format(path), as_root=as_root)
496
497 # misc
498 def core_cpus(self, core):
499 return [i for i, c in enumerate(self.core_names) if c == core]
500
501 def list_online_cpus(self, core=None):
502 path = self.path.join('/sys/devices/system/cpu/online')
503 output = self.read_value(path)
504 all_online = ranges_to_list(output)
505 if core:
506 cpus = self.core_cpus(core)
507 if not cpus:
508 raise ValueError(core)
509 return [o for o in all_online if o in cpus]
510 else:
511 return all_online
512
513 def list_offline_cpus(self):
514 online = self.list_online_cpus()
515 return [c for c in xrange(self.number_of_cpus)
516 if c not in online]
517
518 def getenv(self, variable):
519 return self.execute('echo ${}'.format(variable)).rstrip('\r\n')
520
521 def capture_screen(self, filepath):
522 raise NotImplementedError()
523
524 def install(self, filepath, timeout=None, with_name=None):
525 raise NotImplementedError()
526
527 def uninstall(self, name):
528 raise NotImplementedError()
529
Sebastian Goscik84151f92016-02-15 15:09:27 +0000530 def get_installed(self, name, search_system_binaries=True):
531 # Check user installed binaries first
Sergei Trofimovb5324532015-11-18 18:07:47 +0000532 if self.file_exists(self.executables_directory):
533 if name in self.list_directory(self.executables_directory):
534 return self.path.join(self.executables_directory, name)
Sebastian Goscik84151f92016-02-15 15:09:27 +0000535 # Fall back to binaries in PATH
536 if search_system_binaries:
537 for path in self.getenv('PATH').split(self.path.pathsep):
538 try:
539 if name in self.list_directory(path):
540 return self.path.join(path, name)
541 except TargetError:
542 pass # directory does not exist or no executable premssions
Sergei Trofimov4e6afe92015-10-09 09:30:04 +0100543
544 which = get_installed
545
Sebastian Goscikbe8f9722016-02-15 15:11:38 +0000546 def install_if_needed(self, host_path, search_system_binaries=True):
547
548 binary_path = self.get_installed(os.path.split(host_path)[1],
549 search_system_binaries=search_system_binaries)
550 if not binary_path:
551 binary_path = self.install(host_path)
552 return binary_path
553
Sergei Trofimov4e6afe92015-10-09 09:30:04 +0100554 def is_installed(self, name):
555 return bool(self.get_installed(name))
556
557 def bin(self, name):
558 return self._installed_binaries.get(name, name)
559
560 def has(self, modname):
561 return hasattr(self, identifier(modname))
562
Sergei Trofimov5a81fe92016-01-27 16:34:26 +0000563 def lsmod(self):
564 lines = self.execute('lsmod').splitlines()
565 entries = []
566 for line in lines[1:]: # first line is the header
567 if not line.strip():
568 continue
569 parts = line.split()
570 name = parts[0]
571 size = int(parts[1])
572 use_count = int(parts[2])
573 if len(parts) > 3:
Sergei Trofimov10a80d22016-01-27 17:02:59 +0000574 used_by = ''.join(parts[3:]).split(',')
Sergei Trofimov5a81fe92016-01-27 16:34:26 +0000575 else:
576 used_by = []
577 entries.append(LsmodEntry(name, size, use_count, used_by))
578 return entries
579
Sergei Trofimov10a80d22016-01-27 17:02:59 +0000580 def insmod(self, path):
581 target_path = self.get_workpath(os.path.basename(path))
582 self.push(path, target_path)
583 self.execute('insmod {}'.format(target_path), as_root=True)
584
Sergei Trofimovbaaa67b2016-07-14 11:00:24 +0100585
586 def extract(self, path, dest=None):
587 """
588 Extact the specified on-target file. The extraction method to be used
589 (unzip, gunzip, bunzip2, or tar) will be based on the file's extension.
590 If ``dest`` is specified, it must be an existing directory on target;
591 the extracted contents will be placed there.
592
593 Note that, depending on the archive file format (and therfore the
594 extraction method used), the original archive file may or may not exist
595 after the extraction.
596
597 The return value is the path to the extracted contents. In case of
598 gunzip and bunzip2, this will be path to the extracted file; for tar
599 and uzip, this will be the directory with the extracted file(s)
600 (``dest`` if it was specified otherwise, the directory that cotained
601 the archive).
602
603 """
604 for ending in ['.tar.gz', '.tar.bz', '.tar.bz2',
605 '.tgz', '.tbz', '.tbz2']:
606 if path.endswith(ending):
607 return self._extract_archive(path, 'tar xf {} -C {}', dest)
608
609 ext = self.path.splitext(path)[1]
610 if ext in ['.bz', '.bz2']:
611 return self._extract_file(path, 'bunzip2 -f {}', dest)
612 elif ext == '.gz':
613 return self._extract_file(path, 'gunzip -f {}', dest)
614 elif ext == '.zip':
615 return self._extract_archive(path, 'unzip {} -d {}', dest)
616 else:
617 raise ValueError('Unknown compression format: {}'.format(ext))
618
Sergei Trofimov69a83d42017-05-12 11:54:31 +0100619 def sleep(self, duration):
620 timeout = duration + 10
621 self.execute('sleep {}'.format(duration), timeout=timeout)
622
Sergei Trofimovbaaa67b2016-07-14 11:00:24 +0100623 # internal methods
624
Sergei Trofimov8b2ac8d2017-05-12 11:48:19 +0100625 def _execute_util(self, command, timeout=None, check_exit_code=True, as_root=False):
626 command = '{} {}'.format(self.shutils, command)
627 return self.conn.execute(command, timeout, check_exit_code, as_root)
628
Sergei Trofimovbaaa67b2016-07-14 11:00:24 +0100629 def _extract_archive(self, path, cmd, dest=None):
630 cmd = '{} ' + cmd # busybox
631 if dest:
632 extracted = dest
633 else:
634 extracted = self.path.dirname(path)
635 cmdtext = cmd.format(self.busybox, path, extracted)
636 self.execute(cmdtext)
637 return extracted
638
639 def _extract_file(self, path, cmd, dest=None):
640 cmd = '{} ' + cmd # busybox
641 cmdtext = cmd.format(self.busybox, path)
642 self.execute(cmdtext)
643 extracted = self.path.splitext(path)[0]
644 if dest:
645 self.execute('mv -f {} {}'.format(extracted, dest))
646 if dest.endswith('/'):
647 extracted = self.path.join(dest, self.path.basename(extracted))
648 else:
649 extracted = dest
650 return extracted
651
Sergei Trofimov4e6afe92015-10-09 09:30:04 +0100652 def _update_modules(self, stage):
653 for mod in self.modules:
654 if isinstance(mod, dict):
655 mod, params = mod.items()[0]
656 else:
657 params = {}
658 mod = get_module(mod)
659 if not mod.stage == stage:
660 continue
661 if mod.probe(self):
662 self._install_module(mod, **params)
663 else:
Javi Merinod0c71fb2016-01-18 12:27:48 +0000664 msg = 'Module {} is not supported by the target'.format(mod.name)
665 if self.load_default_modules:
666 self.logger.debug(msg)
667 else:
668 self.logger.warning(msg)
Sergei Trofimov4e6afe92015-10-09 09:30:04 +0100669
670 def _install_module(self, mod, **params):
671 if mod.name not in self._installed_modules:
672 self.logger.debug('Installing module {}'.format(mod.name))
673 mod.install(self, **params)
674 self._installed_modules[mod.name] = mod
675 else:
676 self.logger.debug('Module {} is already installed.'.format(mod.name))
677
Sergei Trofimov961f9572015-11-18 17:32:26 +0000678 def _resolve_paths(self):
679 raise NotImplementedError()
680
Sergei Trofimov4e6afe92015-10-09 09:30:04 +0100681
682class LinuxTarget(Target):
683
Sergei Trofimov4e6afe92015-10-09 09:30:04 +0100684 path = posixpath
685 os = 'linux'
686
687 @property
688 @memoized
689 def abi(self):
Sebastian Goscik5880f6e2016-06-16 13:31:53 +0100690 value = self.execute('uname -m').strip()
Sergei Trofimov4e6afe92015-10-09 09:30:04 +0100691 for abi, architectures in ABI_MAP.iteritems():
692 if value in architectures:
693 result = abi
694 break
695 else:
696 result = value
697 return result
698
699 @property
700 @memoized
701 def os_version(self):
702 os_version = {}
703 try:
704 command = 'ls /etc/*-release /etc*-version /etc/*_release /etc/*_version 2>/dev/null'
705 version_files = self.execute(command, check_exit_code=False).strip().split()
706 for vf in version_files:
707 name = self.path.basename(vf)
708 output = self.read_value(vf)
709 os_version[name] = output.strip().replace('\n', ' ')
710 except TargetError:
711 raise
712 return os_version
713
Sebastian Goscikf5b7c822016-02-23 17:10:01 +0000714 @property
715 @memoized
716 # There is currently no better way to do this cross platform.
717 # ARM does not have dmidecode
718 def model(self):
719 if self.file_exists("/proc/device-tree/model"):
720 raw_model = self.execute("cat /proc/device-tree/model")
721 return '_'.join(raw_model.split()[:2])
722 return None
723
Sergei Trofimovbeaf8d42016-12-07 15:11:32 +0000724 def __init__(self,
725 connection_settings=None,
726 platform=None,
727 working_directory=None,
728 executables_directory=None,
729 connect=True,
730 modules=None,
731 load_default_modules=True,
732 shell_prompt=DEFAULT_SHELL_PROMPT,
733 conn_cls=SshConnection,
734 ):
735 super(LinuxTarget, self).__init__(connection_settings=connection_settings,
736 platform=platform,
737 working_directory=working_directory,
738 executables_directory=executables_directory,
739 connect=connect,
740 modules=modules,
741 load_default_modules=load_default_modules,
742 shell_prompt=shell_prompt,
743 conn_cls=conn_cls)
744
Sergei Trofimov4e6afe92015-10-09 09:30:04 +0100745 def connect(self, timeout=None):
746 super(LinuxTarget, self).connect(timeout=timeout)
Sergei Trofimov4e6afe92015-10-09 09:30:04 +0100747
748 def kick_off(self, command, as_root=False):
749 command = 'sh -c "{}" 1>/dev/null 2>/dev/null &'.format(escape_double_quotes(command))
750 return self.conn.execute(command, as_root=as_root)
751
752 def get_pids_of(self, process_name):
753 """Returns a list of PIDs of all processes with the specified name."""
754 # result should be a column of PIDs with the first row as "PID" header
755 result = self.execute('ps -C {} -o pid'.format(process_name), # NOQA
756 check_exit_code=False).strip().split()
757 if len(result) >= 2: # at least one row besides the header
758 return map(int, result[1:])
759 else:
760 return []
761
762 def ps(self, **kwargs):
763 command = 'ps -eo user,pid,ppid,vsize,rss,wchan,pcpu,state,fname'
764 lines = iter(convert_new_lines(self.execute(command)).split('\n'))
765 lines.next() # header
766
767 result = []
768 for line in lines:
769 parts = re.split(r'\s+', line, maxsplit=8)
770 if parts and parts != ['']:
771 result.append(PsEntry(*(parts[0:1] + map(int, parts[1:5]) + parts[5:])))
772
773 if not kwargs:
774 return result
775 else:
776 filtered_result = []
777 for entry in result:
778 if all(getattr(entry, k) == v for k, v in kwargs.iteritems()):
779 filtered_result.append(entry)
780 return filtered_result
781
782 def list_directory(self, path, as_root=False):
783 contents = self.execute('ls -1 {}'.format(path), as_root=as_root)
784 return [x.strip() for x in contents.split('\n') if x.strip()]
785
786 def install(self, filepath, timeout=None, with_name=None): # pylint: disable=W0221
787 destpath = self.path.join(self.executables_directory,
788 with_name and with_name or self.path.basename(filepath))
789 self.push(filepath, destpath)
790 self.execute('chmod a+x {}'.format(destpath), timeout=timeout)
791 self._installed_binaries[self.path.basename(destpath)] = destpath
792 return destpath
793
794 def uninstall(self, name):
795 path = self.path.join(self.executables_directory, name)
796 self.remove(path)
797
798 def capture_screen(self, filepath):
799 if not self.is_installed('scrot'):
800 self.logger.debug('Could not take screenshot as scrot is not installed.')
801 return
802 try:
803
804 tmpfile = self.tempfile()
805 self.execute('DISPLAY=:0.0 scrot {}'.format(tmpfile))
806 self.pull(tmpfile, filepath)
807 self.remove(tmpfile)
808 except TargetError as e:
809 if "Can't open X dispay." not in e.message:
810 raise e
811 message = e.message.split('OUTPUT:', 1)[1].strip() # pylint: disable=no-member
812 self.logger.debug('Could not take screenshot: {}'.format(message))
813
Sergei Trofimov961f9572015-11-18 17:32:26 +0000814 def _resolve_paths(self):
815 if self.working_directory is None:
816 if self.connected_as_root:
817 self.working_directory = '/root/devlib-target'
818 else:
819 self.working_directory = '/home/{}/devlib-target'.format(self.user)
820 if self.executables_directory is None:
821 self.executables_directory = self.path.join(self.working_directory, 'bin')
822
Sergei Trofimov4e6afe92015-10-09 09:30:04 +0100823
824class AndroidTarget(Target):
825
Sergei Trofimov4e6afe92015-10-09 09:30:04 +0100826 path = posixpath
827 os = 'android'
Chris Redpath2a4eafa2016-09-27 12:08:33 +0100828 ls_command = ''
Sergei Trofimov4e6afe92015-10-09 09:30:04 +0100829
830 @property
831 @memoized
832 def abi(self):
833 return self.getprop()['ro.product.cpu.abi'].split('-')[0]
834
835 @property
836 @memoized
Marc Bonnici98fb2e22017-07-14 17:41:16 +0100837 def supported_abi(self):
838 props = self.getprop()
839 result = [props['ro.product.cpu.abi']]
840 if 'ro.product.cpu.abi2' in props:
841 result.append(props['ro.product.cpu.abi2'])
842 if 'ro.product.cpu.abilist' in props:
843 for abi in props['ro.product.cpu.abilist'].split(','):
844 if abi not in result:
845 result.append(abi)
846
847 mapped_result = []
848 for supported_abi in result:
849 for abi, architectures in ABI_MAP.iteritems():
850 found = False
851 if supported_abi in architectures and abi not in mapped_result:
852 mapped_result.append(abi)
853 found = True
854 break
855 if not found and supported_abi not in mapped_result:
856 mapped_result.append(supported_abi)
857 return mapped_result
858
859 @property
860 @memoized
Sergei Trofimov4e6afe92015-10-09 09:30:04 +0100861 def os_version(self):
862 os_version = {}
863 for k, v in self.getprop().iteritems():
864 if k.startswith('ro.build.version'):
865 part = k.split('.')[-1]
866 os_version[part] = v
867 return os_version
868
869 @property
870 def adb_name(self):
871 return self.conn.device
872
873 @property
Sebastian Goscikf5b7c822016-02-23 17:10:01 +0000874 @memoized
Sebastian Goscikcafeb812016-02-15 15:17:32 +0000875 def android_id(self):
876 """
877 Get the device's ANDROID_ID. Which is
878
879 "A 64-bit number (as a hex string) that is randomly generated when the user
880 first sets up the device and should remain constant for the lifetime of the
881 user's device."
882
883 .. note:: This will get reset on userdata erasure.
884
885 """
886 output = self.execute('content query --uri content://settings/secure --projection value --where "name=\'android_id\'"').strip()
887 return output.split('value=')[-1]
888
889 @property
Sergei Trofimov4e6afe92015-10-09 09:30:04 +0100890 @memoized
Sebastian Goscikf5b7c822016-02-23 17:10:01 +0000891 def model(self):
892 try:
893 return self.getprop(prop='ro.product.device')
894 except KeyError:
895 return None
896
897 @property
898 @memoized
Sergei Trofimov4e6afe92015-10-09 09:30:04 +0100899 def screen_resolution(self):
900 output = self.execute('dumpsys window')
901 match = ANDROID_SCREEN_RESOLUTION_REGEX.search(output)
902 if match:
903 return (int(match.group('width')),
904 int(match.group('height')))
905 else:
906 return (0, 0)
907
Sebastian Goscik0a8b0c62016-02-16 17:24:19 +0000908 def __init__(self,
909 connection_settings=None,
910 platform=None,
911 working_directory=None,
912 executables_directory=None,
913 connect=True,
914 modules=None,
915 load_default_modules=True,
916 shell_prompt=DEFAULT_SHELL_PROMPT,
Sergei Trofimovbeaf8d42016-12-07 15:11:32 +0000917 conn_cls=AdbConnection,
Sebastian Goscik0a8b0c62016-02-16 17:24:19 +0000918 package_data_directory="/data/data",
Sebastian Goscik0a8b0c62016-02-16 17:24:19 +0000919 ):
920 super(AndroidTarget, self).__init__(connection_settings=connection_settings,
921 platform=platform,
922 working_directory=working_directory,
923 executables_directory=executables_directory,
924 connect=connect,
925 modules=modules,
926 load_default_modules=load_default_modules,
Sergei Trofimovbeaf8d42016-12-07 15:11:32 +0000927 shell_prompt=shell_prompt,
928 conn_cls=conn_cls)
Sebastian Goscik0a8b0c62016-02-16 17:24:19 +0000929 self.package_data_directory = package_data_directory
930
Sergei Trofimov4e6afe92015-10-09 09:30:04 +0100931 def reset(self, fastboot=False): # pylint: disable=arguments-differ
932 try:
933 self.execute('reboot {}'.format(fastboot and 'fastboot' or ''),
Javi Merino16d87c62016-06-23 14:55:19 +0100934 as_root=self.needs_su, timeout=2)
Sergei Trofimov4e6afe92015-10-09 09:30:04 +0100935 except (TargetError, TimeoutError, subprocess.CalledProcessError):
936 # on some targets "reboot" doesn't return gracefully
937 pass
Marc Bonnici0687dac2017-02-28 13:48:10 +0000938 self._connected_as_root = None
Sergei Trofimov4e6afe92015-10-09 09:30:04 +0100939
Patrick Bellasif26f9422017-07-12 12:27:28 +0100940 def wait_boot_complete(self, timeout=10):
Sergei Trofimov4e6afe92015-10-09 09:30:04 +0100941 start = time.time()
Patrick Bellasif26f9422017-07-12 12:27:28 +0100942 boot_completed = boolean(self.getprop('sys.boot_completed'))
943 while not boot_completed and timeout >= time.time() - start:
944 time.sleep(5)
945 boot_completed = boolean(self.getprop('sys.boot_completed'))
946 if not boot_completed:
947 raise TargetError('Connected but Android did not fully boot.')
948
949 def connect(self, timeout=10, check_boot_completed=True): # pylint: disable=arguments-differ
Sergei Trofimov4e6afe92015-10-09 09:30:04 +0100950 device = self.connection_settings.get('device')
951 if device and ':' in device:
952 # ADB does not automatically remove a network device from it's
953 # devices list when the connection is broken by the remote, so the
954 # adb connection may have gone "stale", resulting in adb blocking
955 # indefinitely when making calls to the device. To avoid this,
956 # always disconnect first.
957 adb_disconnect(device)
958 super(AndroidTarget, self).connect(timeout=timeout)
Sergei Trofimov4e6afe92015-10-09 09:30:04 +0100959
960 if check_boot_completed:
Patrick Bellasif26f9422017-07-12 12:27:28 +0100961 self.wait_boot_complete(timeout)
Sergei Trofimov4e6afe92015-10-09 09:30:04 +0100962
963 def setup(self, executables=None):
964 super(AndroidTarget, self).setup(executables)
965 self.execute('mkdir -p {}'.format(self._file_transfer_cache))
966
Sebastian Goscik9af32ec2016-05-27 16:26:30 +0100967 def kick_off(self, command, as_root=None):
Sergei Trofimov4e6afe92015-10-09 09:30:04 +0100968 """
969 Like execute but closes adb session and returns immediately, leaving the command running on the
970 device (this is different from execute(background=True) which keeps adb connection open and returns
971 a subprocess object).
Sergei Trofimov4e6afe92015-10-09 09:30:04 +0100972 """
Sebastian Goscik9af32ec2016-05-27 16:26:30 +0100973 if as_root is None:
Javi Merino16d87c62016-06-23 14:55:19 +0100974 as_root = self.needs_su
Sergei Trofimov4e6afe92015-10-09 09:30:04 +0100975 try:
Sergei Trofimovca0b6e82016-08-30 14:25:11 +0100976 command = 'cd {} && {} nohup {} &'.format(self.working_directory, self.busybox, command)
Sergei Trofimov4e6afe92015-10-09 09:30:04 +0100977 output = self.execute(command, timeout=1, as_root=as_root)
978 except TimeoutError:
979 pass
Sergei Trofimov4e6afe92015-10-09 09:30:04 +0100980
Chris Redpath2a4eafa2016-09-27 12:08:33 +0100981 def __setup_list_directory(self):
982 # In at least Linaro Android 16.09 (which was their first Android 7 release) and maybe
983 # AOSP 7.0 as well, the ls command was changed.
984 # Previous versions default to a single column listing, which is nice and easy to parse.
985 # Newer versions default to a multi-column listing, which is not, but it does support
986 # a '-1' option to get into single column mode. Older versions do not support this option
987 # so we try the new version, and if it fails we use the old version.
988 self.ls_command = 'ls -1'
989 try:
Marc Bonnici06552372017-03-29 16:43:22 +0100990 self.execute('ls -1 {}'.format(self.working_directory), as_root=False)
Chris Redpath2a4eafa2016-09-27 12:08:33 +0100991 except TargetError:
992 self.ls_command = 'ls'
993
Sergei Trofimov4e6afe92015-10-09 09:30:04 +0100994 def list_directory(self, path, as_root=False):
Chris Redpath2a4eafa2016-09-27 12:08:33 +0100995 if self.ls_command == '':
996 self.__setup_list_directory()
997 contents = self.execute('{} {}'.format(self.ls_command, path), as_root=as_root)
Sergei Trofimov4e6afe92015-10-09 09:30:04 +0100998 return [x.strip() for x in contents.split('\n') if x.strip()]
999
1000 def install(self, filepath, timeout=None, with_name=None): # pylint: disable=W0221
1001 ext = os.path.splitext(filepath)[1].lower()
1002 if ext == '.apk':
1003 return self.install_apk(filepath, timeout)
1004 else:
1005 return self.install_executable(filepath, with_name)
1006
1007 def uninstall(self, name):
1008 if self.package_is_installed(name):
1009 self.uninstall_package(name)
1010 else:
1011 self.uninstall_executable(name)
1012
1013 def get_pids_of(self, process_name):
1014 result = self.execute('ps {}'.format(process_name[-15:]), check_exit_code=False).strip()
1015 if result and 'not found' not in result:
1016 return [int(x.split()[1]) for x in result.split('\n')[1:]]
1017 else:
1018 return []
1019
1020 def ps(self, **kwargs):
1021 lines = iter(convert_new_lines(self.execute('ps')).split('\n'))
1022 lines.next() # header
1023 result = []
1024 for line in lines:
Brendan Jackman55c27e22017-04-12 16:30:58 +01001025 parts = line.split(None, 8)
Sergei Trofimov4e6afe92015-10-09 09:30:04 +01001026 if parts:
1027 result.append(PsEntry(*(parts[0:1] + map(int, parts[1:5]) + parts[5:])))
1028 if not kwargs:
1029 return result
1030 else:
1031 filtered_result = []
1032 for entry in result:
1033 if all(getattr(entry, k) == v for k, v in kwargs.iteritems()):
1034 filtered_result.append(entry)
1035 return filtered_result
1036
1037 def capture_screen(self, filepath):
1038 on_device_file = self.path.join(self.working_directory, 'screen_capture.png')
1039 self.execute('screencap -p {}'.format(on_device_file))
1040 self.pull(on_device_file, filepath)
1041 self.remove(on_device_file)
1042
1043 def push(self, source, dest, as_root=False, timeout=None): # pylint: disable=arguments-differ
1044 if not as_root:
1045 self.conn.push(source, dest, timeout=timeout)
1046 else:
1047 device_tempfile = self.path.join(self._file_transfer_cache, source.lstrip(self.path.sep))
Sebastian Goscik1424ceb2016-02-15 15:27:19 +00001048 self.execute("mkdir -p '{}'".format(self.path.dirname(device_tempfile)))
Sergei Trofimov4e6afe92015-10-09 09:30:04 +01001049 self.conn.push(source, device_tempfile, timeout=timeout)
Sebastian Goscik1424ceb2016-02-15 15:27:19 +00001050 self.execute("cp '{}' '{}'".format(device_tempfile, dest), as_root=True)
Sergei Trofimov4e6afe92015-10-09 09:30:04 +01001051
1052 def pull(self, source, dest, as_root=False, timeout=None): # pylint: disable=arguments-differ
1053 if not as_root:
1054 self.conn.pull(source, dest, timeout=timeout)
1055 else:
1056 device_tempfile = self.path.join(self._file_transfer_cache, source.lstrip(self.path.sep))
Sebastian Goscik1424ceb2016-02-15 15:27:19 +00001057 self.execute("mkdir -p '{}'".format(self.path.dirname(device_tempfile)))
1058 self.execute("cp '{}' '{}'".format(source, device_tempfile), as_root=True)
Marc Bonnicif6d02c62017-04-21 15:21:40 +01001059 self.execute("chmod 0644 '{}'".format(device_tempfile), as_root=True)
Sergei Trofimov4e6afe92015-10-09 09:30:04 +01001060 self.conn.pull(device_tempfile, dest, timeout=timeout)
1061
1062 # Android-specific
1063
Marc Bonnici8839ed02017-08-10 17:19:13 +01001064 def swipe_to_unlock(self, direction="diagonal"):
Sergei Trofimov4e6afe92015-10-09 09:30:04 +01001065 width, height = self.screen_resolution
Sergei Trofimov4e6afe92015-10-09 09:30:04 +01001066 command = 'input swipe {} {} {} {}'
Marc Bonnici8839ed02017-08-10 17:19:13 +01001067 if direction == "diagonal":
1068 start = 100
1069 stop = width - start
1070 swipe_height = height * 2 // 3
1071 self.execute(command.format(start, swipe_height, stop, 0))
1072 elif direction == "horizontal":
Marc Bonnici1229af02017-07-26 11:29:10 +01001073 swipe_height = height * 2 // 3
Sebastian Goscik880a0bc2016-02-15 15:19:47 +00001074 start = 100
1075 stop = width - start
Marc Bonnici1229af02017-07-26 11:29:10 +01001076 self.execute(command.format(start, swipe_height, stop, swipe_height))
1077 elif direction == "vertical":
1078 swipe_middle = width / 2
1079 swipe_height = height * 2 // 3
1080 self.execute(command.format(swipe_middle, swipe_height, swipe_middle, 0))
Sebastian Goscik880a0bc2016-02-15 15:19:47 +00001081 else:
Marc Bonnici1229af02017-07-26 11:29:10 +01001082 raise TargetError("Invalid swipe direction: {}".format(direction))
Sergei Trofimov4e6afe92015-10-09 09:30:04 +01001083
1084 def getprop(self, prop=None):
1085 props = AndroidProperties(self.execute('getprop'))
1086 if prop:
1087 return props[prop]
1088 return props
1089
1090 def is_installed(self, name):
1091 return super(AndroidTarget, self).is_installed(name) or self.package_is_installed(name)
1092
1093 def package_is_installed(self, package_name):
1094 return package_name in self.list_packages()
1095
1096 def list_packages(self):
1097 output = self.execute('pm list packages')
1098 output = output.replace('package:', '')
1099 return output.split()
1100
1101 def get_package_version(self, package):
1102 output = self.execute('dumpsys package {}'.format(package))
1103 for line in convert_new_lines(output).split('\n'):
1104 if 'versionName' in line:
1105 return line.split('=', 1)[1]
1106 return None
1107
Marc Bonnicid3396f22017-05-31 15:56:50 +01001108 def get_sdk_version(self):
1109 try:
1110 return int(self.getprop('ro.build.version.sdk'))
1111 except (ValueError, TypeError):
1112 return None
1113
Marc Bonnicic33dd652017-05-31 15:51:31 +01001114 def install_apk(self, filepath, timeout=None, replace=False, allow_downgrade=False): # pylint: disable=W0221
Sergei Trofimov4e6afe92015-10-09 09:30:04 +01001115 ext = os.path.splitext(filepath)[1].lower()
1116 if ext == '.apk':
Marc Bonnicic33dd652017-05-31 15:51:31 +01001117 flags = []
1118 if replace:
1119 flags.append('-r') # Replace existing APK
1120 if allow_downgrade:
1121 flags.append('-d') # Install the APK even if a newer version is already installed
1122 if self.get_sdk_version() >= 23:
1123 flags.append('-g') # Grant all runtime permissions
1124 self.logger.debug("Replace APK = {}, ADB flags = '{}'".format(replace, ' '.join(flags)))
1125 return adb_command(self.adb_name, "install {} '{}'".format(' '.join(flags), filepath), timeout=timeout)
Sergei Trofimov4e6afe92015-10-09 09:30:04 +01001126 else:
1127 raise TargetError('Can\'t install {}: unsupported format.'.format(filepath))
1128
1129 def install_executable(self, filepath, with_name=None):
1130 self._ensure_executables_directory_is_writable()
1131 executable_name = with_name or os.path.basename(filepath)
1132 on_device_file = self.path.join(self.working_directory, executable_name)
1133 on_device_executable = self.path.join(self.executables_directory, executable_name)
1134 self.push(filepath, on_device_file)
1135 if on_device_file != on_device_executable:
Javi Merino16d87c62016-06-23 14:55:19 +01001136 self.execute('cp {} {}'.format(on_device_file, on_device_executable), as_root=self.needs_su)
1137 self.remove(on_device_file, as_root=self.needs_su)
1138 self.execute("chmod 0777 '{}'".format(on_device_executable), as_root=self.needs_su)
Sergei Trofimov4e6afe92015-10-09 09:30:04 +01001139 self._installed_binaries[executable_name] = on_device_executable
1140 return on_device_executable
1141
1142 def uninstall_package(self, package):
1143 adb_command(self.adb_name, "uninstall {}".format(package), timeout=30)
1144
1145 def uninstall_executable(self, executable_name):
1146 on_device_executable = self.path.join(self.executables_directory, executable_name)
1147 self._ensure_executables_directory_is_writable()
Javi Merino16d87c62016-06-23 14:55:19 +01001148 self.remove(on_device_executable, as_root=self.needs_su)
Sergei Trofimov4e6afe92015-10-09 09:30:04 +01001149
1150 def dump_logcat(self, filepath, filter=None, append=False, timeout=30): # pylint: disable=redefined-builtin
Sebastian Goscikaab487c2016-02-15 15:21:40 +00001151 op = '>>' if append else '>'
Sergei Trofimov4e6afe92015-10-09 09:30:04 +01001152 filtstr = ' -s {}'.format(filter) if filter else ''
1153 command = 'logcat -d{} {} {}'.format(filtstr, op, filepath)
1154 adb_command(self.adb_name, command, timeout=timeout)
1155
1156 def clear_logcat(self):
1157 adb_command(self.adb_name, 'logcat -c', timeout=30)
1158
Valentin Schneider7c2fd872017-09-11 16:58:24 +01001159 def get_logcat_monitor(self, regexps=None):
1160 return LogcatMonitor(self, regexps)
1161
Patrick Bellasi0c7eb9e2017-07-12 12:30:30 +01001162 def adb_kill_server(self, timeout=30):
1163 adb_command(self.adb_name, 'kill-server', timeout)
1164
1165 def adb_wait_for_device(self, timeout=30):
1166 adb_command(self.adb_name, 'wait-for-device', timeout)
1167
Patrick Bellasi9ce57c02017-02-16 13:11:02 +00001168 def adb_reboot_bootloader(self, timeout=30):
1169 adb_command(self.adb_name, 'reboot-bootloader', timeout)
1170
Ionela Voinescu66eaf152017-02-24 14:19:25 +00001171 def adb_root(self, enable=True, force=False):
Patrick Bellasi9ce57c02017-02-16 13:11:02 +00001172 if enable:
Ionela Voinescu66eaf152017-02-24 14:19:25 +00001173 if self._connected_as_root and not force:
Patrick Bellasi9ce57c02017-02-16 13:11:02 +00001174 return
1175 adb_command(self.adb_name, 'root', timeout=30)
1176 self._connected_as_root = True
1177 return
1178 adb_command(self.adb_name, 'unroot', timeout=30)
1179 self._connected_as_root = False
1180
Sergei Trofimov4e6afe92015-10-09 09:30:04 +01001181 def is_screen_on(self):
1182 output = self.execute('dumpsys power')
1183 match = ANDROID_SCREEN_STATE_REGEX.search(output)
1184 if match:
1185 return boolean(match.group(1))
1186 else:
1187 raise TargetError('Could not establish screen state.')
1188
1189 def ensure_screen_is_on(self):
1190 if not self.is_screen_on():
1191 self.execute('input keyevent 26')
1192
Sergei Trofimov69a83d42017-05-12 11:54:31 +01001193 def ensure_screen_is_off(self):
1194 if self.is_screen_on():
1195 self.execute('input keyevent 26')
1196
Marc Bonniciddd2e292017-07-06 17:30:12 +01001197 def set_auto_brightness(self, auto_brightness):
1198 cmd = 'settings put system screen_brightness_mode {}'
1199 self.execute(cmd.format(int(boolean(auto_brightness))))
1200
1201 def get_auto_brightness(self):
1202 cmd = 'settings get system screen_brightness_mode'
1203 return boolean(self.execute(cmd).strip())
1204
1205 def set_brightness(self, value):
1206 if not 0 <= value <= 255:
1207 msg = 'Invalid brightness "{}"; Must be between 0 and 255'
1208 raise ValueError(msg.format(value))
1209 self.set_auto_brightness(False)
1210 cmd = 'settings put system screen_brightness {}'
1211 self.execute(cmd.format(int(value)))
1212
1213 def get_brightness(self):
1214 cmd = 'settings get system screen_brightness'
1215 return integer(self.execute(cmd).strip())
1216
Marc Bonnici3e751742017-07-06 17:31:02 +01001217 def get_airplane_mode(self):
1218 cmd = 'settings get global airplane_mode_on'
1219 return boolean(self.execute(cmd).strip())
1220
1221 def set_airplane_mode(self, mode):
1222 root_required = self.get_sdk_version() > 23
1223 if root_required and not self.is_rooted:
1224 raise TargetError('Root is required to toggle airplane mode on Android 7+')
1225 cmd = 'settings put global airplane_mode_on {}'
1226 self.execute(cmd.format(int(boolean(mode))))
1227 self.execute('am broadcast -a android.intent.action.AIRPLANE_MODE', as_root=root_required)
1228
Marc Bonnici003785d2017-07-24 17:44:33 +01001229 def get_auto_rotation(self):
1230 cmd = 'settings get system accelerometer_rotation'
1231 return boolean(self.execute(cmd).strip())
1232
1233 def set_auto_rotation(self, autorotate):
1234 cmd = 'settings put system accelerometer_rotation {}'
1235 self.execute(cmd.format(int(boolean(autorotate))))
1236
1237 def set_natural_rotation(self):
1238 self.set_rotation(0)
1239
1240 def set_left_rotation(self):
1241 self.set_rotation(1)
1242
1243 def set_inverted_rotation(self):
1244 self.set_rotation(2)
1245
1246 def set_right_rotation(self):
1247 self.set_rotation(3)
1248
1249 def get_rotation(self):
1250 cmd = 'settings get system user_rotation'
1251 return self.execute(cmd).strip()
1252
1253 def set_rotation(self, rotation):
1254 if not 0 <= rotation <= 3:
1255 raise ValueError('Rotation value must be between 0 and 3')
1256 self.set_auto_rotation(False)
1257 cmd = 'settings put system user_rotation {}'
1258 self.execute(cmd.format(rotation))
1259
Sergei Trofimov69a83d42017-05-12 11:54:31 +01001260 def homescreen(self):
1261 self.execute('am start -a android.intent.action.MAIN -c android.intent.category.HOME')
1262
Sergei Trofimov961f9572015-11-18 17:32:26 +00001263 def _resolve_paths(self):
1264 if self.working_directory is None:
1265 self.working_directory = '/data/local/tmp/devlib-target'
1266 self._file_transfer_cache = self.path.join(self.working_directory, '.file-cache')
1267 if self.executables_directory is None:
Sebastian Goscikff8261e2016-02-15 15:28:20 +00001268 self.executables_directory = '/data/local/tmp/bin'
Sergei Trofimov961f9572015-11-18 17:32:26 +00001269
Sergei Trofimov4e6afe92015-10-09 09:30:04 +01001270 def _ensure_executables_directory_is_writable(self):
1271 matched = []
1272 for entry in self.list_file_systems():
1273 if self.executables_directory.rstrip('/').startswith(entry.mount_point):
1274 matched.append(entry)
1275 if matched:
1276 entry = sorted(matched, key=lambda x: len(x.mount_point))[-1]
1277 if 'rw' not in entry.options:
1278 self.execute('mount -o rw,remount {} {}'.format(entry.device,
1279 entry.mount_point),
1280 as_root=True)
1281 else:
1282 message = 'Could not find mount point for executables directory {}'
1283 raise TargetError(message.format(self.executables_directory))
1284
Brendan Jackmanbaa32ec2017-02-09 11:53:11 +00001285 _charging_enabled_path = '/sys/class/power_supply/battery/charging_enabled'
1286
1287 @property
1288 def charging_enabled(self):
1289 """
1290 Whether drawing power to charge the battery is enabled
1291
1292 Not all devices have the ability to enable/disable battery charging
1293 (e.g. because they don't have a battery). In that case,
1294 ``charging_enabled`` is None.
1295 """
1296 if not self.file_exists(self._charging_enabled_path):
1297 return None
1298 return self.read_bool(self._charging_enabled_path)
1299
1300 @charging_enabled.setter
1301 def charging_enabled(self, enabled):
1302 """
1303 Enable/disable drawing power to charge the battery
1304
1305 Not all devices have this facility. In that case, do nothing.
1306 """
1307 if not self.file_exists(self._charging_enabled_path):
1308 return
1309 self.write_value(self._charging_enabled_path, int(bool(enabled)))
Sergei Trofimov4e6afe92015-10-09 09:30:04 +01001310
1311FstabEntry = namedtuple('FstabEntry', ['device', 'mount_point', 'fs_type', 'options', 'dump_freq', 'pass_num'])
1312PsEntry = namedtuple('PsEntry', 'user pid ppid vsize rss wchan pc state name')
Sergei Trofimov5a81fe92016-01-27 16:34:26 +00001313LsmodEntry = namedtuple('LsmodEntry', ['name', 'size', 'use_count', 'used_by'])
Sergei Trofimov4e6afe92015-10-09 09:30:04 +01001314
1315
1316class Cpuinfo(object):
1317
1318 @property
1319 @memoized
1320 def architecture(self):
1321 for section in self.sections:
1322 if 'CPU architecture' in section:
1323 return section['CPU architecture']
1324 if 'architecture' in section:
1325 return section['architecture']
1326
1327 @property
1328 @memoized
1329 def cpu_names(self):
1330 cpu_names = []
1331 global_name = None
1332 for section in self.sections:
1333 if 'processor' in section:
1334 if 'CPU part' in section:
1335 cpu_names.append(_get_part_name(section))
1336 elif 'model name' in section:
1337 cpu_names.append(_get_model_name(section))
1338 else:
1339 cpu_names.append(None)
1340 elif 'CPU part' in section:
1341 global_name = _get_part_name(section)
1342 return [caseless_string(c or global_name) for c in cpu_names]
1343
1344 def __init__(self, text):
1345 self.sections = None
1346 self.text = None
1347 self.parse(text)
1348
1349 @memoized
1350 def get_cpu_features(self, cpuid=0):
1351 global_features = []
1352 for section in self.sections:
1353 if 'processor' in section:
1354 if int(section.get('processor')) != cpuid:
1355 continue
1356 if 'Features' in section:
1357 return section.get('Features').split()
Sergei Trofimov59f4f812015-12-15 18:07:34 +00001358 elif 'flags' in section:
1359 return section.get('flags').split()
Sergei Trofimov4e6afe92015-10-09 09:30:04 +01001360 elif 'Features' in section:
1361 global_features = section.get('Features').split()
Sergei Trofimov59f4f812015-12-15 18:07:34 +00001362 elif 'flags' in section:
1363 global_features = section.get('flags').split()
Sergei Trofimov4e6afe92015-10-09 09:30:04 +01001364 return global_features
1365
1366 def parse(self, text):
1367 self.sections = []
1368 current_section = {}
1369 self.text = text.strip()
1370 for line in self.text.split('\n'):
1371 line = line.strip()
1372 if line:
1373 key, value = line.split(':', 1)
1374 current_section[key.strip()] = value.strip()
1375 else: # not line
1376 self.sections.append(current_section)
1377 current_section = {}
1378 self.sections.append(current_section)
1379
1380 def __str__(self):
1381 return 'CpuInfo({})'.format(self.cpu_names)
1382
1383 __repr__ = __str__
1384
1385
1386class KernelVersion(object):
Brendan Jackman54adf802017-02-20 17:57:51 +00001387 """
1388 Class representing the version of a target kernel
Sergei Trofimov4e6afe92015-10-09 09:30:04 +01001389
Brendan Jackman54adf802017-02-20 17:57:51 +00001390 Not expected to work for very old (pre-3.0) kernel version numbers.
1391
1392 :ivar release: Version number/revision string. Typical output of
1393 ``uname -r``
1394 :type release: str
1395 :ivar version: Extra version info (aside from ``release``) reported by
1396 ``uname``
1397 :type version: str
1398 :ivar version_number: Main version number (e.g. 3 for Linux 3.18)
1399 :type version_number: int
1400 :ivar major: Major version number (e.g. 18 for Linux 3.18)
1401 :type major: int
1402 :ivar minor: Minor version number for stable kernels (e.g. 9 for 4.9.9). May
1403 be None
1404 :type minor: int
1405 :ivar rc: Release candidate number (e.g. 3 for Linux 4.9-rc3). May be None.
1406 :type rc: int
1407 :ivar sha1: Kernel git revision hash, if available (otherwise None)
1408 :type sha1: str
Brendan Jackman18b77b82017-02-20 17:52:56 +00001409
1410 :ivar parts: Tuple of version number components. Can be used for
1411 lexicographically comparing kernel versions.
1412 :type parts: tuple(int)
Brendan Jackman54adf802017-02-20 17:57:51 +00001413 """
Sergei Trofimov4e6afe92015-10-09 09:30:04 +01001414 def __init__(self, version_string):
1415 if ' #' in version_string:
1416 release, version = version_string.split(' #')
1417 self.release = release
1418 self.version = version
1419 elif version_string.startswith('#'):
1420 self.release = ''
1421 self.version = version_string
1422 else:
1423 self.release = version_string
1424 self.version = ''
1425
Patrick Bellasi9a8d5392017-02-17 15:28:07 +00001426 self.version_number = None
1427 self.major = None
1428 self.minor = None
1429 self.sha1 = None
1430 self.rc = None
1431 match = KVERSION_REGEX.match(version_string)
1432 if match:
Brendan Jackman03561ee2017-02-20 17:51:17 +00001433 groups = match.groupdict()
1434 self.version_number = int(groups['version'])
1435 self.major = int(groups['major'])
1436 if groups['minor'] is not None:
1437 self.minor = int(groups['minor'])
1438 if groups['rc'] is not None:
1439 self.rc = int(groups['rc'])
1440 if groups['sha1'] is not None:
1441 self.sha1 = match.group('sha1')
Patrick Bellasi9a8d5392017-02-17 15:28:07 +00001442
Brendan Jackman18b77b82017-02-20 17:52:56 +00001443 self.parts = (self.version_number, self.major, self.minor)
1444
Sergei Trofimov4e6afe92015-10-09 09:30:04 +01001445 def __str__(self):
1446 return '{} {}'.format(self.release, self.version)
1447
1448 __repr__ = __str__
1449
1450
1451class KernelConfig(object):
1452
1453 not_set_regex = re.compile(r'# (\S+) is not set')
1454
1455 @staticmethod
1456 def get_config_name(name):
1457 name = name.upper()
1458 if not name.startswith('CONFIG_'):
1459 name = 'CONFIG_' + name
1460 return name
1461
1462 def iteritems(self):
1463 return self._config.iteritems()
1464
1465 def __init__(self, text):
1466 self.text = text
1467 self._config = {}
1468 for line in text.split('\n'):
1469 line = line.strip()
1470 if line.startswith('#'):
1471 match = self.not_set_regex.search(line)
1472 if match:
1473 self._config[match.group(1)] = 'n'
1474 elif '=' in line:
1475 name, value = line.split('=', 1)
1476 self._config[name.strip()] = value.strip()
1477
1478 def get(self, name):
1479 return self._config.get(self.get_config_name(name))
1480
1481 def like(self, name):
1482 regex = re.compile(name, re.I)
1483 result = {}
1484 for k, v in self._config.iteritems():
1485 if regex.search(k):
1486 result[k] = v
1487 return result
1488
1489 def is_enabled(self, name):
1490 return self.get(name) == 'y'
1491
1492 def is_module(self, name):
1493 return self.get(name) == 'm'
1494
1495 def is_not_set(self, name):
1496 return self.get(name) == 'n'
1497
1498 def has(self, name):
1499 return self.get(name) in ['m', 'y']
1500
1501
1502class LocalLinuxTarget(LinuxTarget):
1503
Sergei Trofimovbeaf8d42016-12-07 15:11:32 +00001504 def __init__(self,
1505 connection_settings=None,
1506 platform=None,
1507 working_directory=None,
1508 executables_directory=None,
1509 connect=True,
1510 modules=None,
1511 load_default_modules=True,
1512 shell_prompt=DEFAULT_SHELL_PROMPT,
1513 conn_cls=LocalConnection,
1514 ):
1515 super(LocalLinuxTarget, self).__init__(connection_settings=connection_settings,
1516 platform=platform,
1517 working_directory=working_directory,
1518 executables_directory=executables_directory,
1519 connect=connect,
1520 modules=modules,
1521 load_default_modules=load_default_modules,
1522 shell_prompt=shell_prompt,
1523 conn_cls=conn_cls)
Sergei Trofimov4e6afe92015-10-09 09:30:04 +01001524
Sergei Trofimov961f9572015-11-18 17:32:26 +00001525 def _resolve_paths(self):
Sergei Trofimov4e6afe92015-10-09 09:30:04 +01001526 if self.working_directory is None:
1527 self.working_directory = '/tmp'
1528 if self.executables_directory is None:
1529 self.executables_directory = '/tmp'
Sergei Trofimov4e6afe92015-10-09 09:30:04 +01001530
1531
1532def _get_model_name(section):
1533 name_string = section['model name']
1534 parts = name_string.split('@')[0].strip().split()
1535 return ' '.join([p for p in parts
1536 if '(' not in p and p != 'CPU'])
1537
1538
1539def _get_part_name(section):
1540 implementer = section.get('CPU implementer', '0x0')
1541 part = section['CPU part']
1542 variant = section.get('CPU variant', '0x0')
1543 name = get_cpu_name(*map(integer, [implementer, part, variant]))
1544 if name is None:
1545 name = '{}/{}/{}'.format(implementer, part, variant)
1546 return name