Skip to content
go-libfossil Artifact [6a2d06cb38]
Login

Artifact [6a2d06cb38]

Artifact 6a2d06cb385c45068dde73454099af259d2f6517356719c0a11dac1fb3630466:


package conformance

import (
	"context"
	"fmt"
	"path/filepath"
	"testing"

	"github.com/danmestas/go-libfossil/internal/cone"
	"github.com/danmestas/go-libfossil/internal/content"
	"github.com/danmestas/go-libfossil/internal/manifest"
	"github.com/danmestas/go-libfossil/internal/repo"
	"github.com/danmestas/go-libfossil/internal/sync"
)

// These exercise Appendix A step 5 against real flat and tree histories. The
// expected keep set is computed from the fixture's paths, not the compactor.
func scCompactCases() []scCase {
	return []scCase{
		{Case{ID: "sparsecone.5.4.compact-flat", Req: "SC-5.4-2"}, func(t *testing.T) error { return scCompactHistory(t, "flat") }},
		{Case{ID: "sparsecone.5.4.compact-tree", Req: "SC-5.4-2"}, func(t *testing.T) error { return scCompactHistory(t, "tree") }},
		{Case{ID: "sparsecone.5.4.compact-hybrid", Req: "SC-5.4-2"}, func(t *testing.T) error { return scCompactHistory(t, "hybrid") }},
	}
}

func TestSparseCompaction(t *testing.T) {
	for _, c := range scCompactCases() {
		check(t, c.Case, func() error { return c.run(t) })
	}
}

func scCompactHoldings(t *testing.T, r *repo.Repo) string {
	t.Helper()
	var result string
	err := r.DB().QueryRow(`SELECT coalesce(group_concat(rid || ':' || size || ':' || hex(content)), '')
 FROM (SELECT rid, size, content FROM blob ORDER BY rid)`).Scan(&result)
	if err != nil {
		t.Fatal(err)
	}
	return result
}

func scCompactHistory(t *testing.T, mode string) error {
	origin := newSCRepo(t, mode == "tree")
	if mode == "hybrid" {
		if err := origin.r.SetConfig(manifest.SettingEnableSubmanifests, "on"); err != nil {
			return err
		}
		if err := origin.r.SetConfig(manifest.SettingSubmanifestK, "1"); err != nil {
			return err
		}
	}
	// A historical in-cone name keeps its bytes even after a later rename moves
	// the only current alias outside the cone.
	later := map[string]string{}
	for name, data := range scFiles {
		later[name] = data
	}
	delete(later, "src/a.c")
	later["tools/renamed.c"] = scFiles["src/a.c"]
	origin.commit(t, later, origin.c1)
	s := &scSparse{origin: origin, gimmes: map[string]bool{}}
	if _, err := origin.r.DB().Exec("UPDATE user SET cap='gio' WHERE login='nobody'"); err != nil {
		return err
	}
	clone, _, err := sync.Clone(context.Background(), filepath.Join(t.TempDir(), "compact.fossil"), s.transport(origin.r), sync.CloneOpts{})
	if err != nil {
		return err
	}
	defer clone.Close()
	before := scCompactHoldings(t, clone)
	if err := content.SetStoreCone(clone.DB(), scDefault(t)); err != nil {
		return err
	}
	if after := scCompactHoldings(t, clone); after != before {
		return expectf("narrowing changed artifact holdings")
	}
	n, err := content.CompactStore(context.Background(), clone.DB())
	if err != nil {
		return err
	}
	wanted := map[string]bool{}
	for path := range scFiles {
		if !scDefault(t).Covers(path) {
			wanted[scUUIDOf(t, origin.r, origin.c1, path)] = true
		}
	}
	if n != len(wanted) {
		return expectf("compacted %d artifacts; want %d", n, len(wanted))
	}
	classes := scAbsences(t, clone)
	if err := scSameList("compacted elided names", scClassed(classes, content.Elided), scSorted(wanted)); err != nil {
		return err
	}
	if len(classes) != len(wanted) {
		return expectf("unexpected phantom class: %v", classes)
	}
	if _, err := scVerify(clone); err != nil {
		return err
	}
	if n, err := content.CompactStore(context.Background(), clone.DB()); err != nil || n != 0 {
		return fmt.Errorf("repeat compact: %d, %v", n, err)
	}
	return scCompactHeal(t, s, clone, mode == "tree")
}

func scCompactHeal(t *testing.T, s *scSparse, clone *repo.Repo, losePolicy bool) error {
	var wide *cone.Cone
	if !losePolicy {
		wide = scCone(t, []string{"README", "docs", "src", "src.txt", "src-x", "tools"}, nil)
	}
	// Widening and policy loss both make ordinary sync heal compacted names.
	if err := content.SetStoreCone(clone.DB(), wide); err != nil {
		return err
	}
	if err := s.sync(t, clone, s.origin.r, false, true); err != nil {
		return err
	}
	if got := scAbsences(t, clone); len(got) != 0 {
		return expectf("dense pull left absences: %v", got)
	}
	_, err := scVerify(clone)
	return err
}