f99010fae1
Desktop Artifacts / Desktop Build (Linux) (push) Waiting to run
Desktop Artifacts / Desktop Build (Windows) (push) Waiting to run
Desktop Artifacts / Desktop Build (Linux (arm64)) (push) Waiting to run
Desktop Artifacts (macOS) / Desktop Build (macOS (aarch64)) (push) Waiting to run
Desktop Artifacts (macOS) / Desktop Build (macOS (x86_64)) (push) Waiting to run
CI / lint (push) Failing after 1s
CI / frontend (push) Failing after 1s
CI / scripts (push) Failing after 1s
CI / Go Test (ubuntu-latest) (push) Failing after 0s
CI / frontend-node-25 (push) Failing after 1s
CI / docs (push) Failing after 0s
CI / coverage (push) Failing after 0s
CI / e2e (push) Failing after 0s
Docker / build-and-push (push) Failing after 1s
CI / integration (push) Failing after 4m43s
CI / Go Test (windows-latest) (push) Has been cancelled
CI / Desktop Unit Tests (Windows) (push) Has been cancelled
185 lines
5.5 KiB
Go
185 lines
5.5 KiB
Go
package sync
|
|
|
|
import (
|
|
"runtime"
|
|
"strconv"
|
|
"testing"
|
|
|
|
"github.com/stretchr/testify/assert"
|
|
"github.com/stretchr/testify/require"
|
|
)
|
|
|
|
func verifiedSourceSignatureForTest(seed int64) verifiedSourceSignature {
|
|
return verifiedSourceSignature{
|
|
size: seed + 10, mtime: seed + 20,
|
|
inode: seed + 30, device: seed + 40,
|
|
changeTime: seed + 50, sidecarSize: seed + 60,
|
|
sidecarMtime: seed + 70, sidecarInode: seed + 80,
|
|
sidecarDevice: seed + 90, sidecarChangeTime: seed + 100,
|
|
}
|
|
}
|
|
|
|
func TestVerifiedSourceGatePromotionAndInvalidation(t *testing.T) {
|
|
const path = "archive/session-a.jsonl"
|
|
signature := verifiedSourceSignatureForTest(1)
|
|
|
|
t.Run("promotion trusts the captured signature", func(t *testing.T) {
|
|
e := &Engine{}
|
|
capture, fresh := e.captureVerifiedSource(path, signature)
|
|
assert.False(t, fresh)
|
|
e.promoteVerifiedSource(capture)
|
|
|
|
_, fresh = e.captureVerifiedSource(path, signature)
|
|
assert.True(t, fresh)
|
|
_, fresh = e.captureVerifiedSource(
|
|
path, verifiedSourceSignatureForTest(2),
|
|
)
|
|
assert.False(t, fresh, "a changed signature must re-verify")
|
|
})
|
|
|
|
t.Run("path invalidation vetoes stale promotion", func(t *testing.T) {
|
|
e := &Engine{}
|
|
capture, _ := e.captureVerifiedSource(path, signature)
|
|
e.invalidateVerifiedSource(path)
|
|
e.promoteVerifiedSource(capture)
|
|
|
|
_, fresh := e.captureVerifiedSource(path, signature)
|
|
assert.False(t, fresh)
|
|
})
|
|
|
|
t.Run("global clear vetoes stale promotion", func(t *testing.T) {
|
|
e := &Engine{}
|
|
capture, _ := e.captureVerifiedSource(path, signature)
|
|
e.clearVerifiedSources()
|
|
e.promoteVerifiedSource(capture)
|
|
|
|
_, fresh := e.captureVerifiedSource(path, signature)
|
|
assert.False(t, fresh)
|
|
})
|
|
|
|
t.Run("unrelated invalidation does not veto promotion", func(t *testing.T) {
|
|
e := &Engine{}
|
|
capture, _ := e.captureVerifiedSource(path, signature)
|
|
e.invalidateVerifiedSource("archive/session-b.jsonl")
|
|
e.promoteVerifiedSource(capture)
|
|
|
|
_, fresh := e.captureVerifiedSource(path, signature)
|
|
assert.True(t, fresh)
|
|
})
|
|
}
|
|
|
|
func TestVerifiedSourceGateFullPassPruning(t *testing.T) {
|
|
e := &Engine{}
|
|
keepSignature := verifiedSourceSignatureForTest(1)
|
|
dropSignature := verifiedSourceSignatureForTest(2)
|
|
|
|
firstPass := e.beginVerifiedSourcePass()
|
|
keepCapture, keepFresh := e.captureVerifiedSource(
|
|
"archive/keep.jsonl", keepSignature,
|
|
)
|
|
dropCapture, dropFresh := e.captureVerifiedSource(
|
|
"archive/drop.jsonl", dropSignature,
|
|
)
|
|
require.False(t, keepFresh)
|
|
require.False(t, dropFresh)
|
|
e.promoteVerifiedSource(keepCapture)
|
|
e.promoteVerifiedSource(dropCapture)
|
|
e.finishVerifiedSourcePass(firstPass, true)
|
|
require.Len(t, e.verifiedSources, 2)
|
|
|
|
secondPass := e.beginVerifiedSourcePass()
|
|
_, keepFresh = e.captureVerifiedSource("archive/keep.jsonl", keepSignature)
|
|
require.True(t, keepFresh)
|
|
e.finishVerifiedSourcePass(secondPass, true)
|
|
|
|
require.Len(t, e.verifiedSources, 1)
|
|
_, ok := e.verifiedSources["archive/keep.jsonl"]
|
|
assert.True(t, ok)
|
|
|
|
incompletePass := e.beginVerifiedSourcePass()
|
|
e.finishVerifiedSourcePass(incompletePass, false)
|
|
assert.Len(t, e.verifiedSources, 1,
|
|
"an incomplete pass must not prune trusted sources")
|
|
}
|
|
|
|
func TestVerifiedSourceGateInvalidationSurvivesActivePassPruning(t *testing.T) {
|
|
const path = "archive/session.jsonl"
|
|
e := &Engine{}
|
|
signature := verifiedSourceSignatureForTest(1)
|
|
|
|
pass := e.beginVerifiedSourcePass()
|
|
capture, _ := e.captureVerifiedSource(path, signature)
|
|
e.invalidateVerifiedSource(path)
|
|
e.finishVerifiedSourcePass(pass, true)
|
|
|
|
require.Len(t, e.verifiedSources, 1,
|
|
"the invalidation record must survive its active pass")
|
|
e.promoteVerifiedSource(capture)
|
|
_, fresh := e.captureVerifiedSource(path, signature)
|
|
assert.False(t, fresh,
|
|
"pruning must not erase the generation that vetoes stale promotion")
|
|
}
|
|
|
|
func TestVerifiedSourceGateRetainedBudget(t *testing.T) {
|
|
const (
|
|
smallCount = 100
|
|
largeCount = 47_000
|
|
budget = 16 << 20
|
|
)
|
|
|
|
retainedBytes := func(count int) uint64 {
|
|
t.Helper()
|
|
runtime.GC()
|
|
var before runtime.MemStats
|
|
runtime.ReadMemStats(&before)
|
|
|
|
e := &Engine{}
|
|
pass := e.beginVerifiedSourcePass()
|
|
for i := range count {
|
|
path := "archive/provider/session-" + strconv.Itoa(i) + ".jsonl"
|
|
capture, _ := e.captureVerifiedSource(
|
|
path, verifiedSourceSignatureForTest(int64(i)),
|
|
)
|
|
e.promoteVerifiedSource(capture)
|
|
}
|
|
e.finishVerifiedSourcePass(pass, true)
|
|
require.Len(t, e.verifiedSources, count)
|
|
|
|
runtime.GC()
|
|
var after runtime.MemStats
|
|
runtime.ReadMemStats(&after)
|
|
runtime.KeepAlive(e)
|
|
if after.HeapAlloc <= before.HeapAlloc {
|
|
return 0
|
|
}
|
|
return after.HeapAlloc - before.HeapAlloc
|
|
}
|
|
|
|
smallBytes := retainedBytes(smallCount)
|
|
largeBytes := retainedBytes(largeCount)
|
|
t.Logf("verified-source retained bytes: %d entries=%d, %d entries=%d",
|
|
smallCount, smallBytes, largeCount, largeBytes)
|
|
assert.Greater(t, largeBytes, smallBytes)
|
|
assert.Less(t, largeBytes, uint64(budget),
|
|
"47k source records exceed the retained-memory budget")
|
|
|
|
e := &Engine{}
|
|
firstPass := e.beginVerifiedSourcePass()
|
|
for i := range largeCount {
|
|
path := "archive/provider/session-" + strconv.Itoa(i) + ".jsonl"
|
|
capture, _ := e.captureVerifiedSource(
|
|
path, verifiedSourceSignatureForTest(int64(i)),
|
|
)
|
|
e.promoteVerifiedSource(capture)
|
|
}
|
|
e.finishVerifiedSourcePass(firstPass, true)
|
|
secondPass := e.beginVerifiedSourcePass()
|
|
for i := range smallCount {
|
|
path := "archive/provider/session-" + strconv.Itoa(i) + ".jsonl"
|
|
e.captureVerifiedSource(path, verifiedSourceSignatureForTest(int64(i)))
|
|
}
|
|
e.finishVerifiedSourcePass(secondPass, true)
|
|
assert.Len(t, e.verifiedSources, smallCount,
|
|
"pruning must return retained state to the active set")
|
|
}
|