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

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