From e5d7fddfcfce220f439ef85e69aba780fe35eebd Mon Sep 17 00:00:00 2001 From: xiaoxia Date: Mon, 31 Aug 2026 09:04:05 +0800 Subject: [PATCH 1/5] =?UTF-8?q?fix(test):=20hevc=20=E8=BD=AC=E7=A0=81?= =?UTF-8?q?=E6=B5=8B=E8=AF=95=E4=B8=8D=E5=86=8D=E6=B3=A8=E5=85=A5=20video?= =?UTF-8?q?=5Fprocessing=20mock=20=E5=88=B0=20sys.modules?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit 根因:sys.modules 注入 video_processing.oss_helpers / thumbnail_generator 的 MagicMock 即使清理,仍因 video_processing/__init__.py 的 `from . import oss_helpers` 导致包属性残留为 MagicMock, 跨文件污染 test_oss_helpers_pure / test_oss_upload_crash_fix / test_p02_worker_oss_fix 等(39 个用例失败)。 修复:不再向 sys.modules 注入 video_processing.* mock,改为直接 import 真实模块后用 patch.object 打补丁(patch.stop 自动还原)。 worker_app.db / celery_app mock 保留(独立包树,不影响共享工具包)。 --- tests/unit/test_ingest_hevc_transcode_task.py | 43 ++++++++----------- 1 file changed, 18 insertions(+), 25 deletions(-) diff --git a/tests/unit/test_ingest_hevc_transcode_task.py b/tests/unit/test_ingest_hevc_transcode_task.py index db1159220..5894f16e6 100644 --- a/tests/unit/test_ingest_hevc_transcode_task.py +++ b/tests/unit/test_ingest_hevc_transcode_task.py @@ -16,12 +16,11 @@ from types import SimpleNamespace from unittest.mock import MagicMock, patch # 在 import worker_app 模块前 mock 掉数据库连接和 celery(同 test_ingest_validation.py) -# 注意:模块级 sys.modules 注入若不撤销,会污染同一 pytest 进程(含 xdist -# worker)后续收集/执行的其他测试文件——它们 from video_processing.xxx -# import 会拿到 MagicMock(表现为 test_thumbnail_generator 纯逻辑用例 -# 断言到 ,50 个用例失败,且与 xdist 分发顺序相关)。 -# 因此在成功 import ingest_mod 之后立即恢复 sys.modules(同 test_dedup_pure.py -# 的做法),mock 对象仍由本文件变量/ingest_mod 引用持有,不影响本文件测试。 +# 注意:只 mock worker_app 的依赖(db / celery),不 mock video_processing.*—— +# 后者属于共享工具包,sys.modules 注入即使清理也会因 video_processing/__init__.py +# 的 `from . import oss_helpers` 导致包属性残留为 MagicMock,跨文件污染 +# test_oss_helpers_pure / test_oss_upload_crash_fix / test_p02_worker_oss_fix +# 等测试文件(39 个用例失败)。改为直接 import 真实模块,patch.object 打补丁。 _SAVED_MODULES_KEYS = set(sys.modules.keys()) _mock_db_module = MagicMock() @@ -41,32 +40,24 @@ def _passthrough_decorator(*args, **kwargs): _mock_celery_module.celery_app.task = MagicMock(side_effect=_passthrough_decorator) sys.modules["worker_app.celery_app"] = _mock_celery_module -# mock video_processing 子模块(主流程会 import 它们) -_oss_helpers_mock = MagicMock() -_thumbnail_mock = MagicMock() -sys.modules["video_processing.oss_helpers"] = _oss_helpers_mock -sys.modules["video_processing.thumbnail_generator"] = _thumbnail_mock - sys.path.insert(0, str(Path(__file__).resolve().parents[2] / "apps" / "worker")) +# ── 先导入真实 video_processing 子模块,再导入 ingest_mod ── +# 不使用 sys.modules 注入 mock,避免 video_processing/__init__.py 的 +# `from . import oss_helpers` 与 mock 注入/清理产生时序冲突。 +from video_processing import oss_helpers as _oss_helpers_real # noqa: E402 +from video_processing import thumbnail_generator as _thumb_gen_real # noqa: E402 + + import pytest # noqa: E402 from worker_app.tasks import ingest as ingest_mod # noqa: E402 -# ── 立即恢复 sys.modules,避免 mock 泄漏到其他测试文件 ── -# 本文件 patch.object 不依赖 mock 条目保留在 sys.modules:download_asset -# 已被 ingest_mod 顶部 from import 绑定;thumb patcher 是字符串目标,start -# 时会触发真实模块重新导入后再打补丁(真实模块 CI 可导入)。因此注入的 -# mock 条目必须全部删除——若保留 video_processing.* mock,同 xdist worker -# 后续测试文件 import 仍会拿到 MagicMock(#1566 v1 曾因此漏修)。 +# ── 恢复 worker_app mock,避免泄漏到其他测试文件 ── for _key in list(sys.modules.keys()): if _key not in _SAVED_MODULES_KEYS: del sys.modules[_key] del _SAVED_MODULES_KEYS -# 导入真实模块供本文件 patch.object 打补丁(restore 后 sys.modules 中已无 -# mock)。patch 在 stop 时会自动还原模块属性,不影响其他测试文件。 -from video_processing import oss_helpers as _oss_helpers_real # noqa: E402 - class _FakeJobRepo: def __init__(self, db): @@ -200,9 +191,10 @@ def task_env(tmp_path): ), "subprocess": patch.object(ingest_mod.subprocess, "run", side_effect=fake_subprocess_run), "ntf": patch.object(tempfile, "NamedTemporaryFile", side_effect=fake_ntf), - # 缩略图生成跳过 - "thumb": patch( - "video_processing.thumbnail_generator.extract_first_frame", + # 缩略图生成跳过——打在真实模块属性上,patch.stop() 自动还原 + "thumb": patch.object( + _thumb_gen_real, + "extract_first_frame", side_effect=RuntimeError("skip thumb"), ), } @@ -312,3 +304,4 @@ class TestIngestHEVCTranscodeFlow: for call in mocks["subprocess"].call_args_list: cmd = call.args[0] if call.args else call.kwargs.get("cmd", []) assert "libx264" not in cmd + -- 2.54.0 From 26856e101521249d6f31914d2e7b2659d5faa2ab Mon Sep 17 00:00:00 2001 From: CI Bot Date: Mon, 31 Aug 2026 01:11:43 +0000 Subject: [PATCH 2/5] style: auto-format with black + isort + prettier [skip ci-format-check] --- tests/unit/test_ingest_hevc_transcode_task.py | 6 ++---- 1 file changed, 2 insertions(+), 4 deletions(-) diff --git a/tests/unit/test_ingest_hevc_transcode_task.py b/tests/unit/test_ingest_hevc_transcode_task.py index 5894f16e6..808c82c68 100644 --- a/tests/unit/test_ingest_hevc_transcode_task.py +++ b/tests/unit/test_ingest_hevc_transcode_task.py @@ -42,14 +42,13 @@ sys.modules["worker_app.celery_app"] = _mock_celery_module sys.path.insert(0, str(Path(__file__).resolve().parents[2] / "apps" / "worker")) +import pytest # noqa: E402 + # ── 先导入真实 video_processing 子模块,再导入 ingest_mod ── # 不使用 sys.modules 注入 mock,避免 video_processing/__init__.py 的 # `from . import oss_helpers` 与 mock 注入/清理产生时序冲突。 from video_processing import oss_helpers as _oss_helpers_real # noqa: E402 from video_processing import thumbnail_generator as _thumb_gen_real # noqa: E402 - - -import pytest # noqa: E402 from worker_app.tasks import ingest as ingest_mod # noqa: E402 # ── 恢复 worker_app mock,避免泄漏到其他测试文件 ── @@ -304,4 +303,3 @@ class TestIngestHEVCTranscodeFlow: for call in mocks["subprocess"].call_args_list: cmd = call.args[0] if call.args else call.kwargs.get("cmd", []) assert "libx264" not in cmd - -- 2.54.0 From 2662cd78fe40621c6180bca2309b28c71938d817 Mon Sep 17 00:00:00 2001 From: xiaoxia Date: Mon, 31 Aug 2026 09:23:19 +0800 Subject: [PATCH 3/5] =?UTF-8?q?fix(test):=20thumb=20patcher=20=E6=94=B9?= =?UTF-8?q?=E5=9B=9E=E5=AD=97=E7=AC=A6=E4=B8=B2=E7=9B=AE=E6=A0=87?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit patch.object(_thumb_gen_real) 对 ingest.py 的局部 import 不生效 (模块对象可能不同),改回 patch(字符串目标) 从 sys.modules 取真实模块打补丁。 核心修复(不注入 video_processing mock 到 sys.modules)不变。 --- tests/unit/test_ingest_hevc_transcode_task.py | 8 +++----- 1 file changed, 3 insertions(+), 5 deletions(-) diff --git a/tests/unit/test_ingest_hevc_transcode_task.py b/tests/unit/test_ingest_hevc_transcode_task.py index 808c82c68..eec93ad3f 100644 --- a/tests/unit/test_ingest_hevc_transcode_task.py +++ b/tests/unit/test_ingest_hevc_transcode_task.py @@ -48,7 +48,6 @@ import pytest # noqa: E402 # 不使用 sys.modules 注入 mock,避免 video_processing/__init__.py 的 # `from . import oss_helpers` 与 mock 注入/清理产生时序冲突。 from video_processing import oss_helpers as _oss_helpers_real # noqa: E402 -from video_processing import thumbnail_generator as _thumb_gen_real # noqa: E402 from worker_app.tasks import ingest as ingest_mod # noqa: E402 # ── 恢复 worker_app mock,避免泄漏到其他测试文件 ── @@ -190,10 +189,9 @@ def task_env(tmp_path): ), "subprocess": patch.object(ingest_mod.subprocess, "run", side_effect=fake_subprocess_run), "ntf": patch.object(tempfile, "NamedTemporaryFile", side_effect=fake_ntf), - # 缩略图生成跳过——打在真实模块属性上,patch.stop() 自动还原 - "thumb": patch.object( - _thumb_gen_real, - "extract_first_frame", + # 缩略图生成跳过——字符串目标,patch.start 时从 sys.modules 取真实模块再打补丁 + "thumb": patch( + "video_processing.thumbnail_generator.extract_first_frame", side_effect=RuntimeError("skip thumb"), ), } -- 2.54.0 From 8d8b565499c4ef0a03fef29e398025905cb50903 Mon Sep 17 00:00:00 2001 From: xiaoxia Date: Mon, 31 Aug 2026 10:57:54 +0800 Subject: [PATCH 4/5] =?UTF-8?q?fix(test):=20upload=5Fto=5Foss=20patcher=20?= =?UTF-8?q?=E6=94=B9=E7=94=A8=E5=AD=97=E7=AC=A6=E4=B8=B2=E7=9B=AE=E6=A0=87?= =?UTF-8?q?=EF=BC=8C=E4=BF=AE=E5=A4=8D=20ingest.py=20=E5=B1=80=E9=83=A8=20?= =?UTF-8?q?import=20=E4=B8=8D=E7=94=9F=E6=95=88?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit patch.object(_oss_helpers_real, ...) 对 ingest.py 函数体内的 from video_processing.oss_helpers import upload_to_oss 不生效, 改为 patch("video_processing.oss_helpers.upload_to_oss", ...) 与 thumb patcher 策略一致,确保 patch.start() 时从 sys.modules 解析模块对象再打补丁。 --- tests/unit/test_ingest_hevc_transcode_task.py | 6 ++---- 1 file changed, 2 insertions(+), 4 deletions(-) diff --git a/tests/unit/test_ingest_hevc_transcode_task.py b/tests/unit/test_ingest_hevc_transcode_task.py index eec93ad3f..8627a8a17 100644 --- a/tests/unit/test_ingest_hevc_transcode_task.py +++ b/tests/unit/test_ingest_hevc_transcode_task.py @@ -47,7 +47,6 @@ import pytest # noqa: E402 # ── 先导入真实 video_processing 子模块,再导入 ingest_mod ── # 不使用 sys.modules 注入 mock,避免 video_processing/__init__.py 的 # `from . import oss_helpers` 与 mock 注入/清理产生时序冲突。 -from video_processing import oss_helpers as _oss_helpers_real # noqa: E402 from worker_app.tasks import ingest as ingest_mod # noqa: E402 # ── 恢复 worker_app mock,避免泄漏到其他测试文件 ── @@ -166,9 +165,8 @@ def task_env(tmp_path): "job_repo": patch.object(ingest_mod, "SQLAlchemyIngestJobRepository", return_value=job_repo), "asset_repo": patch.object(ingest_mod, "SQLAlchemyAssetRepository", return_value=asset_repo), "download": patch.object(ingest_mod, "download_asset", return_value=True), - "upload": patch.object( - _oss_helpers_real, - "upload_to_oss", + "upload": patch( + "video_processing.oss_helpers.upload_to_oss", return_value=control["upload_url"], ), "metadata": patch.object( -- 2.54.0 From 301b5910375f4d834d8415d973f5d6085e9eeaa7 Mon Sep 17 00:00:00 2001 From: xiaoxia Date: Mon, 31 Aug 2026 11:11:43 +0800 Subject: [PATCH 5/5] =?UTF-8?q?fix(test):=20=E6=B8=85=E7=90=86=20sys.modul?= =?UTF-8?q?es=20=E6=97=B6=E4=BF=9D=E7=95=99=20video=5Fprocessing.*=20?= =?UTF-8?q?=E6=A8=A1=E5=9D=97?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit 清理逻辑跳过 video_processing.* 前缀的模块,确保 patch() 字符串 目标解析到的模块对象与 ingest_mod 内部引用的一致,避免 mock 失效。 修复 AI Code Review 阻塞级问题。 --- tests/unit/test_ingest_hevc_transcode_task.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/tests/unit/test_ingest_hevc_transcode_task.py b/tests/unit/test_ingest_hevc_transcode_task.py index 8627a8a17..cbcb8f13a 100644 --- a/tests/unit/test_ingest_hevc_transcode_task.py +++ b/tests/unit/test_ingest_hevc_transcode_task.py @@ -51,7 +51,7 @@ from worker_app.tasks import ingest as ingest_mod # noqa: E402 # ── 恢复 worker_app mock,避免泄漏到其他测试文件 ── for _key in list(sys.modules.keys()): - if _key not in _SAVED_MODULES_KEYS: + if _key not in _SAVED_MODULES_KEYS and not _key.startswith("video_processing"): del sys.modules[_key] del _SAVED_MODULES_KEYS -- 2.54.0