1package backend_test 2 3import ( 4 "encoding/json" 5 "fmt" 6 "sync" 7 "testing" 8 "time" 9 10 "github.com/google/go-cmp/cmp" 11 "github.com/grafana/grafana-plugin-sdk-go/backend" 12 "github.com/grafana/grafana-plugin-sdk-go/data" 13 "github.com/stretchr/testify/require" 14) 15 16func testDataResponse() backend.DataResponse { 17 frames := data.Frames{ 18 data.NewFrame("simple", 19 data.NewField("time", nil, []time.Time{ 20 time.Date(2020, 1, 2, 3, 4, 0, 0, time.UTC), 21 time.Date(2020, 1, 2, 3, 5, 0, 0, time.UTC), 22 }), 23 data.NewField("valid", nil, []bool{true, false}), 24 ), 25 data.NewFrame("other", 26 data.NewField("value", nil, []float64{1.0}), 27 ), 28 } 29 return backend.DataResponse{ 30 Frames: frames, 31 } 32} 33 34// TestResponseEncoder makes sure that the JSON produced from arrow and dataframes match 35func TestResponseEncoder(t *testing.T) { 36 dr := testDataResponse() 37 38 b, err := json.Marshal(dr) 39 require.NoError(t, err) 40 41 str := string(b) 42 require.Equal(t, `{"frames":[{"schema":{"name":"simple","fields":[{"name":"time","type":"time","typeInfo":{"frame":"time.Time"}},{"name":"valid","type":"boolean","typeInfo":{"frame":"bool"}}]},"data":{"values":[[1577934240000,1577934300000],[true,false]]}},{"schema":{"name":"other","fields":[{"name":"value","type":"number","typeInfo":{"frame":"float64"}}]},"data":{"values":[[1]]}}]}`, str) 43 44 b2, err := json.Marshal(&dr) 45 require.NoError(t, err) 46 require.Equal(t, str, string(b2), "same result from pointer or object") 47 48 // Now the same thing in query data 49 qdr := backend.NewQueryDataResponse() 50 qdr.Responses["A"] = dr 51 52 b, err = json.Marshal(qdr) 53 require.NoError(t, err) 54 55 str = string(b) 56 require.Equal(t, `{"results":{"A":{"frames":[{"schema":{"name":"simple","fields":[{"name":"time","type":"time","typeInfo":{"frame":"time.Time"}},{"name":"valid","type":"boolean","typeInfo":{"frame":"bool"}}]},"data":{"values":[[1577934240000,1577934300000],[true,false]]}},{"schema":{"name":"other","fields":[{"name":"value","type":"number","typeInfo":{"frame":"float64"}}]},"data":{"values":[[1]]}}]}}}`, str) 57 58 // Read the parsed result and make sure it is the same 59 respCopy := &backend.QueryDataResponse{} 60 err = json.Unmarshal(b, respCopy) 61 require.NoError(t, err) 62 require.Equal(t, len(qdr.Responses), len(respCopy.Responses)) 63 64 // Check the final result 65 for k, val := range qdr.Responses { 66 other := respCopy.Responses[k] 67 require.Equal(t, len(val.Frames), len(other.Frames)) 68 69 for idx := range val.Frames { 70 a := val.Frames[idx] 71 b := other.Frames[idx] 72 73 if diff := cmp.Diff(a, b, data.FrameTestCompareOptions()...); diff != "" { 74 t.Errorf("Result mismatch (-want +got):\n%s", diff) 75 } 76 } 77 } 78} 79 80func TestDataResponseMarshalJSONConcurrent(t *testing.T) { 81 dr := testDataResponse() 82 initialJSON, err := json.Marshal(dr) 83 require.NoError(t, err) 84 var wg sync.WaitGroup 85 for i := 0; i < 2; i++ { 86 wg.Add(1) 87 go func(dr backend.DataResponse) { 88 defer wg.Done() 89 for j := 0; j < 100; j++ { 90 jsonData, err := json.Marshal(dr) 91 require.NoError(t, err) 92 require.JSONEq(t, string(initialJSON), string(jsonData)) 93 } 94 }(dr) 95 } 96 wg.Wait() 97} 98 99func TestQueryDataResponseMarshalJSONConcurrent(t *testing.T) { 100 qdr := backend.NewQueryDataResponse() 101 qdr.Responses["A"] = testDataResponse() 102 initialJSON, err := json.Marshal(qdr) 103 require.NoError(t, err) 104 var wg sync.WaitGroup 105 for i := 0; i < 2; i++ { 106 wg.Add(1) 107 go func(qdr *backend.QueryDataResponse) { 108 defer wg.Done() 109 for j := 0; j < 100; j++ { 110 jsonData, err := json.Marshal(qdr) 111 require.NoError(t, err) 112 require.JSONEq(t, string(initialJSON), string(jsonData)) 113 } 114 }(qdr) 115 } 116 wg.Wait() 117} 118 119func TestQueryDataResponseOrdering(t *testing.T) { 120 qdr := backend.NewQueryDataResponse() 121 qdr.Responses["C"] = testDataResponse() 122 qdr.Responses["A"] = testDataResponse() 123 qdr.Responses["B"] = testDataResponse() 124 b, err := json.Marshal(qdr) 125 require.NoError(t, err) 126 127 expectedDataResponse := `{"frames":[{"schema":{"name":"simple","fields":[{"name":"time","type":"time","typeInfo":{"frame":"time.Time"}},{"name":"valid","type":"boolean","typeInfo":{"frame":"bool"}}]},"data":{"values":[[1577934240000,1577934300000],[true,false]]}},{"schema":{"name":"other","fields":[{"name":"value","type":"number","typeInfo":{"frame":"float64"}}]},"data":{"values":[[1]]}}]}` 128 expected := fmt.Sprintf(`{"results":{"A":%s,"B":%s,"C":%s}}`, expectedDataResponse, expectedDataResponse, expectedDataResponse) 129 require.Equal(t, expected, string(b)) 130} 131