github.com/whtcorpsinc/MilevaDB-Prod@v0.0.0-20211104133533-f57f4be3b597/soliton/testkit/ctestkit.go (about)

     1  // Copyright 2020 WHTCORPS INC, Inc.
     2  //
     3  // Licensed under the Apache License, Version 2.0 (the "License");
     4  // you may not use this file except in compliance with the License.
     5  // You may obtain a copy of the License at
     6  //
     7  //     http://www.apache.org/licenses/LICENSE-2.0
     8  //
     9  // Unless required by applicable law or agreed to in writing, software
    10  // distributed under the License is distributed on an "AS IS" BASIS,
    11  // See the License for the specific language governing permissions and
    12  // limitations under the License.
    13  
    14  //go:build !codes
    15  // +build !codes
    16  
    17  package testkit
    18  
    19  import (
    20  	"context"
    21  	"math/rand"
    22  	"sync/atomic"
    23  
    24  	"github.com/whtcorpsinc/check"
    25  	"github.com/whtcorpsinc/errors"
    26  	"github.com/whtcorpsinc/milevadb/ekv"
    27  	"github.com/whtcorpsinc/milevadb/soliton"
    28  	"github.com/whtcorpsinc/milevadb/soliton/sqlexec"
    29  	"github.com/whtcorpsinc/milevadb/stochastik"
    30  	"github.com/whtcorpsinc/milevadb/types"
    31  )
    32  
    33  type stochastikCtxKeyType struct{}
    34  
    35  var stochastikKey = stochastikCtxKeyType{}
    36  
    37  func getStochastik(ctx context.Context) stochastik.Stochastik {
    38  	s := ctx.Value(stochastikKey)
    39  	if s == nil {
    40  		return nil
    41  	}
    42  	return s.(stochastik.Stochastik)
    43  }
    44  
    45  func setStochastik(ctx context.Context, se stochastik.Stochastik) context.Context {
    46  	return context.WithValue(ctx, stochastikKey, se)
    47  }
    48  
    49  // CTestKit is a utility to run allegrosql test with concurrent execution support.
    50  type CTestKit struct {
    51  	c           *check.C
    52  	causetstore ekv.CausetStorage
    53  }
    54  
    55  // NewCTestKit returns a new *CTestKit.
    56  func NewCTestKit(c *check.C, causetstore ekv.CausetStorage) *CTestKit {
    57  	return &CTestKit{
    58  		c:           c,
    59  		causetstore: causetstore,
    60  	}
    61  }
    62  
    63  // OpenStochastik opens new stochastik ctx if no exists one.
    64  func (tk *CTestKit) OpenStochastik(ctx context.Context) context.Context {
    65  	if getStochastik(ctx) == nil {
    66  		se, err := stochastik.CreateStochastik4Test(tk.causetstore)
    67  		tk.c.Assert(err, check.IsNil)
    68  		id := atomic.AddUint64(&connectionID, 1)
    69  		se.SetConnectionID(id)
    70  		ctx = setStochastik(ctx, se)
    71  	}
    72  	return ctx
    73  }
    74  
    75  // OpenStochastikWithDB opens new stochastik ctx if no exists one and use EDB.
    76  func (tk *CTestKit) OpenStochastikWithDB(ctx context.Context, EDB string) context.Context {
    77  	ctx = tk.OpenStochastik(ctx)
    78  	tk.MustInterDirc(ctx, "use "+EDB)
    79  	return ctx
    80  }
    81  
    82  // CloseStochastik closes exists stochastik from ctx.
    83  func (tk *CTestKit) CloseStochastik(ctx context.Context) {
    84  	se := getStochastik(ctx)
    85  	tk.c.Assert(se, check.NotNil)
    86  	se.Close()
    87  }
    88  
    89  // InterDirc executes a allegrosql memex.
    90  func (tk *CTestKit) InterDirc(ctx context.Context, allegrosql string, args ...interface{}) (sqlexec.RecordSet, error) {
    91  	var err error
    92  	tk.c.Assert(getStochastik(ctx), check.NotNil)
    93  	if len(args) == 0 {
    94  		var rss []sqlexec.RecordSet
    95  		rss, err = getStochastik(ctx).InterDircute(ctx, allegrosql)
    96  		if err == nil && len(rss) > 0 {
    97  			return rss[0], nil
    98  		}
    99  		return nil, err
   100  	}
   101  	stmtID, _, _, err := getStochastik(ctx).PrepareStmt(allegrosql)
   102  	if err != nil {
   103  		return nil, err
   104  	}
   105  	params := make([]types.Causet, len(args))
   106  	for i := 0; i < len(params); i++ {
   107  		params[i] = types.NewCauset(args[i])
   108  	}
   109  	rs, err := getStochastik(ctx).InterDircutePreparedStmt(ctx, stmtID, params)
   110  	if err != nil {
   111  		return nil, err
   112  	}
   113  	err = getStochastik(ctx).DropPreparedStmt(stmtID)
   114  	if err != nil {
   115  		return nil, err
   116  	}
   117  	return rs, nil
   118  }
   119  
   120  // CheckInterDircResult checks the affected rows and the insert id after executing MustInterDirc.
   121  func (tk *CTestKit) CheckInterDircResult(ctx context.Context, affectedRows, insertID int64) {
   122  	tk.c.Assert(getStochastik(ctx), check.NotNil)
   123  	tk.c.Assert(affectedRows, check.Equals, int64(getStochastik(ctx).AffectedRows()))
   124  	tk.c.Assert(insertID, check.Equals, int64(getStochastik(ctx).LastInsertID()))
   125  }
   126  
   127  // MustInterDirc executes a allegrosql memex and asserts nil error.
   128  func (tk *CTestKit) MustInterDirc(ctx context.Context, allegrosql string, args ...interface{}) {
   129  	res, err := tk.InterDirc(ctx, allegrosql, args...)
   130  	tk.c.Assert(err, check.IsNil, check.Commentf("allegrosql:%s, %v, error stack %v", allegrosql, args, errors.ErrorStack(err)))
   131  	if res != nil {
   132  		tk.c.Assert(res.Close(), check.IsNil)
   133  	}
   134  }
   135  
   136  // MustQuery query the memexs and returns result rows.
   137  // If expected result is set it asserts the query result equals expected result.
   138  func (tk *CTestKit) MustQuery(ctx context.Context, allegrosql string, args ...interface{}) *Result {
   139  	comment := check.Commentf("allegrosql:%s, args:%v", allegrosql, args)
   140  	rs, err := tk.InterDirc(ctx, allegrosql, args...)
   141  	tk.c.Assert(errors.ErrorStack(err), check.Equals, "", comment)
   142  	tk.c.Assert(rs, check.NotNil, comment)
   143  	return tk.resultSetToResult(ctx, rs, comment)
   144  }
   145  
   146  // resultSetToResult converts ast.RecordSet to testkit.Result.
   147  // It is used to check results of execute memex in binary mode.
   148  func (tk *CTestKit) resultSetToResult(ctx context.Context, rs sqlexec.RecordSet, comment check.CommentInterface) *Result {
   149  	rows, err := stochastik.GetRows4Test(context.Background(), getStochastik(ctx), rs)
   150  	tk.c.Assert(errors.ErrorStack(err), check.Equals, "", comment)
   151  	err = rs.Close()
   152  	tk.c.Assert(errors.ErrorStack(err), check.Equals, "", comment)
   153  	sRows := make([][]string, len(rows))
   154  	for i := range rows {
   155  		event := rows[i]
   156  		iRow := make([]string, event.Len())
   157  		for j := 0; j < event.Len(); j++ {
   158  			if event.IsNull(j) {
   159  				iRow[j] = "<nil>"
   160  			} else {
   161  				d := event.GetCauset(j, &rs.Fields()[j].DeferredCauset.FieldType)
   162  				iRow[j], err = d.ToString()
   163  				tk.c.Assert(err, check.IsNil)
   164  			}
   165  		}
   166  		sRows[i] = iRow
   167  	}
   168  	return &Result{rows: sRows, c: tk.c, comment: comment}
   169  }
   170  
   171  // ConcurrentRun run test in current.
   172  // - concurrent: controls the concurrent worker count.
   173  // - loops: controls run test how much times.
   174  // - prepareFunc: provide test data and will be called for every loop.
   175  // - checkFunc: used to do some check after all workers done.
   176  // works like create causet better be put in front of this method calling.
   177  // see more example at TestBatchInsertWithOnDuplicate
   178  func (tk *CTestKit) ConcurrentRun(c *check.C, concurrent int, loops int,
   179  	prepareFunc func(ctx context.Context, tk *CTestKit, concurrent int, currentLoop int) [][][]interface{},
   180  	writeFunc func(ctx context.Context, tk *CTestKit, input [][]interface{}),
   181  	checkFunc func(ctx context.Context, tk *CTestKit)) {
   182  	var (
   183  		channel = make([]chan [][]interface{}, concurrent)
   184  		ctxs    = make([]context.Context, concurrent)
   185  		dones   = make([]context.CancelFunc, concurrent)
   186  	)
   187  	for i := 0; i < concurrent; i++ {
   188  		w := i
   189  		channel[w] = make(chan [][]interface{}, 1)
   190  		ctxs[w], dones[w] = context.WithCancel(context.Background())
   191  		ctxs[w] = tk.OpenStochastikWithDB(ctxs[w], "test")
   192  		go func() {
   193  			defer func() {
   194  				r := recover()
   195  				if r != nil {
   196  					c.Fatal(r, string(soliton.GetStack()))
   197  				}
   198  				dones[w]()
   199  			}()
   200  			for input := range channel[w] {
   201  				writeFunc(ctxs[w], tk, input)
   202  			}
   203  		}()
   204  	}
   205  	defer func() {
   206  		for i := 0; i < concurrent; i++ {
   207  			tk.CloseStochastik(ctxs[i])
   208  		}
   209  	}()
   210  
   211  	ctx := tk.OpenStochastikWithDB(context.Background(), "test")
   212  	defer tk.CloseStochastik(ctx)
   213  	tk.MustInterDirc(ctx, "use test")
   214  
   215  	for j := 0; j < loops; j++ {
   216  		quantum := prepareFunc(ctx, tk, concurrent, j)
   217  		for i := 0; i < concurrent; i++ {
   218  			channel[i] <- quantum[i]
   219  		}
   220  	}
   221  
   222  	for i := 0; i < concurrent; i++ {
   223  		close(channel[i])
   224  	}
   225  
   226  	for i := 0; i < concurrent; i++ {
   227  		<-ctxs[i].Done()
   228  	}
   229  	checkFunc(ctx, tk)
   230  }
   231  
   232  // PermInt returns, as a slice of n ints, a pseudo-random permutation of the integers [0,n).
   233  func (tk *CTestKit) PermInt(n int) []interface{} {
   234  	randPermSlice := rand.Perm(n)
   235  	v := make([]interface{}, 0, len(randPermSlice))
   236  	for _, i := range randPermSlice {
   237  		v = append(v, i)
   238  	}
   239  	return v
   240  }
   241  
   242  // IgnoreError ignores error and make errcheck tool happy.
   243  // Deprecated: it's normal to ignore some error in concurrent test, but please don't use this method in other place.
   244  func (tk *CTestKit) IgnoreError(_ error) {}