Hash files under 10 MiB in full, gate larger files on head/tail
check / check (push) Successful in 59s
check / check (push) Successful in 59s
Amends the issue 61 ladder per the owner's design change. A file under 10 MiB (new headTailMin) is now hashed in full and compared directly, with no end-window step: its head, tail, and content all carry the whole-file SHA-256, so its signature is decided by size and content alone. A file at 10 MiB or above keeps the 64 KiB head/tail gate, then the whole-file content hash below 50 MiB or gigabyte-spaced 1 MiB samples at or above. hashEnds drops its now-unreachable single-window branch, and hashWhole errors if the file shrank below its recorded size (the only read for the sub-10-MiB range). README, TODO, tests, and the schema-version note updated to match. Model: opus-4-8
This commit is contained in:
+88
-30
@@ -53,10 +53,23 @@ func pattern(tag byte, n int) []byte {
|
||||
return data
|
||||
}
|
||||
|
||||
// TestHashSignatureEnds exercises the head and tail rungs across the
|
||||
// window boundaries. Every file here is below wholeFileMax, so the
|
||||
// content rung is a whole-file hash.
|
||||
func TestHashSignatureEnds(t *testing.T) {
|
||||
// sig returns a file's full signature (head, tail, content), failing the
|
||||
// test on any error.
|
||||
func sig(t *testing.T, path string, size int64) (string, string, string) {
|
||||
t.Helper()
|
||||
|
||||
head, tail, content, err := hashSignature(path, size)
|
||||
if err != nil {
|
||||
t.Fatalf("hashSignature %s: %v", path, err)
|
||||
}
|
||||
|
||||
return head, tail, content
|
||||
}
|
||||
|
||||
// TestHashSignatureBelowThreshold verifies that a file below headTailMin
|
||||
// is hashed in full and compared directly: head, tail, and content all
|
||||
// carry the whole-file SHA-256, with no separate end-window step.
|
||||
func TestHashSignatureBelowThreshold(t *testing.T) {
|
||||
t.Parallel()
|
||||
|
||||
dir := t.TempDir()
|
||||
@@ -65,13 +78,10 @@ func TestHashSignatureEnds(t *testing.T) {
|
||||
name string
|
||||
data []byte
|
||||
}{
|
||||
{"empty", nil},
|
||||
{"one-byte", []byte("x")},
|
||||
{"under-one-window", pattern(1, headTailWindow-1)},
|
||||
{"exactly-one-window", pattern(2, headTailWindow)},
|
||||
{"overlapping-windows", pattern(3, headTailWindow+headTailWindow/2)},
|
||||
{"exactly-two-windows", pattern(4, 2*headTailWindow)},
|
||||
{"beyond-two-windows", pattern(5, 3*headTailWindow)},
|
||||
{"one-window", pattern(1, headTailWindow)},
|
||||
{"several-windows", pattern(2, 3*headTailWindow)},
|
||||
{"near-threshold", pattern(3, headTailMin-1)},
|
||||
}
|
||||
for _, c := range cases {
|
||||
t.Run(c.name, func(t *testing.T) {
|
||||
@@ -79,28 +89,79 @@ func TestHashSignatureEnds(t *testing.T) {
|
||||
|
||||
p := writeFile(t, dir, c.name, c.data)
|
||||
|
||||
head, tail, content, err := hashSignature(p, int64(len(c.data)))
|
||||
if err != nil {
|
||||
t.Fatalf("hashSignature: %v", err)
|
||||
}
|
||||
head, tail, content := sig(t, p, int64(len(c.data)))
|
||||
|
||||
n := min(headTailWindow, len(c.data))
|
||||
if want := hexSum(c.data[:n]); head != want {
|
||||
t.Errorf("head = %s, want %s", head, want)
|
||||
}
|
||||
|
||||
if want := hexSum(c.data[len(c.data)-n:]); tail != want {
|
||||
t.Errorf("tail = %s, want %s", tail, want)
|
||||
}
|
||||
|
||||
// Below wholeFileMax the content rung hashes the whole file.
|
||||
if want := hexSum(c.data); content != want {
|
||||
t.Errorf("content = %s, want whole-file %s", content, want)
|
||||
whole := hexSum(c.data)
|
||||
if head != whole || tail != whole || content != whole {
|
||||
t.Errorf("head=%s tail=%s content=%s, want all whole-file %s",
|
||||
head, tail, content, whole)
|
||||
}
|
||||
})
|
||||
}
|
||||
}
|
||||
|
||||
// TestHashSignatureEnds exercises the head and tail rungs, which apply
|
||||
// only to files at least headTailMin. Sparse files keep the fixtures
|
||||
// cheap: a difference in the first window changes only head, a
|
||||
// difference in the last window changes only tail, and a difference
|
||||
// between the windows changes neither end hash but does change the
|
||||
// whole-file content rung (the file is below wholeFileMax).
|
||||
func TestHashSignatureEnds(t *testing.T) {
|
||||
t.Parallel()
|
||||
|
||||
dir := t.TempDir()
|
||||
|
||||
// Between headTailMin and wholeFileMax: the end-window gate is active
|
||||
// and the content rung is a whole-file hash.
|
||||
const size = int64(headTailMin + 2*1024*1024)
|
||||
|
||||
base := sparseFile(t, dir, "ends-base", size)
|
||||
headDiff := sparseFile(t, dir, "ends-head", size)
|
||||
tailDiff := sparseFile(t, dir, "ends-tail", size)
|
||||
midDiff := sparseFile(t, dir, "ends-mid", size)
|
||||
|
||||
pokeAt(t, headDiff, 0, []byte{1})
|
||||
pokeAt(t, tailDiff, size-1, []byte{1})
|
||||
pokeAt(t, midDiff, size/2, []byte{1})
|
||||
|
||||
bHead, bTail, bContent := sig(t, base, size)
|
||||
|
||||
h, tl, c := sig(t, headDiff, size)
|
||||
if h == bHead {
|
||||
t.Error("a byte in the first window did not change head")
|
||||
}
|
||||
|
||||
if tl != bTail {
|
||||
t.Error("a byte in the first window changed tail")
|
||||
}
|
||||
|
||||
if c == bContent {
|
||||
t.Error("a byte in the first window did not change content")
|
||||
}
|
||||
|
||||
h, tl, c = sig(t, tailDiff, size)
|
||||
if tl == bTail {
|
||||
t.Error("a byte in the last window did not change tail")
|
||||
}
|
||||
|
||||
if h != bHead {
|
||||
t.Error("a byte in the last window changed head")
|
||||
}
|
||||
|
||||
if c == bContent {
|
||||
t.Error("a byte in the last window did not change content")
|
||||
}
|
||||
|
||||
h, tl, c = sig(t, midDiff, size)
|
||||
if h != bHead || tl != bTail {
|
||||
t.Error("a byte between the windows changed an end hash")
|
||||
}
|
||||
|
||||
if c == bContent {
|
||||
t.Error("whole-file content rung ignored a byte between the windows")
|
||||
}
|
||||
}
|
||||
|
||||
func TestHashSignatureErrors(t *testing.T) {
|
||||
t.Parallel()
|
||||
|
||||
@@ -189,10 +250,7 @@ func pokeAt(t *testing.T, path string, off int64, data []byte) {
|
||||
func contentHash(t *testing.T, path string, size int64) string {
|
||||
t.Helper()
|
||||
|
||||
_, _, content, err := hashSignature(path, size)
|
||||
if err != nil {
|
||||
t.Fatalf("hashSignature %s: %v", path, err)
|
||||
}
|
||||
_, _, content := sig(t, path, size)
|
||||
|
||||
return content
|
||||
}
|
||||
|
||||
Reference in New Issue
Block a user