Artifact 419511b3542971bb76dce38e13342218c6e927dd9f105d32930a52fd705d0cbc:
- File
internal/manifest/submanifest_write_test.go
— part of check-in
[6d79130418]
at
2026-10-18 16:58:56
on branch feat/submanifest-reader
— feat(manifest): select k on enable as zit does, and add disabling
The draft's automatic selection is advisory, and zit lets history outrank the snapshot: on enable it replays the newest 200 check-ins through the tree builder at the snapshot's k and its two neighbours and pins the value whose steady-state commits materialize the fewest bytes, the lowest on a tie, keeping the snapshot when fewer than two check-ins replay or every candidate stays flat. replayK ports that step by step, so two writers enabling on one history pin alike.
DisableSubmanifests turns emission off and keeps the pin, returning zit's note when trees already exist. The out-of-range fallback logs zit's report. (user: [email protected], size: 14335) [annotate] [blame] [check-ins using]
package manifest import ( "errors" "fmt" "strings" "testing" "time" "github.com/danmestas/go-libfossil/db" "github.com/danmestas/go-libfossil/internal/blob" "github.com/danmestas/go-libfossil/internal/deck" libfossil "github.com/danmestas/go-libfossil/internal/fsltype" "github.com/danmestas/go-libfossil/internal/repo" ) // These tests drive the writer of draft-fossil-submanifests-00 through // Checkin, the one place check-in manifests are made: the emission gate // (§8.1), the tree stored beside its manifest (§6.2), pinning and selecting // k (§6.3, §8.1) and re-tuning it (§6.4). // sha3Repo is a test repository that names artifacts as the vectors do. func sha3Repo(t *testing.T) *repo.Repo { t.Helper() r := setupTestRepo(t) if err := r.SetHashPolicy("sha3"); err != nil { t.Fatal(err) } return r } func a1CheckinFiles() []File { return []File{ {Name: "README.md", Content: []byte("# Example\n")}, {Name: "src/main.c", Content: []byte("int main(void){return 0;}\n")}, {Name: "src/node11.c", Content: []byte("/* node11 */\n")}, {Name: "src/util.c", Content: []byte("/* util */\n")}, } } func setSettings(t *testing.T, r *repo.Repo, kv ...string) { t.Helper() for i := 0; i+1 < len(kv); i += 2 { if err := r.SetConfig(kv[i], kv[i+1]); err != nil { t.Fatal(err) } } } var writeTime = time.Date(2026, 7, 26, 0, 0, 0, 0, time.UTC) func commit(t *testing.T, r *repo.Repo, opts CheckinOpts) (libfossil.FslID, *deck.Deck) { t.Helper() if opts.User == "" { opts.User = "drh" } if opts.Time.IsZero() { opts.Time = writeTime } rid, _, err := Checkin(r, opts) if err != nil { t.Fatalf("Checkin: %v", err) } d, err := GetManifest(r, rid) if err != nil { t.Fatal(err) } return rid, d } func seen(t *testing.T, r *repo.Repo) bool { t.Helper() ok, err := SeenSubmanifest(r.DB()) if err != nil { t.Fatal(err) } return ok } // §8.1: emission is off by default, and the manifest stays flat. func TestCheckinFlatByDefault(t *testing.T) { r := sha3Repo(t) _, d := commit(t, r, CheckinOpts{Files: a1CheckinFiles(), Comment: "flat"}) if len(d.S) != 0 || len(d.F) != 4 || seen(t, r) { t.Fatalf("default commit: %d S cards, %d F cards, seen %v", len(d.S), len(d.F), seen(t, r)) } } // §6.2: with emission on at k = 3 the A.1 files go to the two leaves of // Appendix A.2, the manifest carries their S cards and no F card, the tree // walks back to the four files, and the indicator of §8.1 is set. func TestCheckinEmitsTree(t *testing.T) { r := sha3Repo(t) setSettings(t, r, SettingEnableSubmanifests, "on", SettingSubmanifestK, "3") rid, d := commit(t, r, CheckinOpts{Files: a1CheckinFiles(), Comment: "tree"}) if len(d.F) != 0 || len(d.S) != 2 || d.S[0].UUID != "aa9d404c14fe4dadd2ab398961fb0dfe650a877c061f650d8233a5888e920acf" || d.S[1].UUID != "a50a722b465ed67b42ee0fbe61e4a19d6b1d22940ba5740e99e450921e3f52ea" { t.Fatalf("manifest S %+v F %d", d.S, len(d.F)) } files, err := ListFiles(r, rid) if err != nil || len(files) != 4 || files[3].Name != "src/util.c" { t.Fatalf("ListFiles = %+v, %v", files, err) } if !seen(t, r) { t.Fatal("seen-submanifest not recorded") } for _, s := range d.S { leaf, _ := blob.Exists(r.DB(), s.UUID) var n int r.DB().QueryRow("SELECT count(*) FROM unsent WHERE rid=?", leaf).Scan(&n) if n != 1 { t.Fatalf("leaf %s is not marked unsent", s.UUID[:10]) } } } // §4.4: a tree check-in carries no B card even when a delta is asked for; // a flat construction under emission still may be a delta. func TestCheckinTreeIsNeverDelta(t *testing.T) { r := sha3Repo(t) parent, _ := commit(t, r, CheckinOpts{Files: a1CheckinFiles(), Comment: "base"}) setSettings(t, r, SettingEnableSubmanifests, "on", SettingSubmanifestK, "3") files := a1CheckinFiles() files[1].Content = []byte("int main(void){return 1;}\n") _, d := commit(t, r, CheckinOpts{Files: files, Comment: "edit", Parent: parent, Delta: true, Time: writeTime.Add(time.Hour)}) if d.B != "" || len(d.S) != 2 { t.Fatalf("B %q with %d S cards", d.B, len(d.S)) } } // §6.3: a stored k outside 1..12 falls back to flat; §7.2: a commit made in // a session whose server sent avoid-submanifests is flat. func TestCheckinFlatFallbacks(t *testing.T) { for _, tc := range []struct { name, k string avoid bool }{{"k 13", "13", false}, {"k 0", "0", false}, {"k unset", "", false}, {"avoid", "3", true}} { t.Run(tc.name, func(t *testing.T) { r := sha3Repo(t) setSettings(t, r, SettingEnableSubmanifests, "on") if tc.k != "" { setSettings(t, r, SettingSubmanifestK, tc.k) } _, d := commit(t, r, CheckinOpts{Files: a1CheckinFiles(), Comment: "c", AvoidSubmanifests: tc.avoid}) if len(d.S) != 0 || len(d.F) != 4 || seen(t, r) { t.Fatalf("%d S cards, %d F cards", len(d.S), len(d.F)) } }) } } // subCount counts the sub-manifests r holds. func subCount(t *testing.T, r *repo.Repo) int { t.Helper() subs := 0 rows, err := r.DB().Query("SELECT uuid FROM blob WHERE size>=0") if err != nil { t.Fatal(err) } var uuids []string for rows.Next() { var u string rows.Scan(&u) uuids = append(uuids, u) } rows.Close() for _, u := range uuids { rid, _ := blob.Exists(r.DB(), u) d, err := GetManifest(r, rid) if err != nil { continue } if d.Type == deck.Submanifest { subs++ } } return subs } // §6.2: content addressing shares every unchanged sub-manifest, so editing // one file adds exactly one leaf. func TestCheckinReusesUnchangedLeaves(t *testing.T) { r := sha3Repo(t) setSettings(t, r, SettingEnableSubmanifests, "on", SettingSubmanifestK, "3") parent, first := commit(t, r, CheckinOpts{Files: a1CheckinFiles(), Comment: "one"}) files := a1CheckinFiles() files[3].Content = []byte("/* util 2 */\n") _, second := commit(t, r, CheckinOpts{Files: files, Comment: "two", Parent: parent, Time: writeTime.Add(time.Hour)}) if second.S[0] != first.S[0] || second.S[1] == first.S[1] { t.Fatalf("S runs %+v then %+v", first.S, second.S) } if n := subCount(t, r); n != 3 { t.Fatalf("%d sub-manifests stored, want 3", n) } } func settingValue(t *testing.T, r *repo.Repo, name string) string { t.Helper() v, err := r.Config(name) if err != nil { return "" } return v } // §8.1: a local enable request pins k by the rules of §6.3, and one that // cannot be met changes nothing. func TestEnableSubmanifests(t *testing.T) { r := sha3Repo(t) one, _ := commit(t, r, CheckinOpts{Files: a1CheckinFiles()[:1], Comment: "one file"}) four, _ := commit(t, r, CheckinOpts{Files: a1CheckinFiles(), Comment: "four", Parent: one, Time: writeTime.Add(time.Hour)}) unchanged := func(what string) { t.Helper() if settingValue(t, r, SettingEnableSubmanifests) != "" || settingValue(t, r, SettingSubmanifestK) != "" { t.Fatalf("%s left settings behind", what) } } // Appendix A.5: over one name no k is a candidate, so the request fails. if _, err := EnableSubmanifests(r, one, 0); !errors.Is(err, errNoSelection) { t.Fatalf("enable over one file: %v", err) } unchanged("selection without a result") if _, err := EnableSubmanifests(r, 0, 0); !errors.Is(err, errNoSelection) { t.Fatalf("enable with no current check-in: %v", err) } unchanged("no current check-in") for _, k := range []int{-1, 13} { if _, err := EnableSubmanifests(r, four, k); err == nil { t.Fatalf("enable --k %d accepted", k) } unchanged(fmt.Sprintf("k %d", k)) } // Selection over A.1: k = 1, 2 and 3 tie, and the lowest wins. if k, err := EnableSubmanifests(r, four, 0); err != nil || k != 1 { t.Fatalf("enable = %d, %v", k, err) } if settingValue(t, r, SettingEnableSubmanifests) != "on" || settingValue(t, r, SettingSubmanifestK) != "1" { t.Fatal("settings not written") } // The pin takes precedence; a different explicit k is a re-tune. if k, err := EnableSubmanifests(r, four, 1); err != nil || k != 1 { t.Fatalf("re-enable at the pin = %d, %v", k, err) } if _, err := EnableSubmanifests(r, four, 3); err == nil || !strings.Contains(err.Error(), "re-tune") { t.Fatalf("enable --k 3 over pin 1: %v", err) } // An out-of-range pin is no valid pin: an explicit value replaces it. setSettings(t, r, SettingEnableSubmanifests, "off", SettingSubmanifestK, "13") if k, err := EnableSubmanifests(r, four, 2); err != nil || k != 2 { t.Fatalf("enable --k 2 over pin 13 = %d, %v", k, err) } } // §8.1: a persistence failure partway through enabling leaves neither // setting applied. func TestEnableSubmanifestsAtomic(t *testing.T) { r := sha3Repo(t) four, _ := commit(t, r, CheckinOpts{Files: a1CheckinFiles(), Comment: "four"}) if _, err := r.DB().Exec(`CREATE TRIGGER fail_enable BEFORE INSERT ON config WHEN NEW.name='enable-submanifests' BEGIN SELECT RAISE(ABORT, 'injected'); END`); err != nil { t.Fatal(err) } if _, err := EnableSubmanifests(r, four, 3); err == nil { t.Fatal("enable succeeded through an injected failure") } if settingValue(t, r, SettingSubmanifestK) != "" || settingValue(t, r, SettingEnableSubmanifests) != "" { t.Fatal("a setting was applied by a failed enable") } } // §6.4: a manual re-tune governs the next commit; leaves whose bytes are the // same under both constructions are shared, and history is untouched. func TestRetuneGovernsNextCommit(t *testing.T) { r := sha3Repo(t) setSettings(t, r, SettingEnableSubmanifests, "on", SettingSubmanifestK, "3") parent, first := commit(t, r, CheckinOpts{Files: a1CheckinFiles(), Comment: "one"}) if err := RetuneSubmanifests(r, 13); err == nil { t.Fatal("re-tune to 13 accepted") } if err := RetuneSubmanifests(r, 4); err != nil { t.Fatal(err) } _, second := commit(t, r, CheckinOpts{Files: a1CheckinFiles(), Comment: "two", Parent: parent, Time: writeTime.Add(time.Hour)}) if len(second.S) != 0 || len(second.F) != 4 { t.Fatalf("k=4 commit has %d S cards; A.1 is flat at k = 4", len(second.S)) } if again, _ := GetManifest(r, parent); len(again.S) != len(first.S) { t.Fatal("the parent changed") } if err := RetuneSubmanifests(r, 2); err != nil { t.Fatal(err) } _, third := commit(t, r, CheckinOpts{Files: a1CheckinFiles(), Comment: "three", Parent: parent, Time: writeTime.Add(2 * time.Hour)}) if len(third.S) != 2 || third.S[0] != first.S[0] || third.S[1] != first.S[1] { t.Fatalf("k=2 shares A.1's partition, so its leaves: %+v vs %+v", third.S, first.S) } } // retuneFiles is a file set on which k = 1 is far from the optimum. func retuneFiles() []File { var out []File for i := 0; i < 300; i++ { name := fmt.Sprintf("dir%02d/file%03d.txt", i%20, i) out = append(out, File{Name: name, Content: []byte(name + "\n")}) } return out } // §6.4: only while submanifest-auto-retune is on does a commit re-pin k, and // then only past the margin; off, the pin stays. func TestAutoRetune(t *testing.T) { var cards []deck.FileCard for _, f := range retuneFiles() { cards = append(cards, deck.FileCard{Name: f.Name, UUID: strings.Repeat("0", 64)}) } sortCards(cards) target, ok := retuneTarget(partitionCosts(cards), 1) if !ok { t.Fatal("fixture: k = 1 shows no drift") } for _, auto := range []string{"off", "on"} { r := sha3Repo(t) setSettings(t, r, SettingEnableSubmanifests, "on", SettingSubmanifestK, "1", SettingAutoRetune, auto) commit(t, r, CheckinOpts{Files: retuneFiles(), Comment: "c"}) want := "1" if auto == "on" { want = fmt.Sprint(target) } if got := settingValue(t, r, SettingSubmanifestK); got != want { t.Fatalf("auto-retune %s: k %s, want %s", auto, got, want) } } } func sortCards(cards []deck.FileCard) { for i := 1; i < len(cards); i++ { for j := i; j > 0 && cards[j-1].Name > cards[j].Name; j-- { cards[j-1], cards[j] = cards[j], cards[j-1] } } } // A duplicate name is refused rather than built into a tree. func TestCheckinTreeRefusesDuplicateName(t *testing.T) { r := sha3Repo(t) setSettings(t, r, SettingEnableSubmanifests, "on", SettingSubmanifestK, "3") files := append(a1CheckinFiles(), File{Name: "README.md", Content: []byte("again\n")}) if _, _, err := Checkin(r, CheckinOpts{Files: files, Comment: "dup", User: "u"}); err == nil { t.Fatal("duplicate name accepted") } var n int if err := r.WithTx(func(tx *db.Tx) error { return tx.QueryRow("SELECT count(*) FROM event").Scan(&n) }); err != nil || n != 0 { t.Fatalf("%d events after a refused commit (%v)", n, err) } } // zit's replay refinement of §6.3: it needs two replayable check-ins, and it // keeps to the snapshot's k and its neighbours, the lowest on a tie. func TestReplayK(t *testing.T) { r := sha3Repo(t) if _, ok, err := replayK(r.DB(), 3); ok || err != nil { t.Fatalf("an empty history replayed: %v", err) } one, _ := commit(t, r, CheckinOpts{Files: a1CheckinFiles(), Comment: "one"}) if _, ok, _ := replayK(r.DB(), 3); ok { t.Fatal("one check-in replayed") } files := a1CheckinFiles() files[3].Content = []byte("/* util 2 */\n") commit(t, r, CheckinOpts{Files: files, Comment: "two", Parent: one, Time: writeTime.Add(time.Hour)}) // A.1 partitions alike at k = 1, 2 and 3: every arm ties, the lowest wins. if k, ok, err := replayK(r.DB(), 2); err != nil || !ok || k != 1 { t.Fatalf("replayK(2) = %d, %v, %v", k, ok, err) } // From k = 4 every arm is flat: no result. if k, ok, err := replayK(r.DB(), 6); err != nil || ok { t.Fatalf("replayK(6) = %d, %v, %v", k, ok, err) } } // §8.1: disabling stops trees and keeps the pin; once trees exist it says // they cannot be retired. func TestDisableSubmanifests(t *testing.T) { r := sha3Repo(t) setSettings(t, r, SettingEnableSubmanifests, "on", SettingSubmanifestK, "3") if note, err := DisableSubmanifests(r); err != nil || note != "" { t.Fatalf("disable before any tree = %q, %v", note, err) } setSettings(t, r, SettingEnableSubmanifests, "on") parent, _ := commit(t, r, CheckinOpts{Files: a1CheckinFiles(), Comment: "tree"}) if note, err := DisableSubmanifests(r); err != nil || note != SubmanifestDisableNote { t.Fatalf("disable after a tree = %q, %v", note, err) } _, d := commit(t, r, CheckinOpts{Files: a1CheckinFiles(), Comment: "flat", Parent: parent, Time: writeTime.Add(time.Hour)}) if len(d.S) != 0 || settingValue(t, r, SettingSubmanifestK) != "3" { t.Fatalf("after disable: %d S cards, k %q", len(d.S), settingValue(t, r, SettingSubmanifestK)) } }