Spaces:
Running
Running
| """ | |
| Unit tests for the MD force field / solvent configuration menu. | |
| Covers the parts that do not require OpenMM (fast, always run): | |
| - FF_XML / SOLVENT_XML menus contain the documented entries | |
| - get_forcefields_menu() shape and that it only exposes verified combos | |
| - resolve_combo() normalization and explicit ValueError for unknown force | |
| fields, unknown solvents, and (when OpenMM is present) unverified pairs | |
| The heavy per-combo createSystem() probe is exercised once (skip-if-no-OpenMM) | |
| to confirm the startup verification itself runs end to end. | |
| """ | |
| import pytest | |
| from app.tools.md_config import ( | |
| FF_XML, | |
| SOLVENT_XML, | |
| DEFAULT_FORCEFIELD, | |
| DEFAULT_SOLVENT, | |
| get_forcefields_menu, | |
| resolve_combo, | |
| verify_ff_solvent_combos, | |
| ) | |
| class TestMenus: | |
| def test_forcefield_menu_entries(self): | |
| assert FF_XML == { | |
| "amber14": "amber14-all.xml", | |
| "ff14sb": "amber14/protein.ff14SB.xml", | |
| "ff15ipq": "amber14/protein.ff15ipq.xml", | |
| "ff19sb": "amber19/protein.ff19SB.xml", | |
| "amberfb15": "amberfb15.xml", | |
| "charmm36": "charmm36.xml", | |
| } | |
| def test_solvent_menu_entries(self): | |
| assert SOLVENT_XML == { | |
| "obc1": "implicit/obc1.xml", | |
| "obc2": "implicit/obc2.xml", | |
| "gbn2": "implicit/gbn2.xml", | |
| } | |
| def test_defaults_are_amber14_obc2(self): | |
| assert DEFAULT_FORCEFIELD == "amber14" | |
| assert DEFAULT_SOLVENT == "obc2" | |
| class TestGetForcefieldsMenu: | |
| def test_menu_shape(self): | |
| menu = get_forcefields_menu() | |
| assert set(menu) >= {"forcefields", "solvents", "combos", "defaults"} | |
| assert isinstance(menu["forcefields"], list) | |
| assert isinstance(menu["solvents"], list) | |
| assert menu["defaults"] == {"forcefield": "amber14", "solvent": "obc2"} | |
| # every listed force field has at least one verified solvent | |
| combos = menu["combos"] | |
| for ff in menu["forcefields"]: | |
| assert ff["value"] in combos | |
| assert combos[ff["value"]] | |
| def test_combos_only_contain_known_solvents(self): | |
| menu = get_forcefields_menu() | |
| for ff, solvents in menu["combos"].items(): | |
| assert ff in FF_XML | |
| assert set(solvents) <= set(SOLVENT_XML) | |
| class TestResolveCombo: | |
| def test_defaults(self): | |
| assert resolve_combo(None, None) == ("amber14", "obc2") | |
| def test_normalization(self): | |
| assert resolve_combo(" ff14SB ", "GBN2") == ("ff14sb", "gbn2") | |
| def test_unknown_forcefield_raises(self): | |
| with pytest.raises(ValueError, match="Unsupported force field"): | |
| resolve_combo("opls", "obc2") | |
| def test_unknown_solvent_raises(self): | |
| with pytest.raises(ValueError, match="Unsupported solvent"): | |
| resolve_combo("ff14sb", "explicit") | |
| def test_unverified_combo_raises(self): | |
| # Simulate a deployment where only amber14 passed verification. | |
| verified = verify_ff_solvent_combos() | |
| if not verified: | |
| pytest.skip("OpenMM unavailable — verification empty") | |
| unverified = [ff for ff in FF_XML if ff not in verified] | |
| if not unverified: | |
| pytest.skip("all force fields verified — nothing unverified to test") | |
| with pytest.raises(ValueError, match="not available"): | |
| resolve_combo(unverified[0], "obc2") | |
| class TestStartupVerification: | |
| def test_verification_runs_end_to_end(self): | |
| verified = verify_ff_solvent_combos() | |
| if not verified: | |
| pytest.importorskip("openmm") | |
| # Every entry maps to a valid force field with known solvents. | |
| assert verified == {ff: tuple(s) for ff, s in verified.items()} | |
| for ff, solvents in verified.items(): | |
| assert ff in FF_XML | |
| assert set(solvents) <= set(SOLVENT_XML) | |