From 14e5d16c3a2de82548ea583b525869c6d2b43d42 Mon Sep 17 00:00:00 2001 From: Sebastian Rittau Date: Mon, 27 Dec 2021 17:28:59 +0100 Subject: [PATCH] Replace METADATA stubtest key with stubtest_apt_packages (#6704) --- .github/workflows/stubtest.yml | 2 +- .github/workflows/tests.yml | 2 +- CONTRIBUTING.md | 5 +-- stubs/pyaudio/@tests/stubtest_allowlist.txt | 2 ++ stubs/pyaudio/METADATA.toml | 2 +- stubs/pycurl/@tests/stubtest_allowlist.txt | 2 +- stubs/pycurl/METADATA.toml | 2 +- tests/check_consistent.py | 2 +- tests/stubtest_third_party.py | 34 ++++++++++++++++----- 9 files changed, 38 insertions(+), 15 deletions(-) create mode 100644 stubs/pyaudio/@tests/stubtest_allowlist.txt diff --git a/.github/workflows/stubtest.yml b/.github/workflows/stubtest.yml index 790bc506c..655b3131b 100644 --- a/.github/workflows/stubtest.yml +++ b/.github/workflows/stubtest.yml @@ -53,4 +53,4 @@ jobs: - name: Install dependencies run: pip install $(grep tomli== requirements-tests.txt) - name: Run stubtest - run: python tests/stubtest_third_party.py --num-shards 4 --shard-index ${{ matrix.shard-index }} + run: python tests/stubtest_third_party.py --num-shards 4 --shard-index ${{ matrix.shard-index }} --sudo-install-apt diff --git a/.github/workflows/tests.yml b/.github/workflows/tests.yml index c1e4f9fed..459573bf2 100644 --- a/.github/workflows/tests.yml +++ b/.github/workflows/tests.yml @@ -131,7 +131,7 @@ jobs: STUBS=$(git diff --name-only origin/${{ github.base_ref }} HEAD | egrep ^stubs/ | cut -d "/" -f 2 | sort -u | (while read stub; do [ -d stubs/$stub ] && echo $stub || true; done)) if test -n "$STUBS"; then echo "Testing $STUBS..." - python tests/stubtest_third_party.py $STUBS + python tests/stubtest_third_party.py --sudo-install-apt $STUBS else echo "Nothing to test" fi diff --git a/CONTRIBUTING.md b/CONTRIBUTING.md index cc5a2ae85..15492cf60 100644 --- a/CONTRIBUTING.md +++ b/CONTRIBUTING.md @@ -177,8 +177,9 @@ supported: [removing obsolete third-party libraries](#third-party-library-removal-policy). It contains the first version of the corresponding library that ships its own `py.typed` file. -* `stubtest` (default: `true`): Whether stubtest should be run against this - package. Please avoid setting this to `false`, and add a comment if you have to. +* `stubtest_apt_dependencies` (default: `[]`): A list of Ubuntu APT packages + that need to be installed for stubtest to run successfully. These are + usually packages needed to pip install the implementation distribution. The format of all `METADATA.toml` files can be checked by running `python3 ./tests/check_consistent.py`. diff --git a/stubs/pyaudio/@tests/stubtest_allowlist.txt b/stubs/pyaudio/@tests/stubtest_allowlist.txt new file mode 100644 index 000000000..ceb8f4a01 --- /dev/null +++ b/stubs/pyaudio/@tests/stubtest_allowlist.txt @@ -0,0 +1,2 @@ +pyaudio.PaMacCoreStreamInfo +pyaudio.paMacCoreStreamInfo diff --git a/stubs/pyaudio/METADATA.toml b/stubs/pyaudio/METADATA.toml index c55d16c24..c31d59608 100644 --- a/stubs/pyaudio/METADATA.toml +++ b/stubs/pyaudio/METADATA.toml @@ -1,3 +1,3 @@ version = "0.2.*" python2 = true -stubtest = false # install fails locally +stubtest_apt_dependencies = ["portaudio19-dev"] diff --git a/stubs/pycurl/@tests/stubtest_allowlist.txt b/stubs/pycurl/@tests/stubtest_allowlist.txt index d4ba5cd3d..4f56e2ff5 100644 --- a/stubs/pycurl/@tests/stubtest_allowlist.txt +++ b/stubs/pycurl/@tests/stubtest_allowlist.txt @@ -1 +1 @@ -pycurl +pycurl.VERSION_FIRST diff --git a/stubs/pycurl/METADATA.toml b/stubs/pycurl/METADATA.toml index cd206b1a6..354f2a0fc 100644 --- a/stubs/pycurl/METADATA.toml +++ b/stubs/pycurl/METADATA.toml @@ -1,2 +1,2 @@ version = "7.44.*" -stubtest = false # install failure, missing libcurl +stubtest_apt_dependencies = ["libcurl4-openssl-dev"] diff --git a/tests/check_consistent.py b/tests/check_consistent.py index 1e58e64fc..105d838b0 100755 --- a/tests/check_consistent.py +++ b/tests/check_consistent.py @@ -18,7 +18,7 @@ import re import tomli consistent_files = [{"stdlib/@python2/builtins.pyi", "stdlib/@python2/__builtin__.pyi"}] -metadata_keys = {"version", "python2", "requires", "extra_description", "obsolete_since", "stubtest"} +metadata_keys = {"version", "python2", "requires", "extra_description", "obsolete_since", "stubtest_apt_dependencies"} allowed_files = {"README.md"} diff --git a/tests/stubtest_third_party.py b/tests/stubtest_third_party.py index cf3848d9a..3a6565837 100755 --- a/tests/stubtest_third_party.py +++ b/tests/stubtest_third_party.py @@ -22,14 +22,17 @@ def get_mypy_req(): return next(line.strip() for line in f if "mypy" in line) -def run_stubtest(dist: Path) -> bool: +def run_stubtest(dist: Path, *, install_apt: bool = False) -> bool: with open(dist / "METADATA.toml") as f: metadata = dict(tomli.loads(f.read())) - if not run_stubtest_for(metadata, dist): + if not has_py3_stubs(dist): print(f"Skipping stubtest for {dist.name}\n\n") return True + if not install_apt_packages(dist, metadata, install_apt): + return False + with tempfile.TemporaryDirectory() as tmp: venv_dir = Path(tmp) venv.create(venv_dir, with_pip=True, clear=True) @@ -109,19 +112,36 @@ def run_stubtest(dist: Path) -> bool: return True -def run_stubtest_for(metadata: dict[str, Any], dist: Path) -> bool: - return has_py3_stubs(dist) and metadata.get("stubtest", True) - - # Keep this in sync with mypy_test.py def has_py3_stubs(dist: Path) -> bool: return len(glob(f"{dist}/*.pyi")) > 0 or len(glob(f"{dist}/[!@]*/__init__.pyi")) > 0 +def install_apt_packages(dist: Path, metadata: dict[str, Any], install: bool) -> bool: + apt_packages = metadata.get("stubtest_apt_dependencies", []) + if not apt_packages: + return True + if not install: + print(f"Ensure the following apt packages are installed for {dist.name}: {', '.join(apt_packages)}", file=sys.stderr) + return True + try: + apt_cmd = ["sudo", "apt", "install", "-y", *apt_packages] + print(" ".join(apt_cmd), file=sys.stderr) + subprocess.run(apt_cmd, check=True, capture_output=True) + except subprocess.CalledProcessError as e: + print(f"Failed to install APT packages for {dist.name}: {', '.join(apt_packages)}", file=sys.stderr) + print(e.stdout.decode(), file=sys.stderr) + print(e.stderr.decode(), file=sys.stderr) + return False + else: + return True + + def main() -> NoReturn: parser = argparse.ArgumentParser() parser.add_argument("--num-shards", type=int, default=1) parser.add_argument("--shard-index", type=int, default=0) + parser.add_argument("--sudo-install-apt", action="store_true") parser.add_argument("dists", metavar="DISTRIBUTION", type=str, nargs=argparse.ZERO_OR_MORE) args = parser.parse_args() @@ -135,7 +155,7 @@ def main() -> NoReturn: for i, dist in enumerate(dists): if i % args.num_shards != args.shard_index: continue - if not run_stubtest(dist): + if not run_stubtest(dist, install_apt=args.sudo_install_apt): result = 1 sys.exit(result)