blob: e74411075b36a66af50beffe4873d23b993ac2c0 [file] [log] [blame]
Sergei Trofimov4e6afe92015-10-09 09:30:04 +01001import os
2import re
3import time
4import logging
5import posixpath
6import subprocess
7import tempfile
8import threading
9from collections import namedtuple
10
11from devlib.host import LocalConnection, PACKAGE_BIN_DIRECTORY
12from devlib.module import get_module
13from devlib.platform import Platform
14from devlib.exception import TargetError, TargetNotRespondingError, TimeoutError
15from devlib.utils.ssh import SshConnection
16from devlib.utils.android import AdbConnection, AndroidProperties, adb_command, adb_disconnect
17from devlib.utils.misc import memoized, isiterable, convert_new_lines, merge_lists
18from devlib.utils.misc import ABI_MAP, get_cpu_name, ranges_to_list, escape_double_quotes
19from devlib.utils.types import integer, boolean, bitmask, identifier, caseless_string
20
21
22FSTAB_ENTRY_REGEX = re.compile(r'(\S+) on (\S+) type (\S+) \((\S+)\)')
23ANDROID_SCREEN_STATE_REGEX = re.compile('(?:mPowerState|mScreenOn)=([0-9]+|true|false)',
24 re.IGNORECASE)
25ANDROID_SCREEN_RESOLUTION_REGEX = re.compile(r'mUnrestrictedScreen=\(\d+,\d+\)'
26 r'\s+(?P<width>\d+)x(?P<height>\d+)')
27DEFAULT_SHELL_PROMPT = re.compile(r'^.*(shell|root)@.*:/\S* [#$] ',
28 re.MULTILINE)
29
30
31class Target(object):
32
33 conn_cls = None
34 path = None
35 os = None
36
37 default_modules = [
38 'hotplug',
39 'cpufreq',
40 'cpuidle',
41 'cgroups',
42 'hwmon',
43 ]
44
45 @property
46 def core_names(self):
47 return self.platform.core_names
48
49 @property
50 def core_clusters(self):
51 return self.platform.core_clusters
52
53 @property
54 def big_core(self):
55 return self.platform.big_core
56
57 @property
58 def little_core(self):
59 return self.platform.little_core
60
61 @property
62 def is_connected(self):
63 return self.conn is not None
64
65 @property
66 @memoized
67 def connected_as_root(self):
68 result = self.execute('id')
69 return 'uid=0(' in result
70
71 @property
72 @memoized
73 def is_rooted(self):
74 if self.connected_as_root:
75 return True
76 try:
77 self.execute('ls /', timeout=2, as_root=True)
78 return True
79 except (TargetError, TimeoutError):
80 return False
81
82 @property
83 @memoized
84 def kernel_version(self):
85 return KernelVersion(self.execute('uname -r -v').strip())
86
87 @property
88 def os_version(self): # pylint: disable=no-self-use
89 return {}
90
91 @property
92 def abi(self): # pylint: disable=no-self-use
93 return None
94
95 @property
96 @memoized
97 def cpuinfo(self):
98 return Cpuinfo(self.execute('cat /proc/cpuinfo'))
99
100 @property
101 @memoized
102 def number_of_cpus(self):
103 num_cpus = 0
104 corere = re.compile(r'^\s*cpu\d+\s*$')
105 output = self.execute('ls /sys/devices/system/cpu')
106 for entry in output.split():
107 if corere.match(entry):
108 num_cpus += 1
109 return num_cpus
110
111 @property
112 @memoized
113 def config(self):
114 try:
115 return KernelConfig(self.execute('zcat /proc/config.gz'))
116 except TargetError:
117 for path in ['/boot/config', '/boot/config-$(uname -r)']:
118 try:
119 return KernelConfig(self.execute('cat {}'.format(path)))
120 except TargetError:
121 pass
122 return KernelConfig('')
123
124 @property
125 @memoized
126 def user(self):
127 return self.getenv('USER')
128
129 @property
130 def conn(self):
131 if self._connections:
132 tid = id(threading.current_thread())
133 if tid not in self._connections:
134 self._connections[tid] = self.get_connection()
135 return self._connections[tid]
136 else:
137 return None
138
139 def __init__(self,
140 connection_settings=None,
141 platform=None,
142 working_directory=None,
143 executables_directory=None,
144 connect=True,
145 modules=None,
146 load_default_modules=True,
147 shell_prompt=DEFAULT_SHELL_PROMPT,
148 ):
149 self.connection_settings = connection_settings or {}
150 self.platform = platform or Platform()
151 self.working_directory = working_directory
152 self.executables_directory = executables_directory
153 self.modules = modules or []
154 self.load_default_modules = load_default_modules
155 self.shell_prompt = shell_prompt
156 self.logger = logging.getLogger(self.__class__.__name__)
157 self._installed_binaries = {}
158 self._installed_modules = {}
159 self._cache = {}
160 self._connections = {}
161 self.busybox = None
162
163 if load_default_modules:
164 module_lists = [self.default_modules]
165 else:
166 module_lists = []
167 module_lists += [self.modules, self.platform.modules]
168 self.modules = merge_lists(*module_lists, duplicates='first')
169 self._update_modules('early')
170 if connect:
171 self.connect()
172
173 # connection and initialization
174
175 def connect(self, timeout=None):
176 self.platform.init_target_connection(self)
177 tid = id(threading.current_thread())
178 self._connections[tid] = self.get_connection(timeout=timeout)
Sergei Trofimov961f9572015-11-18 17:32:26 +0000179 self._resolve_paths()
Sergei Trofimov4e6afe92015-10-09 09:30:04 +0100180 self.busybox = self.get_installed('busybox')
Sergei Trofimov4e6afe92015-10-09 09:30:04 +0100181 self.platform.update_from_target(self)
Patrick Bellasib83e5182015-10-12 12:37:11 +0100182 self._update_modules('connected')
Sergei Trofimov4e6afe92015-10-09 09:30:04 +0100183 if self.platform.big_core and self.load_default_modules:
184 self._install_module(get_module('bl'))
185
186 def disconnect(self):
187 for conn in self._connections.itervalues():
188 conn.close()
189 self._connections = {}
190
191 def get_connection(self, timeout=None):
192 if self.conn_cls is None:
193 raise NotImplementedError('conn_cls must be set by the subclass of Target')
194 return self.conn_cls(timeout=timeout, **self.connection_settings) # pylint: disable=not-callable
195
196 def setup(self, executables=None):
197 self.execute('mkdir -p {}'.format(self.working_directory))
198 self.execute('mkdir -p {}'.format(self.executables_directory))
199 self.busybox = self.install(os.path.join(PACKAGE_BIN_DIRECTORY, self.abi, 'busybox'))
200 for host_exe in (executables or []): # pylint: disable=superfluous-parens
201 self.install(host_exe)
202
203 def reboot(self, hard=False, connect=True, timeout=180):
204 if hard:
205 if not self.has('hard_reset'):
206 raise TargetError('Hard reset not supported for this target.')
207 self.hard_reset() # pylint: disable=no-member
208 else:
209 if not self.is_connected:
210 message = 'Cannot reboot target becuase it is disconnected. ' +\
211 'Either connect() first, or specify hard=True ' +\
212 '(in which case, a hard_reset module must be installed)'
213 raise TargetError(message)
214 self.reset()
215 if self.has('boot'):
216 self.boot() # pylint: disable=no-member
217 if connect:
218 self.connect(timeout=timeout)
219
220 # file transfer
221
222 def push(self, source, dest, timeout=None):
223 return self.conn.push(source, dest, timeout=timeout)
224
225 def pull(self, source, dest, timeout=None):
226 return self.conn.pull(source, dest, timeout=timeout)
227
228 # execution
229
230 def execute(self, command, timeout=None, check_exit_code=True, as_root=False):
231 return self.conn.execute(command, timeout, check_exit_code, as_root)
232
233 def background(self, command, stdout=subprocess.PIPE, stderr=subprocess.PIPE, as_root=False):
234 return self.conn.background(command, stdout, stderr, as_root)
235
236 def invoke(self, binary, args=None, in_directory=None, on_cpus=None,
237 as_root=False, timeout=30):
238 """
239 Executes the specified binary under the specified conditions.
240
241 :binary: binary to execute. Must be present and executable on the device.
242 :args: arguments to be passed to the binary. The can be either a list or
243 a string.
244 :in_directory: execute the binary in the specified directory. This must
245 be an absolute path.
246 :on_cpus: taskset the binary to these CPUs. This may be a single ``int`` (in which
247 case, it will be interpreted as the mask), a list of ``ints``, in which
248 case this will be interpreted as the list of cpus, or string, which
249 will be interpreted as a comma-separated list of cpu ranges, e.g.
250 ``"0,4-7"``.
251 :as_root: Specify whether the command should be run as root
252 :timeout: If the invocation does not terminate within this number of seconds,
253 a ``TimeoutError`` exception will be raised. Set to ``None`` if the
254 invocation should not timeout.
255
256 """
257 command = binary
258 if args:
259 if isiterable(args):
260 args = ' '.join(args)
261 command = '{} {}'.format(command, args)
262 if on_cpus:
263 on_cpus = bitmask(on_cpus)
264 command = '{} taskset 0x{:x} {}'.format(self.busybox, on_cpus, command)
265 if in_directory:
266 command = 'cd {} && {}'.format(in_directory, command)
267 return self.execute(command, as_root=as_root, timeout=timeout)
268
269 def kick_off(self, command, as_root=False):
270 raise NotImplementedError()
271
272 # sysfs interaction
273
274 def read_value(self, path, kind=None):
275 output = self.execute('cat \'{}\''.format(path), as_root=self.is_rooted).strip() # pylint: disable=E1103
276 if kind:
277 return kind(output)
278 else:
279 return output
280
281 def read_int(self, path):
282 return self.read_value(path, kind=integer)
283
284 def read_bool(self, path):
285 return self.read_value(path, kind=boolean)
286
287 def write_value(self, path, value, verify=True):
288 value = str(value)
289 self.execute('echo {} > \'{}\''.format(value, path), check_exit_code=False, as_root=True)
290 if verify:
291 output = self.read_value(path)
292 if not output == value:
293 message = 'Could not set the value of {} to "{}" (read "{}")'.format(path, value, output)
294 raise TargetError(message)
295
296 def reset(self):
297 try:
298 self.execute('reboot', as_root=self.is_rooted, timeout=2)
299 except (TargetError, TimeoutError, subprocess.CalledProcessError):
300 # on some targets "reboot" doesn't return gracefully
301 pass
302
303 def check_responsive(self):
304 try:
305 self.conn.execute('ls /', timeout=5)
306 except (TimeoutError, subprocess.CalledProcessError):
307 raise TargetNotRespondingError(self.conn.name)
308
309 # process management
310
311 def kill(self, pid, signal=None, as_root=False):
312 signal_string = '-s {}'.format(signal) if signal else ''
313 self.execute('kill {} {}'.format(signal_string, pid), as_root=as_root)
314
315 def killall(self, process_name, signal=None, as_root=False):
316 for pid in self.get_pids_of(process_name):
317 self.kill(pid, signal=signal, as_root=as_root)
318
319 def get_pids_of(self, process_name):
320 raise NotImplementedError()
321
322 def ps(self, **kwargs):
323 raise NotImplementedError()
324
325 # files
326
327 def file_exists(self, filepath):
328 command = 'if [ -e \'{}\' ]; then echo 1; else echo 0; fi'
329 return boolean(self.execute(command.format(filepath)).strip())
330
331 def list_file_systems(self):
332 output = self.execute('mount')
333 fstab = []
334 for line in output.split('\n'):
335 line = line.strip()
336 if not line:
337 continue
338 match = FSTAB_ENTRY_REGEX.search(line)
339 if match:
340 fstab.append(FstabEntry(match.group(1), match.group(2),
341 match.group(3), match.group(4),
342 None, None))
343 else: # assume pre-M Android
344 fstab.append(FstabEntry(*line.split()))
345 return fstab
346
347 def list_directory(self, path, as_root=False):
348 raise NotImplementedError()
349
350 def get_workpath(self, name):
351 return self.path.join(self.working_directory, name)
352
353 def tempfile(self, prefix='', suffix=''):
354 names = tempfile._get_candidate_names() # pylint: disable=W0212
355 for _ in xrange(tempfile.TMP_MAX):
356 name = names.next()
357 path = self.get_workpath(prefix + name + suffix)
358 if not self.file_exists(path):
359 return path
360 raise IOError('No usable temporary filename found')
361
362 def remove(self, path, as_root=False):
363 self.execute('rm -rf {}'.format(path), as_root=as_root)
364
365 # misc
366 def core_cpus(self, core):
367 return [i for i, c in enumerate(self.core_names) if c == core]
368
369 def list_online_cpus(self, core=None):
370 path = self.path.join('/sys/devices/system/cpu/online')
371 output = self.read_value(path)
372 all_online = ranges_to_list(output)
373 if core:
374 cpus = self.core_cpus(core)
375 if not cpus:
376 raise ValueError(core)
377 return [o for o in all_online if o in cpus]
378 else:
379 return all_online
380
381 def list_offline_cpus(self):
382 online = self.list_online_cpus()
383 return [c for c in xrange(self.number_of_cpus)
384 if c not in online]
385
386 def getenv(self, variable):
387 return self.execute('echo ${}'.format(variable)).rstrip('\r\n')
388
389 def capture_screen(self, filepath):
390 raise NotImplementedError()
391
392 def install(self, filepath, timeout=None, with_name=None):
393 raise NotImplementedError()
394
395 def uninstall(self, name):
396 raise NotImplementedError()
397
398 def get_installed(self, name):
399 for path in self.getenv('PATH').split(self.path.pathsep):
400 try:
401 if name in self.list_directory(path):
402 return self.path.join(path, name)
403 except TargetError:
404 pass # directory does not exist or no executable premssions
Sergei Trofimovb5324532015-11-18 18:07:47 +0000405 if self.file_exists(self.executables_directory):
406 if name in self.list_directory(self.executables_directory):
407 return self.path.join(self.executables_directory, name)
Sergei Trofimov4e6afe92015-10-09 09:30:04 +0100408
409 which = get_installed
410
411 def is_installed(self, name):
412 return bool(self.get_installed(name))
413
414 def bin(self, name):
415 return self._installed_binaries.get(name, name)
416
417 def has(self, modname):
418 return hasattr(self, identifier(modname))
419
420 def _update_modules(self, stage):
421 for mod in self.modules:
422 if isinstance(mod, dict):
423 mod, params = mod.items()[0]
424 else:
425 params = {}
426 mod = get_module(mod)
427 if not mod.stage == stage:
428 continue
429 if mod.probe(self):
430 self._install_module(mod, **params)
431 else:
Javi Merinod0c71fb2016-01-18 12:27:48 +0000432 msg = 'Module {} is not supported by the target'.format(mod.name)
433 if self.load_default_modules:
434 self.logger.debug(msg)
435 else:
436 self.logger.warning(msg)
Sergei Trofimov4e6afe92015-10-09 09:30:04 +0100437
438 def _install_module(self, mod, **params):
439 if mod.name not in self._installed_modules:
440 self.logger.debug('Installing module {}'.format(mod.name))
441 mod.install(self, **params)
442 self._installed_modules[mod.name] = mod
443 else:
444 self.logger.debug('Module {} is already installed.'.format(mod.name))
445
Sergei Trofimov961f9572015-11-18 17:32:26 +0000446 def _resolve_paths(self):
447 raise NotImplementedError()
448
Sergei Trofimov4e6afe92015-10-09 09:30:04 +0100449
450class LinuxTarget(Target):
451
452 conn_cls = SshConnection
453 path = posixpath
454 os = 'linux'
455
456 @property
457 @memoized
458 def abi(self):
459 value = self.execute('uname -m').strip()
460 for abi, architectures in ABI_MAP.iteritems():
461 if value in architectures:
462 result = abi
463 break
464 else:
465 result = value
466 return result
467
468 @property
469 @memoized
470 def os_version(self):
471 os_version = {}
472 try:
473 command = 'ls /etc/*-release /etc*-version /etc/*_release /etc/*_version 2>/dev/null'
474 version_files = self.execute(command, check_exit_code=False).strip().split()
475 for vf in version_files:
476 name = self.path.basename(vf)
477 output = self.read_value(vf)
478 os_version[name] = output.strip().replace('\n', ' ')
479 except TargetError:
480 raise
481 return os_version
482
483 def connect(self, timeout=None):
484 super(LinuxTarget, self).connect(timeout=timeout)
Sergei Trofimov4e6afe92015-10-09 09:30:04 +0100485
486 def kick_off(self, command, as_root=False):
487 command = 'sh -c "{}" 1>/dev/null 2>/dev/null &'.format(escape_double_quotes(command))
488 return self.conn.execute(command, as_root=as_root)
489
490 def get_pids_of(self, process_name):
491 """Returns a list of PIDs of all processes with the specified name."""
492 # result should be a column of PIDs with the first row as "PID" header
493 result = self.execute('ps -C {} -o pid'.format(process_name), # NOQA
494 check_exit_code=False).strip().split()
495 if len(result) >= 2: # at least one row besides the header
496 return map(int, result[1:])
497 else:
498 return []
499
500 def ps(self, **kwargs):
501 command = 'ps -eo user,pid,ppid,vsize,rss,wchan,pcpu,state,fname'
502 lines = iter(convert_new_lines(self.execute(command)).split('\n'))
503 lines.next() # header
504
505 result = []
506 for line in lines:
507 parts = re.split(r'\s+', line, maxsplit=8)
508 if parts and parts != ['']:
509 result.append(PsEntry(*(parts[0:1] + map(int, parts[1:5]) + parts[5:])))
510
511 if not kwargs:
512 return result
513 else:
514 filtered_result = []
515 for entry in result:
516 if all(getattr(entry, k) == v for k, v in kwargs.iteritems()):
517 filtered_result.append(entry)
518 return filtered_result
519
520 def list_directory(self, path, as_root=False):
521 contents = self.execute('ls -1 {}'.format(path), as_root=as_root)
522 return [x.strip() for x in contents.split('\n') if x.strip()]
523
524 def install(self, filepath, timeout=None, with_name=None): # pylint: disable=W0221
525 destpath = self.path.join(self.executables_directory,
526 with_name and with_name or self.path.basename(filepath))
527 self.push(filepath, destpath)
528 self.execute('chmod a+x {}'.format(destpath), timeout=timeout)
529 self._installed_binaries[self.path.basename(destpath)] = destpath
530 return destpath
531
532 def uninstall(self, name):
533 path = self.path.join(self.executables_directory, name)
534 self.remove(path)
535
536 def capture_screen(self, filepath):
537 if not self.is_installed('scrot'):
538 self.logger.debug('Could not take screenshot as scrot is not installed.')
539 return
540 try:
541
542 tmpfile = self.tempfile()
543 self.execute('DISPLAY=:0.0 scrot {}'.format(tmpfile))
544 self.pull(tmpfile, filepath)
545 self.remove(tmpfile)
546 except TargetError as e:
547 if "Can't open X dispay." not in e.message:
548 raise e
549 message = e.message.split('OUTPUT:', 1)[1].strip() # pylint: disable=no-member
550 self.logger.debug('Could not take screenshot: {}'.format(message))
551
Sergei Trofimov961f9572015-11-18 17:32:26 +0000552 def _resolve_paths(self):
553 if self.working_directory is None:
554 if self.connected_as_root:
555 self.working_directory = '/root/devlib-target'
556 else:
557 self.working_directory = '/home/{}/devlib-target'.format(self.user)
558 if self.executables_directory is None:
559 self.executables_directory = self.path.join(self.working_directory, 'bin')
560
Sergei Trofimov4e6afe92015-10-09 09:30:04 +0100561
562class AndroidTarget(Target):
563
564 conn_cls = AdbConnection
565 path = posixpath
566 os = 'android'
567
568 @property
569 @memoized
570 def abi(self):
571 return self.getprop()['ro.product.cpu.abi'].split('-')[0]
572
573 @property
574 @memoized
575 def os_version(self):
576 os_version = {}
577 for k, v in self.getprop().iteritems():
578 if k.startswith('ro.build.version'):
579 part = k.split('.')[-1]
580 os_version[part] = v
581 return os_version
582
583 @property
584 def adb_name(self):
585 return self.conn.device
586
587 @property
588 @memoized
589 def screen_resolution(self):
590 output = self.execute('dumpsys window')
591 match = ANDROID_SCREEN_RESOLUTION_REGEX.search(output)
592 if match:
593 return (int(match.group('width')),
594 int(match.group('height')))
595 else:
596 return (0, 0)
597
598
Sergei Trofimov4e6afe92015-10-09 09:30:04 +0100599 def reset(self, fastboot=False): # pylint: disable=arguments-differ
600 try:
601 self.execute('reboot {}'.format(fastboot and 'fastboot' or ''),
602 as_root=self.is_rooted, timeout=2)
603 except (TargetError, TimeoutError, subprocess.CalledProcessError):
604 # on some targets "reboot" doesn't return gracefully
605 pass
606
607 def connect(self, timeout=10, check_boot_completed=True): # pylint: disable=arguments-differ
608 start = time.time()
609 device = self.connection_settings.get('device')
610 if device and ':' in device:
611 # ADB does not automatically remove a network device from it's
612 # devices list when the connection is broken by the remote, so the
613 # adb connection may have gone "stale", resulting in adb blocking
614 # indefinitely when making calls to the device. To avoid this,
615 # always disconnect first.
616 adb_disconnect(device)
617 super(AndroidTarget, self).connect(timeout=timeout)
Sergei Trofimov4e6afe92015-10-09 09:30:04 +0100618
619 if check_boot_completed:
620 boot_completed = boolean(self.getprop('sys.boot_completed'))
621 while not boot_completed and timeout >= time.time() - start:
622 time.sleep(5)
623 boot_completed = boolean(self.getprop('sys.boot_completed'))
624 if not boot_completed:
625 raise TargetError('Connected but Android did not fully boot.')
626
627 def setup(self, executables=None):
628 super(AndroidTarget, self).setup(executables)
629 self.execute('mkdir -p {}'.format(self._file_transfer_cache))
630
631 def kick_off(self, command, as_root=False):
632 """
633 Like execute but closes adb session and returns immediately, leaving the command running on the
634 device (this is different from execute(background=True) which keeps adb connection open and returns
635 a subprocess object).
636
637 .. note:: This relies on busybox's nohup applet and so won't work on unrooted devices.
638
639 """
640 if not self.is_rooted:
641 raise TargetError('kick_off uses busybox\'s nohup applet and so can only be run a rooted device.')
642 try:
643 command = 'cd {} && {} nohup {}'.format(self.working_directory, self.bin('busybox'), command)
644 output = self.execute(command, timeout=1, as_root=as_root)
645 except TimeoutError:
646 pass
647 else:
648 raise ValueError('Background command exited before timeout; got "{}"'.format(output))
649
650 def list_directory(self, path, as_root=False):
651 contents = self.execute('ls {}'.format(path), as_root=as_root)
652 return [x.strip() for x in contents.split('\n') if x.strip()]
653
654 def install(self, filepath, timeout=None, with_name=None): # pylint: disable=W0221
655 ext = os.path.splitext(filepath)[1].lower()
656 if ext == '.apk':
657 return self.install_apk(filepath, timeout)
658 else:
659 return self.install_executable(filepath, with_name)
660
661 def uninstall(self, name):
662 if self.package_is_installed(name):
663 self.uninstall_package(name)
664 else:
665 self.uninstall_executable(name)
666
667 def get_pids_of(self, process_name):
668 result = self.execute('ps {}'.format(process_name[-15:]), check_exit_code=False).strip()
669 if result and 'not found' not in result:
670 return [int(x.split()[1]) for x in result.split('\n')[1:]]
671 else:
672 return []
673
674 def ps(self, **kwargs):
675 lines = iter(convert_new_lines(self.execute('ps')).split('\n'))
676 lines.next() # header
677 result = []
678 for line in lines:
679 parts = line.split()
680 if parts:
681 result.append(PsEntry(*(parts[0:1] + map(int, parts[1:5]) + parts[5:])))
682 if not kwargs:
683 return result
684 else:
685 filtered_result = []
686 for entry in result:
687 if all(getattr(entry, k) == v for k, v in kwargs.iteritems()):
688 filtered_result.append(entry)
689 return filtered_result
690
691 def capture_screen(self, filepath):
692 on_device_file = self.path.join(self.working_directory, 'screen_capture.png')
693 self.execute('screencap -p {}'.format(on_device_file))
694 self.pull(on_device_file, filepath)
695 self.remove(on_device_file)
696
697 def push(self, source, dest, as_root=False, timeout=None): # pylint: disable=arguments-differ
698 if not as_root:
699 self.conn.push(source, dest, timeout=timeout)
700 else:
701 device_tempfile = self.path.join(self._file_transfer_cache, source.lstrip(self.path.sep))
702 self.execute('mkdir -p {}'.format(self.path.dirname(device_tempfile)))
703 self.conn.push(source, device_tempfile, timeout=timeout)
704 self.execute('cp {} {}'.format(device_tempfile, dest), as_root=True)
705
706 def pull(self, source, dest, as_root=False, timeout=None): # pylint: disable=arguments-differ
707 if not as_root:
708 self.conn.pull(source, dest, timeout=timeout)
709 else:
710 device_tempfile = self.path.join(self._file_transfer_cache, source.lstrip(self.path.sep))
711 self.execute('mkdir -p {}'.format(self.path.dirname(device_tempfile)))
712 self.execute('cp {} {}'.format(source, device_tempfile), as_root=True)
713 self.conn.pull(device_tempfile, dest, timeout=timeout)
714
715 # Android-specific
716
717 def swipe_to_unlock(self):
718 width, height = self.screen_resolution
719 swipe_heigh = height * 2 // 3
720 start = 100
721 stop = width - start
722 command = 'input swipe {} {} {} {}'
723 self.execute(command.format(start, swipe_heigh, stop, swipe_heigh))
724
725 def getprop(self, prop=None):
726 props = AndroidProperties(self.execute('getprop'))
727 if prop:
728 return props[prop]
729 return props
730
731 def is_installed(self, name):
732 return super(AndroidTarget, self).is_installed(name) or self.package_is_installed(name)
733
734 def package_is_installed(self, package_name):
735 return package_name in self.list_packages()
736
737 def list_packages(self):
738 output = self.execute('pm list packages')
739 output = output.replace('package:', '')
740 return output.split()
741
742 def get_package_version(self, package):
743 output = self.execute('dumpsys package {}'.format(package))
744 for line in convert_new_lines(output).split('\n'):
745 if 'versionName' in line:
746 return line.split('=', 1)[1]
747 return None
748
749 def install_apk(self, filepath, timeout=None): # pylint: disable=W0221
750 ext = os.path.splitext(filepath)[1].lower()
751 if ext == '.apk':
752 return adb_command(self.adb_name, "install {}".format(filepath), timeout=timeout)
753 else:
754 raise TargetError('Can\'t install {}: unsupported format.'.format(filepath))
755
756 def install_executable(self, filepath, with_name=None):
757 self._ensure_executables_directory_is_writable()
758 executable_name = with_name or os.path.basename(filepath)
759 on_device_file = self.path.join(self.working_directory, executable_name)
760 on_device_executable = self.path.join(self.executables_directory, executable_name)
761 self.push(filepath, on_device_file)
762 if on_device_file != on_device_executable:
763 self.execute('cp {} {}'.format(on_device_file, on_device_executable), as_root=self.is_rooted)
764 self.remove(on_device_file, as_root=self.is_rooted)
765 self.execute('chmod 0777 {}'.format(on_device_executable), as_root=self.is_rooted)
766 self._installed_binaries[executable_name] = on_device_executable
767 return on_device_executable
768
769 def uninstall_package(self, package):
770 adb_command(self.adb_name, "uninstall {}".format(package), timeout=30)
771
772 def uninstall_executable(self, executable_name):
773 on_device_executable = self.path.join(self.executables_directory, executable_name)
774 self._ensure_executables_directory_is_writable()
775 self.remove(on_device_executable, as_root=self.is_rooted)
776
777 def dump_logcat(self, filepath, filter=None, append=False, timeout=30): # pylint: disable=redefined-builtin
778 op = '>>' if append == True else '>'
779 filtstr = ' -s {}'.format(filter) if filter else ''
780 command = 'logcat -d{} {} {}'.format(filtstr, op, filepath)
781 adb_command(self.adb_name, command, timeout=timeout)
782
783 def clear_logcat(self):
784 adb_command(self.adb_name, 'logcat -c', timeout=30)
785
786 def is_screen_on(self):
787 output = self.execute('dumpsys power')
788 match = ANDROID_SCREEN_STATE_REGEX.search(output)
789 if match:
790 return boolean(match.group(1))
791 else:
792 raise TargetError('Could not establish screen state.')
793
794 def ensure_screen_is_on(self):
795 if not self.is_screen_on():
796 self.execute('input keyevent 26')
797
Sergei Trofimov961f9572015-11-18 17:32:26 +0000798 def _resolve_paths(self):
799 if self.working_directory is None:
800 self.working_directory = '/data/local/tmp/devlib-target'
801 self._file_transfer_cache = self.path.join(self.working_directory, '.file-cache')
802 if self.executables_directory is None:
803 self.executables_directory = self.path.join(self.working_directory, 'bin')
804
Sergei Trofimov4e6afe92015-10-09 09:30:04 +0100805 def _ensure_executables_directory_is_writable(self):
806 matched = []
807 for entry in self.list_file_systems():
808 if self.executables_directory.rstrip('/').startswith(entry.mount_point):
809 matched.append(entry)
810 if matched:
811 entry = sorted(matched, key=lambda x: len(x.mount_point))[-1]
812 if 'rw' not in entry.options:
813 self.execute('mount -o rw,remount {} {}'.format(entry.device,
814 entry.mount_point),
815 as_root=True)
816 else:
817 message = 'Could not find mount point for executables directory {}'
818 raise TargetError(message.format(self.executables_directory))
819
820
821FstabEntry = namedtuple('FstabEntry', ['device', 'mount_point', 'fs_type', 'options', 'dump_freq', 'pass_num'])
822PsEntry = namedtuple('PsEntry', 'user pid ppid vsize rss wchan pc state name')
823
824
825class Cpuinfo(object):
826
827 @property
828 @memoized
829 def architecture(self):
830 for section in self.sections:
831 if 'CPU architecture' in section:
832 return section['CPU architecture']
833 if 'architecture' in section:
834 return section['architecture']
835
836 @property
837 @memoized
838 def cpu_names(self):
839 cpu_names = []
840 global_name = None
841 for section in self.sections:
842 if 'processor' in section:
843 if 'CPU part' in section:
844 cpu_names.append(_get_part_name(section))
845 elif 'model name' in section:
846 cpu_names.append(_get_model_name(section))
847 else:
848 cpu_names.append(None)
849 elif 'CPU part' in section:
850 global_name = _get_part_name(section)
851 return [caseless_string(c or global_name) for c in cpu_names]
852
853 def __init__(self, text):
854 self.sections = None
855 self.text = None
856 self.parse(text)
857
858 @memoized
859 def get_cpu_features(self, cpuid=0):
860 global_features = []
861 for section in self.sections:
862 if 'processor' in section:
863 if int(section.get('processor')) != cpuid:
864 continue
865 if 'Features' in section:
866 return section.get('Features').split()
Sergei Trofimov59f4f812015-12-15 18:07:34 +0000867 elif 'flags' in section:
868 return section.get('flags').split()
Sergei Trofimov4e6afe92015-10-09 09:30:04 +0100869 elif 'Features' in section:
870 global_features = section.get('Features').split()
Sergei Trofimov59f4f812015-12-15 18:07:34 +0000871 elif 'flags' in section:
872 global_features = section.get('flags').split()
Sergei Trofimov4e6afe92015-10-09 09:30:04 +0100873 return global_features
874
875 def parse(self, text):
876 self.sections = []
877 current_section = {}
878 self.text = text.strip()
879 for line in self.text.split('\n'):
880 line = line.strip()
881 if line:
882 key, value = line.split(':', 1)
883 current_section[key.strip()] = value.strip()
884 else: # not line
885 self.sections.append(current_section)
886 current_section = {}
887 self.sections.append(current_section)
888
889 def __str__(self):
890 return 'CpuInfo({})'.format(self.cpu_names)
891
892 __repr__ = __str__
893
894
895class KernelVersion(object):
896
897 def __init__(self, version_string):
898 if ' #' in version_string:
899 release, version = version_string.split(' #')
900 self.release = release
901 self.version = version
902 elif version_string.startswith('#'):
903 self.release = ''
904 self.version = version_string
905 else:
906 self.release = version_string
907 self.version = ''
908
909 def __str__(self):
910 return '{} {}'.format(self.release, self.version)
911
912 __repr__ = __str__
913
914
915class KernelConfig(object):
916
917 not_set_regex = re.compile(r'# (\S+) is not set')
918
919 @staticmethod
920 def get_config_name(name):
921 name = name.upper()
922 if not name.startswith('CONFIG_'):
923 name = 'CONFIG_' + name
924 return name
925
926 def iteritems(self):
927 return self._config.iteritems()
928
929 def __init__(self, text):
930 self.text = text
931 self._config = {}
932 for line in text.split('\n'):
933 line = line.strip()
934 if line.startswith('#'):
935 match = self.not_set_regex.search(line)
936 if match:
937 self._config[match.group(1)] = 'n'
938 elif '=' in line:
939 name, value = line.split('=', 1)
940 self._config[name.strip()] = value.strip()
941
942 def get(self, name):
943 return self._config.get(self.get_config_name(name))
944
945 def like(self, name):
946 regex = re.compile(name, re.I)
947 result = {}
948 for k, v in self._config.iteritems():
949 if regex.search(k):
950 result[k] = v
951 return result
952
953 def is_enabled(self, name):
954 return self.get(name) == 'y'
955
956 def is_module(self, name):
957 return self.get(name) == 'm'
958
959 def is_not_set(self, name):
960 return self.get(name) == 'n'
961
962 def has(self, name):
963 return self.get(name) in ['m', 'y']
964
965
966class LocalLinuxTarget(LinuxTarget):
967
968 conn_cls = LocalConnection
969
Sergei Trofimov961f9572015-11-18 17:32:26 +0000970 def _resolve_paths(self):
Sergei Trofimov4e6afe92015-10-09 09:30:04 +0100971 if self.working_directory is None:
972 self.working_directory = '/tmp'
973 if self.executables_directory is None:
974 self.executables_directory = '/tmp'
Sergei Trofimov4e6afe92015-10-09 09:30:04 +0100975
976
977def _get_model_name(section):
978 name_string = section['model name']
979 parts = name_string.split('@')[0].strip().split()
980 return ' '.join([p for p in parts
981 if '(' not in p and p != 'CPU'])
982
983
984def _get_part_name(section):
985 implementer = section.get('CPU implementer', '0x0')
986 part = section['CPU part']
987 variant = section.get('CPU variant', '0x0')
988 name = get_cpu_name(*map(integer, [implementer, part, variant]))
989 if name is None:
990 name = '{}/{}/{}'.format(implementer, part, variant)
991 return name
992