Documentation
¶
Overview ¶
Package fixturedb builds SQLite test fixtures whose TABLE SHAPE is the shape a real producer writes, and removes a test's ability to state a schema at all.
Why this exists ¶
Before this package, ~34 test files hand-wrote `CREATE TABLE node_refs (...)`, in ten mutually incompatible spellings, and zero of them reproduced ley-line's real shape. That was not a cosmetic problem. `ensureCanonicalViews` PROBES for columns and emits a structurally DIFFERENT `v_refs` / `v_defs` body per combination — most sharply:
refsReferrerExpr := "node_id"
if hasRefsContainer {
refsReferrerExpr = "COALESCE(NULLIF(container_node_id, ''), node_id)"
}
So a fixture's `CREATE TABLE` literal was a HIDDEN TEST PARAMETER selecting which SQL the rule under test actually executed. A rule tested against a two-column node_refs exercised the degraded arm while production exercised the full arm, and neither side noticed.
Compounding it, `node_refs.node_id` has two incompatible meanings: the mache/tree-sitter projection puts the ENCLOSING CONSTRUCT there; ley-line puts the CALL-SITE LEAF there and the enclosing def in `container_node_id`. A fixture picked a meaning implicitly, by what it INSERTed, with nowhere in the test saying which.
This is the same defect class as mache-e3d9bb (`Node.Children` had two accessors that silently disagreed; the fix was removing the second way to ask), one layer down. The fix shape is the same: remove the second way to state a schema.
The three properties that carry the weight ¶
NO method on Builder accepts SQL, DDL, a column name, or a table name. That absence IS the design. The DDL in schema.go is unexported and there is no escape hatch.
Builder.Ref takes `from` and `at` as SEPARATE TYPED PARAMETERS, so the two meanings of node_id become distinct arguments instead of an undeclared convention. Leyline writes node_id=<at>, container_node_id=<from>; Standalone writes node_id=<from> and structurally cannot express <at>.
Builder.Build returns a fixture already wired through the canonical view installer, so a test cannot forget the install (a real past problem — see the note in cmd/smell_refs_views.go about "the 26 tests that build fixtures by hand").
Provenance ¶
The Leyline DDL is DERIVED, not hand-written: see schema_leyline.go and the conformance test that re-derives it from the pinned binary and fails on drift. The Standalone DDL is likewise conformance-tested against what ingest.NewSQLiteWriter actually creates.
Index ¶
- Variables
- func RegisterViewInstaller(install func(RefsQuerier) error)
- type Builder
- func (b *Builder) ASTNode(id, kind string, in SourceID, span Span, detail ...Detail) *Builder
- func (b *Builder) Build() (string, *FixtureDB)
- func (b *Builder) Construct(id ConstructID, where ...Where) *Builder
- func (b *Builder) Def(token string, in ConstructID, kind CanonicalKind, detail ...Detail) *Builder
- func (b *Builder) Import(alias, importPath string, in SourceID) *Builder
- func (b *Builder) LSPDef(token string, in ConstructID, uri string, ...) *Builder
- func (b *Builder) Producer() Producer
- func (b *Builder) Ref(token string, from ConstructID, at SiteID, qualifier string, detail ...Detail) *Builder
- func (b *Builder) Source(id SourceID, lang, content string) *Builder
- type CanonicalKind
- type ConstructID
- type Detail
- type FixtureDB
- type Producer
- type RefsQuerier
- type SiteID
- type SourceID
- type Span
- type Where
Constants ¶
This section is empty.
Variables ¶
var ( // Leyline is the ley-line-open parse output: node_refs carries // source_id / container_node_id / qualifier / node_hash and has NO // primary key, so duplicate (token, node_id) rows SURVIVE. node_id is // the call-SITE leaf; the enclosing definition is container_node_id. // This is the shape production reads. Leyline = Producer{/* contains filtered or unexported fields */} // Standalone is mache's own schema projection (internal/ingest.SQLiteWriter): // node_refs is (token, node_id) with PRIMARY KEY (token, node_id) WITHOUT // ROWID, so duplicate rows are DEDUPED. node_id is the ENCLOSING CONSTRUCT; // there is no place to put a call site or a qualifier. // // The dedupe is not incidental. Any COUNT/AVG-over-v_refs rule measures a // different quantity here than on [Leyline] — the fan_out_skew class // (mache-50e939). Standalone = Producer{/* contains filtered or unexported fields */} )
Functions ¶
func RegisterViewInstaller ¶
func RegisterViewInstaller(install func(RefsQuerier) error)
RegisterViewInstaller records the canonical-view installer for this test binary. Call it from an init() in the package under test; calling it twice is a programming error and panics rather than silently taking the last write.
Types ¶
type Builder ¶
type Builder struct {
// contains filtered or unexported fields
}
Builder accumulates a fixture's CONTENT. It has no method that accepts SQL, DDL, a column name or a table name — that absence is the design, not an oversight, and adding such a method re-opens the defect this package closed.
Every method returns the receiver so calls chain.
func New ¶
New starts a fixture for the given producer. p is MANDATORY and must be one of Leyline / Standalone; the zero Producer fails the test immediately, which is how "a fixture cannot exist without naming its producer" is enforced at the one place a fixture can be created.
func (*Builder) ASTNode ¶
ASTNode declares one `_ast` row: a parse-tree node of tree-sitter kind `kind` spanning `span` in source `in`.
func (*Builder) Build ¶
Build materialises the fixture and returns its path alongside it.
The connection is capped at ONE so the TEMP views survive across queries — TEMP objects are per-connection, and a fixture whose pool hands out a second connection loses v_defs / v_refs / v_test_nodes non-deterministically. Getting that wrong was a per-fixture coin flip before this package; now it is settled in one place.
The canonical views are installed here, not by the caller, so a test cannot forget them.
func (*Builder) Construct ¶
func (b *Builder) Construct(id ConstructID, where ...Where) *Builder
Construct declares an enclosing definition and its `nodes` row. Declaring the same id twice fills in whatever Where adds rather than duplicating the construct, so a fixture can name a construct before it knows its source.
func (*Builder) Def ¶
func (b *Builder) Def(token string, in ConstructID, kind CanonicalKind, detail ...Detail) *Builder
Def declares that `token` is DEFINED by construct `in`, with κ kind `kind`.
On Leyline this writes token / node_id / source_id / container_node_id / canonical_kind / node_hash. On Standalone it writes token and node_id and DROPS the rest, because the mache projection has nowhere to put them — which is the point: the same test source now produces the shape it claims.
func (*Builder) Import ¶
Import declares one `_imports` row: `alias` bound to module `importPath` inside source `in`. Only Leyline has this table; on Standalone the call is dropped.
func (*Builder) LSPDef ¶
func (b *Builder) LSPDef(token string, in ConstructID, uri string, startLine, startCol, endLine, endCol int) *Builder
LSPDef declares one `_lsp_defs` row — a binding-fidelity definition, which ensureCanonicalViews unions into v_defs when `_lsp_defs.def_token` exists. The table is LSP-enrichment output, not a producer table, so it is available on both producers.
func (*Builder) Producer ¶
Producer reports which producer shape this fixture reproduces, so a test can assert on it or branch its expectations explicitly rather than by accident.
func (*Builder) Ref ¶
func (b *Builder) Ref(token string, from ConstructID, at SiteID, qualifier string, detail ...Detail) *Builder
Ref declares that `token` is REFERENCED from inside construct `from`, at occurrence `at`, through selector `qualifier`.
`from` and `at` are separate parameters because `node_refs.node_id` means two incompatible things:
- Leyline writes node_id = at (the call-SITE leaf), container_node_id = from (the enclosing def), plus source_id / qualifier / node_hash.
- Standalone writes node_id = from (the enclosing CONSTRUCT) and has no column for at or qualifier at all.
An empty `at` means "an unnamed occurrence": the builder synthesises a unique ley-line-shaped call-site leaf under `from`. An empty `qualifier` is stored as NULL, which is what ley-line writes for a bare (unqualified) call.
func (*Builder) Source ¶
Source declares a source file. lang may be empty when the test does not care; content may be empty, in which case `_source.content` is NULL — which is what ley-line v0.13.0 writes (it stores bytes in source_blobs and only a path here). Pass content when the test is about snippet extraction.
Re-declaring a source fills in whichever of lang/content was previously empty rather than replacing it, so ASTNode's implicit declaration never clobbers an explicit one.
type CanonicalKind ¶
type CanonicalKind string
CanonicalKind is ley-line's closed κ vocabulary, written to `node_defs.canonical_kind`. It normalises per-language tree-sitter kinds (function_declaration / function_item / function_definition) to one value.
Standalone has no column for it; a kind passed to a Standalone fixture is dropped, exactly as the mache projection drops it.
const ( NoKind CanonicalKind = "" Function CanonicalKind = "function" Method CanonicalKind = "method" Type CanonicalKind = "type" Module CanonicalKind = "module" Constant CanonicalKind = "constant" Variable CanonicalKind = "variable" Field CanonicalKind = "field" Interface CanonicalKind = "interface" )
The κ values ley-line emits. NoKind is for defs whose kind ley-line leaves NULL.
type ConstructID ¶
type ConstructID string
ConstructID identifies an enclosing DEFINITION — a function, method, type or module. On both producers this is what lands in `node_defs.node_id`.
type Detail ¶
type Detail struct {
// Container is the construct a DEFINITION is nested inside —
// `node_defs.container_node_id`, which ley-line populates for a method in an
// impl/type block and leaves NULL for a top-level item.
Container ConstructID
// Subtree labels a deduped merkle subtree. Occurrences carrying the same
// label share one node_hash; an empty label means "unique to this
// occurrence".
//
// It is a NAME, not bytes. node_hash is one-to-many by construction (one
// content row, many occurrences), and a fan-out test needs to say "these
// are the same subtree" without any test ever writing a hash literal.
Subtree string
// Token is an `_ast` node's token text, which the builder stores in
// `node_content` and points at from `_ast.node_hash` — the same indirection
// ley-line uses, and the one v_test_nodes' attribute detection walks.
Token string
}
Detail states the optional facts about a definition, a reference or an `_ast` row. Fields that do not apply to the row kind are ignored, and every field is dropped on Standalone when the mache projection has no column for it.
type FixtureDB ¶
type FixtureDB struct {
// contains filtered or unexported fields
}
FixtureDB is a built fixture: a real on-disk SQLite database whose shape is the producer's, with the canonical views already installed.
It satisfies RefsQuerier and cmd's dbPathProvider, so it drops directly into the production call path.
func (*FixtureDB) DB ¶
DB exposes the connection so a test can assert on rows directly.
It is NOT an escape hatch for DDL: the fixture's shape is already fixed by the producer, and internal/lint's LLO boundary rule fails any test that writes an LLO-owned table.
type Producer ¶
type Producer struct {
// contains filtered or unexported fields
}
Producer names the tool whose table shape a fixture reproduces.
The set is CLOSED: the only values are Leyline and Standalone. The single field is unexported, so the zero value is invalid and a caller outside this package cannot construct a third. A fixture therefore cannot exist without naming the producer it models — which is precisely what the 34 hand-written fixtures never did.
type RefsQuerier ¶
RefsQuerier is the query surface a fixture presents. It is deliberately the same shape as cmd's refsQuerier so a fixture drops straight into the production call path.
type SiteID ¶
type SiteID string
SiteID identifies one distinct OCCURRENCE inside a construct: a single call expression, not the function containing it. The empty SiteID means "an unnamed occurrence" and the builder synthesises a unique leaf path for it.
SiteID exists as its own type because `node_refs.node_id` means a site on Leyline and a construct on Standalone. Making them two parameters of two types is the whole reason a migrated test can no longer be ambiguous about which producer it modelled.
type SourceID ¶
type SourceID string
SourceID is a source file's ley-line identity: its path RELATIVE to the parse root ("pkg/orphan.go"), never an absolute path and never a bare basename.
type Where ¶
type Where struct {
// Source pins the construct's source file instead of inferring it.
Source SourceID
// Name sets `nodes.name`. Defaults to the id's last path segment.
Name string
// Parent sets `nodes.parent_id`.
Parent ConstructID
// Directory marks a directory node (`nodes.kind` 0) rather than a leaf (1).
Directory bool
}
Where states where a construct lives. The zero value means "infer it", which is the common case: Builder.Construct derives the source from the id's longest leading path prefix whose last segment has a file extension — "pkg/served_test.go/function_declaration_0" infers "pkg/served_test.go", which is exactly how ley-line composes these ids.