vitess.io/vitess@v0.16.2/go/vt/vttablet/endtoend/framework/testcase.go (about)

     1  /*
     2  Copyright 2019 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 framework
    18  
    19  import (
    20  	"errors"
    21  	"fmt"
    22  	"reflect"
    23  	"strings"
    24  
    25  	"vitess.io/vitess/go/vt/vterrors"
    26  
    27  	"vitess.io/vitess/go/sqltypes"
    28  	querypb "vitess.io/vitess/go/vt/proto/query"
    29  )
    30  
    31  // Testable restricts the types that can be added to
    32  // a test case.
    33  type Testable interface {
    34  	Test(name string, client *QueryClient) error
    35  	Benchmark(client *QueryClient) error
    36  }
    37  
    38  var (
    39  	_ Testable = TestQuery("")
    40  	_ Testable = &TestCase{}
    41  	_ Testable = &MultiCase{}
    42  )
    43  
    44  // TestQuery represents a plain query. It will be
    45  // executed without a bind variable. The framework
    46  // will check for errors, but nothing beyond. Statements
    47  // like 'begin', etc. will be converted to the corresponding
    48  // transaction commands.
    49  type TestQuery string
    50  
    51  // Test executes the query and returns an error if it failed.
    52  func (tq TestQuery) Test(name string, client *QueryClient) error {
    53  	_, err := exec(client, string(tq), nil)
    54  	if err != nil {
    55  		if name == "" {
    56  			return err
    57  		}
    58  		return vterrors.Wrapf(err, "%s: Execute failed", name)
    59  	}
    60  	return nil
    61  }
    62  
    63  // Benchmark executes the query and discards the results
    64  func (tq TestQuery) Benchmark(client *QueryClient) error {
    65  	_, err := exec(client, string(tq), nil)
    66  	return err
    67  }
    68  
    69  // TestCase represents one test case. It will execute the
    70  // query and verify its results and effects against what
    71  // must be expected. Expected fields are optional.
    72  type TestCase struct {
    73  	// Name gives a name to the test case. It will be used
    74  	// for reporting failures.
    75  	Name string
    76  
    77  	// Query and BindVars are the input.
    78  	Query    string
    79  	BindVars map[string]*querypb.BindVariable
    80  
    81  	// Result is the list of rows that must be returned.
    82  	// It's represented as 2-d strings. They byte values
    83  	// will be compared against The bytes returned by the
    84  	// query. The check is skipped if Result is nil.
    85  	Result [][]string
    86  
    87  	// RowsAffected affected can be nil or an int.
    88  	RowsAffected any
    89  
    90  	// 	RowsReturned affected can be nil or an int.
    91  	RowsReturned any
    92  
    93  	// Rewritten specifies how the query should have be rewritten.
    94  	Rewritten []string
    95  
    96  	// Plan specifies the plan type that was used. It will be matched
    97  	// against tabletserver.PlanType(val).String().
    98  	Plan string
    99  
   100  	// If Table is specified, then the framework will validate the
   101  	// cache stats for that table. If the stat values are nil, then
   102  	// the check is skipped.
   103  	Table         string
   104  	Hits          any
   105  	Misses        any
   106  	Absent        any
   107  	Invalidations any
   108  }
   109  
   110  // Benchmark executes the test case and discards the results without verifying them
   111  func (tc *TestCase) Benchmark(client *QueryClient) error {
   112  	_, err := exec(client, tc.Query, tc.BindVars)
   113  	return err
   114  }
   115  
   116  // Test executes the test case and returns an error if it failed.
   117  // The name parameter is used if the test case doesn't have a name.
   118  func (tc *TestCase) Test(name string, client *QueryClient) error {
   119  	var errs []string
   120  	if tc.Name != "" {
   121  		name = tc.Name
   122  	}
   123  
   124  	// wait for all previous test cases to have been settled in cache
   125  	client.server.QueryPlanCacheWait()
   126  
   127  	catcher := NewQueryCatcher()
   128  	defer catcher.Close()
   129  
   130  	qr, err := exec(client, tc.Query, tc.BindVars)
   131  	if err != nil {
   132  		return vterrors.Wrapf(err, "%s: Execute failed", name)
   133  	}
   134  
   135  	if tc.Result != nil {
   136  		result := RowsToStrings(qr)
   137  		if !reflect.DeepEqual(result, tc.Result) {
   138  			errs = append(errs, fmt.Sprintf("Result mismatch:\n'%+v' does not match\n'%+v'", result, tc.Result))
   139  		}
   140  	}
   141  
   142  	if tc.RowsAffected != nil {
   143  		want := tc.RowsAffected.(int)
   144  		if int(qr.RowsAffected) != want {
   145  			errs = append(errs, fmt.Sprintf("RowsAffected mismatch: %d, want %d", int(qr.RowsAffected), want))
   146  		}
   147  	}
   148  
   149  	if tc.RowsReturned != nil {
   150  		want := tc.RowsReturned.(int)
   151  		if len(qr.Rows) != want {
   152  			errs = append(errs, fmt.Sprintf("RowsReturned mismatch: %d, want %d", len(qr.Rows), want))
   153  		}
   154  	}
   155  
   156  	queryInfo, err := catcher.Next()
   157  	if err != nil {
   158  		errs = append(errs, fmt.Sprintf("Query catcher failed: %v", err))
   159  	}
   160  	if tc.Rewritten != nil {
   161  		// Work-around for a quirk. The stream comments also contain
   162  		// "; ". So, we have to get rid of the additional artifacts
   163  		// to make it match expected results.
   164  		unstripped := strings.Split(queryInfo.RewrittenSQL(), "; ")
   165  		got := make([]string, 0, len(unstripped))
   166  		for _, str := range unstripped {
   167  			if str == "" || str == "*/" {
   168  				continue
   169  			}
   170  			got = append(got, str)
   171  		}
   172  		if !reflect.DeepEqual(got, tc.Rewritten) {
   173  			errs = append(errs, fmt.Sprintf("Rewritten mismatch:\n'%q' does not match\n'%q'", got, tc.Rewritten))
   174  		}
   175  	}
   176  	if tc.Plan != "" {
   177  		if queryInfo.PlanType != tc.Plan {
   178  			errs = append(errs, fmt.Sprintf("Plan mismatch: %s, want %s", queryInfo.PlanType, tc.Plan))
   179  		}
   180  	}
   181  	if len(errs) != 0 {
   182  		if name == "" {
   183  			return errors.New(strings.Join(errs, "\n"))
   184  		}
   185  		return errors.New(fmt.Sprintf("%s failed:\n", name) + strings.Join(errs, "\n"))
   186  	}
   187  	return nil
   188  }
   189  
   190  func exec(client *QueryClient, query string, bv map[string]*querypb.BindVariable) (*sqltypes.Result, error) {
   191  	switch query {
   192  	case "begin":
   193  		return nil, client.Begin(false)
   194  	case "commit":
   195  		return nil, client.Commit()
   196  	case "rollback":
   197  		return nil, client.Rollback()
   198  	}
   199  	return client.Execute(query, bv)
   200  }
   201  
   202  // RowsToStrings converts qr.Rows to [][]string.
   203  func RowsToStrings(qr *sqltypes.Result) [][]string {
   204  	var result [][]string
   205  	for _, row := range qr.Rows {
   206  		var srow []string
   207  		for _, cell := range row {
   208  			srow = append(srow, cell.ToString())
   209  		}
   210  		result = append(result, srow)
   211  	}
   212  	return result
   213  }
   214  
   215  // MultiCase groups a number of test cases under a name.
   216  // A MultiCase is also Testable. So, it can be recursive.
   217  type MultiCase struct {
   218  	Name  string
   219  	Cases []Testable
   220  }
   221  
   222  // Test executes the test cases in MultiCase. The test is
   223  // interrupted if there is a failure. The name parameter is
   224  // used if MultiCase doesn't have a Name.
   225  func (mc *MultiCase) Test(name string, client *QueryClient) error {
   226  	if mc.Name != "" {
   227  		name = mc.Name
   228  	}
   229  	for _, tcase := range mc.Cases {
   230  		if err := tcase.Test(name, client); err != nil {
   231  			client.Rollback()
   232  			return err
   233  		}
   234  	}
   235  	return nil
   236  }
   237  
   238  // Benchmark executes the test cases in MultiCase and discards the
   239  // results without validating them.
   240  func (mc *MultiCase) Benchmark(client *QueryClient) error {
   241  	for _, tcase := range mc.Cases {
   242  		if err := tcase.Benchmark(client); err != nil {
   243  			client.Rollback()
   244  			return err
   245  		}
   246  	}
   247  	return nil
   248  }