From 2475c47a00e224f40328aa2ac5151f015fe72126 Mon Sep 17 00:00:00 2001 From: "Matthew W. Thompson" Date: Tue, 7 Apr 2026 13:21:49 -0500 Subject: [PATCH 1/7] Fix some pytest warnings --- devtools/conda-envs/test_env.yaml | 4 +- openff/toolkit/_tests/test_forcefield.py | 18 +-- openff/toolkit/_tests/test_parameters.py | 2 +- openff/toolkit/_tests/test_topology.py | 158 ++++++++--------------- pyproject.toml | 43 +++--- setup.cfg | 6 - 6 files changed, 93 insertions(+), 138 deletions(-) delete mode 100644 setup.cfg diff --git a/devtools/conda-envs/test_env.yaml b/devtools/conda-envs/test_env.yaml index 65da06d64..53f313f77 100644 --- a/devtools/conda-envs/test_env.yaml +++ b/devtools/conda-envs/test_env.yaml @@ -14,7 +14,7 @@ dependencies: - xmltodict <=1.0.2 - python-constraint - openmm >=7.6 - - openff-forcefields >=2023.05.1 + - openff-forcefields >=2025 - openff-units - openff-amber-ff-ports - openff-utilities >=0.1.5 @@ -30,7 +30,7 @@ dependencies: - openeye-toolkits # Test-only/optional/dev/typing - - pytest =8 + - pytest - pytest-cov - pytest-xdist - pytest-rerunfailures diff --git a/openff/toolkit/_tests/test_forcefield.py b/openff/toolkit/_tests/test_forcefield.py index d5fed21fb..bd77371f8 100644 --- a/openff/toolkit/_tests/test_forcefield.py +++ b/openff/toolkit/_tests/test_forcefield.py @@ -695,25 +695,25 @@ def generate_monatomic_ions(): "toolkit": AmberToolsToolkitWrapper, "partial_charge_method": "AM1-Mulliken", "exception": None, - "exception_match": "", + "exception_match": None, }, { "toolkit": AmberToolsToolkitWrapper, "partial_charge_method": "Gasteiger", "exception": None, - "exception_match": "", + "exception_match": None, }, { "toolkit": AmberToolsToolkitWrapper, "partial_charge_method": "Madeup-ChargeMethod", "exception": ChargeMethodUnavailableError, - "exception_match": "", + "exception_match": None, }, { "toolkit": OpenEyeToolkitWrapper, "partial_charge_method": "AM1-Mulliken", "exception": None, - "exception_match": "", + "exception_match": None, }, { "toolkit": OpenEyeToolkitWrapper, @@ -725,31 +725,31 @@ def generate_monatomic_ions(): "toolkit": OpenEyeToolkitWrapper, "partial_charge_method": "MMFF94", "exception": None, - "exception_match": "", + "exception_match": None, }, { "toolkit": OpenEyeToolkitWrapper, "partial_charge_method": "am1bcc", "exception": None, - "exception_match": "", + "exception_match": None, }, { "toolkit": OpenEyeToolkitWrapper, "partial_charge_method": "am1bccnosymspt", "exception": None, - "exception_match": "", + "exception_match": None, }, { "toolkit": OpenEyeToolkitWrapper, "partial_charge_method": "am1bccelf10", "exception": None, - "exception_match": "", + "exception_match": None, }, { "toolkit": OpenEyeToolkitWrapper, "partial_charge_method": "Madeup-ChargeMethod", "exception": ChargeMethodUnavailableError, - "exception_match": "", + "exception_match": None, }, ] diff --git a/openff/toolkit/_tests/test_parameters.py b/openff/toolkit/_tests/test_parameters.py index 8bb0b196b..ca065f8d3 100644 --- a/openff/toolkit/_tests/test_parameters.py +++ b/openff/toolkit/_tests/test_parameters.py @@ -2202,7 +2202,7 @@ def test_validate_found_match( ): if unsafe_vsites: monkeypatch.setenv("OPENFF_UNSAFE_VSITES", "1") - molecule = Molecule.from_smiles(smiles, allow_undefined_stereo=True) + molecule = Molecule.from_mapped_smiles(smiles, allow_undefined_stereo=True) topology: Topology = molecule.to_topology() atoms = {i: atom for i, atom in enumerate(topology.atoms)} diff --git a/openff/toolkit/_tests/test_topology.py b/openff/toolkit/_tests/test_topology.py index 6b6a17d36..0881565b2 100644 --- a/openff/toolkit/_tests/test_topology.py +++ b/openff/toolkit/_tests/test_topology.py @@ -5,15 +5,20 @@ import itertools import re +import tempfile +from collections import defaultdict from copy import deepcopy +from io import StringIO +from pathlib import Path import numpy as np +import openmm.app +import openmm.unit import pytest from openff.units.openmm import from_openmm from openff.utilities import skip_if_missing -from openmm import app -from openff.toolkit import Quantity, unit +from openff.toolkit import Molecule, Quantity, Topology, unit from openff.toolkit._tests.create_molecules import ( create_ammonia, create_cyclohexane, @@ -39,9 +44,7 @@ from openff.toolkit.topology import ( Atom, ImproperDict, - Molecule, TagSortedDict, - Topology, ValenceDict, ) from openff.toolkit.topology._mm_molecule import _SimpleMolecule @@ -253,11 +256,10 @@ def test_box_vectors(self): def test_issue_1527(self): """Test the error handling of setting box vectors with an OpenMM quantity.""" - import numpy import openmm.unit topology = Topology() - topology.box_vectors = numpy.ones(3) * openmm.unit.nanometer + topology.box_vectors = np.ones(3) * openmm.unit.nanometer assert isinstance(topology.box_vectors, unit.Quantity) @@ -535,7 +537,7 @@ def test_pruned_impropers(self): def test_from_openmm(self): """Test creation of an OpenFF Topology object from an OpenMM Topology and component molecules""" - pdbfile = app.PDBFile(get_data_file_path("systems/packmol_boxes/cyclohexane_ethanol_0.4_0.6.pdb")) + pdbfile = openmm.app.PDBFile(get_data_file_path("systems/packmol_boxes/cyclohexane_ethanol_0.4_0.6.pdb")) with pytest.raises(MissingUniqueMoleculesError, match="requires a list of Molecule objects"): Topology.from_openmm(pdbfile.topology) @@ -571,7 +573,7 @@ def test_from_openmm_virtual_sites(self, opc): def test_from_openmm_missing_reference(self): """Test creation of an OpenFF Topology object from an OpenMM Topology when missing a unique molecule""" - pdbfile = app.PDBFile(get_data_file_path("systems/packmol_boxes/cyclohexane_ethanol_0.4_0.6.pdb")) + pdbfile = openmm.app.PDBFile(get_data_file_path("systems/packmol_boxes/cyclohexane_ethanol_0.4_0.6.pdb")) molecules = [create_ethanol()] with pytest.raises(ValueError, match="No match found for molecule C6H12"): @@ -582,7 +584,7 @@ def test_from_openmm_missing_conect(self): Test creation of an OpenFF Topology object from an OpenMM Topology when the origin PDB lacks CONECT records """ - pdbfile = app.PDBFile(get_data_file_path("systems/test_systems/1_ethanol_no_conect.pdb")) + pdbfile = openmm.app.PDBFile(get_data_file_path("systems/test_systems/1_ethanol_no_conect.pdb")) molecules = [] molecules.append(Molecule.from_smiles("CCO")) @@ -610,7 +612,7 @@ def test_to_from_openmm(self): # Check that bond orders are preserved. n_double_bonds = sum([b.order == 2 for b in omm_topology.bonds()]) - n_aromatic_bonds = sum([b.type is app.Aromatic for b in omm_topology.bonds()]) + n_aromatic_bonds = sum([b.type is openmm.app.Aromatic for b in omm_topology.bonds()]) assert n_double_bonds == 6 assert n_aromatic_bonds == 12 @@ -797,21 +799,18 @@ def test_from_pdb(self): @requires_rdkit def test_from_pdb_input_types(self): - import pathlib - from io import StringIO - import openmm.app protein_path = get_data_file_path("proteins/ace-ala-nh2.pdb") Topology.from_pdb(protein_path) - Topology.from_pdb(pathlib.Path(protein_path)) + Topology.from_pdb(Path(protein_path)) with open(protein_path) as f: Topology.from_pdb(f) - pdb_string = pathlib.Path(protein_path).read_text() + pdb_string = Path(protein_path).read_text() with StringIO(pdb_string) as f: Topology.from_pdb(f) @@ -954,21 +953,15 @@ def test_to_file_units_check(self): - unitless NumPy array - converted OpenMM quantity """ - from tempfile import NamedTemporaryFile - - from openff.units.openmm import to_openmm - - from openff.toolkit.topology import Molecule, Topology - - topology = Topology() - mol = Molecule.from_pdb_and_smiles(get_data_file_path("systems/test_systems/1_ethanol.pdb"), "CCO") - topology.add_molecule(mol) - positions_angstrom = mol.conformers[0] + topology = Topology.from_pdb( + get_data_file_path("systems/test_systems/1_ethanol.pdb"), unique_molecules=[Molecule.from_smiles("CCO")] + ) + positions_angstrom = topology.get_positions().to("angstrom") def _check_file(topology, coordinates): # Write the molecule to PDB and ensure that the X coordinate of the first atom is 10.172 count = 1 - with NamedTemporaryFile(suffix=".pdb") as iofile: + with tempfile.NamedTemporaryFile(suffix=".pdb") as iofile: topology.to_file(iofile.name, coordinates) data = open(iofile.name).readlines() for line in data: @@ -980,7 +973,7 @@ def _check_file(topology, coordinates): _check_file(topology, coordinates=positions_angstrom) _check_file(topology, coordinates=positions_angstrom.to(unit.nanometer)) _check_file(topology, coordinates=positions_angstrom.m) - _check_file(topology, coordinates=to_openmm(positions_angstrom)) + _check_file(topology, coordinates=positions_angstrom.to_openmm()) with pytest.raises(ValueError, match=r"Could not process.*list.*"): _check_file(topology, coordinates=positions_angstrom.m.tolist()) @@ -990,16 +983,13 @@ def test_to_file_fileformat_lettercase(self): """ Checks if fileformat specifier is indpendent of upper/lowercase """ - from tempfile import NamedTemporaryFile - - from openff.toolkit.topology import Molecule, Topology + topology = Topology.from_pdb( + get_data_file_path("systems/test_systems/1_ethanol.pdb"), unique_molecules=[Molecule.from_smiles("CCO")] + ) + positions = topology.get_positions().to("angstrom") - topology = Topology() - mol = Molecule.from_pdb_and_smiles(get_data_file_path("systems/test_systems/1_ethanol.pdb"), "CCO") - topology.add_molecule(mol) - positions = mol.conformers[0] count = 1 - with NamedTemporaryFile(suffix=".pdb") as iofile: + with tempfile.NamedTemporaryFile(suffix=".pdb") as iofile: topology.to_file(iofile.name, positions, file_format="pDb") data = open(iofile.name).readlines() for line in data: @@ -1013,12 +1003,11 @@ def test_to_file_fileformat_invalid(self): """ Checks for invalid file format """ - from openff.toolkit.topology import Molecule, Topology + topology = Topology.from_pdb( + get_data_file_path("systems/test_systems/1_ethanol.pdb"), unique_molecules=[Molecule.from_smiles("CCO")] + ) + positions = topology.get_positions().to("angstrom") - topology = Topology() - mol = Molecule.from_pdb_and_smiles(get_data_file_path("systems/test_systems/1_ethanol.pdb"), "CCO") - topology.add_molecule(mol) - positions = mol.conformers[0] fname = "ethanol_file.pdb" with pytest.raises(NotImplementedError): topology.to_file(fname, positions, file_format="AbC") @@ -1027,13 +1016,9 @@ def test_to_file_no_molecules(self): """ Checks if Topology.to_file() writes a file with no topology and no coordinates """ - from tempfile import NamedTemporaryFile - - from openff.toolkit.topology import Topology - topology = Topology() lines = [] - with NamedTemporaryFile(suffix=".pdb") as iofile: + with tempfile.NamedTemporaryFile(suffix=".pdb") as iofile: topology.to_file(iofile.name, [] * unit.nanometer) data = open(iofile.name).readlines() for line in data: @@ -1046,21 +1031,19 @@ def test_to_file_multi_molecule_different_order(self): Checks for the following if Topology.to_write maintains the order of atoms for the same molecule with different indexing """ - from tempfile import NamedTemporaryFile - - from openff.toolkit.topology import Molecule, Topology - topology = Topology() topology.add_molecule(create_ethanol()) topology.add_molecule(create_reversed_ethanol()) - mol = Molecule.from_pdb_and_smiles(get_data_file_path("systems/test_systems/1_ethanol.pdb"), "CCO") + mol = Topology.from_pdb( + get_data_file_path("systems/test_systems/1_ethanol.pdb"), unique_molecules=[Molecule.from_smiles("CCO")] + ).molecule(0) positions = mol.conformers[0] # Make up coordinates for the second ethanol by translating the first by 10 angstroms (note that this will # still be a gibberish conformation, since the atom order in the second molecule is different) positions = np.concatenate([positions, positions + 10.0 * unit.angstrom]) element_order = [] - with NamedTemporaryFile(suffix=".pdb") as iofile: + with tempfile.NamedTemporaryFile(suffix=".pdb") as iofile: topology.to_file(iofile.name, positions) data = open(iofile.name).readlines() for line in data: @@ -1092,15 +1075,10 @@ def test_to_file_object(self): """ Checks that a file-like object can be written to (vs a path or str) """ - from io import StringIO - from tempfile import NamedTemporaryFile - - from openff.toolkit.topology import Molecule, Topology - - topology = Topology() - mol = Molecule.from_pdb_and_smiles(get_data_file_path("systems/test_systems/1_ethanol.pdb"), "CCO") - topology.add_molecule(mol) - positions = mol.conformers[0] + topology = Topology.from_pdb( + get_data_file_path("systems/test_systems/1_ethanol.pdb"), unique_molecules=[Molecule.from_smiles("CCO")] + ) + positions = topology.get_positions() # Check a file-like wrapper around a str with StringIO() as iofile: @@ -1108,13 +1086,13 @@ def test_to_file_object(self): data1 = [line + "\n" for line in iofile.getvalue().splitlines()] # Check an actual real file object - with NamedTemporaryFile(mode="w+", suffix=".pdb") as iofile: + with tempfile.NamedTemporaryFile(mode="w+", suffix=".pdb") as iofile: topology.to_file(iofile, positions) iofile.seek(0) data2 = iofile.readlines() # Do it the old fashioned way for comparison - with NamedTemporaryFile(mode="w+", suffix=".pdb") as iofile: + with tempfile.NamedTemporaryFile(mode="w+", suffix=".pdb") as iofile: topology.to_file(iofile.name, positions) data3 = iofile.readlines() @@ -1126,13 +1104,9 @@ def test_to_file_automatic_positions(self): """ Checks that to_file can take positions from the topology """ - from io import StringIO - - from openff.toolkit.topology import Molecule, Topology - - topology = Topology() - mol = Molecule.from_pdb_and_smiles(get_data_file_path("systems/test_systems/1_ethanol.pdb"), "CCO") - topology.add_molecule(mol) + topology = Topology.from_pdb( + get_data_file_path("systems/test_systems/1_ethanol.pdb"), unique_molecules=[Molecule.from_smiles("CCO")] + ) count = 1 with StringIO() as iofile: @@ -1148,12 +1122,7 @@ def test_to_file_ensure_uniqueness_true(self): """ Checks that ensure_unique_atom_names=True provides per-molecule unique atom names """ - from io import StringIO - - from openff.toolkit.topology import Molecule, Topology - - mol = Molecule.from_polymer_pdb(get_data_file_path("proteins/MainChain_ALA_ALA.pdb")) - topology = Topology.from_molecules([mol]) + topology = Topology.from_pdb(get_data_file_path("proteins/MainChain_ALA_ALA.pdb")) with StringIO() as iofile: topology.to_file(iofile, ensure_unique_atom_names=True) @@ -1170,12 +1139,8 @@ def test_to_file_ensure_uniqueness_false(self): """ Checks that ensure_unique_atom_names=False preserves atom names """ - from io import StringIO - - from openff.toolkit.topology import Molecule, Topology + topology = Topology.from_pdb(get_data_file_path("proteins/MainChain_ALA_ALA.pdb")) - mol = Molecule.from_polymer_pdb(get_data_file_path("proteins/MainChain_ALA_ALA.pdb")) - topology = Topology.from_molecules([mol]) atom_names = set([atom.name for atom in topology.atoms]) assert None not in atom_names, "All input atoms must be named" @@ -1192,19 +1157,12 @@ def test_to_file_ensure_uniqueness_residues(self): """ Checks that ensure_unique_atom_names="residues" provides per-residue unique atom names """ - from io import StringIO - - from openff.toolkit.topology import Molecule, Topology - - mol = Molecule.from_polymer_pdb(get_data_file_path("proteins/MainChain_ALA_ALA.pdb")) - topology = Topology.from_molecules([mol]) + topology = Topology.from_pdb(get_data_file_path("proteins/MainChain_ALA_ALA.pdb")) with StringIO() as iofile: topology.to_file(iofile, ensure_unique_atom_names="residues") data = iofile.getvalue().splitlines() - from collections import defaultdict - residues = defaultdict(list) for line in data: if line.startswith("ATOM") or line.startswith("HETATM"): @@ -1222,7 +1180,7 @@ def test_from_openmm_duplicate_unique_mol(self): """ Check that a DuplicateUniqueMoleculeError is raised if we try to pass in two indistinguishably unique mols """ - pdbfile = app.PDBFile(get_data_file_path("systems/packmol_boxes/cyclohexane_ethanol_0.4_0.6.pdb")) + pdbfile = openmm.app.PDBFile(get_data_file_path("systems/packmol_boxes/cyclohexane_ethanol_0.4_0.6.pdb")) molecules = [ Molecule.from_file(get_data_file_path(name)) for name in ( @@ -1349,8 +1307,7 @@ def test_to_openmm_preserve_per_residue_unique_atom_names(self, explicit_arg): Test that to_openmm preserves atom names that are unique per-residue by default """ # Create a topology from a capped dialanine - peptide = Molecule.from_polymer_pdb(get_data_file_path("proteins/MainChain_ALA_ALA.pdb")) - off_topology = Topology.from_molecules([peptide]) + off_topology = Topology.from_pdb(get_data_file_path("proteins/MainChain_ALA_ALA.pdb")) # Assert the test's assumptions _ace, ala1, ala2, _nme = off_topology.hierarchy_iterator("residues") @@ -1383,8 +1340,7 @@ def test_to_openmm_generate_per_residue_unique_atom_names(self, explicit_arg): Test that to_openmm preserves atom names that are unique per-residue by default """ # Create a topology from a capped dialanine - peptide = Molecule.from_polymer_pdb(get_data_file_path("proteins/MainChain_ALA_ALA.pdb")) - off_topology = Topology.from_molecules([peptide]) + off_topology = Topology.from_pdb(get_data_file_path("proteins/MainChain_ALA_ALA.pdb")) # Remove atom names from some residues, make others have duplicate atom names ace, ala1, ala2, nme = off_topology.hierarchy_iterator("residues") @@ -1429,8 +1385,7 @@ def test_to_openmm_generate_per_molecule_unique_atom_names_with_residues(self, e Test that to_openmm preserves atom names that are unique per-residue by default """ # Create a topology from a capped dialanine - peptide = Molecule.from_polymer_pdb(get_data_file_path("proteins/MainChain_ALA_ALA.pdb")) - off_topology = Topology.from_molecules([peptide]) + off_topology = Topology.from_pdb(get_data_file_path("proteins/MainChain_ALA_ALA.pdb")) # Remove atom names from some residues, make others have duplicate atom names ace, ala1, ala2, nme = off_topology.hierarchy_iterator("residues") @@ -1566,7 +1521,7 @@ def test_identical_molecule_groups_mixed_topology(self, mixed_topology): def test_chemical_environments_matches_OE(self): """Test Topology.chemical_environment_matches""" toolkit_wrapper = OpenEyeToolkitWrapper() - pdbfile = app.PDBFile(get_data_file_path("systems/packmol_boxes/cyclohexane_ethanol_0.4_0.6.pdb")) + pdbfile = openmm.app.PDBFile(get_data_file_path("systems/packmol_boxes/cyclohexane_ethanol_0.4_0.6.pdb")) # toolkit_wrapper = RDKitToolkitWrapper() molecules = [ Molecule.from_file(get_data_file_path(name)) @@ -1592,7 +1547,7 @@ def test_chemical_environments_matches_OE(self): def test_chemical_environments_matches_RDK(self): """Test Topology.chemical_environment_matches""" toolkit_wrapper = RDKitToolkitWrapper() - pdbfile = app.PDBFile(get_data_file_path("systems/packmol_boxes/cyclohexane_ethanol_0.4_0.6.pdb")) + pdbfile = openmm.app.PDBFile(get_data_file_path("systems/packmol_boxes/cyclohexane_ethanol_0.4_0.6.pdb")) # toolkit_wrapper = RDKitToolkitWrapper() # molecules = [Molecule.from_file(get_data_file_path(name)) for name in ('molecules/ethanol.mol2', # 'molecules/cyclohexane.mol2')] @@ -1855,12 +1810,9 @@ def test_molecule_order_wins_over_residue_order( """Test that no effort is made to sort residues by residue number across molecules.""" protein_path = get_data_file_path("proteins/ace-ala-nh2.pdb") - topology = Topology.from_molecules( - [ - Molecule.from_polymer_pdb(protein_path), - Molecule.from_polymer_pdb(protein_path), - ] - ) + protein = Topology.from_pdb(protein_path).molecule(0) + + topology = Topology.from_molecules([protein, protein]) assert topology.residues[0].residue_number == topology.residues[3].residue_number assert topology.residues[1].residue_number == topology.residues[4].residue_number diff --git a/pyproject.toml b/pyproject.toml index e00f4868a..31c5b9b63 100644 --- a/pyproject.toml +++ b/pyproject.toml @@ -20,17 +20,15 @@ classifiers = [ "Programming Language :: Python :: 3.14", "Topic :: Utilities", ] - dynamic = [ "version" ] urls = { Homepage = "https://github.com/openforcefield/openff-toolkit" } -[tool.setuptools.packages.find] -include = [ 'openff.toolkit*' ] +[tool.setuptools] +packages.find.include = [ "openff.toolkit*" ] [tool.ruff] line-length = 119 exclude = [ "examples/deprecated/", "utilities/deprecated" ] - lint.select = [ "E", "F", "I", "NPY", "RUF", "UP", "W" ] lint.ignore = [ "D105", "D107", "D200", "D203", "D212", "E721", "RUF012" ] lint.per-file-ignores."docs/users/molecule_cookbook.ipynb" = [ "E402", "F821" ] @@ -44,19 +42,6 @@ lint.isort.known-first-party = [ "openff.toolkit" ] # https://docs.astral.sh/ruff/settings/#lint_isort_known-third-party lint.isort.known-third-party = [ "openff.interchange", "openff.utilities", "openff.units" ] -[tool.coverage.run] -omit = [ - "*/*/_tests/*", -] - -[tool.coverage.report] -exclude_lines = [ - "pragma: no cover", - "if TYPE_CHECKING:", - "raise NotImplementedError", - "@overload", -] - [tool.mypy] python_version = 3.12 plugins = "numpy.typing.mypy_plugin" @@ -92,4 +77,28 @@ module = [ ] ignore_missing_imports = true +[tool.pytest] +ini_options.addopts = [ "--strict-markers" ] +ini_options.markers = [ + "slow: marks tests as slow (deselect with '-m \"not slow\"')", + "wip: marks tests as work in progress", +] +ini_options.doctest_optionflags = [ + "ELLIPSIS", + "DONT_ACCEPT_TRUE_FOR_1", + "NORMALIZE_WHITESPACE", +] +ini_options.testpaths = [ "openff/toolkit/_tests/" ] + +[tool.coverage] +run.omit = [ + "*/*/_tests/*", +] +report.exclude_lines = [ + "@overload", + "if TYPE_CHECKING:", + "pragma: no cover", + "raise NotImplementedError", +] + [tool.versioningit] diff --git a/setup.cfg b/setup.cfg deleted file mode 100644 index 42a98b1ea..000000000 --- a/setup.cfg +++ /dev/null @@ -1,6 +0,0 @@ -[tool:pytest] -doctest_optionflags = - ELLIPSIS - DONT_ACCEPT_TRUE_FOR_1 - NORMALIZE_WHITESPACE -testpaths = openff/toolkit/_tests/ From 756b8253646761ffe8b68603463396b0e27ad7c7 Mon Sep 17 00:00:00 2001 From: "Matthew W. Thompson" Date: Tue, 7 Apr 2026 13:36:03 -0500 Subject: [PATCH 2/7] Sync test dependencies across environments --- devtools/conda-envs/rdkit-examples.yaml | 1 + devtools/conda-envs/rdkit.yaml | 1 + 2 files changed, 2 insertions(+) diff --git a/devtools/conda-envs/rdkit-examples.yaml b/devtools/conda-envs/rdkit-examples.yaml index dbb60133e..a2fa0122f 100644 --- a/devtools/conda-envs/rdkit-examples.yaml +++ b/devtools/conda-envs/rdkit-examples.yaml @@ -32,6 +32,7 @@ dependencies: - pytest =8 - pytest-xdist - pytest-rerunfailures + - pytest-timeout - pyyaml - toml - bson diff --git a/devtools/conda-envs/rdkit.yaml b/devtools/conda-envs/rdkit.yaml index 6f1d980ce..d5c7c4414 100644 --- a/devtools/conda-envs/rdkit.yaml +++ b/devtools/conda-envs/rdkit.yaml @@ -30,6 +30,7 @@ dependencies: - pytest-cov - pytest-xdist - pytest-rerunfailures + - pytest-timeout - pyyaml - toml - bson From 36dc55603675f6c950083cf63682c0e830c074a2 Mon Sep 17 00:00:00 2001 From: "Matthew W. Thompson" Date: Tue, 7 Apr 2026 13:39:43 -0500 Subject: [PATCH 3/7] Sync test dependencies across environments --- devtools/conda-envs/openeye.yaml | 1 + 1 file changed, 1 insertion(+) diff --git a/devtools/conda-envs/openeye.yaml b/devtools/conda-envs/openeye.yaml index 61f75e468..0a8406040 100644 --- a/devtools/conda-envs/openeye.yaml +++ b/devtools/conda-envs/openeye.yaml @@ -29,6 +29,7 @@ dependencies: - pytest-cov - pytest-xdist - pytest-rerunfailures + - pytest-timeout - pyyaml - toml - bson From 331351c80a54a821474f8bf15754ca5c453f7862 Mon Sep 17 00:00:00 2001 From: Matt Thompson Date: Tue, 7 Apr 2026 14:33:34 -0500 Subject: [PATCH 4/7] Apply suggestions from code review Co-authored-by: Copilot <175728472+Copilot@users.noreply.github.com> --- openff/toolkit/_tests/test_topology.py | 5 ++--- 1 file changed, 2 insertions(+), 3 deletions(-) diff --git a/openff/toolkit/_tests/test_topology.py b/openff/toolkit/_tests/test_topology.py index 0881565b2..142792f3d 100644 --- a/openff/toolkit/_tests/test_topology.py +++ b/openff/toolkit/_tests/test_topology.py @@ -256,8 +256,6 @@ def test_box_vectors(self): def test_issue_1527(self): """Test the error handling of setting box vectors with an OpenMM quantity.""" - import openmm.unit - topology = Topology() topology.box_vectors = np.ones(3) * openmm.unit.nanometer @@ -1811,8 +1809,9 @@ def test_molecule_order_wins_over_residue_order( protein_path = get_data_file_path("proteins/ace-ala-nh2.pdb") protein = Topology.from_pdb(protein_path).molecule(0) + protein_copy = deepcopy(protein) - topology = Topology.from_molecules([protein, protein]) + topology = Topology.from_molecules([protein, protein_copy]) assert topology.residues[0].residue_number == topology.residues[3].residue_number assert topology.residues[1].residue_number == topology.residues[4].residue_number From 3bcf7dd53f3dd26c1e532266870a7b1abd72b93f Mon Sep 17 00:00:00 2001 From: "Matthew W. Thompson" Date: Thu, 9 Apr 2026 08:11:59 -0500 Subject: [PATCH 5/7] Run "slow" tests only on cron jobs --- .github/workflows/CI.yml | 5 +++-- 1 file changed, 3 insertions(+), 2 deletions(-) diff --git a/.github/workflows/CI.yml b/.github/workflows/CI.yml index cd75ebf54..120fa78a0 100644 --- a/.github/workflows/CI.yml +++ b/.github/workflows/CI.yml @@ -142,8 +142,9 @@ jobs: PYTEST_ARGS+=" --ignore=openff/toolkit/_tests/test_examples.py" PYTEST_ARGS+=" --ignore=openff/toolkit/_tests/test_links.py" - # TODO: Flip back to schedule condition before merge - PYTEST_ARGS+=" --runslow" + if [[ "$GITHUB_EVENT_NAME" == "schedule" ]]; then + PYTEST_ARGS+=" --runslow" + fi python -m pytest -x --durations=20 $PYTEST_ARGS $COV openff/toolkit/_tests From cfd65a82a9f175bd6d16c9ba372eeba204e60262 Mon Sep 17 00:00:00 2001 From: "Matthew W. Thompson" Date: Thu, 9 Apr 2026 08:19:35 -0500 Subject: [PATCH 6/7] Avoid another warning --- openff/toolkit/_tests/test_nagl.py | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/openff/toolkit/_tests/test_nagl.py b/openff/toolkit/_tests/test_nagl.py index 16e32be87..845b12e3d 100644 --- a/openff/toolkit/_tests/test_nagl.py +++ b/openff/toolkit/_tests/test_nagl.py @@ -95,8 +95,8 @@ def test_atom_order_dependence(self): ) numpy.testing.assert_allclose( - forward.partial_charges, - reverse.partial_charges[::-1], + forward.partial_charges.m, + reverse.partial_charges.m[::-1], atol=1e-7, ) From 08f2d10713db3ee837be52485b2e1eb002338a5e Mon Sep 17 00:00:00 2001 From: "Matthew W. Thompson" Date: Thu, 9 Apr 2026 08:56:20 -0500 Subject: [PATCH 7/7] Have RDKit when required --- openff/toolkit/_tests/test_topology.py | 7 +++++++ 1 file changed, 7 insertions(+) diff --git a/openff/toolkit/_tests/test_topology.py b/openff/toolkit/_tests/test_topology.py index 142792f3d..a41e5dd4f 100644 --- a/openff/toolkit/_tests/test_topology.py +++ b/openff/toolkit/_tests/test_topology.py @@ -1116,6 +1116,7 @@ def test_to_file_automatic_positions(self): coord = line.split()[-6] assert coord == "10.172" + @requires_rdkit def test_to_file_ensure_uniqueness_true(self): """ Checks that ensure_unique_atom_names=True provides per-molecule unique atom names @@ -1133,6 +1134,7 @@ def test_to_file_ensure_uniqueness_true(self): atom_names.append(atom_name) assert len(atom_names) == len(set(atom_names)) + @requires_rdkit def test_to_file_ensure_uniqueness_false(self): """ Checks that ensure_unique_atom_names=False preserves atom names @@ -1151,6 +1153,7 @@ def test_to_file_ensure_uniqueness_false(self): atom_name = line[12:16].strip() assert atom_name in atom_names + @requires_rdkit def test_to_file_ensure_uniqueness_residues(self): """ Checks that ensure_unique_atom_names="residues" provides per-residue unique atom names @@ -1299,6 +1302,7 @@ def test_to_openmm_do_not_assign_unique_atom_names(self): # and 12 atoms named "", for a total of 3 unique atom names assert len(atom_names) == 3 + @requires_rdkit @pytest.mark.parametrize("explicit_arg", [True, False]) def test_to_openmm_preserve_per_residue_unique_atom_names(self, explicit_arg): """ @@ -1332,6 +1336,7 @@ def test_to_openmm_preserve_per_residue_unique_atom_names(self, explicit_arg): final_atomnames = [str(atom.name) for atom in omm_topology.atoms()] assert final_atomnames == init_atomnames + @requires_rdkit @pytest.mark.parametrize("explicit_arg", [True, False]) def test_to_openmm_generate_per_residue_unique_atom_names(self, explicit_arg): """ @@ -1377,6 +1382,7 @@ def test_to_openmm_generate_per_residue_unique_atom_names(self, explicit_arg): atom_names.add(atom.name) assert len(atom_names) < 32, "There should be duplicate atom names in this output topology" + @requires_rdkit @pytest.mark.parametrize("ensure_unique_atom_names", ["chains", True]) def test_to_openmm_generate_per_molecule_unique_atom_names_with_residues(self, ensure_unique_atom_names): """ @@ -1802,6 +1808,7 @@ def test_error_when_mixed_iterators( ): topology.residues + @requires_rdkit def test_molecule_order_wins_over_residue_order( self, ):