Extend run_on_as_app.py with --run-tests option
Change-Id: Ie3ebd498283d25e58ce4f7e8bcfdb03b685eb7a4
diff --git a/tools/run_on_as_app.py b/tools/run_on_as_app.py
index 65c8923..d77352a 100755
--- a/tools/run_on_as_app.py
+++ b/tools/run_on_as_app.py
@@ -17,6 +17,7 @@
import time
import utils
import zipfile
+from xml.dom import minidom
import as_utils
import create_maven_release
@@ -49,6 +50,7 @@
'compile_sdk': None,
'dir': '.',
'flavor': None,
+ 'has_instrumentation_tests': False,
'main_dex_rules': None,
'module': module,
'min_sdk': None,
@@ -90,7 +92,8 @@
App({
'id': 'com.numix.calculator',
'dir': 'Calculator',
- 'name': 'numix-calculator'
+ 'name': 'numix-calculator',
+ 'has_instrumentation_tests': True
})
]
}),
@@ -330,9 +333,6 @@
return (app, repo)
assert False
-# TODO(christofferqa): Do not rely on 'emulator-5554' name
-emulator_id = 'emulator-5554'
-
def ComputeSizeOfDexFilesInApk(apk):
dex_size = 0
z = zipfile.ZipFile(apk, 'r')
@@ -380,6 +380,12 @@
'Expected APK to be built with R8 version {} (was: {})'.format(
expected_version, marker))
+def isR8(shrinker):
+ return 'r8' in shrinker
+
+def isR8FullMode(shrinker):
+ return shrinker == 'r8-full' or shrinker == 'r8-nolib-full'
+
def IsMinifiedR8(shrinker):
return 'nolib' not in shrinker
@@ -401,7 +407,7 @@
return subprocess.check_output(['git', 'checkout', file]).strip()
def InstallApkOnEmulator(apk_dest, options):
- cmd = ['adb', '-s', emulator_id, 'install', '-r', '-d', apk_dest]
+ cmd = ['adb', '-s', options.emulator_id, 'install', '-r', '-d', apk_dest]
if options.quiet:
with open(os.devnull, 'w') as devnull:
subprocess.check_call(cmd, stdout=devnull)
@@ -416,7 +422,7 @@
def UninstallApkOnEmulator(app, options):
process = subprocess.Popen(
- ['adb', '-s', emulator_id, 'uninstall', app.id],
+ ['adb', '-s', options.emulator_id, 'uninstall', app.id],
stdout=subprocess.PIPE, stderr=subprocess.PIPE)
stdout, stderr = process.communicate()
@@ -432,20 +438,20 @@
'Unexpected result from `adb uninstall {}\nStdout: {}\nStderr: {}'.format(
app.id, stdout, stderr))
-def WaitForEmulator():
+def WaitForEmulator(options):
stdout = subprocess.check_output(['adb', 'devices'])
- if '{}\tdevice'.format(emulator_id) in stdout:
+ if '{}\tdevice'.format(options.emulator_id) in stdout:
return True
print('Emulator \'{}\' not connected; waiting for connection'.format(
- emulator_id))
+ options.emulator_id))
time_waited = 0
while True:
time.sleep(10)
time_waited += 10
stdout = subprocess.check_output(['adb', 'devices'])
- if '{}\tdevice'.format(emulator_id) not in stdout:
+ if '{}\tdevice'.format(options.emulator_id) not in stdout:
print('... still waiting for connection')
if time_waited >= 5 * 60:
return False
@@ -519,70 +525,15 @@
app, options, apk_dest) else 'failed'
if 'r8' in shrinker and options.r8_compilation_steps > 1:
- recompilation_results = []
+ result['recompilation_results'] = \
+ ComputeRecompilationResults(
+ app, repo, options, checkout_dir, temp_dir, shrinker,
+ proguard_config_file)
- # Build app with gradle using -D...keepRuleSynthesisForRecompilation=
- # true.
- out_dir = os.path.join(checkout_dir, 'out', shrinker + '-1')
- (apk_dest, profile_dest_dir, ext_proguard_config_file) = \
- BuildAppWithShrinker(
- app, repo, shrinker, checkout_dir, out_dir,
- temp_dir, options, keepRuleSynthesisForRecompilation=True)
- dex_size = ComputeSizeOfDexFilesInApk(apk_dest)
- recompilation_result = {
- 'apk_dest': apk_dest,
- 'build_status': 'success',
- 'dex_size': ComputeSizeOfDexFilesInApk(apk_dest),
- 'monkey_status': 'skipped'
- }
- recompilation_results.append(recompilation_result)
-
- # Sanity check that keep rules have changed.
- with open(ext_proguard_config_file) as new:
- with open(proguard_config_file) as old:
- assert(
- sum(1 for line in new
- if line.strip() and '-printconfiguration' not in line)
- >
- sum(1 for line in old
- if line.strip() and '-printconfiguration' not in line))
-
- # Extract min-sdk and target-sdk
- (min_sdk, compile_sdk) = \
- as_utils.GetMinAndCompileSdk(app, checkout_dir, apk_dest)
-
- # Now rebuild generated apk.
- previous_apk = apk_dest
-
- # We may need main dex rules when re-compiling with R8 as standalone.
- main_dex_rules = None
- if app.main_dex_rules:
- main_dex_rules = os.path.join(checkout_dir, app.main_dex_rules)
-
- for i in range(1, options.r8_compilation_steps):
- try:
- recompiled_apk_dest = os.path.join(
- checkout_dir, 'out', shrinker, 'app-release-{}.apk'.format(i))
- RebuildAppWithShrinker(
- app, previous_apk, recompiled_apk_dest,
- ext_proguard_config_file, shrinker, min_sdk, compile_sdk,
- options, temp_dir, main_dex_rules)
- recompilation_result = {
- 'apk_dest': recompiled_apk_dest,
- 'build_status': 'success',
- 'dex_size': ComputeSizeOfDexFilesInApk(recompiled_apk_dest)
- }
- if options.monkey:
- recompilation_result['monkey_status'] = 'success' if RunMonkey(
- app, options, recompiled_apk_dest) else 'failed'
- recompilation_results.append(recompilation_result)
- previous_apk = recompiled_apk_dest
- except Exception as e:
- warn('Failed to recompile {} with {}'.format(
- app.name, shrinker))
- recompilation_results.append({ 'build_status': 'failed' })
- break
- result['recompilation_results'] = recompilation_results
+ if options.run_tests and app.has_instrumentation_tests:
+ result['instrumentation_test_results'] = \
+ ComputeInstrumentationTestResults(
+ app, options, checkout_dir, out_dir, shrinker)
result_per_shrinker[shrinker] = result
@@ -620,30 +571,26 @@
as_utils.SetPrintConfigurationDirective(
app, checkout_dir, proguard_config_dest)
- env = {}
- env['ANDROID_HOME'] = utils.getAndroidHome()
- env['JAVA_OPTS'] = '-ea:com.android.tools.r8...'
+ env_vars = {}
+ env_vars['ANDROID_HOME'] = utils.getAndroidHome()
+ env_vars['JAVA_OPTS'] = '-ea:com.android.tools.r8...'
releaseTarget = app.releaseTarget
if not releaseTarget:
releaseTarget = app.module.replace('/', ':') + ':' + 'assemble' + (
app.flavor.capitalize() if app.flavor else '') + 'Release'
- # Value for property android.enableR8.
- enableR8 = 'r8' in shrinker
- # Value for property android.enableR8.fullMode.
- enableR8FullMode = shrinker == 'r8-full' or shrinker == 'r8-nolib-full'
- # Build gradlew command line.
- cmd = ['./gradlew', '--no-daemon', 'clean', releaseTarget,
- '--profile', '--stacktrace',
- '-Pandroid.enableR8=' + str(enableR8).lower(),
- '-Pandroid.enableR8.fullMode=' + str(enableR8FullMode).lower()]
+ # Build using gradle.
+ args = [releaseTarget,
+ '--profile',
+ '-Pandroid.enableR8=' + str(isR8(shrinker)).lower(),
+ '-Pandroid.enableR8.fullMode=' + str(isR8FullMode(shrinker)).lower()]
if keepRuleSynthesisForRecompilation:
- cmd.append('-Dcom.android.tools.r8.keepRuleSynthesisForRecompilation=true')
+ args.append('-Dcom.android.tools.r8.keepRuleSynthesisForRecompilation=true')
if options.gradle_flags:
- cmd.extend(options.gradle_flags.split(' '))
+ args.extend(options.gradle_flags.split(' '))
- stdout = utils.RunCmd(cmd, env, quiet=options.quiet)
+ stdout = utils.RunGradlew(args, env_vars=env_vars, quiet=options.quiet)
apk_base_name = (archives_base_name
+ (('-' + app.flavor) if app.flavor else '') + '-release')
@@ -689,6 +636,100 @@
return (apk_dest, profile_dest_dir, proguard_config_dest)
+def ComputeInstrumentationTestResults(
+ app, options, checkout_dir, out_dir, shrinker):
+ args = ['connectedAndroidTest',
+ '-Pandroid.enableR8=' + str(isR8(shrinker)).lower(),
+ '-Pandroid.enableR8.fullMode=' + str(isR8FullMode(shrinker)).lower()]
+ env_vars = { 'ANDROID_SERIAL': options.emulator_id }
+ stdout = \
+ utils.RunGradlew(args, env_vars=env_vars, quiet=options.quiet, fail=False)
+
+ xml_test_result_dest = os.path.join(out_dir, 'test_result')
+ as_utils.MoveXMLTestResultFileTo(
+ xml_test_result_dest, stdout, quiet=options.quiet)
+
+ with open(xml_test_result_dest, 'r') as f:
+ xml_test_result_contents = f.read()
+
+ xml_document = minidom.parseString(xml_test_result_contents)
+ testsuite_element = xml_document.documentElement
+
+ return {
+ 'xml_test_result_dest': xml_test_result_dest,
+ 'tests': int(testsuite_element.getAttribute('tests')),
+ 'failures': int(testsuite_element.getAttribute('failures')),
+ 'errors': int(testsuite_element.getAttribute('errors')),
+ 'skipped': int(testsuite_element.getAttribute('skipped'))
+ }
+
+def ComputeRecompilationResults(
+ app, repo, options, checkout_dir, temp_dir, shrinker, proguard_config_file):
+ recompilation_results = []
+
+ # Build app with gradle using -D...keepRuleSynthesisForRecompilation=
+ # true.
+ out_dir = os.path.join(checkout_dir, 'out', shrinker + '-1')
+ (apk_dest, profile_dest_dir, ext_proguard_config_file) = \
+ BuildAppWithShrinker(
+ app, repo, shrinker, checkout_dir, out_dir,
+ temp_dir, options, keepRuleSynthesisForRecompilation=True)
+ dex_size = ComputeSizeOfDexFilesInApk(apk_dest)
+ recompilation_result = {
+ 'apk_dest': apk_dest,
+ 'build_status': 'success',
+ 'dex_size': ComputeSizeOfDexFilesInApk(apk_dest),
+ 'monkey_status': 'skipped'
+ }
+ recompilation_results.append(recompilation_result)
+
+ # Sanity check that keep rules have changed.
+ with open(ext_proguard_config_file) as new:
+ with open(proguard_config_file) as old:
+ assert(
+ sum(1 for line in new
+ if line.strip() and '-printconfiguration' not in line)
+ >
+ sum(1 for line in old
+ if line.strip() and '-printconfiguration' not in line))
+
+ # Extract min-sdk and target-sdk
+ (min_sdk, compile_sdk) = \
+ as_utils.GetMinAndCompileSdk(app, checkout_dir, apk_dest)
+
+ # Now rebuild generated apk.
+ previous_apk = apk_dest
+
+ # We may need main dex rules when re-compiling with R8 as standalone.
+ main_dex_rules = None
+ if app.main_dex_rules:
+ main_dex_rules = os.path.join(checkout_dir, app.main_dex_rules)
+
+ for i in range(1, options.r8_compilation_steps):
+ try:
+ recompiled_apk_dest = os.path.join(
+ checkout_dir, 'out', shrinker, 'app-release-{}.apk'.format(i))
+ RebuildAppWithShrinker(
+ app, previous_apk, recompiled_apk_dest,
+ ext_proguard_config_file, shrinker, min_sdk, compile_sdk,
+ options, temp_dir, main_dex_rules)
+ recompilation_result = {
+ 'apk_dest': recompiled_apk_dest,
+ 'build_status': 'success',
+ 'dex_size': ComputeSizeOfDexFilesInApk(recompiled_apk_dest)
+ }
+ if options.monkey:
+ recompilation_result['monkey_status'] = 'success' if RunMonkey(
+ app, options, recompiled_apk_dest) else 'failed'
+ recompilation_results.append(recompilation_result)
+ previous_apk = recompiled_apk_dest
+ except Exception as e:
+ warn('Failed to recompile {} with {}'.format(
+ app.name, shrinker))
+ recompilation_results.append({ 'build_status': 'failed' })
+ break
+ return recompilation_results
+
def RebuildAppWithShrinker(
app, apk, apk_dest, proguard_config_file, shrinker, min_sdk, compile_sdk,
options, temp_dir, main_dex_rules):
@@ -728,7 +769,7 @@
quiet=options.quiet)
def RunMonkey(app, options, apk_dest):
- if not WaitForEmulator():
+ if not WaitForEmulator(options):
return False
UninstallApkOnEmulator(app, options)
@@ -740,8 +781,8 @@
# event sequence for each shrinker.
random_seed = 42
- cmd = ['adb', 'shell', 'monkey', '-p', app.id, '-s', str(random_seed),
- str(number_of_events_to_generate)]
+ cmd = ['adb', '-s', options.emulator_id, 'shell', 'monkey', '-p', app.id,
+ '-s', str(random_seed), str(number_of_events_to_generate)]
try:
stdout = utils.RunCmd(cmd, quiet=options.quiet)
@@ -756,8 +797,7 @@
def LogResultsForApps(result_per_shrinker_per_app, options):
print('')
- for app, result_per_shrinker in sorted(
- result_per_shrinker_per_app.iteritems(), key=lambda s: s[0].lower()):
+ for (app, result_per_shrinker) in result_per_shrinker_per_app:
LogResultsForApp(app, result_per_shrinker, options)
def LogResultsForApp(app, result_per_shrinker, options):
@@ -771,7 +811,7 @@
if shrinker not in result_per_shrinker:
continue
result = result_per_shrinker[shrinker];
- benchmark_name = '{}-{}'.format(options.print_dexsegments, app)
+ benchmark_name = '{}-{}'.format(options.print_dexsegments, app.name)
utils.print_dexsegments(benchmark_name, [result.get('apk_dest')])
duration = sum(result.get('profile').values())
print('%s-Total(RunTimeRaw): %s ms' % (benchmark_name, duration * 1000))
@@ -829,6 +869,7 @@
warn(' monkey: {}'.format(monkey_status))
else:
success(' monkey: {}'.format(monkey_status))
+
recompilation_results = result.get('recompilation_results', [])
i = 0
for recompilation_result in recompilation_results:
@@ -850,6 +891,23 @@
warn(msg)
i += 1
+ if options.run_tests and 'instrumentation_test_results' in result:
+ instrumentation_test_results = \
+ result.get('instrumentation_test_results')
+ succeeded = (
+ instrumentation_test_results.get('failures')
+ + instrumentation_test_results.get('errors')
+ + instrumentation_test_results.get('skipped')) == 0
+ if succeeded:
+ success(' tests: succeeded')
+ else:
+ warn(
+ ' tests: failed (failures: {}, errors: {}, skipped: {})'
+ .format(
+ instrumentation_test_results.get('failures'),
+ instrumentation_test_results.get('errors'),
+ instrumentation_test_results.get('skipped')))
+
def ParseOptions(argv):
result = optparse.OptionParser()
result.add_option('--app',
@@ -859,6 +917,9 @@
help='Whether to download apps without any compilation',
default=False,
action='store_true')
+ result.add_option('--emulator-id', '--emulator_id',
+ help='Id of the emulator to use',
+ default='emulator-5554')
result.add_option('--golem',
help='Running on golem, do not download',
default=False,
@@ -901,6 +962,10 @@
help='Number of times R8 should be run on each app',
default=2,
type=int)
+ result.add_option('--run-tests', '--run_tests',
+ help='Whether to run instrumentation tests',
+ default=False,
+ action='store_true')
result.add_option('--sign-apks', '--sign_apks',
help='Whether the APKs should be signed',
default=False,
@@ -985,13 +1050,13 @@
assert os.path.isfile(utils.R8LIB_JAR), 'Cannot build without r8lib.jar'
shutil.copyfile(utils.R8LIB_JAR, os.path.join(temp_dir, 'r8lib.jar'))
- result_per_shrinker_per_app = {}
+ result_per_shrinker_per_app = []
for (app, repo) in options.apps:
if app.skip:
continue
- result_per_shrinker_per_app[app.name] = \
- GetResultsForApp(app, repo, options, temp_dir)
+ result_per_shrinker_per_app.append(
+ (app, GetResultsForApp(app, repo, options, temp_dir)))
LogResultsForApps(result_per_shrinker_per_app, options)