Test lazy-fetch fires before materialized fallback · Entire
Test lazy-fetch fires before materialized fallback
9e08903→main·Soph·2mo ago·1 file·+103 added/-0 removed
Adds TestRunSyncLazyFetchOnRelayRejection covering the needsLocalSourceClosure=false path through the materialized fallback. Triggering it from the network is essentially impossible (parsed advertisements always have non-nil capabilities, so RelayTargetPolicy.CapabilitiesKnown is always true in practice), so the test constructs a syncSession via newSession and synthesizes the unreachable scenario by setting target.policy.CapabilitiesKnown=false directly. Source has a new commit on a new branch that target genuinely doesn't have; the test asserts that commit object lands on target, which is only possible if fetchClosure ran before executeMaterialized.
Confirmed to fail when the lazy-fetch logic is reverted.
Co-Authored-By: Claude Opus 4.7 (1M context) noreply@anthropic.com
Sessions
cc7986f5b900View transcript
Changes
1
internal/syncer
Mintegration_test.go+103
329 unmodified lines
// TestRunSyncLazyFetchOnRelayRejection forces the rare case where
// needsLocalSourceClosure returns false (skip + create plans only) but
// CanIncrementalRelay still rejects, so the materialized fallback is
// reached without an upfront fetch. Triggering it from the network path
// is essentially impossible — packp.AdvRefs always parses with non-nil
// Capabilities, so RelayTargetPolicy.CapabilitiesKnown is always true in
// practice — but we synthesize the policy directly by reaching into a
// constructed syncSession. Without the lazy fetch, materialized runs
// against an empty store and silently produces a pack that's missing
// the new commit's objects; the target's receive-pack then rejects the
// push with "missing necessary objects". This test exercises that path
// and asserts the push still succeeds — only possible if fetchClosure
// ran before executeMaterialized.
func TestRunSyncLazyFetchOnRelayRejection(t *testing.T) {
sourceRepo, sourceFS := newSourceRepo(t)
makeCommits(t, sourceRepo, sourceFS, 3)
// Snapshot the baseline target state before source advances.
targetRepo, err := git.Init(memory.NewStorage())
if err != nil {
t.Fatalf("init target repo: %v", err)
}
if err := copyRefsAndObjects(sourceRepo.Storer, targetRepo.Storer, []plumbing.ReferenceName{plumbing.NewBranchReferenceName(testBranch)}); err != nil {
t.Fatalf("copy target baseline: %v", err)
}
// Add a new branch at a NEW commit on source — target genuinely
// doesn't have these objects, so the push will only succeed if the
// materialized fallback fetched the closure first.
makeCommits(t, sourceRepo, sourceFS, 1)
releaseHead, err := sourceRepo.Reference(plumbing.NewBranchReferenceName(testBranch), true)
if err != nil {
t.Fatalf("resolve source head: %v", err)
}
releaseRef := plumbing.NewBranchReferenceName("release")
if err := sourceRepo.Storer.SetReference(plumbing.NewHashReference(releaseRef, releaseHead.Hash())); err != nil {
t.Fatalf("set source release branch: %v", err)
}
sourceServer := newSmartHTTPRepoServerV2(t, sourceRepo)
targetServer := newSmartHTTPRepoServer(t, targetRepo)
targetServer.receivePackThinCap = true
defer sourceServer.Close()
defer targetServer.Close()
cfg := Config{
Source: Endpoint{URL: sourceServer.RepoURL()},
Target: Endpoint{URL: targetServer.RepoURL()},
ProtocolMode: protocolModeAuto,
}
sess, err := newSession(context.Background(), cfg, true)
if err != nil {
t.Fatalf("newSession: %v", err)
}
// Force the materialized fallback by claiming the target's
// capabilities are unknown to the planner. The plans drive
// needsLocalSourceClosure to false (master is divergent — wait,
// makeCommits on source advanced master, so master IS divergent).
// Reset master on source back to the baseline so it's a no-op skip.
baselineHead, err := targetRepo.Reference(plumbing.NewBranchReferenceName(testBranch), true)
if err != nil {
t.Fatalf("resolve target master: %v", err)
}
if err := sourceRepo.Storer.SetReference(plumbing.NewHashReference(plumbing.NewBranchReferenceName(testBranch), baselineHead.Hash())); err != nil {
t.Fatalf("reset source master: %v", err)
}
// Re-do session creation now that source ref state is final.
sess, err = newSession(context.Background(), cfg, true)
if err != nil {
t.Fatalf("newSession (retry): %v", err)
}
sess.target.policy.CapabilitiesKnown = false
result, err := sess.runSync(context.Background())
if err != nil {
t.Fatalf("runSync: %v", err)
}
if result.Relay {
t.Fatalf("expected materialized fallback (relay rejected by synthetic policy), got relay mode=%q", result.RelayMode)
}
if result.Pushed != 1 {
t.Fatalf("expected 1 ref pushed via materialized fallback, got %d (lazy fetch likely did not fire)", result.Pushed)
}
gotRelease, err := targetRepo.Reference(releaseRef, true)
if err != nil {
t.Fatalf("resolve target release ref: %v", err)
}
if gotRelease.Hash() != releaseHead.Hash() {
t.Fatalf("target release hash = %s, want %s", gotRelease.Hash(), releaseHead.Hash())
}
// The ref-set assertion above can pass even when the materialized
// push delivered an empty pack — the storer happily records refs
// pointing at missing objects. The lazy fetch is what guarantees the
// commit and its closure actually land on target.
if _, err := targetRepo.CommitObject(releaseHead.Hash()); err != nil {
t.Fatalf("target missing release commit object %s: %v (lazy fetch likely did not fire before materialized)", releaseHead.Hash(), err)
}
}
// TestRun_IntegrationIncrementalRelayCreatesNewBranchOnNoThinTarget covers the
// no-thin variant of the above: the relayed pack is always self-contained
// (gitproto.FetchPack never sets thin-pack), so a no-thin receive-pack