From 64c96dc35df1f85a02ec90a56cb175e10b50dedc Mon Sep 17 00:00:00 2001 From: Han Wang Date: Sat, 4 Jul 2026 00:56:29 +0800 Subject: [PATCH 1/2] test(pt_expt): restore sys.modules snapshot in plugin entry-point test test_plugin popped deepmd.pt_expt from sys.modules without restoring it, leaving the package's cached submodules bound to a dead parent. Any later import of a cached submodule (e.g. deepmd.pt_expt.infer.deep_eval) re-created a BARE parent whose utils/infer attributes were never rebound, and mock.patch('deepmd.pt_expt.utils...') in test_deep_eval_serialize_api failed with AttributeError under py3.10's mock target resolution. Shard-order dependent: this PR's new test files reshuffled CI shard 4 and exposed the latent master-side hygiene bug. Snapshot the whole deepmd.pt_expt module tree before the re-import and restore it (including the parent-package attribute) afterwards. --- source/tests/pt_expt/test_plugin.py | 26 +++++++++++++++++++++++++- 1 file changed, 25 insertions(+), 1 deletion(-) diff --git a/source/tests/pt_expt/test_plugin.py b/source/tests/pt_expt/test_plugin.py index a59242e592..ddee7a6876 100644 --- a/source/tests/pt_expt/test_plugin.py +++ b/source/tests/pt_expt/test_plugin.py @@ -22,12 +22,36 @@ def fake_entry_points(*, group=None): return [_FakeEntryPoint(calls)] monkeypatch.setattr(importlib.metadata, "entry_points", fake_entry_points) + + # Snapshot the deepmd.pt_expt module tree BEFORE re-importing. Just popping + # "deepmd.pt_expt" and leaving its submodules cached poisons sys.modules + # for the rest of the pytest process: a later import of a cached submodule + # (e.g. deepmd.pt_expt.infer.deep_eval) re-creates a BARE parent package + # whose submodule attributes (utils/infer/...) are never rebound, and + # mock.patch("deepmd.pt_expt.utils...") then fails with AttributeError on + # py3.10 (shard-order dependent CI failure). + saved = { + k: v + for k, v in sys.modules.items() + if k == "deepmd.pt_expt" or k.startswith("deepmd.pt_expt.") + } + deepmd_pkg = sys.modules.get("deepmd") sys.modules.pop("deepmd.pt_expt", None) try: importlib.import_module("deepmd.pt_expt") finally: - sys.modules.pop("deepmd.pt_expt", None) + # drop everything the fresh import created, then restore the snapshot + # (including the parent-package attribute binding). + for k in [ + m + for m in list(sys.modules) + if m == "deepmd.pt_expt" or m.startswith("deepmd.pt_expt.") + ]: + sys.modules.pop(k, None) + sys.modules.update(saved) + if deepmd_pkg is not None and "deepmd.pt_expt" in saved: + deepmd_pkg.pt_expt = saved["deepmd.pt_expt"] assert groups == ["deepmd.pt_expt"] assert calls == ["load"] From 69450a5f3f4ab38e043f6b583ed4bed10580558e Mon Sep 17 00:00:00 2001 From: Han Wang Date: Sat, 4 Jul 2026 15:56:10 +0800 Subject: [PATCH 2/2] test(pt_expt): drop the freshly-bound pt_expt attr when it was absent MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit The finally block only rebound deepmd_pkg.pt_expt when deepmd.pt_expt was in the saved snapshot. When the test runs before anything imports deepmd.pt_expt (shard-order dependent), the fresh import_module still binds deepmd_pkg.pt_expt, and popping the module from sys.modules left that attribute dangling on the parent package — the same leak class this test set out to prevent. Delete the attribute in the not-in-saved case so the parent is restored exactly as found. --- source/tests/pt_expt/test_plugin.py | 10 ++++++++-- 1 file changed, 8 insertions(+), 2 deletions(-) diff --git a/source/tests/pt_expt/test_plugin.py b/source/tests/pt_expt/test_plugin.py index ddee7a6876..61e4ba071d 100644 --- a/source/tests/pt_expt/test_plugin.py +++ b/source/tests/pt_expt/test_plugin.py @@ -50,8 +50,14 @@ def fake_entry_points(*, group=None): ]: sys.modules.pop(k, None) sys.modules.update(saved) - if deepmd_pkg is not None and "deepmd.pt_expt" in saved: - deepmd_pkg.pt_expt = saved["deepmd.pt_expt"] + if deepmd_pkg is not None: + if "deepmd.pt_expt" in saved: + deepmd_pkg.pt_expt = saved["deepmd.pt_expt"] + elif hasattr(deepmd_pkg, "pt_expt"): + # deepmd.pt_expt was not imported before this test; the fresh + # import bound it onto the parent package, so drop that stale + # attribute to leave the parent exactly as we found it. + delattr(deepmd_pkg, "pt_expt") assert groups == ["deepmd.pt_expt"] assert calls == ["load"]