|
@@ -1,5 +1,3 @@
|
|
-// +build !windows
|
|
|
|
-
|
|
|
|
package processors
|
|
package processors
|
|
|
|
|
|
import (
|
|
import (
|
|
@@ -7,8 +5,15 @@ import (
|
|
"encoding/json"
|
|
"encoding/json"
|
|
"fmt"
|
|
"fmt"
|
|
"github.com/emqx/kuiper/common"
|
|
"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"
|
|
"os"
|
|
"os"
|
|
"path"
|
|
"path"
|
|
|
|
+ "reflect"
|
|
|
|
+ "strings"
|
|
"testing"
|
|
"testing"
|
|
"time"
|
|
"time"
|
|
)
|
|
)
|
|
@@ -101,10 +106,6 @@ func TestExtensions(t *testing.T) {
|
|
time.Sleep(1000 * time.Millisecond)
|
|
time.Sleep(1000 * time.Millisecond)
|
|
log.Printf("exit main program after a second")
|
|
log.Printf("exit main program after a second")
|
|
results := getResults()
|
|
results := getResults()
|
|
- if len(results) == 0 {
|
|
|
|
- t.Errorf("no result found")
|
|
|
|
- continue
|
|
|
|
- }
|
|
|
|
log.Infof("get results %v", results)
|
|
log.Infof("get results %v", results)
|
|
os.Remove(CACHE_FILE)
|
|
os.Remove(CACHE_FILE)
|
|
var maps [][]map[string]interface{}
|
|
var maps [][]map[string]interface{}
|
|
@@ -173,3 +174,267 @@ func getResults() []string {
|
|
f.Close()
|
|
f.Close()
|
|
return result
|
|
return result
|
|
}
|
|
}
|
|
|
|
+
|
|
|
|
+func getExtMockSource(name string, done <-chan int, size int) *nodes.SourceNode {
|
|
|
|
+ var data []*xsql.Tuple
|
|
|
|
+ switch name {
|
|
|
|
+ case "text":
|
|
|
|
+ data = []*xsql.Tuple{
|
|
|
|
+ {
|
|
|
|
+ Emitter: name,
|
|
|
|
+ Message: map[string]interface{}{
|
|
|
|
+ "slogan": "Impossible is nothing",
|
|
|
|
+ "brand": "Adidas",
|
|
|
|
+ },
|
|
|
|
+ },
|
|
|
|
+ {
|
|
|
|
+ Emitter: name,
|
|
|
|
+ Message: map[string]interface{}{
|
|
|
|
+ "slogan": "Stronger than dirt",
|
|
|
|
+ "brand": "Ajax",
|
|
|
|
+ },
|
|
|
|
+ },
|
|
|
|
+ {
|
|
|
|
+ Emitter: name,
|
|
|
|
+ Message: map[string]interface{}{
|
|
|
|
+ "slogan": "Belong anywhere",
|
|
|
|
+ "brand": "Airbnb",
|
|
|
|
+ },
|
|
|
|
+ },
|
|
|
|
+ {
|
|
|
|
+ Emitter: name,
|
|
|
|
+ Message: map[string]interface{}{
|
|
|
|
+ "slogan": "I can't believe I ate the whole thing",
|
|
|
|
+ "brand": "Alka Seltzer",
|
|
|
|
+ },
|
|
|
|
+ },
|
|
|
|
+ {
|
|
|
|
+ Emitter: name,
|
|
|
|
+ Message: map[string]interface{}{
|
|
|
|
+ "slogan": "You're in good hands",
|
|
|
|
+ "brand": "Allstate",
|
|
|
|
+ },
|
|
|
|
+ },
|
|
|
|
+ {
|
|
|
|
+ Emitter: name,
|
|
|
|
+ Message: map[string]interface{}{
|
|
|
|
+ "slogan": "Don't leave home without it",
|
|
|
|
+ "brand": "American Express",
|
|
|
|
+ },
|
|
|
|
+ },
|
|
|
|
+ {
|
|
|
|
+ Emitter: name,
|
|
|
|
+ Message: map[string]interface{}{
|
|
|
|
+ "slogan": "Think different",
|
|
|
|
+ "brand": "Apple",
|
|
|
|
+ },
|
|
|
|
+ },
|
|
|
|
+ {
|
|
|
|
+ Emitter: name,
|
|
|
|
+ Message: map[string]interface{}{
|
|
|
|
+ "slogan": "We try harder",
|
|
|
|
+ "brand": "Avis",
|
|
|
|
+ },
|
|
|
|
+ },
|
|
|
|
+ }
|
|
|
|
+
|
|
|
|
+ }
|
|
|
|
+ return nodes.NewSourceNodeWithSource(name, test.NewMockSource(data[:size], done, false), map[string]string{
|
|
|
|
+ "DATASOURCE": name,
|
|
|
|
+ })
|
|
|
|
+}
|
|
|
|
+
|
|
|
|
+func setup2() *RuleProcessor {
|
|
|
|
+ log := common.Log
|
|
|
|
+
|
|
|
|
+ dbDir, err := common.GetAndCreateDataLoc("test")
|
|
|
|
+ if err != nil {
|
|
|
|
+ log.Panic(err)
|
|
|
|
+ }
|
|
|
|
+ log.Infof("db location is %s", dbDir)
|
|
|
|
+
|
|
|
|
+ p := NewStreamProcessor(path.Join(dbDir, "stream"))
|
|
|
|
+ demo := `DROP STREAM text`
|
|
|
|
+ p.ExecStmt(demo)
|
|
|
|
+
|
|
|
|
+ demo = "CREATE STREAM text (slogan string, brand string) WITH (DATASOURCE=\"users\", FORMAT=\"JSON\")"
|
|
|
|
+ _, err = p.ExecStmt(demo)
|
|
|
|
+ if err != nil {
|
|
|
|
+ panic(err)
|
|
|
|
+ }
|
|
|
|
+
|
|
|
|
+ rp := NewRuleProcessor(dbDir)
|
|
|
|
+ return rp
|
|
|
|
+}
|
|
|
|
+
|
|
|
|
+func TestFuncState(t *testing.T) {
|
|
|
|
+ var tests = []struct {
|
|
|
|
+ name string
|
|
|
|
+ sql string
|
|
|
|
+ r [][]map[string]interface{}
|
|
|
|
+ s string
|
|
|
|
+ m map[string]interface{}
|
|
|
|
+ }{
|
|
|
|
+ {
|
|
|
|
+ name: `rule1`,
|
|
|
|
+ sql: `SELECT accumulateWordCount(slogan, " ") as wc FROM text`,
|
|
|
|
+ r: [][]map[string]interface{}{
|
|
|
|
+ {{
|
|
|
|
+ "wc": float64(3),
|
|
|
|
+ }},
|
|
|
|
+ {{
|
|
|
|
+ "wc": float64(6),
|
|
|
|
+ }},
|
|
|
|
+ {{
|
|
|
|
+ "wc": float64(8),
|
|
|
|
+ }},
|
|
|
|
+ {{
|
|
|
|
+ "wc": float64(16),
|
|
|
|
+ }},
|
|
|
|
+ {{
|
|
|
|
+ "wc": float64(20),
|
|
|
|
+ }},
|
|
|
|
+ {{
|
|
|
|
+ "wc": float64(25),
|
|
|
|
+ }},
|
|
|
|
+ {{
|
|
|
|
+ "wc": float64(27),
|
|
|
|
+ }},
|
|
|
|
+ {{
|
|
|
|
+ "wc": float64(30),
|
|
|
|
+ }},
|
|
|
|
+ },
|
|
|
|
+ m: map[string]interface{}{
|
|
|
|
+ "op_preprocessor_text_0_exceptions_total": int64(0),
|
|
|
|
+ "op_preprocessor_text_0_process_latency_ms": int64(0),
|
|
|
|
+ "op_preprocessor_text_0_records_in_total": int64(8),
|
|
|
|
+ "op_preprocessor_text_0_records_out_total": int64(8),
|
|
|
|
+
|
|
|
|
+ "op_project_0_exceptions_total": int64(0),
|
|
|
|
+ "op_project_0_process_latency_ms": int64(0),
|
|
|
|
+ "op_project_0_records_in_total": int64(8),
|
|
|
|
+ "op_project_0_records_out_total": int64(8),
|
|
|
|
+
|
|
|
|
+ "sink_mockSink_0_exceptions_total": int64(0),
|
|
|
|
+ "sink_mockSink_0_records_in_total": int64(8),
|
|
|
|
+ "sink_mockSink_0_records_out_total": int64(8),
|
|
|
|
+
|
|
|
|
+ "source_text_0_exceptions_total": int64(0),
|
|
|
|
+ "source_text_0_records_in_total": int64(8),
|
|
|
|
+ "source_text_0_records_out_total": int64(8),
|
|
|
|
+ },
|
|
|
|
+ s: "sink_mockSink_0_records_out_total",
|
|
|
|
+ },
|
|
|
|
+ }
|
|
|
|
+ p := setup2()
|
|
|
|
+ for i, tt := range tests {
|
|
|
|
+ p.ExecDrop(tt.name)
|
|
|
|
+ parser := xsql.NewParser(strings.NewReader(tt.sql))
|
|
|
|
+ var (
|
|
|
|
+ sources []*nodes.SourceNode
|
|
|
|
+ syncs []chan int
|
|
|
|
+ )
|
|
|
|
+ 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: map[string]interface{}{
|
|
|
|
+ "bufferLength": float64(100),
|
|
|
|
+ }}, 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 < 8; i++ {
|
|
|
|
+ syncs[i%len(syncs)] <- i
|
|
|
|
+ select {
|
|
|
|
+ case err = <-errCh:
|
|
|
|
+ t.Log(err)
|
|
|
|
+ tp.Cancel()
|
|
|
|
+ return
|
|
|
|
+ default:
|
|
|
|
+ }
|
|
|
|
+ }
|
|
|
|
+ for retry := 100; retry > 0; retry-- {
|
|
|
|
+ if err := compareMetrics2(tp, tt.m, tt.sql); err == nil {
|
|
|
|
+ break
|
|
|
|
+ }
|
|
|
|
+ time.Sleep(time.Duration(retry) * time.Millisecond)
|
|
|
|
+ }
|
|
|
|
+ }()
|
|
|
|
+ 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 !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
|
|
|
|
+ }
|
|
|
|
+ if err := compareMetrics2(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 {
|
|
|
|
+ var (
|
|
|
|
+ index int
|
|
|
|
+ key string
|
|
|
|
+ matched bool
|
|
|
|
+ )
|
|
|
|
+ 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 {
|
|
|
|
+ break
|
|
|
|
+ }
|
|
|
|
+ }
|
|
|
|
+ if values[index] == v {
|
|
|
|
+ matched = true
|
|
|
|
+ }
|
|
|
|
+ break
|
|
|
|
+ }
|
|
|
|
+ }
|
|
|
|
+ if matched {
|
|
|
|
+ continue
|
|
|
|
+ }
|
|
|
|
+ //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)
|
|
|
|
+ }
|
|
|
|
+ }
|
|
|
|
+ return nil
|
|
|
|
+}
|