blob: 2b197aff30a96385dad83b91b4b1e3336fb5853b [file]
# Copyright 2024 The Chromium Authors
# Use of this source code is governed by a BSD-style license that can be
# found in the LICENSE file.
from __future__ import annotations
import logging
import pathlib
import re
import unittest
import pytest
import tomllib
from tabulate import tabulate
from tests import test_helper
RUN_SNIPPET = """
if __name__ == "__main__":
test_helper.run_pytest(__file__)
""".strip()
FUTURE_ANNOTATIONS_SNIPPET = "from __future__ import annotations"
COMMENTS_ONLY_RE = re.compile(r"^(?:#.*|\s*)*$", re.MULTILINE)
UNITTEST_DIR = pathlib.Path(__file__).parent
ROOT_DIR = UNITTEST_DIR.parents[1]
CROSSBENCH_DIR = ROOT_DIR / "crossbench"
class MetaTestCase(unittest.TestCase):
def test_unittest_runner_snippet(self):
# - All unittests files must end with the snippet for the CQ to pick it up.
# - pytest files (in end2end) use a different approach that doesn't rely
# on a per-file runner
for test_file in UNITTEST_DIR.glob("**/test_*.py"):
with self.subTest(test_file=str(test_file)):
self.assertTrue(
test_file.read_text().rstrip().endswith(RUN_SNIPPET),
f"{test_file} misses runner snippet: "
"test_helper.run_pytest(__file__)")
def test_future_annotation(self):
for py_file in CROSSBENCH_DIR.glob("**/*.py"):
with self.subTest(py_file=str(py_file)):
text = py_file.read_text()
if FUTURE_ANNOTATIONS_SNIPPET in text:
continue
if "pytype: skip-file" in text:
continue
if py_file.name == "__init__.py" and COMMENTS_ONLY_RE.fullmatch(text):
continue
self.fail(f"{py_file} is missing future annotation")
@pytest.mark.xfail
def test_vpython_poetry_version_match(self):
vpython_content = (ROOT_DIR / ".vpython3").read_text()
poetry_lock_content = (ROOT_DIR / "poetry.lock").read_text()
vpython_re = re.compile(
r"name: \"infra/python/wheels/(?P<name>[^/\"]+).*?"
r"version: \"version:(?P<version>[^\"]+)\"", re.DOTALL)
poetry_re = re.compile(
r'name = "(?P<name>[^"]+)".*?'
r'version = "(?P<version>[^"]+)"', re.DOTALL)
vpython_packages = {}
for wheel_block in vpython_content.split("wheel: <"):
if not wheel_block.strip():
continue
match = vpython_re.search(wheel_block)
if match:
name = match.group("name").split("-py")[0]
version = match.group("version").split(".chromium")[0]
vpython_packages[name] = version
poetry_packages = {}
for package_block in poetry_lock_content.split("[[package]]"):
if not package_block.strip():
continue
match = poetry_re.search(package_block)
if match:
name = match.group("name")
version = match.group("version")
poetry_packages[name] = version
self.assertGreater(
len(vpython_packages), 0, "No packages found in .vpython3")
self.assertGreater(
len(poetry_packages), 0, "No packages found in poetry.lock")
vpython_only = sorted(vpython_packages.keys() - poetry_packages.keys())
if vpython_only:
logging.warning("Packages only in .vpython3: %s", ", ".join(vpython_only))
poetry_only = sorted(poetry_packages.keys() - vpython_packages.keys())
if poetry_only:
logging.warning("Packages only in poetry.lock: %s",
", ".join(poetry_only))
mismatches = []
for name, vpython_version_str in vpython_packages.items():
if name not in poetry_packages:
continue
poetry_version_str = poetry_packages[name]
if vpython_version_str == poetry_version_str:
continue
mismatches.append((name, vpython_version_str, poetry_version_str))
mismatches.sort(key=lambda row: row[0])
if mismatches:
headers = ["Package", ".vpython3", "poetry.lock"]
self.fail("Version mismatches found:\n" +
tabulate(mismatches, headers=headers))
def test_dependencies_are_sorted(self):
pyproject_toml_path = ROOT_DIR / "pyproject.toml"
pyproject_toml = tomllib.loads(pyproject_toml_path.read_text())
poetry_options = pyproject_toml["tool"]["poetry"]
dependencies = poetry_options["dependencies"]
sorted_dependencies = sorted(dependencies.keys())
self.assertEqual(
list(dependencies.keys()),
sorted_dependencies,
"Dependencies in pyproject.toml are not sorted alphabetically.",
)
dev_dependencies = poetry_options["group"]["dev"]["dependencies"]
sorted_dev_dependencies = sorted(dev_dependencies.keys())
self.assertEqual(
list(dev_dependencies.keys()),
sorted_dev_dependencies,
"Dev dependencies in pyproject.toml are not sorted alphabetically.",
)
if __name__ == "__main__":
test_helper.run_pytest(__file__)