File: pytype_runner.py

package info (click to toggle)
chromium 139.0.7258.127-1
  • links: PTS, VCS
  • area: main
  • in suites:
  • size: 6,122,068 kB
  • sloc: cpp: 35,100,771; ansic: 7,163,530; javascript: 4,103,002; python: 1,436,920; asm: 946,517; xml: 746,709; pascal: 187,653; perl: 88,691; sh: 88,436; objc: 79,953; sql: 51,488; cs: 44,583; fortran: 24,137; makefile: 22,147; tcl: 15,277; php: 13,980; yacc: 8,984; ruby: 7,485; awk: 3,720; lisp: 3,096; lex: 1,327; ada: 727; jsp: 228; sed: 36
file content (175 lines) | stat: -rw-r--r-- 6,157 bytes parent folder | download | duplicates (6)
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