• Home
  • Line#
  • Scopes#
  • Navigate#
  • Raw
  • Download
1# Copyright 2022 The Chromium Authors
2# Use of this source code is governed by a BSD-style license that can be
3# found in the LICENSE file.
4
5import argparse
6import json
7import os
8import subprocess
9import sys
10import time
11import typing
12
13CHROMIUM_SRC_DIR = os.path.realpath(
14    os.path.join(os.path.abspath(os.path.dirname(__file__)), '..', '..'))
15
16sys.path.append(os.path.join(CHROMIUM_SRC_DIR, 'build', 'util'))
17
18# pylint: disable=wrong-import-position
19from lib.results import result_sink
20from lib.results import result_types
21
22# pylint: disable=wrong-import-position
23
24
25# pylint: disable=too-many-arguments
26def report_results(test_name: str,
27                   test_location: str,
28                   status: str,
29                   duration: float,
30                   log: str,
31                   output_file: typing.Optional[str],
32                   sink_client: typing.Optional[result_sink.ResultSinkClient],
33                   failure_reason: typing.Optional[str] = None) -> None:
34    """Report results on bots.
35
36    Args:
37        test_name: The name of the test to report.
38        test_location: The Chromium src-relative path (starting with //) of the
39            test file that will be reported in results. Usually the path to
40            whatever script is calling this function.
41        status: A string containing the test status.
42        duration: An float containing the test duration in seconds.
43        log: A string containing the log output of the test.
44        output_dir: An optional string containing a path to a file to output
45            JSON to.
46        sink_client: An optional client for reporting results to ResultDB.
47        failure_reason: An optional string containing a reason why the test
48            failed.
49    """
50    if output_file:
51        report_json_results(output_file)
52    if sink_client:
53        sink_client.Post(test_id=test_name,
54                         status=status,
55                         duration=(duration * 1000),
56                         test_log=log,
57                         test_file=test_location,
58                         failure_reason=failure_reason)
59
60
61# pylint: enable=too-many-arguments
62
63
64def report_json_results(output_file: str) -> None:
65    """'Report' results on bots.
66
67    Actually just writes an empty JSON object to a file since all we need to
68    do is make the merge scripts happy.
69
70    Args:
71        output_dir: An optional string containing a path to a file to output
72            JSON to.
73    """
74    with open(output_file, 'w') as outfile:
75        json.dump({}, outfile)
76
77
78def parse_args() -> argparse.Namespace:
79    parser = argparse.ArgumentParser()
80    parser.add_argument('--isolated-script-test-output',
81                        dest='output_file',
82                        help=('Path to JSON output file.'))
83
84    args, _ = parser.parse_known_args()
85    return args
86
87
88def run_pytype(test_name: str, test_location: str,
89               files_to_check: typing.Iterable[str],
90               python_paths: typing.Iterable[str], cwd: str) -> int:
91    """Runs pytype on a given list of files/directories.
92
93    Args:
94        test_name: The name of the test that will be reported in results.
95        test_location: The Chromium src-relative path (starting with //) of the
96            test file that will be reported in results. Usually the path to
97            whatever script is calling this function.
98        files_to_check: Files and directories to run pytype on as absolute
99            paths.
100        python_paths: Any paths that should be set as PYTHONPATH when running
101            pytype.
102        cwd: The directory that pytype should be run from.
103
104    Returns:
105        0 on success, non-zero on failure.
106    """
107    sink_client = result_sink.TryInitClient()
108    args = parse_args()
109
110    if sys.platform != 'linux':
111        print('pytype is currently only supported on Linux, see '
112              'https://github.com/google/pytype/issues/1154')
113        report_results(test_name, test_location, result_types.SKIP, 0,
114                       'Skipped due to unsupported platform.',
115                       args.output_file, sink_client)
116        return 0
117
118    # Strangely, pytype won't complain if you tell it to analyze a directory
119    # that
120    # doesn't exist, which could potentially lead to code not being analyzed if
121    # it's added here but not added to the isolate. So, ensure that everything
122    # we expect to analyze actually exists.
123    for f in files_to_check:
124        if not os.path.exists(f):
125            raise RuntimeError(
126                'Requested file or directory %s does not exist.' % f)
127
128    # pytype looks for a 'python' or 'python3' executable in PATH, so make sure
129    # that the Python 3 executable from vpython is in the path.
130    executable_dir = os.path.dirname(sys.executable)
131    os.environ['PATH'] = executable_dir + os.pathsep + os.environ['PATH']
132
133    # pytype specifies that the provided PYTHONPATH is :-separated.
134    pythonpath = ':'.join(python_paths)
135    pytype_cmd = [
136        sys.executable,
137        '-m',
138        'pytype',
139        '--pythonpath',
140        pythonpath,
141        '--keep-going',
142        '--jobs',
143        'auto',
144    ]
145    pytype_cmd.extend(files_to_check)
146
147    if sink_client:
148        stdout_handle = subprocess.PIPE
149        stderr_handle = subprocess.STDOUT
150    else:
151        stdout_handle = None
152        stderr_handle = None
153
154    start_time = time.time()
155    try:
156        proc = subprocess.run(pytype_cmd,
157                              check=True,
158                              cwd=cwd,
159                              stdout=stdout_handle,
160                              stderr=stderr_handle,
161                              text=True)
162        stdout = proc.stdout
163        status = result_types.PASS
164        failure_reason = None
165    except subprocess.CalledProcessError as e:
166        stdout = e.stdout
167        status = result_types.FAIL
168        failure_reason = 'Checking Python 3 type hinting failed.'
169    duration = (time.time() - start_time)
170
171    if stdout:
172        print(stdout)
173    report_results(test_name, test_location, status, duration, stdout or '',
174                   args.output_file, sink_client, failure_reason)
175
176    if status == result_types.FAIL:
177        return 1
178    return 0
179