@@ -61,6 +61,10 @@ type Stage struct {
61
61
// Use RandomlyExecuteUntil to specify a duration like "1h" or an integer as the number of queries should be executed
62
62
// before exiting.
63
63
RandomlyExecuteUntil * string `json:"randomly_execute_until,omitempty"`
64
+ // If NoRandomDuplicates is set to true, queries will not be repeated during random execution
65
+ // until all queries have been executed once. After that, the selection pool resets if more
66
+ // executions are needed.
67
+ NoRandomDuplicates * bool `json:"no_random_duplicates,omitempty"`
64
68
// If not set, the default is 1. The default value is set when the stage is run.
65
69
ColdRuns * int `json:"cold_runs,omitempty" validate:"omitempty,gte=0"`
66
70
// If not set, the default is 0.
@@ -343,9 +347,17 @@ func (s *Stage) runRandomly(ctx context.Context) error {
343
347
return nil
344
348
}
345
349
}
350
+
346
351
r := rand .New (rand .NewSource (s .States .RandSeed ))
347
352
s .States .RandSeedUsed = true
348
353
log .Info ().Int64 ("seed" , s .States .RandSeed ).Msg ("random source seeded" )
354
+
355
+ // If NoRandomDuplicates is enabled, use bag-based selection
356
+ if s .NoRandomDuplicates != nil && * s .NoRandomDuplicates {
357
+ return s .runRandomlyWithoutDuplicates (ctx , r , continueExecution )
358
+ }
359
+
360
+ // Original random execution logic (with duplicates allowed)
349
361
randIndexUpperBound := len (s .Queries ) + len (s .QueryFiles )
350
362
for i := 1 ; continueExecution (i ); i ++ {
351
363
idx := r .Intn (randIndexUpperBound )
@@ -377,6 +389,74 @@ func (s *Stage) runRandomly(ctx context.Context) error {
377
389
return nil
378
390
}
379
391
392
+ func (s * Stage ) runRandomlyWithoutDuplicates (ctx context.Context , r * rand.Rand , continueExecution func (int ) bool ) error {
393
+ // Create initial bag of all query indices
394
+ totalQueries := len (s .Queries ) + len (s .QueryFiles )
395
+ if totalQueries == 0 {
396
+ log .Info ().Msg ("no queries available for random execution" )
397
+ return nil
398
+ }
399
+
400
+ // Initialize the bag with all available query indices
401
+ var bag []int
402
+ for i := 0 ; i < totalQueries ; i ++ {
403
+ bag = append (bag , i )
404
+ }
405
+
406
+ log .Info ().Int ("total_queries" , totalQueries ).Bool ("no_duplicates" , true ).Msg ("starting random execution without duplicates" )
407
+
408
+ for i := 1 ; continueExecution (i ); i ++ {
409
+ // If bag is empty, refill it (reset)
410
+ if len (bag ) == 0 {
411
+ log .Info ().Int ("execution_count" , i - 1 ).Msg ("bag exhausted, refilling for next round" )
412
+ for j := 0 ; j < totalQueries ; j ++ {
413
+ bag = append (bag , j )
414
+ }
415
+ }
416
+
417
+ // Skip executions if needed
418
+ if i <= s .States .RandSkip {
419
+ if i == s .States .RandSkip {
420
+ log .Info ().Msgf ("skipped %d random selections" , i )
421
+ }
422
+ continue
423
+ }
424
+
425
+ // Randomly select an index from the bag
426
+ bagIndex := r .Intn (len (bag ))
427
+ selectedIdx := bag [bagIndex ]
428
+
429
+ // Remove the selected index from the bag (no replacement)
430
+ bag = append (bag [:bagIndex ], bag [bagIndex + 1 :]... )
431
+
432
+ log .Debug ().Int ("selected_idx" , selectedIdx ).Int ("remaining_in_bag" , len (bag )).Msg ("selected query from bag" )
433
+
434
+ // Execute the selected query
435
+ if selectedIdx < len (s .Queries ) {
436
+ // Run query embedded in the json file
437
+ pseudoFileName := fmt .Sprintf ("rand_%d" , i )
438
+ if err := s .runQueries (ctx , s .Queries [selectedIdx :selectedIdx + 1 ], & pseudoFileName , 0 ); err != nil {
439
+ return err
440
+ }
441
+ } else {
442
+ // Run query from file
443
+ queryFileIdx := selectedIdx - len (s .Queries )
444
+ queryFile := s .QueryFiles [queryFileIdx ]
445
+ fileAlias := queryFile
446
+ if relPath , relErr := filepath .Rel (s .BaseDir , queryFile ); relErr == nil {
447
+ fileAlias = relPath
448
+ }
449
+ fileAlias = fmt .Sprintf ("rand_%d_%s" , i , fileAlias )
450
+ if err := s .runQueryFile (ctx , queryFile , nil , & fileAlias ); err != nil {
451
+ return err
452
+ }
453
+ }
454
+ }
455
+
456
+ log .Info ().Msg ("random execution without duplicates concluded." )
457
+ return nil
458
+ }
459
+
380
460
func (s * Stage ) runShellScripts (ctx context.Context , shellScripts []string ) error {
381
461
for i , script := range shellScripts {
382
462
cmd := exec .CommandContext (ctx , "/bin/sh" , "-c" , script )
0 commit comments