From 99159874b487895d5f0200beadd81903346f0df3 Mon Sep 17 00:00:00 2001 From: Dennis Date: Sun, 4 Oct 2026 20:19:06 -0700 Subject: [PATCH 01/11] Measure branch coverage for the hallmark package Configure coverage.py to measure the hallmark package with branch coverage and repository-relative paths, so data from separate CI jobs on Linux and macOS can be combined. Add pytest-cov and pytest-timeout to the dev dependency group and document `pytest --cov` for local runs. (cherry picked from commit 2852809fd4227a812282c2da1f24629b2663ff5b) --- .gitignore | 2 ++ README.rst | 4 ++-- pyproject.toml | 15 ++++++++++++++- 3 files changed, 18 insertions(+), 3 deletions(-) diff --git a/.gitignore b/.gitignore index 3a2b4eb..7e64860 100644 --- a/.gitignore +++ b/.gitignore @@ -7,6 +7,8 @@ venv/ .pytest_cache/ .ruff_cache/ .coverage +.coverage.* +coverage.xml htmlcov/ build/ dist/ diff --git a/README.rst b/README.rst index 04b526b..3bab969 100644 --- a/README.rst +++ b/README.rst @@ -135,8 +135,8 @@ version, operating system, and a minimal example without credentials or private From a development checkout, install the validation tools and run:: - python -m pip install -e . pytest ruff sphinx - python -m pytest + python -m pip install -e . --group dev --group lint --group doc + python -m pytest --cov ruff check --select E,F mod test python -m sphinx -b html -W doc doc/_build diff --git a/pyproject.toml b/pyproject.toml index b5ab0d7..637320a 100644 --- a/pyproject.toml +++ b/pyproject.toml @@ -41,7 +41,7 @@ Repository = "https://github.com/l6a/hallmark" Issues = "https://github.com/l6a/hallmark/issues" [dependency-groups] -dev = ["pytest"] +dev = ["pytest", "pytest-cov", "pytest-timeout"] doc = ["sphinx"] lint = ["ruff"] @@ -62,3 +62,16 @@ markers = [ "ssh_client: exercise the actual local OpenSSH SFTP command parser", "ssh_integration: disposable loopback sshd (HALLMARK_RUN_SSH_TESTS=1 required)", ] + +[tool.coverage.run] +source_pkgs = ["hallmark"] +branch = true +# Store repository-relative paths so data from Linux and macOS jobs combines. +relative_files = true + +[tool.coverage.paths] +hallmark = ["mod/hallmark", "*/mod/hallmark", "*/site-packages/hallmark"] + +[tool.coverage.report] +show_missing = true +skip_covered = true From 034a5ecfd73a5ac10cf70fe07d18dcbce68fe246 Mon Sep 17 00:00:00 2001 From: Dennis Date: Sun, 4 Oct 2026 20:20:40 -0700 Subject: [PATCH 02/11] Run the whole suite with SSH tests in every CI job Replace the separate build and ssh-transport jobs with one test matrix that installs the OpenSSH server and runs the complete suite with HALLMARK_RUN_SSH_TESTS=1 on Python 3.9-3.14 (Linux) and 3.9/3.14 (macOS). Run CI for pull requests into any branch so stacked feature branches are tested, lint once in its own job, update the actions to their current majors, and check the built distributions with twine. Move the ruff exclusions to the top-level table so `ruff check .` no longer parses the shell cells in the demo notebooks. (cherry picked from commit efb2bc971784fa8b1a3fb1b1cc93f068b23c849c) --- .github/workflows/ci.yml | 106 ++++++++++++++++++++++----------------- doc/usecase.rst | 4 +- pyproject.toml | 3 +- 3 files changed, 65 insertions(+), 48 deletions(-) diff --git a/.github/workflows/ci.yml b/.github/workflows/ci.yml index 17a74fc..dcf2570 100644 --- a/.github/workflows/ci.yml +++ b/.github/workflows/ci.yml @@ -1,4 +1,5 @@ -# This workflow will install Python dependencies, run tests and lint with a variety of Python versions +# Lints, tests every supported Python version on Linux and macOS, and checks +# the built distributions and documentation. # For more information see: https://docs.github.com/en/actions/automating-builds-and-tests/building-and-testing-python name: CI @@ -6,74 +7,89 @@ name: CI on: push: branches: [ "main", "dev" ] + # Pull requests into any branch, including stacked feature branches. pull_request: - branches: [ "main", "dev" ] + workflow_dispatch: + +permissions: + contents: read + +concurrency: + group: ci-${{ github.event.pull_request.number || github.ref }} + cancel-in-progress: ${{ github.event_name == 'pull_request' }} jobs: - build: + lint: + runs-on: ubuntu-24.04 + steps: + - uses: actions/checkout@v7 + with: + persist-credentials: false + - uses: actions/setup-python@v7 + with: + python-version: "3.13" + - name: Install ruff + run: python -m pip install ruff + - name: Lint with ruff + run: ruff check . - runs-on: ubuntu-latest + # Every job runs the whole suite, including the loopback OpenSSH tests, so + # each Python version and both operating systems exercise every module. + test: + runs-on: ${{ matrix.os }} + timeout-minutes: 25 strategy: fail-fast: false matrix: - python-version: ["3.9", "3.10","3.11","3.12","3.13","3.14"] - + # Pinned because Python 3.9 builds may not exist for newer images. + os: [ubuntu-24.04] + python-version: ["3.9", "3.10", "3.11", "3.12", "3.13", "3.14"] + include: + - os: macos-latest + python-version: "3.9" + - os: macos-latest + python-version: "3.14" steps: - - uses: actions/checkout@v4 + - uses: actions/checkout@v7 + with: + persist-credentials: false - name: Set up Python ${{ matrix.python-version }} - uses: actions/setup-python@v3 + uses: actions/setup-python@v7 with: python-version: ${{ matrix.python-version }} - - name: Install dependencies + - name: Install the OpenSSH server + if: runner.os == 'Linux' + run: sudo apt-get update && sudo apt-get install -y --no-install-recommends openssh-server + - name: Install Hallmark and test tools run: | python -m pip install --upgrade pip - python -m pip install ruff pytest - python -m pip install -e . - - name: Lint with ruff - run: | - ruff check --select E,F --exclude ./demo + python -m pip install -e . --group dev - name: Test with pytest - run: | - pytest + env: + HALLMARK_RUN_SSH_TESTS: "1" + run: python -m pytest --timeout=600 -ra --durations=15 distribution-and-docs: - runs-on: ubuntu-latest + runs-on: ubuntu-24.04 steps: - - uses: actions/checkout@v4 + - uses: actions/checkout@v7 with: fetch-depth: 0 - - uses: actions/setup-python@v5 + persist-credentials: false + - uses: actions/setup-python@v7 with: - python-version: "3.12" - - name: Build source and wheel distributions + python-version: "3.13" + - name: Build and check source and wheel distributions run: | - python -m pip install build + python -m pip install build twine python -m build + python -m twine check --strict dist/* - name: Install the wheel and documentation tools run: python -m pip install dist/*.whl sphinx - - name: Check the installed CLI - run: hm --help + - name: Check both installed commands + run: | + hm --help + hallmark --help working-directory: ${{ runner.temp }} - name: Build documentation with warnings as errors run: python -m sphinx -b html -W --keep-going doc "${{ runner.temp }}/hallmark-docs" - - ssh-transport: - runs-on: ${{ matrix.os }} - strategy: - fail-fast: false - matrix: - os: [ubuntu-latest, macos-latest] - steps: - - uses: actions/checkout@v4 - - uses: actions/setup-python@v3 - with: - python-version: "3.9" - - name: Install Linux SSH server - if: runner.os == 'Linux' - run: sudo apt-get update && sudo apt-get install -y openssh-server - - name: Install Hallmark and tests - run: python -m pip install -e . pytest - - name: Exercise disposable OpenSSH server and client - env: - HALLMARK_RUN_SSH_TESTS: "1" - run: python -m pytest test/test_ssh_integration.py test/test_transport.py -q diff --git a/doc/usecase.rst b/doc/usecase.rst index c6fd20e..be22692 100644 --- a/doc/usecase.rst +++ b/doc/usecase.rst @@ -357,5 +357,5 @@ SSH configuration and known-hosts files. They do not connect to a lab server or change the user's SSH trust files. Enabling the tests requires the OpenSSH tools to be installed; missing tools cause a test failure. -CI installs the server and runs these tests separately from the -Python-version matrix. +CI installs the server and runs the complete suite, these tests included, +in every Linux and macOS test job. diff --git a/pyproject.toml b/pyproject.toml index 637320a..6f78fb2 100644 --- a/pyproject.toml +++ b/pyproject.toml @@ -50,10 +50,11 @@ where = ["mod"] [tool.ruff] target-version = "py39" +# Excluded at the top level so the shell cells in demo notebooks are not parsed. +extend-exclude = ["doc", "demo"] [tool.ruff.lint] select = ["E", "F"] -exclude = ["doc", "demo"] [tool.pytest.ini_options] testpaths = ["test"] From 09e0665cf36d4daf7072f854de1174be44804d9b Mon Sep 17 00:00:00 2001 From: Dennis Date: Sun, 4 Oct 2026 20:25:25 -0700 Subject: [PATCH 03/11] Treat exhausted prompt input consistently across Click versions in tests Click 8.1, the newest release for Python 3.9, makes CliRunner read exhausted input as an empty line instead of raising EOFError, so an empty answer to the clone download prompt declines rather than aborts there. Real terminals abort on end of input with both versions; only the test harness differs. Expect the empty-line behaviour when Click is older than 8.2 so the Python 3.9 CI job tests the same outcomes. (cherry picked from commit aa6f5960c8efdff6f44ff65be4db819e12f24bc6) --- test/test_cli.py | 10 +++++++++- 1 file changed, 9 insertions(+), 1 deletion(-) diff --git a/test/test_cli.py b/test/test_cli.py index 52bd799..c4a141e 100644 --- a/test/test_cli.py +++ b/test/test_cli.py @@ -15,7 +15,7 @@ import os import requests -import importlib +import importlib.metadata import pytest import pandas as pd from pathlib import Path @@ -30,6 +30,10 @@ from hallmark.remote.plan import DownloadItem, DownloadPlan from hallmark.utils import use_working_directory +_CLICK_RUNNER_SIGNALS_EOF = tuple( + int(part) for part in importlib.metadata.version("click").split(".")[:2] +) >= (8, 2) + cli_module = importlib.import_module("hallmark.cli") # files to create for testing, with a variety of a and i values to test regex encoding @@ -1187,6 +1191,10 @@ def capture_get(url, **kwargs): assert "1 file(s); 4 bytes" in result.output assert "Download these files? [y/N]" in result.output assert "Source: https://example.test/data/" in result.output + # Click 8.1 (the newest for Python 3.9) reads exhausted CliRunner input + # as an empty line instead of end of input, so "" acts like "\n" there. + if answer == "" and not _CLICK_RUNNER_SIGNALS_EOF: + answer = "\n" if answer == "y\n": assert result.exit_code == 0, result.output assert requests_made == ["https://example.test/data/tiny.fits"] From 8afa4f726bd5898bdd5204c348c255b91167b67c Mon Sep 17 00:00:00 2001 From: Dennis Date: Sun, 4 Oct 2026 20:25:34 -0700 Subject: [PATCH 04/11] Combine coverage across jobs and enforce a floor Each test job writes coverage data under its OS and Python version and uploads it. A coverage job combines them, writes the report to the job summary, uploads XML and HTML reports, and fails below 90%. Some branches only run on one Python version or operating system, so no single job sees the total. The floor lives in the workflow rather than in pyproject.toml because pytest-cov would apply it to each partial run. (cherry picked from commit a4c4ccfde6333f1e06c6212c784ede602df83ca3) --- .github/workflows/ci.yml | 48 +++++++++++++++++++++++++++++++++++++++- 1 file changed, 47 insertions(+), 1 deletion(-) diff --git a/.github/workflows/ci.yml b/.github/workflows/ci.yml index dcf2570..d6a2040 100644 --- a/.github/workflows/ci.yml +++ b/.github/workflows/ci.yml @@ -67,7 +67,53 @@ jobs: - name: Test with pytest env: HALLMARK_RUN_SSH_TESTS: "1" - run: python -m pytest --timeout=600 -ra --durations=15 + COVERAGE_FILE: .coverage.${{ runner.os }}-py${{ matrix.python-version }} + run: python -m pytest --cov --cov-report= --timeout=600 -ra --durations=15 + - name: Upload coverage data + uses: actions/upload-artifact@v7 + with: + name: coverage-${{ runner.os }}-py${{ matrix.python-version }} + path: .coverage.${{ runner.os }}-py${{ matrix.python-version }} + include-hidden-files: true + if-no-files-found: error + retention-days: 7 + + # Combines coverage from every test job. Some branches only run on one + # Python version or operating system, so no single job sees the total. + coverage: + needs: test + runs-on: ubuntu-24.04 + steps: + - uses: actions/checkout@v7 + with: + persist-credentials: false + - uses: actions/setup-python@v7 + with: + python-version: "3.13" + - name: Install coverage + run: python -m pip install coverage + - uses: actions/download-artifact@v8 + with: + pattern: coverage-* + merge-multiple: true + - name: Combine and report coverage + run: | + coverage combine + coverage report + coverage report --format=markdown >> "$GITHUB_STEP_SUMMARY" + coverage xml + coverage html + - name: Upload the combined report + uses: actions/upload-artifact@v7 + with: + name: coverage-report + path: | + coverage.xml + htmlcov/ + # The floor lives here rather than in pyproject.toml because pytest-cov + # would apply it to each partial run. + - name: Enforce the coverage floor + run: coverage report --fail-under=90 distribution-and-docs: runs-on: ubuntu-24.04 From 5114c3a661d3afdacbf9cf8b2ec2ff164ec44eec Mon Sep 17 00:00:00 2001 From: Dennis Date: Sun, 4 Oct 2026 20:26:09 -0700 Subject: [PATCH 05/11] Upload combined coverage to Codecov Upload the combined coverage.xml from the coverage job, verified with OIDC so no repository secret is needed; pull requests from forks upload tokenless. The upload cannot fail CI, and codecov.yml marks Codecov's statuses informational, so the floor in the workflow remains the only gate. Add a coverage badge and describe the coverage workflow in the contribution guide. The Codecov GitHub App must be installed on the lanisrc organization for uploads to be accepted. (cherry picked from commit 015f0351a5d6b83b156de41b17765833132be3af) --- .github/workflows/ci.yml | 14 ++++++++++++++ README.rst | 4 ++++ codecov.yml | 25 +++++++++++++++++++++++++ doc/CONTRIBUTING.md | 1 + 4 files changed, 44 insertions(+) create mode 100644 codecov.yml diff --git a/.github/workflows/ci.yml b/.github/workflows/ci.yml index d6a2040..36e1b88 100644 --- a/.github/workflows/ci.yml +++ b/.github/workflows/ci.yml @@ -83,6 +83,10 @@ jobs: coverage: needs: test runs-on: ubuntu-24.04 + permissions: + contents: read + # Lets Codecov verify uploads with OIDC instead of a stored token. + id-token: write steps: - uses: actions/checkout@v7 with: @@ -110,6 +114,16 @@ jobs: path: | coverage.xml htmlcov/ + # Codecov needs its GitHub App installed on the organization. Until then + # the upload fails without failing CI; the floor below is the gate. + # Pull requests from forks cannot request OIDC tokens and upload tokenless. + - name: Upload coverage to Codecov + uses: codecov/codecov-action@303a32d7a59b442fa8d48b6a1cc6825c09c847a5 # v7.1.1 + with: + files: coverage.xml + disable_search: true + fail_ci_if_error: false + use_oidc: ${{ !github.event.pull_request.head.repo.fork }} # The floor lives here rather than in pyproject.toml because pytest-cov # would apply it to each partial run. - name: Enforce the coverage floor diff --git a/README.rst b/README.rst index 3bab969..f2e118e 100644 --- a/README.rst +++ b/README.rst @@ -3,6 +3,10 @@ Reproducibility is the |hallmark|_ of the scientific method. +.. image:: https://codecov.io/gh/lanisrc/hallmark/graph/badge.svg?branch=main + :target: https://codecov.io/gh/lanisrc/hallmark + :alt: Coverage + Modern science has become so complex that many science projects rely on multiple software packages to work in unison, resulting in networks of data products along the analyses. diff --git a/codecov.yml b/codecov.yml new file mode 100644 index 0000000..7671050 --- /dev/null +++ b/codecov.yml @@ -0,0 +1,25 @@ +# Codecov reports are informational; CI enforces the coverage floor in +# .github/workflows/ci.yml from the combined report it uploads. +codecov: + notify: + after_n_builds: 1 +coverage: + precision: 1 + round: down + status: + project: + default: + target: auto + threshold: 1% + informational: true + patch: + default: + target: 80% + informational: true +comment: + layout: "condensed_header, diff, flags, files" + after_n_builds: 1 +ignore: + - "test/**" + - "demo/**" + - "doc/**" diff --git a/doc/CONTRIBUTING.md b/doc/CONTRIBUTING.md index dce1cbf..f30a85f 100644 --- a/doc/CONTRIBUTING.md +++ b/doc/CONTRIBUTING.md @@ -28,6 +28,7 @@ * If the implementation of a feature or fix is less clear than the desired outcome, write unit or end-to-end tests first to define the expected functionality. Then write the code to make those tests pass. * If the implementation is as clear as (or clearer than) the goal, implement the solution first. Then write tests to ensure the solution covers edge cases and realistic user scenarios. The tests may reveal places where the implementation needs to be fixed, expanded, or simplified. +* Run `pytest --cov` to see which lines the tests miss, or `pytest --cov --cov-report=html` for a browsable report in `htmlcov/`. CI runs the whole suite, including the loopback SSH tests, on every supported Python version, combines coverage from every job, and fails if the total drops below the floor set in `.github/workflows/ci.yml`. Codecov comments on each pull request with the coverage of its changed lines; those comments are informational. ## Data Management From c59c74d63119b1314908ff1ba8b588bc9ce3add0 Mon Sep 17 00:00:00 2001 From: Dennis Date: Sat, 10 Oct 2026 14:20:33 -0700 Subject: [PATCH 06/11] Upload coverage from PyPI's Codecov CLI and show upload failures Both CI runs on #59 failed to download the Codecov CLI from cli.codecov.io, and fail_ci_if_error: false hid that, so Codecov has never received a report. Install the CLI from PyPI instead, and let an upload error fail the step but not the job, so a rejected or failed upload is visible while the in-workflow floor stays the only gate. --- .github/workflows/ci.yml | 12 +++--------- 1 file changed, 3 insertions(+), 9 deletions(-) diff --git a/.github/workflows/ci.yml b/.github/workflows/ci.yml index 36e1b88..0247848 100644 --- a/.github/workflows/ci.yml +++ b/.github/workflows/ci.yml @@ -78,14 +78,11 @@ jobs: if-no-files-found: error retention-days: 7 - # Combines coverage from every test job. Some branches only run on one - # Python version or operating system, so no single job sees the total. coverage: needs: test runs-on: ubuntu-24.04 permissions: contents: read - # Lets Codecov verify uploads with OIDC instead of a stored token. id-token: write steps: - uses: actions/checkout@v7 @@ -114,18 +111,15 @@ jobs: path: | coverage.xml htmlcov/ - # Codecov needs its GitHub App installed on the organization. Until then - # the upload fails without failing CI; the floor below is the gate. - # Pull requests from forks cannot request OIDC tokens and upload tokenless. - name: Upload coverage to Codecov + continue-on-error: true uses: codecov/codecov-action@303a32d7a59b442fa8d48b6a1cc6825c09c847a5 # v7.1.1 with: files: coverage.xml disable_search: true - fail_ci_if_error: false + fail_ci_if_error: true use_oidc: ${{ !github.event.pull_request.head.repo.fork }} - # The floor lives here rather than in pyproject.toml because pytest-cov - # would apply it to each partial run. + use_pypi: true - name: Enforce the coverage floor run: coverage report --fail-under=90 From 8bad35854ce1e3736b820b27fdba1905725300e5 Mon Sep 17 00:00:00 2001 From: Dennis Date: Sat, 10 Oct 2026 14:24:16 -0700 Subject: [PATCH 07/11] Report combined coverage when some tests fail The coverage job needed every test job to succeed, so one failing test skipped the combined report, the Codecov upload and the floor. Upload each job's coverage data and combine it unless the run was cancelled. The test jobs still fail on their own. --- .github/workflows/ci.yml | 2 ++ 1 file changed, 2 insertions(+) diff --git a/.github/workflows/ci.yml b/.github/workflows/ci.yml index 0247848..400fa2a 100644 --- a/.github/workflows/ci.yml +++ b/.github/workflows/ci.yml @@ -70,6 +70,7 @@ jobs: COVERAGE_FILE: .coverage.${{ runner.os }}-py${{ matrix.python-version }} run: python -m pytest --cov --cov-report= --timeout=600 -ra --durations=15 - name: Upload coverage data + if: ${{ !cancelled() }} uses: actions/upload-artifact@v7 with: name: coverage-${{ runner.os }}-py${{ matrix.python-version }} @@ -80,6 +81,7 @@ jobs: coverage: needs: test + if: ${{ !cancelled() }} runs-on: ubuntu-24.04 permissions: contents: read From 7c9824d93b508c113ec1c75a7aeaf6846ff7f13d Mon Sep 17 00:00:00 2001 From: Dennis Date: Sat, 10 Oct 2026 14:24:45 -0700 Subject: [PATCH 08/11] Declare the oldest dependency versions the tests pass with The dependencies had no lower bounds, so pip could install pandas 1.3, whose DataFrame.to_csv() has no lineterminator argument; staging then fails with a TypeError in Dothm. Require pandas 1.5, which added it, and bound the other dependencies at releases that pass the suite on Python 3.9 when installed at exactly these versions. They are tested floors, not proven minimums. --- pyproject.toml | 16 ++++++++-------- 1 file changed, 8 insertions(+), 8 deletions(-) diff --git a/pyproject.toml b/pyproject.toml index 6f78fb2..4d7ea4f 100644 --- a/pyproject.toml +++ b/pyproject.toml @@ -10,14 +10,14 @@ readme = "README.rst" requires-python = ">=3.9" dependencies =[ - "click", - "GitPython", - "numpy", - "pandas", - "parse", - "pyyaml", - "requests", - "tqdm", + "click>=8.1", + "GitPython>=3.1.30", + "numpy>=1.21", + "pandas>=1.5", + "parse>=1.19", + "pyyaml>=5.4", + "requests>=2.26", + "tqdm>=4.60", ] authors = [ From c56869cc47c987026084705a086988c41286f981 Mon Sep 17 00:00:00 2001 From: Dennis Date: Sat, 10 Oct 2026 14:34:16 -0700 Subject: [PATCH 09/11] Test the oldest and pre-release dependency versions weekly Add a Dependencies workflow that runs weekly, on demand, and on pull requests that change pyproject.toml or the workflow. One job installs the oldest versions pyproject.toml allows on Python 3.9, resolved with uv's lowest-direct strategy so the test tools stay current. The other installs the newest releases, including pre-releases, on Python 3.14 and 3.15. Both run the whole suite with the loopback SSH tests and list the installed versions in the job summary. --- .github/workflows/dependencies.yml | 84 ++++++++++++++++++++++++++++++ doc/CONTRIBUTING.md | 1 + 2 files changed, 85 insertions(+) create mode 100644 .github/workflows/dependencies.yml diff --git a/.github/workflows/dependencies.yml b/.github/workflows/dependencies.yml new file mode 100644 index 0000000..f2dfe12 --- /dev/null +++ b/.github/workflows/dependencies.yml @@ -0,0 +1,84 @@ +# Tests the oldest dependency versions pyproject.toml allows, and the newest +# releases including pre-releases. + +name: Dependencies + +on: + schedule: + - cron: "17 7 * * 1" + workflow_dispatch: + pull_request: + paths: + - pyproject.toml + - .github/workflows/dependencies.yml + +permissions: + contents: read + +concurrency: + group: dependencies-${{ github.event.pull_request.number || github.ref }} + cancel-in-progress: true + +jobs: + lowest: + runs-on: ubuntu-24.04 + timeout-minutes: 25 + steps: + - uses: actions/checkout@v7 + with: + persist-credentials: false + - uses: actions/setup-python@v7 + with: + python-version: "3.9" + - name: Install the OpenSSH server + run: sudo apt-get update && sudo apt-get install -y --no-install-recommends openssh-server + # Resolved separately so the test tools are not held at their oldest versions. + - name: Install the oldest allowed dependencies + run: | + python -m pip install --upgrade pip uv + uv pip compile --python-version 3.9 --resolution lowest-direct --no-header pyproject.toml -o "$RUNNER_TEMP/minimum.txt" + python -m pip install -r "$RUNNER_TEMP/minimum.txt" -e . --group dev + python -m pip check + { + echo "### Oldest allowed dependencies" + echo '```' + grep -v '^ *#' "$RUNNER_TEMP/minimum.txt" + echo '```' + } >> "$GITHUB_STEP_SUMMARY" + - name: Test with pytest + env: + HALLMARK_RUN_SSH_TESTS: "1" + run: python -m pytest --timeout=600 -ra + + pre-release: + runs-on: ubuntu-24.04 + timeout-minutes: 25 + strategy: + fail-fast: false + matrix: + python-version: ["3.14", "3.15"] + steps: + - uses: actions/checkout@v7 + with: + persist-credentials: false + - uses: actions/setup-python@v7 + with: + python-version: ${{ matrix.python-version }} + - name: Install the OpenSSH server + run: sudo apt-get update && sudo apt-get install -y --no-install-recommends openssh-server + - name: Install the newest dependencies, including pre-releases + env: + PYTHON_VERSION: ${{ matrix.python-version }} + run: | + python -m pip install --upgrade pip + python -m pip install --upgrade --pre -e . --group dev + { + echo "### Installed on Python $PYTHON_VERSION" + echo '```' + python -m pip list --format=freeze --exclude-editable + echo '```' + } >> "$GITHUB_STEP_SUMMARY" + - name: Test with pytest + env: + HALLMARK_RUN_SSH_TESTS: "1" + run: python -m pytest --timeout=600 -ra diff --git a/doc/CONTRIBUTING.md b/doc/CONTRIBUTING.md index f30a85f..5378cb2 100644 --- a/doc/CONTRIBUTING.md +++ b/doc/CONTRIBUTING.md @@ -29,6 +29,7 @@ * If the implementation of a feature or fix is less clear than the desired outcome, write unit or end-to-end tests first to define the expected functionality. Then write the code to make those tests pass. * If the implementation is as clear as (or clearer than) the goal, implement the solution first. Then write tests to ensure the solution covers edge cases and realistic user scenarios. The tests may reveal places where the implementation needs to be fixed, expanded, or simplified. * Run `pytest --cov` to see which lines the tests miss, or `pytest --cov --cov-report=html` for a browsable report in `htmlcov/`. CI runs the whole suite, including the loopback SSH tests, on every supported Python version, combines coverage from every job, and fails if the total drops below the floor set in `.github/workflows/ci.yml`. Codecov comments on each pull request with the coverage of its changed lines; those comments are informational. +* Each week, and on pull requests that change `pyproject.toml`, `.github/workflows/dependencies.yml` runs the suite against the oldest dependency versions `pyproject.toml` allows (on Python 3.9) and against the newest releases including pre-releases (on the two newest Python versions). If you start using a newer feature of a dependency, raise its lower bound in `pyproject.toml`. ## Data Management From cda2480b8e451b30dc64b8445e8ac6a9a14b0d96 Mon Sep 17 00:00:00 2001 From: Dennis Date: Sat, 10 Oct 2026 14:34:39 -0700 Subject: [PATCH 10/11] Add Tianrui (Dennis) Wu and Arshia Akhtarkavan to the authors --- pyproject.toml | 4 +++- 1 file changed, 3 insertions(+), 1 deletion(-) diff --git a/pyproject.toml b/pyproject.toml index 4d7ea4f..751b474 100644 --- a/pyproject.toml +++ b/pyproject.toml @@ -28,7 +28,9 @@ authors = [ {name = "Ram Adithya"}, {name = "Hayden Foote"}, {name = "Samuel Jacobs"}, - {name = "Yaqing Su"} + {name = "Yaqing Su"}, + {name = "Tianrui (Dennis) Wu"}, + {name = "Arshia Akhtarkavan"} ] [project.scripts] From 983f357c8776a98e08d420185864b96ab169d568 Mon Sep 17 00:00:00 2001 From: Dennis Date: Sat, 10 Oct 2026 14:49:03 -0700 Subject: [PATCH 11/11] Set the coverage floor one point below the combined total The first CI run on this branch combined 90.45% branch coverage across all eight test jobs, with 27-29 tests failing in each. A floor of 90 would leave under half a point of margin, so use #59's rule of one point below the whole-number total: 89. --- .github/workflows/ci.yml | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/.github/workflows/ci.yml b/.github/workflows/ci.yml index 400fa2a..422e07f 100644 --- a/.github/workflows/ci.yml +++ b/.github/workflows/ci.yml @@ -123,7 +123,7 @@ jobs: use_oidc: ${{ !github.event.pull_request.head.repo.fork }} use_pypi: true - name: Enforce the coverage floor - run: coverage report --fail-under=90 + run: coverage report --fail-under=89 distribution-and-docs: runs-on: ubuntu-24.04