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 16import monitors 17 18from common import has_ffx_isolate_dir, is_daemon_running, \ 19 register_common_args, register_device_args, \ 20 register_log_args, resolve_packages 21from compatible_utils import running_unattended 22from ffx_integration import ScopedFfxConfig 23from flash_device import register_update_args, update 24from isolate_daemon import IsolateDaemon 25from log_manager import LogManager, start_system_log 26from publish_package import publish_packages, register_package_args 27from run_blink_test import BlinkTestRunner 28from run_executable_test import create_executable_test_runner, \ 29 register_executable_test_args 30from run_telemetry_test import TelemetryTestRunner 31from run_webpage_test import WebpageTestRunner 32from serve_repo import register_serve_args, serve_repository 33from start_emulator import create_emulator_from_args, register_emulator_args 34from test_connection import test_connection, test_device_connection 35from test_runner import TestRunner 36 37 38def _get_test_runner(runner_args: argparse.Namespace, 39 test_args: List[str]) -> TestRunner: 40 """Initialize a suitable TestRunner class.""" 41 42 if not runner_args.out_dir: 43 raise ValueError('--out-dir must be specified.') 44 45 if runner_args.test_type == 'blink': 46 return BlinkTestRunner(runner_args.out_dir, test_args, 47 runner_args.target_id) 48 if runner_args.test_type in ['gpu', 'perf']: 49 return TelemetryTestRunner(runner_args.test_type, runner_args.out_dir, 50 test_args, runner_args.target_id) 51 if runner_args.test_type == 'webpage': 52 return WebpageTestRunner(runner_args.out_dir, test_args, 53 runner_args.target_id, runner_args.logs_dir) 54 return create_executable_test_runner(runner_args, test_args) 55 56 57# pylint: disable=too-many-statements 58def main(): 59 """E2E method for installing packages and running a test.""" 60 # Always add time stamps to the logs. 61 logging.basicConfig(format='%(levelname)s %(asctime)s %(message)s') 62 63 parser = argparse.ArgumentParser() 64 parser.add_argument( 65 'test_type', 66 help='The type of test to run. Options include \'blink\', \'gpu\', ' 67 'or in the case of executable tests, the test name.') 68 parser.add_argument('--device', 69 '-d', 70 action='store_true', 71 default=False, 72 help='Use an existing device.') 73 parser.add_argument('--extra-path', 74 action='append', 75 help='Extra paths to append to the PATH environment') 76 77 # Register arguments 78 register_common_args(parser) 79 register_device_args(parser) 80 register_emulator_args(parser) 81 register_executable_test_args(parser) 82 register_update_args(parser, default_os_check='ignore') 83 register_log_args(parser) 84 register_package_args(parser, allow_temp_repo=True) 85 register_serve_args(parser) 86 87 # Treat unrecognized arguments as test specific arguments. 88 runner_args, test_args = parser.parse_known_args() 89 90 if runner_args.target_id: 91 runner_args.device = True 92 93 with ExitStack() as stack: 94 if runner_args.logs_dir: 95 # TODO(crbug.com/343242386): Find a way to upload metric output when 96 # logs_dir is not defined. 97 stack.push(lambda *_: monitors.dump( 98 os.path.join(runner_args.logs_dir, 'invocations'))) 99 if runner_args.extra_path: 100 os.environ['PATH'] += os.pathsep + os.pathsep.join( 101 runner_args.extra_path) 102 if running_unattended(): 103 # Only restart the daemon if 1) daemon will be run in a new isolate 104 # dir, or 2) if there isn't a daemon running in the predefined 105 # isolate dir. 106 if not has_ffx_isolate_dir() or not is_daemon_running(): 107 stack.enter_context(IsolateDaemon(runner_args.logs_dir)) 108 109 if runner_args.everlasting: 110 # Setting the emu.instance_dir to match the named cache, so 111 # we can keep these files across multiple runs. 112 # The configuration attaches to the daemon isolate-dir, so it 113 # needs to go after the IsolateDaemon. 114 # There isn't a point of enabling the feature on devbox, it 115 # won't use isolate-dir and the emu.instance_dir always goes to 116 # the HOME directory. 117 stack.enter_context( 118 ScopedFfxConfig( 119 'emu.instance_dir', 120 os.path.join(os.environ['HOME'], 121 '.fuchsia_emulator/'))) 122 elif runner_args.logs_dir: 123 # Never restart daemon if not in the unattended mode. 124 logging.warning('You are using a --logs-dir, ensure the ffx ' 125 'daemon is started with the logs.dir config ' 126 'updated. We won\'t restart the daemon randomly' 127 ' anymore.') 128 log_manager = LogManager(runner_args.logs_dir) 129 stack.enter_context(log_manager) 130 131 if runner_args.device: 132 update(runner_args.system_image_dir, runner_args.os_check, 133 runner_args.target_id, runner_args.serial_num) 134 # Try to reboot the device if necessary since the ffx may ignore the 135 # device state after the flash. See 136 # https://cs.opensource.google/fuchsia/fuchsia/+/main:src/developer/ffx/lib/fastboot/src/common/fastboot.rs;drc=cfba0bdd4f8857adb6409f8ae9e35af52c0da93e;l=454 137 test_device_connection(runner_args.target_id) 138 else: 139 runner_args.target_id = stack.enter_context( 140 create_emulator_from_args(runner_args)) 141 test_connection(runner_args.target_id) 142 143 test_runner = _get_test_runner(runner_args, test_args) 144 package_deps = test_runner.package_deps 145 146 # Start system logging, after all possible restarts of the ffx daemon 147 # so that logging will not be interrupted. 148 start_system_log(log_manager, False, package_deps.values(), 149 ('--since', 'now'), runner_args.target_id) 150 151 if package_deps: 152 if not runner_args.repo: 153 # Create a directory that serves as a temporary repository. 154 runner_args.repo = stack.enter_context( 155 tempfile.TemporaryDirectory()) 156 publish_packages(package_deps.values(), runner_args.repo, 157 not runner_args.no_repo_init) 158 stack.enter_context(serve_repository(runner_args)) 159 resolve_packages(package_deps.keys(), runner_args.target_id) 160 161 return test_runner.run_test().returncode 162 163 164if __name__ == '__main__': 165 sys.exit(main()) 166