Artifact f19ae03e15a1a5693983e2bfa16f89af8a9248a934275b669909efc3d3fe9202:
- File
internal/content/structural_horizon_test.go
— part of check-in
[31b140507a]
at
2026-10-22 15:49:33
on branch feat/horizon-cones
— feat(content): hold and classify a structural horizon store
A structural-horizon clone (draft-fossil-horizon-00 §5.1) keeps only the manifests and nodes its held set reaches, so its phantoms need classes the content horizon never had: a parent the origin labelled below the horizon is elided, an unlabelled one stays transit because it may only be late (§5.3), and a phantom only a tag or cherry-pick names is a manifest below the horizon nothing in K reaches. SetStoreCone stops refusing the depth, and keeps the boundary labels beside the store cone, dropping them when the horizon moves since they name what lies below one C. (user: [email protected], size: 5721) [annotate] [blame] [check-ins using] [more...]
package content_test import ( "path/filepath" "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" libfossil "github.com/danmestas/go-libfossil/internal/fsltype" "github.com/danmestas/go-libfossil/internal/manifest" "github.com/danmestas/go-libfossil/internal/repo" "github.com/danmestas/go-libfossil/internal/tag" "github.com/danmestas/go-libfossil/simio" ) // structuralStore is what a structural-horizon clone at c3 of hzFixture // holds (draft-fossil-horizon-00 §5.1): c3's manifest and the blobs it names, // and a control artifact tagging c1, crosslinked. c2, c3's parent, is a // phantom only c3's P card names; c1 is a phantom only the tag names. func structuralStore(t *testing.T) (r *repo.Repo, c1, c2, c3 string) { t.Helper() f := newHZFixtureHeld(t) when := time.Date(2026, 10, 2, 0, 0, 0, 0, time.UTC) ctl, err := tag.AddTag(f.r, tag.TagOpts{ TargetRID: f.c1, TagName: "hue", TagType: tag.TagPropagating, Value: "red", User: "u", Time: when, }) if err != nil { t.Fatal(err) } r, err = repo.Create(filepath.Join(t.TempDir(), "structural.fossil"), "u", simio.CryptoRand{}, "") if err != nil { t.Fatal(err) } t.Cleanup(func() { r.Close() }) rids := []libfossil.FslID{f.c3, ctl} for _, fc := range hzDeck(t, f.r, f.c3).F { rid, ok := content.AvailableByUUID(f.r.DB(), fc.UUID) if !ok { t.Fatalf("fixture: %s is not held", fc.Name) } rids = append(rids, rid) } err = r.WithTx(func(tx *db.Tx) error { for _, rid := range rids { data, err := content.Expand(f.r.DB(), rid) if err != nil { return err } if _, _, err := blob.Store(tx, data); err != nil { return err } } return nil }) if err != nil { t.Fatal(err) } if _, err := manifest.Crosslink(r); err != nil { t.Fatal(err) } return r, f.uuidOf(t, f.c1), f.uuidOf(t, f.c2), f.uuidOf(t, f.c3) } func structuralCone(t *testing.T, c string, depth cone.HorizonDepth) *cone.Cone { t.Helper() h, err := cone.NewHorizon(c, cone.RuleDescendants, depth) if err != nil { t.Fatal(err) } sc, err := cone.NewWithHorizon(nil, nil, h) if err != nil { t.Fatal(err) } return sc } func classOf(t *testing.T, r *repo.Repo, uuid string) content.Absence { t.Helper() a, err := content.LoadAbsences(r.DB()) if err != nil { t.Fatal(err) } rid, ok := blobRID(r, uuid) if !ok { t.Fatalf("%s is not named by the store", uuid) } return a.Class(rid) } func blobRID(r *repo.Repo, uuid string) (libfossil.FslID, bool) { var rid libfossil.FslID err := r.DB().QueryRow("SELECT rid FROM blob WHERE uuid=?", uuid).Scan(&rid) return rid, err == nil } // A structural horizon is stored. Under it an unlabelled parent of a held // check-in is a transit phantom and a labelled one elided (§5.3); a phantom // only a control artifact names is a manifest below the horizon nothing in // K reaches, elided (§5.1); and the labels belong to the horizon they were // given under, so moving C drops them. A content horizon is unchanged. func TestStructuralHorizonClassification(t *testing.T) { r, c1, c2, c3 := structuralStore(t) if err := content.SetStoreCone(r.DB(), structuralCone(t, c3, cone.DepthStructural)); err != nil { t.Fatalf("storing a structural horizon: %v", err) } if got := classOf(t, r, c2); got != content.Transit { t.Fatalf("unlabelled parent c2 is %v, want transit", got) } if got := classOf(t, r, c1); got != content.Elided { t.Fatalf("c1, named only by a tag, is %v, want elided", got) } if err := content.LabelBelow(r.DB(), []string{c2}); err != nil { t.Fatal(err) } if got := classOf(t, r, c2); got != content.Elided { t.Fatalf("labelled parent c2 is %v, want elided", got) } labels, err := content.BelowLabels(r.DB()) if err != nil || len(labels) != 1 || !labels[c2] { t.Fatalf("labels %v, %v; want c2", labels, err) } if err := content.SetStoreCone(r.DB(), structuralCone(t, c2, cone.DepthStructural)); err != nil { t.Fatal(err) } if labels, err := content.BelowLabels(r.DB()); err != nil || len(labels) != 0 { t.Fatalf("moving C kept labels %v, %v", labels, err) } if err := content.SetStoreCone(r.DB(), structuralCone(t, c3, cone.DepthContent)); err != nil { t.Fatal(err) } if err := content.LabelBelow(r.DB(), []string{c2}); err != nil { t.Fatal(err) } for _, uuid := range []string{c1, c2} { if got := classOf(t, r, uuid); got != content.Transit { t.Fatalf("under a content horizon %s is %v, want transit", uuid, got) } } } // newHZFixtureHeld is hzFixture with every artifact still held. func newHZFixtureHeld(t *testing.T) *hzFixture { t.Helper() r, err := repo.Create(filepath.Join(t.TempDir(), "hz.fossil"), "u", simio.CryptoRand{}, "") if err != nil { t.Fatal(err) } t.Cleanup(func() { r.Close() }) f := &hzFixture{r: r, blob: map[string]libfossil.FslID{}, uuid: map[string]string{}} when := time.Date(2026, 10, 1, 0, 0, 0, 0, time.UTC) var parent libfossil.FslID for i, files := range [][]manifest.File{ {{Name: "src/keep.c", Content: []byte("keep\n")}, {Name: "src/old.c", Content: []byte("old 1\n")}}, {{Name: "src/keep.c", Content: []byte("keep\n")}, {Name: "src/old.c", Content: []byte("old 2\n")}}, {{Name: "src/keep.c", Content: []byte("keep\n")}, {Name: "src/new.c", Content: []byte("new\n")}}, } { when = when.Add(time.Hour) rid, _, err := manifest.Checkin(r, manifest.CheckinOpts{ Files: files, Comment: "c", User: "u", Parent: parent, Time: when, }) if err != nil { t.Fatal(err) } parent = rid switch i { case 0: f.c1 = rid case 1: f.c2 = rid case 2: f.c3 = rid } } return f }