vitess.io/vitess@v0.16.2/go/test/endtoend/utils/utils.go (about) 1 /* 2 Copyright 2021 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 "fmt" 21 "strings" 22 "testing" 23 "time" 24 25 "vitess.io/vitess/go/test/endtoend/cluster" 26 27 "vitess.io/vitess/go/test/utils" 28 29 "github.com/google/go-cmp/cmp" 30 "github.com/stretchr/testify/assert" 31 "github.com/stretchr/testify/require" 32 33 "vitess.io/vitess/go/mysql" 34 "vitess.io/vitess/go/sqltypes" 35 ) 36 37 // AssertContains ensures the given query result contains the expected results. 38 func AssertContains(t testing.TB, conn *mysql.Conn, query, expected string) { 39 t.Helper() 40 qr := Exec(t, conn, query) 41 got := fmt.Sprintf("%v", qr.Rows) 42 assert.Contains(t, got, expected, "Query: %s", query) 43 } 44 45 // AssertMatches ensures the given query produces the expected results. 46 func AssertMatches(t testing.TB, conn *mysql.Conn, query, expected string) { 47 t.Helper() 48 qr := Exec(t, conn, query) 49 got := fmt.Sprintf("%v", qr.Rows) 50 diff := cmp.Diff(expected, got) 51 if diff != "" { 52 t.Errorf("Query: %s (-want +got):\n%s\nGot:%s", query, diff, got) 53 } 54 } 55 56 // AssertMatchesContains ensures the given query produces the given substring. 57 func AssertMatchesContains(t testing.TB, conn *mysql.Conn, query string, substrings ...string) { 58 t.Helper() 59 qr := Exec(t, conn, query) 60 got := fmt.Sprintf("%v", qr.Rows) 61 for _, substring := range substrings { 62 if !strings.Contains(got, substring) { 63 t.Errorf("Query: %s Got:\n%s\nLooking for substring:%s", query, got, substring) 64 } 65 } 66 } 67 68 // AssertMatchesNotContains ensures the given query's output doesn't have the given substring. 69 func AssertMatchesNotContains(t testing.TB, conn *mysql.Conn, query string, substrings ...string) { 70 t.Helper() 71 qr := Exec(t, conn, query) 72 got := fmt.Sprintf("%v", qr.Rows) 73 for _, substring := range substrings { 74 if strings.Contains(got, substring) { 75 t.Errorf("Query: %s Got:\n%s\nFound substring:%s", query, got, substring) 76 } 77 } 78 } 79 80 // AssertMatchesAny ensures the given query produces any one of the expected results. 81 func AssertMatchesAny(t testing.TB, conn *mysql.Conn, query string, expected ...string) { 82 t.Helper() 83 qr := Exec(t, conn, query) 84 got := fmt.Sprintf("%v", qr.Rows) 85 for _, e := range expected { 86 diff := cmp.Diff(e, got) 87 if diff == "" { 88 return 89 } 90 } 91 t.Errorf("Query: %s (-want +got):\n%v\nGot:%s", query, expected, got) 92 } 93 94 // AssertMatchesCompareMySQL executes the given query on both Vitess and MySQL and make sure 95 // they have the same result set. The result set of Vitess is then matched with the given expectation. 96 func AssertMatchesCompareMySQL(t *testing.T, vtConn, mysqlConn *mysql.Conn, query, expected string) { 97 t.Helper() 98 qr := ExecCompareMySQL(t, vtConn, mysqlConn, query) 99 got := fmt.Sprintf("%v", qr.Rows) 100 diff := cmp.Diff(expected, got) 101 if diff != "" { 102 t.Errorf("Query: %s (-want +got):\n%s\nGot:%s", query, diff, got) 103 } 104 } 105 106 // AssertContainsError ensures that the given query returns a certain error. 107 func AssertContainsError(t *testing.T, conn *mysql.Conn, query, expected string) { 108 t.Helper() 109 _, err := ExecAllowError(t, conn, query) 110 require.Error(t, err) 111 assert.Contains(t, err.Error(), expected, "actual error: %s", err.Error()) 112 } 113 114 // AssertMatchesNoOrder executes the given query and makes sure it matches the given `expected` string. 115 // The order applied to the results or expectation is ignored. They are both re-sorted. 116 func AssertMatchesNoOrder(t *testing.T, conn *mysql.Conn, query, expected string) { 117 t.Helper() 118 qr := Exec(t, conn, query) 119 actual := fmt.Sprintf("%v", qr.Rows) 120 assert.Equal(t, utils.SortString(expected), utils.SortString(actual), "for query: [%s] expected \n%s \nbut actual \n%s", query, expected, actual) 121 } 122 123 // AssertIsEmpty ensures that the given query returns 0 row. 124 func AssertIsEmpty(t *testing.T, conn *mysql.Conn, query string) { 125 t.Helper() 126 qr := Exec(t, conn, query) 127 assert.Empty(t, qr.Rows, "for query: "+query) 128 } 129 130 func AssertSingleRowIsReturned(t *testing.T, conn *mysql.Conn, predicate string, expectedKs string) { 131 t.Run(predicate, func(t *testing.T) { 132 qr, err := conn.ExecuteFetch("SELECT distinct table_schema FROM information_schema.tables WHERE "+predicate, 1000, true) 133 require.NoError(t, err) 134 assert.Equal(t, 1, len(qr.Rows), "did not get enough rows back") 135 assert.Equal(t, expectedKs, qr.Rows[0][0].ToString()) 136 }) 137 } 138 139 func AssertResultIsEmpty(t *testing.T, conn *mysql.Conn, pre string) { 140 t.Run(pre, func(t *testing.T) { 141 qr, err := conn.ExecuteFetch("SELECT distinct table_schema FROM information_schema.tables WHERE "+pre, 1000, true) 142 require.NoError(t, err) 143 assert.Empty(t, qr.Rows) 144 }) 145 } 146 147 // Exec executes the given query using the given connection. The results are returned. 148 // The test fails if the query produces an error. 149 func Exec(t testing.TB, conn *mysql.Conn, query string) *sqltypes.Result { 150 t.Helper() 151 qr, err := conn.ExecuteFetch(query, 1000, true) 152 require.NoError(t, err, "for query: "+query) 153 return qr 154 } 155 156 // ExecCompareMySQL executes the given query against both Vitess and MySQL and compares 157 // the two result set. If there is a mismatch, the difference will be printed and the 158 // test will fail. If the query produces an error in either Vitess or MySQL, the test 159 // will be marked as failed. 160 // The result set of Vitess is returned to the caller. 161 func ExecCompareMySQL(t *testing.T, vtConn, mysqlConn *mysql.Conn, query string) *sqltypes.Result { 162 t.Helper() 163 vtQr, err := vtConn.ExecuteFetch(query, 1000, true) 164 require.NoError(t, err, "[Vitess Error] for query: "+query) 165 166 mysqlQr, err := mysqlConn.ExecuteFetch(query, 1000, true) 167 require.NoError(t, err, "[MySQL Error] for query: "+query) 168 compareVitessAndMySQLResults(t, query, vtQr, mysqlQr, false) 169 return vtQr 170 } 171 172 // ExecAllowError executes the given query without failing the test if it produces 173 // an error. The error is returned to the client, along with the result set. 174 func ExecAllowError(t *testing.T, conn *mysql.Conn, query string) (*sqltypes.Result, error) { 175 t.Helper() 176 return conn.ExecuteFetch(query, 1000, true) 177 } 178 179 // SkipIfBinaryIsBelowVersion skips the given test if the binary's major version is below majorVersion. 180 func SkipIfBinaryIsBelowVersion(t *testing.T, majorVersion int, binary string) { 181 version, err := cluster.GetMajorVersion(binary) 182 if err != nil { 183 return 184 } 185 if version < majorVersion { 186 t.Skip("Current version of ", binary, ": v", version, ", expected version >= v", majorVersion) 187 } 188 } 189 190 // AssertMatchesWithTimeout asserts that the given query produces the expected result. 191 // The query will be executed every 'r' duration until it matches the expected result. 192 // If after 'd' duration we still did not find the expected result, the test will be marked as failed. 193 func AssertMatchesWithTimeout(t *testing.T, conn *mysql.Conn, query, expected string, r time.Duration, d time.Duration, failureMsg string) { 194 t.Helper() 195 timeout := time.After(d) 196 diff := "actual and expectation does not match" 197 for len(diff) > 0 { 198 select { 199 case <-timeout: 200 require.Fail(t, failureMsg, diff) 201 case <-time.After(r): 202 qr, err := ExecAllowError(t, conn, query) 203 if err != nil { 204 diff = err.Error() 205 break 206 } 207 diff = cmp.Diff(expected, 208 fmt.Sprintf("%v", qr.Rows)) 209 } 210 211 } 212 } 213 214 // WaitForAuthoritative waits for a table to become authoritative 215 func WaitForAuthoritative(t *testing.T, vtgateProcess cluster.VtgateProcess, ks, tbl string) error { 216 timeout := time.After(10 * time.Second) 217 for { 218 select { 219 case <-timeout: 220 return fmt.Errorf("schema tracking didn't mark table t2 as authoritative until timeout") 221 default: 222 time.Sleep(1 * time.Second) 223 res, err := vtgateProcess.ReadVSchema() 224 require.NoError(t, err, res) 225 t2Map := getTableT2Map(res, ks, tbl) 226 authoritative, fieldPresent := t2Map["column_list_authoritative"] 227 if !fieldPresent { 228 continue 229 } 230 authoritativeBool, isBool := authoritative.(bool) 231 if !isBool || !authoritativeBool { 232 continue 233 } 234 return nil 235 } 236 } 237 } 238 239 // WaitForColumn waits for a table's column to be present 240 func WaitForColumn(t *testing.T, vtgateProcess cluster.VtgateProcess, ks, tbl, col string) error { 241 timeout := time.After(10 * time.Second) 242 for { 243 select { 244 case <-timeout: 245 return fmt.Errorf("schema tracking did not find column '%s' in table '%s'", col, tbl) 246 default: 247 time.Sleep(1 * time.Second) 248 res, err := vtgateProcess.ReadVSchema() 249 require.NoError(t, err, res) 250 t2Map := getTableT2Map(res, ks, tbl) 251 authoritative, fieldPresent := t2Map["column_list_authoritative"] 252 if !fieldPresent { 253 break 254 } 255 authoritativeBool, isBool := authoritative.(bool) 256 if !isBool || !authoritativeBool { 257 break 258 } 259 colMap, exists := t2Map["columns"] 260 if !exists { 261 break 262 } 263 colList, isSlice := colMap.([]interface{}) 264 if !isSlice { 265 break 266 } 267 for _, c := range colList { 268 colDef, isMap := c.(map[string]interface{}) 269 if !isMap { 270 break 271 } 272 if colName, exists := colDef["name"]; exists && colName == col { 273 return nil 274 } 275 } 276 } 277 } 278 } 279 280 func getTableT2Map(res *interface{}, ks, tbl string) map[string]interface{} { 281 step1 := convertToMap(*res)["keyspaces"] 282 step2 := convertToMap(step1)[ks] 283 step3 := convertToMap(step2)["tables"] 284 tblMap := convertToMap(step3)[tbl] 285 return convertToMap(tblMap) 286 } 287 288 func convertToMap(input interface{}) map[string]interface{} { 289 output := input.(map[string]interface{}) 290 return output 291 } 292 293 // TimeoutAction performs the action within the given timeout limit. 294 // If the timeout is reached, the test is failed with errMsg. 295 // If action returns false, the timeout loop continues, if it returns true, the function succeeds. 296 func TimeoutAction(t *testing.T, timeout time.Duration, errMsg string, action func() bool) { 297 deadline := time.After(timeout) 298 ok := false 299 for !ok { 300 select { 301 case <-deadline: 302 t.Error(errMsg) 303 case <-time.After(1 * time.Second): 304 ok = action() 305 } 306 } 307 }