github.com/cockroachdb/cockroach@v20.2.0-alpha.1+incompatible/pkg/sql/tests/rsg_test.go (about) 1 // Copyright 2016 The Cockroach Authors. 2 // 3 // Use of this software is governed by the Business Source License 4 // included in the file licenses/BSL.txt. 5 // 6 // As of the Change Date specified in that file, in accordance with 7 // the Business Source License, use of this software will be governed 8 // by the Apache License, Version 2.0, included in the file 9 // licenses/APL.txt. 10 11 package tests_test 12 13 import ( 14 "context" 15 gosql "database/sql" 16 "flag" 17 "fmt" 18 "io/ioutil" 19 "math/rand" 20 "path/filepath" 21 "regexp" 22 "runtime" 23 "strings" 24 "testing" 25 "time" 26 27 // Enable CCL statements. 28 _ "github.com/cockroachdb/cockroach/pkg/ccl" 29 "github.com/cockroachdb/cockroach/pkg/ccl/utilccl" 30 "github.com/cockroachdb/cockroach/pkg/internal/rsg" 31 "github.com/cockroachdb/cockroach/pkg/internal/sqlsmith" 32 "github.com/cockroachdb/cockroach/pkg/sql" 33 "github.com/cockroachdb/cockroach/pkg/sql/parser" 34 "github.com/cockroachdb/cockroach/pkg/sql/pgwire/pgcode" 35 "github.com/cockroachdb/cockroach/pkg/sql/sem/builtins" 36 "github.com/cockroachdb/cockroach/pkg/sql/sem/tree" 37 "github.com/cockroachdb/cockroach/pkg/sql/tests" 38 "github.com/cockroachdb/cockroach/pkg/sql/types" 39 "github.com/cockroachdb/cockroach/pkg/testutils/serverutils" 40 "github.com/cockroachdb/cockroach/pkg/util/ctxgroup" 41 "github.com/cockroachdb/cockroach/pkg/util/leaktest" 42 "github.com/cockroachdb/cockroach/pkg/util/syncutil" 43 "github.com/cockroachdb/cockroach/pkg/util/timeutil" 44 "github.com/cockroachdb/errors" 45 "github.com/lib/pq" 46 ) 47 48 var ( 49 flagRSGTime = flag.Duration("rsg", 0, "random syntax generator test duration") 50 flagRSGGoRoutines = flag.Int("rsg-routines", 1, "number of Go routines executing random statements in each RSG test") 51 flagRSGExecTimeout = flag.Duration("rsg-exec-timeout", 15*time.Second, "timeout duration when executing a statement") 52 ) 53 54 func verifyFormat(sql string) error { 55 stmts, err := parser.Parse(sql) 56 if err != nil { 57 // Cannot serialize a statement list without parsing it. 58 return nil //nolint:returnerrcheck 59 } 60 formattedSQL := stmts.StringWithFlags(tree.FmtShowPasswords) 61 formattedStmts, err := parser.Parse(formattedSQL) 62 if err != nil { 63 return errors.Wrapf(err, "cannot parse output of Format: sql=%q, formattedSQL=%q", sql, formattedSQL) 64 } 65 formattedFormattedSQL := formattedStmts.StringWithFlags(tree.FmtShowPasswords) 66 if formattedSQL != formattedFormattedSQL { 67 return errors.Errorf("Parse followed by Format is not idempotent: %q -> %q != %q", sql, formattedSQL, formattedFormattedSQL) 68 } 69 // TODO(eisen): ensure that the reconstituted SQL not only parses but also has 70 // the same meaning as the original. 71 return nil 72 } 73 74 type verifyFormatDB struct { 75 db *gosql.DB 76 verifyFormatErr error 77 mu struct { 78 syncutil.Mutex 79 // active holds the currently executing statements. 80 active map[string]int 81 } 82 } 83 84 // Incr records sql in the active map and returns a func to decrement it. 85 func (db *verifyFormatDB) Incr(sql string) func() { 86 db.mu.Lock() 87 if db.mu.active == nil { 88 db.mu.active = make(map[string]int) 89 } 90 db.mu.active[sql]++ 91 db.mu.Unlock() 92 93 return func() { 94 db.mu.Lock() 95 db.mu.active[sql]-- 96 if db.mu.active[sql] == 0 { 97 delete(db.mu.active, sql) 98 } 99 db.mu.Unlock() 100 } 101 } 102 103 type crasher struct { 104 sql string 105 err error 106 detail string 107 } 108 109 func (c *crasher) Error() string { 110 return fmt.Sprintf("server panic: %s", c.err) 111 } 112 113 type nonCrasher struct { 114 sql string 115 err error 116 } 117 118 func (c *nonCrasher) Error() string { 119 return c.err.Error() 120 } 121 122 func (db *verifyFormatDB) exec(ctx context.Context, sql string) error { 123 if err := verifyFormat(sql); err != nil { 124 db.verifyFormatErr = err 125 return err 126 } 127 128 defer db.Incr(sql)() 129 130 funcdone := make(chan error, 1) 131 go func() { 132 _, err := db.db.ExecContext(ctx, sql) 133 funcdone <- err 134 }() 135 select { 136 case err := <-funcdone: 137 if err != nil { 138 if pqerr := (*pq.Error)(nil); errors.As(err, &pqerr) { 139 // Output Postgres error code if it's available. 140 if pqerr.Code == pgcode.CrashShutdown { 141 return &crasher{ 142 sql: sql, 143 err: err, 144 detail: pqerr.Detail, 145 } 146 } 147 } 148 if es := err.Error(); strings.Contains(es, "internal error") || 149 strings.Contains(es, "driver: bad connection") || 150 strings.Contains(es, "unexpected error inside CockroachDB") { 151 return &crasher{ 152 sql: sql, 153 err: err, 154 } 155 } 156 return &nonCrasher{sql: sql, err: err} 157 } 158 return nil 159 case <-time.After(*flagRSGExecTimeout): 160 db.mu.Lock() 161 defer db.mu.Unlock() 162 b := make([]byte, 1024*1024) 163 n := runtime.Stack(b, true) 164 fmt.Printf("%s\n", b[:n]) 165 // Now see if we can execute a SELECT 1. This is useful because sometimes an 166 // exec timeout is because of a slow-executing statement, and other times 167 // it's because the server is completely wedged. This is an automated way 168 // to find out. 169 errch := make(chan error, 1) 170 go func() { 171 rows, err := db.db.Query(`SELECT 1`) 172 if err == nil { 173 rows.Close() 174 } 175 errch <- err 176 }() 177 select { 178 case <-time.After(5 * time.Second): 179 fmt.Println("SELECT 1 timeout: probably a wedged server") 180 case err := <-errch: 181 if err != nil { 182 fmt.Println("SELECT 1 execute error:", err) 183 } else { 184 fmt.Println("SELECT 1 executed successfully: probably a slow statement") 185 } 186 } 187 fmt.Printf("timeout: %q. currently executing: %v\n", sql, db.mu.active) 188 panic("statement exec timeout") 189 } 190 } 191 192 func TestRandomSyntaxGeneration(t *testing.T) { 193 defer leaktest.AfterTest(t)() 194 195 const rootStmt = "stmt" 196 197 testRandomSyntax(t, false, "ident", nil, func(ctx context.Context, db *verifyFormatDB, r *rsg.RSG) error { 198 s := r.Generate(rootStmt, 20) 199 // Don't start transactions since closing them is tricky. Just issuing a 200 // ROLLBACK after all queries doesn't work due to the parellel uses of db, 201 // which can start another immediately after the ROLLBACK and cause problems 202 // for the following statement. The CREATE DATABASE below would fail with 203 // errors about an aborted transaction and thus panic. 204 if strings.HasPrefix(s, "BEGIN") || strings.HasPrefix(s, "START") { 205 return errors.New("transactions are unsupported") 206 } 207 if strings.HasPrefix(s, "SET SESSION CHARACTERISTICS AS TRANSACTION") { 208 return errors.New("setting session characteristics is unsupported") 209 } 210 if strings.Contains(s, "READ ONLY") || strings.Contains(s, "read_only") { 211 return errors.New("READ ONLY settings are unsupported") 212 } 213 if strings.Contains(s, "REVOKE") || strings.Contains(s, "GRANT") { 214 return errors.New("REVOKE and GRANT are unsupported") 215 } 216 if strings.Contains(s, "EXPERIMENTAL SCRUB DATABASE SYSTEM") { 217 return errors.New("See #43693") 218 } 219 // Recreate the database on every run in case it was dropped or renamed in 220 // a previous run. Should always succeed. 221 if err := db.exec(ctx, `CREATE DATABASE IF NOT EXISTS ident`); err != nil { 222 return err 223 } 224 return db.exec(ctx, s) 225 }) 226 } 227 228 func TestRandomSyntaxSelect(t *testing.T) { 229 defer leaktest.AfterTest(t)() 230 231 const rootStmt = "target_list" 232 233 testRandomSyntax(t, false, "ident", func(ctx context.Context, db *verifyFormatDB, r *rsg.RSG) error { 234 return db.exec(ctx, `CREATE DATABASE IF NOT EXISTS ident; CREATE TABLE IF NOT EXISTS ident.ident (ident decimal);`) 235 }, func(ctx context.Context, db *verifyFormatDB, r *rsg.RSG) error { 236 targets := r.Generate(rootStmt, 300) 237 var where, from string 238 // Only generate complex clauses half the time. 239 if rand.Intn(2) == 0 { 240 where = r.Generate("where_clause", 300) 241 from = r.Generate("from_clause", 300) 242 } else { 243 from = "FROM ident" 244 } 245 s := fmt.Sprintf("SELECT %s %s %s", targets, from, where) 246 return db.exec(ctx, s) 247 }) 248 } 249 250 type namedBuiltin struct { 251 name string 252 builtin tree.Overload 253 } 254 255 func TestRandomSyntaxFunctions(t *testing.T) { 256 defer leaktest.AfterTest(t)() 257 258 done := make(chan struct{}) 259 defer close(done) 260 namedBuiltinChan := make(chan namedBuiltin) 261 go func() { 262 for { 263 for _, name := range builtins.AllBuiltinNames { 264 lower := strings.ToLower(name) 265 if strings.HasPrefix(lower, "crdb_internal.force_") { 266 continue 267 } 268 switch lower { 269 case 270 "pg_sleep": 271 continue 272 } 273 _, variations := builtins.GetBuiltinProperties(name) 274 for _, builtin := range variations { 275 select { 276 case <-done: 277 return 278 case namedBuiltinChan <- namedBuiltin{name: name, builtin: builtin}: 279 } 280 } 281 } 282 } 283 }() 284 285 testRandomSyntax(t, false, "defaultdb", nil, func(ctx context.Context, db *verifyFormatDB, r *rsg.RSG) error { 286 nb := <-namedBuiltinChan 287 var args []string 288 switch ft := nb.builtin.Types.(type) { 289 case tree.ArgTypes: 290 for _, arg := range ft { 291 args = append(args, r.GenerateRandomArg(arg.Typ)) 292 } 293 case tree.HomogeneousType: 294 for i := r.Intn(5); i > 0; i-- { 295 var typ *types.T 296 switch r.Intn(4) { 297 case 0: 298 typ = types.String 299 case 1: 300 typ = types.Float 301 case 2: 302 typ = types.Bool 303 case 3: 304 typ = types.TimestampTZ 305 } 306 args = append(args, r.GenerateRandomArg(typ)) 307 } 308 case tree.VariadicType: 309 for _, t := range ft.FixedTypes { 310 args = append(args, r.GenerateRandomArg(t)) 311 } 312 for i := r.Intn(5); i > 0; i-- { 313 args = append(args, r.GenerateRandomArg(ft.VarType)) 314 } 315 default: 316 panic(fmt.Sprintf("unknown fn.Types: %T", ft)) 317 } 318 var limit string 319 switch strings.ToLower(nb.name) { 320 case "generate_series": 321 limit = " LIMIT 100" 322 } 323 s := fmt.Sprintf("SELECT %s(%s) %s", nb.name, strings.Join(args, ", "), limit) 324 return db.exec(ctx, s) 325 }) 326 } 327 328 func TestRandomSyntaxFuncCommon(t *testing.T) { 329 defer leaktest.AfterTest(t)() 330 331 const rootStmt = "func_expr_common_subexpr" 332 333 testRandomSyntax(t, false, "defaultdb", nil, func(ctx context.Context, db *verifyFormatDB, r *rsg.RSG) error { 334 expr := r.Generate(rootStmt, 30) 335 s := fmt.Sprintf("SELECT %s", expr) 336 return db.exec(ctx, s) 337 }) 338 } 339 340 func TestRandomSyntaxSchemaChangeDatabase(t *testing.T) { 341 defer leaktest.AfterTest(t)() 342 343 roots := []string{ 344 "create_database_stmt", 345 "drop_database_stmt", 346 "alter_rename_database_stmt", 347 "create_user_stmt", 348 "drop_user_stmt", 349 "alter_user_stmt", 350 } 351 352 testRandomSyntax(t, true, "ident", func(ctx context.Context, db *verifyFormatDB, r *rsg.RSG) error { 353 return db.exec(ctx, ` 354 CREATE DATABASE ident; 355 `) 356 }, func(ctx context.Context, db *verifyFormatDB, r *rsg.RSG) error { 357 n := r.Intn(len(roots)) 358 s := r.Generate(roots[n], 30) 359 return db.exec(ctx, s) 360 }) 361 } 362 363 func TestRandomSyntaxSchemaChangeColumn(t *testing.T) { 364 defer leaktest.AfterTest(t)() 365 366 roots := []string{ 367 "alter_table_cmd", 368 } 369 370 testRandomSyntax(t, true, "ident", func(ctx context.Context, db *verifyFormatDB, r *rsg.RSG) error { 371 return db.exec(ctx, ` 372 CREATE DATABASE ident; 373 CREATE TABLE ident.ident (ident decimal); 374 `) 375 }, func(ctx context.Context, db *verifyFormatDB, r *rsg.RSG) error { 376 n := r.Intn(len(roots)) 377 s := fmt.Sprintf("ALTER TABLE ident.ident %s", r.Generate(roots[n], 500)) 378 return db.exec(ctx, s) 379 }) 380 } 381 382 var ignoredErrorPatterns = []string{ 383 "unimplemented", 384 "unsupported binary operator", 385 "unsupported comparison operator", 386 "memory budget exceeded", 387 "generator functions are not allowed in", 388 "txn already encountered an error; cannot be used anymore", 389 "no data source matches prefix", 390 "index .* already contains column", 391 "cannot convert .* to .*", 392 "index .* is in used as unique constraint", 393 "could not decorrelate subquery", 394 "column reference .* is ambiguous", 395 "INSERT has more expressions than target columns", 396 "index .* is in use as unique constraint", 397 "frame .* offset must not be .*", 398 "bit string length .* does not match type", 399 "column reference .* not allowed in this context", 400 "cannot write directly to computed column", 401 "index .* in the middle of being added", 402 "could not mark job .* as succeeded", 403 "failed to read backup descriptor", 404 "AS OF SYSTEM TIME: cannot specify timestamp in the future", 405 "AS OF SYSTEM TIME: timestamp before 1970-01-01T00:00:00Z is invalid", 406 "BACKUP for requested time needs option 'revision_history'", 407 "RESTORE timestamp: supplied backups do not cover requested time", 408 409 // Numeric conditions 410 "exponent out of range", 411 "result out of range", 412 "argument out of range", 413 "integer out of range", 414 "invalid operation", 415 "invalid mask", 416 "cannot take square root of a negative number", 417 "out of int64 range", 418 "underflow, subnormal", 419 "overflow", 420 "requested length too large", 421 "division by zero", 422 "zero modulus", 423 "is out of range", 424 425 // Type checking 426 "value type .* doesn't match type .* of column", 427 "incompatible value type", 428 "incompatible COALESCE expressions", 429 "error type checking constant value", 430 "ambiguous binary operator", 431 "ambiguous call", 432 "cannot be matched", 433 "unknown signature", 434 "cannot determine type of empty array", 435 "conflicting ColumnTypes", 436 437 // Data dependencies 438 "violates not-null constraint", 439 "violates unique constraint", 440 "column .* is referenced by the primary key", 441 "column .* is referenced by existing index", 442 443 // Context-specific string formats 444 "invalid regexp flag", 445 "unrecognized privilege", 446 "invalid escape string", 447 "error parsing regexp", 448 "could not parse .* as type bytes", 449 "UUID must be exactly 16 bytes long", 450 "unsupported timespan", 451 "does not exist", 452 "unterminated string", 453 "incorrect UUID length", 454 "the input string must not be empty", 455 456 // JSON builtins 457 "mismatched array dimensions", 458 "cannot get array length of a non-array", 459 "cannot get array length of a scalar", 460 "cannot be called on a non-array", 461 "cannot call json_object_keys on an array", 462 "cannot set path in scalar", 463 "cannot delete path in scalar", 464 "unable to encode table key: \\*tree\\.DJSON", 465 "path element at position .* is null", 466 "path element is not an integer", 467 "cannot delete from object using integer index", 468 "invalid concatenation of jsonb objects", 469 "null value not allowed for object key", 470 471 // Builtins that have funky preconditions 472 "cannot delete from scalar", 473 "lastval is not yet defined", 474 "negative substring length", 475 "non-positive substring length", 476 "bit strings of different sizes", 477 "inet addresses with different sizes", 478 "zero length IP", 479 "values of different sizes", 480 "must have even number of elements", 481 "cannot take logarithm of a negative number", 482 "input value must be", 483 "formats are supported for decode", 484 "only available in ccl", 485 "expect comma-separated list of filename", 486 "unknown constraint", 487 "invalid destination encoding name", 488 "invalid IP format", 489 "invalid format code", 490 `.*val\(\): syntax error`, 491 `.*val\(\): syntax error at or near`, 492 `.*val\(\): help token in input`, 493 "invalid source encoding name", 494 "strconv.Atoi: parsing .*: invalid syntax", 495 "field position .* must be greater than zero", 496 "cannot take logarithm of zero", 497 "only 'hex', 'escape', and 'base64' formats are supported for encode", 498 "LIKE pattern must not end with escape character", 499 500 // TODO(mjibson): fix these 501 "column .* must appear in the GROUP BY clause or be used in an aggregate function", 502 "aggregate functions are not allowed in ON", 503 } 504 505 var ignoredRegex = regexp.MustCompile(strings.Join(ignoredErrorPatterns, "|")) 506 507 func TestRandomSyntaxSQLSmith(t *testing.T) { 508 defer leaktest.AfterTest(t)() 509 defer utilccl.TestingEnableEnterprise()() 510 511 var smither *sqlsmith.Smither 512 513 tableStmts := make([]string, 0) 514 testRandomSyntax(t, true, "defaultdb", func(ctx context.Context, db *verifyFormatDB, r *rsg.RSG) error { 515 setups := []string{"rand-tables", "seed"} 516 for _, s := range setups { 517 randTables := sqlsmith.Setups[s](r.Rnd) 518 if err := db.exec(ctx, randTables); err != nil { 519 return err 520 } 521 tableStmts = append(tableStmts, randTables) 522 fmt.Printf("%s;\n", randTables) 523 } 524 var err error 525 smither, err = sqlsmith.NewSmither(db.db, r.Rnd, sqlsmith.DisableMutations()) 526 return err 527 }, func(ctx context.Context, db *verifyFormatDB, r *rsg.RSG) error { 528 s := smither.Generate() 529 err := db.exec(ctx, s) 530 if c := (*crasher)(nil); errors.As(err, &c) { 531 if err := db.exec(ctx, "USE defaultdb"); err != nil { 532 t.Fatalf("couldn't reconnect to db after crasher: %v", c) 533 } 534 fmt.Printf("CRASHER:\ncaused by: %s\n\nSTATEMENT:\n%s;\n\nserver stacktrace:\n%s\n\n", c.Error(), s, c.detail) 535 return c 536 } 537 if err == nil { 538 return nil 539 } 540 msg := err.Error() 541 shouldLogErr := true 542 if ignoredRegex.MatchString(msg) { 543 shouldLogErr = false 544 } 545 if testing.Verbose() && shouldLogErr { 546 fmt.Printf("ERROR: %s\ncaused by:\n%s;\n\n", err, s) 547 } 548 return err 549 }) 550 if smither != nil { 551 smither.Close() 552 } 553 554 fmt.Printf("To reproduce, use schema:\n\n") 555 for _, stmt := range tableStmts { 556 fmt.Printf("%s;", stmt) 557 } 558 fmt.Printf("\n") 559 } 560 561 func TestRandomDatumRoundtrip(t *testing.T) { 562 defer leaktest.AfterTest(t)() 563 564 eval := tree.MakeTestingEvalContext(nil) 565 566 var smither *sqlsmith.Smither 567 testRandomSyntax(t, true, "", func(ctx context.Context, db *verifyFormatDB, r *rsg.RSG) error { 568 var err error 569 smither, err = sqlsmith.NewSmither(nil, r.Rnd) 570 return err 571 }, func(ctx context.Context, db *verifyFormatDB, r *rsg.RSG) error { 572 defer func() { 573 if err := recover(); err != nil { 574 s := fmt.Sprint(err) 575 // JSONB NaN and Infinity can't round 576 // trip because JSON doesn't support 577 // those as Numbers, only strings. (Try 578 // `JSON.stringify(Infinity)` in a JS console.) 579 if strings.Contains(s, "JSONB") && (strings.Contains(s, "Infinity") || strings.Contains(s, "NaN")) { 580 return 581 } 582 for _, cmp := range []string{ 583 "ReturnType called on TypedExpr with empty typeAnnotation", 584 "runtime error: invalid memory address or nil pointer dereference", 585 } { 586 if strings.Contains(s, cmp) { 587 return 588 } 589 } 590 panic(err) 591 } 592 }() 593 generated := smither.GenerateExpr() 594 typ := generated.ResolvedType() 595 switch typ { 596 case types.Date, types.Decimal: 597 return nil 598 } 599 serializedGen := tree.Serialize(generated) 600 601 sema := tree.MakeSemaContext() 602 // We don't care about errors below because they are often 603 // caused by sqlsmith generating bogus queries. We're just 604 // looking for datums that don't match. 605 parsed1, err := parser.ParseExpr(serializedGen) 606 if err != nil { 607 return nil //nolint:returnerrcheck 608 } 609 typed1, err := parsed1.TypeCheck(ctx, &sema, typ) 610 if err != nil { 611 return nil //nolint:returnerrcheck 612 } 613 datum1, err := typed1.Eval(&eval) 614 if err != nil { 615 return nil //nolint:returnerrcheck 616 } 617 serialized1 := tree.Serialize(datum1) 618 619 parsed2, err := parser.ParseExpr(serialized1) 620 if err != nil { 621 return nil //nolint:returnerrcheck 622 } 623 typed2, err := parsed2.TypeCheck(ctx, &sema, typ) 624 if err != nil { 625 return nil //nolint:returnerrcheck 626 } 627 datum2, err := typed2.Eval(&eval) 628 if err != nil { 629 return nil //nolint:returnerrcheck 630 } 631 serialized2 := tree.Serialize(datum2) 632 633 if serialized1 != serialized2 { 634 panic(errors.Errorf("serialized didn't match:\nexpr: %s\nfirst: %s\nsecond: %s", generated, serialized1, serialized2)) 635 } 636 if datum1.Compare(&eval, datum2) != 0 { 637 panic(errors.Errorf("%s [%[1]T] != %s [%[2]T] (original expr: %s)", serialized1, serialized2, serializedGen)) 638 } 639 return nil 640 }) 641 } 642 643 // testRandomSyntax performs all of the RSG setup and teardown for common 644 // random syntax testing operations. It takes a closure where the random 645 // expression should be generated and executed. It returns an error indicating 646 // if the statement executed successfully. This is used to verify that at 647 // least 1 success occurs (otherwise it is likely a bad test). 648 func testRandomSyntax( 649 t *testing.T, 650 allowDuplicates bool, 651 databaseName string, 652 setup func(context.Context, *verifyFormatDB, *rsg.RSG) error, 653 fn func(context.Context, *verifyFormatDB, *rsg.RSG) error, 654 ) { 655 if *flagRSGTime == 0 { 656 t.Skip("enable with '-rsg <duration>'") 657 } 658 ctx := context.Background() 659 defer utilccl.TestingEnableEnterprise()() 660 661 params, _ := tests.CreateTestServerParams() 662 params.UseDatabase = databaseName 663 // Catch panics and return them as errors. 664 params.Knobs.PGWireTestingKnobs = &sql.PGWireTestingKnobs{ 665 CatchPanics: true, 666 } 667 s, rawDB, _ := serverutils.StartServer(t, params) 668 defer s.Stopper().Stop(ctx) 669 db := &verifyFormatDB{db: rawDB} 670 671 yBytes, err := ioutil.ReadFile(filepath.Join("..", "parser", "sql.y")) 672 if err != nil { 673 t.Fatal(err) 674 } 675 r, err := rsg.NewRSG(timeutil.Now().UnixNano(), string(yBytes), allowDuplicates) 676 if err != nil { 677 t.Fatal(err) 678 } 679 680 if setup != nil { 681 err := setup(ctx, db, r) 682 if err != nil { 683 t.Fatal(err) 684 } 685 } 686 687 // Broadcast channel for all workers. 688 done := make(chan struct{}) 689 time.AfterFunc(*flagRSGTime, func() { 690 close(done) 691 }) 692 var countsMu struct { 693 syncutil.Mutex 694 total, success int 695 } 696 ctx, cancel := context.WithCancel(ctx) 697 // Print status updates. We want this go routine to continue until all the 698 // workers are done, even if their ctx has been canceled, so the ctx for 699 // this func is a separate one with its own cancel. 700 go func(ctx context.Context) { 701 start := timeutil.Now() 702 for { 703 select { 704 case <-ctx.Done(): 705 return 706 case <-time.After(5 * time.Second): 707 } 708 countsMu.Lock() 709 fmt.Printf("%v of %v: %d executions, %d successful\n", 710 timeutil.Since(start).Round(time.Second), 711 *flagRSGTime, 712 countsMu.total, 713 countsMu.success, 714 ) 715 countsMu.Unlock() 716 } 717 }(ctx) 718 ctx, timeoutCancel := context.WithTimeout(ctx, *flagRSGTime) 719 err = ctxgroup.GroupWorkers(ctx, *flagRSGGoRoutines, func(ctx context.Context, _ int) error { 720 for { 721 select { 722 case <-ctx.Done(): 723 return nil 724 default: 725 } 726 err := fn(ctx, db, r) 727 countsMu.Lock() 728 countsMu.total++ 729 if err == nil { 730 countsMu.success++ 731 } else { 732 if c := (*crasher)(nil); errors.As(err, &c) { 733 t.Errorf("Crash detected: \n%s\n\nStack trace:\n%s", c.sql, c.detail) 734 } 735 } 736 countsMu.Unlock() 737 } 738 }) 739 timeoutCancel() 740 // cancel the timer printing's ctx 741 cancel() 742 t.Logf("%d executions, %d successful", countsMu.total, countsMu.success) 743 if err != nil { 744 t.Fatal(err) 745 } 746 if countsMu.success == 0 { 747 t.Fatal("0 successful executions") 748 } 749 if db.verifyFormatErr != nil { 750 t.Error(db.verifyFormatErr) 751 } 752 }