Artifact 87875cd9d7dc945a03f98f16fa0c081031d77bd24f4d80b09f54618c04673226:
- File
internal/bisect/bisect_test.go
— part of check-in
[f5744359d3]
at
2026-04-19 16:30:54
on branch trunk
— Initial open source release
libfossil is a pure-Go implementation of the Fossil SCM, reading and writing the same .fossil SQLite repository format as upstream Fossil.
This is the first public release of code that previously lived in a private repository. The module path is github.com/danmestas/libfossil and the binary is named libfossil to avoid $PATH collisions with upstream fossil.
Included: - Repo lifecycle (create, clone), checkout/checkin, timeline, merge, diff, annotate, shun, tag, branch, bisect, stash, undo/redo - Sync-over-HTTP protocol (client and handler) - Two SQLite drivers: modernc (pure Go, default) and ncruces (wasm-capable) - Observer interfaces (SyncObserver, CheckoutObserver) with an optional OpenTelemetry implementation in observer/otel (separate submodule) - Deterministic simulation testing harness with BUGGIFY fault injection - Kong-based embeddable CLI under cli/ and a standalone binary under cmd/libfossil/ (user: [email protected], size: 4837) [annotate] [blame] [check-ins using]
package bisect import ( "database/sql" "errors" "testing" libfossil "github.com/danmestas/libfossil/internal/fsltype" libdb "github.com/danmestas/libfossil/db" _ "github.com/danmestas/libfossil/internal/testdriver" ) // setupBisectDB creates an in-memory SQLite DB with vvar, plink, blob, and // event tables, then inserts a linear chain 1->2->...->8 with matching // blob and event rows. func setupBisectDB(t *testing.T) *sql.DB { t.Helper() db, err := sql.Open(libdb.RegisteredDriver().Name, ":memory:") if err != nil { t.Fatal(err) } t.Cleanup(func() { db.Close() }) stmts := []string{ `CREATE TABLE vvar(name TEXT PRIMARY KEY, value CLOB) WITHOUT ROWID`, `CREATE TABLE plink(pid INTEGER, cid INTEGER, isprim BOOLEAN, mtime REAL, UNIQUE(pid,cid))`, `CREATE INDEX plink_i2 ON plink(cid, pid)`, `CREATE TABLE blob(rid INTEGER PRIMARY KEY, uuid TEXT, size INTEGER, content BLOB)`, `CREATE TABLE event(type TEXT, mtime REAL, objid INTEGER, tagid INTEGER, uid INTEGER, bgcolor TEXT, euser TEXT, ecomment TEXT, omtime REAL, usedbymerge INTEGER)`, } for _, s := range stmts { if _, err := db.Exec(s); err != nil { t.Fatalf("setup %q: %v", s, err) } } // Linear chain: 1->2->3->4->5->6->7->8 for i := 1; i <= 7; i++ { if _, err := db.Exec("INSERT INTO plink(pid,cid,isprim) VALUES(?,?,1)", i, i+1); err != nil { t.Fatal(err) } } // blob and event rows for each RID. for i := 1; i <= 8; i++ { uuid := "abc" + string(rune('0'+i)) + "0000000000000000000000000000000000000000" if _, err := db.Exec("INSERT INTO blob(rid,uuid,size) VALUES(?,?,100)", i, uuid); err != nil { t.Fatal(err) } mtime := 2460000.5 + float64(i) if _, err := db.Exec("INSERT INTO event(type,mtime,objid) VALUES('ci',?,?)", mtime, i); err != nil { t.Fatal(err) } } return db } func TestBisectSession(t *testing.T) { db := setupBisectDB(t) s := NewSession(db) if err := s.MarkGood(1); err != nil { t.Fatal(err) } if err := s.MarkBad(8); err != nil { t.Fatal(err) } mid, err := s.Next() if err != nil { t.Fatal(err) } // Midpoint of path [1,2,3,4,5,6,7,8] (len=8) should be path[4] = 5 // But any value 3-6 is reasonable depending on BFS ordering. if mid < 3 || mid > 6 { t.Fatalf("expected midpoint in [3,6], got %d", mid) } } func TestBisectConverges(t *testing.T) { db := setupBisectDB(t) s := NewSession(db) // Bug introduced at commit 5. if err := s.MarkGood(1); err != nil { t.Fatal(err) } if err := s.MarkBad(8); err != nil { t.Fatal(err) } iterations := 0 for iterations < 10 { mid, err := s.Next() if err != nil { if errors.Is(err, ErrBisectComplete) { break } t.Fatal(err) } iterations++ if mid >= 5 { if err := s.MarkBad(mid); err != nil { t.Fatal(err) } } else { if err := s.MarkGood(mid); err != nil { t.Fatal(err) } } } if iterations >= 10 { t.Fatal("bisect did not converge within 10 iterations") } // After convergence, bad should be 5. st := s.Status() if st.Bad != 5 { t.Fatalf("expected bad=5, got %d", st.Bad) } } func TestBisectReset(t *testing.T) { db := setupBisectDB(t) s := NewSession(db) if err := s.MarkGood(1); err != nil { t.Fatal(err) } if err := s.MarkBad(8); err != nil { t.Fatal(err) } s.Reset() st := s.Status() if st.Good != 0 || st.Bad != 0 { t.Fatalf("expected 0/0 after reset, got %d/%d", st.Good, st.Bad) } if st.Log != "" { t.Fatalf("expected empty log after reset, got %q", st.Log) } } func TestBisectSkip(t *testing.T) { db := setupBisectDB(t) s := NewSession(db) if err := s.MarkGood(1); err != nil { t.Fatal(err) } if err := s.MarkBad(8); err != nil { t.Fatal(err) } first, err := s.Next() if err != nil { t.Fatal(err) } if err := s.Skip(first); err != nil { t.Fatal(err) } second, err := s.Next() if err != nil { t.Fatal(err) } if second == first { t.Fatalf("expected different midpoint after skip, both are %d", first) } } func TestBisectList(t *testing.T) { db := setupBisectDB(t) s := NewSession(db) if err := s.MarkGood(1); err != nil { t.Fatal(err) } if err := s.MarkBad(8); err != nil { t.Fatal(err) } entries, err := s.List(libfossil.FslID(3)) if err != nil { t.Fatal(err) } if len(entries) != 8 { t.Fatalf("expected 8 entries, got %d", len(entries)) } // Check labels on first and last. if entries[0].Label != "GOOD" { t.Errorf("expected first entry labelled GOOD, got %q", entries[0].Label) } if entries[len(entries)-1].Label != "BAD" { t.Errorf("expected last entry labelled BAD, got %q", entries[len(entries)-1].Label) } // Check that currentRID=3 is labelled CURRENT. found := false for _, e := range entries { if e.RID == 3 && e.Label == "CURRENT" { found = true } } if !found { t.Error("expected entry with RID=3 labelled CURRENT") } }