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
169 lines
4.8 KiB
Go
169 lines
4.8 KiB
Go
package quality
|
|
|
|
import (
|
|
"os"
|
|
"path/filepath"
|
|
"testing"
|
|
"time"
|
|
)
|
|
|
|
func TestDiffFingerprints_NoPreviousIsNoDrift(t *testing.T) {
|
|
cur := EmbedderFingerprint{Provider: "hugot", Model: "MiniLM-L6-v2", EmbeddingDim: 384}
|
|
w := DiffFingerprints(EmbedderFingerprint{}, cur)
|
|
if w.HasDrift() {
|
|
t.Errorf("no previous fingerprint should not flag drift; got %v", w.Changes)
|
|
}
|
|
}
|
|
|
|
func TestDiffFingerprints_ProviderChangeFlagged(t *testing.T) {
|
|
prev := EmbedderFingerprint{Provider: "hugot", Model: "MiniLM-L6-v2", EmbeddingDim: 384}
|
|
cur := EmbedderFingerprint{Provider: "openai", Model: "text-embedding-3-small", EmbeddingDim: 384}
|
|
w := DiffFingerprints(prev, cur)
|
|
if !w.HasDrift() {
|
|
t.Fatal("provider change should flag drift")
|
|
}
|
|
hasProvider := false
|
|
hasModel := false
|
|
for _, c := range w.Changes {
|
|
if contains(c, "provider:") {
|
|
hasProvider = true
|
|
}
|
|
if contains(c, "model:") {
|
|
hasModel = true
|
|
}
|
|
}
|
|
if !hasProvider || !hasModel {
|
|
t.Errorf("expected provider + model in changes, got %v", w.Changes)
|
|
}
|
|
}
|
|
|
|
func TestDiffFingerprints_DimChangeFlagged(t *testing.T) {
|
|
prev := EmbedderFingerprint{Provider: "hugot", Model: "MiniLM-L6-v2", EmbeddingDim: 384}
|
|
cur := EmbedderFingerprint{Provider: "hugot", Model: "MiniLM-L6-v2", EmbeddingDim: 768}
|
|
w := DiffFingerprints(prev, cur)
|
|
if !w.HasDrift() {
|
|
t.Fatal("dim change should flag drift")
|
|
}
|
|
}
|
|
|
|
func TestDiffFingerprints_IdenticalIsNoDrift(t *testing.T) {
|
|
fp := EmbedderFingerprint{Provider: "hugot", Model: "MiniLM-L6-v2", EmbeddingDim: 384, SampleVecSHA256: "abc"}
|
|
w := DiffFingerprints(fp, fp)
|
|
if w.HasDrift() {
|
|
t.Errorf("identical fingerprints should not flag drift; got %v", w.Changes)
|
|
}
|
|
}
|
|
|
|
func TestDiffFingerprints_SampleVecChangeFlagged(t *testing.T) {
|
|
prev := EmbedderFingerprint{Provider: "hugot", Model: "M", EmbeddingDim: 384, SampleVecSHA256: "abc"}
|
|
cur := EmbedderFingerprint{Provider: "hugot", Model: "M", EmbeddingDim: 384, SampleVecSHA256: "xyz"}
|
|
w := DiffFingerprints(prev, cur)
|
|
if !w.HasDrift() {
|
|
t.Fatal("sample-vec change should flag drift")
|
|
}
|
|
}
|
|
|
|
func TestDriftDetector_RoundTripFingerprint(t *testing.T) {
|
|
dir := t.TempDir()
|
|
path := filepath.Join(dir, "fp.json")
|
|
d := NewDriftDetector(path)
|
|
|
|
want := EmbedderFingerprint{
|
|
Provider: "hugot",
|
|
Model: "MiniLM-L6-v2",
|
|
ModelRevision: "fp32",
|
|
EmbeddingDim: 384,
|
|
SampleVecSHA256: "deadbeef",
|
|
RecordedAt: time.Now().UTC().Round(time.Second),
|
|
}
|
|
if err := d.Save(want); err != nil {
|
|
t.Fatalf("Save: %v", err)
|
|
}
|
|
got, err := d.LoadPrevious()
|
|
if err != nil {
|
|
t.Fatalf("LoadPrevious: %v", err)
|
|
}
|
|
if got.Provider != want.Provider || got.EmbeddingDim != want.EmbeddingDim {
|
|
t.Errorf("round-trip lost data: got %+v want %+v", got, want)
|
|
}
|
|
}
|
|
|
|
func TestDriftDetector_LoadMissingReturnsZero(t *testing.T) {
|
|
d := NewDriftDetector(filepath.Join(t.TempDir(), "missing.json"))
|
|
got, err := d.LoadPrevious()
|
|
if err != nil {
|
|
t.Errorf("missing file should not error, got %v", err)
|
|
}
|
|
if got != (EmbedderFingerprint{}) {
|
|
t.Errorf("missing file should yield zero, got %+v", got)
|
|
}
|
|
}
|
|
|
|
func TestDriftDetector_EmptyPathSilent(t *testing.T) {
|
|
d := NewDriftDetector("")
|
|
if err := d.Save(EmbedderFingerprint{Provider: "x"}); err != nil {
|
|
t.Errorf("empty path Save should be no-op, got %v", err)
|
|
}
|
|
got, err := d.LoadPrevious()
|
|
if err != nil || got != (EmbedderFingerprint{}) {
|
|
t.Errorf("empty path LoadPrevious should return zero / nil, got %+v / %v", got, err)
|
|
}
|
|
}
|
|
|
|
func TestDriftDetector_Compare_EndToEnd(t *testing.T) {
|
|
dir := t.TempDir()
|
|
d := NewDriftDetector(filepath.Join(dir, "fp.json"))
|
|
|
|
// First compare: no previous record → no drift.
|
|
cur := EmbedderFingerprint{Provider: "hugot", Model: "A", EmbeddingDim: 384}
|
|
w, err := d.Compare(cur)
|
|
if err != nil {
|
|
t.Fatal(err)
|
|
}
|
|
if w.HasDrift() {
|
|
t.Error("first comparison should report no drift")
|
|
}
|
|
_ = d.Save(cur)
|
|
|
|
// Second compare with same fingerprint: still no drift.
|
|
w, _ = d.Compare(cur)
|
|
if w.HasDrift() {
|
|
t.Errorf("identical fingerprint should not drift, got %v", w.Changes)
|
|
}
|
|
|
|
// Change the model: should drift.
|
|
cur.Model = "B"
|
|
w, _ = d.Compare(cur)
|
|
if !w.HasDrift() {
|
|
t.Error("model change should drift")
|
|
}
|
|
}
|
|
|
|
func TestDefaultFingerprintPath(t *testing.T) {
|
|
got := DefaultFingerprintPath()
|
|
if got == "" {
|
|
// Acceptable when UserCacheDir is unavailable; just don't panic.
|
|
return
|
|
}
|
|
if filepath.Base(got) != "embedding-fingerprint.json" {
|
|
t.Errorf("default path basename = %q, want embedding-fingerprint.json", filepath.Base(got))
|
|
}
|
|
}
|
|
|
|
func contains(s, sub string) bool {
|
|
return len(s) >= len(sub) && (s == sub || stringsHasSubstring(s, sub))
|
|
}
|
|
|
|
func stringsHasSubstring(s, sub string) bool {
|
|
for i := 0; i+len(sub) <= len(s); i++ {
|
|
if s[i:i+len(sub)] == sub {
|
|
return true
|
|
}
|
|
}
|
|
return false
|
|
}
|
|
|
|
// Quiet "imported and not used" when the harness iterates the
|
|
// fingerprint file directly without going through DriftDetector.
|
|
var _ = os.Stat
|