Skip to content
go-libfossil Artifact [94a538d45e]
Login

Artifact [94a538d45e]

Artifact 94a538d45ef092b8cd0be41cd953bfb9ba3bfd7c58c8fdf93c7b5790a22f5ce9:


package manifest

import (
	"errors"
	"testing"
	"time"

	"github.com/danmestas/go-libfossil/db"
	"github.com/danmestas/go-libfossil/internal/blob"
	"github.com/danmestas/go-libfossil/internal/cone"
	"github.com/danmestas/go-libfossil/internal/content"
	"github.com/danmestas/go-libfossil/internal/deck"
	libfossil "github.com/danmestas/go-libfossil/internal/fsltype"
)

func partialMetadataFixture(t *testing.T) (*treeFixture, libfossil.FslID, string, []byte) {
	t.Helper()
	f := newTreeFixture(t)
	outside := f.leaf(f.file("vendor/a"))
	root := treeDeck(time.Date(2026, 1, 1, 0, 0, 0, 0, time.UTC), []deck.SubmanifestCard{f.leaf(f.file("src/a")), outside})
	root.T = []deck.TagCard{{Type: deck.TagPropagating, Name: "branch", UUID: "*", Value: "trunk"}}
	uuid := f.store(root)
	rid, _ := content.AvailableByUUID(f.r.DB(), uuid)
	node, _ := content.AvailableByUUID(f.r.DB(), outside.UUID)
	payload, err := content.Expand(f.r.DB(), node)
	if err != nil {
		t.Fatal(err)
	}
	elideWriterNode(t, f.r, outside.UUID)
	c, err := cone.New([]string{"src"}, nil)
	if err != nil {
		t.Fatal(err)
	}
	if err := content.SetStoreCone(f.r.DB(), c); err != nil {
		t.Fatal(err)
	}
	return f, rid, outside.UUID, payload
}

func TestSparseMetadataRebuildAndRollback(t *testing.T) {
	f, rid, outside, payload := partialMetadataFixture(t)
	if _, err := Crosslink(f.r); err != nil {
		t.Fatal(err)
	}
	assertSparseRows(t, f, int64(rid), 1, 0, 1)
	if err := f.r.WithTx(func(tx *db.Tx) error {
		for _, table := range []string{"event", "plink", "mlink", "tagxref", "leaf", "orphan"} {
			if _, err := tx.Exec("DELETE FROM " + table); err != nil {
				return err
			}
		}
		_, err := CrosslinkRebuild(tx)
		return err
	}); err != nil {
		t.Fatal(err)
	}
	assertSparseRows(t, f, int64(rid), 1, 0, 1)
	rollback := errors.New("rollback after complete derivation")
	err := f.r.WithTx(func(tx *db.Tx) error {
		if _, _, err := blob.Store(tx, payload); err != nil {
			return err
		}
		if _, err := CrosslinkRebuild(tx); err != nil {
			return err
		}
		var n int
		if err := tx.QueryRow("SELECT count(*) FROM mlink WHERE mid=?", rid).Scan(&n); err != nil {
			return err
		}
		if n != 2 {
			t.Errorf("completion inside rollback transaction = %d mlinks, want 2", n)
		}
		return rollback
	})
	if !errors.Is(err, rollback) {
		t.Fatalf("rollback = %v", err)
	}
	assertSparseRows(t, f, int64(rid), 1, 0, 1)
	if _, ok := content.AvailableByUUID(f.r.DB(), outside); ok {
		t.Fatal("rolled-back node arrival leaked")
	}
	f.storeBytes(payload)
	if _, err := Crosslink(f.r); err != nil {
		t.Fatal(err)
	}
	assertSparseRows(t, f, int64(rid), 1, 2, 0)
	want := dumpDerivedRows(t, f.r)
	if err := f.r.WithTx(func(tx *db.Tx) error {
		for _, table := range []string{"event", "plink", "mlink", "tagxref", "leaf", "orphan"} {
			if _, err := tx.Exec("DELETE FROM " + table); err != nil {
				return err
			}
		}
		_, err := CrosslinkRebuild(tx)
		return err
	}); err != nil {
		t.Fatal(err)
	}
	if got := dumpDerivedRows(t, f.r); got != want {
		t.Fatalf("dense rebuild diverged:\ngot:\n%s\nwant:\n%s", got, want)
	}
	assertSparseRows(t, f, int64(rid), 1, 2, 0)
}

func TestSparseMetadataRejectsLaterNeededGap(t *testing.T) {
	f := newTreeFixture(t)
	refs := []deck.SubmanifestCard{f.leaf(f.file("src/a")), f.leaf(f.file("vendor/a")), f.leaf(f.file("z/a"))}
	uuid := f.store(treeDeck(time.Date(2026, 1, 1, 0, 0, 0, 0, time.UTC), refs))
	for _, ref := range refs[1:] {
		elideWriterNode(t, f.r, ref.UUID)
	}
	c, err := cone.New([]string{"src", "z"}, nil)
	if err != nil {
		t.Fatal(err)
	}
	if err := content.SetStoreCone(f.r.DB(), c); err != nil {
		t.Fatal(err)
	}
	if _, err := Crosslink(f.r); err != nil {
		t.Fatal(err)
	}
	rid, _ := content.AvailableByUUID(f.r.DB(), uuid)
	var n int
	if err := f.r.DB().QueryRow("SELECT count(*) FROM event WHERE objid=?", rid).Scan(&n); err != nil {
		t.Fatal(err)
	}
	if n != 0 {
		t.Fatal("outside first gap concealed a later needed gap")
	}
}