package engine import ( "context" "testing" "time" ) // mockEngine 是一个用于测试的模拟引擎实现 type mockEngine struct { points []DataPoint stats EngineStats opened bool closed bool } func (m *mockEngine) Open() error { m.opened = true return nil } func (m *mockEngine) Close() error { m.closed = true return nil } func (m *mockEngine) Write(ctx context.Context, points []DataPoint) error { m.points = append(m.points, points...) m.stats.PointsCount += int64(len(points)) m.stats.LastWriteTime = time.Now() return nil } func (m *mockEngine) Query(ctx context.Context, query Query) (QueryResult, error) { var result QueryResult if len(m.points) == 0 { return result, nil } // 根据查询类型返回不同的结果 switch query.Type { case QueryTypeLatest: // 返回最新的数据点 latest := m.points[len(m.points)-1] result = append(result, SeriesResult{ SeriesID: latest.GetSeriesID(), Points: []DataPoint{latest}, }) case QueryTypeRaw: // 返回所有匹配的数据点 var matchedPoints []DataPoint for _, point := range m.points { if point.Timestamp >= query.StartTime && point.Timestamp <= query.EndTime { // 检查标签是否匹配 if lmatchTags(point.Labels, query.Tags) { matchedPoints = append(matchedPoints, point) } } } if len(matchedPoints) > 0 { result = append(result, SeriesResult{ SeriesID: matchedPoints[0].GetSeriesID(), Points: matchedPoints, }) } } return result, nil } func (m *mockEngine) Compact() error { m.stats.CompactionCount++ m.stats.LastCompaction = time.Now() return nil } func (m *mockEngine) Cleanup() error { return nil } func (m *mockEngine) Stats() EngineStats { return m.stats } // matchTags 检查数据点的标签是否匹配查询标签 func lmatchTags(pointTags, queryTags map[string]string) bool { for k, v := range queryTags { if pointTags[k] != v { return false } } return true } func TestDataPoint_GetSeriesID(t *testing.T) { tests := []struct { name string point DataPoint labels map[string]string }{ { name: "empty labels", point: DataPoint{ Labels: map[string]string{}, }, }, { name: "single label", point: DataPoint{ Labels: map[string]string{"host": "server1"}, }, }, { name: "multiple labels", point: DataPoint{ Labels: map[string]string{ "host": "server1", "region": "us-west", "service": "api", }, }, }, } for _, tt := range tests { t.Run(tt.name, func(t *testing.T) { id := tt.point.GetSeriesID() if id == "" { t.Error("GetSeriesID() returned empty string") } // 相同标签应该生成相同的ID id2 := tt.point.GetSeriesID() if id != id2 { t.Errorf("GetSeriesID() not consistent: got %v and %v", id, id2) } // 创建一个新的数据点,使用相同的标签 point2 := DataPoint{Labels: tt.point.Labels} id3 := point2.GetSeriesID() if id != id3 { t.Errorf("GetSeriesID() not consistent across instances: got %v and %v", id, id3) } }) } } func TestWriteBuffer(t *testing.T) { // 创建一个模拟的WriteBufferHandler handler := &mockBufferHandler{ toflush: []DataPoint{}, points: make([]DataPoint, 0), } // 创建WriteBuffer,设置缓冲区大小为2 buffer := NewWriteBuffer(handler, 2) // 测试写入单个数据点 point1 := DataPoint{ Timestamp: time.Now().UnixNano(), Value: 1.0, Labels: map[string]string{"test": "1"}, } if err := buffer.Write(point1); err != nil { t.Errorf("Write() error = %v", err) } if len(handler.points) != 0 { t.Errorf("Buffer flushed too early, got %d points, want 0", len(handler.points)) } // 测试写入第二个数据点(应该触发刷新) point2 := DataPoint{ Timestamp: time.Now().UnixNano(), Value: 2.0, Labels: map[string]string{"test": "2"}, } if err := buffer.Write(point2); err != nil { t.Errorf("Write() error = %v", err) } if len(handler.toflush) != 2 { t.Errorf("Buffer not flushed when full, got %d points, want 2", len(handler.points)) } // 测试手动刷新 point3 := DataPoint{ Timestamp: time.Now().UnixNano(), Value: 3.0, Labels: map[string]string{"test": "3"}, } if err := buffer.Write(point3); err != nil { t.Errorf("Write() error = %v", err) } if err := buffer.Flush(); err != nil { t.Errorf("Flush() error = %v", err) } if len(handler.toflush) != 3 { t.Errorf("Manual flush failed, got %d points, want 3", len(handler.points)) } } // mockBufferHandler 是一个用于测试的WriteBufferHandler实现 type mockBufferHandler struct { toflush []DataPoint points []DataPoint } func (h *mockBufferHandler) WriteToBuffer(point DataPoint) error { h.points = append(h.points, point) return nil } func (h *mockBufferHandler) FlushBuffer() error { h.toflush = append(h.toflush, h.points...) h.points = []DataPoint{} return nil } func (h *mockBufferHandler) ValidatePoint(point DataPoint) error { return nil } func TestEngineFactory(t *testing.T) { // 注册模拟引擎 RegisterMemoryEngine(func(config *MemoryEngineConfig) (Engine, error) { return &mockEngine{}, nil }) RegisterBoltEngine(func(config *BoltEngineConfig) (Engine, error) { return &mockEngine{}, nil }) tests := []struct { name string config EngineConfig wantErr bool }{ { name: "memory engine", config: NewMemoryEngineConfig(), wantErr: false, }, { name: "bolt engine", config: NewBoltEngineConfig("test.db"), wantErr: false, }, { name: "invalid config type", config: &struct{ EngineConfig }{}, wantErr: true, }, } for _, tt := range tests { t.Run(tt.name, func(t *testing.T) { engine, err := NewEngine(tt.config) if (err != nil) != tt.wantErr { t.Errorf("NewEngine() error = %v, wantErr %v", err, tt.wantErr) return } if !tt.wantErr && engine == nil { t.Error("NewEngine() returned nil engine") } }) } } func TestEngineConfig(t *testing.T) { // 测试内存引擎配置 memConfig := NewMemoryEngineConfig() if memConfig.Type() != "memory" { t.Errorf("MemoryEngineConfig.Type() = %v, want memory", memConfig.Type()) } if memConfig.MaxHistoryValues != 30 { t.Errorf("MemoryEngineConfig.MaxHistoryValues = %v, want 30", memConfig.MaxHistoryValues) } // 测试Bolt引擎配置 boltConfig := NewBoltEngineConfig("test.db") if boltConfig.Type() != "bolt" { t.Errorf("BoltEngineConfig.Type() = %v, want bolt", boltConfig.Type()) } if boltConfig.Path != "test.db" { t.Errorf("BoltEngineConfig.Path = %v, want test.db", boltConfig.Path) } }