memoryweave/go/internal/selfoptimize/selfoptimize_test.go

313 lines
8.1 KiB
Go

// 织忆 MemoryWeave — 自优化层单元测试
package selfoptimize
import (
"testing"
"time"
)
// ─── 仪表盘 ──────────────────────────────────────────────
func TestDashboard_InitialMetricsAllZero(t *testing.T) {
d := &Dashboard{}
m := d.Metrics()
if m["recall_usefulness_rate"] != 0 {
t.Errorf("expected 0, got %f", m["recall_usefulness_rate"])
}
if m["recall_hit_rate"] != 0 {
t.Errorf("expected 0, got %f", m["recall_hit_rate"])
}
}
func TestDashboard_UsefulnessRate(t *testing.T) {
d := &Dashboard{}
d.RecordFeedback(true)
d.RecordFeedback(true)
d.RecordFeedback(true)
d.RecordFeedback(false)
m := d.Metrics()
if m["recall_usefulness_rate"] != 0.75 {
t.Errorf("expected 0.75, got %f", m["recall_usefulness_rate"])
}
}
func TestDashboard_HitRate(t *testing.T) {
d := &Dashboard{}
d.RecordRecall(true)
d.RecordRecall(true)
d.RecordRecall(false)
m := d.Metrics()
if m["recall_hit_rate"] < 0.66 || m["recall_hit_rate"] > 0.67 {
t.Errorf("expected ~0.67, got %f", m["recall_hit_rate"])
}
}
func TestDashboard_GapClosureRate(t *testing.T) {
d := &Dashboard{}
d.TotalGaps = 10
d.ClosedGaps = 5
m := d.Metrics()
if m["gap_closure_rate"] != 0.5 {
t.Errorf("expected 0.5, got %f", m["gap_closure_rate"])
}
}
func TestDashboard_RecordDeprecation(t *testing.T) {
d := &Dashboard{}
d.RecordDeprecation()
d.RecordDeprecation()
if d.DeprecatedToday != 2 {
t.Errorf("expected 2, got %d", d.DeprecatedToday)
}
}
func TestDashboard_RecordCorrection(t *testing.T) {
d := &Dashboard{}
d.RecordCorrection("muchen_correction")
d.RecordCorrection("config_parse")
if d.TotalFixes != 2 {
t.Errorf("expected 2 fixes, got %d", d.TotalFixes)
}
if d.CascadeFixedTotal != 1 {
t.Errorf("expected 1 cascade, got %d", d.CascadeFixedTotal)
}
}
func TestDashboard_RecordConflictResolved(t *testing.T) {
d := &Dashboard{}
d.RecordConflictResolved(true)
d.RecordConflictResolved(false)
if d.TotalConflicts != 2 {
t.Errorf("expected 2 conflicts, got %d", d.TotalConflicts)
}
if d.AutoResolvedConflicts != 1 {
t.Errorf("expected 1 auto, got %d", d.AutoResolvedConflicts)
}
m := d.Metrics()
if m["auto_resolve_rate"] != 0.5 {
t.Errorf("expected 0.5, got %f", m["auto_resolve_rate"])
}
}
// ─── 知识缺口检测 ────────────────────────────────────────
func TestGapDetector_NotTriggeredBeforeThreshold(t *testing.T) {
gd := NewGapDetector()
gap := gd.RecordMiss("kubernetes")
if gap != nil {
t.Error("should not trigger gap after 1 miss")
}
gd.RecordMiss("kubernetes")
if gap := gd.RecordMiss("kubernetes"); gap == nil {
t.Error("should trigger gap after 3 misses")
}
}
func TestGapDetector_DetectsGapAtThreshold(t *testing.T) {
gd := NewGapDetector()
gd.RecordMiss("topicX")
gd.RecordMiss("topicX")
gap := gd.RecordMiss("topicX")
if gap == nil {
t.Fatal("expected gap after 3 misses")
}
if gap.Topic != "topicX" {
t.Errorf("wrong topic: %s", gap.Topic)
}
if gap.MissCount != 3 {
t.Errorf("expected 3 misses, got %d", gap.MissCount)
}
}
func TestGapDetector_CloseGap(t *testing.T) {
gd := NewGapDetector()
gd.RecordMiss("topic")
gd.RecordMiss("topic")
gd.RecordMiss("topic")
gd.Close("topic")
gaps := gd.List()
if len(gaps) == 0 || !gaps[0].Closed {
t.Error("gap should be closed")
}
}
func TestGapDetector_ClassifySynonym(t *testing.T) {
gd := NewGapDetector()
gd.RecordMiss("API")
gd.RecordMiss("API")
gd.RecordMiss("API")
gaps := gd.List()
if len(gaps) > 0 && gaps[0].Type != GapSynonym {
t.Errorf("expected synonym gap, got %s", gaps[0].Type)
}
}
// ─── 因果追踪 ────────────────────────────────────────────
func TestCausalTracker_RecordVersions(t *testing.T) {
ct := NewCausalTracker()
ct.RecordVersion("mem1", "Docker is used by Hermes", "muchen_oral", "init")
ct.RecordVersion("mem1", "Docker is used by Hermes AND OpenClaw", "config_parse", "update")
if !ct.IsVolatile("mem1") {
// 2 versions → not volatile (volatile = 3+)
}
ct.RecordVersion("mem1", "Docker usage updated", "muchen_correction", "correction")
if !ct.IsVolatile("mem1") {
t.Error("mem1 should be volatile after 3 versions")
}
}
func TestCausalTracker_DependencyChain(t *testing.T) {
ct := NewCausalTracker()
ct.AddDependency("memory_A", "memory_B")
ct.AddDependency("memory_B", "memory_C")
affected := ct.GetAffected("memory_C", nil)
// memory_B depends on C, memory_A depends on B
foundB := false
foundA := false
for _, id := range affected {
if id == "memory_B" {
foundB = true
}
if id == "memory_A" {
foundA = true
}
}
if !foundB || !foundA {
t.Errorf("expected both A and B affected, got %v", affected)
}
}
func TestCausalTracker_CircularDependency(t *testing.T) {
ct := NewCausalTracker()
ct.AddDependency("A", "B")
ct.AddDependency("B", "A")
affected := ct.GetAffected("A", nil)
// Should not loop infinitely
if len(affected) >= 10 {
t.Errorf("circular dependency not handled: %d affected", len(affected))
}
}
// ─── 来源信任度 ──────────────────────────────────────────
func TestSourceTrust(t *testing.T) {
tests := []struct {
source string
expect float64
}{
{"muchen_oral", 1.0},
{"muchen_feishu", 0.95},
{"config_parse", 0.7},
{"agent_infer", 0.5},
{"llm_distill", 0.4},
{"unknown", 0.3},
}
for _, tc := range tests {
if got := SourceTrust(tc.source); got != tc.expect {
t.Errorf("SourceTrust(%s) = %f, want %f", tc.source, got, tc.expect)
}
}
}
// ─── 记忆预取 ────────────────────────────────────────────
func TestPrefetchGraph_CoOccurrence(t *testing.T) {
pg := NewPrefetchGraph()
pg.RecordCoAccess("docker", "nginx")
pg.RecordCoAccess("docker", "nginx")
pg.RecordCoAccess("docker", "kubernetes")
prefetch := pg.GetPrefetch("docker")
if len(prefetch) < 1 {
t.Errorf("expected at least 1 prefetch item, got %d", len(prefetch))
}
}
func TestPrefetchGraph_NothingToPrefetch(t *testing.T) {
pg := NewPrefetchGraph()
prefetch := pg.GetPrefetch("nonexistent")
if len(prefetch) != 0 {
t.Errorf("expected empty prefetch, got %d", len(prefetch))
}
}
// ─── 并发安全 ────────────────────────────────────────────
func TestDashboard_Concurrent(t *testing.T) {
d := &Dashboard{}
done := make(chan bool)
for i := 0; i < 50; i++ {
go func(v bool) {
for j := 0; j < 100; j++ {
d.RecordFeedback(v)
d.RecordRecall(v)
}
done <- true
}(i%2 == 0)
}
for i := 0; i < 50; i++ {
<-done
}
m := d.Metrics()
if m["recall_usefulness_rate"] < 0 || m["recall_usefulness_rate"] > 1 {
t.Errorf("usefulness rate out of bounds: %f", m["recall_usefulness_rate"])
}
}
// ─── 基准测试 ────────────────────────────────────────────
func BenchmarkDashboardMetrics(b *testing.B) {
d := &Dashboard{}
d.UsefulCount = 1000
d.NotUsefulCount = 200
d.TotalRecalls = 1200
d.HitCount = 900
b.ResetTimer()
for i := 0; i < b.N; i++ {
d.Metrics()
}
}
func BenchmarkGapDetection(b *testing.B) {
gd := NewGapDetector()
b.ResetTimer()
for i := 0; i < b.N; i++ {
gd.RecordMiss("benchmark_topic")
}
}
func BenchmarkCausalTracking(b *testing.B) {
ct := NewCausalTracker()
for i := 0; i < 100; i++ {
ct.AddDependency("dep"+string(rune('a'+i%26)), "dep"+string(rune('a'+(i+1)%26)))
}
b.ResetTimer()
for i := 0; i < b.N; i++ {
ct.GetAffected("depa", nil)
}
}
func BenchmarkPrefetch(b *testing.B) {
pg := NewPrefetchGraph()
for i := 0; i < 100; i++ {
pg.RecordCoAccess("main", "item"+string(rune('a'+i%26)))
}
b.ResetTimer()
for i := 0; i < b.N; i++ {
pg.GetPrefetch("main")
}
}
func init() {
// Ensure consistent timestamps for snapshot tests
time.Local = time.UTC
}