Skip to content

Commit

Permalink
Android: Store test data in an app's data directory
Browse files Browse the repository at this point in the history
This CL adds the --store-data-in-app-directory android test harness.
Android versions R+ now have scoped storage, meaning apps cannot
access all data on the external storage drive. Therefore we will
have to store test data in the test app's data directory.

Bug: 1324649
Change-Id: Ia539865de6e187efe23c048e9da21f3030c2593e
Reviewed-on: https://chromium-review.googlesource.com/c/chromium/src/+/3708044
Reviewed-by: Richard Coles <torne@chromium.org>
Reviewed-by: Peter Wen <wnwen@chromium.org>
Commit-Queue: Rakib Hasan <rmhasan@google.com>
Cr-Commit-Position: refs/heads/main@{#1031519}
  • Loading branch information
Rakib M. Hasan authored and Chromium LUCI CQ committed Aug 4, 2022
1 parent c7f505f commit eeec896
Show file tree
Hide file tree
Showing 6 changed files with 92 additions and 18 deletions.
Original file line number Diff line number Diff line change
Expand Up @@ -638,6 +638,9 @@ private static void clearDataDirectory(Context targetContext) {
if (file.getName().equals("lib")) {
continue;
}
if (file.getName().equals("chromium_tests_root")) {
continue;
}
if (file.getName().equals("incremental-install-files")) {
continue;
}
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -606,6 +606,7 @@ def __init__(self, args, data_deps_delegate, error_func):
self._data_deps = None
self._data_deps_delegate = None
self._runtime_deps_path = None
self._store_data_in_app_directory = False
self._initializeDataDependencyAttributes(args, data_deps_delegate)

self._annotations = None
Expand Down Expand Up @@ -782,7 +783,7 @@ def _initializeDataDependencyAttributes(self, args, data_deps_delegate):
self._data_deps = []
self._data_deps_delegate = data_deps_delegate
self._runtime_deps_path = args.runtime_deps_path

self._store_data_in_app_directory = args.store_data_in_app_directory
if not self._runtime_deps_path:
logging.warning('No data dependencies will be pushed.')

Expand Down Expand Up @@ -1001,6 +1002,10 @@ def screenshot_dir(self):
def skia_gold_properties(self):
return self._skia_gold_properties

@property
def store_data_in_app_directory(self):
return self._store_data_in_app_directory

@property
def store_tombstones(self):
return self._store_tombstones
Expand Down
12 changes: 9 additions & 3 deletions build/android/pylib/local/device/local_device_environment.py
Original file line number Diff line number Diff line change
Expand Up @@ -84,7 +84,7 @@ def wrapper(dev, *args, **kwargs):
return decorator


def place_nomedia_on_device(dev, device_root):
def place_nomedia_on_device(dev, device_root, run_as=None, as_root=False):
"""Places .nomedia file in test data root.
This helps to prevent system from scanning media files inside test data.
Expand All @@ -94,8 +94,14 @@ def place_nomedia_on_device(dev, device_root):
device_root: Base path on device to place .nomedia file.
"""

dev.RunShellCommand(['mkdir', '-p', device_root], check_return=True)
dev.WriteFile('%s/.nomedia' % device_root, 'https://crbug.com/796640')
dev.RunShellCommand(['mkdir', '-p', device_root],
run_as=run_as,
as_root=as_root,
check_return=True)
dev.WriteFile('%s/.nomedia' % device_root,
'https://crbug.com/796640',
run_as=run_as,
as_root=as_root)


# TODO(1262303): After Telemetry is supported by python3 we can re-add
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -191,14 +191,22 @@ def __init__(self, env, test_instance):
self._shared_prefs_to_restore = []
self._skia_gold_session_manager = None
self._skia_gold_work_dir = None
self._target_package = _GetTargetPackageName(test_instance.test_apk)

#override
def TestPackage(self):
return self._test_instance.suite

def _GetDataStorageRootDirectory(self, device):
if self._test_instance.store_data_in_app_directory:
# TODO(rmhasan): Add check to makes sure api level > 27. Selinux
# policy on Oreo does not allow app to read files from app data dir
# that were not put there by the app.
return device.GetApplicationDataDirectory(self._target_package)
return device.GetExternalStoragePath()

#override
def SetUp(self):
target_package = _GetTargetPackageName(self._test_instance.test_apk)

@local_device_environment.handle_shard_failures_with(
self._env.DenylistDevice)
Expand Down Expand Up @@ -383,7 +391,7 @@ def set_debug_app(dev):
cmd = ['am', 'set-debug-app', '--persistent']
if self._test_instance.wait_for_java_debugger:
cmd.append('-w')
cmd.append(target_package)
cmd.append(self._target_package)
dev.RunShellCommand(cmd, check_return=True)

@trace_event.traced
Expand Down Expand Up @@ -418,20 +426,32 @@ def set_vega_permissions(dev):

@instrumentation_tracing.no_tracing
def push_test_data(dev):
device_root = posixpath.join(dev.GetExternalStoragePath(),
'chromium_tests_root')
test_data_root_dir = posixpath.join(
self._GetDataStorageRootDirectory(dev), 'chromium_tests_root')
host_device_tuples_substituted = [
(h, local_device_test_run.SubstituteDeviceRoot(d, device_root))
for h, d in host_device_tuples]
(h,
local_device_test_run.SubstituteDeviceRoot(d, test_data_root_dir))
for h, d in host_device_tuples
]
logging.info('Pushing data dependencies.')
for h, d in host_device_tuples_substituted:
logging.debug(' %r -> %r', h, d)
local_device_environment.place_nomedia_on_device(dev, device_root)

as_root = self._test_instance.store_data_in_app_directory
local_device_environment.place_nomedia_on_device(dev,
test_data_root_dir,
as_root=as_root)
dev.PushChangedFiles(host_device_tuples_substituted,
delete_device_stale=True)
delete_device_stale=True,
as_root=as_root)

