• Home
  • Line#
  • Scopes#
  • Navigate#
  • Raw
  • Download
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