modernc.org/ql@v1.4.7/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 // import "modernc.org/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 fi, err := os.Stat("testdata.log") 283 if err != nil { 284 return 285 } 286 287 if fi.Mode()&0200 == 0 { // R/O 288 return 289 } 290 291 if _, err := logf.Seek(0, 0); err != nil { 292 t.Error(err) 293 return 294 } 295 296 dst, err := os.Create("testdata.log") 297 if err != nil { 298 t.Error(err) 299 return 300 } 301 302 if _, err := io.Copy(dst, logf); err != nil { 303 t.Error(err) 304 return 305 } 306 307 if err := dst.Close(); err != nil { 308 t.Error(err) 309 } 310 }() 311 } 312 313 nm := logf.Name() 314 if err := logf.Close(); err != nil { 315 t.Error(err) 316 } 317 318 if hasLogf { 319 if err := os.Remove(nm); err != nil { 320 t.Error(err) 321 } 322 } 323 }() 324 325 log := bufio.NewWriter(logf) 326 327 defer func() { 328 if err := log.Flush(); err != nil { 329 t.Error(err) 330 } 331 }() 332 333 max := len(testdata) 334 if n := *oM; n != 0 && n < max { 335 max = n 336 } 337 for itest, test := range testdata[*oN:max] { 338 var re *regexp.Regexp 339 a := strings.Split(test+"|", "|") 340 q, rset := a[0], strings.TrimSpace(a[1]) 341 var expErr string 342 if len(a) < 3 { 343 t.Error(itest, "internal error 066") 344 return 345 } 346 347 if expErr = a[2]; expErr != "" { 348 re = regexp.MustCompile("(?i:" + strings.TrimSpace(expErr) + ")") 349 } 350 351 q = strings.Replace(q, "∨", "|", -1) 352 q = strings.Replace(q, "⩖", "||", -1) 353 list, err := Compile(q) 354 if err != nil { 355 if !chk(itest, err, expErr, re) && *oFastFail { 356 return 357 } 358 359 continue 360 } 361 362 for _, s := range list.l { 363 if err := testMentionedColumns(s); err != nil { 364 t.Error(itest, err) 365 return 366 } 367 } 368 369 s1 := list.String() 370 list1, err := Compile(s1) 371 if err != nil { 372 t.Errorf("recreated source does not compile: %v\n---- orig\n%s\n---- recreated\n%s", err, q, s1) 373 if *oFastFail { 374 return 375 } 376 377 continue 378 } 379 380 s2 := list1.String() 381 if g, e := s2, s1; g != e { 382 t.Errorf("recreated source is not idempotent\n---- orig\n%s\n---- recreated1\n%s\n---- recreated2\n%s", q, s1, s2) 383 if *oFastFail { 384 return 385 } 386 387 continue 388 } 389 390 if !func() (ok bool) { 391 tnl0 := db.tnl 392 defer func() { 393 s3 := list.String() 394 if g, e := s1, s3; g != e { 395 t.Errorf("#%d: execution mutates compiled statement list\n---- orig\n%s----new\n%s", itest, g, e) 396 } 397 398 if !ok { 399 noErrors = false 400 } 401 402 if noErrors { 403 hdr := false 404 for _, v := range list.l { 405 s, err := explained(db, v, tctx) 406 if err != nil { 407 t.Error(err) 408 return 409 } 410 411 if !strings.HasPrefix(s, "┌") { 412 continue 413 } 414 415 if !hdr { 416 fmt.Fprintf(log, "---- %v\n", itest) 417 hdr = true 418 } 419 fmt.Fprintf(log, "%s\n", v) 420 fmt.Fprintf(log, "%s\n\n", s) 421 } 422 } 423 424 tnl := db.tnl 425 if tnl != tnl0 { 426 panic(fmt.Errorf("internal error 057: tnl0 %v, tnl %v", tnl0, tnl)) 427 } 428 429 nfo, err := db.Info() 430 if err != nil { 431 //dbg("", err) 432 panic(err) 433 } 434 435 for _, idx := range nfo.Indices { 436 //dbg("#%d: cleanup index %s", itest, idx.Name) 437 if _, _, err = db.run(tctx, fmt.Sprintf(` 438 BEGIN TRANSACTION; 439 DROP INDEX %s; 440 COMMIT; 441 `, 442 idx.Name)); err != nil { 443 t.Errorf("#%d: cleanup DROP INDEX %s: %v", itest, idx.Name, err) 444 ok = false 445 } 446 } 447 for _, tab := range nfo.Tables { 448 //dbg("#%d: cleanup table %s", itest, tab.Name) 449 if _, _, err = db.run(tctx, fmt.Sprintf(` 450 BEGIN TRANSACTION; 451 DROP table %s; 452 COMMIT; 453 `, 454 tab.Name)); err != nil { 455 t.Errorf("#%d: cleanup DROP TABLE %s: %v", itest, tab.Name, err) 456 ok = false 457 } 458 } 459 db.hasIndex2 = 0 460 }() 461 462 if err = s.mark(); err != nil { 463 t.Error(itest, err) 464 return 465 } 466 467 rs, _, err := db.Execute(tctx, list, int64(30)) 468 if err != nil { 469 return chk(itest, err, expErr, re) 470 } 471 472 if rs == nil { 473 t.Errorf("FAIL: %d: expected non nil Recordset or error %q", itest, expErr) 474 return 475 } 476 477 g, err := recSetDump(rs[len(rs)-1]) 478 if err != nil { 479 return chk(itest, err, expErr, re) 480 } 481 482 if expErr != "" { 483 t.Errorf("FAIL: %d: expected error %q", itest, expErr) 484 return 485 } 486 487 a = strings.Split(rset, "\n") 488 for i, v := range a { 489 a[i] = strings.TrimSpace(v) 490 } 491 e := strings.Join(a, "\n") 492 if g != e { 493 t.Errorf("FAIL: test # %d\n%s\n---- g\n%s\n---- e\n%s\n----", itest, q, g, e) 494 return 495 } 496 497 return true 498 }() && *oFastFail { 499 return 500 } 501 } 502 return 503 }