diff --git a/.github/workflows/tests.yml b/.github/workflows/tests.yml index a5507a9..83fdce8 100644 --- a/.github/workflows/tests.yml +++ b/.github/workflows/tests.yml @@ -13,7 +13,14 @@ concurrency: jobs: validate: - runs-on: macos-latest + name: Validate (${{ matrix.os }}) + strategy: + fail-fast: false + matrix: + os: + - macos-latest + - ubuntu-latest + runs-on: ${{ matrix.os }} timeout-minutes: 10 steps: - uses: actions/checkout@3d3c42e5aac5ba805825da76410c181273ba90b1 # v7.0.1 @@ -27,3 +34,43 @@ jobs: run: python -m pip install ".[dev]" - name: Run Python tests run: python -m pytest + + linux-distributions: + name: Validate (${{ matrix.name }}) + runs-on: ubuntu-latest + strategy: + fail-fast: false + matrix: + include: + - name: Debian 12 + image: debian:12-slim + family: debian + - name: Fedora latest + image: fedora:latest + family: fedora + steps: + - uses: actions/checkout@3d3c42e5aac5ba805825da76410c181273ba90b1 # v7.0.1 + - name: Run distribution validation in Docker + env: + DISTRO_IMAGE: ${{ matrix.image }} + DISTRO_FAMILY: ${{ matrix.family }} + run: | + docker run --rm \ + --volume "$GITHUB_WORKSPACE:/workspace" \ + --workdir /workspace \ + --env DISTRO_FAMILY \ + "$DISTRO_IMAGE" \ + sh -lc ' + set -eu + if [ "$DISTRO_FAMILY" = debian ]; then + apt-get update + DEBIAN_FRONTEND=noninteractive apt-get install -y bash python3 python3-pip python3-venv + else + dnf install -y python3 python3-pip + fi + ./tests/validate.sh + python3 -m venv /tmp/base-cli-venv + /tmp/base-cli-venv/bin/python -m pip install ".[dev]" + /tmp/base-cli-venv/bin/python -m pytest + /tmp/base-cli-venv/bin/python -c "import base_cli; print(base_cli.__version__)" + ' diff --git a/CHANGELOG.md b/CHANGELOG.md index 9603a75..b16f056 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -12,6 +12,7 @@ and versions are tracked in the repo-root `VERSION` file. locking backends. - Make terminal detection tolerate closed streams and record `COMSPEC` when Windows has no `SHELL` environment variable. +- Add Linux distribution and WSL2 validation guidance for the generic package. ## [0.2.0] - 2026-08-01 diff --git a/docs/platform-support.md b/docs/platform-support.md new file mode 100644 index 0000000..3841e9d --- /dev/null +++ b/docs/platform-support.md @@ -0,0 +1,27 @@ +# Platform support + +`base-cli` is a pure-Python framework. Its Linux support is distribution-neutral +and is validated on Ubuntu, Debian, and Fedora-family environments. The +package does not install or manage operating-system packages; consumers remain +responsible for Python and any external tools their commands need. + +## WSL2 + +WSL2 is supported when Python runs inside the Linux distribution. Validate a +checkout from the WSL shell with: + +```bash +python3 -m venv .venv +.venv/bin/python -m pip install --upgrade pip +.venv/bin/python -m pip install ".[dev]" +.venv/bin/python -m pytest +``` + +Prefer a checkout in the WSL filesystem (for example, under `~/work`) for +normal development. Windows-mounted paths such as `/mnt/c` remain usable, but +their filesystem performance, case-sensitivity, and permission behavior are +provided by the Windows mount and are outside the Linux filesystem contract. + +WSL2 support does not imply that the generic package translates paths between +Linux and Windows or that a consumer's native Windows commands are available +inside the distribution. diff --git a/tests/test_app_runtime_errors.py b/tests/test_app_runtime_errors.py index bc55dfa..2e2354d 100644 --- a/tests/test_app_runtime_errors.py +++ b/tests/test_app_runtime_errors.py @@ -42,7 +42,7 @@ def main(ctx: base_cli.Context) -> None: invoke(app, []) @unittest.skipUnless(importlib.util.find_spec("click"), "Click is not installed") - def test_run_app_reports_unwritable_cache_root_without_traceback(self) -> None: + def test_run_app_reports_unusable_cache_root_without_traceback(self) -> None: app = generic_app(name="cache-failure", version="0.1.0") @app.command() @@ -55,18 +55,17 @@ def main(ctx: base_cli.Context) -> None: home = root / "home" cache_root = root / "cache-root" home.mkdir() - cache_root.mkdir() - cache_root.chmod(0o500) + # A regular file is unusable as a cache root on every platform and + # also behaves consistently when the test suite runs as root in a + # Linux distribution container (where mode bits are bypassed). + cache_root.write_text("not a directory", encoding="utf-8") stderr = io.StringIO() - try: - with mock.patch.dict(os.environ, {"HOME": str(home), "BASE_CLI_CACHE_DIR": str(cache_root)}): - with redirect_stderr(stderr): - try: - exit_code = base_cli.run_app(app, []) - except PermissionError as exc: - self.fail(f"run_app should handle context creation permission errors: {exc}") - finally: - cache_root.chmod(0o700) + with mock.patch.dict(os.environ, {"HOME": str(home), "BASE_CLI_CACHE_DIR": str(cache_root)}): + with redirect_stderr(stderr): + try: + exit_code = base_cli.run_app(app, []) + except PermissionError as exc: + self.fail(f"run_app should handle context creation permission errors: {exc}") error = stderr.getvalue() self.assertEqual(exit_code, 1)