|  | #!/usr/bin/env python3 | 
|  | # | 
|  | # Copyright 2013 The Flutter 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 argparse | 
|  | import glob | 
|  | import re | 
|  | import os | 
|  | import subprocess | 
|  | import sys | 
|  | from compatibility_helper import byte_str_decode | 
|  |  | 
|  | if 'STORAGE_BUCKET' not in os.environ: | 
|  | print('The GCP storage bucket must be provided as an environment variable.') | 
|  | sys.exit(1) | 
|  | BUCKET = os.environ['STORAGE_BUCKET'] | 
|  |  | 
|  | if 'GCP_PROJECT' not in os.environ: | 
|  | print('The GCP project must be provided as an environment variable.') | 
|  | sys.exit(1) | 
|  | PROJECT = os.environ['GCP_PROJECT'] | 
|  |  | 
|  | # Exit codes returned by the FTL command that signal an infrastructure failure. | 
|  | FTL_INFRA_FAILURE_CODES = [1, 15, 20] | 
|  |  | 
|  | # Maximum number of retries done if an infrastructure failure occurs. | 
|  | MAX_RETRY_ATTEMPTS = 2 | 
|  |  | 
|  | script_dir = os.path.dirname(os.path.realpath(__file__)) | 
|  | buildroot_dir = os.path.abspath(os.path.join(script_dir, '..', '..')) | 
|  | out_dir = os.path.join(buildroot_dir, 'out') | 
|  | error_re = re.compile(r'[EF]/flutter.+') | 
|  |  | 
|  |  | 
|  | def run_firebase_test(apk, results_dir): | 
|  | # game-loop tests are meant for OpenGL apps. | 
|  | # This type of test will give the application a handle to a file, and | 
|  | # we'll write the timeline JSON to that file. | 
|  | # See https://firebase.google.com/docs/test-lab/android/game-loop | 
|  | # Pixel 5. As of this commit, this is a highly available device in FTL. | 
|  | process = subprocess.Popen( | 
|  | [ | 
|  | 'gcloud', | 
|  | '--project', | 
|  | PROJECT, | 
|  | 'firebase', | 
|  | 'test', | 
|  | 'android', | 
|  | 'run', | 
|  | '--type', | 
|  | 'game-loop', | 
|  | '--app', | 
|  | apk, | 
|  | '--timeout', | 
|  | '2m', | 
|  | '--results-bucket', | 
|  | BUCKET, | 
|  | '--results-dir', | 
|  | results_dir, | 
|  | '--device', | 
|  | 'model=shiba,version=34', | 
|  | ], | 
|  | stdout=subprocess.PIPE, | 
|  | stderr=subprocess.STDOUT, | 
|  | universal_newlines=True, | 
|  | ) | 
|  | return process | 
|  |  | 
|  |  | 
|  | def check_logcat(results_dir): | 
|  | logcat = subprocess.check_output(['gsutil', 'cat', '%s/%s/*/logcat' % (BUCKET, results_dir)]) | 
|  | logcat = byte_str_decode(logcat) | 
|  | if not logcat: | 
|  | sys.exit(1) | 
|  |  | 
|  | logcat_matches = error_re.findall(logcat) | 
|  | if logcat_matches: | 
|  | print('Errors in logcat:') | 
|  | print(logcat_matches) | 
|  | sys.exit(1) | 
|  |  | 
|  |  | 
|  | def check_timeline(results_dir): | 
|  | gsutil_du = subprocess.check_output([ | 
|  | 'gsutil', 'du', | 
|  | '%s/%s/*/game_loop_results/results_scenario_0.json' % (BUCKET, results_dir) | 
|  | ]) | 
|  | gsutil_du = byte_str_decode(gsutil_du) | 
|  | gsutil_du = gsutil_du.strip() | 
|  | if gsutil_du == '0': | 
|  | print('Failed to produce a timeline.') | 
|  | sys.exit(1) | 
|  |  | 
|  |  | 
|  | def main(): | 
|  | parser = argparse.ArgumentParser() | 
|  | parser.add_argument( | 
|  | '--variant', | 
|  | dest='variant', | 
|  | action='store', | 
|  | default='android_profile_arm64', | 
|  | help='The engine variant to run tests for.' | 
|  | ) | 
|  | parser.add_argument( | 
|  | '--build-id', | 
|  | default=os.environ.get('SWARMING_TASK_ID', 'local_test'), | 
|  | help='A unique build identifier for this test. Used to sort results in the GCS bucket.' | 
|  | ) | 
|  |  | 
|  | args = parser.parse_args() | 
|  |  | 
|  | apks_dir = os.path.join(out_dir, args.variant, 'firebase_apks') | 
|  | apks = set(glob.glob('%s/*.apk' % apks_dir)) | 
|  |  | 
|  | if not apks: | 
|  | print('No APKs found at %s' % apks_dir) | 
|  | return 1 | 
|  |  | 
|  | git_revision = subprocess.check_output(['git', 'rev-parse', 'HEAD'], cwd=script_dir) | 
|  | git_revision = byte_str_decode(git_revision) | 
|  | git_revision = git_revision.strip() | 
|  |  | 
|  | for retry in range(MAX_RETRY_ATTEMPTS): | 
|  | if retry > 0: | 
|  | print('Retrying %s' % apks) | 
|  |  | 
|  | results = [] | 
|  | for apk in sorted(apks): | 
|  | results_dir = '%s/%s/%s' % (os.path.basename(apk), git_revision, args.build_id) | 
|  | process = run_firebase_test(apk, results_dir) | 
|  | results.append((apk, results_dir, process)) | 
|  |  | 
|  | for apk, results_dir, process in results: | 
|  | print('===== Test output for %s' % apk) | 
|  | for line in iter(process.stdout.readline, ''): | 
|  | print(line.strip()) | 
|  |  | 
|  | return_code = process.wait() | 
|  | if return_code in FTL_INFRA_FAILURE_CODES: | 
|  | print('Firebase test %s failed with infrastructure error code: %s' % (apk, return_code)) | 
|  | continue | 
|  | if return_code != 0: | 
|  | print('Firebase test %s failed with code: %s' % (apk, return_code)) | 
|  | sys.exit(return_code) | 
|  |  | 
|  | print('Checking logcat for %s' % results_dir) | 
|  | check_logcat(results_dir) | 
|  | # scenario_app produces a timeline, but the android image test does not. | 
|  | if 'scenario' in apk: | 
|  | print('Checking timeline for %s' % results_dir) | 
|  | check_timeline(results_dir) | 
|  |  | 
|  | apks.remove(apk) | 
|  |  | 
|  | if not apks: | 
|  | break | 
|  |  | 
|  | return 0 | 
|  |  | 
|  |  | 
|  | if __name__ == '__main__': | 
|  | sys.exit(main()) |