From b69bed5e4c0ea53d9e3e3a82c926674f3f7d2960 Mon Sep 17 00:00:00 2001 From: lyskov-ai <277346777+lyskov-ai@users.noreply.github.com> Date: Tue, 2 Jun 2026 12:51:28 -0600 Subject: [PATCH] test: bootstrap mypy + pytest + coverage CI gates (#284) MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit * test: bootstrap mypy + pytest + coverage CI gates Wire up the tooling the upcoming test/annotation/refactor work depends on: - Add mypy>=1.13 to [dev] and a lenient [tool.mypy] config scoped to src/foundry + src/foundry_cli (ignore_missing_imports, no disallow_untyped_defs). The 14 modules with pre-existing type errors are pinned via [[tool.mypy.overrides]] ignore_errors=true and listed as the ratchet target — fix and remove, never add. - Add [tool.pytest.ini_options] (testpaths=["tests"], --strict-markers --strict-config) and [tool.coverage.*] (source = src/foundry + src/foundry_cli, branch = true) for opt-in gap finding. - Add .github/workflows/test.yaml with mypy and pytest jobs running on the same triggers as lint_production.yaml. Top-level tests/ only; per-model tests under models/*/tests/ may require GPU and checkpoints and stay out of CI for now. Co-authored-by: Sergey Lyskov * test(mypy): extend ignore list with 4 modules CI surfaced Local sanity-check ran mypy without foundry installed, so torch / lightning resolved to `Any` and errors that depend on knowing those types stayed invisible. First CI run installed the full deps and surfaced 6 errors in 4 additional modules: foundry.model.layers.blocks foundry.training.schedulers foundry.utils.logging foundry.utils.xpu.xpu_accelerator Same ratchet contract as the original 14: do not add, only remove (after fixing the errors and removing `ignore_errors = true`). Co-authored-by: Sergey Lyskov * style: ruff format 2 pre-existing files unrelated to bootstrap These files have been failing `ruff format --check` on production HEAD (merged via #275 and #281 without a pre-commit run). They block the existing `lint_production` workflow on every PR, including this bootstrap. Strictly out of scope for 0001 — kept in a separate commit so it can be cherry-picked or reverted cleanly. models/rfd3/src/rfd3/inference/input_parsing.py models/rfd3/tests/test_partial_diffusion.py No semantic changes — `ruff format` output only. Co-authored-by: Sergey Lyskov * ci: run test workflow on all pull requests Drop the base-branch filter on the pull_request trigger so PRs targeting stacked task branches are gated too, not only PRs into the mainline branches. Co-authored-by: Sergey Lyskov * Update test.yaml to remove pull_request_target Removed pull_request_target event from workflow. --------- Co-authored-by: Sergey Lyskov Co-authored-by: Sergey Lyskov <3302736+lyskov@users.noreply.github.com> --- .github/workflows/test.yaml | 49 +++++++++++++++++ .../rfd3/src/rfd3/inference/input_parsing.py | 4 +- models/rfd3/tests/test_partial_diffusion.py | 18 +++---- pyproject.toml | 53 +++++++++++++++++++ 4 files changed, 112 insertions(+), 12 deletions(-) create mode 100644 .github/workflows/test.yaml diff --git a/.github/workflows/test.yaml b/.github/workflows/test.yaml new file mode 100644 index 0000000..65b0a91 --- /dev/null +++ b/.github/workflows/test.yaml @@ -0,0 +1,49 @@ +name: test + +on: + push: + branches: [main, production, wip/for-release, wip/remove-chemdata] + # Run on every pull request regardless of base branch, so stacked task-branch PRs + # are gated too (not just PRs targeting the mainline branches above). + pull_request: + + workflow_dispatch: + +concurrency: + group: ${{ github.workflow }}-${{ github.ref }} + cancel-in-progress: true + +jobs: + mypy: + name: mypy (type check) + runs-on: ubuntu-latest + steps: + - uses: actions/checkout@v4 + - uses: actions/setup-python@v5 + with: + python-version: "3.12" + cache: 'pip' + cache-dependency-path: pyproject.toml + - name: Install package + dev extras + # `[dev]` pulls in mypy and the dev tooling. The package itself is needed + # so mypy can resolve `import foundry.*` against the installed source. + run: pip install -e ".[dev]" + - name: mypy + run: mypy + + pytest: + name: pytest (top-level tests/) + runs-on: ubuntu-latest + steps: + - uses: actions/checkout@v4 + - uses: actions/setup-python@v5 + with: + python-version: "3.12" + cache: 'pip' + cache-dependency-path: pyproject.toml + - name: Install package + dev extras + run: pip install -e ".[dev]" + - name: pytest + # Top-level tests/ only. Per-model tests under models/*/tests/ may require + # GPU and downloaded checkpoints and are not yet wired into CI. + run: pytest diff --git a/models/rfd3/src/rfd3/inference/input_parsing.py b/models/rfd3/src/rfd3/inference/input_parsing.py index ead10ba..f57ed4c 100644 --- a/models/rfd3/src/rfd3/inference/input_parsing.py +++ b/models/rfd3/src/rfd3/inference/input_parsing.py @@ -778,9 +778,7 @@ class DesignInputSpecification(BaseModel): if is_motif.any() and (~is_motif).any(): diffused_coord = atom_array.coord[~is_motif] finite = np.isfinite(diffused_coord).all(axis=-1) - center = np.nan_to_num( - np.mean(diffused_coord[finite], axis=0) - ) + center = np.nan_to_num(np.mean(diffused_coord[finite], axis=0)) atom_array.coord = atom_array.coord - center logger.info( f"Partial diffusion: centering on diffused-region COM ({center})." diff --git a/models/rfd3/tests/test_partial_diffusion.py b/models/rfd3/tests/test_partial_diffusion.py index a6cb316..6e792de 100644 --- a/models/rfd3/tests/test_partial_diffusion.py +++ b/models/rfd3/tests/test_partial_diffusion.py @@ -58,12 +58,12 @@ def test_partial_diffusion_respects_ori_token(): delta = aa_shift.coord.mean(axis=0) - aa_default.coord.mean(axis=0) # Coords are translated by -ori_token at parse time, so the post-parse # whole-structure mean must drop by ~50 Å on x. - assert delta[0] == pytest.approx(-50.0, abs=1.5), ( - f"ori_token=[50,0,0] should shift coords by -50 in x; got delta={delta}" - ) - assert abs(delta[1]) < 1.5 and abs(delta[2]) < 1.5, ( - f"ori_token=[50,0,0] should not move y/z; got delta={delta}" - ) + assert delta[0] == pytest.approx( + -50.0, abs=1.5 + ), f"ori_token=[50,0,0] should shift coords by -50 in x; got delta={delta}" + assert ( + abs(delta[1]) < 1.5 and abs(delta[2]) < 1.5 + ), f"ori_token=[50,0,0] should not move y/z; got delta={delta}" @pytest.mark.fast @@ -86,9 +86,9 @@ def test_partial_diffusion_defaults_to_diffused_region_com(): pytest.skip("Test fixture has no separable motif/diffused split") diffused_com = aa.coord[~is_motif].mean(axis=0) - assert np.allclose(diffused_com, 0, atol=1e-3), ( - f"diffused-region COM should be at origin after centering; got {diffused_com}" - ) + assert np.allclose( + diffused_com, 0, atol=1e-3 + ), f"diffused-region COM should be at origin after centering; got {diffused_com}" if __name__ == "__main__": diff --git a/pyproject.toml b/pyproject.toml index 0084d17..4e23110 100644 --- a/pyproject.toml +++ b/pyproject.toml @@ -73,6 +73,8 @@ dev = [ "assertpy", "ruff==0.8.3", "ipdb", + # Type checking + "mypy>=1.13,<2", # Debugger/interactive "debugpy>=1.8.5,<2", "ipykernel>=6.29.4,<7", @@ -201,6 +203,57 @@ ignore = [ [tool.pyright] typeCheckingMode = "off" +# Type checking ---------------------------------------------------------------------- +# Lenient baseline so the existing largely-unannotated code does not fail the gate. +# Per-module strictness is ratcheted up via [tool.mypy.overrides] as annotations land. +[tool.mypy] +python_version = "3.12" +files = ["src/foundry", "src/foundry_cli"] +ignore_missing_imports = true +warn_unused_ignores = true +warn_redundant_casts = true +disallow_untyped_defs = false +check_untyped_defs = false + +# Modules with pre-existing type errors at bootstrap time. These are the track-1 +# ratchet list — fix the errors and remove the entry to enable type-checking for +# that module. Do NOT add modules to this list; only remove from it. +[[tool.mypy.overrides]] +module = [ + "foundry.callbacks.health_logging", + "foundry.callbacks.metrics_logging", + "foundry.callbacks.train_logging", + "foundry.common", + "foundry.hydra.resolvers", + "foundry.inference_engines.base", + "foundry.metrics.metric", + "foundry.model.layers.blocks", + "foundry.trainers.fabric", + "foundry.training.schedulers", + "foundry.utils.components", + "foundry.utils.datasets", + "foundry.utils.ddp", + "foundry.utils.logging", + "foundry.utils.rigid", + "foundry.utils.weights", + "foundry.utils.xpu.xpu_accelerator", + "foundry_cli.download_checkpoints", +] +ignore_errors = true + +# Testing ---------------------------------------------------------------------------- +[tool.pytest.ini_options] +testpaths = ["tests"] +addopts = "-ra --strict-markers --strict-config" + +[tool.coverage.run] +source = ["src/foundry", "src/foundry_cli"] +branch = true + +[tool.coverage.report] +show_missing = true +skip_covered = false + [dependency-groups] dev = [ "build>=1.3.0",