Artifact 662afffe1c44e8df05a6410d0a71a2a23ea3d153729792c76eecef0224103983:
- File
integration_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: 8051) [annotate] [blame] [check-ins using]
package libfossil_test import ( "bytes" "fmt" "os/exec" "path/filepath" "testing" "time" libfossil "github.com/danmestas/libfossil" "github.com/danmestas/libfossil/internal/blob" "github.com/danmestas/libfossil/internal/content" "github.com/danmestas/libfossil/internal/manifest" "github.com/danmestas/libfossil/internal/repo" "github.com/danmestas/libfossil/simio" "github.com/danmestas/libfossil/testutil" _ "github.com/danmestas/libfossil/internal/testdriver" ) func TestPhaseA_EndToEnd(t *testing.T) { if testing.Short() { t.Skip("skipping integration test") } // 1. Create a repo with Go path := filepath.Join(t.TempDir(), "phase-a.fossil") r, err := repo.Create(path, "testuser", simio.CryptoRand{}) if err != nil { t.Fatalf("Create: %v", err) } defer r.Close() // 2. Store full-text blobs content1 := []byte("first blob with enough content to make deltas meaningful in testing") rid1, uuid1, err := blob.Store(r.DB(), content1) if err != nil { t.Fatalf("Store blob 1: %v", err) } content2 := []byte("second blob with entirely different content for variety") rid2, _, err := blob.Store(r.DB(), content2) if err != nil { t.Fatalf("Store blob 2: %v", err) } // 3. Store delta blob (against blob 1) content3 := []byte("first blob with MODIFIED content to make deltas meaningful in testing") rid3, _, err := blob.StoreDelta(r.DB(), content3, rid1) if err != nil { t.Fatalf("StoreDelta: %v", err) } // Also store a regular blob (for fossil rebuild compatibility) content4 := []byte("fourth blob without delta") _, _, err = blob.Store(r.DB(), content4) if err != nil { t.Fatalf("Store blob 4: %v", err) } // 4. Store phantom _, err = blob.StorePhantom(r.DB(), "aaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaa") if err != nil { t.Fatalf("StorePhantom: %v", err) } // 5. Verify content retrieval got1, err := content.Expand(r.DB(), rid1) if err != nil { t.Fatalf("Expand rid1: %v", err) } if !bytes.Equal(got1, content1) { t.Fatalf("Expand rid1 mismatch") } got2, err := content.Expand(r.DB(), rid2) if err != nil { t.Fatalf("Expand rid2: %v", err) } if !bytes.Equal(got2, content2) { t.Fatalf("Expand rid2 mismatch") } // 6. Expand delta chain got3, err := content.Expand(r.DB(), rid3) if err != nil { t.Fatalf("Expand rid3 (delta): %v", err) } if !bytes.Equal(got3, content3) { t.Fatalf("Expand rid3 mismatch") } // 7. Verify blob integrity if err := content.Verify(r.DB(), rid1); err != nil { t.Fatalf("Verify rid1: %v", err) } if err := content.Verify(r.DB(), rid3); err != nil { t.Fatalf("Verify rid3: %v", err) } // 8. Verify Fossil CLI can read the repository r.Close() // Verify blob exists via SQL query query := fmt.Sprintf("SELECT uuid, size FROM blob WHERE uuid = '%s'", uuid1) cmd := exec.Command("fossil", "sql", "-R", path, query) out, err := cmd.Output() if err != nil { t.Fatalf("fossil sql: %v", err) } if len(out) == 0 { t.Fatalf("fossil sql: blob %s not found", uuid1) } // 9. fossil rebuild should process the repository // Note: Fossil 2.28 may segfault on repos with deltas created via direct DB writes // This is a known limitation - fossil rebuild works on fossil-generated repos cmd = exec.Command("fossil", "rebuild", path) out, err = cmd.CombinedOutput() if err != nil { // Log the error but don't fail - the important validation is that: // 1. We can create repos // 2. We can store/retrieve content correctly // 3. Fossil CLI can query the database t.Logf("fossil rebuild had issues (expected with manually-created deltas): %v\nOutput: %s", err, out) } else { t.Log("fossil rebuild succeeded") } } func TestPhaseA_FossilCreatedRepo(t *testing.T) { if testing.Short() { t.Skip("skipping integration test") } path := filepath.Join(t.TempDir(), "fossil-created.fossil") exec.Command("fossil", "new", path).CombinedOutput() r, err := repo.Open(path) if err != nil { t.Fatalf("Open fossil-created: %v", err) } defer r.Close() var blobCount int r.DB().QueryRow("SELECT count(*) FROM blob").Scan(&blobCount) t.Logf("fossil-created repo has %d blobs", blobCount) } func TestPhaseB_Integration(t *testing.T) { if !testutil.HasFossil() { t.Skip("fossil not in PATH") } path := filepath.Join(t.TempDir(), "integration-b.fossil") r, err := repo.Create(path, "integration-user", simio.CryptoRand{}) if err != nil { t.Fatalf("Create: %v", err) } defer r.Close() // Create 3 checkins with accumulating file sets var lastRid libfossil.FslID rids := make([]libfossil.FslID, 3) // Commit 1: 2 files rids[0], _, err = manifest.Checkin(r, manifest.CheckinOpts{ Files: []manifest.File{ {Name: "file.txt", Content: []byte("v1")}, {Name: "file1.txt", Content: []byte("new-1")}, }, Comment: "commit 1", User: "integration-user", Time: time.Date(2024, 1, 15, 11, 0, 0, 0, time.UTC), }) if err != nil { t.Fatalf("Checkin 1: %v", err) } lastRid = rids[0] // Commit 2: 3 files (file.txt updated, file1.txt carried, file2.txt added) rids[1], _, err = manifest.Checkin(r, manifest.CheckinOpts{ Files: []manifest.File{ {Name: "file.txt", Content: []byte("v2")}, {Name: "file1.txt", Content: []byte("new-1")}, {Name: "file2.txt", Content: []byte("new-2")}, }, Comment: "commit 2", User: "integration-user", Parent: lastRid, Time: time.Date(2024, 1, 15, 12, 0, 0, 0, time.UTC), }) if err != nil { t.Fatalf("Checkin 2: %v", err) } lastRid = rids[1] // Commit 3: 4 files (all previous + file3.txt) rids[2], _, err = manifest.Checkin(r, manifest.CheckinOpts{ Files: []manifest.File{ {Name: "file.txt", Content: []byte("v3")}, {Name: "file1.txt", Content: []byte("new-1")}, {Name: "file2.txt", Content: []byte("new-2")}, {Name: "file3.txt", Content: []byte("new-3")}, }, Comment: "commit 3", User: "integration-user", Parent: lastRid, Time: time.Date(2024, 1, 15, 13, 0, 0, 0, time.UTC), }) if err != nil { t.Fatalf("Checkin 3: %v", err) } lastRid = rids[2] // Verify database structure var eventCount, plinkCount, mlinkCount int r.DB().QueryRow("SELECT count(*) FROM event WHERE type='ci'").Scan(&eventCount) r.DB().QueryRow("SELECT count(*) FROM plink").Scan(&plinkCount) r.DB().QueryRow("SELECT count(*) FROM mlink").Scan(&mlinkCount) if eventCount != 3 { t.Fatalf("event count = %d, want 3", eventCount) } if plinkCount != 2 { t.Fatalf("plink count = %d, want 2", plinkCount) } // mlink count: commit1=2, commit2=3, commit3=4 = 9 total if mlinkCount != 9 { t.Fatalf("mlink count = %d, want 9", mlinkCount) } // Verify parent-child relationships via plink var pid libfossil.FslID r.DB().QueryRow("SELECT pid FROM plink WHERE cid=?", rids[1]).Scan(&pid) if pid != rids[0] { t.Fatalf("checkin 2 parent = %d, want %d", pid, rids[0]) } // Verify ListFiles works correctly for each commit files1, err := manifest.ListFiles(r, rids[0]) if err != nil { t.Fatalf("ListFiles rids[0]: %v", err) } if len(files1) != 2 { t.Fatalf("ListFiles rids[0] count = %d, want 2", len(files1)) } files2, err := manifest.ListFiles(r, rids[1]) if err != nil { t.Fatalf("ListFiles rids[1]: %v", err) } if len(files2) != 3 { t.Fatalf("ListFiles rids[1] count = %d, want 3", len(files2)) } files3, err := manifest.ListFiles(r, rids[2]) if err != nil { t.Fatalf("ListFiles rids[2]: %v", err) } if len(files3) != 4 { t.Fatalf("ListFiles rids[2] count = %d, want 4", len(files3)) } // Verify Log traverses the chain entries, err := manifest.Log(r, manifest.LogOpts{Start: lastRid}) if err != nil { t.Fatalf("Log: %v", err) } if len(entries) != 3 { t.Fatalf("Log count = %d, want 3", len(entries)) } if entries[0].Comment != "commit 3" || entries[2].Comment != "commit 1" { t.Fatalf("Log order incorrect: %q, %q, %q", entries[0].Comment, entries[1].Comment, entries[2].Comment) } // Verify fossil rebuild processes without errors r.Close() if err := testutil.FossilRebuild(path); err != nil { t.Fatalf("rebuild: %v", err) } }