if not host_device_tuples_substituted:
dev.RunShellCommand(['rm', '-rf', device_root], check_return=True)
dev.RunShellCommand(['mkdir', '-p', device_root], check_return=True)
dev.RunShellCommand(['rm', '-rf', test_data_root_dir],
check_return=True,
as_root=as_root)
dev.RunShellCommand(['mkdir', '-p', test_data_root_dir],
check_return=True,
as_root=as_root)

@trace_event.traced
def create_flag_changer(dev):
Expand Down Expand Up @@ -493,7 +513,7 @@ def bind_crash_handler(step, dev):
if self._test_instance.wait_for_java_debugger:
logging.warning('*' * 80)
logging.warning('Waiting for debugger to attach to process: %s',
target_package)
self._target_package)
logging.warning('*' * 80)

#override
Expand Down Expand Up @@ -798,6 +818,9 @@ def name_and_timeout(t):
self._CreateFlagChangerIfNeeded(device)
self._flag_changers[str(device)].PushFlags(add=flags_to_add)

if self._test_instance.store_data_in_app_directory:
extras.update({'fetchTestDataFromAppDataDir': 'true'})

time_ms = lambda: int(time.time() * 1e3)
start_ms = time_ms()

Expand Down Expand Up @@ -1084,11 +1107,15 @@ def _GetTestsFromRunner(self):
logging.info('Could not get tests from pickle: %s', e)
logging.info('Getting tests by having %s list them.',
self._test_instance.junit4_runner_class)
# We need to use GetAppWritablePath instead of GetExternalStoragePath
# here because we will not have applied legacy storage workarounds on R+
# yet.
# TODO(rmhasan): Figure out how to create the temp file inside the test
# app's data directory. Currently when the temp file is created read
# permissions are only given to the app's user id. Therefore we can't
# pull the file from the device.
def list_tests(d):
def _run(dev):
# We need to use GetAppWritablePath instead of GetExternalStoragePath
# here because we will not have applied legacy storage workarounds on R+
# yet.
with device_temp_file.DeviceTempFile(
dev.adb, suffix='.json',
dir=dev.GetAppWritablePath()) as dev_test_list_json:
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -9,6 +9,7 @@


import unittest
import mock # pylint: disable=import-error

from pylib.base import base_test_result
from pylib.base import mock_environment
Expand Down Expand Up @@ -164,6 +165,33 @@ def testReplaceUncommonChars(self):
with self.assertRaises(ValueError):
local_device_instrumentation_test_run._ReplaceUncommonChars(original)

def testStoreDataInAppDir(self):
env = mock.MagicMock()
test_instance = mock.MagicMock()
test_instance.store_data_in_app_directory = True
device = mock.MagicMock()

device.GetApplicationDataDirectory.return_value = 'app_dir'
device.GetExternalStoragePath.return_value = 'external_dir'
test_run = (
local_device_instrumentation_test_run.LocalDeviceInstrumentationTestRun(
env, test_instance))
self.assertEqual(test_run._GetDataStorageRootDirectory(device), 'app_dir')

def testStoreDataInExternalDir(self):
env = mock.MagicMock()
test_instance = mock.MagicMock()
test_instance.store_data_in_app_directory = False
device = mock.MagicMock()

device.GetApplicationDataDirectory.return_value = 'app_dir'
device.GetExternalStoragePath.return_value = 'external_dir'
test_run = (
local_device_instrumentation_test_run.LocalDeviceInstrumentationTestRun(
env, test_instance))
self.assertEqual(test_run._GetDataStorageRootDirectory(device),
'external_dir')


if __name__ == '__main__':
unittest.main(verbosity=2)
5 changes: 5 additions & 0 deletions build/android/test_runner.py
Original file line number Diff line number Diff line change
Expand Up @@ -471,6 +471,11 @@ def AddInstrumentationTestOptions(parser):
parser.add_argument(
'--apk-under-test',
help='Path or name of the apk under test.')
parser.add_argument(
'--store-data-in-app-directory',
action='store_true',
help='Store test data in the application\'s data directory. By default '
'the test data is stored in the external storage folder.')
parser.add_argument(
'--module',
action='append',
Expand Down

0 comments on commit eeec896

Please sign in to comment.