Artifact aa67248393eaa23f5032298863d42b37e3d74124ebd84e8ec466574456f5f6d9:
- File
internal/content/available_test.go
— part of check-in
[6273a0750d]
at
2026-07-22 23:03:24
on branch trunk
— fix: correct go.mod module path to go-libfossil (#76) (#117)
* fix: correct go.mod module path to go-libfossil (#76)
The module directive still declared github.com/danmestas/libfossil after the repository was renamed to go-libfossil. pkg.go.dev/godoc scraping matches the declared module path against its hosting path, so the stale path broke SDK doc generation.
Rename the module path across all four in-repo modules (root, the two db/driver submodules, observer/otel, and docs/site) and every internal import, plus the release workflow, the doc-generation script, and the docs.
No tag has been published under the go-libfossil path yet, so the cross-module requires cannot resolve from the proxy. Add replace directives pointing the in-repo requires at their local directories so GOWORK=off builds and the CI matrix resolve without a published version. These replaces are ignored by downstream consumers and can be dropped once new-path tags are cut.
* Guard releases against replace directives; regenerate SDK docs
The go-libfossil rename bridges the in-repo modules with `replace` directives. Go refuses a main module that carries them, so a tag cut while they are present breaks `go install .../cmd/libfossil@<tag>` and every `@version` fetch of the driver submodules. Add a Release workflow step that fails the job before anything is published if any tracked go.mod still contains a replace directive, so the bridge is a CI-enforced TODO rather than a silent landmine. The step comment records what unblocks a release: re-tag the submodules with the corrected module paths, bump the root requires, then drop the replace blocks.
Regenerate the SDK reference docs. The previous pass only substituted URLs over the pre-existing generated files, leaving stale signatures (Repo.Timeline was still documented as taking LogOpts rather than TimelineOpts). Note that gomarkdoc must be run outside a git worktree — inside one it silently strips every source link.
Also reword the README rename note, which read as though the import path had not changed, and re-sort the ncruces require block. (user: [email protected], size: 8933) [annotate] [blame] [check-ins using]
package content import ( "database/sql" "strings" "testing" "time" "github.com/danmestas/go-libfossil/db" "github.com/danmestas/go-libfossil/internal/blob" libfossil "github.com/danmestas/go-libfossil/internal/fsltype" _ "github.com/danmestas/go-libfossil/internal/testdriver" ) func TestIsAvailable_Phantom(t *testing.T) { d := setupTestDB(t) rid, err := blob.StorePhantom(d, "0000000000000000000000000000000000000001") if err != nil { t.Fatalf("StorePhantom: %v", err) } if IsAvailable(d, rid) { t.Fatalf("IsAvailable(phantom rid=%d) = true, want false", rid) } } // A delta whose base is a phantom is unavailable even though every blob row // in the chain exists and the delta's own size is >= 0. A size-only check // passes this case; the transitive walk must not. func TestIsAvailable_DeltaOnPhantom(t *testing.T) { d := setupTestDB(t) baseRid, err := blob.StorePhantom(d, "0000000000000000000000000000000000000002") if err != nil { t.Fatalf("StorePhantom: %v", err) } // Store a delta row directly: the blob exists with a real size, but its // delta source is the phantom above. This is the normal steady state // during clone when a delta arrives before its base. res, err := d.Exec( "INSERT INTO blob(uuid, size, content, rcvid) VALUES(?, 42, x'00', 1)", "0000000000000000000000000000000000000003", ) if err != nil { t.Fatalf("insert delta blob: %v", err) } id, err := res.LastInsertId() if err != nil { t.Fatalf("LastInsertId: %v", err) } deltaRid := libfossil.FslID(id) if _, err := d.Exec("INSERT INTO delta(rid, srcid) VALUES(?, ?)", deltaRid, baseRid); err != nil { t.Fatalf("insert delta: %v", err) } // Sanity: a size-only check would report this as present. var size int64 if err := d.QueryRow("SELECT size FROM blob WHERE rid=?", deltaRid).Scan(&size); err != nil { t.Fatalf("scan size: %v", err) } if size < 0 { t.Fatalf("test setup wrong: delta blob size = %d, want >= 0", size) } if IsAvailable(d, deltaRid) { t.Fatalf("IsAvailable(delta-on-phantom rid=%d) = true, want false", deltaRid) } } func TestIsAvailable_UnknownRid(t *testing.T) { d := setupTestDB(t) if IsAvailable(d, libfossil.FslID(9999)) { t.Fatalf("IsAvailable(unknown rid) = true, want false") } } func TestIsAvailable_GroundedChain(t *testing.T) { d := setupTestDB(t) source := []byte("the original source content for delta testing purposes here") target := []byte("the original source content for MODIFIED testing purposes here") srcRid, _, err := blob.Store(d, source) if err != nil { t.Fatalf("Store: %v", err) } deltaRid, _, err := blob.StoreDelta(d, target, srcRid) if err != nil { t.Fatalf("StoreDelta: %v", err) } if !IsAvailable(d, srcRid) { t.Fatalf("IsAvailable(full-text rid=%d) = false, want true", srcRid) } if !IsAvailable(d, deltaRid) { t.Fatalf("IsAvailable(grounded delta rid=%d) = false, want true", deltaRid) } } // A cyclic delta table must terminate rather than hang the process. Cycles // are reachable from a corrupt repository or from hostile input over the // wire during clone, so the walk carries an explicit bound. func TestIsAvailable_CyclicChainTerminates(t *testing.T) { d := setupTestDB(t) var rids []libfossil.FslID for i := 0; i < 3; i++ { res, err := d.Exec( "INSERT INTO blob(uuid, size, content, rcvid) VALUES(?, 42, x'00', 1)", []string{ "000000000000000000000000000000000000000a", "000000000000000000000000000000000000000b", "000000000000000000000000000000000000000c", }[i], ) if err != nil { t.Fatalf("insert blob %d: %v", i, err) } id, err := res.LastInsertId() if err != nil { t.Fatalf("LastInsertId: %v", err) } rids = append(rids, libfossil.FslID(id)) } // a -> b -> c -> a for i := range rids { src := rids[(i+1)%len(rids)] if _, err := d.Exec("INSERT INTO delta(rid, srcid) VALUES(?, ?)", rids[i], src); err != nil { t.Fatalf("insert delta: %v", err) } } done := make(chan bool, 1) go func() { done <- IsAvailable(d, rids[0]) }() select { case got := <-done: if got { t.Fatalf("IsAvailable(cyclic chain) = true, want false") } case <-time.After(30 * time.Second): t.Fatalf("IsAvailable(cyclic chain) did not terminate") } } func TestAvailableByUUID(t *testing.T) { d := setupTestDB(t) content := []byte("available by uuid content") rid, uuid, err := blob.Store(d, content) if err != nil { t.Fatalf("Store: %v", err) } gotRid, ok := AvailableByUUID(d, uuid) if !ok || gotRid != rid { t.Fatalf("AvailableByUUID(filled) = (%d, %v), want (%d, true)", gotRid, ok, rid) } phantomUUID := "000000000000000000000000000000000000000f" if _, err := blob.StorePhantom(d, phantomUUID); err != nil { t.Fatalf("StorePhantom: %v", err) } if _, ok := AvailableByUUID(d, phantomUUID); ok { t.Fatalf("AvailableByUUID(phantom) = true, want false") } if _, ok := AvailableByUUID(d, "00000000000000000000000000000000deadbeef"); ok { t.Fatalf("AvailableByUUID(unknown) = true, want false") } } // deltaQueryFailer delegates every query to the wrapped Querier except the // chain-step lookup that reads a blob's delta linkage, which it rewrites into // one that genuinely fails. That yields a real error on the exact path // IsAvailable uses to decide a chain is grounded. // // IsAvailable reads size and delta linkage in one statement now, so this can // no longer fail the delta half alone: what it proves is that a failing // chain-step query reports unavailable rather than grounded, which is the // property that matters. The match is on the join specifically, so an // unrelated statement that happens to mention delta is not caught by it. type deltaQueryFailer struct { db.Querier } func (f deltaQueryFailer) QueryRow(query string, args ...any) *sql.Row { if strings.Contains(query, "LEFT JOIN delta") || strings.Contains(query, "FROM delta") { return f.Querier.QueryRow("SELECT no_such_column FROM no_such_table") } return f.Querier.QueryRow(query, args...) } // A chain-step lookup that fails tells us nothing about whether the chain is // grounded. Reporting the content available on that basis would resurrect the // exact bug this predicate exists to prevent — claiming readable content we // never verified — under error conditions, on data arriving over the wire // during clone. func TestIsAvailable_DeltaLookupErrorFailsClosed(t *testing.T) { d := setupTestDB(t) // A perfectly ordinary full-text blob: no delta row, size >= 0. Against // the real DB this is available, which is what makes the injected failure // the only difference between the two assertions below. rid, _, err := blob.Store(d, []byte("grounded full text")) if err != nil { t.Fatalf("Store: %v", err) } if !IsAvailable(d, rid) { t.Fatalf("IsAvailable(full-text rid=%d) = false, want true (test premise broken)", rid) } if IsAvailable(deltaQueryFailer{d}, rid) { t.Fatalf("IsAvailable(rid=%d) = true when the delta lookup errored; "+ "must fail closed on anything but sql.ErrNoRows", rid) } } // A cycle must cost a number of queries proportional to the cycle, not to // maxDeltaChainDepth. IsAvailable is reachable from the wire -- sync's clone // and handler paths, and once per F-card of every check-in Crosslink // processes -- so a peer that plants a two-node delta cycle must not be able // to buy a bounded-but-huge query loop on every call. func TestIsAvailable_CycleCostsCycleLengthNotBound(t *testing.T) { d := setupTestDB(t) var rids []libfossil.FslID for i := 0; i < 2; i++ { res, err := d.Exec( "INSERT INTO blob(uuid, size, content, rcvid) VALUES(?, 42, x'00', 1)", []string{ "00000000000000000000000000000000000000d1", "00000000000000000000000000000000000000d2", }[i], ) if err != nil { t.Fatalf("insert blob %d: %v", i, err) } id, err := res.LastInsertId() if err != nil { t.Fatalf("LastInsertId: %v", err) } rids = append(rids, libfossil.FslID(id)) } for i := range rids { src := rids[(i+1)%len(rids)] if _, err := d.Exec("INSERT INTO delta(rid, srcid) VALUES(?, ?)", rids[i], src); err != nil { t.Fatalf("insert delta: %v", err) } } counter := &chainStepCounter{Querier: d} if IsAvailable(counter, rids[0]) { t.Fatal("IsAvailable(cyclic chain) = true, want false") } // Two nodes in the cycle, so the walk revisits on its third step. if counter.steps > 8 { t.Fatalf("cyclic chain cost %d chain-step queries for a 2-node cycle; "+ "a cycle must stop at the first repeat, not run to maxDeltaChainDepth (%d)", counter.steps, maxDeltaChainDepth) } } // chainStepCounter counts the per-node chain-step queries a walk issues. type chainStepCounter struct { db.Querier steps int } func (c *chainStepCounter) QueryRow(query string, args ...any) *sql.Row { if strings.Contains(query, "LEFT JOIN delta") || strings.Contains(query, "FROM delta") { c.steps++ } return c.Querier.QueryRow(query, args...) }