Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
62 changes: 62 additions & 0 deletions .github/workflows/docs-tests.yml
Original file line number Diff line number Diff line change
@@ -0,0 +1,62 @@
name: Documentation Tests

on:
push:
branches: [main]
paths:
- 'docs/**'
- 'diff_diff/**'
- 'tests/test_doc_snippets.py'
# tests/conftest.py is auto-loaded by pytest for the snippet
# test run and mutates sys.path + MPLBACKEND (conftest.py:14, 18);
# changes there can break snippet exec without touching the test
# file itself.
- 'tests/conftest.py'
- 'pyproject.toml'
- '.github/workflows/docs-tests.yml'
pull_request:
branches: [main]
types: [opened, synchronize, reopened, labeled, unlabeled]
paths:
- 'docs/**'
- 'diff_diff/**'
- 'tests/test_doc_snippets.py'
- 'tests/conftest.py'
- 'pyproject.toml'
- '.github/workflows/docs-tests.yml'
schedule:
# Weekly Sunday 6am UTC - smoke test that snippets still execute
# against current upstream deps (mirrors notebooks.yml schedule).
- cron: '0 6 * * 0'

permissions:
contents: read

jobs:
doc-snippets:
name: Validate RST code snippets
if: >-
github.event_name != 'pull_request'
|| contains(github.event.pull_request.labels.*.name, 'ready-for-ci')
runs-on: ubuntu-latest

steps:
- uses: actions/checkout@v6

- name: Set up Python
uses: actions/setup-python@v6
with:
# 3.14 to mirror Pure Python Fallback (the only existing job
# that actually ran these tests). notebooks.yml uses 3.11 for
# nbmake compat, not relevant here.
python-version: '3.14'

- name: Install dependencies
# Keep in sync with pyproject.toml [project.dependencies] and [project.optional-dependencies.dev]
run: pip install numpy pandas scipy pytest

- name: Run doc snippet tests in pure Python mode
# PYTHONPATH=. lets the test import diff_diff directly from
# source without invoking the maturin/Rust build (mirrors Pure
# Python Fallback at rust-test.yml:189-193).
run: PYTHONPATH=. DIFF_DIFF_BACKEND=python pytest tests/test_doc_snippets.py -v
18 changes: 15 additions & 3 deletions .github/workflows/rust-test.yml
Original file line number Diff line number Diff line change
Expand Up @@ -7,6 +7,9 @@ on:
- 'rust/**'
- 'diff_diff/**'
- 'tests/**'
# tests/test_doc_snippets.py is owned by docs-tests.yml; exclude it
# so a harness-only edit does not fan out into the Rust matrix.
- '!tests/test_doc_snippets.py'
- 'pyproject.toml'
- '.github/workflows/rust-test.yml'
pull_request:
Expand All @@ -16,6 +19,7 @@ on:
- 'rust/**'
- 'diff_diff/**'
- 'tests/**'
- '!tests/test_doc_snippets.py'
- 'pyproject.toml'
- '.github/workflows/rust-test.yml'

Expand Down Expand Up @@ -155,14 +159,18 @@ jobs:
- name: Run tests with Rust backend (Unix)
if: runner.os != 'Windows'
working-directory: /tmp
run: DIFF_DIFF_BACKEND=rust pytest tests/ -q -n auto --dist worksteal -m ''
# Doc snippet tests own .github/workflows/docs-tests.yml; ignore
# them here to keep one workflow per surface and avoid double
# execution (the matrix copies tests/ to /tmp/tests without
# docs/, so this ignore is defensive on the Rust path).
run: DIFF_DIFF_BACKEND=rust pytest tests/ --ignore=tests/test_doc_snippets.py -q -n auto --dist worksteal -m ''

- name: Run tests with Rust backend (Windows)
if: runner.os == 'Windows'
working-directory: ${{ runner.temp }}
run: |
$env:DIFF_DIFF_BACKEND="rust"
pytest tests/ -q -n auto --dist worksteal -m ''
pytest tests/ --ignore=tests/test_doc_snippets.py -q -n auto --dist worksteal -m ''
shell: pwsh

# Test pure Python fallback (without Rust extension)
Expand Down Expand Up @@ -190,4 +198,8 @@ jobs:
PYTHONPATH=. python -c "from diff_diff import HAS_RUST_BACKEND; print(f'HAS_RUST_BACKEND: {HAS_RUST_BACKEND}'); assert not HAS_RUST_BACKEND"

- name: Run tests in pure Python mode
run: PYTHONPATH=. DIFF_DIFF_BACKEND=python pytest tests/ -q --ignore=tests/test_rust_backend.py -n auto --dist worksteal
# Doc snippet tests own .github/workflows/docs-tests.yml; ignore
# them here to keep one workflow per surface (this is the only
# invocation that actually executes test_doc_snippets.py since
# this job runs from the repo root, not /tmp/tests).
run: PYTHONPATH=. DIFF_DIFF_BACKEND=python pytest tests/ -q --ignore=tests/test_rust_backend.py --ignore=tests/test_doc_snippets.py -n auto --dist worksteal
2 changes: 1 addition & 1 deletion TODO.md
Original file line number Diff line number Diff line change
Expand Up @@ -120,7 +120,7 @@ Deferred items from PR reviews that were not addressed before merge.
|-------|----------|----|----------|
| ImputationDiD event-study SEs recompute full conservative variance per horizon (should cache A0/A1 factorization) | `imputation.py` | #141 | Low |
| Rust faer SVD ndarray-to-faer conversion overhead (minimal vs SVD cost) | `rust/src/linalg.rs:67` | #115 | Low |
| Unrelated label events (e.g., adding `bug` label) re-trigger CI workflows when `ready-for-ci` is already present; filter `labeled`/`unlabeled` events to only `ready-for-ci` transitions | `.github/workflows/rust-test.yml`, `notebooks.yml` | #269 | Low |
| Unrelated label events (e.g., adding `bug` label) re-trigger CI workflows when `ready-for-ci` is already present; filter `labeled`/`unlabeled` events to only `ready-for-ci` transitions | `.github/workflows/rust-test.yml`, `notebooks.yml`, `docs-tests.yml` | #269 | Low |
| `bread_inv` as a performance kwarg on `compute_robust_vcov` to avoid re-inverting `(X'WX)` when the caller already has it. Deferred from Phase 1a for scope. HC2 and HC2+BM both need the bread inverse, so a shared hint would save one `np.linalg.solve` per sandwich. | `linalg.py::compute_robust_vcov` | Phase 1a | Low |
| Rust-backend HC2 implementation. Current Rust path only supports HC1; HC2 and CR2 Bell-McCaffrey fall through to the NumPy backend. For large-n fits this is noticeable. | `rust/src/linalg.rs` | Phase 1a | Low |
| CR2 Bell-McCaffrey DOF uses a naive `O(n² k)` per-coefficient loop over cluster pairs. Pustejovsky-Tipton (2018) Appendix B has a scores-based formulation that avoids the full `n × n` `M` matrix. Switch when a user hits a large-`n` cluster-robust design. | `linalg.py::_compute_cr2_bm` | Phase 1a | Low |
Expand Down
Loading