Skip to content

Commit 8e08bbd

Browse files
authored
feat: add post_test_sleep_duration config option (#162)
## Summary - Add `post_test_sleep_duration` config option for a configurable sleep after each test, allowing clients time for internal cleanup between tests - Supports global (`runner.client.config`) and per-instance overrides, following the existing config pattern (e.g. `wait_after_rpc_ready`) - Wired into all three executor paths: normal, container strategy, and checkpoint-restore ## Test plan - [x] `go build ./pkg/config/ ./pkg/executor/` passes - [x] `go test ./pkg/config/ ./pkg/executor/` passes - [x] `go vet ./pkg/config/ ./pkg/executor/` passes - [x] Manual test with a config using `post_test_sleep_duration: 200ms` and verify sleep appears in debug logs
1 parent 0ed8e01 commit 8e08bbd

8 files changed

Lines changed: 90 additions & 0 deletions

File tree

config.example.yaml

Lines changed: 4 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -306,6 +306,9 @@ runner:
306306
# Optional: Maximum duration for the test execution phase.
307307
# If exceeded, the run is cancelled with "timed_out" status. Partial results are kept.
308308
# run_timeout: 2h
309+
# Optional: Sleep duration after each test (e.g. "200ms", "1s"). Default: 0 (disabled).
310+
# Useful for clients that need a brief pause between tests to let internal cleanup finish.
311+
# post_test_sleep_duration: 200ms
309312
# Optional: Retry engine_newPayload calls when client returns SYNCING status.
310313
# Useful for clients with internal sync pipelines (e.g., Erigon) that may return
311314
# SYNCING while still processing blocks internally.
@@ -452,6 +455,7 @@ runner:
452455
# wait_after_tcp_drop_connections: "10s"
453456
# wait_after_rpc_ready: 60s # Instance-level override (optional)
454457
# run_timeout: 1h # Instance-level override (optional)
458+
# post_test_sleep_duration: 500ms # Instance-level override (optional)
455459
# retry_new_payloads_syncing_state: # Instance-level override (optional)
456460
# enabled: true
457461
# max_retries: 10

docs/configuration.md

Lines changed: 19 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -525,6 +525,7 @@ runner:
525525
| `retry_new_payloads_syncing_state` | object | - | Retry config for SYNCING responses (see below) |
526526
| `resource_limits` | object | - | Container resource constraints (see [Resource Limits](#resource-limits)) |
527527
| `post_test_rpc_calls` | []object | - | Arbitrary RPC calls to execute after each test step (see [Post-Test RPC Calls](#post-test-rpc-calls)) |
528+
| `post_test_sleep_duration` | string | - | Sleep duration after each test, e.g. `200ms`, `1s` (see below) |
528529
| `bootstrap_fcu` | bool/object | - | Send an `engine_forkchoiceUpdatedV3` after RPC is ready to confirm the client is fully synced (see [Bootstrap FCU](#bootstrap-fcu)) |
529530
| `genesis` | map | - | Genesis file URLs keyed by client type |
530531

@@ -704,6 +705,23 @@ The timeout covers only the test execution phase — container setup, image pull
704705
- When you want to enforce a maximum wall-clock time per instance
705706
- When running in CI/CD environments with time constraints
706707

708+
##### Post-Test Sleep Duration
709+
710+
The `post_test_sleep_duration` option adds a configurable pause after each test completes (after rollback and post-test RPC calls, but before the next test begins). This is useful for clients that need time to complete internal cleanup between tests.
711+
712+
```yaml
713+
runner:
714+
client:
715+
config:
716+
post_test_sleep_duration: 200ms
717+
```
718+
719+
Uses Go duration format (e.g., `200ms`, `1s`, `5s`). Default is `0` (disabled).
720+
721+
**When to use:**
722+
- When a client needs time for internal cleanup between tests
723+
- When you observe flaky results due to rapid successive test execution
724+
707725
##### Retry New Payloads Syncing State
708726

709727
When `engine_newPayload` returns a `SYNCING` status, it indicates the client hasn't fully processed the parent block yet. The `retry_new_payloads_syncing_state` option configures automatic retries with exponential backoff.
@@ -875,6 +893,7 @@ runner:
875893
| `retry_new_payloads_syncing_state` | object | No | From `runner.client.config` | Instance-specific retry config for SYNCING responses |
876894
| `resource_limits` | object | No | From `runner.client.config` | Instance-specific resource limits |
877895
| `post_test_rpc_calls` | []object | No | From `runner.client.config` | Instance-specific post-test RPC calls (replaces global) |
896+
| `post_test_sleep_duration` | string | No | From `runner.client.config` | Instance-specific post-test sleep duration |
878897
| `bootstrap_fcu` | bool/object | No | From `runner.client.config` | Instance-specific bootstrap FCU setting |
879898

880899
## Resource Limits

pkg/config/config.go

Lines changed: 49 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -596,6 +596,7 @@ type ClientDefaults struct {
596596
WaitAfterRPCReady string `yaml:"wait_after_rpc_ready,omitempty" mapstructure:"wait_after_rpc_ready"`
597597
RunTimeout string `yaml:"run_timeout,omitempty" mapstructure:"run_timeout"`
598598
PostTestRPCCalls []PostTestRPCCall `yaml:"post_test_rpc_calls,omitempty" mapstructure:"post_test_rpc_calls"`
599+
PostTestSleepDuration string `yaml:"post_test_sleep_duration,omitempty" mapstructure:"post_test_sleep_duration"`
599600
BootstrapFCU *BootstrapFCUConfig `yaml:"bootstrap_fcu,omitempty" mapstructure:"bootstrap_fcu"`
600601
CheckpointRestoreStrategyOptions *CheckpointRestoreStrategyOptions `yaml:"checkpoint_restore_strategy_options,omitempty" mapstructure:"checkpoint_restore_strategy_options"`
601602
}
@@ -620,6 +621,7 @@ type ClientInstance struct {
620621
WaitAfterRPCReady string `yaml:"wait_after_rpc_ready,omitempty" mapstructure:"wait_after_rpc_ready"`
621622
RunTimeout string `yaml:"run_timeout,omitempty" mapstructure:"run_timeout"`
622623
PostTestRPCCalls []PostTestRPCCall `yaml:"post_test_rpc_calls,omitempty" mapstructure:"post_test_rpc_calls"`
624+
PostTestSleepDuration string `yaml:"post_test_sleep_duration,omitempty" mapstructure:"post_test_sleep_duration"`
623625
BootstrapFCU *BootstrapFCUConfig `yaml:"bootstrap_fcu,omitempty" mapstructure:"bootstrap_fcu"`
624626
CheckpointRestoreStrategyOptions *CheckpointRestoreStrategyOptions `yaml:"checkpoint_restore_strategy_options,omitempty" mapstructure:"checkpoint_restore_strategy_options"`
625627
}
@@ -1043,6 +1045,11 @@ func (c *Config) Validate(opts ...ValidateOpts) error {
10431045
return err
10441046
}
10451047

1048+
// Validate post_test_sleep_duration settings.
1049+
if err := c.validatePostTestSleepDuration(); err != nil {
1050+
return err
1051+
}
1052+
10461053
// Validate run_timeout settings.
10471054
if err := c.validateRunTimeout(); err != nil {
10481055
return err
@@ -1292,6 +1299,29 @@ func (c *Config) GetWaitAfterRPCReady(instance *ClientInstance) time.Duration {
12921299
return d
12931300
}
12941301

1302+
// GetPostTestSleepDuration returns the duration to sleep after each test.
1303+
// Instance-level value overrides the global default. Returns 0 if not set.
1304+
func (c *Config) GetPostTestSleepDuration(instance *ClientInstance) time.Duration {
1305+
var sleepStr string
1306+
1307+
if instance.PostTestSleepDuration != "" {
1308+
sleepStr = instance.PostTestSleepDuration
1309+
} else {
1310+
sleepStr = c.Runner.Client.Config.PostTestSleepDuration
1311+
}
1312+
1313+
if sleepStr == "" {
1314+
return 0
1315+
}
1316+
1317+
d, err := time.ParseDuration(sleepStr)
1318+
if err != nil {
1319+
return 0
1320+
}
1321+
1322+
return d
1323+
}
1324+
12951325
// GetRunnerRunTimeout returns the global runner-level timeout that caps
12961326
// the entire run (all instances, setup, and teardown). Returns 0 if not set.
12971327
func (c *Config) GetRunnerRunTimeout() time.Duration {
@@ -1689,6 +1719,25 @@ func (c *Config) validateWaitAfterRPCReady() error {
16891719
return nil
16901720
}
16911721

1722+
// validatePostTestSleepDuration validates post_test_sleep_duration settings.
1723+
func (c *Config) validatePostTestSleepDuration() error {
1724+
for _, instance := range c.Runner.Instances {
1725+
sleepStr := instance.PostTestSleepDuration
1726+
if sleepStr == "" {
1727+
sleepStr = c.Runner.Client.Config.PostTestSleepDuration
1728+
}
1729+
1730+
if sleepStr != "" {
1731+
if _, err := time.ParseDuration(sleepStr); err != nil {
1732+
return fmt.Errorf("instance %q: invalid post_test_sleep_duration %q: %w",
1733+
instance.ID, sleepStr, err)
1734+
}
1735+
}
1736+
}
1737+
1738+
return nil
1739+
}
1740+
16921741
// validateRunTimeout validates run_timeout settings.
16931742
func (c *Config) validateRunTimeout() error {
16941743
if c.Runner.RunTimeout != "" {

pkg/executor/executor.go

Lines changed: 6 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -72,6 +72,7 @@ type ExecuteOptions struct {
7272
BlockLogCollector BlockLogCollector // Optional collector for capturing block logs from client.
7373
RetryNewPayloadsSyncingConfig *config.RetryNewPayloadsSyncingConfig // Retry config for SYNCING responses.
7474
PostTestRPCCalls []config.PostTestRPCCall // Arbitrary RPC calls to execute after the test step.
75+
PostTestSleepDuration time.Duration // Sleep duration after each test (0 = disabled).
7576
}
7677

7778
// ExecutionResult contains the overall execution summary.
@@ -536,6 +537,11 @@ func (e *executor) ExecuteTests(ctx context.Context, opts *ExecuteOptions) (*Exe
536537
}
537538
}
538539

540+
if opts.PostTestSleepDuration > 0 {
541+
log.WithField("duration", opts.PostTestSleepDuration).Debug("Sleeping after test")
542+
time.Sleep(opts.PostTestSleepDuration)
543+
}
544+
539545
if testPassed {
540546
testsPassed++
541547
log.Info("Test completed successfully")

pkg/runner/lifecycle.go

Lines changed: 9 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -587,6 +587,14 @@ func (r *runner) runContainerLifecycle(
587587
}
588588
return nil
589589
}(),
590+
PostTestSleepDuration: func() string {
591+
if r.cfg.FullConfig != nil {
592+
if d := r.cfg.FullConfig.GetPostTestSleepDuration(instance); d > 0 {
593+
return d.String()
594+
}
595+
}
596+
return ""
597+
}(),
590598
BootstrapFCU: func() *config.BootstrapFCUConfig {
591599
if r.cfg.FullConfig != nil {
592600
return r.cfg.FullConfig.GetBootstrapFCU(instance)
@@ -1038,6 +1046,7 @@ func (r *runner) runContainerLifecycle(
10381046
BlockLogCollector: params.BlockLogCollector,
10391047
RetryNewPayloadsSyncingConfig: r.cfg.FullConfig.GetRetryNewPayloadsSyncingState(instance),
10401048
PostTestRPCCalls: r.cfg.FullConfig.GetPostTestRPCCalls(instance),
1049+
PostTestSleepDuration: r.cfg.FullConfig.GetPostTestSleepDuration(instance),
10411050
}
10421051

10431052
result, execErr = r.executor.ExecuteTests(execCtx, execOpts)

pkg/runner/runner.go

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -170,6 +170,7 @@ type ResolvedInstance struct {
170170
RetryNewPayloadsSyncingState *config.RetryNewPayloadsSyncingConfig `json:"retry_new_payloads_syncing_state,omitempty"`
171171
ResourceLimits *ResolvedResourceLimits `json:"resource_limits,omitempty"`
172172
PostTestRPCCalls []config.PostTestRPCCall `json:"post_test_rpc_calls,omitempty"`
173+
PostTestSleepDuration string `json:"post_test_sleep_duration,omitempty"`
173174
BootstrapFCU *config.BootstrapFCUConfig `json:"bootstrap_fcu,omitempty"`
174175
CheckpointRestoreStrategyOptions *config.CheckpointRestoreStrategyOptions `json:"checkpoint_restore_strategy_options,omitempty"`
175176
}

pkg/runner/strategy_checkpoint.go

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -510,6 +510,7 @@ func (r *runner) runTestsWithCheckpointRestore(
510510
BlockLogCollector: params.BlockLogCollector,
511511
RetryNewPayloadsSyncingConfig: r.cfg.FullConfig.GetRetryNewPayloadsSyncingState(params.Instance),
512512
PostTestRPCCalls: r.cfg.FullConfig.GetPostTestRPCCalls(params.Instance),
513+
PostTestSleepDuration: r.cfg.FullConfig.GetPostTestSleepDuration(params.Instance),
513514
}
514515

515516
result, execErr := r.executor.ExecuteTests(ctx, execOpts)

pkg/runner/strategy_container.go

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -738,6 +738,7 @@ func (r *runner) runTestsWithContainerStrategy(
738738
BlockLogCollector: params.BlockLogCollector,
739739
RetryNewPayloadsSyncingConfig: r.cfg.FullConfig.GetRetryNewPayloadsSyncingState(params.Instance),
740740
PostTestRPCCalls: r.cfg.FullConfig.GetPostTestRPCCalls(params.Instance),
741+
PostTestSleepDuration: r.cfg.FullConfig.GetPostTestSleepDuration(params.Instance),
741742
}
742743

743744
result, err := r.executor.ExecuteTests(ctx, execOpts)

0 commit comments

Comments
 (0)