mirror of
https://gitee.com/rulego/streamsql.git
synced 2026-03-13 22:07:22 +00:00
204 lines
6.2 KiB
Go
204 lines
6.2 KiB
Go
package streamsql
|
||
|
||
import (
|
||
"context"
|
||
"fmt"
|
||
"sync"
|
||
"testing"
|
||
"time"
|
||
|
||
"math/rand"
|
||
|
||
"github.com/stretchr/testify/assert"
|
||
"github.com/stretchr/testify/require"
|
||
)
|
||
|
||
func TestStreamData(t *testing.T) {
|
||
ssql := New()
|
||
// 定义SQL语句。TumblingWindow 滚动窗口,5秒滚动一次
|
||
rsql := "SELECT deviceId,avg(temperature) as avg_temp,min(humidity) as min_humidity ," +
|
||
"window_start() as start,window_end() as end FROM stream where deviceId!='device3' group by deviceId,TumblingWindow('5s')"
|
||
// 根据SQL语句,创建流式分析任务。
|
||
err := ssql.Execute(rsql)
|
||
if err != nil {
|
||
panic(err)
|
||
}
|
||
var wg sync.WaitGroup
|
||
wg.Add(1)
|
||
// 设置30秒测试超时时间
|
||
ctx, cancel := context.WithTimeout(context.Background(), 30*time.Second)
|
||
defer cancel()
|
||
// 添加测试数据
|
||
go func() {
|
||
defer wg.Done()
|
||
ticker := time.NewTicker(1 * time.Second)
|
||
defer ticker.Stop()
|
||
for {
|
||
select {
|
||
case <-ticker.C:
|
||
// 生成随机测试数据,每秒生成10条数据
|
||
for i := 0; i < 10; i++ {
|
||
randomData := map[string]interface{}{
|
||
"deviceId": fmt.Sprintf("device%d", rand.Intn(3)+1),
|
||
"temperature": 20.0 + rand.Float64()*10, // 20-30度之间
|
||
"humidity": 50.0 + rand.Float64()*20, // 50-70%湿度
|
||
}
|
||
// 将数据添加到流中
|
||
ssql.stream.AddData(randomData)
|
||
}
|
||
|
||
case <-ctx.Done():
|
||
return
|
||
}
|
||
}
|
||
}()
|
||
|
||
resultChan := make(chan interface{})
|
||
// 添加计算结果回调
|
||
ssql.stream.AddSink(func(result interface{}) {
|
||
resultChan <- result
|
||
})
|
||
// 记录收到的结果数量
|
||
resultCount := 0
|
||
go func() {
|
||
for result := range resultChan {
|
||
//每隔5秒打印一次结果
|
||
fmt.Printf("打印结果: [%s] %v\n", time.Now().Format("15:04:05.000"), result)
|
||
resultCount++
|
||
}
|
||
}()
|
||
//测试结束
|
||
wg.Wait()
|
||
|
||
// 验证是否收到了结果
|
||
assert.Equal(t, resultCount, 5)
|
||
}
|
||
|
||
func TestStreamsql(t *testing.T) {
|
||
streamsql := New()
|
||
var rsql = "SELECT device,max(temperature) as max_temp,min(humidity) as min_humidity,window_start() as start,window_end() as end FROM stream group by device,SlidingWindow('2s','1s') with (TIMESTAMP='Ts',TIMEUNIT='ss')"
|
||
err := streamsql.Execute(rsql)
|
||
assert.Nil(t, err)
|
||
strm := streamsql.stream
|
||
baseTime := time.Date(2025, 4, 7, 16, 46, 0, 0, time.UTC)
|
||
testData := []interface{}{
|
||
map[string]interface{}{"device": "aa", "temperature": 25.0, "humidity": 60, "Ts": baseTime},
|
||
map[string]interface{}{"device": "aa", "temperature": 30.0, "humidity": 55, "Ts": baseTime.Add(1 * time.Second)},
|
||
map[string]interface{}{"device": "bb", "temperature": 22.0, "humidity": 70, "Ts": baseTime},
|
||
}
|
||
|
||
for _, data := range testData {
|
||
strm.AddData(data)
|
||
}
|
||
// 捕获结果
|
||
resultChan := make(chan interface{})
|
||
strm.AddSink(func(result interface{}) {
|
||
resultChan <- result
|
||
})
|
||
|
||
ctx, cancel := context.WithTimeout(context.Background(), 3*time.Second)
|
||
defer cancel()
|
||
|
||
var actual interface{}
|
||
select {
|
||
case actual = <-resultChan:
|
||
cancel()
|
||
case <-ctx.Done():
|
||
t.Fatal("Timeout waiting for results")
|
||
}
|
||
|
||
expected := []map[string]interface{}{
|
||
{
|
||
"device": "aa",
|
||
"max_temp": 30.0,
|
||
"min_humidity": 55.0,
|
||
"start": baseTime.UnixNano(),
|
||
"end": baseTime.Add(2 * time.Second).UnixNano(),
|
||
},
|
||
{
|
||
"device": "bb",
|
||
"max_temp": 22.0,
|
||
"min_humidity": 70.0,
|
||
"start": baseTime.UnixNano(),
|
||
"end": baseTime.Add(2 * time.Second).UnixNano(),
|
||
},
|
||
}
|
||
|
||
assert.IsType(t, []map[string]interface{}{}, actual)
|
||
resultSlice, ok := actual.([]map[string]interface{})
|
||
require.True(t, ok)
|
||
assert.Len(t, resultSlice, 2)
|
||
for _, expectedResult := range expected {
|
||
found := false
|
||
for _, resultMap := range resultSlice {
|
||
if resultMap["device"] == expectedResult["device"] {
|
||
assert.InEpsilon(t, expectedResult["max_temp"].(float64), resultMap["max_temp"].(float64), 0.0001)
|
||
assert.InEpsilon(t, expectedResult["min_humidity"].(float64), resultMap["min_humidity"].(float64), 0.0001)
|
||
assert.Equal(t, expectedResult["start"].(int64), resultMap["start"].(int64))
|
||
assert.Equal(t, expectedResult["end"].(int64), resultMap["end"].(int64))
|
||
found = true
|
||
break
|
||
}
|
||
}
|
||
assert.True(t, found, fmt.Sprintf("Expected result for device %v not found", expectedResult["device"]))
|
||
}
|
||
}
|
||
|
||
func TestStreamsqlWithoutGroupBy(t *testing.T) {
|
||
streamsql := New()
|
||
var rsql = "SELECT max(temperature) as max_temp,min(humidity) as min_humidity,window_start() as start,window_end() as end FROM stream SlidingWindow('2s','1s') with (TIMESTAMP='Ts',TIMEUNIT='ss')"
|
||
err := streamsql.Execute(rsql)
|
||
assert.Nil(t, err)
|
||
strm := streamsql.stream
|
||
baseTime := time.Date(2025, 4, 7, 16, 46, 0, 0, time.UTC)
|
||
testData := []interface{}{
|
||
map[string]interface{}{"device": "aa", "temperature": 25.0, "humidity": 60, "Ts": baseTime},
|
||
map[string]interface{}{"device": "aa", "temperature": 30.0, "humidity": 55, "Ts": baseTime.Add(1 * time.Second)},
|
||
map[string]interface{}{"device": "bb", "temperature": 22.0, "humidity": 70, "Ts": baseTime},
|
||
}
|
||
|
||
for _, data := range testData {
|
||
strm.AddData(data)
|
||
}
|
||
// 捕获结果
|
||
resultChan := make(chan interface{})
|
||
strm.AddSink(func(result interface{}) {
|
||
resultChan <- result
|
||
})
|
||
|
||
ctx, cancel := context.WithTimeout(context.Background(), 4*time.Second)
|
||
defer cancel()
|
||
|
||
var actual interface{}
|
||
select {
|
||
case actual = <-resultChan:
|
||
cancel()
|
||
case <-ctx.Done():
|
||
t.Fatal("Timeout waiting for results")
|
||
}
|
||
|
||
expected := []map[string]interface{}{
|
||
{
|
||
"max_temp": 30.0,
|
||
"min_humidity": 55.0,
|
||
"start": baseTime.UnixNano(),
|
||
"end": baseTime.Add(2 * time.Second).UnixNano(),
|
||
},
|
||
}
|
||
|
||
assert.IsType(t, []map[string]interface{}{}, actual)
|
||
resultSlice, ok := actual.([]map[string]interface{})
|
||
require.True(t, ok)
|
||
assert.Len(t, resultSlice, 1)
|
||
for _, expectedResult := range expected {
|
||
//found := false
|
||
for _, resultMap := range resultSlice {
|
||
assert.InEpsilon(t, expectedResult["max_temp"].(float64), resultMap["max_temp"].(float64), 0.0001)
|
||
assert.InEpsilon(t, expectedResult["min_humidity"].(float64), resultMap["min_humidity"].(float64), 0.0001)
|
||
assert.Equal(t, expectedResult["start"].(int64), resultMap["start"].(int64))
|
||
assert.Equal(t, expectedResult["end"].(int64), resultMap["end"].(int64))
|
||
}
|
||
//assert.True(t, found, fmt.Sprintf("Expected result for device %v not found", expectedResult["device"]))
|
||
}
|
||
}
|