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