284 lines
6.5 KiB
Go
284 lines
6.5 KiB
Go
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)
|
||
}
|
||
}
|