Files
kage/clone/dedup_test.go
Tam Nguyen Duc 7dc3621ced Count real pages and store identical pages once (#20)
Two related changes for crawling faceted sites, where one path spawns
thousands of ?q=.../?page=... URLs that all render the same page.

The progress line was counting every written file, so the page number ran
far ahead of the site's real size. It now shows distinct URL paths as
"pages" and folds the query-string permutations into a separate "variants"
count, so the live counter tracks real pages and is easy to read.

Pages with identical bytes are now stored once. The first page with a given
content is written normally; a later page with the same bytes becomes a hard
link to it, so duplicate content never takes disk twice. Links still resolve
because each variant keeps its own path. When hard links are unsupported the
bytes are written, so correctness never depends on the link. The summary
reports how many pages were deduped.
2026-06-15 19:07:49 +07:00

70 lines
2.1 KiB
Go

package clone
import (
"os"
"path/filepath"
"testing"
)
// TestWritePageDedup checks that identical page bytes are stored once and shared
// by a hard link, while different bytes are written as their own file.
func TestWritePageDedup(t *testing.T) {
dir := t.TempDir()
c := &Cloner{outRoot: dir, seenContent: map[string]string{}}
body := []byte("<html><body>same page</body></html>")
if deduped, err := c.writePage("a/index.html", body); err != nil || deduped {
t.Fatalf("first write: deduped=%v err=%v, want false/nil", deduped, err)
}
if deduped, err := c.writePage("b/index.html", body); err != nil || !deduped {
t.Fatalf("second identical write: deduped=%v err=%v, want true/nil", deduped, err)
}
if deduped, err := c.writePage("c/index.html", []byte("<html>other</html>")); err != nil || deduped {
t.Fatalf("third different write: deduped=%v err=%v, want false/nil", deduped, err)
}
// The two identical pages must be the same file on disk (one inode).
fa, err := os.Stat(filepath.Join(dir, "a/index.html"))
if err != nil {
t.Fatal(err)
}
fb, err := os.Stat(filepath.Join(dir, "b/index.html"))
if err != nil {
t.Fatal(err)
}
if !os.SameFile(fa, fb) {
t.Error("identical pages were not hard-linked to the same file")
}
// The different page must stand alone with its own bytes.
got, err := os.ReadFile(filepath.Join(dir, "c/index.html"))
if err != nil {
t.Fatal(err)
}
if string(got) != "<html>other</html>" {
t.Errorf("third page content = %q", got)
}
}
// TestRecordPageCounts checks that pages counts every write, pagePaths counts
// distinct query-stripped paths, and pagesLinked counts deduped writes.
func TestRecordPageCounts(t *testing.T) {
var s stats
s.recordPage("showcase/index.html", false)
s.recordPage("showcase/index.html", true) // a ?q= variant of the same path
s.recordPage("showcase/index.html", true)
s.recordPage("about/index.html", false)
p := s.snapshot()
if p.Pages != 4 {
t.Errorf("Pages = %d, want 4", p.Pages)
}
if p.PagePaths != 2 {
t.Errorf("PagePaths = %d, want 2", p.PagePaths)
}
if p.PagesLinked != 2 {
t.Errorf("PagesLinked = %d, want 2", p.PagesLinked)
}
}