-
Notifications
You must be signed in to change notification settings - Fork 2.1k
/
cmp.go
254 lines (221 loc) · 9.03 KB
/
cmp.go
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
202
203
204
205
206
207
208
209
210
211
212
213
214
215
216
217
218
219
220
221
222
223
224
225
226
227
228
229
230
231
232
233
234
235
236
237
238
239
240
241
242
243
244
245
246
247
248
249
250
251
252
253
254
/*
Copyright 2022 The Vitess Authors.
Licensed under the Apache License, Version 2.0 (the "License");
you may not use this file except in compliance with the License.
You may obtain a copy of the License at
http://www.apache.org/licenses/LICENSE-2.0
Unless required by applicable law or agreed to in writing, software
distributed under the License is distributed on an "AS IS" BASIS,
WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
See the License for the specific language governing permissions and
limitations under the License.
*/
package utils
import (
"context"
"fmt"
"testing"
"github.com/google/go-cmp/cmp"
"github.com/stretchr/testify/assert"
"github.com/stretchr/testify/require"
"vitess.io/vitess/go/mysql"
"vitess.io/vitess/go/sqltypes"
"vitess.io/vitess/go/test/utils"
)
type MySQLCompare struct {
t *testing.T
MySQLConn, VtConn *mysql.Conn
}
func NewMySQLCompare(t *testing.T, vtParams, mysqlParams mysql.ConnParams) (MySQLCompare, error) {
ctx := context.Background()
vtConn, err := mysql.Connect(ctx, &vtParams)
if err != nil {
return MySQLCompare{}, err
}
mysqlConn, err := mysql.Connect(ctx, &mysqlParams)
if err != nil {
return MySQLCompare{}, err
}
return MySQLCompare{
t: t,
MySQLConn: mysqlConn,
VtConn: vtConn,
}, nil
}
func (mcmp *MySQLCompare) Close() {
mcmp.VtConn.Close()
mcmp.MySQLConn.Close()
}
// AssertMatches executes the given query on both Vitess and MySQL and make sure
// they have the same result set. The result set of Vitess is then matched with the given expectation.
func (mcmp *MySQLCompare) AssertMatches(query, expected string) {
mcmp.t.Helper()
qr := mcmp.Exec(query)
got := fmt.Sprintf("%v", qr.Rows)
diff := cmp.Diff(expected, got)
if diff != "" {
mcmp.t.Errorf("Query: %s (-want +got):\n%s\nGot:%s", query, diff, got)
}
}
// AssertMatchesAny ensures the given query produces any one of the expected results.
func (mcmp *MySQLCompare) AssertMatchesAny(query string, expected ...string) {
mcmp.t.Helper()
qr := mcmp.Exec(query)
got := fmt.Sprintf("%v", qr.Rows)
for _, e := range expected {
diff := cmp.Diff(e, got)
if diff == "" {
return
}
}
mcmp.t.Errorf("Query: %s (-want +got):\n%v\nGot:%s", query, expected, got)
}
// AssertMatchesAnyNoCompare ensures the given query produces any one of the expected results.
// This method does not compare the mysql and vitess results together
func (mcmp *MySQLCompare) AssertMatchesAnyNoCompare(query string, expected ...string) {
mcmp.t.Helper()
mQr, vQr := mcmp.execNoCompare(query)
got := fmt.Sprintf("%v", mQr.Rows)
valid := false
for _, e := range expected {
diff := cmp.Diff(e, got)
if diff == "" {
valid = true
break
}
}
if !valid {
mcmp.t.Errorf("MySQL Query: %s (-want +got):\n%v\nGot:%s", query, expected, got)
}
valid = false
got = fmt.Sprintf("%v", vQr.Rows)
for _, e := range expected {
diff := cmp.Diff(e, got)
if diff == "" {
valid = true
break
}
}
if !valid {
mcmp.t.Errorf("Vitess Query: %s (-want +got):\n%v\nGot:%s", query, expected, got)
}
}
// AssertContainsError executes the query on both Vitess and MySQL.
// Both clients need to return an error. The error of Vitess must be matching the given expectation.
func (mcmp *MySQLCompare) AssertContainsError(query, expected string) {
mcmp.t.Helper()
_, err := mcmp.ExecAllowAndCompareError(query)
require.Error(mcmp.t, err)
assert.Contains(mcmp.t, err.Error(), expected, "actual error: %s", err.Error())
}
// AssertMatchesNoOrder executes the given query against both Vitess and MySQL.
// The test will be marked as failed if there is a mismatch between the two result sets.
func (mcmp *MySQLCompare) AssertMatchesNoOrder(query, expected string) {
mcmp.t.Helper()
qr := mcmp.Exec(query)
actual := fmt.Sprintf("%v", qr.Rows)
assert.Equal(mcmp.t, utils.SortString(expected), utils.SortString(actual), "for query: [%s] expected \n%s \nbut actual \n%s", query, expected, actual)
}
// AssertMatchesNoOrderInclColumnNames executes the given query against both Vitess and MySQL.
// The test will be marked as failed if there is a mismatch between the two result sets.
// This method also checks that the column names are the same and in the same order
func (mcmp *MySQLCompare) AssertMatchesNoOrderInclColumnNames(query, expected string) {
mcmp.t.Helper()
qr := mcmp.ExecWithColumnCompare(query)
actual := fmt.Sprintf("%v", qr.Rows)
assert.Equal(mcmp.t, utils.SortString(expected), utils.SortString(actual), "for query: [%s] expected \n%s \nbut actual \n%s", query, expected, actual)
}
// AssertIsEmpty executes the given query against both Vitess and MySQL and ensures
// their results match and are empty.
func (mcmp *MySQLCompare) AssertIsEmpty(query string) {
mcmp.t.Helper()
qr := mcmp.Exec(query)
assert.Empty(mcmp.t, qr.Rows, "for query: "+query)
}
// AssertFoundRowsValue executes the given query against both Vitess and MySQL.
// The results of that query must match between Vitess and MySQL, otherwise the test will be
// marked as failed. Once the query is executed, the test checks the value of `found_rows`,
// which must match the given `count` argument.
func (mcmp *MySQLCompare) AssertFoundRowsValue(query, workload string, count int) {
mcmp.Exec(query)
qr := mcmp.Exec("select found_rows()")
got := fmt.Sprintf("%v", qr.Rows)
want := fmt.Sprintf(`[[INT64(%d)]]`, count)
assert.Equalf(mcmp.t, want, got, "Workload: %s\nQuery:%s\n", workload, query)
}
// AssertMatchesNoCompare compares the record of mysql and vitess separately and not with each other.
func (mcmp *MySQLCompare) AssertMatchesNoCompare(query, mExp string, vExp string) {
mcmp.t.Helper()
mQr, vQr := mcmp.execNoCompare(query)
got := fmt.Sprintf("%v", mQr.Rows)
diff := cmp.Diff(mExp, got)
if diff != "" {
mcmp.t.Errorf("MySQL Query: %s (-want +got):\n%s\nGot:%s", query, diff, got)
}
got = fmt.Sprintf("%v", vQr.Rows)
diff = cmp.Diff(vExp, got)
if diff != "" {
mcmp.t.Errorf("Vitess Query: %s (-want +got):\n%s\nGot:%s", query, diff, got)
}
}
// Exec executes the given query against both Vitess and MySQL and compares
// the two result set. If there is a mismatch, the difference will be printed and the
// test will fail. If the query produces an error in either Vitess or MySQL, the test
// will be marked as failed.
// The result set of Vitess is returned to the caller.
func (mcmp *MySQLCompare) Exec(query string) *sqltypes.Result {
mcmp.t.Helper()
vtQr, err := mcmp.VtConn.ExecuteFetch(query, 1000, true)
require.NoError(mcmp.t, err, "[Vitess Error] for query: "+query)
mysqlQr, err := mcmp.MySQLConn.ExecuteFetch(query, 1000, true)
require.NoError(mcmp.t, err, "[MySQL Error] for query: "+query)
compareVitessAndMySQLResults(mcmp.t, query, vtQr, mysqlQr, false)
return vtQr
}
func (mcmp *MySQLCompare) execNoCompare(query string) (*sqltypes.Result, *sqltypes.Result) {
mcmp.t.Helper()
vtQr, err := mcmp.VtConn.ExecuteFetch(query, 1000, true)
require.NoError(mcmp.t, err, "[Vitess Error] for query: "+query)
mysqlQr, err := mcmp.MySQLConn.ExecuteFetch(query, 1000, true)
require.NoError(mcmp.t, err, "[MySQL Error] for query: "+query)
return mysqlQr, vtQr
}
// ExecWithColumnCompare executes the given query against both Vitess and MySQL and compares
// the two result set. If there is a mismatch, the difference will be printed and the
// test will fail. If the query produces an error in either Vitess or MySQL, the test
// will be marked as failed.
// The result set of Vitess is returned to the caller.
func (mcmp *MySQLCompare) ExecWithColumnCompare(query string) *sqltypes.Result {
mcmp.t.Helper()
vtQr, err := mcmp.VtConn.ExecuteFetch(query, 1000, true)
require.NoError(mcmp.t, err, "[Vitess Error] for query: "+query)
mysqlQr, err := mcmp.MySQLConn.ExecuteFetch(query, 1000, true)
require.NoError(mcmp.t, err, "[MySQL Error] for query: "+query)
compareVitessAndMySQLResults(mcmp.t, query, vtQr, mysqlQr, true)
return vtQr
}
// ExecAllowAndCompareError executes the query against both Vitess and MySQL.
// The test will pass if:
// - MySQL and Vitess both agree that there is an error
// - MySQL and Vitess did not find an error, but their results are matching
//
// The result set and error produced by Vitess are returned to the caller.
func (mcmp *MySQLCompare) ExecAllowAndCompareError(query string) (*sqltypes.Result, error) {
mcmp.t.Helper()
vtQr, vtErr := mcmp.VtConn.ExecuteFetch(query, 1000, true)
mysqlQr, mysqlErr := mcmp.MySQLConn.ExecuteFetch(query, 1000, true)
compareVitessAndMySQLErrors(mcmp.t, vtErr, mysqlErr)
// Since we allow errors, we don't want to compare results if one of the client failed.
// Vitess and MySQL should always be agreeing whether the query returns an error or not.
if vtErr == nil && mysqlErr == nil {
compareVitessAndMySQLResults(mcmp.t, query, vtQr, mysqlQr, false)
}
return vtQr, vtErr
}
// ExecAndIgnore executes the query against both Vitess and MySQL.
// Errors and results difference are ignored.
func (mcmp *MySQLCompare) ExecAndIgnore(query string) (*sqltypes.Result, error) {
mcmp.t.Helper()
_, _ = mcmp.MySQLConn.ExecuteFetch(query, 1000, true)
return mcmp.VtConn.ExecuteFetch(query, 1000, true)
}