The purpose here is to provide support for selectively enabling Runtime Resource Overlays (RROs) (specifically those pertaining to a specific SKU, within a OEM's "single build" covering multiple SKUs) at boot based on the value of a pre-defined system property. This mechanism is designed to be compatible with other, recent changes to Runtime Resource Overlays - specifically: - has no effect on 'isStatic'. Resource overlays must be attributed as static in order to qualify for loading into the system_server. The 'requiredSystemPropertyName/ requiredSystemPropertyValue' mechanism operates independent of this and can be used on both static and non static overlays. The effect of specifying a conditional property on any overlay is that it will ONLY be enabled in the event that the system reflects both the property and the specified value (Note that in the ABSENCE of a conditional property, overlays are assumed to be enabled). - has no effect on OverlayManagerService (OMS) API. The OMS provides the system with an interface through which overlays can be enabled/disabled and even rearranged at runtime. This provides the basis of support for various user-level features (e.g. dynamic theme selection). The 'requiredSystemPropertyName/requiredSystemPropertyValue' mechanism operates independent of this - with enablement being completely coupled to the available system properties on the device and NOT subject to change at runtime. Note: as part of this change, original overlay tests have been updated (fixed) and expanded to include tests to cover the conditional property implementation. Issue: http://b/35100249 Test: frameworks/base/core/tests/overlaytests/testrunner.py Change-Id: I1990ce21a27a385db1e2f53294b69dd03988351e
733 lines
26 KiB
Python
Executable File
733 lines
26 KiB
Python
Executable File
#!/usr/bin/python
|
|
import hashlib
|
|
import optparse
|
|
import os
|
|
import re
|
|
import shlex
|
|
import subprocess
|
|
import sys
|
|
import threading
|
|
import time
|
|
|
|
TASK_COMPILATION = 'compile'
|
|
TASK_DISABLE_OVERLAYS = 'disable overlays'
|
|
TASK_ENABLE_MULTIPLE_OVERLAYS = 'enable multiple overlays'
|
|
TASK_ENABLE_SINGLE_OVERLAY = 'enable single overlay'
|
|
TASK_ENABLE_FILTERED_OVERLAYS = 'enable filtered overlays'
|
|
TASK_FILE_EXISTS_TEST = 'test (file exists)'
|
|
TASK_GREP_IDMAP_TEST = 'test (grep idmap)'
|
|
TASK_MD5_TEST = 'test (md5)'
|
|
TASK_IDMAP_PATH = 'idmap --path'
|
|
TASK_IDMAP_SCAN = 'idmap --scan'
|
|
TASK_INSTRUMENTATION = 'instrumentation'
|
|
TASK_INSTRUMENTATION_TEST = 'test (instrumentation)'
|
|
TASK_MKDIR = 'mkdir'
|
|
TASK_PUSH = 'push'
|
|
TASK_ROOT = 'root'
|
|
TASK_REMOUNT = 'remount'
|
|
TASK_RM = 'rm'
|
|
TASK_SETPROP = 'setprop'
|
|
TASK_SETUP_IDMAP_PATH = 'setup idmap --path'
|
|
TASK_SETUP_IDMAP_SCAN = 'setup idmap --scan'
|
|
TASK_START = 'start'
|
|
TASK_STOP = 'stop'
|
|
|
|
adb = 'adb'
|
|
|
|
def _adb_shell(cmd):
|
|
argv = shlex.split(adb + " shell '" + cmd + "; echo $?'")
|
|
proc = subprocess.Popen(argv, bufsize=1, stdout=subprocess.PIPE, stderr=subprocess.PIPE)
|
|
(stdout, stderr) = proc.communicate()
|
|
(stdout, stderr) = (stdout.replace('\r', ''), stderr.replace('\r', ''))
|
|
tmp = stdout.rsplit('\n', 2)
|
|
if len(tmp) == 2:
|
|
stdout == ''
|
|
returncode = int(tmp[0])
|
|
else:
|
|
stdout = tmp[0] + '\n'
|
|
returncode = int(tmp[1])
|
|
return returncode, stdout, stderr
|
|
|
|
class VerbosePrinter:
|
|
class Ticker(threading.Thread):
|
|
def _print(self):
|
|
s = '\r' + self.text + '[' + '.' * self.i + ' ' * (4 - self.i) + ']'
|
|
sys.stdout.write(s)
|
|
sys.stdout.flush()
|
|
self.i = (self.i + 1) % 5
|
|
|
|
def __init__(self, cond_var, text):
|
|
threading.Thread.__init__(self)
|
|
self.text = text
|
|
self.setDaemon(True)
|
|
self.cond_var = cond_var
|
|
self.running = False
|
|
self.i = 0
|
|
self._print()
|
|
self.running = True
|
|
|
|
def run(self):
|
|
self.cond_var.acquire()
|
|
while True:
|
|
self.cond_var.wait(0.25)
|
|
running = self.running
|
|
if not running:
|
|
break
|
|
self._print()
|
|
self.cond_var.release()
|
|
|
|
def stop(self):
|
|
self.cond_var.acquire()
|
|
self.running = False
|
|
self.cond_var.notify_all()
|
|
self.cond_var.release()
|
|
|
|
def _start_ticker(self):
|
|
self.ticker = VerbosePrinter.Ticker(self.cond_var, self.text)
|
|
self.ticker.start()
|
|
|
|
def _stop_ticker(self):
|
|
self.ticker.stop()
|
|
self.ticker.join()
|
|
self.ticker = None
|
|
|
|
def _format_begin(self, type, name):
|
|
N = self.width - len(type) - len(' [ ] ')
|
|
fmt = '%%s %%-%ds ' % N
|
|
return fmt % (type, name)
|
|
|
|
def __init__(self, use_color):
|
|
self.cond_var = threading.Condition()
|
|
self.ticker = None
|
|
if use_color:
|
|
self.color_RED = '\033[1;31m'
|
|
self.color_red = '\033[0;31m'
|
|
self.color_reset = '\033[0;37m'
|
|
else:
|
|
self.color_RED = ''
|
|
self.color_red = ''
|
|
self.color_reset = ''
|
|
|
|
argv = shlex.split('stty size') # get terminal width
|
|
proc = subprocess.Popen(argv, stdout=subprocess.PIPE, stderr=subprocess.PIPE)
|
|
(stdout, stderr) = proc.communicate()
|
|
if proc.returncode == 0:
|
|
(h, w) = stdout.split()
|
|
self.width = int(w)
|
|
else:
|
|
self.width = 72 # conservative guesstimate
|
|
|
|
def begin(self, type, name):
|
|
self.text = self._format_begin(type, name)
|
|
sys.stdout.write(self.text + '[ ]')
|
|
sys.stdout.flush()
|
|
self._start_ticker()
|
|
|
|
def end_pass(self, type, name):
|
|
self._stop_ticker()
|
|
sys.stdout.write('\r' + self.text + '[ OK ]\n')
|
|
sys.stdout.flush()
|
|
|
|
def end_fail(self, type, name, msg):
|
|
self._stop_ticker()
|
|
sys.stdout.write('\r' + self.color_RED + self.text + '[FAIL]\n')
|
|
sys.stdout.write(self.color_red)
|
|
sys.stdout.write(msg)
|
|
sys.stdout.write(self.color_reset)
|
|
sys.stdout.flush()
|
|
|
|
class QuietPrinter:
|
|
def begin(self, type, name):
|
|
pass
|
|
|
|
def end_pass(self, type, name):
|
|
sys.stdout.write('PASS ' + type + ' ' + name + '\n')
|
|
sys.stdout.flush()
|
|
|
|
def end_fail(self, type, name, msg):
|
|
sys.stdout.write('FAIL ' + type + ' ' + name + '\n')
|
|
sys.stdout.flush()
|
|
|
|
class CompilationTask:
|
|
def __init__(self, makefile):
|
|
self.makefile = makefile
|
|
|
|
def get_type(self):
|
|
return TASK_COMPILATION
|
|
|
|
def get_name(self):
|
|
return self.makefile
|
|
|
|
def execute(self):
|
|
os.putenv('ONE_SHOT_MAKEFILE', os.getcwd() + "/" + self.makefile)
|
|
argv = shlex.split('make -C "../../../../../" files')
|
|
proc = subprocess.Popen(argv, stdout=subprocess.PIPE, stderr=subprocess.PIPE)
|
|
(stdout, stderr) = proc.communicate()
|
|
return proc.returncode, stdout, stderr
|
|
|
|
class InstrumentationTask:
|
|
def __init__(self, instrumentation_class):
|
|
self.instrumentation_class = instrumentation_class
|
|
|
|
def get_type(self):
|
|
return TASK_INSTRUMENTATION
|
|
|
|
def get_name(self):
|
|
return self.instrumentation_class
|
|
|
|
def execute(self):
|
|
return _adb_shell('am instrument -r -w -e class %s com.android.overlaytest/android.test.InstrumentationTestRunner' % self.instrumentation_class)
|
|
|
|
class PushTask:
|
|
def __init__(self, src, dest):
|
|
self.src = src
|
|
self.dest = dest
|
|
|
|
def get_type(self):
|
|
return TASK_PUSH
|
|
|
|
def get_name(self):
|
|
return "%s -> %s" % (self.src, self.dest)
|
|
|
|
def execute(self):
|
|
src = os.getenv('OUT')
|
|
if (src is None):
|
|
return 1, "", "Unable to proceed - $OUT environment var not set\n"
|
|
src += "/" + self.src
|
|
argv = shlex.split(adb + ' push %s %s' % (src, self.dest))
|
|
proc = subprocess.Popen(argv, stdout=subprocess.PIPE, stderr=subprocess.PIPE)
|
|
(stdout, stderr) = proc.communicate()
|
|
return proc.returncode, stdout, stderr
|
|
|
|
class MkdirTask:
|
|
def __init__(self, path):
|
|
self.path = path
|
|
|
|
def get_type(self):
|
|
return TASK_MKDIR
|
|
|
|
def get_name(self):
|
|
return self.path
|
|
|
|
def execute(self):
|
|
return _adb_shell('mkdir -p %s' % self.path)
|
|
|
|
class RmTask:
|
|
def __init__(self, path):
|
|
self.path = path
|
|
|
|
def get_type(self):
|
|
return TASK_RM
|
|
|
|
def get_name(self):
|
|
return self.path
|
|
|
|
def execute(self):
|
|
returncode, stdout, stderr = _adb_shell('ls %s' % self.path)
|
|
if returncode != 0 and stderr.endswith(': No such file or directory\n'):
|
|
return 0, "", ""
|
|
return _adb_shell('rm -r %s' % self.path)
|
|
|
|
class SetPropTask:
|
|
def __init__(self, prop, value):
|
|
self.prop = prop
|
|
self.value = value
|
|
|
|
def get_type(self):
|
|
return TASK_SETPROP
|
|
|
|
def get_name(self):
|
|
return self.prop
|
|
|
|
def execute(self):
|
|
return _adb_shell('setprop %s %s' % (self.prop, self.value))
|
|
|
|
class IdmapPathTask:
|
|
def __init__(self, path_target_apk, path_overlay_apk, path_idmap):
|
|
self.path_target_apk = path_target_apk
|
|
self.path_overlay_apk = path_overlay_apk
|
|
self.path_idmap = path_idmap
|
|
|
|
def get_type(self):
|
|
return TASK_IDMAP_PATH
|
|
|
|
def get_name(self):
|
|
return self.path_idmap
|
|
|
|
def execute(self):
|
|
return _adb_shell('su system idmap --scan "%s" "%s" "%s" "%s"' % (self.target_pkg_name, self.target_pkg, self.idmap_dir, self.overlay_dir))
|
|
|
|
class IdmapScanTask:
|
|
def __init__(self, overlay_dir, target_pkg_name, target_pkg, idmap_dir, symlink_dir):
|
|
self.overlay_dir = overlay_dir
|
|
self.target_pkg_name = target_pkg_name
|
|
self.target_pkg = target_pkg
|
|
self.idmap_dir = idmap_dir
|
|
self.symlink_dir = symlink_dir
|
|
|
|
def get_type(self):
|
|
return TASK_IDMAP_SCAN
|
|
|
|
def get_name(self):
|
|
return self.target_pkg_name
|
|
|
|
def execute(self):
|
|
return _adb_shell('su system idmap --scan "%s" "%s" "%s" "%s"' % (self.overlay_dir, self.target_pkg_name, self.target_pkg, self.idmap_dir))
|
|
|
|
class FileExistsTest:
|
|
def __init__(self, path):
|
|
self.path = path
|
|
|
|
def get_type(self):
|
|
return TASK_FILE_EXISTS_TEST
|
|
|
|
def get_name(self):
|
|
return self.path
|
|
|
|
def execute(self):
|
|
return _adb_shell('ls %s' % self.path)
|
|
|
|
class GrepIdmapTest:
|
|
def __init__(self, path_idmap, pattern, expected_n):
|
|
self.path_idmap = path_idmap
|
|
self.pattern = pattern
|
|
self.expected_n = expected_n
|
|
|
|
def get_type(self):
|
|
return TASK_GREP_IDMAP_TEST
|
|
|
|
def get_name(self):
|
|
return self.pattern
|
|
|
|
def execute(self):
|
|
returncode, stdout, stderr = _adb_shell('idmap --inspect %s' % self.path_idmap)
|
|
if returncode != 0:
|
|
return returncode, stdout, stderr
|
|
all_matches = re.findall('\s' + self.pattern + '$', stdout, flags=re.MULTILINE)
|
|
if len(all_matches) != self.expected_n:
|
|
return 1, 'pattern=%s idmap=%s expected=%d found=%d\n' % (self.pattern, self.path_idmap, self.expected_n, len(all_matches)), ''
|
|
return 0, "", ""
|
|
|
|
class Md5Test:
|
|
def __init__(self, path, expected_content):
|
|
self.path = path
|
|
self.expected_md5 = hashlib.md5(expected_content).hexdigest()
|
|
|
|
def get_type(self):
|
|
return TASK_MD5_TEST
|
|
|
|
def get_name(self):
|
|
return self.path
|
|
|
|
def execute(self):
|
|
returncode, stdout, stderr = _adb_shell('md5sum %s' % self.path)
|
|
if returncode != 0:
|
|
return returncode, stdout, stderr
|
|
actual_md5 = stdout.split()[0]
|
|
if actual_md5 != self.expected_md5:
|
|
return 1, 'expected %s, got %s\n' % (self.expected_md5, actual_md5), ''
|
|
return 0, "", ""
|
|
|
|
class StartTask:
|
|
def get_type(self):
|
|
return TASK_START
|
|
|
|
def get_name(self):
|
|
return ""
|
|
|
|
def execute(self):
|
|
(returncode, stdout, stderr) = _adb_shell('start')
|
|
if returncode != 0:
|
|
return returncode, stdout, stderr
|
|
|
|
while True:
|
|
(returncode, stdout, stderr) = _adb_shell('getprop dev.bootcomplete')
|
|
if returncode != 0:
|
|
return returncode, stdout, stderr
|
|
if stdout.strip() == "1":
|
|
break
|
|
time.sleep(0.5)
|
|
|
|
return 0, "", ""
|
|
|
|
class StopTask:
|
|
def get_type(self):
|
|
return TASK_STOP
|
|
|
|
def get_name(self):
|
|
return ""
|
|
|
|
def execute(self):
|
|
(returncode, stdout, stderr) = _adb_shell('stop')
|
|
if returncode != 0:
|
|
return returncode, stdout, stderr
|
|
return _adb_shell('setprop dev.bootcomplete 0')
|
|
|
|
class RootTask:
|
|
def get_type(self):
|
|
return TASK_ROOT
|
|
|
|
def get_name(self):
|
|
return ""
|
|
|
|
def execute(self):
|
|
(returncode, stdout, stderr) = _adb_shell('getprop service.adb.root 0')
|
|
if returncode != 0:
|
|
return returncode, stdout, stderr
|
|
if stdout.strip() == '1': # already root
|
|
return 0, "", ""
|
|
|
|
argv = shlex.split(adb + ' root')
|
|
proc = subprocess.Popen(argv, stdout=subprocess.PIPE, stderr=subprocess.PIPE)
|
|
(stdout, stderr) = proc.communicate()
|
|
if proc.returncode != 0:
|
|
return proc.returncode, stdout, stderr
|
|
|
|
argv = shlex.split(adb + ' wait-for-device')
|
|
proc = subprocess.Popen(argv, stdout=subprocess.PIPE, stderr=subprocess.PIPE)
|
|
(stdout, stderr) = proc.communicate()
|
|
return proc.returncode, stdout, stderr
|
|
|
|
class RemountTask:
|
|
def get_type(self):
|
|
return TASK_REMOUNT
|
|
|
|
def get_name(self):
|
|
return ""
|
|
|
|
def execute(self):
|
|
argv = shlex.split(adb + ' remount')
|
|
proc = subprocess.Popen(argv, stdout=subprocess.PIPE, stderr=subprocess.PIPE)
|
|
(stdout, stderr) = proc.communicate()
|
|
# adb remount returns 0 even if the operation failed, so check stdout
|
|
if stdout.startswith('remount failed:'):
|
|
return 1, stdout, stderr
|
|
return proc.returncode, stdout, stderr
|
|
|
|
class CompoundTask:
|
|
def __init__(self, type, tasks):
|
|
self.type = type
|
|
self.tasks = tasks
|
|
|
|
def get_type(self):
|
|
return self.type
|
|
|
|
def get_name(self):
|
|
return ""
|
|
|
|
def execute(self):
|
|
for t in self.tasks:
|
|
(returncode, stdout, stderr) = t.execute()
|
|
if returncode != 0:
|
|
return returncode, stdout, stderr
|
|
return 0, "", ""
|
|
|
|
def _create_disable_overlays_task():
|
|
tasks = [
|
|
RmTask("/vendor/overlay/framework_a.apk"),
|
|
RmTask("/vendor/overlay/framework_b.apk"),
|
|
RmTask("/data/resource-cache/vendor@overlay@framework_a.apk@idmap"),
|
|
RmTask("/data/resource-cache/vendor@overlay@framework_b.apk@idmap"),
|
|
RmTask("/vendor/overlay/app_a.apk"),
|
|
RmTask("/vendor/overlay/app_b.apk"),
|
|
RmTask("/vendor/overlay/app_c.apk"),
|
|
RmTask("/data/resource-cache/vendor@overlay@app_a.apk@idmap"),
|
|
RmTask("/data/resource-cache/vendor@overlay@app_b.apk@idmap"),
|
|
RmTask("/data/resource-cache/vendor@overlay@app_c.apk@idmap"),
|
|
SetPropTask('persist.oem.overlay.test', '""'),
|
|
RmTask("/data/property/persist.oem.overlay.test"),
|
|
]
|
|
return CompoundTask(TASK_DISABLE_OVERLAYS, tasks)
|
|
|
|
def _create_enable_single_overlay_task():
|
|
tasks = [
|
|
_create_disable_overlays_task(),
|
|
MkdirTask('/system/vendor'),
|
|
MkdirTask('/vendor/overlay'),
|
|
PushTask('/data/app/com.android.overlaytest.overlay/com.android.overlaytest.overlay.apk', '/vendor/overlay/framework_a.apk'),
|
|
PushTask('/data/app/com.android.overlaytest.first_app_overlay/com.android.overlaytest.first_app_overlay.apk', '/vendor/overlay/app_a.apk'),
|
|
]
|
|
return CompoundTask(TASK_ENABLE_SINGLE_OVERLAY, tasks)
|
|
|
|
def _create_enable_multiple_overlays_task():
|
|
tasks = [
|
|
_create_disable_overlays_task(),
|
|
MkdirTask('/system/vendor'),
|
|
MkdirTask('/vendor/overlay'),
|
|
|
|
PushTask('/data/app/com.android.overlaytest.overlay/com.android.overlaytest.overlay.apk', '/vendor/overlay/framework_b.apk'),
|
|
PushTask('/data/app/com.android.overlaytest.first_app_overlay/com.android.overlaytest.first_app_overlay.apk', '/vendor/overlay/app_a.apk'),
|
|
PushTask('/data/app/com.android.overlaytest.second_app_overlay/com.android.overlaytest.second_app_overlay.apk', '/vendor/overlay/app_b.apk'),
|
|
PushTask('/data/app/com.android.overlaytest.filtered_app_overlay/com.android.overlaytest.filtered_app_overlay.apk', '/vendor/overlay/app_c.apk'),
|
|
]
|
|
return CompoundTask(TASK_ENABLE_MULTIPLE_OVERLAYS, tasks)
|
|
|
|
def _create_enable_filtered_overlays_task():
|
|
tasks = [
|
|
_create_disable_overlays_task(),
|
|
SetPropTask('persist.oem.overlay.test', 'foo'),
|
|
MkdirTask('/system/vendor'),
|
|
MkdirTask('/vendor/overlay'),
|
|
PushTask('/data/app/com.android.overlaytest.overlay/com.android.overlaytest.overlay.apk', '/vendor/overlay/framework_b.apk'),
|
|
PushTask('/data/app/com.android.overlaytest.first_app_overlay/com.android.overlaytest.first_app_overlay.apk', '/vendor/overlay/app_a.apk'),
|
|
PushTask('/data/app/com.android.overlaytest.second_app_overlay/com.android.overlaytest.second_app_overlay.apk', '/vendor/overlay/app_b.apk'),
|
|
PushTask('/data/app/com.android.overlaytest.filtered_app_overlay/com.android.overlaytest.filtered_app_overlay.apk', '/vendor/overlay/app_c.apk'),
|
|
]
|
|
return CompoundTask(TASK_ENABLE_FILTERED_OVERLAYS, tasks)
|
|
|
|
def _create_setup_idmap_path_task(idmaps, symlinks):
|
|
tasks = [
|
|
_create_enable_single_overlay_task(),
|
|
RmTask(symlinks),
|
|
RmTask(idmaps),
|
|
MkdirTask(idmaps),
|
|
MkdirTask(symlinks),
|
|
]
|
|
return CompoundTask(TASK_SETUP_IDMAP_PATH, tasks)
|
|
|
|
def _create_setup_idmap_scan_task(idmaps, symlinks):
|
|
tasks = [
|
|
_create_enable_filtered_overlays_task(),
|
|
RmTask(symlinks),
|
|
RmTask(idmaps),
|
|
MkdirTask(idmaps),
|
|
MkdirTask(symlinks),
|
|
]
|
|
return CompoundTask(TASK_SETUP_IDMAP_SCAN, tasks)
|
|
|
|
def _handle_instrumentation_task_output(stdout, printer):
|
|
regex_status_code = re.compile(r'^INSTRUMENTATION_STATUS_CODE: -?(\d+)')
|
|
regex_name = re.compile(r'^INSTRUMENTATION_STATUS: test=(.*)')
|
|
regex_begin_stack = re.compile(r'^INSTRUMENTATION_STATUS: stack=(.*)')
|
|
regex_end_stack = re.compile(r'^$')
|
|
|
|
failed_tests = 0
|
|
current_test = None
|
|
current_stack = []
|
|
mode_stack = False
|
|
for line in stdout.split("\n"):
|
|
line = line.rstrip() # strip \r from adb output
|
|
m = regex_status_code.match(line)
|
|
if m:
|
|
c = int(m.group(1))
|
|
if c == 1:
|
|
printer.begin(TASK_INSTRUMENTATION_TEST, current_test)
|
|
elif c == 0:
|
|
printer.end_pass(TASK_INSTRUMENTATION_TEST, current_test)
|
|
else:
|
|
failed_tests += 1
|
|
current_stack.append("\n")
|
|
msg = "\n".join(current_stack)
|
|
printer.end_fail(TASK_INSTRUMENTATION_TEST, current_test, msg.rstrip() + '\n')
|
|
continue
|
|
|
|
m = regex_name.match(line)
|
|
if m:
|
|
current_test = m.group(1)
|
|
continue
|
|
|
|
m = regex_begin_stack.match(line)
|
|
if m:
|
|
mode_stack = True
|
|
current_stack = []
|
|
current_stack.append(" " + m.group(1))
|
|
continue
|
|
|
|
m = regex_end_stack.match(line)
|
|
if m:
|
|
mode_stack = False
|
|
continue
|
|
|
|
if mode_stack:
|
|
current_stack.append(" " + line.strip())
|
|
|
|
return failed_tests
|
|
|
|
def _set_adb_device(option, opt, value, parser):
|
|
global adb
|
|
if opt == '-d' or opt == '--device':
|
|
adb = 'adb -d'
|
|
if opt == '-e' or opt == '--emulator':
|
|
adb = 'adb -e'
|
|
if opt == '-s' or opt == '--serial':
|
|
adb = 'adb -s ' + value
|
|
|
|
def _create_opt_parser():
|
|
parser = optparse.OptionParser()
|
|
parser.add_option('-d', '--device', action='callback', callback=_set_adb_device,
|
|
help='pass -d to adb')
|
|
parser.add_option('-e', '--emulator', action='callback', callback=_set_adb_device,
|
|
help='pass -e to adb')
|
|
parser.add_option('-s', '--serial', type="str", action='callback', callback=_set_adb_device,
|
|
help='pass -s <serical> to adb')
|
|
parser.add_option('-C', '--no-color', action='store_false',
|
|
dest='use_color', default=True,
|
|
help='disable color escape sequences in output')
|
|
parser.add_option('-q', '--quiet', action='store_true',
|
|
dest='quiet_mode', default=False,
|
|
help='quiet mode, output only results')
|
|
parser.add_option('-b', '--no-build', action='store_false',
|
|
dest='do_build', default=True,
|
|
help='do not rebuild test projects')
|
|
parser.add_option('-k', '--continue', action='store_true',
|
|
dest='do_continue', default=False,
|
|
help='do not rebuild test projects')
|
|
parser.add_option('-i', '--test-idmap', action='store_true',
|
|
dest='test_idmap', default=False,
|
|
help='run tests for idmap')
|
|
parser.add_option('-0', '--test-no-overlay', action='store_true',
|
|
dest='test_no_overlay', default=False,
|
|
help='run tests without any overlay')
|
|
parser.add_option('-1', '--test-single-overlay', action='store_true',
|
|
dest='test_single_overlay', default=False,
|
|
help='run tests for single overlay')
|
|
parser.add_option('-2', '--test-multiple-overlays', action='store_true',
|
|
dest='test_multiple_overlays', default=False,
|
|
help='run tests for multiple overlays')
|
|
parser.add_option('-3', '--test-filtered-overlays', action='store_true',
|
|
dest='test_filtered_overlays', default=False,
|
|
help='run tests for filtered (sys prop) overlays')
|
|
return parser
|
|
|
|
if __name__ == '__main__':
|
|
opt_parser = _create_opt_parser()
|
|
opts, args = opt_parser.parse_args(sys.argv[1:])
|
|
if not opts.test_idmap and not opts.test_no_overlay and not opts.test_single_overlay and not opts.test_multiple_overlays and not opts.test_filtered_overlays:
|
|
opts.test_idmap = True
|
|
opts.test_no_overlay = True
|
|
opts.test_single_overlay = True
|
|
opts.test_multiple_overlays = True
|
|
opts.test_filtered_overlays = True
|
|
|
|
if len(args) > 0:
|
|
opt_parser.error("unexpected arguments: %s" % " ".join(args))
|
|
# will never reach this: opt_parser.error will call sys.exit
|
|
|
|
if opts.quiet_mode:
|
|
printer = QuietPrinter()
|
|
else:
|
|
printer = VerbosePrinter(opts.use_color)
|
|
tasks = []
|
|
|
|
# must be in the same directory as this script for compilation tasks to work
|
|
script = sys.argv[0]
|
|
dirname = os.path.dirname(script)
|
|
wd = os.path.realpath(dirname)
|
|
os.chdir(wd)
|
|
|
|
# build test cases
|
|
if opts.do_build:
|
|
tasks.append(CompilationTask('OverlayTest/Android.mk'))
|
|
tasks.append(CompilationTask('OverlayTestOverlay/Android.mk'))
|
|
tasks.append(CompilationTask('OverlayAppFirst/Android.mk'))
|
|
tasks.append(CompilationTask('OverlayAppSecond/Android.mk'))
|
|
tasks.append(CompilationTask('OverlayAppFiltered/Android.mk'))
|
|
|
|
# remount filesystem, install test project
|
|
tasks.append(RootTask())
|
|
tasks.append(RemountTask())
|
|
tasks.append(PushTask('/system/app/OverlayTest/OverlayTest.apk', '/system/app/OverlayTest.apk'))
|
|
|
|
# test idmap
|
|
if opts.test_idmap:
|
|
idmaps='/data/local/tmp/idmaps'
|
|
symlinks='/data/local/tmp/symlinks'
|
|
|
|
# idmap --path
|
|
tasks.append(StopTask())
|
|
tasks.append(_create_setup_idmap_path_task(idmaps, symlinks))
|
|
tasks.append(StartTask())
|
|
tasks.append(IdmapPathTask('/vendor/overlay/framework_a.apk', '/system/framework/framework-res.apk', idmaps + '/a.idmap'))
|
|
tasks.append(FileExistsTest(idmaps + '/a.idmap'))
|
|
tasks.append(GrepIdmapTest(idmaps + '/a.idmap', 'bool/config_annoy_dianne', 1))
|
|
|
|
# idmap --scan
|
|
tasks.append(StopTask())
|
|
tasks.append(_create_setup_idmap_scan_task(idmaps, symlinks))
|
|
tasks.append(StartTask())
|
|
tasks.append(IdmapScanTask('/vendor/overlay', 'android', '/system/framework/framework-res.apk', idmaps, symlinks))
|
|
tasks.append(FileExistsTest(idmaps + '/vendor@overlay@framework_b.apk@idmap'))
|
|
tasks.append(GrepIdmapTest(idmaps + '/vendor@overlay@framework_b.apk@idmap', 'bool/config_annoy_dianne', 1))
|
|
|
|
|
|
# overlays.list
|
|
overlays_list_path = idmaps + '/overlays.list'
|
|
expected_content = '''\
|
|
/vendor/overlay/framework_b.apk /data/local/tmp/idmaps/vendor@overlay@framework_b.apk@idmap
|
|
'''
|
|
tasks.append(FileExistsTest(overlays_list_path))
|
|
tasks.append(Md5Test(overlays_list_path, expected_content))
|
|
|
|
# idmap cleanup
|
|
tasks.append(RmTask(symlinks))
|
|
tasks.append(RmTask(idmaps))
|
|
|
|
# test no overlay: all overlays cleared
|
|
if opts.test_no_overlay:
|
|
tasks.append(StopTask())
|
|
tasks.append(_create_disable_overlays_task())
|
|
tasks.append(StartTask())
|
|
tasks.append(InstrumentationTask('com.android.overlaytest.WithoutOverlayTest'))
|
|
|
|
# test single overlay: one overlay (a)
|
|
if opts.test_single_overlay:
|
|
tasks.append(StopTask())
|
|
tasks.append(_create_enable_single_overlay_task())
|
|
tasks.append(StartTask())
|
|
tasks.append(InstrumentationTask('com.android.overlaytest.WithOverlayTest'))
|
|
|
|
# test multiple overlays: all overlays - including 'disabled' filtered
|
|
# overlay (system property unset) so expect 'b[p=2]' overrides 'a[p=1]' but
|
|
# 'c[p=3]' should be ignored
|
|
if opts.test_multiple_overlays:
|
|
tasks.append(StopTask())
|
|
tasks.append(_create_enable_multiple_overlays_task())
|
|
tasks.append(StartTask())
|
|
tasks.append(InstrumentationTask('com.android.overlaytest.WithMultipleOverlaysTest'))
|
|
|
|
# test filtered overlays: all overlays - including 'enabled' filtered
|
|
# overlay (system property set/matched) so expect c[p=3] to override both a
|
|
# & b where applicable
|
|
if opts.test_filtered_overlays:
|
|
tasks.append(StopTask())
|
|
tasks.append(_create_enable_filtered_overlays_task())
|
|
tasks.append(StartTask())
|
|
tasks.append(InstrumentationTask('com.android.overlaytest.WithFilteredOverlaysTest'))
|
|
|
|
ignored_errors = 0
|
|
for t in tasks:
|
|
type = t.get_type()
|
|
name = t.get_name()
|
|
if type == TASK_INSTRUMENTATION:
|
|
# InstrumentationTask will run several tests, but we want it
|
|
# to appear as if each test was run individually. Calling
|
|
# "am instrument" with a single test method is prohibitively
|
|
# expensive, so let's instead post-process the output to
|
|
# emulate individual calls.
|
|
retcode, stdout, stderr = t.execute()
|
|
if retcode != 0:
|
|
printer.begin(TASK_INSTRUMENTATION, name)
|
|
printer.end_fail(TASK_INSTRUMENTATION, name, stderr)
|
|
sys.exit(retcode)
|
|
retcode = _handle_instrumentation_task_output(stdout, printer)
|
|
if retcode != 0:
|
|
if not opts.do_continue:
|
|
sys.exit(retcode)
|
|
else:
|
|
ignored_errors += retcode
|
|
else:
|
|
printer.begin(type, name)
|
|
retcode, stdout, stderr = t.execute()
|
|
if retcode == 0:
|
|
printer.end_pass(type, name)
|
|
if retcode != 0:
|
|
if len(stderr) == 0:
|
|
# hope for output from stdout instead (true for eg adb shell rm)
|
|
stderr = stdout
|
|
printer.end_fail(type, name, stderr)
|
|
if not opts.do_continue:
|
|
sys.exit(retcode)
|
|
else:
|
|
ignored_errors += retcode
|
|
sys.exit(ignored_errors)
|