test: bootstrap mypy + pytest + coverage CI gates (#284)

* 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 <sergey.lyskov@jhu.edu>

* 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 <sergey.lyskov@jhu.edu>

* 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 <sergey.lyskov@jhu.edu>

* 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 <sergey.lyskov@jhu.edu>

* Update test.yaml to remove pull_request_target

Removed pull_request_target event from workflow.

---------

Co-authored-by: Sergey Lyskov <sergey.lyskov@jhu.edu>
Co-authored-by: Sergey Lyskov <3302736+lyskov@users.noreply.github.com>
This commit is contained in:
lyskov-ai
2026-06-02 12:51:28 -06:00
committed by GitHub
parent cd4d94fb0c
commit b69bed5e4c
4 changed files with 112 additions and 12 deletions

49
.github/workflows/test.yaml vendored Normal file
View File

@@ -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

View File

@@ -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})."

View File

@@ -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__":

View File

@@ -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",