v0.6.0 BigUpdate
This commit is contained in:
@@ -14,8 +14,11 @@ from md2gost.word_fix import (
|
||||
find_page_break_row,
|
||||
fix_continuations,
|
||||
is_caption_orphaned,
|
||||
continuation_pass_limit,
|
||||
is_orphan_header_break,
|
||||
locate_break_row,
|
||||
parse_caption_text,
|
||||
resume_page_before_cut,
|
||||
split_vmerge_spans_at_break,
|
||||
)
|
||||
|
||||
@@ -61,6 +64,68 @@ def test_find_page_break_row():
|
||||
assert find_page_break_row([14, 14, 14, 14, 14, 14, 15, 15]) == 7
|
||||
|
||||
|
||||
def test_resume_page_before_cut():
|
||||
assert resume_page_before_cut(1) == 1
|
||||
assert resume_page_before_cut(2) == 1
|
||||
assert resume_page_before_cut(15) == 14
|
||||
|
||||
|
||||
def test_locate_break_row_matches_full_scan_from_page_1():
|
||||
samples = [
|
||||
[1, 1, 2, 2],
|
||||
[2, 2, 3],
|
||||
[1, 2],
|
||||
[14, 14, 14, 14, 14, 14, 15, 15],
|
||||
[1, 1, 1],
|
||||
[8] * 30 + [9] * 10 + [12] * 5,
|
||||
]
|
||||
for pages in samples:
|
||||
def page_at(row, pages=pages):
|
||||
return pages[row - 1]
|
||||
|
||||
found, cut = locate_break_row(len(pages), page_at, 1)
|
||||
assert found == find_page_break_row(pages)
|
||||
if found:
|
||||
assert cut == pages[found - 1]
|
||||
|
||||
|
||||
def test_locate_break_row_starts_at_page_before_cut():
|
||||
"""A long table is not read from row 1 when the window starts near the cut."""
|
||||
pages = [10] * 80 + [14] * 8 + [15] * 120
|
||||
probes: list[int] = []
|
||||
|
||||
def page_at(row):
|
||||
probes.append(row)
|
||||
return pages[row - 1]
|
||||
|
||||
found, cut = locate_break_row(len(pages), page_at, min_page=14)
|
||||
assert found == 81
|
||||
assert cut == 14
|
||||
assert len(probes) < 40
|
||||
|
||||
continuation = [15] * 12 + [16] * 200
|
||||
cont_probes: list[int] = []
|
||||
|
||||
def continuation_at(row):
|
||||
cont_probes.append(row)
|
||||
return continuation[row - 1]
|
||||
|
||||
found, cut = locate_break_row(
|
||||
len(continuation),
|
||||
continuation_at,
|
||||
min_page=resume_page_before_cut(15),
|
||||
)
|
||||
assert (found, cut) == (13, 16)
|
||||
assert len(cont_probes) < 30
|
||||
|
||||
|
||||
def test_continuation_pass_limit():
|
||||
assert continuation_pass_limit(0, 0) == 10
|
||||
assert continuation_pass_limit(3, 0) == 13
|
||||
assert continuation_pass_limit(3, 1) == 14
|
||||
assert continuation_pass_limit(3, 5) == 18
|
||||
|
||||
|
||||
def test_orphan_header_and_caption_guards():
|
||||
"""Method guide: no title-only / title+column-headers alone at page end."""
|
||||
# Split would leave only heading row on previous page
|
||||
@@ -104,6 +169,68 @@ def test_fix_continuations_missing_file():
|
||||
assert "не найден" in r.message.lower() or "Файл" in r.message
|
||||
|
||||
|
||||
def test_word_com_apartment_pairs_init(monkeypatch):
|
||||
import types
|
||||
|
||||
calls = []
|
||||
fake = types.ModuleType("pythoncom")
|
||||
fake.CoInitialize = lambda: calls.append("init")
|
||||
fake.CoUninitialize = lambda: calls.append("uninit")
|
||||
monkeypatch.setitem(sys.modules, "pythoncom", fake)
|
||||
from md2gost.word_fix import word_com_apartment
|
||||
|
||||
with word_com_apartment():
|
||||
calls.append("body")
|
||||
assert calls == ["init", "body", "uninit"]
|
||||
|
||||
|
||||
def test_word_com_apartment_skips_uninit_when_init_fails(monkeypatch):
|
||||
import types
|
||||
|
||||
calls = []
|
||||
fake = types.ModuleType("pythoncom")
|
||||
|
||||
def boom():
|
||||
raise RuntimeError("coinit")
|
||||
|
||||
fake.CoInitialize = boom
|
||||
fake.CoUninitialize = lambda: calls.append("uninit")
|
||||
monkeypatch.setitem(sys.modules, "pythoncom", fake)
|
||||
from md2gost.word_fix import word_com_apartment
|
||||
|
||||
with pytest.raises(RuntimeError, match="coinit"):
|
||||
with word_com_apartment():
|
||||
calls.append("body")
|
||||
assert calls == []
|
||||
|
||||
|
||||
@pytest.mark.skipif(sys.platform != "win32", reason="Windows only")
|
||||
def test_second_thread_can_dispatch_com():
|
||||
"""A fresh thread must call CoInitialize itself; module import does it once."""
|
||||
try:
|
||||
import win32com.client # noqa: F401
|
||||
except ImportError:
|
||||
pytest.skip("pywin32 not installed")
|
||||
import threading
|
||||
|
||||
from md2gost.word_fix import word_com_apartment
|
||||
|
||||
errors: list[BaseException] = []
|
||||
|
||||
def work():
|
||||
try:
|
||||
with word_com_apartment():
|
||||
win32com.client.Dispatch("Scripting.Dictionary")
|
||||
except Exception as exc:
|
||||
errors.append(exc)
|
||||
|
||||
for _ in range(2):
|
||||
thread = threading.Thread(target=work)
|
||||
thread.start()
|
||||
thread.join()
|
||||
assert errors == []
|
||||
|
||||
|
||||
@pytest.mark.skipif(sys.platform != "win32", reason="Windows only")
|
||||
@pytest.mark.skipif(os.environ.get("MD2GOST_TEST_WORD") != "1", reason="set MD2GOST_TEST_WORD=1 to run Word COM smoke")
|
||||
def test_fix_continuations_com_smoke(tmp_path):
|
||||
|
||||
Reference in New Issue
Block a user