| Mike Frysinger | 9dfd69f | 2020-12-01 13:27:56 -0500 | [diff] [blame] | 1 | #!/usr/bin/env python3 |
| Mike Frysinger | 5ed12ec | 2025-08-21 10:25:25 -0400 | [diff] [blame] | 2 | # Copyright (C) 2019 The Android Open Source Project |
| Mike Frysinger | 4f42a97 | 2019-06-12 17:42:43 -0400 | [diff] [blame] | 3 | # |
| 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 Mak | 57cb428 | 2023-03-30 05:06:01 +0000 | [diff] [blame] | 16 | """Wrapper to run linters and pytest with the right settings.""" |
| Mike Frysinger | 4f42a97 | 2019-06-12 17:42:43 -0400 | [diff] [blame] | 17 | |
| Mike Frysinger | d508739 | 2025-03-25 12:23:05 -0400 | [diff] [blame] | 18 | import functools |
| Gavin Mak | ea2e330 | 2023-03-11 06:46:20 +0000 | [diff] [blame] | 19 | import os |
| Mike Frysinger | 2719a8e | 2025-10-15 11:11:59 -0400 | [diff] [blame] | 20 | import shlex |
| Mike Frysinger | 21cbcc5 | 2025-04-22 14:10:52 -0400 | [diff] [blame] | 21 | import shutil |
| Gavin Mak | ea2e330 | 2023-03-11 06:46:20 +0000 | [diff] [blame] | 22 | import subprocess |
| Mike Frysinger | 4f42a97 | 2019-06-12 17:42:43 -0400 | [diff] [blame] | 23 | import sys |
| Mike Frysinger | d508739 | 2025-03-25 12:23:05 -0400 | [diff] [blame] | 24 | from typing import List |
| Mike Frysinger | 6447733 | 2023-08-21 21:20:32 -0400 | [diff] [blame] | 25 | |
| Gavin Mak | ea2e330 | 2023-03-11 06:46:20 +0000 | [diff] [blame] | 26 | |
| Gavin Mak | 57cb428 | 2023-03-30 05:06:01 +0000 | [diff] [blame] | 27 | ROOT_DIR = os.path.dirname(os.path.realpath(__file__)) |
| 28 | |
| 29 | |
| Mike Frysinger | 2719a8e | 2025-10-15 11:11:59 -0400 | [diff] [blame] | 30 | def 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 Frysinger | d508739 | 2025-03-25 12:23:05 -0400 | [diff] [blame] | 35 | @functools.lru_cache() |
| 36 | def is_ci() -> bool: |
| 37 | """Whether we're running in our CI system.""" |
| 38 | return os.getenv("LUCI_CQ") == "yes" |
| 39 | |
| 40 | |
| 41 | def run_pytest(argv: List[str]) -> int: |
| 42 | """Returns the exit code from pytest.""" |
| 43 | if is_ci(): |
| Mike Frysinger | 8310436 | 2025-03-25 12:50:36 -0400 | [diff] [blame] | 44 | argv = ["-m", "not skip_cq"] + argv |
| Mike Frysinger | d508739 | 2025-03-25 12:23:05 -0400 | [diff] [blame] | 45 | |
| Mike Frysinger | 2719a8e | 2025-10-15 11:11:59 -0400 | [diff] [blame] | 46 | log_cmd("pytest", argv) |
| Mike Frysinger | 3667de1 | 2025-04-02 00:05:30 -0400 | [diff] [blame] | 47 | return subprocess.run( |
| 48 | [sys.executable, "-m", "pytest"] + argv, |
| 49 | check=False, |
| 50 | cwd=ROOT_DIR, |
| 51 | ).returncode |
| Mike Frysinger | d508739 | 2025-03-25 12:23:05 -0400 | [diff] [blame] | 52 | |
| 53 | |
| Mike Frysinger | 85ee173 | 2025-04-01 23:50:30 -0400 | [diff] [blame] | 54 | def 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 Frysinger | 2719a8e | 2025-10-15 11:11:59 -0400 | [diff] [blame] | 59 | log_cmd("[vpython 3.8] pytest", argv) |
| Mike Frysinger | 85ee173 | 2025-04-01 23:50:30 -0400 | [diff] [blame] | 60 | 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 Frysinger | 3667de1 | 2025-04-02 00:05:30 -0400 | [diff] [blame] | 71 | cwd=ROOT_DIR, |
| Mike Frysinger | 85ee173 | 2025-04-01 23:50:30 -0400 | [diff] [blame] | 72 | ).returncode |
| 73 | except FileNotFoundError: |
| 74 | # Skip if the user doesn't have vpython from depot_tools. |
| 75 | return 0 |
| 76 | |
| 77 | |
| Gavin Mak | ea2e330 | 2023-03-11 06:46:20 +0000 | [diff] [blame] | 78 | def run_black(): |
| Gavin Mak | 57cb428 | 2023-03-30 05:06:01 +0000 | [diff] [blame] | 79 | """Returns the exit code from black.""" |
| Mike Frysinger | b861511 | 2023-09-01 13:58:46 -0400 | [diff] [blame] | 80 | # Black by default only matches .py files. We have to list standalone |
| 81 | # scripts manually. |
| 82 | extra_programs = [ |
| 83 | "repo", |
| 84 | "run_tests", |
| Mike Frysinger | 5591d99 | 2024-04-23 12:17:13 -0400 | [diff] [blame] | 85 | "release/update-hooks", |
| Mike Frysinger | b861511 | 2023-09-01 13:58:46 -0400 | [diff] [blame] | 86 | "release/update-manpages", |
| 87 | ] |
| Mike Frysinger | 2719a8e | 2025-10-15 11:11:59 -0400 | [diff] [blame] | 88 | argv = ["--diff", "--check", ROOT_DIR] + extra_programs |
| 89 | log_cmd("black", argv) |
| Gavin Mak | ea2e330 | 2023-03-11 06:46:20 +0000 | [diff] [blame] | 90 | return subprocess.run( |
| Mike Frysinger | 2719a8e | 2025-10-15 11:11:59 -0400 | [diff] [blame] | 91 | [sys.executable, "-m", "black"] + argv, |
| Mike Frysinger | b861511 | 2023-09-01 13:58:46 -0400 | [diff] [blame] | 92 | check=False, |
| Mike Frysinger | 3667de1 | 2025-04-02 00:05:30 -0400 | [diff] [blame] | 93 | cwd=ROOT_DIR, |
| Gavin Mak | 57cb428 | 2023-03-30 05:06:01 +0000 | [diff] [blame] | 94 | ).returncode |
| 95 | |
| 96 | |
| 97 | def run_flake8(): |
| 98 | """Returns the exit code from flake8.""" |
| Mike Frysinger | 2719a8e | 2025-10-15 11:11:59 -0400 | [diff] [blame] | 99 | argv = [ROOT_DIR] |
| 100 | log_cmd("flake8", argv) |
| Gavin Mak | 57cb428 | 2023-03-30 05:06:01 +0000 | [diff] [blame] | 101 | return subprocess.run( |
| Mike Frysinger | 2719a8e | 2025-10-15 11:11:59 -0400 | [diff] [blame] | 102 | [sys.executable, "-m", "flake8"] + argv, |
| Mike Frysinger | 3667de1 | 2025-04-02 00:05:30 -0400 | [diff] [blame] | 103 | check=False, |
| 104 | cwd=ROOT_DIR, |
| Gavin Mak | ea2e330 | 2023-03-11 06:46:20 +0000 | [diff] [blame] | 105 | ).returncode |
| 106 | |
| 107 | |
| Mike Frysinger | 6447733 | 2023-08-21 21:20:32 -0400 | [diff] [blame] | 108 | def run_isort(): |
| 109 | """Returns the exit code from isort.""" |
| Mike Frysinger | 2719a8e | 2025-10-15 11:11:59 -0400 | [diff] [blame] | 110 | argv = ["--check", ROOT_DIR] |
| 111 | log_cmd("isort", argv) |
| Mike Frysinger | 6447733 | 2023-08-21 21:20:32 -0400 | [diff] [blame] | 112 | return subprocess.run( |
| Mike Frysinger | 2719a8e | 2025-10-15 11:11:59 -0400 | [diff] [blame] | 113 | [sys.executable, "-m", "isort"] + argv, |
| Mike Frysinger | 3667de1 | 2025-04-02 00:05:30 -0400 | [diff] [blame] | 114 | check=False, |
| 115 | cwd=ROOT_DIR, |
| Mike Frysinger | 6447733 | 2023-08-21 21:20:32 -0400 | [diff] [blame] | 116 | ).returncode |
| 117 | |
| 118 | |
| Mike Frysinger | 80d1a5a | 2025-08-21 10:40:51 -0400 | [diff] [blame] | 119 | def run_check_metadata(): |
| 120 | """Returns the exit code from check-metadata.""" |
| Mike Frysinger | 2719a8e | 2025-10-15 11:11:59 -0400 | [diff] [blame] | 121 | argv = [] |
| 122 | log_cmd("release/check-metadata.py", argv) |
| Mike Frysinger | 80d1a5a | 2025-08-21 10:40:51 -0400 | [diff] [blame] | 123 | return subprocess.run( |
| Mike Frysinger | 2719a8e | 2025-10-15 11:11:59 -0400 | [diff] [blame] | 124 | [sys.executable, "release/check-metadata.py"] + argv, |
| Mike Frysinger | 80d1a5a | 2025-08-21 10:40:51 -0400 | [diff] [blame] | 125 | check=False, |
| 126 | cwd=ROOT_DIR, |
| 127 | ).returncode |
| 128 | |
| 129 | |
| Mike Frysinger | 21cbcc5 | 2025-04-22 14:10:52 -0400 | [diff] [blame] | 130 | def run_update_manpages() -> int: |
| 131 | """Returns the exit code from release/update-manpages.""" |
| Mike Frysinger | c448ba9 | 2025-04-30 14:38:52 -0400 | [diff] [blame] | 132 | # Allow this to fail on CI, but not local devs. |
| 133 | if is_ci() and not shutil.which("help2man"): |
| Mike Frysinger | 21cbcc5 | 2025-04-22 14:10:52 -0400 | [diff] [blame] | 134 | print("update-manpages: help2man not found; skipping test") |
| 135 | return 0 |
| 136 | |
| Mike Frysinger | 2719a8e | 2025-10-15 11:11:59 -0400 | [diff] [blame] | 137 | argv = ["--check"] |
| 138 | log_cmd("release/update-manpages", argv) |
| Mike Frysinger | 21cbcc5 | 2025-04-22 14:10:52 -0400 | [diff] [blame] | 139 | return subprocess.run( |
| Mike Frysinger | 2719a8e | 2025-10-15 11:11:59 -0400 | [diff] [blame] | 140 | [sys.executable, "release/update-manpages"] + argv, |
| Mike Frysinger | 21cbcc5 | 2025-04-22 14:10:52 -0400 | [diff] [blame] | 141 | check=False, |
| 142 | cwd=ROOT_DIR, |
| 143 | ).returncode |
| 144 | |
| 145 | |
| Gavin Mak | ea2e330 | 2023-03-11 06:46:20 +0000 | [diff] [blame] | 146 | def main(argv): |
| 147 | """The main entry.""" |
| Gavin Mak | 57cb428 | 2023-03-30 05:06:01 +0000 | [diff] [blame] | 148 | checks = ( |
| Mike Frysinger | d508739 | 2025-03-25 12:23:05 -0400 | [diff] [blame] | 149 | functools.partial(run_pytest, argv), |
| Mike Frysinger | 85ee173 | 2025-04-01 23:50:30 -0400 | [diff] [blame] | 150 | functools.partial(run_pytest_py38, argv), |
| Gavin Mak | 57cb428 | 2023-03-30 05:06:01 +0000 | [diff] [blame] | 151 | run_black, |
| 152 | run_flake8, |
| Mike Frysinger | 6447733 | 2023-08-21 21:20:32 -0400 | [diff] [blame] | 153 | run_isort, |
| Mike Frysinger | 80d1a5a | 2025-08-21 10:40:51 -0400 | [diff] [blame] | 154 | run_check_metadata, |
| Mike Frysinger | 21cbcc5 | 2025-04-22 14:10:52 -0400 | [diff] [blame] | 155 | run_update_manpages, |
| Gavin Mak | 57cb428 | 2023-03-30 05:06:01 +0000 | [diff] [blame] | 156 | ) |
| Mike Frysinger | 91f4280 | 2025-03-25 12:58:26 -0400 | [diff] [blame] | 157 | # 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 Mak | ea2e330 | 2023-03-11 06:46:20 +0000 | [diff] [blame] | 160 | |
| 161 | |
| 162 | if __name__ == "__main__": |
| 163 | sys.exit(main(sys.argv[1:])) |