github.com/runner-mei/ql@v1.1.0/storage_test.go (about) 1 // Copyright (c) 2014 ql Authors. All rights reserved. 2 // Use of this source code is governed by a BSD-style 3 // license that can be found in the LICENSE file. 4 5 package ql 6 7 import ( 8 "bufio" 9 "bytes" 10 "flag" 11 "fmt" 12 "io" 13 "io/ioutil" 14 "log" 15 "os" 16 "regexp" 17 "runtime/debug" 18 "strings" 19 "testing" 20 ) 21 22 var ( 23 oN = flag.Int("N", 0, "") 24 oM = flag.Int("M", 0, "") 25 oFastFail = flag.Bool("fastFail", false, "") 26 oSlow = flag.Bool("slow", false, "Do not wrap storage tests in a single outer transaction, write everything to disk file. Very slow.") 27 ) 28 29 var testdata []string 30 31 func init() { 32 tests, err := ioutil.ReadFile("testdata.ql") 33 if err != nil { 34 log.Panic(err) 35 } 36 37 a := bytes.Split(tests, []byte("\n-- ")) 38 pre := []byte("-- ") 39 pres := []byte("S ") 40 for _, v := range a[1:] { 41 switch { 42 case bytes.HasPrefix(v, pres): 43 v = append(pre, v...) 44 v = append([]byte(sample), v...) 45 default: 46 v = append(pre, v...) 47 } 48 testdata = append(testdata, string(v)) 49 } 50 } 51 52 func typeof(v interface{}) (r int) { //NTYPE 53 switch v.(type) { 54 case bool: 55 return qBool 56 case complex64: 57 return qComplex64 58 case complex128: 59 return qComplex128 60 case float32: 61 return qFloat32 62 case float64: 63 return qFloat64 64 case int8: 65 return qInt8 66 case int16: 67 return qInt16 68 case int32: 69 return qInt32 70 case int64: 71 return qInt64 72 case string: 73 return qString 74 case uint8: 75 return qUint8 76 case uint16: 77 return qUint16 78 case uint32: 79 return qUint32 80 case uint64: 81 return qUint64 82 } 83 return 84 } 85 86 func stypeof(nm string, val interface{}) string { 87 if t := typeof(val); t != 0 { 88 return fmt.Sprintf("%c%s", t, nm) 89 } 90 91 switch val.(type) { 92 case idealComplex: 93 return fmt.Sprintf("c%s", nm) 94 case idealFloat: 95 return fmt.Sprintf("f%s", nm) 96 case idealInt: 97 return fmt.Sprintf("l%s", nm) 98 case idealRune: 99 return fmt.Sprintf("k%s", nm) 100 case idealUint: 101 return fmt.Sprintf("x%s", nm) 102 default: 103 return fmt.Sprintf("?%s", nm) 104 } 105 } 106 107 func dumpCols(cols []*col) string { 108 a := []string{} 109 for _, col := range cols { 110 a = append(a, fmt.Sprintf("%d:%s %s", col.index, col.name, typeStr(col.typ))) 111 } 112 return strings.Join(a, ",") 113 } 114 115 func dumpFlds(flds []*fld) string { 116 a := []string{} 117 for _, fld := range flds { 118 a = append(a, fmt.Sprintf("%s AS %s", fld.expr, fld.name)) 119 } 120 return strings.Join(a, ",") 121 } 122 123 func recSetDump(rs Recordset) (s string, err error) { 124 recset := rs.(recordset) 125 p := recset.plan 126 a0 := append([]string(nil), p.fieldNames()...) 127 for i, v := range a0 { 128 a0[i] = fmt.Sprintf("%q", v) 129 } 130 a := []string{strings.Join(a0, ", ")} 131 if err := p.do(recset.ctx, func(id interface{}, data []interface{}) (bool, error) { 132 if err = expand(data); err != nil { 133 return false, err 134 } 135 136 a = append(a, fmt.Sprintf("%v", data)) 137 return true, nil 138 }); err != nil { 139 return "", err 140 } 141 return strings.Join(a, "\n"), nil 142 } 143 144 // http://en.wikipedia.org/wiki/Join_(SQL)#Sample_tables 145 const sample = ` 146 BEGIN TRANSACTION; 147 CREATE TABLE department ( 148 DepartmentID int, 149 DepartmentName string, 150 ); 151 152 INSERT INTO department VALUES 153 (31, "Sales"), 154 (33, "Engineering"), 155 (34, "Clerical"), 156 (35, "Marketing"), 157 ; 158 159 CREATE TABLE employee ( 160 LastName string, 161 DepartmentID int, 162 ); 163 164 INSERT INTO employee VALUES 165 ("Rafferty", 31), 166 ("Jones", 33), 167 ("Heisenberg", 33), 168 ("Robinson", 34), 169 ("Smith", 34), 170 ("Williams", NULL), 171 ; 172 COMMIT; 173 ` 174 175 func explained(db *DB, s stmt, tctx *TCtx) (string, error) { 176 src := "explain " + s.String() 177 rs, _, err := db.Run(tctx, src, int64(30)) 178 if err != nil { 179 return "", err 180 } 181 182 rows, err := rs[0].Rows(-1, 0) 183 if err != nil { 184 return "", err 185 } 186 187 if !strings.HasPrefix(rows[0][0].(string), "┌") { 188 return "", nil 189 } 190 191 var a []string 192 for _, v := range rows { 193 a = append(a, v[0].(string)) 194 } 195 return strings.Join(a, "\n"), nil 196 } 197 198 // Test provides a testing facility for alternative storage implementations. 199 // The s.setup should return a freshly created and empty storage. Removing the 200 // store from the system is the responsibility of the caller. The test only 201 // guarantees not to panic on recoverable errors and return an error instead. 202 // Test errors are not returned but reported to t. 203 func test(t *testing.T, s testDB) (panicked error) { 204 defer func() { 205 if e := recover(); e != nil { 206 switch x := e.(type) { 207 case error: 208 panicked = x 209 default: 210 panicked = fmt.Errorf("%v", e) 211 } 212 } 213 if panicked != nil { 214 t.Errorf("PANIC: %v\n%s", panicked, debug.Stack()) 215 } 216 }() 217 218 db, err := s.setup() 219 if err != nil { 220 t.Error(err) 221 return 222 } 223 224 tctx := NewRWCtx() 225 if !*oSlow { 226 if _, _, err := db.Execute(tctx, txBegin); err != nil { 227 t.Error(err) 228 return nil 229 } 230 } 231 232 if err = s.mark(); err != nil { 233 t.Error(err) 234 return 235 } 236 237 defer func() { 238 x := tctx 239 if *oSlow { 240 x = nil 241 } 242 if err = s.teardown(x); err != nil { 243 t.Error(err) 244 } 245 }() 246 247 chk := func(test int, err error, expErr string, re *regexp.Regexp) (ok bool) { 248 s := err.Error() 249 if re == nil { 250 t.Error("FAIL: ", test, s) 251 return false 252 } 253 254 if !re.MatchString(s) { 255 t.Error("FAIL: ", test, "error doesn't match:", s, "expected", expErr) 256 return false 257 } 258 259 return true 260 } 261 262 var logf *os.File 263 hasLogf := false 264 noErrors := true 265 if _, ok := s.(*memTestDB); ok { 266 if logf, err = ioutil.TempFile("", "ql-test-log-"); err != nil { 267 t.Error(err) 268 return nil 269 } 270 271 hasLogf = true 272 } else { 273 if logf, err = os.Create(os.DevNull); err != nil { 274 t.Error(err) 275 return nil 276 } 277 } 278 279 defer func() { 280 if hasLogf && noErrors { 281 func() { 282 if _, err := logf.Seek(0, 0); err != nil { 283 t.Error(err) 284 return 285 } 286 287 dst, err := os.Create("testdata.log") 288 if err != nil { 289 t.Error(err) 290 return 291 } 292 293 if _, err := io.Copy(dst, logf); err != nil { 294 t.Error(err) 295 return 296 } 297 298 if err := dst.Close(); err != nil { 299 t.Error(err) 300 } 301 }() 302 } 303 304 nm := logf.Name() 305 if err := logf.Close(); err != nil { 306 t.Error(err) 307 } 308 309 if hasLogf { 310 if err := os.Remove(nm); err != nil { 311 t.Error(err) 312 } 313 } 314 }() 315 316 log := bufio.NewWriter(logf) 317 318 defer func() { 319 if err := log.Flush(); err != nil { 320 t.Error(err) 321 } 322 }() 323 324 max := len(testdata) 325 if n := *oM; n != 0 && n < max { 326 max = n 327 } 328 for itest, test := range testdata[*oN:max] { 329 //dbg("------------------------------------------------------------- ( itest %d ) ----", itest) 330 var re *regexp.Regexp 331 a := strings.Split(test+"|", "|") 332 q, rset := a[0], strings.TrimSpace(a[1]) 333 var expErr string 334 if len(a) < 3 { 335 t.Error(itest, "internal error 066") 336 return 337 } 338 339 if expErr = a[2]; expErr != "" { 340 re = regexp.MustCompile("(?i:" + strings.TrimSpace(expErr) + ")") 341 } 342 343 q = strings.Replace(q, "∨", "|", -1) 344 q = strings.Replace(q, "⩖", "||", -1) 345 list, err := Compile(q) 346 if err != nil { 347 if !chk(itest, err, expErr, re) && *oFastFail { 348 return 349 } 350 351 continue 352 } 353 354 for _, s := range list.l { 355 if err := testMentionedColumns(s); err != nil { 356 t.Error(itest, err) 357 return 358 } 359 } 360 361 s1 := list.String() 362 list1, err := Compile(s1) 363 if err != nil { 364 t.Errorf("recreated source does not compile: %v\n---- orig\n%s\n---- recreated\n%s", err, q, s1) 365 if *oFastFail { 366 return 367 } 368 369 continue 370 } 371 372 s2 := list1.String() 373 if g, e := s2, s1; g != e { 374 t.Errorf("recreated source is not idempotent\n---- orig\n%s\n---- recreated1\n%s\n---- recreated2\n%s", q, s1, s2) 375 if *oFastFail { 376 return 377 } 378 379 continue 380 } 381 382 if !func() (ok bool) { 383 tnl0 := db.tnl 384 defer func() { 385 s3 := list.String() 386 if g, e := s1, s3; g != e { 387 t.Errorf("#%d: execution mutates compiled statement list\n---- orig\n%s----new\n%s", itest, g, e) 388 } 389 390 if !ok { 391 noErrors = false 392 } 393 394 if noErrors { 395 hdr := false 396 for _, v := range list.l { 397 s, err := explained(db, v, tctx) 398 if err != nil { 399 t.Error(err) 400 return 401 } 402 403 if !strings.HasPrefix(s, "┌") { 404 continue 405 } 406 407 if !hdr { 408 fmt.Fprintf(log, "---- %v\n", itest) 409 hdr = true 410 } 411 fmt.Fprintf(log, "%s\n", v) 412 fmt.Fprintf(log, "%s\n\n", s) 413 } 414 } 415 416 tnl := db.tnl 417 if tnl != tnl0 { 418 panic(fmt.Errorf("internal error 057: tnl0 %v, tnl %v", tnl0, tnl)) 419 } 420 421 nfo, err := db.Info() 422 if err != nil { 423 dbg("", err) 424 panic(err) 425 } 426 427 for _, idx := range nfo.Indices { 428 //dbg("#%d: cleanup index %s", itest, idx.Name) 429 if _, _, err = db.run(tctx, fmt.Sprintf(` 430 BEGIN TRANSACTION; 431 DROP INDEX %s; 432 COMMIT; 433 `, 434 idx.Name)); err != nil { 435 t.Errorf("#%d: cleanup DROP INDEX %s: %v", itest, idx.Name, err) 436 ok = false 437 } 438 } 439 for _, tab := range nfo.Tables { 440 //dbg("#%d: cleanup table %s", itest, tab.Name) 441 if _, _, err = db.run(tctx, fmt.Sprintf(` 442 BEGIN TRANSACTION; 443 DROP table %s; 444 COMMIT; 445 `, 446 tab.Name)); err != nil { 447 t.Errorf("#%d: cleanup DROP TABLE %s: %v", itest, tab.Name, err) 448 ok = false 449 } 450 } 451 db.hasIndex2 = 0 452 }() 453 454 if err = s.mark(); err != nil { 455 t.Error(err) 456 return 457 } 458 459 rs, _, err := db.Execute(tctx, list, int64(30)) 460 if err != nil { 461 return chk(itest, err, expErr, re) 462 } 463 464 if rs == nil { 465 t.Errorf("FAIL: %d: expected non nil Recordset or error %q", itest, expErr) 466 return 467 } 468 469 g, err := recSetDump(rs[len(rs)-1]) 470 if err != nil { 471 return chk(itest, err, expErr, re) 472 } 473 474 if expErr != "" { 475 t.Errorf("FAIL: %d: expected error %q", itest, expErr) 476 return 477 } 478 479 a = strings.Split(rset, "\n") 480 for i, v := range a { 481 a[i] = strings.TrimSpace(v) 482 } 483 e := strings.Join(a, "\n") 484 if g != e { 485 t.Errorf("FAIL: test # %d\n%s\n---- g\n%s\n---- e\n%s\n----", itest, q, g, e) 486 return 487 } 488 489 return true 490 }() && *oFastFail { 491 return 492 } 493 } 494 return 495 }