blob: 6b9f760dd511acc69b04c62278138953a1e5f534 [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:
432 self.logger.debug('Module {} is not supported by the target'.format(mod.name))
433
434 def _install_module(self, mod, **params):
435 if mod.name not in self._installed_modules:
436 self.logger.debug('Installing module {}'.format(mod.name))
437 mod.install(self, **params)
438 self._installed_modules[mod.name] = mod
439 else:
440 self.logger.debug('Module {} is already installed.'.format(mod.name))
441
Sergei Trofimov961f9572015-11-18 17:32:26 +0000442 def _resolve_paths(self):
443 raise NotImplementedError()
444
Sergei Trofimov4e6afe92015-10-09 09:30:04 +0100445
446class LinuxTarget(Target):
447
448 conn_cls = SshConnection
449 path = posixpath
450 os = 'linux'
451
452 @property
453 @memoized
454 def abi(self):
455 value = self.execute('uname -m').strip()
456 for abi, architectures in ABI_MAP.iteritems():
457 if value in architectures:
458 result = abi
459 break
460 else:
461 result = value
462 return result
463
464 @property
465 @memoized
466 def os_version(self):
467 os_version = {}
468 try:
469 command = 'ls /etc/*-release /etc*-version /etc/*_release /etc/*_version 2>/dev/null'
470 version_files = self.execute(command, check_exit_code=False).strip().split()
471 for vf in version_files:
472 name = self.path.basename(vf)
473 output = self.read_value(vf)
474 os_version[name] = output.strip().replace('\n', ' ')
475 except TargetError:
476 raise
477 return os_version
478
479 def connect(self, timeout=None):
480 super(LinuxTarget, self).connect(timeout=timeout)
Sergei Trofimov4e6afe92015-10-09 09:30:04 +0100481
482 def kick_off(self, command, as_root=False):
483 command = 'sh -c "{}" 1>/dev/null 2>/dev/null &'.format(escape_double_quotes(command))
484 return self.conn.execute(command, as_root=as_root)
485
486 def get_pids_of(self, process_name):
487 """Returns a list of PIDs of all processes with the specified name."""
488 # result should be a column of PIDs with the first row as "PID" header
489 result = self.execute('ps -C {} -o pid'.format(process_name), # NOQA
490 check_exit_code=False).strip().split()
491 if len(result) >= 2: # at least one row besides the header
492 return map(int, result[1:])
493 else:
494 return []
495
496 def ps(self, **kwargs):
497 command = 'ps -eo user,pid,ppid,vsize,rss,wchan,pcpu,state,fname'
498 lines = iter(convert_new_lines(self.execute(command)).split('\n'))
499 lines.next() # header
500
501 result = []
502 for line in lines:
503 parts = re.split(r'\s+', line, maxsplit=8)
504 if parts and parts != ['']:
505 result.append(PsEntry(*(parts[0:1] + map(int, parts[1:5]) + parts[5:])))
506
507 if not kwargs:
508 return result
509 else:
510 filtered_result = []
511 for entry in result:
512 if all(getattr(entry, k) == v for k, v in kwargs.iteritems()):
513 filtered_result.append(entry)
514 return filtered_result
515
516 def list_directory(self, path, as_root=False):
517 contents = self.execute('ls -1 {}'.format(path), as_root=as_root)
518 return [x.strip() for x in contents.split('\n') if x.strip()]
519
520 def install(self, filepath, timeout=None, with_name=None): # pylint: disable=W0221
521 destpath = self.path.join(self.executables_directory,
522 with_name and with_name or self.path.basename(filepath))
523 self.push(filepath, destpath)
524 self.execute('chmod a+x {}'.format(destpath), timeout=timeout)
525 self._installed_binaries[self.path.basename(destpath)] = destpath
526 return destpath
527
528 def uninstall(self, name):
529 path = self.path.join(self.executables_directory, name)
530 self.remove(path)
531
532 def capture_screen(self, filepath):
533 if not self.is_installed('scrot'):
534 self.logger.debug('Could not take screenshot as scrot is not installed.')
535 return
536 try:
537
538 tmpfile = self.tempfile()
539 self.execute('DISPLAY=:0.0 scrot {}'.format(tmpfile))
540 self.pull(tmpfile, filepath)
541 self.remove(tmpfile)
542 except TargetError as e:
543 if "Can't open X dispay." not in e.message:
544 raise e
545 message = e.message.split('OUTPUT:', 1)[1].strip() # pylint: disable=no-member
546 self.logger.debug('Could not take screenshot: {}'.format(message))
547
Sergei Trofimov961f9572015-11-18 17:32:26 +0000548 def _resolve_paths(self):
549 if self.working_directory is None:
550 if self.connected_as_root:
551 self.working_directory = '/root/devlib-target'
552 else:
553 self.working_directory = '/home/{}/devlib-target'.format(self.user)
554 if self.executables_directory is None:
555 self.executables_directory = self.path.join(self.working_directory, 'bin')
556
Sergei Trofimov4e6afe92015-10-09 09:30:04 +0100557
558class AndroidTarget(Target):
559
560 conn_cls = AdbConnection
561 path = posixpath
562 os = 'android'
563
564 @property
565 @memoized
566 def abi(self):
567 return self.getprop()['ro.product.cpu.abi'].split('-')[0]
568
569 @property
570 @memoized
571 def os_version(self):
572 os_version = {}
573 for k, v in self.getprop().iteritems():
574 if k.startswith('ro.build.version'):
575 part = k.split('.')[-1]
576 os_version[part] = v
577 return os_version
578
579 @property
580 def adb_name(self):
581 return self.conn.device
582
583 @property
584 @memoized
585 def screen_resolution(self):
586 output = self.execute('dumpsys window')
587 match = ANDROID_SCREEN_RESOLUTION_REGEX.search(output)
588 if match:
589 return (int(match.group('width')),
590 int(match.group('height')))
591 else:
592 return (0, 0)
593
594
Sergei Trofimov4e6afe92015-10-09 09:30:04 +0100595 def reset(self, fastboot=False): # pylint: disable=arguments-differ
596 try:
597 self.execute('reboot {}'.format(fastboot and 'fastboot' or ''),
598 as_root=self.is_rooted, timeout=2)
599 except (TargetError, TimeoutError, subprocess.CalledProcessError):
600 # on some targets "reboot" doesn't return gracefully
601 pass
602
603 def connect(self, timeout=10, check_boot_completed=True): # pylint: disable=arguments-differ
604 start = time.time()
605 device = self.connection_settings.get('device')
606 if device and ':' in device:
607 # ADB does not automatically remove a network device from it's
608 # devices list when the connection is broken by the remote, so the
609 # adb connection may have gone "stale", resulting in adb blocking
610 # indefinitely when making calls to the device. To avoid this,
611 # always disconnect first.
612 adb_disconnect(device)
613 super(AndroidTarget, self).connect(timeout=timeout)
Sergei Trofimov4e6afe92015-10-09 09:30:04 +0100614
615 if check_boot_completed:
616 boot_completed = boolean(self.getprop('sys.boot_completed'))
617 while not boot_completed and timeout >= time.time() - start:
618 time.sleep(5)
619 boot_completed = boolean(self.getprop('sys.boot_completed'))
620 if not boot_completed:
621 raise TargetError('Connected but Android did not fully boot.')
622
623 def setup(self, executables=None):
624 super(AndroidTarget, self).setup(executables)
625 self.execute('mkdir -p {}'.format(self._file_transfer_cache))
626
627 def kick_off(self, command, as_root=False):
628 """
629 Like execute but closes adb session and returns immediately, leaving the command running on the
630 device (this is different from execute(background=True) which keeps adb connection open and returns
631 a subprocess object).
632
633 .. note:: This relies on busybox's nohup applet and so won't work on unrooted devices.
634
635 """
636 if not self.is_rooted:
637 raise TargetError('kick_off uses busybox\'s nohup applet and so can only be run a rooted device.')
638 try:
639 command = 'cd {} && {} nohup {}'.format(self.working_directory, self.bin('busybox'), command)
640 output = self.execute(command, timeout=1, as_root=as_root)
641 except TimeoutError:
642 pass
643 else:
644 raise ValueError('Background command exited before timeout; got "{}"'.format(output))
645
646 def list_directory(self, path, as_root=False):
647 contents = self.execute('ls {}'.format(path), as_root=as_root)
648 return [x.strip() for x in contents.split('\n') if x.strip()]
649
650 def install(self, filepath, timeout=None, with_name=None): # pylint: disable=W0221
651 ext = os.path.splitext(filepath)[1].lower()
652 if ext == '.apk':
653 return self.install_apk(filepath, timeout)
654 else:
655 return self.install_executable(filepath, with_name)
656
657 def uninstall(self, name):
658 if self.package_is_installed(name):
659 self.uninstall_package(name)
660 else:
661 self.uninstall_executable(name)
662
663 def get_pids_of(self, process_name):
664 result = self.execute('ps {}'.format(process_name[-15:]), check_exit_code=False).strip()
665 if result and 'not found' not in result:
666 return [int(x.split()[1]) for x in result.split('\n')[1:]]
667 else:
668 return []
669
670 def ps(self, **kwargs):
671 lines = iter(convert_new_lines(self.execute('ps')).split('\n'))
672 lines.next() # header
673 result = []
674 for line in lines:
675 parts = line.split()
676 if parts:
677 result.append(PsEntry(*(parts[0:1] + map(int, parts[1:5]) + parts[5:])))
678 if not kwargs:
679 return result
680 else:
681 filtered_result = []
682 for entry in result:
683 if all(getattr(entry, k) == v for k, v in kwargs.iteritems()):
684 filtered_result.append(entry)
685 return filtered_result
686
687 def capture_screen(self, filepath):
688 on_device_file = self.path.join(self.working_directory, 'screen_capture.png')
689 self.execute('screencap -p {}'.format(on_device_file))
690 self.pull(on_device_file, filepath)
691 self.remove(on_device_file)
692
693 def push(self, source, dest, as_root=False, timeout=None): # pylint: disable=arguments-differ
694 if not as_root:
695 self.conn.push(source, dest, timeout=timeout)
696 else:
697 device_tempfile = self.path.join(self._file_transfer_cache, source.lstrip(self.path.sep))
698 self.execute('mkdir -p {}'.format(self.path.dirname(device_tempfile)))
699 self.conn.push(source, device_tempfile, timeout=timeout)
700 self.execute('cp {} {}'.format(device_tempfile, dest), as_root=True)
701
702 def pull(self, source, dest, as_root=False, timeout=None): # pylint: disable=arguments-differ
703 if not as_root:
704 self.conn.pull(source, dest, timeout=timeout)
705 else:
706 device_tempfile = self.path.join(self._file_transfer_cache, source.lstrip(self.path.sep))
707 self.execute('mkdir -p {}'.format(self.path.dirname(device_tempfile)))
708 self.execute('cp {} {}'.format(source, device_tempfile), as_root=True)
709 self.conn.pull(device_tempfile, dest, timeout=timeout)
710
711 # Android-specific
712
713 def swipe_to_unlock(self):
714 width, height = self.screen_resolution
715 swipe_heigh = height * 2 // 3
716 start = 100
717 stop = width - start
718 command = 'input swipe {} {} {} {}'
719 self.execute(command.format(start, swipe_heigh, stop, swipe_heigh))
720
721 def getprop(self, prop=None):
722 props = AndroidProperties(self.execute('getprop'))
723 if prop:
724 return props[prop]
725 return props
726
727 def is_installed(self, name):
728 return super(AndroidTarget, self).is_installed(name) or self.package_is_installed(name)
729
730 def package_is_installed(self, package_name):
731 return package_name in self.list_packages()
732
733 def list_packages(self):
734 output = self.execute('pm list packages')
735 output = output.replace('package:', '')
736 return output.split()
737
738 def get_package_version(self, package):
739 output = self.execute('dumpsys package {}'.format(package))
740 for line in convert_new_lines(output).split('\n'):
741 if 'versionName' in line:
742 return line.split('=', 1)[1]
743 return None
744
745 def install_apk(self, filepath, timeout=None): # pylint: disable=W0221
746 ext = os.path.splitext(filepath)[1].lower()
747 if ext == '.apk':
748 return adb_command(self.adb_name, "install {}".format(filepath), timeout=timeout)
749 else:
750 raise TargetError('Can\'t install {}: unsupported format.'.format(filepath))
751
752 def install_executable(self, filepath, with_name=None):
753 self._ensure_executables_directory_is_writable()
754 executable_name = with_name or os.path.basename(filepath)
755 on_device_file = self.path.join(self.working_directory, executable_name)
756 on_device_executable = self.path.join(self.executables_directory, executable_name)
757 self.push(filepath, on_device_file)
758 if on_device_file != on_device_executable:
759 self.execute('cp {} {}'.format(on_device_file, on_device_executable), as_root=self.is_rooted)
760 self.remove(on_device_file, as_root=self.is_rooted)
761 self.execute('chmod 0777 {}'.format(on_device_executable), as_root=self.is_rooted)
762 self._installed_binaries[executable_name] = on_device_executable
763 return on_device_executable
764
765 def uninstall_package(self, package):
766 adb_command(self.adb_name, "uninstall {}".format(package), timeout=30)
767
768 def uninstall_executable(self, executable_name):
769 on_device_executable = self.path.join(self.executables_directory, executable_name)
770 self._ensure_executables_directory_is_writable()
771 self.remove(on_device_executable, as_root=self.is_rooted)
772
773 def dump_logcat(self, filepath, filter=None, append=False, timeout=30): # pylint: disable=redefined-builtin
774 op = '>>' if append == True else '>'
775 filtstr = ' -s {}'.format(filter) if filter else ''
776 command = 'logcat -d{} {} {}'.format(filtstr, op, filepath)
777 adb_command(self.adb_name, command, timeout=timeout)
778
779 def clear_logcat(self):
780 adb_command(self.adb_name, 'logcat -c', timeout=30)
781
782 def is_screen_on(self):
783 output = self.execute('dumpsys power')
784 match = ANDROID_SCREEN_STATE_REGEX.search(output)
785 if match:
786 return boolean(match.group(1))
787 else:
788 raise TargetError('Could not establish screen state.')
789
790 def ensure_screen_is_on(self):
791 if not self.is_screen_on():
792 self.execute('input keyevent 26')
793
Sergei Trofimov961f9572015-11-18 17:32:26 +0000794 def _resolve_paths(self):
795 if self.working_directory is None:
796 self.working_directory = '/data/local/tmp/devlib-target'
797 self._file_transfer_cache = self.path.join(self.working_directory, '.file-cache')
798 if self.executables_directory is None:
799 self.executables_directory = self.path.join(self.working_directory, 'bin')
800
Sergei Trofimov4e6afe92015-10-09 09:30:04 +0100801 def _ensure_executables_directory_is_writable(self):
802 matched = []
803 for entry in self.list_file_systems():
804 if self.executables_directory.rstrip('/').startswith(entry.mount_point):
805 matched.append(entry)
806 if matched:
807 entry = sorted(matched, key=lambda x: len(x.mount_point))[-1]
808 if 'rw' not in entry.options:
809 self.execute('mount -o rw,remount {} {}'.format(entry.device,
810 entry.mount_point),
811 as_root=True)
812 else:
813 message = 'Could not find mount point for executables directory {}'
814 raise TargetError(message.format(self.executables_directory))
815
816
817FstabEntry = namedtuple('FstabEntry', ['device', 'mount_point', 'fs_type', 'options', 'dump_freq', 'pass_num'])
818PsEntry = namedtuple('PsEntry', 'user pid ppid vsize rss wchan pc state name')
819
820
821class Cpuinfo(object):
822
823 @property
824 @memoized
825 def architecture(self):
826 for section in self.sections:
827 if 'CPU architecture' in section:
828 return section['CPU architecture']
829 if 'architecture' in section:
830 return section['architecture']
831
832 @property
833 @memoized
834 def cpu_names(self):
835 cpu_names = []
836 global_name = None
837 for section in self.sections:
838 if 'processor' in section:
839 if 'CPU part' in section:
840 cpu_names.append(_get_part_name(section))
841 elif 'model name' in section:
842 cpu_names.append(_get_model_name(section))
843 else:
844 cpu_names.append(None)
845 elif 'CPU part' in section:
846 global_name = _get_part_name(section)
847 return [caseless_string(c or global_name) for c in cpu_names]
848
849 def __init__(self, text):
850 self.sections = None
851 self.text = None
852 self.parse(text)
853
854 @memoized
855 def get_cpu_features(self, cpuid=0):
856 global_features = []
857 for section in self.sections:
858 if 'processor' in section:
859 if int(section.get('processor')) != cpuid:
860 continue
861 if 'Features' in section:
862 return section.get('Features').split()
863 elif 'Features' in section:
864 global_features = section.get('Features').split()
865 return global_features
866
867 def parse(self, text):
868 self.sections = []
869 current_section = {}
870 self.text = text.strip()
871 for line in self.text.split('\n'):
872 line = line.strip()
873 if line:
874 key, value = line.split(':', 1)
875 current_section[key.strip()] = value.strip()
876 else: # not line
877 self.sections.append(current_section)
878 current_section = {}
879 self.sections.append(current_section)
880
881 def __str__(self):
882 return 'CpuInfo({})'.format(self.cpu_names)
883
884 __repr__ = __str__
885
886
887class KernelVersion(object):
888
889 def __init__(self, version_string):
890 if ' #' in version_string:
891 release, version = version_string.split(' #')
892 self.release = release
893 self.version = version
894 elif version_string.startswith('#'):
895 self.release = ''
896 self.version = version_string
897 else:
898 self.release = version_string
899 self.version = ''
900
901 def __str__(self):
902 return '{} {}'.format(self.release, self.version)
903
904 __repr__ = __str__
905
906
907class KernelConfig(object):
908
909 not_set_regex = re.compile(r'# (\S+) is not set')
910
911 @staticmethod
912 def get_config_name(name):
913 name = name.upper()
914 if not name.startswith('CONFIG_'):
915 name = 'CONFIG_' + name
916 return name
917
918 def iteritems(self):
919 return self._config.iteritems()
920
921 def __init__(self, text):
922 self.text = text
923 self._config = {}
924 for line in text.split('\n'):
925 line = line.strip()
926 if line.startswith('#'):
927 match = self.not_set_regex.search(line)
928 if match:
929 self._config[match.group(1)] = 'n'
930 elif '=' in line:
931 name, value = line.split('=', 1)
932 self._config[name.strip()] = value.strip()
933
934 def get(self, name):
935 return self._config.get(self.get_config_name(name))
936
937 def like(self, name):
938 regex = re.compile(name, re.I)
939 result = {}
940 for k, v in self._config.iteritems():
941 if regex.search(k):
942 result[k] = v
943 return result
944
945 def is_enabled(self, name):
946 return self.get(name) == 'y'
947
948 def is_module(self, name):
949 return self.get(name) == 'm'
950
951 def is_not_set(self, name):
952 return self.get(name) == 'n'
953
954 def has(self, name):
955 return self.get(name) in ['m', 'y']
956
957
958class LocalLinuxTarget(LinuxTarget):
959
960 conn_cls = LocalConnection
961
Sergei Trofimov961f9572015-11-18 17:32:26 +0000962 def _resolve_paths(self):
Sergei Trofimov4e6afe92015-10-09 09:30:04 +0100963 if self.working_directory is None:
964 self.working_directory = '/tmp'
965 if self.executables_directory is None:
966 self.executables_directory = '/tmp'
Sergei Trofimov4e6afe92015-10-09 09:30:04 +0100967
968
969def _get_model_name(section):
970 name_string = section['model name']
971 parts = name_string.split('@')[0].strip().split()
972 return ' '.join([p for p in parts
973 if '(' not in p and p != 'CPU'])
974
975
976def _get_part_name(section):
977 implementer = section.get('CPU implementer', '0x0')
978 part = section['CPU part']
979 variant = section.get('CPU variant', '0x0')
980 name = get_cpu_name(*map(integer, [implementer, part, variant]))
981 if name is None:
982 name = '{}/{}/{}'.format(implementer, part, variant)
983 return name
984