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) {}