test(missing): pin _knn_fill against nullable-integer partner columns #753
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
| name: CI | |
| on: | |
| push: | |
| branches: [main] | |
| pull_request: | |
| schedule: | |
| - cron: "0 3 * * *" | |
| workflow_dispatch: | |
| # Only a newer push to the same PR cancels its superseded run. Every other run | |
| # (each merge to main, the nightly schedule, manual dispatches) is grouped by | |
| # commit: a group keeps at most one queued run and a newer queued run replaces | |
| # it even without cancel-in-progress, so a per-ref group still dropped the CI of | |
| # back-to-back merges. Per-commit groups keep a complete result on every commit. | |
| concurrency: | |
| group: ci-${{ github.event_name }}-${{ github.event_name == 'pull_request' && github.ref || github.sha }} | |
| cancel-in-progress: ${{ github.event_name == 'pull_request' }} | |
| # Least privilege by default; jobs that need more (nightly alert issue, | |
| # coverage-badge push) declare their own elevated permissions. | |
| permissions: | |
| contents: read | |
| # Dependency pinning: the gating jobs install with `-c constraints/ci.txt` | |
| # (exported from uv.lock), so an upstream release cannot turn CI red overnight. | |
| # test-matrix deliberately floats to the latest compatible releases: it is the | |
| # early warning for upstream breakage. Regenerate with `make constraints`. | |
| jobs: | |
| lockfile: | |
| runs-on: ubuntu-latest | |
| timeout-minutes: 10 | |
| steps: | |
| - uses: actions/checkout@3d3c42e5aac5ba805825da76410c181273ba90b1 # v7.0.1 | |
| - uses: actions/setup-python@5fda3b95a4ea91299a34e894583c3862153e4b97 # v7.0.0 | |
| with: | |
| python-version: "3.12" | |
| - name: uv.lock and constraints/ci.txt are up to date | |
| run: | | |
| python -m pip install "uv==0.12.10" | |
| uv lock --check | |
| uv export --frozen --no-hashes --all-extras --no-emit-project --quiet -o constraints/ci.txt | |
| if ! git diff --exit-code -- constraints/ci.txt; then | |
| echo "::error::constraints/ci.txt is stale; run 'make constraints' and commit the result" | |
| exit 1 | |
| fi | |
| quality-fast: | |
| runs-on: ubuntu-latest | |
| timeout-minutes: 20 | |
| steps: | |
| - uses: actions/checkout@3d3c42e5aac5ba805825da76410c181273ba90b1 # v7.0.1 | |
| - uses: actions/setup-python@5fda3b95a4ea91299a34e894583c3862153e4b97 # v7.0.0 | |
| with: | |
| python-version: "3.12" | |
| cache: pip | |
| - name: Install | |
| run: | | |
| python -m pip install --upgrade pip | |
| pip install -c constraints/ci.txt -e ".[dev,ml]" | |
| - name: Lint | |
| run: ruff check . | |
| - name: Test (required fast lane) | |
| run: pytest -m "not online and not large" | |
| - name: Typecheck | |
| # mypy targets Python 3.10; NumPy 2.5+ stubs require Python 3.12, so the | |
| # type check runs against numpy<2.5 after the tests used the locked pin. | |
| run: | | |
| pip install "numpy<2.5" | |
| mypy src/freshdata | |
| truthbench: | |
| runs-on: ubuntu-latest | |
| timeout-minutes: 30 | |
| steps: | |
| - uses: actions/checkout@3d3c42e5aac5ba805825da76410c181273ba90b1 # v7.0.1 | |
| - uses: actions/setup-python@5fda3b95a4ea91299a34e894583c3862153e4b97 # v7.0.0 | |
| with: | |
| python-version: "3.12" | |
| cache: pip | |
| - name: Install | |
| run: | | |
| python -m pip install --upgrade pip | |
| pip install -c constraints/ci.txt -e ".[dev,ml]" | |
| - name: TruthBench PR ratchet | |
| # PRs must not regress any gate that passes in the committed | |
| # baseline.json; the known-red gates are release blockers and are | |
| # enforced absolutely by the release workflow (make truthbench-release), | |
| # so they are reported here as KNOWN-RED without failing every | |
| # unrelated PR. Infrastructure errors and partial runs still fail. | |
| run: make truthbench-pr | |
| - name: Upload run artifacts | |
| if: always() | |
| uses: actions/upload-artifact@043fb46d1a93c77aae656e7c1c64a875d1fc6a0a # v7.0.1 | |
| with: | |
| name: truthbench-results | |
| path: benchmarks/truthbench/results/ | |
| test-matrix: | |
| runs-on: ubuntu-latest | |
| timeout-minutes: 30 | |
| strategy: | |
| fail-fast: false | |
| matrix: | |
| python-version: ["3.9", "3.10", "3.11", "3.12", "3.13"] | |
| include: | |
| - python-version: "3.9" | |
| pandas: "pandas>=1.5,<2" | |
| numpy: "numpy<2" | |
| - python-version: "3.13" | |
| pandas: "pandas" | |
| steps: | |
| - uses: actions/checkout@3d3c42e5aac5ba805825da76410c181273ba90b1 # v7.0.1 | |
| - uses: actions/setup-python@5fda3b95a4ea91299a34e894583c3862153e4b97 # v7.0.0 | |
| with: | |
| python-version: ${{ matrix.python-version }} | |
| cache: pip | |
| - name: Install | |
| # Intentionally unpinned (no constraints file): this matrix tracks the | |
| # latest releases each Python version resolves to. | |
| run: | | |
| python -m pip install --upgrade pip | |
| pip install -e ".[dev,ml]" | |
| if [ -n "${{ matrix.pandas }}" ]; then pip install "${{ matrix.pandas }}"; fi | |
| if [ -n "${{ matrix.numpy }}" ]; then pip install "${{ matrix.numpy }}"; fi | |
| - name: Test (fast marker set) | |
| run: pytest -m "not online and not large" | |
| nightly-online-large: | |
| if: github.event_name == 'schedule' || github.event_name == 'workflow_dispatch' | |
| runs-on: ubuntu-latest | |
| timeout-minutes: 45 | |
| # NO continue-on-error here: when this lane runs, a failure is a real | |
| # failure. Visibility is guaranteed by the alert step below (a pinned | |
| # issue), so a red nightly can't scroll by unnoticed. | |
| permissions: | |
| issues: write | |
| contents: read | |
| steps: | |
| - uses: actions/checkout@3d3c42e5aac5ba805825da76410c181273ba90b1 # v7.0.1 | |
| - uses: actions/setup-python@5fda3b95a4ea91299a34e894583c3862153e4b97 # v7.0.0 | |
| with: | |
| python-version: "3.12" | |
| cache: pip | |
| - name: Install | |
| run: | | |
| python -m pip install --upgrade pip | |
| pip install -c constraints/ci.txt -e ".[dev,ml]" | |
| - name: Nightly online/large drift checks | |
| # --no-cov: this lane deliberately runs ~20 tests, so the repo-wide | |
| # --cov-fail-under=93 inherited from addopts can never be met here. | |
| # The coverage gate is enforced by the full fast lane (quality-fast). | |
| run: pytest -m "online or large or tier1" --no-cov | |
| - name: Open/refresh alert issue on failure | |
| if: failure() | |
| uses: actions/github-script@3a2844b7e9c422d3c10d287c895573f7108da1b3 # v9.0.0 | |
| with: | |
| script: | | |
| const title = "nightly large-data lane failing"; | |
| const body = `The scheduled online/large test lane failed.\n\n` + | |
| `Run: ${context.serverUrl}/${context.repo.owner}/${context.repo.repo}` + | |
| `/actions/runs/${context.runId}\n\n` + | |
| `This lane does not run on push/PR — a failure here is only ` + | |
| `visible through this issue and the Actions tab.`; | |
| const open = await github.rest.issues.listForRepo({ | |
| ...context.repo, state: "open", labels: "nightly-failure", | |
| }); | |
| const existing = open.data.find(i => i.title === title); | |
| if (existing) { | |
| await github.rest.issues.createComment({ | |
| ...context.repo, issue_number: existing.number, body, | |
| }); | |
| } else { | |
| await github.rest.issues.create({ | |
| ...context.repo, title, body, labels: ["nightly-failure"], | |
| }); | |
| } | |
| # Runs on every push/PR: makes the *existence and status* of the large-data | |
| # lane visible where a green checkmark would otherwise hide it. Fails if the | |
| # large/online markers stop collecting any tests (a silently-rotted lane). | |
| large-data-status: | |
| runs-on: ubuntu-latest | |
| timeout-minutes: 15 | |
| steps: | |
| - uses: actions/checkout@3d3c42e5aac5ba805825da76410c181273ba90b1 # v7.0.1 | |
| - uses: actions/setup-python@5fda3b95a4ea91299a34e894583c3862153e4b97 # v7.0.0 | |
| with: | |
| python-version: "3.12" | |
| cache: pip | |
| - name: Install | |
| run: | | |
| python -m pip install --upgrade pip | |
| pip install -c constraints/ci.txt -e ".[dev,ml]" | |
| - name: Assert the large/online lane still collects tests | |
| run: | | |
| N=$(pytest -m "online or large or tier1" --collect-only -q -o addopts="" \ | |
| 2>/dev/null | grep -c "::" || true) | |
| echo "large/online/tier1 tests collected: $N" | |
| if [ "$N" -eq 0 ]; then | |
| echo "::error::the large/online test lane collects zero tests — it has rotted" | |
| exit 1 | |
| fi | |
| { | |
| echo "## Large-data test lane" | |
| echo "" | |
| echo "**$N** large/online/tier1 test(s) exist. They do **not** run on this" | |
| echo "push/PR — they run on the nightly schedule (see the *nightly-online-large*" | |
| echo "job in scheduled runs; failures open a pinned \`nightly-failure\` issue)." | |
| } >> "$GITHUB_STEP_SUMMARY" | |
| build: | |
| needs: quality-fast | |
| runs-on: ubuntu-latest | |
| timeout-minutes: 10 | |
| steps: | |
| - uses: actions/checkout@3d3c42e5aac5ba805825da76410c181273ba90b1 # v7.0.1 | |
| - uses: actions/setup-python@5fda3b95a4ea91299a34e894583c3862153e4b97 # v7.0.0 | |
| with: | |
| python-version: "3.12" | |
| - name: Build distributions | |
| run: | | |
| python -m pip install build twine | |
| python -m build | |
| twine check dist/* | |
| freshcore-native: | |
| # Builds the optional FreshCore Rust extension (crates/freshcore) and runs | |
| # the FreshCore tests against it. No other job installs the extension, so | |
| # the native parity tests skip everywhere else. | |
| runs-on: ubuntu-latest | |
| timeout-minutes: 10 | |
| env: | |
| RUSTUP_TOOLCHAIN: "1.98.1" | |
| steps: | |
| - uses: actions/checkout@3d3c42e5aac5ba805825da76410c181273ba90b1 # v7.0.1 | |
| - uses: actions/setup-python@5fda3b95a4ea91299a34e894583c3862153e4b97 # v7.0.0 | |
| with: | |
| python-version: "3.12" | |
| cache: pip | |
| - name: Install Rust toolchain | |
| run: rustup toolchain install "$RUSTUP_TOOLCHAIN" --profile minimal | |
| - name: Install | |
| # maturin develop installs into a virtualenv, so create one in the | |
| # workspace and put it first on PATH for the remaining steps. | |
| run: | | |
| python -m venv .venv | |
| echo "VIRTUAL_ENV=$PWD/.venv" >> "$GITHUB_ENV" | |
| echo "$PWD/.venv/bin" >> "$GITHUB_PATH" | |
| .venv/bin/python -m pip install --upgrade pip | |
| .venv/bin/pip install -c constraints/ci.txt -e ".[dev,freshcore]" | |
| - name: Rust unit tests | |
| run: cargo test --locked --manifest-path crates/freshcore/Cargo.toml | |
| - name: Build the extension | |
| run: maturin develop --manifest-path crates/freshcore/Cargo.toml --features extension-module | |
| - name: FreshCore tests | |
| # The coverage gate in addopts applies to the full suite, not this subset. | |
| run: | | |
| python -c "import freshdata_freshcore" | |
| pytest tests/test_execution -k freshcore -o addopts="--strict-markers" -q | |
| coverage-badge: | |
| # Publish a self-hosted shields endpoint badge to the `badges` branch | |
| # (no third-party account needed). Runs only on main. | |
| if: github.ref == 'refs/heads/main' | |
| needs: quality-fast | |
| runs-on: ubuntu-latest | |
| timeout-minutes: 15 | |
| permissions: | |
| contents: write | |
| steps: | |
| - uses: actions/checkout@3d3c42e5aac5ba805825da76410c181273ba90b1 # v7.0.1 | |
| - uses: actions/setup-python@5fda3b95a4ea91299a34e894583c3862153e4b97 # v7.0.0 | |
| with: | |
| python-version: "3.12" | |
| cache: pip | |
| - name: Install | |
| run: | | |
| python -m pip install --upgrade pip | |
| pip install -c constraints/ci.txt -e ".[dev,ml]" | |
| - name: Compute coverage and write badge JSON | |
| run: | | |
| pytest -m "not online and not large and not tier1" \ | |
| --cov=freshdata --cov-report=json -o addopts="" | |
| python - <<'PY' | |
| import json | |
| pct = round(json.load(open("coverage.json"))["totals"]["percent_covered"]) | |
| color = ("brightgreen" if pct >= 90 else "green" if pct >= 80 | |
| else "yellow" if pct >= 70 else "orange" if pct >= 60 else "red") | |
| json.dump( | |
| {"schemaVersion": 1, "label": "coverage", "message": f"{pct}%", "color": color}, | |
| open("coverage.json", "w"), | |
| ) | |
| print("coverage badge:", pct, "%", color) | |
| PY | |
| - name: Push badge to `badges` branch | |
| env: | |
| GITHUB_TOKEN: ${{ secrets.GITHUB_TOKEN }} | |
| run: | | |
| mkdir -p /tmp/badges && cp coverage.json /tmp/badges/coverage.json | |
| cd /tmp/badges | |
| git init -q | |
| git config user.name "github-actions[bot]" | |
| git config user.email "41898282+github-actions[bot]@users.noreply.github.com" | |
| git checkout -q -b badges | |
| git add coverage.json | |
| git commit -q -m "Update coverage badge" | |
| # Back-to-back merges run this job concurrently on different commits, | |
| # and a force push can fail transiently (ref lock, network). Retry with | |
| # backoff so a badge update never turns main red. | |
| for attempt in 1 2 3 4; do | |
| if git push -q --force \ | |
| "https://x-access-token:${GITHUB_TOKEN}@github.com/${GITHUB_REPOSITORY}.git" badges; then | |
| exit 0 | |
| fi | |
| echo "badge push attempt ${attempt} failed; retrying" | |
| sleep $((attempt * 10)) | |
| done | |
| echo "::warning::coverage badge push failed after 4 attempts" | |
| exit 1 |