From 5356447ca785e04f7a41db6dab31706c02adb097 Mon Sep 17 00:00:00 2001 From: ikappaki Date: Thu, 13 Feb 2025 07:15:26 +0000 Subject: [PATCH 01/11] Pickup bootstrap site-packages location from sysconfig purelib path --- pyproject.toml | 1 + src/basilisp/cli.py | 8 +++--- src/basilisp/main.py | 59 +++++++++++++++++++------------------- tests/basilisp/cli_test.py | 24 ++++++++++++++++ 4 files changed, 59 insertions(+), 33 deletions(-) diff --git a/pyproject.toml b/pyproject.toml index 8cc681d2a..bff11954c 100644 --- a/pyproject.toml +++ b/pyproject.toml @@ -56,6 +56,7 @@ sphinx-copybutton = "^0.5.2" sphinxext-opengraph = "^v0.9.1" furo = "^2023.08.19" tox = "*" +pytest-virtualenv = "^1.8.1" [tool.poetry.extras] pygments = ["pygments"] diff --git a/src/basilisp/cli.py b/src/basilisp/cli.py index b25e9bc73..d761d6ed1 100644 --- a/src/basilisp/cli.py +++ b/src/basilisp/cli.py @@ -425,11 +425,12 @@ def bootstrap_basilisp_installation(_, args: argparse.Namespace) -> None: ): print_("No Basilisp bootstrap files were found.") else: - for file in removed: - print_(f"Removed '{file}'") + if removed is not None: + print_(f"Removed '{removed}'") else: - basilisp.bootstrap_python(site_packages=args.site_packages) + path = basilisp.bootstrap_python(site_packages=args.site_packages) print_( + f"(Added {path})\n\n" "Your Python installation has been bootstrapped! You can undo this at any " "time with with `basilisp bootstrap --uninstall`." ) @@ -473,7 +474,6 @@ def _add_bootstrap_subcommand(parser: argparse.ArgumentParser) -> None: # Not intended to be used by end users. parser.add_argument( "--site-packages", - action="append", help=argparse.SUPPRESS, ) diff --git a/src/basilisp/main.py b/src/basilisp/main.py index dd188017d..d762cae9e 100644 --- a/src/basilisp/main.py +++ b/src/basilisp/main.py @@ -1,5 +1,7 @@ import importlib +import os import site +import sysconfig from pathlib import Path from typing import Optional @@ -56,42 +58,41 @@ def bootstrap( getattr(mod, fn_name)() -def bootstrap_python(site_packages: Optional[list[str]] = None) -> None: - """Bootstrap a Python installation by installing a ``.pth`` file in the first - available ``site-packages`` directory (as by - :external:py:func:`site.getsitepackages`). +def bootstrap_python(site_packages: Optional[str] = None) -> None: + """Bootstrap a Python installation by installing a ``.pth`` file + in ``site-packages`` directory (corresponding to "purelib" in + :external:py:func:`sysconfig.get_paths`). Returns the path to the + ``.pth`` file. Subsequent startups of the Python interpreter will have Basilisp already bootstrapped and available to run.""" if site_packages is None: # pragma: no cover - site_packages = site.getsitepackages() + site_packages = sysconfig.get_paths()["purelib"] - assert site_packages, "Expected at least one site-package directory" + assert site_packages, "Expected a site-package directory" - for d in site_packages: - p = Path(d) - with open(p / "basilispbootstrap.pth", mode="w") as f: - f.write("import basilisp.sitecustomize") - break + pth = Path(site_packages) / "basilispbootstrap.pth" + with open(pth, mode="w") as f: + f.write("import basilisp.sitecustomize") + return pth -def unbootstrap_python(site_packages: Optional[list[str]] = None) -> list[str]: - """Remove any `basilispbootstrap.pth` files found in any Python site-packages - directory (as by :external:py:func:`site.getsitepackages`). Return a list of - removed filenames.""" + +def unbootstrap_python(site_packages: Optional[str] = None) -> str: + """Remove the `basilispbootstrap.pth` file found in the Python site-packages + directory (corresponding to "purelib" in :external:py:func:`sysconfig.get_paths`). + Return the path of the removed file, if any.""" if site_packages is None: # pragma: no cover - site_packages = site.getsitepackages() - - assert site_packages, "Expected at least one site-package directory" - - removed = [] - for d in site_packages: - p = Path(d) - for file in p.glob("basilispbootstrap.pth"): - try: - file.unlink() - except FileNotFoundError: # pragma: no cover - pass - else: - removed.append(str(file)) + site_packages = sysconfig.get_paths()["purelib"] + + assert site_packages, "Expected a site-package directory" + + removed = None + pth = Path(site_packages) / "basilispbootstrap.pth" + try: + os.unlink(pth) + except FileNotFoundError: # pragma: no cover + pass + else: + removed = str(pth) return removed diff --git a/tests/basilisp/cli_test.py b/tests/basilisp/cli_test.py index 8072196a0..8cd521541 100644 --- a/tests/basilisp/cli_test.py +++ b/tests/basilisp/cli_test.py @@ -108,6 +108,7 @@ def test_install(self, tmp_path: pathlib.Path, run_cli): assert bootstrap_file.read_text() == "import basilisp.sitecustomize" assert res.out == ( + f"(Added {bootstrap_file})\n\n" "Your Python installation has been bootstrapped! You can undo this at any " "time with with `basilisp bootstrap --uninstall`.\n" ) @@ -135,6 +136,29 @@ def test_install_quiet(self, tmp_path: pathlib.Path, run_cli, capsys): res = capsys.readouterr() assert res.out == "" + @pytest.mark.skipif( + not os.getenv("GITHUB_ACTIONS"), + reason="Only runs on GitHub CI (time-consuming)", + ) + def test_install_import(self, virtualenv): + virtualenv.install_package(os.path.abspath(".")) + assert "basilisp" in virtualenv.installed_packages().keys() + lpy_file = virtualenv.workspace / "boottest.lpy" + lpy_file.write_text("(ns boottest) (defn abc [] (println (+ 155 4)))") + + cmd = 'python -c "import boottest; boottest.abc()"' + try: + should_fail = virtualenv.run(cmd, capture=True) + assert False, should_fail + except subprocess.CalledProcessError as e: + print(f"Command failed as expected, with exit code {e.returncode}") + + bs = virtualenv.run("basilisp bootstrap", capture=True) + assert "Your Python installation has been bootstrapped!" in bs + + after = virtualenv.run(cmd, capture=True) + assert after.startswith("159") + def test_nothing_to_uninstall(self, tmp_path: pathlib.Path, run_cli, capsys): bootstrap_file = tmp_path / "basilispbootstrap.pth" assert not bootstrap_file.exists() From 02c28db8f0c29664dd76735b81d94145e2bc744c Mon Sep 17 00:00:00 2001 From: ikappaki Date: Thu, 13 Feb 2025 18:53:08 +0000 Subject: [PATCH 02/11] Add CHANGELOG entry, switch CI detection variable to "CI" --- CHANGELOG.md | 1 + tests/basilisp/cli_test.py | 2 +- 2 files changed, 2 insertions(+), 1 deletion(-) diff --git a/CHANGELOG.md b/CHANGELOG.md index 37be0c5ca..17dec7aa7 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -8,6 +8,7 @@ and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0 ### Fixed * Fix a regression introduced in #1176 where the testrunner couldn't handle relative paths in `sys.path`, causing `basilisp test` to fail when no arugments were provided (#1204) * Fix a bug where `basilisp.process/exec` could deadlock reading process output if that output exceeded the buffer size (#1202) + * Fix `basilisp boostrap` issue on MS-Windows where the boostrap file loaded too early, before Basilisp was in `sys.path` (#1208) ## [v0.3.6] ### Added diff --git a/tests/basilisp/cli_test.py b/tests/basilisp/cli_test.py index 8cd521541..1a8d673ba 100644 --- a/tests/basilisp/cli_test.py +++ b/tests/basilisp/cli_test.py @@ -137,7 +137,7 @@ def test_install_quiet(self, tmp_path: pathlib.Path, run_cli, capsys): assert res.out == "" @pytest.mark.skipif( - not os.getenv("GITHUB_ACTIONS"), + not os.getenv("CI"), reason="Only runs on GitHub CI (time-consuming)", ) def test_install_import(self, virtualenv): From 1914ddf142c245764fb33ad563ba0514ecc2bb60 Mon Sep 17 00:00:00 2001 From: ikappaki Date: Thu, 13 Feb 2025 18:58:37 +0000 Subject: [PATCH 03/11] Fix type declarations --- src/basilisp/main.py | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) diff --git a/src/basilisp/main.py b/src/basilisp/main.py index d762cae9e..35c2bc1cb 100644 --- a/src/basilisp/main.py +++ b/src/basilisp/main.py @@ -58,7 +58,7 @@ def bootstrap( getattr(mod, fn_name)() -def bootstrap_python(site_packages: Optional[str] = None) -> None: +def bootstrap_python(site_packages: Optional[str] = None) -> str: """Bootstrap a Python installation by installing a ``.pth`` file in ``site-packages`` directory (corresponding to "purelib" in :external:py:func:`sysconfig.get_paths`). Returns the path to the @@ -75,10 +75,10 @@ def bootstrap_python(site_packages: Optional[str] = None) -> None: with open(pth, mode="w") as f: f.write("import basilisp.sitecustomize") - return pth + return str(pth) -def unbootstrap_python(site_packages: Optional[str] = None) -> str: +def unbootstrap_python(site_packages: Optional[str] = None) -> Optional[str]: """Remove the `basilispbootstrap.pth` file found in the Python site-packages directory (corresponding to "purelib" in :external:py:func:`sysconfig.get_paths`). Return the path of the removed file, if any.""" From ec85d8b7b751658e69bf817b58ea316efff29b0b Mon Sep 17 00:00:00 2001 From: ikappaki Date: Fri, 14 Feb 2025 17:35:27 +0000 Subject: [PATCH 04/11] remove unused site import --- src/basilisp/main.py | 1 - 1 file changed, 1 deletion(-) diff --git a/src/basilisp/main.py b/src/basilisp/main.py index 35c2bc1cb..ea436d010 100644 --- a/src/basilisp/main.py +++ b/src/basilisp/main.py @@ -1,6 +1,5 @@ import importlib import os -import site import sysconfig from pathlib import Path from typing import Optional From 2a3108192524e22d0095d0d251dc3303f3884b81 Mon Sep 17 00:00:00 2001 From: ikappaki Date: Fri, 14 Feb 2025 17:49:37 +0000 Subject: [PATCH 05/11] temporarily uncomment ineffective attempt to only run test on GH CI --- tests/basilisp/cli_test.py | 8 ++++---- 1 file changed, 4 insertions(+), 4 deletions(-) diff --git a/tests/basilisp/cli_test.py b/tests/basilisp/cli_test.py index 1a8d673ba..374a549fc 100644 --- a/tests/basilisp/cli_test.py +++ b/tests/basilisp/cli_test.py @@ -136,10 +136,10 @@ def test_install_quiet(self, tmp_path: pathlib.Path, run_cli, capsys): res = capsys.readouterr() assert res.out == "" - @pytest.mark.skipif( - not os.getenv("CI"), - reason="Only runs on GitHub CI (time-consuming)", - ) + # @pytest.mark.skipif( + # not os.getenv("CI"), + # reason="Only runs on GitHub CI (time-consuming)", + # ) def test_install_import(self, virtualenv): virtualenv.install_package(os.path.abspath(".")) assert "basilisp" in virtualenv.installed_packages().keys() From 1d810cb18cdef2021acfa0f6e45e1c72a71b4da1 Mon Sep 17 00:00:00 2001 From: ikappaki Date: Sat, 15 Feb 2025 11:51:46 +0000 Subject: [PATCH 06/11] import pytest_virtuaenv --- tests/basilisp/cli_test.py | 1 + 1 file changed, 1 insertion(+) diff --git a/tests/basilisp/cli_test.py b/tests/basilisp/cli_test.py index 374a549fc..60cb7d3d3 100644 --- a/tests/basilisp/cli_test.py +++ b/tests/basilisp/cli_test.py @@ -17,6 +17,7 @@ import attr import pytest +import pytest_virtualenv from basilisp.cli import BOOL_FALSE, BOOL_TRUE, invoke_cli from basilisp.prompt import Prompter From 859f0dd73874acb4ceca29c48b23a0ee44207b41 Mon Sep 17 00:00:00 2001 From: ikappaki Date: Sat, 15 Feb 2025 12:09:50 +0000 Subject: [PATCH 07/11] add pytest-virtualenv to tox --- tests/basilisp/cli_test.py | 1 - tox.ini | 1 + 2 files changed, 1 insertion(+), 1 deletion(-) diff --git a/tests/basilisp/cli_test.py b/tests/basilisp/cli_test.py index 60cb7d3d3..374a549fc 100644 --- a/tests/basilisp/cli_test.py +++ b/tests/basilisp/cli_test.py @@ -17,7 +17,6 @@ import attr import pytest -import pytest_virtualenv from basilisp.cli import BOOL_FALSE, BOOL_TRUE, invoke_cli from basilisp.prompt import Prompter diff --git a/tox.ini b/tox.ini index 868a76ba0..fa91fea5f 100644 --- a/tox.ini +++ b/tox.ini @@ -12,6 +12,7 @@ deps = coverage[toml] pytest >=7.0.0,<9.0.0 pytest-xdist >=3.6.1,<4.0.0 + pytest-virtualenv pygments commands = coverage run \ From 5389e8ee6695471093ab7051be362071fb4a74a9 Mon Sep 17 00:00:00 2001 From: ikappaki Date: Sun, 16 Feb 2025 14:01:23 +0000 Subject: [PATCH 08/11] Create `slow` pytest marker for slow tests, add --run-slow option --- tests/basilisp/cli_test.py | 5 +---- tests/conftest.py | 13 +++++++++++++ tox.ini | 2 ++ 3 files changed, 16 insertions(+), 4 deletions(-) diff --git a/tests/basilisp/cli_test.py b/tests/basilisp/cli_test.py index 374a549fc..ee924a59f 100644 --- a/tests/basilisp/cli_test.py +++ b/tests/basilisp/cli_test.py @@ -136,10 +136,7 @@ def test_install_quiet(self, tmp_path: pathlib.Path, run_cli, capsys): res = capsys.readouterr() assert res.out == "" - # @pytest.mark.skipif( - # not os.getenv("CI"), - # reason="Only runs on GitHub CI (time-consuming)", - # ) + @pytest.mark.slow def test_install_import(self, virtualenv): virtualenv.install_package(os.path.abspath(".")) assert "basilisp" in virtualenv.installed_packages().keys() diff --git a/tests/conftest.py b/tests/conftest.py index c6481d5f7..c40f048fa 100644 --- a/tests/conftest.py +++ b/tests/conftest.py @@ -1 +1,14 @@ +import pytest + pytest_plugins = ["pytester"] + +def pytest_configure(config): + config.addinivalue_line("markers", "slow: Marks tests as slow") + +def pytest_addoption(parser): + parser.addoption("--run-slow", action="store_true", help="Run slow tests") + +@pytest.fixture(autouse=True) +def skip_slow(request): + if request.node.get_closest_marker("slow") and not request.config.getoption("--run-slow"): + pytest.skip("Skipping slow test. Use --run-slow to enable.") diff --git a/tox.ini b/tox.ini index fa91fea5f..1d779d0b4 100644 --- a/tox.ini +++ b/tox.ini @@ -21,6 +21,8 @@ commands = -m pytest \ --import-mode=importlib \ --junitxml={toxinidir}/junit/pytest/{envname}.xml \ + # also enable pytest marked as slow \ + --run-slow \ {posargs} [testenv:coverage] From a98d8e083b89ee13d4f0b4ee6130d32e403e5f2b Mon Sep 17 00:00:00 2001 From: ikappaki Date: Sun, 16 Feb 2025 14:23:44 +0000 Subject: [PATCH 09/11] conftest.py format --- tests/conftest.py | 7 ++++++- 1 file changed, 6 insertions(+), 1 deletion(-) diff --git a/tests/conftest.py b/tests/conftest.py index c40f048fa..162f6b808 100644 --- a/tests/conftest.py +++ b/tests/conftest.py @@ -2,13 +2,18 @@ pytest_plugins = ["pytester"] + def pytest_configure(config): config.addinivalue_line("markers", "slow: Marks tests as slow") + def pytest_addoption(parser): parser.addoption("--run-slow", action="store_true", help="Run slow tests") + @pytest.fixture(autouse=True) def skip_slow(request): - if request.node.get_closest_marker("slow") and not request.config.getoption("--run-slow"): + if request.node.get_closest_marker("slow") and not request.config.getoption( + "--run-slow" + ): pytest.skip("Skipping slow test. Use --run-slow to enable.") From e1affe2af0ac2d11803dd27b5f4a21269c7ee2c5 Mon Sep 17 00:00:00 2001 From: ikappaki Date: Wed, 19 Feb 2025 06:04:23 +0000 Subject: [PATCH 10/11] replace pytest_virtualenv with venv module direct calls --- pyproject.toml | 1 - tests/basilisp/cli_test.py | 45 ++++++++++++++++++++++++++------------ tox.ini | 3 +-- 3 files changed, 32 insertions(+), 17 deletions(-) diff --git a/pyproject.toml b/pyproject.toml index bff11954c..8cc681d2a 100644 --- a/pyproject.toml +++ b/pyproject.toml @@ -56,7 +56,6 @@ sphinx-copybutton = "^0.5.2" sphinxext-opengraph = "^v0.9.1" furo = "^2023.08.19" tox = "*" -pytest-virtualenv = "^1.8.1" [tool.poetry.extras] pygments = ["pygments"] diff --git a/tests/basilisp/cli_test.py b/tests/basilisp/cli_test.py index ee924a59f..3fb9812d3 100644 --- a/tests/basilisp/cli_test.py +++ b/tests/basilisp/cli_test.py @@ -10,6 +10,7 @@ import sys import tempfile import time +import venv from collections.abc import Sequence from threading import Thread from typing import Optional @@ -137,24 +138,40 @@ def test_install_quiet(self, tmp_path: pathlib.Path, run_cli, capsys): assert res.out == "" @pytest.mark.slow - def test_install_import(self, virtualenv): - virtualenv.install_package(os.path.abspath(".")) - assert "basilisp" in virtualenv.installed_packages().keys() - lpy_file = virtualenv.workspace / "boottest.lpy" + def test_install_import(self, tmp_path: pathlib.Path): + venv_path = tmp_path / "venv" + venv.create(venv_path, with_pip=True) + + basilisp_path = venv_path / "Scripts" / "basilisp" + if sys.platform == "win32": + pip_path = venv_path / "Scripts" / "pip.exe" + python_path = venv_path / "Scripts" / "python.exe" + else: + pip_path = venv_path / "bin" / "pip" + python_path = venv_path / "bin" / "python" + + cmd = [pip_path, "install", "."] + result = subprocess.run(cmd, capture_output=True, text=True, cwd=os.getcwd()) + + lpy_file = tmp_path / "boottest.lpy" lpy_file.write_text("(ns boottest) (defn abc [] (println (+ 155 4)))") - cmd = 'python -c "import boottest; boottest.abc()"' - try: - should_fail = virtualenv.run(cmd, capture=True) - assert False, should_fail - except subprocess.CalledProcessError as e: - print(f"Command failed as expected, with exit code {e.returncode}") + cmd_import = [python_path, "-c", "import boottest; boottest.abc()"] + result = subprocess.run( + cmd_import, capture_output=True, text=True, cwd=tmp_path + ) + assert "No module named 'boottest'" in result.stderr, result - bs = virtualenv.run("basilisp bootstrap", capture=True) - assert "Your Python installation has been bootstrapped!" in bs + cmd = [basilisp_path, "bootstrap"] + result = subprocess.run(cmd, capture_output=True, text=True, cwd=tmp_path) + assert ( + "Your Python installation has been bootstrapped!" in result.stdout + ), result - after = virtualenv.run(cmd, capture=True) - assert after.startswith("159") + result = subprocess.run( + cmd_import, capture_output=True, text=True, cwd=tmp_path + ) + assert result.stdout.strip() == "159", result def test_nothing_to_uninstall(self, tmp_path: pathlib.Path, run_cli, capsys): bootstrap_file = tmp_path / "basilispbootstrap.pth" diff --git a/tox.ini b/tox.ini index 1d779d0b4..9e6e2014a 100644 --- a/tox.ini +++ b/tox.ini @@ -12,7 +12,6 @@ deps = coverage[toml] pytest >=7.0.0,<9.0.0 pytest-xdist >=3.6.1,<4.0.0 - pytest-virtualenv pygments commands = coverage run \ @@ -22,7 +21,7 @@ commands = --import-mode=importlib \ --junitxml={toxinidir}/junit/pytest/{envname}.xml \ # also enable pytest marked as slow \ - --run-slow \ + --run-slow \ {posargs} [testenv:coverage] From 2f37c2d8b94c392a49351fb036d0c16f82f3efc6 Mon Sep 17 00:00:00 2001 From: ikappaki Date: Wed, 19 Feb 2025 06:22:41 +0000 Subject: [PATCH 11/11] correct unix basilisp path --- tests/basilisp/cli_test.py | 21 ++++++++++----------- 1 file changed, 10 insertions(+), 11 deletions(-) diff --git a/tests/basilisp/cli_test.py b/tests/basilisp/cli_test.py index 3fb9812d3..707245540 100644 --- a/tests/basilisp/cli_test.py +++ b/tests/basilisp/cli_test.py @@ -142,16 +142,14 @@ def test_install_import(self, tmp_path: pathlib.Path): venv_path = tmp_path / "venv" venv.create(venv_path, with_pip=True) - basilisp_path = venv_path / "Scripts" / "basilisp" - if sys.platform == "win32": - pip_path = venv_path / "Scripts" / "pip.exe" - python_path = venv_path / "Scripts" / "python.exe" - else: - pip_path = venv_path / "bin" / "pip" - python_path = venv_path / "bin" / "python" + venv_bin = venv_path / ("Scripts" if sys.platform == "win32" else "bin") + pip_path = venv_bin / "pip" + python_path = venv_bin / "python" + basilisp_path = venv_bin / "basilisp" - cmd = [pip_path, "install", "."] - result = subprocess.run(cmd, capture_output=True, text=True, cwd=os.getcwd()) + result = subprocess.run( + [pip_path, "install", "."], capture_output=True, text=True, cwd=os.getcwd() + ) lpy_file = tmp_path / "boottest.lpy" lpy_file.write_text("(ns boottest) (defn abc [] (println (+ 155 4)))") @@ -162,8 +160,9 @@ def test_install_import(self, tmp_path: pathlib.Path): ) assert "No module named 'boottest'" in result.stderr, result - cmd = [basilisp_path, "bootstrap"] - result = subprocess.run(cmd, capture_output=True, text=True, cwd=tmp_path) + result = subprocess.run( + [basilisp_path, "bootstrap"], capture_output=True, text=True, cwd=tmp_path + ) assert ( "Your Python installation has been bootstrapped!" in result.stdout ), result