Commit bb6ecf6b authored by jbudorick's avatar jbudorick Committed by Commit bot

[Android] Move some pylib modules into devil/

BUG=476719
TBR=maruel@chromium.org

Review URL: https://codereview.chromium.org/1314913009

Cr-Commit-Position: refs/heads/master@{#346806}
parent 36974ce0
...@@ -41,15 +41,15 @@ def CommonChecks(input_api, output_api): ...@@ -41,15 +41,15 @@ def CommonChecks(input_api, output_api):
output_api, output_api,
unit_tests=[ unit_tests=[
J('.', 'emma_coverage_stats_test.py'), J('.', 'emma_coverage_stats_test.py'),
J('devil', 'android', 'battery_utils_test.py'),
J('devil', 'android', 'device_utils_test.py'),
J('devil', 'android', 'md5sum_test.py'),
J('devil', 'android', 'logcat_monitor_test.py'),
J('pylib', 'base', 'test_dispatcher_unittest.py'), J('pylib', 'base', 'test_dispatcher_unittest.py'),
J('pylib', 'device', 'battery_utils_test.py'),
J('pylib', 'device', 'device_utils_test.py'),
J('pylib', 'device', 'logcat_monitor_test.py'),
J('pylib', 'gtest', 'gtest_test_instance_test.py'), J('pylib', 'gtest', 'gtest_test_instance_test.py'),
J('pylib', 'instrumentation', J('pylib', 'instrumentation',
'instrumentation_test_instance_test.py'), 'instrumentation_test_instance_test.py'),
J('pylib', 'results', 'json_results_test.py'), J('pylib', 'results', 'json_results_test.py'),
J('pylib', 'utils', 'md5sum_test.py'),
], ],
env=pylib_test_env)) env=pylib_test_env))
return output return output
......
jbudorick@chromium.org
mikecase@chromium.org
perezju@chromium.org
rnephew@chromium.org
# Copyright 2015 The Chromium Authors. All rights reserved.
# Use of this source code is governed by a BSD-style license that can be
# found in the LICENSE file.
# Copyright (c) 2013 The Chromium Authors. All rights reserved.
# Use of this source code is governed by a BSD-style license that can be
# found in the LICENSE file.
"""Module containing utilities for apk packages."""
import os.path
import re
from devil.android.sdk import aapt
from devil.utils import cmd_helper
from pylib import constants
_AAPT_PATH = os.path.join(constants.ANDROID_SDK_TOOLS, 'aapt')
_MANIFEST_ATTRIBUTE_RE = re.compile(
r'\s*A: ([^\(\)= ]*)\([^\(\)= ]*\)="(.*)" \(Raw: .*\)$')
_MANIFEST_ELEMENT_RE = re.compile(r'\s*(?:E|N): (\S*) .*$')
_PACKAGE_NAME_RE = re.compile(r'package: .*name=\'(\S*)\'')
_SPLIT_NAME_RE = re.compile(r'package: .*split=\'(\S*)\'')
def GetPackageName(apk_path):
"""Returns the package name of the apk."""
return ApkHelper(apk_path).GetPackageName()
# TODO(jbudorick): Deprecate and remove this function once callers have been
# converted to ApkHelper.GetInstrumentationName
def GetInstrumentationName(apk_path):
"""Returns the name of the Instrumentation in the apk."""
return ApkHelper(apk_path).GetInstrumentationName()
def _ParseManifestFromApk(apk_path):
aapt_output = aapt.Dump('xmltree', apk_path, 'AndroidManifest.xml')
parsed_manifest = {}
node_stack = [parsed_manifest]
indent = ' '
for line in aapt_output[1:]:
if len(line) == 0:
continue
indent_depth = 0
while line[(len(indent) * indent_depth):].startswith(indent):
indent_depth += 1
node_stack = node_stack[:indent_depth]
node = node_stack[-1]
m = _MANIFEST_ELEMENT_RE.match(line[len(indent) * indent_depth:])
if m:
if not m.group(1) in node:
node[m.group(1)] = {}
node_stack += [node[m.group(1)]]
continue
m = _MANIFEST_ATTRIBUTE_RE.match(line[len(indent) * indent_depth:])
if m:
if not m.group(1) in node:
node[m.group(1)] = []
node[m.group(1)].append(m.group(2))
continue
return parsed_manifest
class ApkHelper(object):
def __init__(self, apk_path):
self._apk_path = apk_path
self._manifest = None
self._package_name = None
self._split_name = None
def GetActivityName(self):
"""Returns the name of the Activity in the apk."""
manifest_info = self._GetManifest()
try:
activity = (
manifest_info['manifest']['application']['activity']
['android:name'][0])
except KeyError:
return None
if '.' not in activity:
activity = '%s.%s' % (self.GetPackageName(), activity)
elif activity.startswith('.'):
activity = '%s%s' % (self.GetPackageName(), activity)
return activity
def GetInstrumentationName(
self, default='android.test.InstrumentationTestRunner'):
"""Returns the name of the Instrumentation in the apk."""
manifest_info = self._GetManifest()
try:
return manifest_info['manifest']['instrumentation']['android:name'][0]
except KeyError:
return default
def GetPackageName(self):
"""Returns the package name of the apk."""
if self._package_name:
return self._package_name
aapt_output = aapt.Dump('badging', self._apk_path)
for line in aapt_output:
m = _PACKAGE_NAME_RE.match(line)
if m:
self._package_name = m.group(1)
return self._package_name
raise Exception('Failed to determine package name of %s' % self._apk_path)
def GetSplitName(self):
"""Returns the name of the split of the apk."""
if self._split_name:
return self._split_name
aapt_output = aapt.Dump('badging', self._apk_path)
for line in aapt_output:
m = _SPLIT_NAME_RE.match(line)
if m:
self._split_name = m.group(1)
return self._split_name
return None
def _GetManifest(self):
if not self._manifest:
self._manifest = _ParseManifestFromApk(self._apk_path)
return self._manifest
This diff is collapsed.
...@@ -14,12 +14,12 @@ import os ...@@ -14,12 +14,12 @@ import os
import sys import sys
import unittest import unittest
from devil.android import battery_utils
from devil.android import device_errors
from devil.android import device_utils
from devil.android import device_utils_test
from devil.utils import mock_calls
from pylib import constants from pylib import constants
from pylib.device import battery_utils
from pylib.device import device_errors
from pylib.device import device_utils
from pylib.device import device_utils_test
from pylib.utils import mock_calls
sys.path.append(os.path.join( sys.path.append(os.path.join(
constants.DIR_SOURCE_ROOT, 'third_party', 'pymock')) constants.DIR_SOURCE_ROOT, 'third_party', 'pymock'))
......
# Copyright 2014 The Chromium Authors. All rights reserved.
# Use of this source code is governed by a BSD-style license that can be
# found in the LICENSE file.
"""
Function/method decorators that provide timeout and retry logic.
"""
import functools
import os
import sys
import threading
from devil.android import device_errors
from devil.utils import cmd_helper
from devil.utils import reraiser_thread
from devil.utils import timeout_retry
from pylib import constants
DEFAULT_TIMEOUT_ATTR = '_default_timeout'
DEFAULT_RETRIES_ATTR = '_default_retries'
def _TimeoutRetryWrapper(f, timeout_func, retries_func, pass_values=False):
""" Wraps a funcion with timeout and retry handling logic.
Args:
f: The function to wrap.
timeout_func: A callable that returns the timeout value.
retries_func: A callable that returns the retries value.
pass_values: If True, passes the values returned by |timeout_func| and
|retries_func| to the wrapped function as 'timeout' and
'retries' kwargs, respectively.
Returns:
The wrapped function.
"""
@functools.wraps(f)
def TimeoutRetryWrapper(*args, **kwargs):
timeout = timeout_func(*args, **kwargs)
retries = retries_func(*args, **kwargs)
if pass_values:
kwargs['timeout'] = timeout
kwargs['retries'] = retries
def impl():
return f(*args, **kwargs)
try:
if isinstance(threading.current_thread(),
timeout_retry.TimeoutRetryThread):
return impl()
else:
return timeout_retry.Run(impl, timeout, retries)
except reraiser_thread.TimeoutError as e:
raise device_errors.CommandTimeoutError(str(e)), None, (
sys.exc_info()[2])
except cmd_helper.TimeoutError as e:
raise device_errors.CommandTimeoutError(str(e)), None, (
sys.exc_info()[2])
return TimeoutRetryWrapper
def WithTimeoutAndRetries(f):
"""A decorator that handles timeouts and retries.
'timeout' and 'retries' kwargs must be passed to the function.
Args:
f: The function to decorate.
Returns:
The decorated function.
"""
get_timeout = lambda *a, **kw: kw['timeout']
get_retries = lambda *a, **kw: kw['retries']
return _TimeoutRetryWrapper(f, get_timeout, get_retries)
def WithExplicitTimeoutAndRetries(timeout, retries):
"""Returns a decorator that handles timeouts and retries.
The provided |timeout| and |retries| values are always used.
Args:
timeout: The number of seconds to wait for the decorated function to
return. Always used.
retries: The number of times the decorated function should be retried on
failure. Always used.
Returns:
The actual decorator.
"""
def decorator(f):
get_timeout = lambda *a, **kw: timeout
get_retries = lambda *a, **kw: retries
return _TimeoutRetryWrapper(f, get_timeout, get_retries)
return decorator
def WithTimeoutAndRetriesDefaults(default_timeout, default_retries):
"""Returns a decorator that handles timeouts and retries.
The provided |default_timeout| and |default_retries| values are used only
if timeout and retries values are not provided.
Args:
default_timeout: The number of seconds to wait for the decorated function
to return. Only used if a 'timeout' kwarg is not passed
to the decorated function.
default_retries: The number of times the decorated function should be
retried on failure. Only used if a 'retries' kwarg is not
passed to the decorated function.
Returns:
The actual decorator.
"""
def decorator(f):
get_timeout = lambda *a, **kw: kw.get('timeout', default_timeout)
get_retries = lambda *a, **kw: kw.get('retries', default_retries)
return _TimeoutRetryWrapper(f, get_timeout, get_retries, pass_values=True)
return decorator
def WithTimeoutAndRetriesFromInstance(
default_timeout_name=DEFAULT_TIMEOUT_ATTR,
default_retries_name=DEFAULT_RETRIES_ATTR):
"""Returns a decorator that handles timeouts and retries.
The provided |default_timeout_name| and |default_retries_name| are used to
get the default timeout value and the default retries value from the object
instance if timeout and retries values are not provided.
Note that this should only be used to decorate methods, not functions.
Args:
default_timeout_name: The name of the default timeout attribute of the
instance.
default_retries_name: The name of the default retries attribute of the
instance.
Returns:
The actual decorator.
"""
def decorator(f):
def get_timeout(inst, *_args, **kwargs):
return kwargs.get('timeout', getattr(inst, default_timeout_name))
def get_retries(inst, *_args, **kwargs):
return kwargs.get('retries', getattr(inst, default_retries_name))
return _TimeoutRetryWrapper(f, get_timeout, get_retries, pass_values=True)
return decorator
...@@ -14,10 +14,10 @@ import time ...@@ -14,10 +14,10 @@ import time
import traceback import traceback
import unittest import unittest
from devil.android import decorators
from devil.android import device_errors
from devil.utils import reraiser_thread
from pylib import constants from pylib import constants
from pylib.device import decorators
from pylib.device import device_errors
from pylib.utils import reraiser_thread
_DEFAULT_TIMEOUT = 30 _DEFAULT_TIMEOUT = 30
_DEFAULT_RETRIES = 3 _DEFAULT_RETRIES = 3
......
# Copyright 2014 The Chromium Authors. All rights reserved.
# Use of this source code is governed by a BSD-style license that can be
# found in the LICENSE file.
import json
import os
import threading
from pylib import constants
# TODO(jbudorick): Remove this once the blacklist is optional.
BLACKLIST_JSON = os.path.join(
constants.DIR_SOURCE_ROOT,
os.environ.get('CHROMIUM_OUT_DIR', 'out'),
'bad_devices.json')
class Blacklist(object):
def __init__(self, path):
self._blacklist_lock = threading.RLock()
self._path = path
def Read(self):
"""Reads the blacklist from the blacklist file.
Returns:
A list containing bad devices.
"""
with self._blacklist_lock:
if not os.path.exists(self._path):
return []
with open(self._path, 'r') as f:
return json.load(f)
def Write(self, blacklist):
"""Writes the provided blacklist to the blacklist file.
Args:
blacklist: list of bad devices to write to the blacklist file.
"""
with self._blacklist_lock:
with open(self._path, 'w') as f:
json.dump(list(set(blacklist)), f)
def Extend(self, devices):
"""Adds devices to blacklist file.
Args:
devices: list of bad devices to be added to the blacklist file.
"""
with self._blacklist_lock:
blacklist = ReadBlacklist()
blacklist.extend(devices)
WriteBlacklist(blacklist)
def Reset(self):
"""Erases the blacklist file if it exists."""
with self._blacklist_lock:
if os.path.exists(self._path):
os.remove(self._path)
def ReadBlacklist():
# TODO(jbudorick): Phase out once all clients have migrated.
return Blacklist(BLACKLIST_JSON).Read()
def WriteBlacklist(blacklist):
# TODO(jbudorick): Phase out once all clients have migrated.
Blacklist(BLACKLIST_JSON).Write(blacklist)
def ExtendBlacklist(devices):
# TODO(jbudorick): Phase out once all clients have migrated.
Blacklist(BLACKLIST_JSON).Extend(devices)
def ResetBlacklist():
# TODO(jbudorick): Phase out once all clients have migrated.
Blacklist(BLACKLIST_JSON).Reset()
# Copyright 2014 The Chromium Authors. All rights reserved.
# Use of this source code is governed by a BSD-style license that can be
# found in the LICENSE file.
"""
Exception classes raised by AdbWrapper and DeviceUtils.
"""
from devil import base_error
from devil.utils import cmd_helper
class CommandFailedError(base_error.BaseError):
"""Exception for command failures."""
def __init__(self, message, device_serial=None):
if device_serial is not None:
message = '(device: %s) %s' % (device_serial, message)
self.device_serial = device_serial
super(CommandFailedError, self).__init__(message)
class AdbCommandFailedError(CommandFailedError):
"""Exception for adb command failures."""
def __init__(self, args, output, status=None, device_serial=None,
message=None):
self.args = args
self.output = output
self.status = status
if not message:
adb_cmd = ' '.join(cmd_helper.SingleQuote(arg) for arg in self.args)
message = ['adb %s: failed ' % adb_cmd]
if status:
message.append('with exit status %s ' % self.status)
if output:
message.append('and output:\n')
message.extend('- %s\n' % line for line in output.splitlines())
else:
message.append('and no output.')
message = ''.join(message)
super(AdbCommandFailedError, self).__init__(message, device_serial)
class DeviceVersionError(CommandFailedError):
"""Exception for device version failures."""
def __init__(self, message, device_serial=None):
super(DeviceVersionError, self).__init__(message, device_serial)
class AdbShellCommandFailedError(AdbCommandFailedError):
"""Exception for shell command failures run via adb."""
def __init__(self, command, output, status, device_serial=None):
self.command = command
message = ['shell command run via adb failed on the device:\n',
' command: %s\n' % command]
message.append(' exit status: %s\n' % status)
if output:
message.append(' output:\n')
if isinstance(output, basestring):
output_lines = output.splitlines()
else:
output_lines = output
message.extend(' - %s\n' % line for line in output_lines)
else:
message.append(" output: ''\n")
message = ''.join(message)
super(AdbShellCommandFailedError, self).__init__(
['shell', command], output, status, device_serial, message)
class CommandTimeoutError(base_error.BaseError):
"""Exception for command timeouts."""
pass
class DeviceUnreachableError(base_error.BaseError):
"""Exception for device unreachable failures."""
pass
class NoDevicesError(base_error.BaseError):
"""Exception for having no devices attached."""
def __init__(self):
super(NoDevicesError, self).__init__(
'No devices attached.', is_infra_error=True)
# Copyright 2014 The Chromium Authors. All rights reserved.
# Use of this source code is governed by a BSD-style license that can be
# found in the LICENSE file.
"""A module to keep track of devices across builds."""
import os
LAST_DEVICES_FILENAME = '.last_devices'
LAST_MISSING_DEVICES_FILENAME = '.last_missing'
def GetPersistentDeviceList(file_name):
"""Returns a list of devices.
Args:
file_name: the file name containing a list of devices.
Returns: List of device serial numbers that were on the bot.
"""
with open(file_name) as f:
return f.read().splitlines()
def WritePersistentDeviceList(file_name, device_list):
path = os.path.dirname(file_name)
if not os.path.exists(path):
os.makedirs(path)
with open(file_name, 'w') as f:
f.write('\n'.join(set(device_list)))
# Copyright 2015 The Chromium Authors. All rights reserved.
# Use of this source code is governed by a BSD-style license that can be
# found in the LICENSE file.
"""Defines constants for signals that should be supported on devices.
Note: Obtained by running `kill -l` on a user device.
"""
SIGHUP = 1 # Hangup
SIGINT = 2 # Interrupt
SIGQUIT = 3 # Quit
SIGILL = 4 # Illegal instruction
SIGTRAP = 5 # Trap
SIGABRT = 6 # Aborted
SIGBUS = 7 # Bus error
SIGFPE = 8 # Floating point exception
SIGKILL = 9 # Killed
SIGUSR1 = 10 # User signal 1
SIGSEGV = 11 # Segmentation fault
SIGUSR2 = 12 # User signal 2
SIGPIPE = 13 # Broken pipe
SIGALRM = 14 # Alarm clock
SIGTERM = 15 # Terminated
SIGSTKFLT = 16 # Stack fault
SIGCHLD = 17 # Child exited
SIGCONT = 18 # Continue
SIGSTOP = 19 # Stopped (signal)
SIGTSTP = 20 # Stopped
SIGTTIN = 21 # Stopped (tty input)
SIGTTOU = 22 # Stopped (tty output)
SIGURG = 23 # Urgent I/O condition
SIGXCPU = 24 # CPU time limit exceeded
SIGXFSZ = 25 # File size limit exceeded
SIGVTALRM = 26 # Virtual timer expired
SIGPROF = 27 # Profiling timer expired
SIGWINCH = 28 # Window size changed
SIGIO = 29 # I/O possible
SIGPWR = 30 # Power failure
SIGSYS = 31 # Bad system call
# Copyright 2013 The Chromium Authors. All rights reserved.
# Use of this source code is governed by a BSD-style license that can be
# found in the LICENSE file.
"""A temp file that automatically gets pushed and deleted from a device."""
# pylint: disable=W0622
import threading
from devil.android import device_errors
from devil.utils import cmd_helper
_COMMAND_TEMPLATE = (
# Make sure that the temp dir is writable
'test -d {dir} && '
# If 5 random attempts fail, something is up.
'for i in 1 2 3 4 5; do '
'fn={dir}/{prefix}-$(date +%s)-"$RANDOM"{suffix};'
'test -e "$fn" || break;'
'done && '
# Touch the file, so other temp files can't get the same name.
'touch "$fn" && echo -n "$fn"')
class DeviceTempFile(object):
def __init__(self, adb, suffix='', prefix='temp_file', dir='/data/local/tmp'):
"""Find an unused temporary file path in the devices external directory.
When this object is closed, the file will be deleted on the device.
Args:
adb: An instance of AdbWrapper
suffix: The suffix of the name of the temp file.
prefix: The prefix of the name of the temp file.
dir: The directory on the device where to place the temp file.
"""
self._adb = adb
command = _COMMAND_TEMPLATE.format(
dir=cmd_helper.SingleQuote(dir),
suffix=cmd_helper.SingleQuote(suffix),
prefix=cmd_helper.SingleQuote(prefix))
self.name = self._adb.Shell(command)
self.name_quoted = cmd_helper.SingleQuote(self.name)
def close(self):
"""Deletes the temporary file from the device."""
# ignore exception if the file is already gone.
def helper():
try:
self._adb.Shell('rm -f %s' % self.name_quoted, expect_status=None)
except device_errors.AdbCommandFailedError:
# file does not exist on Android version without 'rm -f' support (ICS)
pass
# It shouldn't matter when the temp file gets deleted, so do so
# asynchronously.
threading.Thread(target=helper).start()
def __enter__(self):
return self
def __exit__(self, type, value, traceback):
self.close()
...@@ -12,11 +12,11 @@ import os ...@@ -12,11 +12,11 @@ import os
import sys import sys
import unittest import unittest
from devil.android import device_errors
from devil.android.sdk import adb_wrapper
from devil.utils import device_temp_file
from devil.utils import mock_calls
from pylib import constants from pylib import constants
from pylib.device import adb_wrapper
from pylib.device import device_errors
from pylib.utils import device_temp_file
from pylib.utils import mock_calls
sys.path.append(os.path.join( sys.path.append(os.path.join(
constants.DIR_SOURCE_ROOT, 'third_party', 'pymock')) constants.DIR_SOURCE_ROOT, 'third_party', 'pymock'))
......
This diff is collapsed.
...@@ -12,11 +12,11 @@ import os ...@@ -12,11 +12,11 @@ import os
import tempfile import tempfile
import unittest import unittest
from pylib import cmd_helper
from pylib import constants from pylib import constants
from pylib.device import adb_wrapper from devil.android import device_utils
from pylib.device import device_utils from devil.android.sdk import adb_wrapper
from pylib.utils import md5sum from devil.utils import md5sum
from devil.utils import cmd_helper
_OLD_CONTENTS = "foo" _OLD_CONTENTS = "foo"
_NEW_CONTENTS = "bar" _NEW_CONTENTS = "bar"
......
# Copyright 2015 The Chromium Authors. All rights reserved.
# Use of this source code is governed by a BSD-style license that can be
# found in the LICENSE file.
# pylint: disable=unused-argument
import collections
import itertools
import logging
import subprocess
import tempfile
import time
import re
from devil.android import decorators
from devil.android import device_errors
from devil.android.sdk import adb_wrapper
class LogcatMonitor(object):
_THREADTIME_RE_FORMAT = (
r'(?P<date>\S*) +(?P<time>\S*) +(?P<proc_id>%s) +(?P<thread_id>%s) +'
r'(?P<log_level>%s) +(?P<component>%s) *: +(?P<message>%s)$')
def __init__(self, adb, clear=True, filter_specs=None):
"""Create a LogcatMonitor instance.
Args:
adb: An instance of adb_wrapper.AdbWrapper.
clear: If True, clear the logcat when monitoring starts.
filter_specs: An optional list of '<tag>[:priority]' strings.
"""
if isinstance(adb, adb_wrapper.AdbWrapper):
self._adb = adb
else:
raise ValueError('Unsupported type passed for argument "device"')
self._clear = clear
self._filter_specs = filter_specs
self._logcat_out = None
self._logcat_out_file = None
self._logcat_proc = None
@decorators.WithTimeoutAndRetriesDefaults(10, 0)
def WaitFor(self, success_regex, failure_regex=None, timeout=None,
retries=None):
"""Wait for a matching logcat line or until a timeout occurs.
This will attempt to match lines in the logcat against both |success_regex|
and |failure_regex| (if provided). Note that this calls re.search on each
logcat line, not re.match, so the provided regular expressions don't have
to match an entire line.
Args:
success_regex: The regular expression to search for.
failure_regex: An optional regular expression that, if hit, causes this
to stop looking for a match. Can be None.
timeout: timeout in seconds
retries: number of retries
Returns:
A match object if |success_regex| matches a part of a logcat line, or
None if |failure_regex| matches a part of a logcat line.
Raises:
CommandFailedError on logcat failure (NOT on a |failure_regex| match).
CommandTimeoutError if no logcat line matching either |success_regex| or
|failure_regex| is found in |timeout| seconds.
DeviceUnreachableError if the device becomes unreachable.
"""
if isinstance(success_regex, basestring):
success_regex = re.compile(success_regex)
if isinstance(failure_regex, basestring):
failure_regex = re.compile(failure_regex)
logging.debug('Waiting %d seconds for "%s"', timeout, success_regex.pattern)
# NOTE This will continue looping until:
# - success_regex matches a line, in which case the match object is
# returned.
# - failure_regex matches a line, in which case None is returned
# - the timeout is hit, in which case a CommandTimeoutError is raised.
for l in self._adb.Logcat(filter_specs=self._filter_specs):
m = success_regex.search(l)
if m:
return m
if failure_regex and failure_regex.search(l):
return None
def FindAll(self, message_regex, proc_id=None, thread_id=None, log_level=None,
component=None):
"""Finds all lines in the logcat that match the provided constraints.
Args:
message_regex: The regular expression that the <message> section must
match.
proc_id: The process ID to match. If None, matches any process ID.
thread_id: The thread ID to match. If None, matches any thread ID.
log_level: The log level to match. If None, matches any log level.
component: The component to match. If None, matches any component.
Yields:
A match object for each matching line in the logcat. The match object
will always contain, in addition to groups defined in |message_regex|,
the following named groups: 'date', 'time', 'proc_id', 'thread_id',
'log_level', 'component', and 'message'.
"""
if proc_id is None:
proc_id = r'\d+'
if thread_id is None:
thread_id = r'\d+'
if log_level is None:
log_level = r'[VDIWEF]'
if component is None:
component = r'[^\s:]+'
threadtime_re = re.compile(
type(self)._THREADTIME_RE_FORMAT % (
proc_id, thread_id, log_level, component, message_regex))
for line in self._adb.Logcat(dump=True, logcat_format='threadtime'):
m = re.match(threadtime_re, line)
if m:
yield m
def Start(self):
"""Starts the logcat monitor.
Clears the logcat if |clear| was set in |__init__|.
"""
if self._clear:
self._adb.Logcat(clear=True)
def __enter__(self):
"""Starts the logcat monitor."""
self.Start()
return self
def __exit__(self, exc_type, exc_val, exc_tb):
"""Stops the logcat monitor."""
pass
...@@ -8,10 +8,10 @@ import os ...@@ -8,10 +8,10 @@ import os
import sys import sys
import unittest import unittest
from devil.android import decorators
from devil.android import logcat_monitor
from devil.android.sdk import adb_wrapper
from pylib import constants from pylib import constants
from pylib.device import adb_wrapper
from pylib.device import decorators
from pylib.device import logcat_monitor
sys.path.append(os.path.join( sys.path.append(os.path.join(
constants.DIR_SOURCE_ROOT, 'third_party', 'pymock')) constants.DIR_SOURCE_ROOT, 'third_party', 'pymock'))
......
# Copyright 2014 The Chromium Authors. All rights reserved.
# Use of this source code is governed by a BSD-style license that can be
# found in the LICENSE file.
import collections
import logging
import os
import posixpath
import re
import tempfile
import types
from devil.android import device_errors
from devil.android import device_temp_file
from devil.utils import cmd_helper
from pylib import constants
MD5SUM_DEVICE_LIB_PATH = '/data/local/tmp/md5sum/'
MD5SUM_DEVICE_BIN_PATH = MD5SUM_DEVICE_LIB_PATH + 'md5sum_bin'
_STARTS_WITH_CHECKSUM_RE = re.compile(r'^\s*[0-9a-fA-F]{32}\s+')
def CalculateHostMd5Sums(paths):
"""Calculates the MD5 sum value for all items in |paths|.
Directories are traversed recursively and the MD5 sum of each file found is
reported in the result.
Args:
paths: A list of host paths to md5sum.
Returns:
A dict mapping file paths to their respective md5sum checksums.
"""
if isinstance(paths, basestring):
paths = [paths]
md5sum_bin_host_path = os.path.join(
constants.GetOutDirectory(), 'md5sum_bin_host')
if not os.path.exists(md5sum_bin_host_path):
raise IOError('File not built: %s' % md5sum_bin_host_path)
out = cmd_helper.GetCmdOutput([md5sum_bin_host_path] + [p for p in paths])
return _ParseMd5SumOutput(out.splitlines())
def CalculateDeviceMd5Sums(paths, device):
"""Calculates the MD5 sum value for all items in |paths|.
Directories are traversed recursively and the MD5 sum of each file found is
reported in the result.
Args:
paths: A list of device paths to md5sum.
Returns:
A dict mapping file paths to their respective md5sum checksums.
"""
if not paths:
return {}
if isinstance(paths, basestring):
paths = [paths]
# Allow generators
paths = list(paths)
md5sum_dist_path = os.path.join(constants.GetOutDirectory(), 'md5sum_dist')
md5sum_dist_bin_path = os.path.join(md5sum_dist_path, 'md5sum_bin')
if not os.path.exists(md5sum_dist_path):
raise IOError('File not built: %s' % md5sum_dist_path)
md5sum_file_size = os.path.getsize(md5sum_dist_bin_path)
# For better performance, make the script as small as possible to try and
# avoid needing to write to an intermediary file (which RunShellCommand will
# do if necessary).
md5sum_script = 'a=%s;' % MD5SUM_DEVICE_BIN_PATH
# Check if the binary is missing or has changed (using its file size as an
# indicator), and trigger a (re-)push via the exit code.
md5sum_script += '! [[ $(ls -l $a) = *%d* ]]&&exit 2;' % md5sum_file_size
# Make sure it can find libbase.so
md5sum_script += 'export LD_LIBRARY_PATH=%s;' % MD5SUM_DEVICE_LIB_PATH
if len(paths) > 1:
prefix = posixpath.commonprefix(paths)
if len(prefix) > 4:
md5sum_script += 'p="%s";' % prefix
paths = ['$p"%s"' % p[len(prefix):] for p in paths]
md5sum_script += ';'.join('$a %s' % p for p in paths)
# Don't fail the script if the last md5sum fails (due to file not found)
# Note: ":" is equivalent to "true".
md5sum_script += ';:'
try:
out = device.RunShellCommand(md5sum_script, check_return=True)
except device_errors.AdbShellCommandFailedError as e:
# Push the binary only if it is found to not exist
# (faster than checking up-front).
if e.status == 2:
# If files were previously pushed as root (adbd running as root), trying
# to re-push as non-root causes the push command to report success, but
# actually fail. So, wipe the directory first.
device.RunShellCommand(['rm', '-rf', MD5SUM_DEVICE_LIB_PATH],
as_root=True, check_return=True)
device.adb.Push(md5sum_dist_path, MD5SUM_DEVICE_LIB_PATH)
out = device.RunShellCommand(md5sum_script, check_return=True)
else:
raise
return _ParseMd5SumOutput(out)
def _ParseMd5SumOutput(out):
hash_and_path = (l.split(None, 1) for l in out
if l and _STARTS_WITH_CHECKSUM_RE.match(l))
return dict((p, h) for h, p in hash_and_path)
...@@ -7,10 +7,10 @@ import os ...@@ -7,10 +7,10 @@ import os
import sys import sys
import unittest import unittest
from pylib import cmd_helper from devil.android import device_errors
from devil.android import md5sum
from devil.utils import cmd_helper
from pylib import constants from pylib import constants
from pylib.device import device_errors
from pylib.utils import md5sum
sys.path.append( sys.path.append(
os.path.join(constants.DIR_SOURCE_ROOT, 'third_party', 'pymock')) os.path.join(constants.DIR_SOURCE_ROOT, 'third_party', 'pymock'))
...@@ -39,7 +39,8 @@ class Md5SumTest(unittest.TestCase): ...@@ -39,7 +39,8 @@ class Md5SumTest(unittest.TestCase):
test_path = '/test/host/file.dat' test_path = '/test/host/file.dat'
mock_get_cmd_output = mock.Mock( mock_get_cmd_output = mock.Mock(
return_value='0123456789abcdeffedcba9876543210 /test/host/file.dat') return_value='0123456789abcdeffedcba9876543210 /test/host/file.dat')
with mock.patch('pylib.cmd_helper.GetCmdOutput', new=mock_get_cmd_output): with mock.patch('devil.utils.cmd_helper.GetCmdOutput',
new=mock_get_cmd_output):
out = md5sum.CalculateHostMd5Sums(test_path) out = md5sum.CalculateHostMd5Sums(test_path)
self.assertEquals(1, len(out)) self.assertEquals(1, len(out))
self.assertTrue('/test/host/file.dat' in out) self.assertTrue('/test/host/file.dat' in out)
...@@ -53,7 +54,8 @@ class Md5SumTest(unittest.TestCase): ...@@ -53,7 +54,8 @@ class Md5SumTest(unittest.TestCase):
mock_get_cmd_output = mock.Mock( mock_get_cmd_output = mock.Mock(
return_value='0123456789abcdeffedcba9876543210 /test/host/file0.dat\n' return_value='0123456789abcdeffedcba9876543210 /test/host/file0.dat\n'
'123456789abcdef00fedcba987654321 /test/host/file1.dat\n') '123456789abcdef00fedcba987654321 /test/host/file1.dat\n')
with mock.patch('pylib.cmd_helper.GetCmdOutput', new=mock_get_cmd_output): with mock.patch('devil.utils.cmd_helper.GetCmdOutput',
new=mock_get_cmd_output):
out = md5sum.CalculateHostMd5Sums(test_paths) out = md5sum.CalculateHostMd5Sums(test_paths)
self.assertEquals(2, len(out)) self.assertEquals(2, len(out))
self.assertTrue('/test/host/file0.dat' in out) self.assertTrue('/test/host/file0.dat' in out)
...@@ -71,7 +73,8 @@ class Md5SumTest(unittest.TestCase): ...@@ -71,7 +73,8 @@ class Md5SumTest(unittest.TestCase):
mock_get_cmd_output = mock.Mock( mock_get_cmd_output = mock.Mock(
return_value='0123456789abcdeffedcba9876543210 /test/host/file0.dat\n' return_value='0123456789abcdeffedcba9876543210 /test/host/file0.dat\n'
'123456789abcdef00fedcba987654321 /test/host/file1.dat\n') '123456789abcdef00fedcba987654321 /test/host/file1.dat\n')
with mock.patch('pylib.cmd_helper.GetCmdOutput', new=mock_get_cmd_output): with mock.patch('devil.utils.cmd_helper.GetCmdOutput',
new=mock_get_cmd_output):
out = md5sum.CalculateHostMd5Sums(test_paths) out = md5sum.CalculateHostMd5Sums(test_paths)
self.assertEquals(2, len(out)) self.assertEquals(2, len(out))
self.assertTrue('/test/host/file0.dat' in out) self.assertTrue('/test/host/file0.dat' in out)
......
# Copyright 2015 The Chromium Authors. All rights reserved.
# Use of this source code is governed by a BSD-style license that can be
# found in the LICENSE file.
# This package is intended for modules that are very tightly coupled to
# tools or APIs from the Android SDK.
# Copyright 2015 The Chromium Authors. All rights reserved.
# Use of this source code is governed by a BSD-style license that can be
# found in the LICENSE file.
"""This module wraps the Android Asset Packaging Tool."""
import os
from devil.utils import cmd_helper
from devil.utils import timeout_retry
from pylib import constants
_AAPT_PATH = os.path.join(constants.ANDROID_SDK_TOOLS, 'aapt')
def _RunAaptCmd(args):
"""Runs an aapt command.
Args:
args: A list of arguments for aapt.
Returns:
The output of the command.
"""
cmd = [_AAPT_PATH] + args
status, output = cmd_helper.GetCmdStatusAndOutput(cmd)
if status != 0:
raise Exception('Failed running aapt command: "%s" with output "%s".' %
(' '.join(cmd), output))
return output
def Dump(what, apk, assets=None):
"""Returns the output of the aapt dump command.
Args:
what: What you want to dump.
apk: Path to apk you want to dump information for.
assets: List of assets in apk you want to dump information for.
"""
assets = assets or []
if isinstance(assets, basestring):
assets = [assets]
return _RunAaptCmd(['dump', what, apk] + assets).splitlines()
This diff is collapsed.
...@@ -9,8 +9,8 @@ import tempfile ...@@ -9,8 +9,8 @@ import tempfile
import time import time
import unittest import unittest
from pylib.device import adb_wrapper from devil.android import device_errors
from pylib.device import device_errors from devil.android.sdk import adb_wrapper
class TestAdbWrapper(unittest.TestCase): class TestAdbWrapper(unittest.TestCase):
......
# Copyright 2015 The Chromium Authors. All rights reserved.
# Use of this source code is governed by a BSD-style license that can be
# found in the LICENSE file.
import os
from devil.utils import cmd_helper
from pylib import constants
_DEXDUMP_PATH = os.path.join(constants.ANDROID_SDK_TOOLS, 'dexdump')
def DexDump(dexfiles, file_summary=False):
"""A wrapper around the Android SDK's dexdump tool.
Args:
dexfiles: The dexfile or list of dex files to dump.
file_summary: Display summary information from the file header. (-f)
Returns:
An iterable over the output lines.
"""
# TODO(jbudorick): Add support for more options as necessary.
if isinstance(dexfiles, basestring):
dexfiles = [dexfiles]
args = [_DEXDUMP_PATH] + dexfiles
if file_summary:
args.append('-f')
return cmd_helper.IterCmdOutputLines(args)
# Copyright 2014 The Chromium Authors. All rights reserved.
# Use of this source code is governed by a BSD-style license that can be
# found in the LICENSE file.
"""Manages intents and associated information.
This is generally intended to be used with functions that calls Android's
Am command.
"""
class Intent(object):
def __init__(self, action='android.intent.action.VIEW', activity=None,
category=None, component=None, data=None, extras=None,
flags=None, package=None):
"""Creates an Intent.
Args:
action: A string containing the action.
activity: A string that, with |package|, can be used to specify the
component.
category: A string or list containing any categories.
component: A string that specifies the component to send the intent to.
data: A string containing a data URI.
extras: A dict containing extra parameters to be passed along with the
intent.
flags: A string containing flags to pass.
package: A string that, with activity, can be used to specify the
component.
"""
self._action = action
self._activity = activity
if isinstance(category, list) or category is None:
self._category = category
else:
self._category = [category]
self._component = component
self._data = data
self._extras = extras
self._flags = flags
self._package = package
if self._component and '/' in component:
self._package, self._activity = component.split('/', 1)
elif self._package and self._activity:
self._component = '%s/%s' % (package, activity)
@property
def action(self):
return self._action
@property
def activity(self):
return self._activity
@property
def category(self):
return self._category
@property
def component(self):
return self._component
@property
def data(self):
return self._data
@property
def extras(self):
return self._extras
@property
def flags(self):
return self._flags
@property
def package(self):
return self._package
@property
def am_args(self):
"""Returns the intent as a list of arguments for the activity manager.
For details refer to the specification at:
- http://developer.android.com/tools/help/adb.html#IntentSpec
"""
args = []
if self.action:
args.extend(['-a', self.action])
if self.data:
args.extend(['-d', self.data])
if self.category:
args.extend(arg for cat in self.category for arg in ('-c', cat))
if self.component:
args.extend(['-n', self.component])
if self.flags:
args.extend(['-f', self.flags])
if self.extras:
for key, value in self.extras.iteritems():
if value is None:
args.extend(['--esn', key])
elif isinstance(value, str):
args.extend(['--es', key, value])
elif isinstance(value, bool):
args.extend(['--ez', key, str(value)])
elif isinstance(value, int):
args.extend(['--ei', key, str(value)])
elif isinstance(value, float):
args.extend(['--ef', key, str(value)])
else:
raise NotImplementedError(
'Intent does not know how to pass %s extras' % type(value))
return args
This diff is collapsed.
...@@ -12,9 +12,9 @@ import os ...@@ -12,9 +12,9 @@ import os
import sys import sys
import unittest import unittest
from devil.android import device_utils
from devil.android.sdk import shared_prefs
from pylib import constants from pylib import constants
from pylib.device import device_utils
from pylib.device import shared_prefs
sys.path.append(os.path.join( sys.path.append(os.path.join(
constants.DIR_SOURCE_ROOT, 'third_party', 'pymock')) constants.DIR_SOURCE_ROOT, 'third_party', 'pymock'))
......
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
...@@ -7,7 +7,7 @@ ...@@ -7,7 +7,7 @@
import unittest import unittest
import subprocess import subprocess
from pylib import cmd_helper from devil.utils import cmd_helper
class CmdHelperSingleQuoteTest(unittest.TestCase): class CmdHelperSingleQuoteTest(unittest.TestCase):
......
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
Markdown is supported
0%
or
You are about to add 0 people to the discussion. Proceed with caution.
Finish editing this message first!
Please register or to comment