test(pdf): two bookmarks on one line, and nothing counts the one that is lost
RED. Measured on R761: 2 763 /Outlines nodes entered the bridge, 2 762 marks came out, and `unresolved` was 0 -- so one node left no trace in the return value at all. The lost one is `SVV - Forside`, sharing line 0 with the tree's own root node `R761 Prosesskoden`. The cause is structural, not an edge value: the marks are collected in a dict keyed on the destination line index, so a second bookmark on a line is discarded by `setdefault` in silence. One node today; a document with several bookmarks per line loses several and reports none of them. `outline-collision.pdf` is the fixture no existing one could stand in for -- every other outline fixture puts one bookmark on one line, which is exactly the case where the defect cannot appear. It is laid out by hand through `make_fixtures.py` like the other PDF fixtures, and regenerating that file left every other fixture byte-identical. The known-negative is in the same commit: `outline-collided.pdf` aside, the two existing trees must report `collided == 0`, so the counter can be measured at zero and is not green for an unstated reason. pytest -q: 2 failed, 1554 passed, 1 skipped. Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
This commit is contained in:
parent
e1f4faa098
commit
3f8f03cff5
4 changed files with 106 additions and 0 deletions
|
|
@ -38,6 +38,7 @@ pdfplumber = pytest.importorskip("pdfplumber")
|
|||
FIXTURES = Path(__file__).parent / "fixtures"
|
||||
OUTLINED = FIXTURES / "outlined-krav.pdf"
|
||||
BROKEN = FIXTURES / "outline-broken-dest.pdf"
|
||||
COLLISION = FIXTURES / "outline-collision.pdf"
|
||||
NO_OUTLINE = FIXTURES / "three-page-krav.pdf"
|
||||
|
||||
|
||||
|
|
@ -89,6 +90,43 @@ def test_an_unresolvable_destination_is_dropped_and_counted() -> None:
|
|||
assert outline.unresolved == 1
|
||||
|
||||
|
||||
def test_two_bookmarks_on_one_line_are_counted_not_lost() -> None:
|
||||
"""The node that shares a line is DROPPED on purpose -- and never silently.
|
||||
|
||||
Measured on R761: 2 763 nodes entered the bridge, 2 762 marks came out and
|
||||
`unresolved` was 0, so one node left no trace anywhere. The line it shared
|
||||
was line 0, between the tree's own root node and `SVV - Forside`.
|
||||
|
||||
Keeping BOTH was measured on that same document and felled: the two
|
||||
candidates then open at the same offset, and the first closes with an
|
||||
EMPTY span (0, 0) that the orphan check deletes without a word -- the same
|
||||
node lost, one step later, plus a front-matter title that is in no fasit.
|
||||
So the rule is first-in-tree-order wins, and the loser is COUNTED.
|
||||
"""
|
||||
data = COLLISION.read_bytes()
|
||||
outline = extract.pdf_outline(COLLISION.name, data)
|
||||
|
||||
assert [(mark.line, mark.level, mark.title) for mark in outline.marks] == [
|
||||
(0, 1, "R761 Prosesskoden"),
|
||||
]
|
||||
assert outline.unresolved == 0
|
||||
assert outline.collided == 1
|
||||
# The identity is the point, not the counter: every node the tree declared
|
||||
# is a mark, an unresolved destination, or a collision, and nothing else.
|
||||
assert len(outline.marks) + outline.unresolved + outline.collided == 2
|
||||
|
||||
|
||||
def test_a_bookmark_tree_with_no_collision_counts_none() -> None:
|
||||
"""The known-negative on the same field: the counter must be able to be 0."""
|
||||
outline = extract.pdf_outline(OUTLINED.name, OUTLINED.read_bytes())
|
||||
assert outline.collided == 0
|
||||
assert len(outline.marks) + outline.unresolved + outline.collided == 4
|
||||
|
||||
broken = extract.pdf_outline(BROKEN.name, BROKEN.read_bytes())
|
||||
assert broken.collided == 0
|
||||
assert len(broken.marks) + broken.unresolved + broken.collided == 2
|
||||
|
||||
|
||||
def test_the_outline_replaces_the_text_heuristics_when_it_is_given() -> None:
|
||||
data = OUTLINED.read_bytes()
|
||||
text = extract.extract_text(OUTLINED.name, data)
|
||||
|
|
|
|||
Loading…
Add table
Add a link
Reference in a new issue