|
@@ -8,7 +8,6 @@ import (
|
|
|
"fmt"
|
|
|
"github.com/emqx/kuiper/common"
|
|
|
"github.com/emqx/kuiper/xsql"
|
|
|
- "github.com/emqx/kuiper/xstream"
|
|
|
"github.com/emqx/kuiper/xstream/api"
|
|
|
"github.com/emqx/kuiper/xstream/nodes"
|
|
|
"github.com/emqx/kuiper/xstream/test"
|
|
@@ -188,6 +187,7 @@ func getExtMockSource(name string, done <-chan int, size int) *nodes.SourceNode
|
|
|
"slogan": "Impossible is nothing",
|
|
|
"brand": "Adidas",
|
|
|
},
|
|
|
+ Timestamp: 1541152486500,
|
|
|
},
|
|
|
{
|
|
|
Emitter: name,
|
|
@@ -195,6 +195,7 @@ func getExtMockSource(name string, done <-chan int, size int) *nodes.SourceNode
|
|
|
"slogan": "Stronger than dirt",
|
|
|
"brand": "Ajax",
|
|
|
},
|
|
|
+ Timestamp: 1541152487400,
|
|
|
},
|
|
|
{
|
|
|
Emitter: name,
|
|
@@ -202,6 +203,7 @@ func getExtMockSource(name string, done <-chan int, size int) *nodes.SourceNode
|
|
|
"slogan": "Belong anywhere",
|
|
|
"brand": "Airbnb",
|
|
|
},
|
|
|
+ Timestamp: 1541152488300,
|
|
|
},
|
|
|
{
|
|
|
Emitter: name,
|
|
@@ -209,6 +211,7 @@ func getExtMockSource(name string, done <-chan int, size int) *nodes.SourceNode
|
|
|
"slogan": "I can't believe I ate the whole thing",
|
|
|
"brand": "Alka Seltzer",
|
|
|
},
|
|
|
+ Timestamp: 1541152489200,
|
|
|
},
|
|
|
{
|
|
|
Emitter: name,
|
|
@@ -216,6 +219,7 @@ func getExtMockSource(name string, done <-chan int, size int) *nodes.SourceNode
|
|
|
"slogan": "You're in good hands",
|
|
|
"brand": "Allstate",
|
|
|
},
|
|
|
+ Timestamp: 1541152490100,
|
|
|
},
|
|
|
{
|
|
|
Emitter: name,
|
|
@@ -223,6 +227,7 @@ func getExtMockSource(name string, done <-chan int, size int) *nodes.SourceNode
|
|
|
"slogan": "Don't leave home without it",
|
|
|
"brand": "American Express",
|
|
|
},
|
|
|
+ Timestamp: 1541152491200,
|
|
|
},
|
|
|
{
|
|
|
Emitter: name,
|
|
@@ -230,6 +235,7 @@ func getExtMockSource(name string, done <-chan int, size int) *nodes.SourceNode
|
|
|
"slogan": "Think different",
|
|
|
"brand": "Apple",
|
|
|
},
|
|
|
+ Timestamp: 1541152492300,
|
|
|
},
|
|
|
{
|
|
|
Emitter: name,
|
|
@@ -237,6 +243,7 @@ func getExtMockSource(name string, done <-chan int, size int) *nodes.SourceNode
|
|
|
"slogan": "We try harder",
|
|
|
"brand": "Avis",
|
|
|
},
|
|
|
+ Timestamp: 1541152493400,
|
|
|
},
|
|
|
}
|
|
|
|
|
@@ -373,7 +380,7 @@ func TestFuncState(t *testing.T) {
|
|
|
}
|
|
|
}
|
|
|
for retry := 100; retry > 0; retry-- {
|
|
|
- if err := compareMetrics2(tp, tt.m, tt.sql); err == nil {
|
|
|
+ if err := compareMetrics(tp, tt.m, tt.sql); err == nil {
|
|
|
break
|
|
|
}
|
|
|
time.Sleep(time.Duration(retry) * time.Millisecond)
|
|
@@ -394,49 +401,160 @@ func TestFuncState(t *testing.T) {
|
|
|
t.Errorf("%d. %q\n\nresult mismatch:\n\nexp=%#v\n\ngot=%#v\n\n", i, tt.sql, tt.r, maps)
|
|
|
continue
|
|
|
}
|
|
|
- if err := compareMetrics2(tp, tt.m, tt.sql); err != nil {
|
|
|
+ if err := compareMetrics(tp, tt.m, tt.sql); err != nil {
|
|
|
t.Errorf("%d. %q\n\n%v", i, tt.sql, err)
|
|
|
}
|
|
|
tp.Cancel()
|
|
|
}
|
|
|
}
|
|
|
|
|
|
-func compareMetrics2(tp *xstream.TopologyNew, m map[string]interface{}, sql string) (err error) {
|
|
|
- keys, values := tp.GetMetrics()
|
|
|
- //for i, k := range keys {
|
|
|
- // log.Printf("%s:%v", k, values[i])
|
|
|
- //}
|
|
|
- for k, v := range m {
|
|
|
+func TestFuncStateCheckpoint(t *testing.T) {
|
|
|
+ var tests = []struct {
|
|
|
+ name string
|
|
|
+ sql string
|
|
|
+ r [][]map[string]interface{}
|
|
|
+ size int
|
|
|
+ cc int
|
|
|
+ breakSize int
|
|
|
+ }{
|
|
|
+ {
|
|
|
+ name: `rule1`,
|
|
|
+ sql: `SELECT accumulateWordCount(slogan, " ") as wc FROM text`,
|
|
|
+ size: 8,
|
|
|
+ breakSize: 3,
|
|
|
+ cc: 1,
|
|
|
+ r: [][]map[string]interface{}{
|
|
|
+ {{
|
|
|
+ "wc": float64(3),
|
|
|
+ }},
|
|
|
+ {{
|
|
|
+ "wc": float64(6),
|
|
|
+ }},
|
|
|
+ {{
|
|
|
+ "wc": float64(6),
|
|
|
+ }},
|
|
|
+ {{
|
|
|
+ "wc": float64(8),
|
|
|
+ }},
|
|
|
+ {{
|
|
|
+ "wc": float64(16),
|
|
|
+ }},
|
|
|
+ {{
|
|
|
+ "wc": float64(20),
|
|
|
+ }},
|
|
|
+ {{
|
|
|
+ "wc": float64(25),
|
|
|
+ }},
|
|
|
+ {{
|
|
|
+ "wc": float64(27),
|
|
|
+ }},
|
|
|
+ {{
|
|
|
+ "wc": float64(30),
|
|
|
+ }},
|
|
|
+ },
|
|
|
+ },
|
|
|
+ }
|
|
|
+ p := setup2()
|
|
|
+ for i, tt := range tests {
|
|
|
+ p.ExecDrop(tt.name)
|
|
|
+ cleanStateData()
|
|
|
+ test.ResetClock(1541152485000)
|
|
|
+ mockClock := test.GetMockClock()
|
|
|
+ parser := xsql.NewParser(strings.NewReader(tt.sql))
|
|
|
var (
|
|
|
- index int
|
|
|
- key string
|
|
|
- matched bool
|
|
|
+ sources []*nodes.SourceNode
|
|
|
+ syncs []chan int
|
|
|
)
|
|
|
- for index, key = range keys {
|
|
|
- if k == key {
|
|
|
- if strings.HasSuffix(k, "process_latency_ms") {
|
|
|
- if values[index].(int64) >= v.(int64) {
|
|
|
- matched = true
|
|
|
- continue
|
|
|
- } else {
|
|
|
+ if stmt, err := xsql.Language.Parse(parser); err != nil {
|
|
|
+ t.Errorf("parse sql %s error: %s", tt.sql, err)
|
|
|
+ } else {
|
|
|
+ if selectStmt, ok := stmt.(*xsql.SelectStatement); !ok {
|
|
|
+ t.Errorf("sql %s is not a select statement", tt.sql)
|
|
|
+ } else {
|
|
|
+ streams := xsql.GetStreams(selectStmt)
|
|
|
+ for _, stream := range streams {
|
|
|
+ next := make(chan int)
|
|
|
+ syncs = append(syncs, next)
|
|
|
+ source := getExtMockSource(stream, next, 8)
|
|
|
+ sources = append(sources, source)
|
|
|
+ }
|
|
|
+ }
|
|
|
+ }
|
|
|
+ tp, inputs, err := p.createTopoWithSources(&api.Rule{Id: tt.name, Sql: tt.sql, Options: &api.RuleOption{
|
|
|
+ BufferLength: 100,
|
|
|
+ Qos: api.AtLeastOnce,
|
|
|
+ CheckpointInterval: 2000,
|
|
|
+ }}, sources)
|
|
|
+ if err != nil {
|
|
|
+ t.Error(err)
|
|
|
+ }
|
|
|
+ mockSink := test.NewMockSink()
|
|
|
+ sink := nodes.NewSinkNodeWithSink("mockSink", mockSink, nil)
|
|
|
+ tp.AddSink(inputs, sink)
|
|
|
+ errCh := tp.Open()
|
|
|
+ func() {
|
|
|
+ for i := 0; i < tt.breakSize; i++ {
|
|
|
+ mockClock.Add(1000)
|
|
|
+ log.Debugf("before sent %d at %d", i, common.TimeToUnixMilli(mockClock.Now()))
|
|
|
+ syncs[i%len(syncs)] <- i
|
|
|
+ common.Log.Debugf("send out %d", i)
|
|
|
+ select {
|
|
|
+ case err = <-errCh:
|
|
|
+ t.Log(err)
|
|
|
+ tp.Cancel()
|
|
|
+ return
|
|
|
+ default:
|
|
|
+ }
|
|
|
+ }
|
|
|
+ log.Debugf("first send done at %d", common.TimeToUnixMilli(mockClock.Now()))
|
|
|
+
|
|
|
+ actual := tp.GetCoordinator().GetCompleteCount()
|
|
|
+ if !reflect.DeepEqual(tt.cc, actual) {
|
|
|
+ t.Errorf("%d. checkpoint count\n\nresult mismatch:\n\nexp=%#v\n\ngot=%d\n\n", i, tt.cc, actual)
|
|
|
+ }
|
|
|
+ time.Sleep(1000)
|
|
|
+ tp.Cancel()
|
|
|
+ common.Log.Debugf("cancel and resume data %d", i)
|
|
|
+ errCh := tp.Open()
|
|
|
+ close(syncs[i%len(syncs)])
|
|
|
+ for i := 0; i < tt.size*len(syncs); i++ {
|
|
|
+ common.Log.Debugf("resending data %d", i)
|
|
|
+ retry := 100
|
|
|
+ for ; retry > 0; retry-- {
|
|
|
+ common.Log.Debugf("retry %d", retry)
|
|
|
+ if getMetric(tp, "source_text_0_records_in_total") == i {
|
|
|
break
|
|
|
}
|
|
|
+ time.Sleep(time.Duration(100 * retry))
|
|
|
}
|
|
|
- if values[index] == v {
|
|
|
- matched = true
|
|
|
+ select {
|
|
|
+ case err = <-errCh:
|
|
|
+ t.Log(err)
|
|
|
+ tp.Cancel()
|
|
|
+ return
|
|
|
+ default:
|
|
|
}
|
|
|
- break
|
|
|
}
|
|
|
+ }()
|
|
|
+ common.Log.Debugf("done sending data")
|
|
|
+ results := mockSink.GetResults()
|
|
|
+ var maps [][]map[string]interface{}
|
|
|
+ for _, v := range results {
|
|
|
+ var mapRes []map[string]interface{}
|
|
|
+ err := json.Unmarshal(v, &mapRes)
|
|
|
+ if err != nil {
|
|
|
+ t.Errorf("Failed to parse the input into map")
|
|
|
+ continue
|
|
|
+ }
|
|
|
+ maps = append(maps, mapRes)
|
|
|
}
|
|
|
- if matched {
|
|
|
- continue
|
|
|
+ if len(tt.r) != len(maps) {
|
|
|
+ tt.r = tt.r[:len(maps)]
|
|
|
}
|
|
|
- //do not find
|
|
|
- if index < len(values) {
|
|
|
- return fmt.Errorf("metrics mismatch for %s:\n\nexp=%#v(%t)\n\ngot=%#v(%t)\n\n", k, v, v, values[index], values[index])
|
|
|
- } else {
|
|
|
- return fmt.Errorf("metrics mismatch for %s:\n\nexp=%#v\n\ngot=nil\n\n", k, v)
|
|
|
+ if !reflect.DeepEqual(tt.r, maps) {
|
|
|
+ t.Errorf("%d. %q\n\nresult mismatch:\n\nexp=%#v\n\ngot=%#v\n\n", i, tt.sql, tt.r, maps)
|
|
|
+ continue
|
|
|
}
|
|
|
+ tp.Cancel()
|
|
|
}
|
|
|
- return nil
|
|
|
}
|