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  }