blob: 38da2fcfb983f4f6fa54cc39a3dbcfae29bd2fb1 [file] [log] [blame]
# 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__)), '..', '..'))
sys.path.append(os.path.join(CHROMIUM_SRC_DIR, 'build', 'util'))
# pylint: disable=wrong-import-position
from lib.results import result_sink
from lib.results import result_types
# pylint: disable=wrong-import-position
# 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