"""VideoQueue 生产者扫描的单测。 目前只覆盖一个点:生产者列目录之后、读 mtime 之前,DiskGuard 可能刚好把文件清掉 (两个后台线程的正常竞态)。线上表现为 `生产者扫描异常: [Errno 2] No such file or directory`,代价是**整轮扫描中断**——排在后面的新素材本轮全都登记不上,要等下一轮。 """ import os import pytest from fam_edge import video_queue as vq_mod from fam_edge.video_queue import VideoQueue class _FakeDB: """只实现生产者路径上用到的方法。""" def __init__(self): self.registered = [] self.activities = [] def get_video_by_filename(self, fn): return None def ensure_video(self, fn, path, camera_name=None): self.registered.append(fn) return len(self.registered) def set_video_file_status(self, vid, valid, err, meta=None): pass def record_activity(self, service, action, detail=''): self.activities.append((service, action)) @pytest.fixture def queue_with_two_files(tmp_path, monkeypatch): monkeypatch.setattr(vq_mod, 'load_config', lambda: { 'gdrive_sync': {'local_dir': str(tmp_path), 'camera_name': '客厅'}, 'video_processing': {'file_validate': True, 'stable_window_sec': 60}, }) (tmp_path / "a_被删掉的.mp4").write_bytes(b"x") (tmp_path / "b_正常的.mp4").write_bytes(b"y") db = _FakeDB() q = VideoQueue(db) monkeypatch.setattr(q, '_enqueue', lambda vid: None) monkeypatch.setattr(vq_mod, 'validate_video', lambda p: (True, '', {'fps': 25})) return q, db def test_produce_skips_file_deleted_mid_scan_and_keeps_going(queue_with_two_files, monkeypatch): """被删掉的那个跳过,后面的照常登记——不能整轮中断。""" q, db = queue_with_two_files real_getmtime = os.path.getmtime def fake_getmtime(path): if 'a_被删掉的' in path: raise FileNotFoundError(2, 'No such file or directory', path) return real_getmtime(path) - 3600 # 早于稳定窗口,视为写入完成 monkeypatch.setattr(vq_mod.os.path, 'getmtime', fake_getmtime) q._produce_once() # 不抛异常 assert db.registered == ["b_正常的.mp4"] # 被删的跳过,后面的没受影响 def test_produce_still_skips_files_being_written(queue_with_two_files, monkeypatch): """mtime 太新(rclone 还在写)仍然要跳过,别把半成品入队。""" q, db = queue_with_two_files monkeypatch.setattr(vq_mod.os.path, 'getmtime', lambda p: __import__('time').time()) q._produce_once() assert db.registered == []