diff options
author | toofar <toofar@spalge.com> | 2024-08-10 12:16:57 +1200 |
---|---|---|
committer | toofar <toofar@spalge.com> | 2024-09-18 11:37:15 +1200 |
commit | a7277a09544e30c223fd7ea1a341eb965d3f176b (patch) | |
tree | fa61d741b4ebf96b1e5471b08ecc42a4a7b6cee5 | |
parent | 0dea21ce97902e4d81b89150398ed38b94483571 (diff) | |
download | qutebrowser-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.py | 8 | ||||
-rw-r--r-- | tests/end2end/test_recompile_requirements.py | 214 |
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()}" |