a06f331eb8
CI / benchmark (push) Has been skipped
install-script / posix-syntax (push) Successful in 6m1s
CI / build-onnx (push) Failing after 6m43s
init-smoke / dry-run (push) Failing after 15m57s
security / govulncheck (push) Has been cancelled
security / trivy-fs (push) Has been cancelled
CI / test (1.26, ubuntu-latest) (push) Has been cancelled
Scorecard supply-chain security / Scorecard analysis (push) Has been cancelled
CI / test (1.26, macos-latest) (push) Has been cancelled
CI / build-windows (push) Has been cancelled
CI / lint (push) Has been cancelled
install-script / powershell-syntax (push) Has been cancelled
install-script / install (macos-14) (push) Has been cancelled
install-script / install (ubuntu-latest) (push) Has been cancelled
434 lines
12 KiB
Go
434 lines
12 KiB
Go
package store_sqlite
|
||
|
||
import (
|
||
"bytes"
|
||
"encoding/gob"
|
||
"encoding/json"
|
||
"path/filepath"
|
||
"reflect"
|
||
"testing"
|
||
|
||
"github.com/zzet/gortex/internal/contracts"
|
||
"github.com/zzet/gortex/internal/graph"
|
||
)
|
||
|
||
// flatRoundTrip encodes via the flat codec only (asserting the fast path was
|
||
// taken) and decodes back through decodeMeta.
|
||
func flatRoundTrip(t *testing.T, in map[string]any) map[string]any {
|
||
t.Helper()
|
||
b, ok := encodeMetaFast(in)
|
||
if !ok {
|
||
t.Fatalf("encodeMetaFast bailed on a modelled map: %#v", in)
|
||
}
|
||
if !isFlatMeta(b) {
|
||
t.Fatalf("encodeMetaFast did not stamp the flat magic: %q", b)
|
||
}
|
||
out, err := decodeMetaFast(b)
|
||
if err != nil {
|
||
t.Fatalf("decodeMetaFast: %v", err)
|
||
}
|
||
return out
|
||
}
|
||
|
||
// TestFlatCodecEveryValueType round-trips one value of every type the codec
|
||
// models and asserts exact Go-type fidelity end to end.
|
||
func TestFlatCodecEveryValueType(t *testing.T) {
|
||
shape := &contracts.Shape{
|
||
Kind: "struct",
|
||
Fields: []contracts.ShapeField{{Name: "id", Type: "int64", Required: true}},
|
||
Notes: []string{"partial"},
|
||
}
|
||
in := map[string]any{
|
||
"str": "hello",
|
||
"unicode": "héllo – мир – 世界 – 🚀",
|
||
"bool_t": true,
|
||
"bool_f": false,
|
||
"int": -7,
|
||
"int64": int64(1700000000),
|
||
"float": 0.875,
|
||
"float_int": 2.0, // integral float must stay float64
|
||
"strs": []string{"a", "b", "c"},
|
||
"nested": map[string]any{"inner": 5, "rate": 1.5, "deep": map[string]any{"x": int64(9)}},
|
||
"map_slice": []map[string]any{{"k": "v", "n": 1}, {"k": "w", "n": 2}},
|
||
"any_slice": []any{"x", 3, true, 4.5},
|
||
"shape": shape,
|
||
"nilval": nil,
|
||
"empty_map": map[string]any{},
|
||
}
|
||
got := flatRoundTrip(t, in)
|
||
|
||
if !reflect.DeepEqual(got, in) {
|
||
t.Fatalf("flat round-trip mismatch:\n got: %#v\nwant: %#v", got, in)
|
||
}
|
||
|
||
// Spot-check the load-bearing concrete types explicitly.
|
||
mustType[string](t, got, "str")
|
||
mustType[bool](t, got, "bool_t")
|
||
mustType[int](t, got, "int")
|
||
mustType[int64](t, got, "int64")
|
||
mustType[float64](t, got, "float")
|
||
mustType[float64](t, got, "float_int")
|
||
mustType[[]string](t, got, "strs")
|
||
mustType[map[string]any](t, got, "nested")
|
||
mustType[[]map[string]any](t, got, "map_slice")
|
||
mustType[[]any](t, got, "any_slice")
|
||
mustType[*contracts.Shape](t, got, "shape")
|
||
if got["nilval"] != nil {
|
||
t.Errorf("nilval: want nil, got %#v", got["nilval"])
|
||
}
|
||
}
|
||
|
||
// TestFlatCodecLargeValues exercises long keys and values that cross the
|
||
// single-byte varint boundary (> 127 bytes), proving the length prefixes
|
||
// round-trip.
|
||
func TestFlatCodecLargeValues(t *testing.T) {
|
||
big := string(bytes.Repeat([]byte("x"), 5000))
|
||
bigKey := string(bytes.Repeat([]byte("k"), 300))
|
||
in := map[string]any{
|
||
bigKey: big,
|
||
"slice": []string{big, "", big},
|
||
}
|
||
got := flatRoundTrip(t, in)
|
||
if !reflect.DeepEqual(got, in) {
|
||
t.Fatalf("large-value round-trip mismatch")
|
||
}
|
||
}
|
||
|
||
// TestFlatCodecDeterministic proves the encoding is byte-stable across
|
||
// encodes (keys are sorted), which matters for any content-hash / dedup.
|
||
func TestFlatCodecDeterministic(t *testing.T) {
|
||
in := map[string]any{
|
||
"z": 1, "a": "x", "m": []string{"p", "q"},
|
||
"nested": map[string]any{"d": 4, "b": 2, "c": 3},
|
||
}
|
||
var prev []byte
|
||
for i := 0; i < 16; i++ {
|
||
b, ok := encodeMetaFast(in)
|
||
if !ok {
|
||
t.Fatalf("encodeMetaFast bailed")
|
||
}
|
||
if prev != nil && !bytes.Equal(prev, b) {
|
||
t.Fatalf("encoding is not deterministic across encodes")
|
||
}
|
||
prev = b
|
||
}
|
||
}
|
||
|
||
// TestEncodeMetaFallbackToJSON: a value whose type the flat codec does not
|
||
// model makes encodeMeta fall back to JSON (leading '{'), and decodeMeta
|
||
// still reads it. No data is dropped.
|
||
func TestEncodeMetaFallbackToJSON(t *testing.T) {
|
||
// uint64 is deliberately outside the modelled type set.
|
||
in := map[string]any{"weird": uint64(42), "name": "keep"}
|
||
|
||
if _, ok := encodeMetaFast(in); ok {
|
||
t.Fatal("encodeMetaFast should bail on an unmodelled value type")
|
||
}
|
||
|
||
b, err := encodeMeta(in)
|
||
if err != nil {
|
||
t.Fatalf("encodeMeta: %v", err)
|
||
}
|
||
if isFlatMeta(b) {
|
||
t.Fatalf("encodeMeta should have fallen back to JSON, got a flat blob")
|
||
}
|
||
if !isJSONObject(b) {
|
||
t.Fatalf("encodeMeta fallback did not produce a JSON object: %q", b)
|
||
}
|
||
got, err := decodeMeta(b)
|
||
if err != nil {
|
||
t.Fatalf("decodeMeta(json fallback): %v", err)
|
||
}
|
||
// The JSON fallback widens uint64 -> int (documented, lossy only for the
|
||
// exotic tail), but the string survives and no row is lost.
|
||
if got["name"] != "keep" {
|
||
t.Errorf("name not preserved through JSON fallback: %#v", got["name"])
|
||
}
|
||
if _, ok := got["weird"]; !ok {
|
||
t.Errorf("weird key dropped by JSON fallback")
|
||
}
|
||
}
|
||
|
||
// TestDecodeLegacyJSON proves rows written by the previous JSON encoder still
|
||
// decode (routed through metaWire for exact types) after the flat-codec
|
||
// switch — existing on-disk databases must keep loading.
|
||
func TestDecodeLegacyJSON(t *testing.T) {
|
||
orig := map[string]any{
|
||
"visibility": "private",
|
||
"complexity": 9,
|
||
"confidence": 1.0, // integral float — metaWire must keep it float64
|
||
"path_param_names": []string{"id"},
|
||
"last_authored": map[string]any{"timestamp": int64(1700000000)},
|
||
}
|
||
blob, err := json.Marshal(orig)
|
||
if err != nil {
|
||
t.Fatalf("json.Marshal: %v", err)
|
||
}
|
||
if isFlatMeta(blob) {
|
||
t.Fatalf("JSON blob unexpectedly looks like a flat blob")
|
||
}
|
||
got, err := decodeMeta(blob)
|
||
if err != nil {
|
||
t.Fatalf("decodeMeta(json): %v", err)
|
||
}
|
||
mustType[string](t, got, "visibility")
|
||
mustType[int](t, got, "complexity")
|
||
mustType[float64](t, got, "confidence")
|
||
mustType[[]string](t, got, "path_param_names")
|
||
la, ok := got["last_authored"].(map[string]any)
|
||
if !ok {
|
||
t.Fatalf("last_authored: want map[string]any, got %T", got["last_authored"])
|
||
}
|
||
mustType[int64](t, la, "timestamp")
|
||
}
|
||
|
||
// TestDecodeMetaFastMalformed: corrupt / truncated flat blobs return an error
|
||
// rather than panicking — a single bad row must not crash a store scan.
|
||
func TestDecodeMetaFastMalformed(t *testing.T) {
|
||
good, ok := encodeMetaFast(map[string]any{"k": "value", "n": 7, "s": []string{"a", "b"}})
|
||
if !ok {
|
||
t.Fatalf("encodeMetaFast bailed")
|
||
}
|
||
cases := map[string][]byte{
|
||
"magic only": {metaFlatMagic0, metaFlatVersion},
|
||
"count then nothing": {metaFlatMagic0, metaFlatVersion, 0x05},
|
||
"truncated mid-blob": good[:len(good)-3],
|
||
"unknown value tag": {metaFlatMagic0, metaFlatVersion, 0x01, 0x01, 'k', 0x7E},
|
||
"giant key length": {metaFlatMagic0, metaFlatVersion, 0x01, 0xFF, 0xFF, 0xFF, 0xFF, 0x0F},
|
||
}
|
||
for name, blob := range cases {
|
||
t.Run(name, func(t *testing.T) {
|
||
_, err := decodeMeta(blob)
|
||
if err == nil {
|
||
t.Errorf("expected an error for %q, got nil", name)
|
||
}
|
||
})
|
||
}
|
||
}
|
||
|
||
// TestStoreReloadMetaFidelity is the wired-path proof: persist a node and an
|
||
// edge with rich Meta through the real store, reopen it (warm restart), and
|
||
// assert Meta is byte-for-byte type-identical. Also runs PRAGMA
|
||
// integrity_check.
|
||
func TestStoreReloadMetaFidelity(t *testing.T) {
|
||
path := filepath.Join(t.TempDir(), "store.sqlite")
|
||
|
||
nodeMeta := map[string]any{
|
||
"complexity": 7,
|
||
"loop_depth": 2,
|
||
"confidence": 0.875,
|
||
"coverage_pct": 1.0, // integral float
|
||
"candidate_count": 2,
|
||
"path_param_names": []string{"id", "org"},
|
||
"status_codes": []string{"200", "404"},
|
||
"churn": map[string]any{"commit_count": 12, "churn_rate": 2.0, "last_author": "a@b.c"},
|
||
"last_authored": map[string]any{"timestamp": int64(1700000000), "email": "x@y.z"},
|
||
"response_envelope": []map[string]any{{"name": "data", "n": 1}},
|
||
"shape": &contracts.Shape{
|
||
Kind: "struct",
|
||
Fields: []contracts.ShapeField{{Name: "id", Type: "int64", Required: true}},
|
||
Notes: []string{"partial"},
|
||
},
|
||
"unicode_doc": "héllo 世界 🚀",
|
||
"is_generated": false,
|
||
}
|
||
edgeMeta := map[string]any{
|
||
"candidate_count": 3,
|
||
"similarity": 0.5,
|
||
"score": 1.0, // integral float
|
||
"count": 5,
|
||
"clone_tokens": 128,
|
||
"synthesized_by": "grpc",
|
||
"arg_names": []string{"ctx", "req"},
|
||
}
|
||
|
||
func() {
|
||
s, err := Open(path)
|
||
if err != nil {
|
||
t.Fatalf("Open: %v", err)
|
||
}
|
||
defer s.Close()
|
||
s.AddNode(&graph.Node{ID: "n1", Kind: "function", Name: "Foo", FilePath: "f.go", Meta: cloneMeta(nodeMeta)})
|
||
s.AddNode(&graph.Node{ID: "n2", Kind: "function", Name: "Bar", FilePath: "f.go"})
|
||
s.AddEdge(&graph.Edge{From: "n1", To: "n2", Kind: "calls", FilePath: "f.go", Line: 10, Meta: cloneMeta(edgeMeta)})
|
||
}()
|
||
|
||
// Reopen — the warm-restart path that reads every blob back.
|
||
s, err := Open(path)
|
||
if err != nil {
|
||
t.Fatalf("reopen: %v", err)
|
||
}
|
||
defer s.Close()
|
||
|
||
n := s.GetNode("n1")
|
||
if n == nil {
|
||
t.Fatal("GetNode(n1) = nil after reload")
|
||
}
|
||
if !reflect.DeepEqual(n.Meta, nodeMeta) {
|
||
t.Fatalf("node Meta mismatch after reload:\n got: %#v\nwant: %#v", n.Meta, nodeMeta)
|
||
}
|
||
|
||
edges := s.GetOutEdges("n1")
|
||
var got *graph.Edge
|
||
for _, e := range edges {
|
||
if e.To == "n2" && e.Kind == "calls" {
|
||
got = e
|
||
break
|
||
}
|
||
}
|
||
if got == nil {
|
||
t.Fatalf("edge n1->n2 not found after reload (got %d edges)", len(edges))
|
||
}
|
||
if !reflect.DeepEqual(got.Meta, edgeMeta) {
|
||
t.Fatalf("edge Meta mismatch after reload:\n got: %#v\nwant: %#v", got.Meta, edgeMeta)
|
||
}
|
||
|
||
var res string
|
||
if err := s.db.QueryRow(`PRAGMA integrity_check`).Scan(&res); err != nil {
|
||
t.Fatalf("integrity_check: %v", err)
|
||
}
|
||
if res != "ok" {
|
||
t.Fatalf("integrity_check = %q, want ok", res)
|
||
}
|
||
}
|
||
|
||
func cloneMeta(m map[string]any) map[string]any {
|
||
out := make(map[string]any, len(m))
|
||
for k, v := range m {
|
||
out[k] = v
|
||
}
|
||
return out
|
||
}
|
||
|
||
func mustType[T any](t *testing.T, m map[string]any, key string) {
|
||
t.Helper()
|
||
v, ok := m[key]
|
||
if !ok {
|
||
t.Errorf("%s: missing from decoded map", key)
|
||
return
|
||
}
|
||
if _, ok := v.(T); !ok {
|
||
var zero T
|
||
t.Errorf("%s: want type %T, got %T (value %v)", key, zero, v, v)
|
||
}
|
||
}
|
||
|
||
// -- benchmarks -----------------------------------------------------------
|
||
|
||
var metaSink any
|
||
|
||
// benchMetaSample is a representative node/edge meta map restricted to the
|
||
// types gob auto-registers (scalars + []string), so all three encoders run on
|
||
// identical input for an apples-to-apples comparison. Shape / nested-map /
|
||
// map-slice values also ride the flat path (see the round-trip tests); they
|
||
// are omitted here only because gob refuses unregistered interface types.
|
||
func benchMetaSample() map[string]any {
|
||
return map[string]any{
|
||
"complexity": 7,
|
||
"loop_depth": 2,
|
||
"parse_errors": 0,
|
||
"position": 3,
|
||
"line": 42,
|
||
"confidence": 0.875,
|
||
"coverage_pct": 83.5,
|
||
"candidate_count": 2,
|
||
"count": 5,
|
||
"clone_tokens": 128,
|
||
"timestamp": int64(1700000000),
|
||
"path_param_names": []string{"id", "org"},
|
||
"status_codes": []string{"200", "404"},
|
||
"signature": "func F(ctx context.Context, x int) (T, error)",
|
||
"some_plugin_flag": "go_linkname",
|
||
"is_generated": false,
|
||
"synthesized_by": "grpc",
|
||
}
|
||
}
|
||
|
||
func BenchmarkEncodeMetaGob(b *testing.B) {
|
||
m := benchMetaSample()
|
||
b.ReportAllocs()
|
||
b.ResetTimer()
|
||
for i := 0; i < b.N; i++ {
|
||
var buf bytes.Buffer
|
||
if err := gob.NewEncoder(&buf).Encode(m); err != nil {
|
||
b.Fatalf("gob encode: %v", err)
|
||
}
|
||
metaSink = buf.Bytes()
|
||
}
|
||
}
|
||
|
||
func BenchmarkEncodeMetaJSON(b *testing.B) {
|
||
m := benchMetaSample()
|
||
b.ReportAllocs()
|
||
b.ResetTimer()
|
||
for i := 0; i < b.N; i++ {
|
||
out, err := json.Marshal(m)
|
||
if err != nil {
|
||
b.Fatalf("json marshal: %v", err)
|
||
}
|
||
metaSink = out
|
||
}
|
||
}
|
||
|
||
func BenchmarkEncodeMetaFlat(b *testing.B) {
|
||
m := benchMetaSample()
|
||
b.ReportAllocs()
|
||
b.ResetTimer()
|
||
for i := 0; i < b.N; i++ {
|
||
out, ok := encodeMetaFast(m)
|
||
if !ok {
|
||
b.Fatal("encodeMetaFast bailed")
|
||
}
|
||
metaSink = out
|
||
}
|
||
}
|
||
|
||
func BenchmarkDecodeMetaGob(b *testing.B) {
|
||
var buf bytes.Buffer
|
||
if err := gob.NewEncoder(&buf).Encode(benchMetaSample()); err != nil {
|
||
b.Fatalf("gob encode: %v", err)
|
||
}
|
||
blob := buf.Bytes()
|
||
b.ReportAllocs()
|
||
b.ResetTimer()
|
||
for i := 0; i < b.N; i++ {
|
||
m, err := decodeMeta(blob)
|
||
if err != nil {
|
||
b.Fatalf("decodeMeta: %v", err)
|
||
}
|
||
metaSink = m
|
||
}
|
||
}
|
||
|
||
func BenchmarkDecodeMetaJSON(b *testing.B) {
|
||
blob, err := json.Marshal(benchMetaSample())
|
||
if err != nil {
|
||
b.Fatalf("json marshal: %v", err)
|
||
}
|
||
b.ReportAllocs()
|
||
b.ResetTimer()
|
||
for i := 0; i < b.N; i++ {
|
||
m, err := decodeMeta(blob)
|
||
if err != nil {
|
||
b.Fatalf("decodeMeta: %v", err)
|
||
}
|
||
metaSink = m
|
||
}
|
||
}
|
||
|
||
func BenchmarkDecodeMetaFlat(b *testing.B) {
|
||
blob, ok := encodeMetaFast(benchMetaSample())
|
||
if !ok {
|
||
b.Fatal("encodeMetaFast bailed")
|
||
}
|
||
b.ReportAllocs()
|
||
b.ResetTimer()
|
||
for i := 0; i < b.N; i++ {
|
||
m, err := decodeMeta(blob)
|
||
if err != nil {
|
||
b.Fatalf("decodeMeta: %v", err)
|
||
}
|
||
metaSink = m
|
||
}
|
||
}
|