diff --git a/.github/workflows/rhiza_benchmarks.yml b/.github/workflows/rhiza_benchmarks.yml index 501f2e4..ea92073 100644 --- a/.github/workflows/rhiza_benchmarks.yml +++ b/.github/workflows/rhiza_benchmarks.yml @@ -13,7 +13,7 @@ # - PRs will show a warning comment but not fail # - Main branch updates the baseline for future comparisons -name: (RHIZA) Benchmarks +name: "(RHIZA) BENCHMARKS" permissions: contents: write @@ -32,24 +32,24 @@ jobs: steps: - name: Checkout repository - uses: actions/checkout@v6 + uses: actions/checkout@v6.0.2 with: lfs: true - name: Install uv - uses: astral-sh/setup-uv@v7 + uses: astral-sh/setup-uv@v7.2.1 with: - version: "0.9.26" + version: "0.9.28" python-version: "3.12" - name: Run benchmarks env: - UV_EXTRA_INDEX_URL: ${{ secrets.uv-extra-index-url }} + UV_EXTRA_INDEX_URL: ${{ secrets.UV_EXTRA_INDEX_URL }} run: | make benchmark - name: Upload benchmark results - uses: actions/upload-artifact@v6 + uses: actions/upload-artifact@v6.0.0 if: always() with: name: benchmark-results diff --git a/.github/workflows/rhiza_book.yml b/.github/workflows/rhiza_book.yml index bc75e8e..4e41e48 100644 --- a/.github/workflows/rhiza_book.yml +++ b/.github/workflows/rhiza_book.yml @@ -36,33 +36,33 @@ jobs: steps: # Check out the repository code - - uses: actions/checkout@v6 + - uses: actions/checkout@v6.0.2 with: lfs: true - name: Install uv - uses: astral-sh/setup-uv@v7 + uses: astral-sh/setup-uv@v7.2.1 with: - version: "0.9.26" + version: "0.9.28" - name: "Sync the virtual environment for ${{ github.repository }}" shell: bash env: - UV_EXTRA_INDEX_URL: ${{ secrets.uv-extra-index-url }} + UV_EXTRA_INDEX_URL: ${{ secrets.UV_EXTRA_INDEX_URL }} run: | # will just use .python-version? uv sync --all-extras --all-groups --frozen - name: "Make the book" env: - UV_EXTRA_INDEX_URL: ${{ secrets.uv-extra-index-url }} + UV_EXTRA_INDEX_URL: ${{ secrets.UV_EXTRA_INDEX_URL }} run: | - make -f .rhiza/rhiza.mk book + make book # Step 5: Package all artifacts for GitHub Pages deployment # This prepares the combined outputs for deployment by creating a single artifact - name: Upload static files as artifact - uses: actions/upload-pages-artifact@v4 # Official GitHub Pages artifact upload action + uses: actions/upload-pages-artifact@v4.0.0 # Official GitHub Pages artifact upload action with: path: _book/ # Path to the directory containing all artifacts to deploy @@ -73,5 +73,5 @@ jobs: # If PUBLISH_COMPANION_BOOK is not set, it defaults to allowing deployment - name: Deploy to GitHub Pages if: ${{ !github.event.repository.fork && (vars.PUBLISH_COMPANION_BOOK == 'true' || vars.PUBLISH_COMPANION_BOOK == '') }} - uses: actions/deploy-pages@v4 # Official GitHub Pages deployment action + uses: actions/deploy-pages@v4.0.5 # Official GitHub Pages deployment action continue-on-error: true diff --git a/.github/workflows/rhiza_ci.yml b/.github/workflows/rhiza_ci.yml index 1d6cc5e..dd019eb 100644 --- a/.github/workflows/rhiza_ci.yml +++ b/.github/workflows/rhiza_ci.yml @@ -24,16 +24,18 @@ jobs: outputs: matrix: ${{ steps.versions.outputs.list }} steps: - - uses: actions/checkout@v6 + - uses: actions/checkout@v6.0.2 + with: + lfs: true - name: Install uv - uses: astral-sh/setup-uv@v7 + uses: astral-sh/setup-uv@v7.2.1 with: - version: "0.9.26" + version: "0.9.28" - id: versions env: - UV_EXTRA_INDEX_URL: ${{ secrets.uv-extra-index-url }} + UV_EXTRA_INDEX_URL: ${{ secrets.UV_EXTRA_INDEX_URL }} run: | # Generate Python versions JSON from the script JSON=$(make -f .rhiza/rhiza.mk -s version-matrix) @@ -53,18 +55,36 @@ jobs: steps: - name: Checkout repository - uses: actions/checkout@v6 + uses: actions/checkout@v6.0.2 with: lfs: true - name: Install uv - uses: astral-sh/setup-uv@v7 + uses: astral-sh/setup-uv@v7.2.1 with: - version: "0.9.26" + version: "0.9.28" python-version: ${{ matrix.python-version }} - name: Run tests env: - UV_EXTRA_INDEX_URL: ${{ secrets.uv-extra-index-url }} + UV_EXTRA_INDEX_URL: ${{ secrets.UV_EXTRA_INDEX_URL }} + run: | + make test + + + docs-coverage: + runs-on: ubuntu-latest + steps: + - name: Checkout repository + uses: actions/checkout@v6.0.2 + + - name: Install uv + uses: astral-sh/setup-uv@v7.2.1 + with: + version: "0.9.28" + + - name: Check docs coverage + env: + UV_EXTRA_INDEX_URL: ${{ secrets.UV_EXTRA_INDEX_URL }} run: | - make -f .rhiza/rhiza.mk test + make docs-coverage diff --git a/.github/workflows/rhiza_codeql.yml b/.github/workflows/rhiza_codeql.yml index aca6b23..274f8f6 100644 --- a/.github/workflows/rhiza_codeql.yml +++ b/.github/workflows/rhiza_codeql.yml @@ -81,7 +81,7 @@ jobs: # your codebase is analyzed, see https://docs.github.com/en/code-security/code-scanning/creating-an-advanced-setup-for-code-scanning/codeql-code-scanning-for-compiled-languages steps: - name: Checkout repository - uses: actions/checkout@v6 + uses: actions/checkout@v6.0.2 # Add any setup steps before running the `github/codeql-action/init` action. # This includes steps like installing compilers or runtimes (`actions/setup-node` @@ -91,7 +91,7 @@ jobs: # Initializes the CodeQL tools for scanning. - name: Initialize CodeQL - uses: github/codeql-action/init@v4 + uses: github/codeql-action/init@v4.32.0 with: languages: ${{ matrix.language }} build-mode: ${{ matrix.build-mode }} @@ -120,6 +120,6 @@ jobs: exit 1 - name: Perform CodeQL Analysis - uses: github/codeql-action/analyze@v4 + uses: github/codeql-action/analyze@v4.32.0 with: category: "/language:${{matrix.language}}" diff --git a/.github/workflows/rhiza_deptry.yml b/.github/workflows/rhiza_deptry.yml index e949af7..f4e6f0c 100644 --- a/.github/workflows/rhiza_deptry.yml +++ b/.github/workflows/rhiza_deptry.yml @@ -27,13 +27,13 @@ jobs: name: Check dependencies with deptry runs-on: ubuntu-latest container: - image: ghcr.io/astral-sh/uv:0.9.26-python3.12-trixie + image: ghcr.io/astral-sh/uv:0.9.28-bookworm steps: - - uses: actions/checkout@v6 + - uses: actions/checkout@v6.0.2 - name: Run deptry - run: make -f .rhiza/rhiza.mk deptry + run: make deptry # NOTE: make deptry is good style because it encapsulates the folders to check # (e.g. src and book/marimo) and keeps CI in sync with local development. # Since we use a 'uv' container, the Makefile is optimised to use the diff --git a/.github/workflows/rhiza_devcontainer.yml b/.github/workflows/rhiza_devcontainer.yml index 53c9916..1beda4e 100644 --- a/.github/workflows/rhiza_devcontainer.yml +++ b/.github/workflows/rhiza_devcontainer.yml @@ -58,7 +58,7 @@ jobs: runs-on: ubuntu-latest steps: - name: Checkout repository - uses: actions/checkout@v6 + uses: actions/checkout@v6.0.2 - name: Set registry id: registry @@ -70,7 +70,7 @@ jobs: echo "registry=$REGISTRY" >> "$GITHUB_OUTPUT" - name: Login to Container Registry - uses: docker/login-action@v3 + uses: docker/login-action@v3.7.0 with: registry: ${{ steps.registry.outputs.registry }} username: ${{ github.repository_owner }} diff --git a/.github/workflows/rhiza_docker.yml b/.github/workflows/rhiza_docker.yml index 699882e..2e9b440 100644 --- a/.github/workflows/rhiza_docker.yml +++ b/.github/workflows/rhiza_docker.yml @@ -24,7 +24,7 @@ jobs: steps: - name: Checkout repository - uses: actions/checkout@v6 + uses: actions/checkout@v6.0.2 - name: Detect docker/Dockerfile presence id: check_dockerfile @@ -49,7 +49,7 @@ jobs: - name: Set up Docker Buildx if: ${{ steps.check_dockerfile.outputs.docker_present == 'true' }} - uses: docker/setup-buildx-action@v3 + uses: docker/setup-buildx-action@v3.12.0 - name: Read Python version from .python-version if: ${{ steps.check_dockerfile.outputs.docker_present == 'true' }} diff --git a/.github/workflows/rhiza_marimo.yml b/.github/workflows/rhiza_marimo.yml index 4a47193..0b09c23 100644 --- a/.github/workflows/rhiza_marimo.yml +++ b/.github/workflows/rhiza_marimo.yml @@ -34,7 +34,7 @@ jobs: notebook-list: ${{ steps.notebooks.outputs.matrix }} steps: # Check out the repository code - - uses: actions/checkout@v6 + - uses: actions/checkout@v6.0.2 # Find all Python files in the marimo folder and create a matrix for parallel execution - name: Find notebooks and build matrix @@ -75,18 +75,20 @@ jobs: name: Run notebook ${{ matrix.notebook }} steps: # Check out the repository code - - uses: actions/checkout@v6 + - uses: actions/checkout@v6.0.2 with: lfs: true # Install uv/uvx - name: Install uv - uses: astral-sh/setup-uv@v7 + uses: astral-sh/setup-uv@v7.2.1 with: - version: "0.9.26" + version: "0.9.28" # Execute the notebook with the appropriate runner based on its content - name: Run notebook + env: + UV_EXTRA_INDEX_URL: ${{ secrets.UV_EXTRA_INDEX_URL }} run: | uvx uv run "${{ matrix.notebook }}" # uvx โ†’ creates a fresh ephemeral environment diff --git a/.github/workflows/rhiza_mypy.yml b/.github/workflows/rhiza_mypy.yml index 886676e..cf8ea24 100644 --- a/.github/workflows/rhiza_mypy.yml +++ b/.github/workflows/rhiza_mypy.yml @@ -24,7 +24,7 @@ jobs: name: Static type checking with mypy runs-on: ubuntu-latest container: - image: ghcr.io/astral-sh/uv:0.9.26-python3.12-trixie + image: ghcr.io/astral-sh/uv:0.9.28-bookworm steps: - uses: actions/checkout@v6 diff --git a/.github/workflows/rhiza_pre-commit.yml b/.github/workflows/rhiza_pre-commit.yml index 4190d4d..2ee1877 100644 --- a/.github/workflows/rhiza_pre-commit.yml +++ b/.github/workflows/rhiza_pre-commit.yml @@ -29,9 +29,9 @@ jobs: runs-on: ubuntu-latest steps: - - uses: actions/checkout@v6 + - uses: actions/checkout@v6.0.2 # Run pre-commit - name: Run pre-commit run: | - make -f .rhiza/rhiza.mk fmt + make fmt diff --git a/.github/workflows/rhiza_release.yml b/.github/workflows/rhiza_release.yml index 4f2906c..aa10bca 100644 --- a/.github/workflows/rhiza_release.yml +++ b/.github/workflows/rhiza_release.yml @@ -33,7 +33,7 @@ # - No PyPI credentials stored; relies on Trusted Publishing via GitHub OIDC # - For custom feeds, PYPI_TOKEN secret is used with default username __token__ # - Container registry uses GITHUB_TOKEN for authentication -# - SLSA provenance attestations generated for build artifacts (supply chain security) +# - SLSA provenance attestations generated for build artifacts (public repos only) # # ๐Ÿ“„ Requirements: # - pyproject.toml with top-level version field (for Python packages) @@ -65,7 +65,7 @@ permissions: contents: write # Needed to create releases id-token: write # Needed for OIDC authentication with PyPI packages: write # Needed to publish devcontainer image - attestations: write # Needed for SLSA provenance attestations + attestations: write # Needed for SLSA provenance attestations (public repos only) jobs: tag: @@ -75,7 +75,7 @@ jobs: tag: ${{ steps.set_tag.outputs.tag }} steps: - name: Checkout Code - uses: actions/checkout@v6 + uses: actions/checkout@v6.0.2 with: fetch-depth: 0 @@ -106,21 +106,14 @@ jobs: needs: tag steps: - name: Checkout Code - uses: actions/checkout@v6 + uses: actions/checkout@v6.0.2 with: fetch-depth: 0 - name: Install uv - uses: astral-sh/setup-uv@v7 + uses: astral-sh/setup-uv@v7.2.1 with: - version: "0.9.26" - - - name: "Sync the virtual environment for ${{ github.repository }}" - shell: bash - run: | - export UV_EXTRA_INDEX_URL="${{ secrets.uv-extra-index-url }}" - # will just use .python-version? - uv sync --all-extras --all-groups --frozen + version: "0.9.28" - name: Verify version matches tag if: hashFiles('pyproject.toml') != '' @@ -151,14 +144,14 @@ jobs: uvx hatch build - name: Generate SLSA provenance attestations - if: steps.buildable.outputs.buildable == 'true' + if: steps.buildable.outputs.buildable == 'true' && github.event.repository.private == false uses: actions/attest-build-provenance@v3 with: subject-path: dist/* - name: Upload dist artifact if: steps.buildable.outputs.buildable == 'true' - uses: actions/upload-artifact@v6 + uses: actions/upload-artifact@v6.0.0 with: name: dist path: dist @@ -189,12 +182,12 @@ jobs: steps: - name: Checkout Code - uses: actions/checkout@v6 + uses: actions/checkout@v6.0.2 with: fetch-depth: 0 - name: Download dist artifact - uses: actions/download-artifact@v7 + uses: actions/download-artifact@v7.0.0 with: name: dist path: dist @@ -237,7 +230,7 @@ jobs: image_name: ${{ steps.image_name.outputs.image_name }} steps: - name: Checkout Code - uses: actions/checkout@v6 + uses: actions/checkout@v6.0.2 with: fetch-depth: 0 @@ -266,7 +259,7 @@ jobs: - name: Login to Container Registry if: steps.check_publish.outputs.should_publish == 'true' - uses: docker/login-action@v3 + uses: docker/login-action@v3.7.0 with: registry: ${{ steps.registry.outputs.registry }} username: ${{ github.repository_owner }} @@ -322,24 +315,24 @@ jobs: if: needs.pypi.result == 'success' || needs.devcontainer.result == 'success' steps: - name: Checkout Code - uses: actions/checkout@v6 + uses: actions/checkout@v6.0.2 with: fetch-depth: 0 - name: Install uv - uses: astral-sh/setup-uv@v7 + uses: astral-sh/setup-uv@v7.2.1 with: - version: "0.9.26" + version: "0.9.28" - name: "Sync the virtual environment for ${{ github.repository }}" shell: bash run: | - export UV_EXTRA_INDEX_URL="${{ secrets.uv-extra-index-url }}" + export UV_EXTRA_INDEX_URL="${{ secrets.uv_extra_index_url }}" # will just use .python-version? uv sync --all-extras --all-groups --frozen - name: Set up Python - uses: actions/setup-python@v6 + uses: actions/setup-python@v6.2.0 - name: Generate Devcontainer Link id: devcontainer_link @@ -358,7 +351,7 @@ jobs: id: pypi_link if: needs.pypi.outputs.should_publish == 'true' && needs.pypi.result == 'success' run: | - PACKAGE_NAME=$(python3 -c "import tomllib; print(tomllib.load(open('pyproject.toml', 'rb'))['project']['name'])") + PACKAGE_NAME=$(uv run python -c "import tomllib; print(tomllib.load(open('pyproject.toml', 'rb'))['project']['name'])") VERSION="${{ needs.tag.outputs.tag }}" VERSION=${VERSION#v} diff --git a/.github/workflows/rhiza_security.yml b/.github/workflows/rhiza_security.yml index 0effa48..26b56e8 100644 --- a/.github/workflows/rhiza_security.yml +++ b/.github/workflows/rhiza_security.yml @@ -27,12 +27,18 @@ jobs: name: Security scanning runs-on: ubuntu-latest container: - image: ghcr.io/astral-sh/uv:0.9.26-python3.12-trixie + image: ghcr.io/astral-sh/uv:0.9.28-bookworm steps: - - uses: actions/checkout@v6 + - uses: actions/checkout@v6.0.2 - name: Run security scans env: - UV_EXTRA_INDEX_URL: ${{ secrets.uv-extra-index-url }} + UV_EXTRA_INDEX_URL: ${{ secrets.UV_EXTRA_INDEX_URL }} run: make security + + + - name: Run typecheck + env: + UV_EXTRA_INDEX_URL: ${{ secrets.UV_EXTRA_INDEX_URL }} + run: make typecheck diff --git a/.github/workflows/rhiza_sync.yml b/.github/workflows/rhiza_sync.yml index ea218ea..cf1f664 100644 --- a/.github/workflows/rhiza_sync.yml +++ b/.github/workflows/rhiza_sync.yml @@ -26,7 +26,7 @@ jobs: steps: - name: Checkout repository - uses: actions/checkout@v6 + uses: actions/checkout@v6.0.2 with: token: ${{ secrets.PAT_TOKEN || github.token }} fetch-depth: 0 @@ -50,7 +50,7 @@ jobs: fi - name: Install uv - uses: astral-sh/setup-uv@v7 + uses: astral-sh/setup-uv@v7.2.1 - name: Get Rhiza version id: rhiza-version @@ -90,7 +90,7 @@ jobs: if: > (github.event_name == 'schedule' || inputs.create-pr == true) && steps.sync.outputs.changes_detected == 'true' - uses: peter-evans/create-pull-request@v8 + uses: peter-evans/create-pull-request@v8.1.0 with: token: ${{ secrets.PAT_TOKEN || github.token }} base: ${{ github.event.repository.default_branch }} diff --git a/.github/workflows/rhiza_validate.yml b/.github/workflows/rhiza_validate.yml index 7190db0..c494357 100644 --- a/.github/workflows/rhiza_validate.yml +++ b/.github/workflows/rhiza_validate.yml @@ -15,11 +15,11 @@ jobs: # don't run this in rhiza itself. Rhiza has no template.yml file. if: ${{ github.repository != 'jebel-quant/rhiza' }} container: - image: ghcr.io/astral-sh/uv:0.9.26-python3.12-trixie + image: ghcr.io/astral-sh/uv:0.9.28-bookworm steps: - name: Checkout repository - uses: actions/checkout@v6 + uses: actions/checkout@v6.0.2 - name: Validate Rhiza config shell: bash diff --git a/.pre-commit-config.yaml b/.pre-commit-config.yaml index ad857a2..07bb360 100644 --- a/.pre-commit-config.yaml +++ b/.pre-commit-config.yaml @@ -24,7 +24,7 @@ repos: args: ["--disable", "MD013"] - repo: https://github.com/python-jsonschema/check-jsonschema - rev: 0.36.0 + rev: 0.36.1 hooks: - id: check-renovate args: [ "--verbose" ] diff --git a/.rhiza/history b/.rhiza/history index b15951b..4aa0e4b 100644 --- a/.rhiza/history +++ b/.rhiza/history @@ -61,6 +61,7 @@ presentation/presentation.mk pytest.ini ruff.toml tests/test_rhiza/README.md +tests/test_rhiza/__init__.py tests/test_rhiza/benchmarks/.gitignore tests/test_rhiza/benchmarks/README.md tests/test_rhiza/benchmarks/analyze_benchmarks.py diff --git a/.rhiza/rhiza.mk b/.rhiza/rhiza.mk index a03538b..62ea5e3 100644 --- a/.rhiza/rhiza.mk +++ b/.rhiza/rhiza.mk @@ -240,9 +240,9 @@ deptry: install-uv ## Run deptry fmt: install-uv ## check the pre-commit hooks and the linting @${UVX_BIN} -p ${PYTHON_VERSION} pre-commit run --all-files -mypy: install-uv ## run mypy analysis +mypy: install ## run mypy analysis @if [ -d ${SOURCE_FOLDER} ]; then \ - ${UVX_BIN} -p ${PYTHON_VERSION} mypy ${SOURCE_FOLDER} --strict --config-file=pyproject.toml; \ + ${UV_BIN} run mypy ${SOURCE_FOLDER} --strict --config-file=pyproject.toml; \ fi ##@ Releasing and Versioning diff --git a/.rhiza/scripts/check_workflow_names.py b/.rhiza/scripts/check_workflow_names.py index e4a838d..4771e3c 100644 --- a/.rhiza/scripts/check_workflow_names.py +++ b/.rhiza/scripts/check_workflow_names.py @@ -31,8 +31,17 @@ def check_file(filepath): print(f"Error: {filepath} missing 'name' field.") return False - if not name.startswith("(RHIZA) "): - print(f"Updating {filepath}: name '{name}' -> '(RHIZA) {name}'") + prefix = "(RHIZA) " + # Remove prefix if present to verify the rest of the string + if name.startswith(prefix): + clean_name = name[len(prefix) :] + else: + clean_name = name + + expected_name = f"{prefix}{clean_name.upper()}" + + if name != expected_name: + print(f"Updating {filepath}: name '{name}' -> '{expected_name}'") # Read file lines to perform replacement while preserving comments with open(filepath) as f_read: @@ -47,7 +56,7 @@ def check_file(filepath): # Simple check: does it contain reasonable parts of the name? # Or just blinding replace top-level name: # We'll use quotes to be safe - f_write.write(f'name: "(RHIZA) {name}"\n') + f_write.write(f'name: "{expected_name}"\n') replaced = True else: f_write.write(line) diff --git a/tests/test_rhiza/__init__.py b/tests/test_rhiza/__init__.py new file mode 100644 index 0000000..20e4136 --- /dev/null +++ b/tests/test_rhiza/__init__.py @@ -0,0 +1,5 @@ +"""Test package for rhiza tests. + +This file makes test_rhiza a Python package, enabling relative imports +within the test modules (e.g., from .conftest import ...). +""" diff --git a/tests/test_rhiza/test_book.py b/tests/test_rhiza/test_book.py index c9d560a..00bb3f9 100644 --- a/tests/test_rhiza/test_book.py +++ b/tests/test_rhiza/test_book.py @@ -10,7 +10,8 @@ def test_no_book_folder(git_repo): """Test that make targets fail gracefully when book folder is missing.""" - shutil.rmtree(git_repo / "book") + if (git_repo / "book").exists(): + shutil.rmtree(git_repo / "book") assert not (git_repo / "book").exists() for target in ["book", "docs", "marimushka"]: @@ -24,7 +25,8 @@ def test_no_book_folder(git_repo): def test_book_folder_but_no_mk(git_repo): """Test behavior when book folder exists but book.mk is missing.""" # ensure book folder exists but has no Makefile - shutil.rmtree(git_repo / "book") + if (git_repo / "book").exists(): + shutil.rmtree(git_repo / "book") # create an empty book folder. Make treats an existing directory as an โ€œup-to-dateโ€ target. (git_repo / "book").mkdir() diff --git a/tests/test_rhiza/test_check_workflow_names.py b/tests/test_rhiza/test_check_workflow_names.py index 9651172..76c0447 100644 --- a/tests/test_rhiza/test_check_workflow_names.py +++ b/tests/test_rhiza/test_check_workflow_names.py @@ -18,7 +18,7 @@ class TestCheckFile: def test_correct_prefix_returns_true(self, tmp_path): """File with correct (RHIZA) prefix returns True.""" workflow = tmp_path / "workflow.yml" - workflow.write_text('name: "(RHIZA) My Workflow"\non: push\n') + workflow.write_text('name: "(RHIZA) MY WORKFLOW"\non: push\n') assert check_file(str(workflow)) is True @@ -31,7 +31,7 @@ def test_missing_prefix_updates_file(self, tmp_path): assert result is False content = workflow.read_text() - assert "(RHIZA) My Workflow" in content + assert "(RHIZA) MY WORKFLOW" in content def test_missing_name_field_returns_false(self, tmp_path, capsys): """File without name field returns False with error message.""" @@ -84,7 +84,7 @@ def test_preserves_other_content(self, tmp_path): content = workflow.read_text() # Check name was updated - assert "(RHIZA) CI Pipeline" in content + assert "(RHIZA) CI PIPELINE" in content # Check other content preserved assert "branches: [main]" in content assert "runs-on: ubuntu-latest" in content @@ -93,14 +93,14 @@ def test_preserves_other_content(self, tmp_path): def test_quoted_name_with_prefix(self, tmp_path): """File with quoted name containing prefix returns True.""" workflow = tmp_path / "workflow.yml" - workflow.write_text('name: "(RHIZA) Test"\non: push\n') + workflow.write_text('name: "(RHIZA) TEST"\non: push\n') assert check_file(str(workflow)) is True def test_unquoted_name_with_prefix(self, tmp_path): """File with unquoted name containing prefix returns True.""" workflow = tmp_path / "workflow.yml" - workflow.write_text("name: (RHIZA) Test\non: push\n") + workflow.write_text("name: (RHIZA) TEST\non: push\n") assert check_file(str(workflow)) is True @@ -112,4 +112,4 @@ def test_name_with_special_characters(self, tmp_path): check_file(str(workflow)) content = workflow.read_text() - assert "(RHIZA) Build & Deploy" in content + assert "(RHIZA) BUILD & DEPLOY" in content diff --git a/tests/test_rhiza/test_docstrings.py b/tests/test_rhiza/test_docstrings.py index c7e6f4d..b77c1c2 100644 --- a/tests/test_rhiza/test_docstrings.py +++ b/tests/test_rhiza/test_docstrings.py @@ -3,7 +3,7 @@ This file and its associated tests flow down via a SYNC action from the jebel-quant/rhiza repository (https://github.com/jebel-quant/rhiza). -Automatically discovers all packages under `src/` and runs doctests for each. +Automatically discovers all packages and runs doctests for each. """ from __future__ import annotations @@ -14,6 +14,7 @@ from pathlib import Path import pytest +from dotenv import dotenv_values def _iter_modules_from_path(logger, package_path: Path): @@ -35,16 +36,23 @@ def _iter_modules_from_path(logger, package_path: Path): continue -def test_doctests(logger, root, monkeypatch: pytest.MonkeyPatch): - """Run doctests for each package directory under src/.""" - src_path = root / "src" +def test_doctests(logger, root, monkeypatch: pytest.MonkeyPatch, capsys: pytest.CaptureFixture[str]): + """Run doctests for each package directory.""" + # Read SOURCE_FOLDER from .rhiza/.env + env_path = root / ".rhiza" / ".env" + values = {} + if env_path.exists(): + values = dotenv_values(env_path) + + source_folder = values.get("SOURCE_FOLDER", "src") + src_path = root / source_folder logger.info("Starting doctest discovery in: %s", src_path) if not src_path.exists(): logger.info("Source directory not found: %s โ€” skipping doctests", src_path) pytest.skip(f"Source directory not found: {src_path}") - # Add src to sys.path with automatic cleanup + # Add source path to sys.path with automatic cleanup monkeypatch.syspath_prepend(str(src_path)) logger.debug("Prepended to sys.path: %s", src_path) @@ -52,7 +60,7 @@ def test_doctests(logger, root, monkeypatch: pytest.MonkeyPatch): total_failures = 0 failed_modules = [] - # Find all packages in src + # Find all packages in the source path for package_dir in src_path.iterdir(): if package_dir.is_dir() and (package_dir / "__init__.py").exists(): # Import the package @@ -64,11 +72,13 @@ def test_doctests(logger, root, monkeypatch: pytest.MonkeyPatch): for module in modules: logger.debug("Running doctests for module: %s", module.__name__) - results = doctest.testmod( - module, - verbose=False, - optionflags=(doctest.ELLIPSIS | doctest.NORMALIZE_WHITESPACE), - ) + # Disable pytest's stdout capture during doctest to avoid interference + with capsys.disabled(): + results = doctest.testmod( + module, + verbose=False, + optionflags=(doctest.ELLIPSIS | doctest.NORMALIZE_WHITESPACE), + ) total_tests += results.attempted if results.failed: diff --git a/tests/test_rhiza/test_makefile.py b/tests/test_rhiza/test_makefile.py index ca8704a..5474366 100644 --- a/tests/test_rhiza/test_makefile.py +++ b/tests/test_rhiza/test_makefile.py @@ -153,7 +153,11 @@ def test_help_target(self, logger): def test_fmt_target_dry_run(self, logger, tmp_path): """Fmt target should invoke pre-commit via uvx with Python version in dry-run output.""" - proc = run_make(logger, ["fmt"]) + # Create clean environment without PYTHON_VERSION so Makefile reads from .python-version + env = os.environ.copy() + env.pop("PYTHON_VERSION", None) + + proc = run_make(logger, ["fmt"], env=env) out = proc.stdout # Check for uvx command with the Python version flag # The PYTHON_VERSION should be read from .python-version file (e.g., "3.12") @@ -178,7 +182,12 @@ def test_deptry_target_dry_run(self, logger, tmp_path): env_content += "\nSOURCE_FOLDER=src\n" env_file.write_text(env_content) - proc = run_make(logger, ["deptry"]) + # Create clean environment without PYTHON_VERSION so Makefile reads from .python-version + env = os.environ.copy() + env.pop("PYTHON_VERSION", None) + + proc = run_make(logger, ["deptry"], env=env) + out = proc.stdout # Check for uvx command with the Python version flag python_version_file = tmp_path / ".python-version" @@ -191,7 +200,7 @@ def test_deptry_target_dry_run(self, logger, tmp_path): assert "deptry src" in out def test_mypy_target_dry_run(self, logger, tmp_path): - """Mypy target should invoke mypy via uvx with Python version in dry-run output.""" + """Mypy target should invoke mypy via uv run in dry-run output.""" # Create a mock SOURCE_FOLDER directory so the mypy command runs source_folder = tmp_path / "src" source_folder.mkdir(exist_ok=True) @@ -204,15 +213,8 @@ def test_mypy_target_dry_run(self, logger, tmp_path): proc = run_make(logger, ["mypy"]) out = proc.stdout - # Check for uvx command with the Python version flag - python_version_file = tmp_path / ".python-version" - if python_version_file.exists(): - python_version = python_version_file.read_text().strip() - assert f"uvx -p {python_version} mypy src --strict --config-file=pyproject.toml" in out - else: - # Fallback check if .python-version doesn't exist - assert "uvx -p" in out - assert "mypy src --strict --config-file=pyproject.toml" in out + # Check for uv run command instead of uvx + assert "uv run mypy src --strict --config-file=pyproject.toml" in out def test_test_target_dry_run(self, logger): """Test target should invoke pytest via uv with coverage and HTML outputs in dry-run output.""" @@ -271,14 +273,13 @@ def test_script_folder_is_github_scripts(self, logger): def test_that_target_coverage_is_configurable(self, logger): """Test target should respond to COVERAGE_FAIL_UNDER variable.""" - # Default case (90%) + # Default case: ensure the flag is present proc = run_make(logger, ["test"]) - assert "--cov-fail-under=90" in proc.stdout + assert "--cov-fail-under=" in proc.stdout - # Override case (80%) - # Note: We pass the variable as an argument to make - proc_override = run_make(logger, ["test", "COVERAGE_FAIL_UNDER=80"]) - assert "--cov-fail-under=80" in proc_override.stdout + # Override case: ensure the flag takes the specific value + proc_override = run_make(logger, ["test", "COVERAGE_FAIL_UNDER=42"]) + assert "--cov-fail-under=42" in proc_override.stdout class TestMakefileRootFixture: diff --git a/tests/test_rhiza/test_rhiza_workflows.py b/tests/test_rhiza/test_rhiza_workflows.py index bc3cb24..b3ee926 100644 --- a/tests/test_rhiza/test_rhiza_workflows.py +++ b/tests/test_rhiza/test_rhiza_workflows.py @@ -17,7 +17,8 @@ from pathlib import Path import pytest -from conftest import run_make, setup_rhiza_git_repo, strip_ansi + +from .conftest import run_make, setup_rhiza_git_repo, strip_ansi @pytest.fixture(autouse=True) diff --git a/tests/tests.mk b/tests/tests.mk index f3d1908..7719139 100644 --- a/tests/tests.mk +++ b/tests/tests.mk @@ -50,7 +50,7 @@ test: install ## run all tests typecheck: install ## run mypy type checking @if [ -d ${SOURCE_FOLDER} ]; then \ printf "${BLUE}[INFO] Running mypy type checking...${RESET}\n"; \ - ${UVX_BIN} mypy ${SOURCE_FOLDER} --config-file pyproject.toml; \ + ${UV_BIN} run mypy ${SOURCE_FOLDER} --config-file pyproject.toml; \ else \ printf "${YELLOW}[WARN] Source folder ${SOURCE_FOLDER} not found, skipping typecheck${RESET}\n"; \ fi