- pdftext/outline: resolve named destinations through the catalog /Dests and the /Names /Dests tree, resolve indirect /Dest, /A and /Title, and treat reader.Null (what Dict.Get returns for a missing key) as absent. Before this every LaTeX and Acrobat PDF had an empty outline. pdftest gains NamedDests; TestOutlineNamedDestinations. - api: pool() always goes through its Once (data race on concurrent first use); TestDefaultPoolConcurrentFirstUse. Revalidation of a stale cache entry counts against the caller/host limit. - pdftext/runner: output over MaxText kills the child at once instead of waiting for the deadline; TestRunnerTooMuchTextFailsFast. - fetch: Accept includes application/pdf;q=0.8. Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
61 lines
2.1 KiB
Go
61 lines
2.1 KiB
Go
package pdftext_test
|
|
|
|
// Owner review (P5a): output over MaxText fails fast, not at the deadline. The child writes more
|
|
// than a pipe buffer (64 KiB), so it blocks on stdout until killed; Run must not wait for it to
|
|
// exit on its own before checking the size.
|
|
|
|
import (
|
|
"bytes"
|
|
"context"
|
|
"encoding/json"
|
|
"errors"
|
|
"testing"
|
|
"time"
|
|
|
|
"git.wntrmute.dev/kyle/sift/internal/pdftest"
|
|
"git.wntrmute.dev/kyle/sift/internal/pdftext"
|
|
)
|
|
|
|
func TestRunnerTooMuchTextFailsFast(t *testing.T) {
|
|
var pages []string
|
|
for p := 1; p <= 80; p++ {
|
|
pages = append(pages, pdftest.Lorem(p, 40)) // ~2,700 characters a page, ~200 KiB in all
|
|
}
|
|
r := runner()
|
|
r.MaxText = 1000
|
|
r.Deadline = 30 * time.Second
|
|
start := time.Now()
|
|
_, err := r.Run(context.Background(), pdftest.Build(pdftest.Doc{Pages: pages}))
|
|
if !errors.Is(err, pdftext.ErrTooMuchText) {
|
|
t.Fatalf("err %v, want ErrTooMuchText", err)
|
|
}
|
|
if d := time.Since(start); d > 5*time.Second {
|
|
t.Errorf("ErrTooMuchText after %v; it waited for the deadline instead of killing the child", d)
|
|
}
|
|
}
|
|
|
|
// Owner review (P5a): LaTeX (hyperref) and Acrobat outlines use named destinations through
|
|
// the /Names tree and indirect titles; the first run returned no outline for any arXiv paper
|
|
// or the NIST PDF. reader's Dict.Get answers reader.Null{} for a missing key, which hid /A.
|
|
func TestOutlineNamedDestinations(t *testing.T) {
|
|
b := pdftest.Build(pdftest.Doc{
|
|
Title: "Named",
|
|
Pages: []string{"one", "two", "three"},
|
|
Outline: []pdftest.Outline{{Title: "1 Intro", Page: 1, Level: 1}, {Title: "2 Method", Page: 3, Level: 1}},
|
|
NamedDests: true,
|
|
})
|
|
var out bytes.Buffer
|
|
if err := pdftext.Extract(b, 10, &out); err != nil {
|
|
t.Fatal(err)
|
|
}
|
|
var l struct{ Meta pdftext.Meta }
|
|
line, _ := out.ReadBytes('\n')
|
|
if err := json.Unmarshal(line, &l); err != nil {
|
|
t.Fatal(err)
|
|
}
|
|
want := []pdftext.OutlineItem{{Title: "1 Intro", Page: 1, Level: 1}, {Title: "2 Method", Page: 3, Level: 1}}
|
|
if len(l.Meta.Outline) != 2 || l.Meta.Outline[0] != want[0] || l.Meta.Outline[1] != want[1] {
|
|
t.Errorf("outline = %+v, want %+v", l.Meta.Outline, want)
|
|
}
|
|
}
|