Atlas › Test
Fatal
Exact test identity: go.temporal.io/server/common/testing/await/TestRequire_RetriesUntilAttemptPasses/Fatal
- Package
go.temporal.io/server/common/testing/await
- Suite / test hierarchy
TestRequire_RetriesUntilAttemptPasses/Fatal
- Test
Fatal
- Introduced at
- Fatal Frontier kind: Test frontier
- Covered ranges
- 41
- Covered lines
- 124
- Covered files
- 4
Covered source
Expand a file to inspect source; the > gutter marks covered lines.
go.temporal.io/server/common/testing/await/require_ctx.go 87 covered LOC · 31 ranges
Open complete file
20
const defaultSoftDeadlockTimeout = 30 * time.Second
21
23
>
if s := os.Getenv(softDeadlockTimeoutEnvVar); s != "" {
24
if d, err := time.ParseDuration(s); err == nil {
25
return d
26
}
27
}
29
}
30
54
// Pass the *await.T to require.*/assert.* — failures cause a retry, not a
55
// test failure. Use t.Context() inside the callback to honor the timeout.
56
>
func Require(ctx context.Context, tb testing.TB, condition func(*T), timeout, pollInterval time.Duration) {
require_ctx.go
57
>
tb.Helper()
58
>
run(ctx, tb, condition, legacyConfig(timeout, pollInterval, ""), "Require", requireMisuseHint, true)
59
>
}
60
61
// Requiref is like [Require] but adds a formatted message to the timeout
74
misuseHint string,
75
cancellable bool,
77
>
tb.Helper()
78
>
79
>
// Skip if the test already failed — no point polling.
80
>
if tb.Failed() {
81
tb.Logf("%s: skipping (test already failed)", funcName)
82
return
83
}
84
// Guard: context.WithDeadline panics on a nil parent.
86
tb.Fatalf("%s: nil context", funcName)
87
return
88
}
89
91
>
92
>
// Cap at the parent context's deadline if it's earlier than our timeout.
93
>
if parentDeadline, hasDeadline := parentCtx.Deadline(); hasDeadline && parentDeadline.Before(deadline) {
94
deadline = parentDeadline
95
}
97
// Cap at the test's deadline if it's earlier than our deadline.
98
// Ideally, the parent context already accounts for the test's deadline - but we are being defensive.
99
>
if d, ok := tb.(interface{ Deadline() (time.Time, bool) }); ok {
require_ctx.go
100
>
if testDeadline, hasDeadline := d.Deadline(); hasDeadline && testDeadline.Before(deadline) {
require_ctx.go
101
deadline = testDeadline
102
}
103
}
104
106
>
awaitCtx, awaitCancel := context.WithDeadline(parentCtx, deadline)
107
>
defer awaitCancel()
108
>
109
>
report := timeoutReport{effectiveTimeout: effectiveTimeout}
110
>
111
>
for {
112
>
// Parent context was canceled while we were sleeping (not our deadline).
113
>
if err := awaitCtx.Err(); err != nil && !deadlineReached(deadline) {
114
report.reportAttemptErrors(tb)
115
tb.Fatalf("%s: context canceled before condition was satisfied: %v", funcName, err)
117
}
118
120
>
121
>
// Per-attempt context: bounded by the configured attempt timeout and
122
>
// further capped by the overall awaitCtx.
123
>
attemptCtx, attemptCancel := context.WithTimeout(awaitCtx, cfg.attemptTimeout)
124
>
t := &T{tb: tb, ctx: attemptCtx}
125
>
126
>
// Run attempt.
127
>
res := runAttempt(t, condition, attemptCancel, funcName, cancellable)
128
>
attemptCancel()
129
>
if res.panicVal != nil {
130
panic(res.panicVal) // propagate to caller
131
}
133
report.reportAttemptErrors(tb)
134
if cancellable {
141
return
142
}
144
>
145
>
// Attempt-timeout expiry: attemptCtx is done but awaitCtx is not.
146
>
// Record nothing special - the attempt's recorded errors (if any)
147
>
// already describe what went wrong; otherwise we just retry.
148
>
attemptHitOwnTimeout := attemptCtx.Err() == context.DeadlineExceeded && awaitCtx.Err() == nil
149
>
if attemptHitOwnTimeout {
150
report.recordAttemptTimeout()
151
}
152
153
// Check misuse where the real test failed instead of just the attempt.
155
tb.Fatalf("%s: the test was marked failed directly — %s", funcName, misuseHint)
156
return
158
159
// Parent context was canceled during the attempt (not our deadline).
160
>
if err := awaitCtx.Err(); err != nil && !deadlineReached(deadline) {
require_ctx.go
161
report.reportAttemptErrors(tb)
162
tb.Fatalf("%s: context canceled before condition was satisfied: %v", funcName, err)
165
166
// Our deadline expired.
168
report.reportTimeout(tb, funcName, cfg.timeoutMsg)
169
return
171
172
// Success: attempt completed without failures.
173
>
if !res.stopped && !t.Failed() && !attemptHitOwnTimeout {
require_ctx.go
175
>
}
176
177
// Wait for pollInterval, or context is canceled or deadline is reached.
179
}
180
}
211
funcName string,
212
cancellable bool,
214
>
done := make(chan attemptResult, 1)
215
>
216
>
go func() {
217
>
completed := false
218
>
defer func() {
222
>
return
223
>
}
224
done <- attemptResult{panicVal: r}
225
return
227
// recover returned nil: either normal return (completed=true) or
228
// runtime.Goexit (completed=false; Goexit is not a panic).
230
}()
232
>
completed = true
233
}()
234
236
>
// Soft phase: wait for the condition, our soft timer, or parent cancel.
require_ctx.go
237
>
softTimer := time.NewTimer(softDeadlockTimeout())
238
>
defer softTimer.Stop()
239
>
240
>
select {
242
>
return r
243
case <-softTimer.C:
244
// Soft deadlock: log a warning.
266
}
267
268
>
func sleep(ctx context.Context, deadline time.Time, pollInterval time.Duration) {
require_ctx.go
269
>
remaining := time.Until(deadline)
270
>
if remaining < pollInterval {
271
pollInterval = remaining
272
}
273
275
>
defer timer.Stop()
276
>
277
>
select {
278
case <-ctx.Done():
280
}
281
}
282
284
>
return !time.Now().Before(deadline)
285
>
}
go.temporal.io/server/common/testing/await/config.go 17 covered LOC · 3 ranges
Open complete file
17
}
18
20
>
return config{
21
>
attemptTimeout: envDuration(attemptTimeoutEnvVar, 10*time.Second) * debug.TimeoutMultiplier,
22
>
}
23
>
}
24
25
>
func legacyConfig(timeout, pollInterval time.Duration, timeoutMsg string) config {
config.go
26
>
cfg := newConfig()
27
>
cfg.totalTimeout = timeout
28
>
cfg.pollInterval = pollInterval
29
>
cfg.timeoutMsg = timeoutMsg
30
>
return cfg
31
>
}
32
33
>
func envDuration(name string, fallback time.Duration) time.Duration {
config.go
34
>
if s := os.Getenv(name); s != "" {
35
>
if d, err := time.ParseDuration(s); err == nil && d > 0 {
36
>
return d
37
>
}
38
}
39
return fallback
go.temporal.io/server/common/testing/await/t.go 13 covered LOC · 4 ranges
Open complete file
47
48
// Fail marks the current attempt as failed without stopping it.
49
>
func (t *T) Fail() {
t.go
50
>
t.failed = true
51
>
}
52
53
// Error records an error message for reporting on timeout.
65
// FailNow is called by require.* on failure. It stops the current attempt.
66
// Unlike testing.TB.FailNow(), this does NOT mark the test as failed.
67
>
func (t *T) FailNow() {
t.go
68
>
t.Fail()
69
>
panic(attemptFailed{})
70
}
71
72
// Fatal records an error message and stops this attempt.
73
>
func (t *T) Fatal(args ...any) {
t.go
74
>
t.errors = append(t.errors, strings.TrimSuffix(fmt.Sprintln(args...), "\n"))
75
>
t.FailNow()
76
>
}
77
78
// Fatalf records an error message and stops this attempt.
83
84
// Failed reports whether this attempt has failed.
85
>
func (t *T) Failed() bool {
t.go
86
>
return t.failed
87
>
}
88
89
// Helper marks the calling function as a test helper.
go.temporal.io/server/common/testing/await/report.go 7 covered LOC · 3 ranges
Open complete file
28
}
29
30
>
func (r *timeoutReport) nextPoll() {
report.go
31
>
r.attempts++
32
>
}
33
34
>
func (r *timeoutReport) recordErrors(errors []string) {
report.go
35
>
if len(errors) > 0 {
36
>
r.failures = append(r.failures, attemptFailure{attempt: r.attempts, errors: errors})
report.go
37
>
}
38
}
39