Commit 5ee45898 authored by jbudorick's avatar jbudorick Committed by Commit bot

[Android] Allow gtests to pull app data off the device before clearing it.

The test runner clears application data before each test it runs, as well as
after it has run all tests. However, some suites drop useful files in the
application data directory. This provides a mechanism for retrieving those
files from the device.

BUG=489713

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

Cr-Commit-Position: refs/heads/master@{#333768}
parent 6ba2e639
...@@ -363,6 +363,28 @@ class DeviceUtils(object): ...@@ -363,6 +363,28 @@ class DeviceUtils(object):
str(self)) str(self))
return output[len('package:'):] return output[len('package:'):]
@decorators.WithTimeoutAndRetriesFromInstance()
def GetApplicationDataDirectory(self, package, timeout=None, retries=None):
"""Get the data directory on the device for the given package.
Args:
package: Name of the package.
Returns:
The package's data directory, or None if the package doesn't exist on the
device.
"""
try:
output = self._RunPipedShellCommand(
'pm dump %s | grep dataDir=' % cmd_helper.SingleQuote(package))
for line in output:
_, _, dataDir = line.partition('dataDir=')
if dataDir:
return dataDir
except device_errors.CommandFailedError:
logging.exception('Could not find data directory for %s', package)
return None
@decorators.WithTimeoutAndRetriesFromInstance() @decorators.WithTimeoutAndRetriesFromInstance()
def WaitUntilFullyBooted(self, wifi=False, timeout=None, retries=None): def WaitUntilFullyBooted(self, wifi=False, timeout=None, retries=None):
"""Wait for the device to fully boot. """Wait for the device to fully boot.
......
...@@ -336,6 +336,25 @@ class DeviceUtilsGetApplicationPathTest(DeviceUtilsTest): ...@@ -336,6 +336,25 @@ class DeviceUtilsGetApplicationPathTest(DeviceUtilsTest):
self.device.GetApplicationPath('android') self.device.GetApplicationPath('android')
class DeviceUtilsGetApplicationDataDirectoryTest(DeviceUtilsTest):
def testGetApplicationDataDirectory_exists(self):
with self.assertCall(
self.call.device._RunPipedShellCommand(
'pm dump foo.bar.baz | grep dataDir='),
['dataDir=/data/data/foo.bar.baz']):
self.assertEquals(
'/data/data/foo.bar.baz',
self.device.GetApplicationDataDirectory('foo.bar.baz'))
def testGetApplicationDataDirectory_notExists(self):
with self.assertCall(
self.call.device._RunPipedShellCommand(
'pm dump foo.bar.baz | grep dataDir='),
self.ShellError()):
self.assertIsNone(self.device.GetApplicationDataDirectory('foo.bar.baz'))
@mock.patch('time.sleep', mock.Mock()) @mock.patch('time.sleep', mock.Mock())
class DeviceUtilsWaitUntilFullyBootedTest(DeviceUtilsTest): class DeviceUtilsWaitUntilFullyBootedTest(DeviceUtilsTest):
......
...@@ -7,6 +7,7 @@ import os ...@@ -7,6 +7,7 @@ import os
import re import re
import shutil import shutil
import sys import sys
import tempfile
from pylib import constants from pylib import constants
from pylib.base import base_test_result from pylib.base import base_test_result
...@@ -127,6 +128,17 @@ class GtestTestInstance(test_instance.TestInstance): ...@@ -127,6 +128,17 @@ class GtestTestInstance(test_instance.TestInstance):
logging.warning('No isolate file provided. No data deps will be pushed.'); logging.warning('No isolate file provided. No data deps will be pushed.');
self._isolate_delegate = None self._isolate_delegate = None
if args.app_data_files:
self._app_data_files = args.app_data_files
if args.app_data_file_dir:
self._app_data_file_dir = args.app_data_file_dir
else:
self._app_data_file_dir = tempfile.mkdtemp()
logging.critical('Saving app files to %s', self._app_data_file_dir)
else:
self._app_data_files = None
self._app_data_file_dir = None
#override #override
def TestType(self): def TestType(self):
return 'gtest' return 'gtest'
...@@ -231,6 +243,14 @@ class GtestTestInstance(test_instance.TestInstance): ...@@ -231,6 +243,14 @@ class GtestTestInstance(test_instance.TestInstance):
def apk(self): def apk(self):
return self._apk_path return self._apk_path
@property
def app_file_dir(self):
return self._app_data_file_dir
@property
def app_files(self):
return self._app_data_files
@property @property
def exe(self): def exe(self):
return self._exe_path return self._exe_path
......
...@@ -2,9 +2,10 @@ ...@@ -2,9 +2,10 @@
# Use of this source code is governed by a BSD-style license that can be # Use of this source code is governed by a BSD-style license that can be
# found in the LICENSE file. # found in the LICENSE file.
import itertools
import logging import logging
import os import os
import posixpath
from pylib import constants from pylib import constants
from pylib import ports from pylib import ports
...@@ -37,6 +38,20 @@ _SUITE_REQUIRES_TEST_SERVER_SPAWNER = [ ...@@ -37,6 +38,20 @@ _SUITE_REQUIRES_TEST_SERVER_SPAWNER = [
'net_unittests', 'unit_tests' 'net_unittests', 'unit_tests'
] ]
# TODO(jbudorick): Move this inside _ApkDelegate once TestPackageApk is gone.
def PullAppFilesImpl(device, package, files, directory):
device_dir = device.GetApplicationDataDirectory(package)
host_dir = os.path.join(directory, str(device))
for f in files:
device_file = posixpath.join(device_dir, f)
host_file = os.path.join(host_dir, *f.split(posixpath.sep))
host_file_base, ext = os.path.splitext(host_file)
for i in itertools.count():
host_file = '%s_%d%s' % (host_file_base, i, ext)
if not os.path.exists(host_file):
break
device.PullFile(device_file, host_file)
class _ApkDelegate(object): class _ApkDelegate(object):
def __init__(self, apk): def __init__(self, apk):
self._apk = apk self._apk = apk
...@@ -63,6 +78,9 @@ class _ApkDelegate(object): ...@@ -63,6 +78,9 @@ class _ApkDelegate(object):
return device.StartInstrumentation( return device.StartInstrumentation(
self._component, extras=extras, raw=False, **kwargs) self._component, extras=extras, raw=False, **kwargs)
def PullAppFiles(self, device, files, directory):
PullAppFilesImpl(device, self._package, files, directory)
def Clear(self, device): def Clear(self, device):
device.ClearApplicationState(self._package) device.ClearApplicationState(self._package)
...@@ -119,6 +137,9 @@ class _ExeDelegate(object): ...@@ -119,6 +137,9 @@ class _ExeDelegate(object):
env=env, **kwargs) env=env, **kwargs)
return output return output
def PullAppFiles(self, device, files, directory):
pass
def Clear(self, device): def Clear(self, device):
device.KillAll(self._exe_file_name, blocking=True, timeout=30, quiet=True) device.KillAll(self._exe_file_name, blocking=True, timeout=30, quiet=True)
...@@ -199,6 +220,9 @@ class LocalDeviceGtestRun(local_device_test_run.LocalDeviceTestRun): ...@@ -199,6 +220,9 @@ class LocalDeviceGtestRun(local_device_test_run.LocalDeviceTestRun):
device, '--gtest_filter=%s' % test, timeout=900, retries=0) device, '--gtest_filter=%s' % test, timeout=900, retries=0)
for s in self._servers[str(device)]: for s in self._servers[str(device)]:
s.Reset() s.Reset()
if self._test_instance.app_files:
self._delegate.PullAppFiles(device, self._test_instance.app_files,
self._test_instance.app_file_dir)
self._delegate.Clear(device) self._delegate.Clear(device)
# Parse the output. # Parse the output.
......
...@@ -13,4 +13,6 @@ GTestOptions = collections.namedtuple('GTestOptions', [ ...@@ -13,4 +13,6 @@ GTestOptions = collections.namedtuple('GTestOptions', [
'test_arguments', 'test_arguments',
'timeout', 'timeout',
'isolate_file_path', 'isolate_file_path',
'suite_name']) 'suite_name',
'app_data_files',
'app_data_file_dir'])
...@@ -22,7 +22,7 @@ class TestPackage(object): ...@@ -22,7 +22,7 @@ class TestPackage(object):
Args: Args:
device: Instance of DeviceUtils. device: Instance of DeviceUtils.
""" """
raise NotImplementedError('Method must be overriden.') raise NotImplementedError('Method must be overridden.')
def CreateCommandLineFileOnDevice(self, device, test_filter, test_arguments): def CreateCommandLineFileOnDevice(self, device, test_filter, test_arguments):
"""Creates a test runner script and pushes to the device. """Creates a test runner script and pushes to the device.
...@@ -32,7 +32,7 @@ class TestPackage(object): ...@@ -32,7 +32,7 @@ class TestPackage(object):
test_filter: A test_filter flag. test_filter: A test_filter flag.
test_arguments: Additional arguments to pass to the test binary. test_arguments: Additional arguments to pass to the test binary.
""" """
raise NotImplementedError('Method must be overriden.') raise NotImplementedError('Method must be overridden.')
def GetAllTests(self, device): def GetAllTests(self, device):
"""Returns a list of all tests available in the test suite. """Returns a list of all tests available in the test suite.
...@@ -40,7 +40,7 @@ class TestPackage(object): ...@@ -40,7 +40,7 @@ class TestPackage(object):
Args: Args:
device: Instance of DeviceUtils. device: Instance of DeviceUtils.
""" """
raise NotImplementedError('Method must be overriden.') raise NotImplementedError('Method must be overridden.')
def GetGTestReturnCode(self, _device): def GetGTestReturnCode(self, _device):
return None return None
...@@ -54,7 +54,7 @@ class TestPackage(object): ...@@ -54,7 +54,7 @@ class TestPackage(object):
Returns: Returns:
An instance of pexpect spawn class. An instance of pexpect spawn class.
""" """
raise NotImplementedError('Method must be overriden.') raise NotImplementedError('Method must be overridden.')
def Install(self, device): def Install(self, device):
"""Install the test package to the device. """Install the test package to the device.
...@@ -62,5 +62,15 @@ class TestPackage(object): ...@@ -62,5 +62,15 @@ class TestPackage(object):
Args: Args:
device: Instance of DeviceUtils. device: Instance of DeviceUtils.
""" """
raise NotImplementedError('Method must be overriden.') raise NotImplementedError('Method must be overridden.')
def PullAppFiles(self, device, files, directory):
"""Pull application data from the device.
Args:
device: Instance of DeviceUtils.
files: A list of paths relative to the application data directory to
retrieve from the device.
directory: The host directory to which files should be pulled.
"""
raise NotImplementedError('Method must be overridden.')
...@@ -5,8 +5,10 @@ ...@@ -5,8 +5,10 @@
"""Defines TestPackageApk to help run APK-based native tests.""" """Defines TestPackageApk to help run APK-based native tests."""
# pylint: disable=W0212 # pylint: disable=W0212
import itertools
import logging import logging
import os import os
import posixpath
import shlex import shlex
import sys import sys
import tempfile import tempfile
...@@ -18,6 +20,7 @@ from pylib import pexpect ...@@ -18,6 +20,7 @@ from pylib import pexpect
from pylib.device import device_errors from pylib.device import device_errors
from pylib.device import intent from pylib.device import intent
from pylib.gtest import gtest_test_instance from pylib.gtest import gtest_test_instance
from pylib.gtest import local_device_gtest_run
from pylib.gtest.test_package import TestPackage from pylib.gtest.test_package import TestPackage
...@@ -141,3 +144,8 @@ class TestPackageApk(TestPackage): ...@@ -141,3 +144,8 @@ class TestPackageApk(TestPackage):
def Install(self, device): def Install(self, device):
self.tool.CopyFiles(device) self.tool.CopyFiles(device)
device.Install(self.suite_path) device.Install(self.suite_path)
#override
def PullAppFiles(self, device, files, directory):
local_device_gtest_run.PullAppFilesImpl(
device, self._package_info.package, files, directory)
...@@ -157,3 +157,7 @@ class TestPackageExecutable(TestPackage): ...@@ -157,3 +157,7 @@ class TestPackageExecutable(TestPackage):
deps_path = self.suite_path + '_deps' deps_path = self.suite_path + '_deps'
if os.path.isdir(deps_path): if os.path.isdir(deps_path):
device.PushChangedFiles([(deps_path, test_binary_path + '_deps')]) device.PushChangedFiles([(deps_path, test_binary_path + '_deps')])
#override
def PullAppFiles(self, device, files, directory):
pass
...@@ -5,6 +5,7 @@ ...@@ -5,6 +5,7 @@
import logging import logging
import os import os
import re import re
import tempfile
from pylib import pexpect from pylib import pexpect
from pylib import ports from pylib import ports
...@@ -80,6 +81,17 @@ class TestRunner(base_test_runner.BaseTestRunner): ...@@ -80,6 +81,17 @@ class TestRunner(base_test_runner.BaseTestRunner):
else: else:
self._servers = [] self._servers = []
if test_options.app_data_files:
self._app_data_files = test_options.app_data_files
if test_options.app_data_file_dir:
self._app_data_file_dir = test_options.app_data_file_dir
else:
self._app_data_file_dir = tempfile.mkdtemp()
logging.critical('Saving app files to %s', self._app_data_file_dir)
else:
self._app_data_files = None
self._app_data_file_dir = None
#override #override
def InstallTestPackage(self): def InstallTestPackage(self):
self.test_package.Install(self.device) self.test_package.Install(self.device)
...@@ -167,6 +179,9 @@ class TestRunner(base_test_runner.BaseTestRunner): ...@@ -167,6 +179,9 @@ class TestRunner(base_test_runner.BaseTestRunner):
self.device, test, self._test_arguments) self.device, test, self._test_arguments)
test_results = self._ParseTestOutput( test_results = self._ParseTestOutput(
self.test_package.SpawnTestProcess(self.device)) self.test_package.SpawnTestProcess(self.device))
if self._app_data_files:
self.test_package.PullAppFiles(self.device, self._app_data_files,
self._app_data_file_dir)
finally: finally:
for s in self._servers: for s in self._servers:
s.Reset() s.Reset()
......
...@@ -215,6 +215,12 @@ def AddGTestOptions(parser): ...@@ -215,6 +215,12 @@ def AddGTestOptions(parser):
dest='isolate_file_path', dest='isolate_file_path',
help='.isolate file path to override the default ' help='.isolate file path to override the default '
'path') 'path')
group.add_argument('--app-data-file', action='append', dest='app_data_files',
help='A file path relative to the app data directory '
'that should be saved to the host.')
group.add_argument('--app-data-file-dir',
help='Host directory to which app data files will be'
' saved. Used with --app-data-file.')
filter_group = group.add_mutually_exclusive_group() filter_group = group.add_mutually_exclusive_group()
filter_group.add_argument('-f', '--gtest_filter', '--gtest-filter', filter_group.add_argument('-f', '--gtest_filter', '--gtest-filter',
...@@ -640,7 +646,9 @@ def _RunGTests(args, devices): ...@@ -640,7 +646,9 @@ def _RunGTests(args, devices):
args.test_arguments, args.test_arguments,
args.timeout, args.timeout,
args.isolate_file_path, args.isolate_file_path,
suite_name) suite_name,
args.app_data_files,
args.app_data_file_dir)
runner_factory, tests = gtest_setup.Setup(gtest_options, devices) runner_factory, tests = gtest_setup.Setup(gtest_options, devices)
results, test_exit_code = test_dispatcher.RunTests( results, test_exit_code = test_dispatcher.RunTests(
......
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