blob: 2e1cddb59f428b73e00a65d4698fcdc5da1097d8 [file] [log] [blame]
Mike Frysinger9dfd69f2020-12-01 13:27:56 -05001#!/usr/bin/env python3
Mike Frysinger5ed12ec2025-08-21 10:25:25 -04002# Copyright (C) 2019 The Android Open Source Project
Mike Frysinger4f42a972019-06-12 17:42:43 -04003#
4# Licensed under the Apache License, Version 2.0 (the "License");
5# you may not use this file except in compliance with the License.
6# You may obtain a copy of the License at
7#
8# http://www.apache.org/licenses/LICENSE-2.0
9#
10# Unless required by applicable law or agreed to in writing, software
11# distributed under the License is distributed on an "AS IS" BASIS,
12# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
13# See the License for the specific language governing permissions and
14# limitations under the License.
15
Gavin Mak57cb4282023-03-30 05:06:01 +000016"""Wrapper to run linters and pytest with the right settings."""
Mike Frysinger4f42a972019-06-12 17:42:43 -040017
Mike Frysingerd5087392025-03-25 12:23:05 -040018import functools
Gavin Makea2e3302023-03-11 06:46:20 +000019import os
Mike Frysinger2719a8e2025-10-15 11:11:59 -040020import shlex
Mike Frysinger21cbcc52025-04-22 14:10:52 -040021import shutil
Gavin Makea2e3302023-03-11 06:46:20 +000022import subprocess
Mike Frysinger4f42a972019-06-12 17:42:43 -040023import sys
Mike Frysingerd5087392025-03-25 12:23:05 -040024from typing import List
Mike Frysinger64477332023-08-21 21:20:32 -040025
Gavin Makea2e3302023-03-11 06:46:20 +000026
Gavin Mak57cb4282023-03-30 05:06:01 +000027ROOT_DIR = os.path.dirname(os.path.realpath(__file__))
28
29
Mike Frysinger2719a8e2025-10-15 11:11:59 -040030def log_cmd(cmd: str, argv: list[str]) -> None:
31 """Log a debug message to make history easier to track."""
32 print("+", cmd, shlex.join(argv), file=sys.stderr)
33
34
Mike Frysingerd5087392025-03-25 12:23:05 -040035@functools.lru_cache()
36def is_ci() -> bool:
37 """Whether we're running in our CI system."""
38 return os.getenv("LUCI_CQ") == "yes"
39
40
41def run_pytest(argv: List[str]) -> int:
42 """Returns the exit code from pytest."""
43 if is_ci():
Mike Frysinger83104362025-03-25 12:50:36 -040044 argv = ["-m", "not skip_cq"] + argv
Mike Frysingerd5087392025-03-25 12:23:05 -040045
Mike Frysinger2719a8e2025-10-15 11:11:59 -040046 log_cmd("pytest", argv)
Mike Frysinger3667de12025-04-02 00:05:30 -040047 return subprocess.run(
48 [sys.executable, "-m", "pytest"] + argv,
49 check=False,
50 cwd=ROOT_DIR,
51 ).returncode
Mike Frysingerd5087392025-03-25 12:23:05 -040052
53
Mike Frysinger85ee1732025-04-01 23:50:30 -040054def run_pytest_py38(argv: List[str]) -> int:
55 """Returns the exit code from pytest under Python 3.8."""
56 if is_ci():
57 argv = ["-m", "not skip_cq"] + argv
58
Mike Frysinger2719a8e2025-10-15 11:11:59 -040059 log_cmd("[vpython 3.8] pytest", argv)
Mike Frysinger85ee1732025-04-01 23:50:30 -040060 try:
61 return subprocess.run(
62 [
63 "vpython3",
64 "-vpython-spec",
65 "run_tests.vpython3.8",
66 "-m",
67 "pytest",
68 ]
69 + argv,
70 check=False,
Mike Frysinger3667de12025-04-02 00:05:30 -040071 cwd=ROOT_DIR,
Mike Frysinger85ee1732025-04-01 23:50:30 -040072 ).returncode
73 except FileNotFoundError:
74 # Skip if the user doesn't have vpython from depot_tools.
75 return 0
76
77
Gavin Makea2e3302023-03-11 06:46:20 +000078def run_black():
Gavin Mak57cb4282023-03-30 05:06:01 +000079 """Returns the exit code from black."""
Mike Frysingerb8615112023-09-01 13:58:46 -040080 # Black by default only matches .py files. We have to list standalone
81 # scripts manually.
82 extra_programs = [
83 "repo",
84 "run_tests",
Mike Frysinger5591d992024-04-23 12:17:13 -040085 "release/update-hooks",
Mike Frysingerb8615112023-09-01 13:58:46 -040086 "release/update-manpages",
87 ]
Mike Frysinger2719a8e2025-10-15 11:11:59 -040088 argv = ["--diff", "--check", ROOT_DIR] + extra_programs
89 log_cmd("black", argv)
Gavin Makea2e3302023-03-11 06:46:20 +000090 return subprocess.run(
Mike Frysinger2719a8e2025-10-15 11:11:59 -040091 [sys.executable, "-m", "black"] + argv,
Mike Frysingerb8615112023-09-01 13:58:46 -040092 check=False,
Mike Frysinger3667de12025-04-02 00:05:30 -040093 cwd=ROOT_DIR,
Gavin Mak57cb4282023-03-30 05:06:01 +000094 ).returncode
95
96
97def run_flake8():
98 """Returns the exit code from flake8."""
Mike Frysinger2719a8e2025-10-15 11:11:59 -040099 argv = [ROOT_DIR]
100 log_cmd("flake8", argv)
Gavin Mak57cb4282023-03-30 05:06:01 +0000101 return subprocess.run(
Mike Frysinger2719a8e2025-10-15 11:11:59 -0400102 [sys.executable, "-m", "flake8"] + argv,
Mike Frysinger3667de12025-04-02 00:05:30 -0400103 check=False,
104 cwd=ROOT_DIR,
Gavin Makea2e3302023-03-11 06:46:20 +0000105 ).returncode
106
107
Mike Frysinger64477332023-08-21 21:20:32 -0400108def run_isort():
109 """Returns the exit code from isort."""
Mike Frysinger2719a8e2025-10-15 11:11:59 -0400110 argv = ["--check", ROOT_DIR]
111 log_cmd("isort", argv)
Mike Frysinger64477332023-08-21 21:20:32 -0400112 return subprocess.run(
Mike Frysinger2719a8e2025-10-15 11:11:59 -0400113 [sys.executable, "-m", "isort"] + argv,
Mike Frysinger3667de12025-04-02 00:05:30 -0400114 check=False,
115 cwd=ROOT_DIR,
Mike Frysinger64477332023-08-21 21:20:32 -0400116 ).returncode
117
118
Mike Frysinger80d1a5a2025-08-21 10:40:51 -0400119def run_check_metadata():
120 """Returns the exit code from check-metadata."""
Mike Frysinger2719a8e2025-10-15 11:11:59 -0400121 argv = []
122 log_cmd("release/check-metadata.py", argv)
Mike Frysinger80d1a5a2025-08-21 10:40:51 -0400123 return subprocess.run(
Mike Frysinger2719a8e2025-10-15 11:11:59 -0400124 [sys.executable, "release/check-metadata.py"] + argv,
Mike Frysinger80d1a5a2025-08-21 10:40:51 -0400125 check=False,
126 cwd=ROOT_DIR,
127 ).returncode
128
129
Mike Frysinger21cbcc52025-04-22 14:10:52 -0400130def run_update_manpages() -> int:
131 """Returns the exit code from release/update-manpages."""
Mike Frysingerc448ba92025-04-30 14:38:52 -0400132 # Allow this to fail on CI, but not local devs.
133 if is_ci() and not shutil.which("help2man"):
Mike Frysinger21cbcc52025-04-22 14:10:52 -0400134 print("update-manpages: help2man not found; skipping test")
135 return 0
136
Mike Frysinger2719a8e2025-10-15 11:11:59 -0400137 argv = ["--check"]
138 log_cmd("release/update-manpages", argv)
Mike Frysinger21cbcc52025-04-22 14:10:52 -0400139 return subprocess.run(
Mike Frysinger2719a8e2025-10-15 11:11:59 -0400140 [sys.executable, "release/update-manpages"] + argv,
Mike Frysinger21cbcc52025-04-22 14:10:52 -0400141 check=False,
142 cwd=ROOT_DIR,
143 ).returncode
144
145
Gavin Makea2e3302023-03-11 06:46:20 +0000146def main(argv):
147 """The main entry."""
Gavin Mak57cb4282023-03-30 05:06:01 +0000148 checks = (
Mike Frysingerd5087392025-03-25 12:23:05 -0400149 functools.partial(run_pytest, argv),
Mike Frysinger85ee1732025-04-01 23:50:30 -0400150 functools.partial(run_pytest_py38, argv),
Gavin Mak57cb4282023-03-30 05:06:01 +0000151 run_black,
152 run_flake8,
Mike Frysinger64477332023-08-21 21:20:32 -0400153 run_isort,
Mike Frysinger80d1a5a2025-08-21 10:40:51 -0400154 run_check_metadata,
Mike Frysinger21cbcc52025-04-22 14:10:52 -0400155 run_update_manpages,
Gavin Mak57cb4282023-03-30 05:06:01 +0000156 )
Mike Frysinger91f42802025-03-25 12:58:26 -0400157 # Run all the tests all the time to get full feedback. Don't exit on the
158 # first error as that makes it more difficult to iterate in the CQ.
159 return 1 if sum(c() for c in checks) else 0
Gavin Makea2e3302023-03-11 06:46:20 +0000160
161
162if __name__ == "__main__":
163 sys.exit(main(sys.argv[1:]))