1#!/usr/bin/env vpython3 2# Copyright 2022 The Chromium Authors 3# Use of this source code is governed by a BSD-style license that can be 4# found in the LICENSE file. 5"""Implements commands for running tests E2E on a Fuchsia device.""" 6 7import argparse 8import logging 9import os 10import sys 11import tempfile 12 13from contextlib import ExitStack 14from typing import List 15 16from common import register_common_args, register_device_args, \ 17 register_log_args, resolve_packages 18from compatible_utils import running_unattended 19from ffx_integration import ScopedFfxConfig 20from flash_device import register_update_args, update 21from isolate_daemon import IsolateDaemon 22from log_manager import LogManager, start_system_log 23from publish_package import publish_packages, register_package_args 24from modification_waiter import ModificationWaiter 25from run_blink_test import BlinkTestRunner 26from run_executable_test import create_executable_test_runner, \ 27 register_executable_test_args 28from run_telemetry_test import TelemetryTestRunner 29from run_webpage_test import WebpageTestRunner 30from serve_repo import register_serve_args, serve_repository 31from start_emulator import create_emulator_from_args, register_emulator_args 32from test_connection import test_connection, test_device_connection 33from test_runner import TestRunner 34 35 36def _get_test_runner(runner_args: argparse.Namespace, 37 test_args: List[str]) -> TestRunner: 38 """Initialize a suitable TestRunner class.""" 39 40 if runner_args.test_type == 'blink': 41 return BlinkTestRunner(runner_args.out_dir, test_args, 42 runner_args.target_id) 43 if runner_args.test_type in ['gpu', 'perf']: 44 return TelemetryTestRunner(runner_args.test_type, runner_args.out_dir, 45 test_args, runner_args.target_id) 46 if runner_args.test_type in ['webpage']: 47 return WebpageTestRunner(runner_args.out_dir, test_args, 48 runner_args.target_id) 49 return create_executable_test_runner(runner_args, test_args) 50 51 52# pylint: disable=too-many-statements 53def main(): 54 """E2E method for installing packages and running a test.""" 55 # Always add time stamps to the logs. 56 logging.basicConfig(format='%(levelname)s %(asctime)s %(message)s') 57 58 parser = argparse.ArgumentParser() 59 parser.add_argument( 60 'test_type', 61 help='The type of test to run. Options include \'blink\', \'gpu\', ' 62 'or in the case of executable tests, the test name.') 63 parser.add_argument('--device', 64 '-d', 65 action='store_true', 66 default=False, 67 help='Use an existing device.') 68 parser.add_argument('--extra-path', 69 action='append', 70 help='Extra paths to append to the PATH environment') 71 72 # Register arguments 73 register_common_args(parser) 74 register_device_args(parser) 75 register_emulator_args(parser) 76 register_executable_test_args(parser) 77 register_update_args(parser, default_os_check='ignore') 78 register_log_args(parser) 79 register_package_args(parser, allow_temp_repo=True) 80 register_serve_args(parser) 81 82 # Treat unrecognized arguments as test specific arguments. 83 runner_args, test_args = parser.parse_known_args() 84 85 if not runner_args.out_dir: 86 raise ValueError('--out-dir must be specified.') 87 88 if runner_args.target_id: 89 runner_args.device = True 90 91 with ExitStack() as stack: 92 # See http://crbug.com/343611361#comment5, the ffx daemon may still 93 # write logs after being shut down and causes the unnecessary test 94 # failures. So this AbstractContextManager would check the modification 95 # time of the output folder to ensure no writes happening before 96 # exiting. 97 # This may belong to IsolateDaemon, but let's keep it here to test its 98 # functionality and reliability. 99 stack.enter_context(ModificationWaiter(runner_args.out_dir)) 100 log_manager = LogManager(runner_args.logs_dir) 101 if running_unattended(): 102 if runner_args.extra_path: 103 os.environ['PATH'] += os.pathsep + os.pathsep.join( 104 runner_args.extra_path) 105 106 extra_inits = [log_manager] 107 if runner_args.everlasting: 108 # Setting the emu.instance_dir to match the named cache, so 109 # we can keep these files across multiple runs. 110 extra_inits.append( 111 ScopedFfxConfig( 112 'emu.instance_dir', 113 os.path.join(os.environ['HOME'], 114 '.fuchsia_emulator/'))) 115 stack.enter_context(IsolateDaemon(extra_inits)) 116 else: 117 if runner_args.logs_dir: 118 logging.warning( 119 'You are using a --logs-dir, ensure the ffx ' 120 'daemon is started with the logs.dir config ' 121 'updated. We won\'t restart the daemon randomly' 122 ' anymore.') 123 stack.enter_context(log_manager) 124 125 if runner_args.device: 126 update(runner_args.system_image_dir, runner_args.os_check, 127 runner_args.target_id, runner_args.serial_num) 128 # Try to reboot the device if necessary since the ffx may ignore the 129 # device state after the flash. See 130 # https://cs.opensource.google/fuchsia/fuchsia/+/main:src/developer/ffx/lib/fastboot/src/common/fastboot.rs;drc=cfba0bdd4f8857adb6409f8ae9e35af52c0da93e;l=454 131 test_device_connection(runner_args.target_id) 132 else: 133 runner_args.target_id = stack.enter_context( 134 create_emulator_from_args(runner_args)) 135 test_connection(runner_args.target_id) 136 137 test_runner = _get_test_runner(runner_args, test_args) 138 package_deps = test_runner.package_deps 139 140 if not runner_args.repo: 141 # Create a directory that serves as a temporary repository. 142 runner_args.repo = stack.enter_context( 143 tempfile.TemporaryDirectory()) 144 145 publish_packages(package_deps.values(), runner_args.repo, 146 not runner_args.no_repo_init) 147 148 stack.enter_context(serve_repository(runner_args)) 149 150 # Start system logging, after all possible restarts of the ffx daemon 151 # so that logging will not be interrupted. 152 start_system_log(log_manager, False, package_deps.values(), 153 ('--since', 'now'), runner_args.target_id) 154 155 resolve_packages(package_deps.keys(), runner_args.target_id) 156 return test_runner.run_test().returncode 157 158 159if __name__ == '__main__': 160 sys.exit(main()) 161