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