From e556c8bab068944d68ecdae491996ed6e37fce5a Mon Sep 17 00:00:00 2001 From: LLLin000 <809867916@qq.com> Date: Tue, 14 Jul 2026 18:32:48 +0800 Subject: [PATCH] fix: complete final OCR batch item normally --- paperforge/commands/ocr.py | 9 ++- tests/cli/test_ocr_progress_contracts.py | 70 ++++++++++++++++++++++++ 2 files changed, 76 insertions(+), 3 deletions(-) diff --git a/paperforge/commands/ocr.py b/paperforge/commands/ocr.py index aabe458c..5d9a9348 100644 --- a/paperforge/commands/ocr.py +++ b/paperforge/commands/ocr.py @@ -342,7 +342,8 @@ def _run_ocr_redo(vault: Path, keys: list[str] | None = None, dry_run: bool = Fa if failed_keys: print(f"Redo OCR pending/failed={len(failed_keys)}: {', '.join(failed_keys)}", flush=True) - if batch and _is_stopped(): + _real_stop = batch and _is_stopped() and _current[0] < total + if _real_stop: print(f"Batch stopped (SIGINT) after {_current[0]} paper(s).") if batch: @@ -350,7 +351,7 @@ def _run_ocr_redo(vault: Path, keys: list[str] | None = None, dry_run: bool = Fa finally: _restore_signal() - if batch and _is_stopped(): + if batch and _is_stopped() and _current[0] < total: return 130 return worker_exit_code @@ -591,7 +592,9 @@ def _run_ocr_rebuild( print(f"OCR_REBUILD_DONE", flush=True) finally: _restore_signal() - return 0 if not _is_stopped() else 130 + + _real_stop = batch and _is_stopped() and _count < total + return 130 if _real_stop else 0 diff --git a/tests/cli/test_ocr_progress_contracts.py b/tests/cli/test_ocr_progress_contracts.py index 4b287306..9d99ca0f 100644 --- a/tests/cli/test_ocr_progress_contracts.py +++ b/tests/cli/test_ocr_progress_contracts.py @@ -500,6 +500,76 @@ class TestCooperativeStop: restore() + def test_redo_stop_flag_but_all_complete_returns_normal(self, capsys, monkeypatch, tmp_path): + """Stop flag set after all papers done: normal exit, no 'stopped' message.""" + _mock_run_ocr(monkeypatch) + _mock_validate_ocr_meta(monkeypatch) + vault = _make_minimal_vault(tmp_path) + _add_literature_note(vault, "KEY00001") + _add_literature_note(vault, "KEY00002") + + # Mock _make_cooperative_stop; _StopChecker always returns False + # (so all papers process), but _is_stopped returns True after processing + # to simulate late SIGINT after all progress reached total + class _LateStop: + """Return True only after being checked at least 3 times (past all papers).""" + def __init__(self): + self.count = 0 + def __call__(self): + self.count += 1 + return self.count >= 10 # always False during processing + + checker = _LateStop() + def _fake_make(): + return (checker, lambda: None) + + monkeypatch.setattr( + "paperforge.commands.ocr._make_cooperative_stop", + _fake_make, + ) + + from paperforge.commands.ocr import _run_ocr_redo + rc = _run_ocr_redo(vault, keys=["KEY00001", "KEY00002"]) + + assert rc == 0, f"Expected 0 (all done), got {rc}" + captured = capsys.readouterr().out + assert "Batch stopped" not in captured + assert "OCR_REDO_DONE" in captured + + def test_rebuild_stop_flag_but_all_complete_returns_normal(self, capsys, monkeypatch, tmp_path): + """Rebuild stop flag after all done: normal exit, no 'stopped'.""" + vault = _make_minimal_vault(tmp_path) + keys = ["KEY00001", "KEY00002"] + + from tests.cli.test_ocr_progress_contracts import TestOcrRebuildProgressTokens + helper = TestOcrRebuildProgressTokens() + helper._setup_mock_selection_and_worker(vault, keys, monkeypatch) + + # Late stop as above + class _LateStop: + def __init__(self): + self.count = 0 + def __call__(self): + self.count += 1 + return self.count >= 10 + + checker = _LateStop() + def _fake_make(): + return (checker, lambda: None) + + monkeypatch.setattr( + "paperforge.commands.ocr._make_cooperative_stop", + _fake_make, + ) + + from paperforge.commands.ocr import _run_ocr_rebuild + rc = _run_ocr_rebuild(vault, keys=keys) + + assert rc == 0, f"Expected 0 (all done), got {rc}" + captured = capsys.readouterr().out + assert "OCR_REBUILD_DONE" in captured + assert "Batch stopped" not in captured + class TestOcrListNeedsRebuild: """OCR list --json includes needs_derived_rebuild."""