summaryrefslogtreecommitdiff
diff options
context:
space:
mode:
authortoofar <toofar@spalge.com>2024-08-10 12:16:57 +1200
committertoofar <toofar@spalge.com>2024-09-18 11:37:15 +1200
commita7277a09544e30c223fd7ea1a341eb965d3f176b (patch)
treefa61d741b4ebf96b1e5471b08ecc42a4a7b6cee5
parent0dea21ce97902e4d81b89150398ed38b94483571 (diff)
downloadqutebrowser-a7277a09544e30c223fd7ea1a341eb965d3f176b.tar.gz
qutebrowser-a7277a09544e30c223fd7ea1a341eb965d3f176b.zip
Add tests for comments supported by recompile_requirements
Since I was looking at how hard it would be to support using pip-compile to recompute requirements, I was worried that I would break the markers we support in the raw requirements files. This adds two tests: * disabled_test_markers_real_pip_and_venv A test that sets up a local python index and runs the real pip/uv binaries. It works (and it was fun to setup a package index factory) but has a couple of downsides: 1. it hits the real pypi index, which is not great in a test. This can be prevented by removing the EXTRA bit from the INDEX_URL env vars and pre-downloading pip, wheel, setuptools and uv to the test repo (and adding index.htmls for them). But because of the next item I'm not sure it's worth the effort of keeping this test around 2. it's slow because of having to download packages from the internet (even if we pre-cached them it would still have to download them, I guess we could include a zip of fixed/vendored versions, but that will probably require maintenance over time) and because it cals venv to make new virtual environments, which isn't the quickest operation (maybe uv venv is quicker?) * test_markers_in_comments Tests just the comment reading and line manipulation logic. Could be made a bit more pure by just calling read_comments() and convert_line(), but that wouldn't test that "add" marker.
-rw-r--r--scripts/dev/recompile_requirements.py8
-rw-r--r--tests/end2end/test_recompile_requirements.py214
2 files changed, 220 insertions, 2 deletions
diff --git a/scripts/dev/recompile_requirements.py b/scripts/dev/recompile_requirements.py
index 270f1046a..e1958b1f0 100644
--- a/scripts/dev/recompile_requirements.py
+++ b/scripts/dev/recompile_requirements.py
@@ -29,6 +29,11 @@ REQ_DIR = os.path.join(REPO_DIR, 'misc', 'requirements')
CHANGELOG_URLS_PATH = pathlib.Path(__file__).parent / "changelog_urls.json"
CHANGELOG_URLS = json.loads(CHANGELOG_URLS_PATH.read_text())
+PREAMBLE = (
+ "# This file is automatically generated by "
+ "scripts/dev/recompile_requirements.py\n\n"
+)
+
def normalize_pkg(name):
"""Normalize a package name for comparisons.
@@ -430,8 +435,7 @@ def build_requirements(name, mode):
outfile = get_outfile(name)
with open(outfile, 'w', encoding='utf-8') as f:
- f.write("# This file is automatically generated by "
- "scripts/dev/recompile_requirements.py\n\n")
+ f.write(PREAMBLE)
for line in reqs.splitlines():
if line.startswith('qutebrowser=='):
continue
diff --git a/tests/end2end/test_recompile_requirements.py b/tests/end2end/test_recompile_requirements.py
new file mode 100644
index 000000000..8a7a76220
--- /dev/null
+++ b/tests/end2end/test_recompile_requirements.py
@@ -0,0 +1,214 @@
+import tarfile
+import pathlib
+import shutil
+import textwrap
+
+import pytest
+
+from scripts.dev import recompile_requirements
+
+
+def create_package(repo_dir, name, version, dependencies=[]):
+ # Minimal package structure as per https://packaging-guide.openastronomy.org/en/latest/minimal.html
+ # Install like pip3 install -U --extra-index-url file:///{repo_dir} pkg
+ # `--index-url` doesn't seem to work because this package format seems to
+ # need setuptools to install.
+ pkg_dir = repo_dir / "raw" / name
+ (pkg_dir / name).mkdir(exist_ok=True, parents=True)
+ init_file = pkg_dir / name / "__init__.py"
+ init_file.write_text(f"__version__ = '{version}'")
+ project_file = pkg_dir / "pyproject.toml"
+ project_file.write_text(
+ f"""
+[project]
+name = "{name}"
+dependencies = {dependencies}
+version = "{version}"
+"""
+ )
+
+ # Repo structure:
+ # /
+ # /raw/pkg/ - source of pkg
+ # /simple/index.html - list of packages
+ # /simple/pkg/pkg-1.0.0.tar.gz - installable taball for pkg
+ # /simple/pkg/index.html - list of artifacts for pkg
+ simple = repo_dir / "simple"
+
+ install_dir = simple / name
+ install_dir.mkdir(exist_ok=True, parents=True)
+
+ def fname_filter(info):
+ info.name = str(("/" / pathlib.Path(info.name)).relative_to(pkg_dir))
+ return info
+
+ tarball = install_dir / f"{name}-{version}.tar.gz"
+ with tarfile.open(tarball, "w:gz") as tf:
+ tf.add(pkg_dir, filter=fname_filter)
+ with (install_dir / "index.html").open(mode="w") as file_index:
+ file_index.write("<!DOCTYPE html>\n<html>\n<body>\n")
+ file_index.write(f'<a href="{tarball.parts[-1]}">{name}-{version}</a>\n')
+ file_index.write("</body>\n</html>\n")
+
+ # Regenerate repo index for every new package
+ with (simple / "index.html").open(mode="w") as repo_index:
+ repo_index.write("<!DOCTYPE html>\n<html>\n<body>\n")
+ for package in simple.glob("**/*tar.gz"):
+ repo_index.write(f'<a href="{package.parent.parts[-1]}/">{package.parent.parts[-1]}</a>\n')
+ repo_index.write("</body>\n</html>\n")
+
+
+@pytest.mark.parametrize("reqs, expected", [
+ (
+ """
+ qute-test-1
+ qute-test-2
+ """,
+ """
+ qute-test-1==1.0.0
+ qute-test-2==1.0.0
+ """,
+ ),
+])
+def disabled_test_markers_real_pip_and_venv(reqs, expected, tmp_path, monkeypatch):
+ """Very slow test.
+
+ Slow bits are a) downloading real packages (pip, setuptools, wheel, uv)
+ from pypi.org b) creating the venv.
+ """
+ repo_dir = tmp_path / "test_repo"
+ monkeypatch.setenv("PIP_EXTRA_INDEX_URL", f"file://{repo_dir}/simple/")
+ monkeypatch.setenv("UV_EXTRA_INDEX_URL", f"file://{repo_dir}/simple/")
+
+ create_package(repo_dir, "qute-test-1", "1.0.0")
+ create_package(repo_dir, "qute-test-2", "1.0.0", dependencies=["qute-test-1==1.0.0"])
+
+ monkeypatch.setattr(recompile_requirements, "REQ_DIR", tmp_path)
+
+ (tmp_path / "requirements-test.txt-raw").write_text(
+ textwrap.dedent(reqs)
+ )
+ recompile_requirements.build_requirements("test", mode="compile")
+
+ result = (tmp_path / "requirements-test.txt").read_text()
+ assert result.strip() == f"{recompile_requirements.PREAMBLE}{textwrap.dedent(expected).strip()}"
+
+
+@pytest.mark.parametrize("reqs, initial_compiled, expected", [
+ (
+ """
+ PyQt5==5.15.2
+ PyQtWebEngine==5.15.2
+ #@ filter: PyQt5 == 5.15.2
+ #@ filter: PyQtWebEngine == 5.15.2
+ """,
+ """
+ pyqt5==5.15.2
+ PyQtWebEngine==5.15.2
+ """,
+ """
+ pyqt5==5.15.2 # rq.filter: == 5.15.2
+ PyQtWebEngine==5.15.2 # rq.filter: == 5.15.2
+ """,
+ ),
+ (
+ """
+ PyQt5 >= 5.15, < 5.16
+ PyQtWebEngine >= 5.15, < 5.16
+ #@ filter: PyQt5 < 5.16
+ #@ filter: PyQtWebEngine < 5.16
+ """,
+ """
+ pyqt5==5.15.10
+ PyQtWebEngine==5.15.6
+ """,
+ """
+ pyqt5==5.15.10 # rq.filter: < 5.16
+ PyQtWebEngine==5.15.6 # rq.filter: < 5.16
+ """,
+ ),
+ (
+ """
+ before
+ #@ add: # Unpinned due to recompile_requirements.py limitations
+ #@ add: pyobjc-core ; sys_platform=="darwin"
+ after
+ """,
+ """
+ before
+ after
+ """,
+ """
+ before
+ after
+ # Unpinned due to recompile_requirements.py limitations
+ pyobjc-core ; sys_platform=="darwin"
+ """,
+ ),
+ (
+ """
+ foo
+ Jinja2
+ #@ ignore: Jinja2
+ """,
+ """
+ foo
+ jinja2
+ """,
+ """
+ foo
+ # jinja2
+ """,
+ ),
+ (
+ """
+ importlib_resources
+ typed_ast
+ #@ markers: importlib_resources python_version=="3.8.*"
+ #@ markers: typed_ast python_version<"3.8"
+ """,
+ """
+ importlib-resources
+ typed-ast
+ """,
+ """
+ importlib-resources ; python_version=="3.8.*"
+ typed-ast ; python_version<"3.8"
+ """,
+ ),
+ (
+ """
+ ./scripts/dev/pylint_checkers
+ #@ replace: qute[_-]pylint.* ./scripts/dev/pylint_checkers
+ """,
+ """
+ qute-pylint@/home/runner/qutebrowser/scripts/dev/pylint_checkers
+ """,
+ """
+ ./scripts/dev/pylint_checkers
+ """,
+ ),
+])
+def test_markers_in_comments(reqs, initial_compiled, expected, tmp_path, monkeypatch):
+ """Ensure that each of the comments support in read_comments() works.
+
+ Also tests that packages where their names have been normalized in the
+ initial_compiled output still get processed as expected.
+ """
+ monkeypatch.setattr(recompile_requirements, "REQ_DIR", tmp_path)
+ # Write the raw requirements file with comments in.
+ (tmp_path / "requirements-test.txt-raw").write_text(
+ textwrap.dedent(reqs)
+ )
+ # Mock the actual requirements compilation that involves pip and venv
+ # with raw compiled requirements without our post processing.
+ monkeypatch.setattr(
+ recompile_requirements,
+ "get_updated_requirements_compile",
+ lambda *args: textwrap.dedent(initial_compiled).strip(),
+ )
+
+ recompile_requirements.build_requirements("test", mode="compile")
+
+ result = (tmp_path / "requirements-test.txt").read_text()
+ assert result.strip() == f"{recompile_requirements.PREAMBLE}{textwrap.dedent(expected).strip()}"