Skip to content
go-libfossil Artifact [1fc1185c73]
Login

Artifact [1fc1185c73]

Artifact 1fc1185c738c64d8f8150fd586553dd163beee06a4899cbc850bb4e996333d8f:


package libfossil

import (
	"context"
	"errors"
	"strings"
	"testing"

	"github.com/danmestas/go-libfossil/internal/blob"
	"github.com/danmestas/go-libfossil/internal/checkout"
	"github.com/danmestas/go-libfossil/internal/content"
)

// An unlocked exchange cannot silently replay a queue selected before another
// operation changed it. The local retry must reject that stale snapshot.
func TestCheckinContextRefusesChangedQueue(t *testing.T) {
	f := newRequiredPublicFixture(t, "sha3")
	co := requiredPublicCheckout(t, f)
	changed := false
	peer := TransportFunc(func(ctx context.Context, payload []byte) ([]byte, error) {
		if !changed {
			changed = true
			if err := co.inner.Enqueue(checkout.EnqueueOpts{Paths: []string{"f0000"}}); err != nil {
				return nil, err
			}
		}
		return f.source.HandleSync(ctx, payload)
	})
	_, _, err := co.CheckinContext(context.Background(), CheckoutCommitOpts{
		User: "test", AllowEmpty: true,
	}, RequiredContentOpts{Peer: peer})
	if !changed || err == nil || !strings.Contains(err.Error(), "snapshot") {
		t.Fatalf("changed queue was not rejected: changed=%v err=%v", changed, err)
	}
}

// Lasting coverage must affect a later ordinary pull of a newly-created path,
// not just the exact payloads fetched while editing the worktree cone.
func TestSetConeContextFuturePull(t *testing.T) {
	f := newRequiredPublicFixture(t, "sha3")
	co := requiredPublicCheckout(t, f)
	next, err := co.Cone().With([]string{"future"}, nil)
	if err != nil {
		t.Fatal(err)
	}
	if _, err := co.SetConeContext(context.Background(), next,
		RequiredContentOpts{Peer: f.peer}); err != nil {
		t.Fatal(err)
	}
	var parentUUID string
	if err := f.target.inner.DB().QueryRow("SELECT uuid FROM blob WHERE rid=?", f.parent).
		Scan(&parentUUID); err != nil {
		t.Fatal(err)
	}
	parent, held := blob.Exists(f.source.inner.DB(), parentUUID)
	if !held {
		t.Fatal("source parent disappeared")
	}
	_, _, err = f.source.Commit(CommitOpts{ParentID: int64(parent), User: "test",
		Files: []FileToCommit{{Name: "future/new", Content: []byte("future payload")}}})
	if err != nil {
		t.Fatal(err)
	}
	if _, err := f.target.Sync(context.Background(), f.peer, SyncOpts{Pull: true}); err != nil {
		t.Fatal(err)
	}
	_, uuid, err := blob.Store(f.source.inner.DB(), []byte("future payload"))
	if err != nil {
		t.Fatal(err)
	}
	if _, held := content.AvailableByUUID(f.target.inner.DB(), uuid); !held {
		t.Fatal("ordinary pull did not fetch a new file in widened coverage")
	}
	stored, err := f.target.StoreCone()
	if err != nil || stored == nil || !stored.Covers("future/another") || !stored.Covers("f0000") {
		t.Fatalf("lasting shared-store coverage: %v, %v", stored, err)
	}
	for _, name := range f.outside {
		if _, held := content.AvailableByUUID(f.target.inner.DB(), name); held {
			t.Fatal("widening fetched unrelated outside payload")
		}
	}
}

// A failed fetch retains explicit widening intent, as the pinned reference does,
// while no-peer refusal does not silently change policy or the worktree.
func TestSetConeContextFailedFetchPolicy(t *testing.T) {
	for _, hasPeer := range []bool{false, true} {
		f := newRequiredPublicFixture(t, "sha3")
		co := requiredPublicCheckout(t, f)
		before, err := f.target.StoreCone()
		if err != nil {
			t.Fatal(err)
		}
		var required RequiredContentOpts
		if hasPeer {
			required.Peer = TransportFunc(func(context.Context, []byte) ([]byte, error) {
				return nil, errors.New("injected fetch refusal")
			})
		}
		if _, err := co.SetConeContext(context.Background(), nil, required); err == nil {
			t.Fatal("widening unexpectedly succeeded")
		}
		after, err := f.target.StoreCone()
		if err != nil {
			t.Fatal(err)
		}
		if hasPeer {
			if after != nil {
				t.Fatal("failed fetch discarded explicit dense store intent")
			}
		} else if after == nil || after.Digest() != before.Digest() {
			t.Fatal("no-peer refusal changed store policy")
		}
		if co.Cone() == nil {
			t.Fatal("failed fetch changed worktree coverage")
		}
	}
}

// A widening combines coverage, not raw members: one checkout's exclusions
// must never take coverage away from another checkout sharing its store.
func TestRequiredStoreUnion(t *testing.T) {
	left, err := NewCone([]string{"a", "a/cut/back", "left"}, []string{"a/cut"})
	if err != nil {
		t.Fatal(err)
	}
	right, err := NewCone([]string{"a/cut", "right"}, []string{"a/cut/hole"})
	if err != nil {
		t.Fatal(err)
	}
	got, err := requiredStoreUnion(left, right)
	if err != nil {
		t.Fatal(err)
	}
	for name, covered := range map[string]bool{
		"a/file": true, "a/cut/file": true, "a/cut/hole/file": false,
		"a/cut/back/file": true, "left/new": true, "right/new": true, "other": false,
	} {
		if got.Covers(name) != covered {
			t.Fatalf("union coverage of %s: got %v, want %v", name, got.Covers(name), covered)
		}
	}
	for _, pair := range [][2]*Cone{{left, nil}, {nil, right}, {nil, nil}} {
		dense, err := requiredStoreUnion(pair[0], pair[1])
		if err != nil || dense != nil {
			t.Fatalf("union with dense: %v, %v", dense, err)
		}
	}
}