Skip to content

Commit 3649a82

Browse files
committed
pkg/services/servicetest: add RunCfg for customized Run configurations
1 parent 00dd182 commit 3649a82

File tree

1 file changed

+79
-7
lines changed
  • pkg/services/servicetest

1 file changed

+79
-7
lines changed

pkg/services/servicetest/run.go

Lines changed: 79 additions & 7 deletions
Original file line numberDiff line numberDiff line change
@@ -27,11 +27,7 @@ type TestingT interface {
2727
// Run fails tb if the service fails to start or close.
2828
func Run[R Runnable](tb TestingT, r R) R {
2929
tb.Helper()
30-
require.NoError(tb, r.Start(tests.Context(tb)), "service failed to start: %T", r)
31-
tb.Cleanup(func() {
32-
tb.Helper()
33-
assert.NoError(tb, r.Close(), "error closing service: %T", r)
34-
})
30+
RunCfg{}.run(tb, r)
3531
return r
3632
}
3733

@@ -40,7 +36,81 @@ func Run[R Runnable](tb TestingT, r R) R {
4036
// - if ever ready, then health will be checked at least once, before closing
4137
func RunHealthy[S services.Service](tb TestingT, s S) S {
4238
tb.Helper()
43-
Run(tb, s)
39+
RunCfg{Healthy: true}.Run(tb, s)
40+
return s
41+
}
42+
43+
// RunCfg specifies a test configuration for running a service.
44+
// By default, health checks are not enforced, but Start/Close timeout are.
45+
type RunCfg struct {
46+
// Healthy includes extra checks for whether the service is never ready, or is ever unhealthy (based on periodic checks).
47+
// - after starting, readiness will always be checked at least once, before closing
48+
// - if ever ready, then health will be checked at least once, before closing
49+
Healthy bool
50+
// WaitForReady blocks returning until after Ready() returns nil, after calling Start().
51+
WaitForReady bool
52+
// StartTimeout sets a limit for Start which results in an error if exceeded.
53+
StartTimeout time.Duration
54+
// StartTimeout sets a limit for Close which results in an error if exceeded.
55+
CloseTimeout time.Duration
56+
}
57+
58+
func (cfg RunCfg) Run(tb TestingT, s services.Service) {
59+
tb.Helper()
60+
61+
cfg.run(tb, s)
62+
63+
if cfg.WaitForReady {
64+
ctx := tests.Context(tb)
65+
cfg.waitForReady(tb, s, ctx.Done())
66+
}
67+
68+
if cfg.Healthy {
69+
cfg.healthCheck(tb, s)
70+
}
71+
}
72+
73+
func (cfg RunCfg) run(tb TestingT, s Runnable) {
74+
tb.Helper()
75+
//TODO remove....set from built-ins? or disallow unbounded, so exceptions must be explicit?
76+
if cfg.StartTimeout == 0 {
77+
cfg.StartTimeout = time.Second
78+
}
79+
if cfg.CloseTimeout == 0 {
80+
cfg.CloseTimeout = time.Second
81+
}
82+
83+
start := time.Now()
84+
err := s.Start(tests.Context(tb))
85+
if elapsed := time.Since(start); cfg.StartTimeout > 0 && elapsed > cfg.StartTimeout {
86+
tb.Errorf("slow service start: %T.Start() took %s", s, elapsed)
87+
}
88+
require.NoError(tb, err, "service failed to start: %T", s)
89+
90+
tb.Cleanup(func() {
91+
tb.Helper()
92+
start := time.Now()
93+
err := s.Close()
94+
if elapsed := time.Since(start); cfg.CloseTimeout > 0 && elapsed > cfg.CloseTimeout {
95+
tb.Errorf("slow service close: %T.Close() took %s", s, elapsed)
96+
}
97+
assert.NoError(tb, err, "error closing service: %T", s)
98+
})
99+
}
100+
101+
func (cfg RunCfg) waitForReady(tb TestingT, s services.Service, done <-chan struct{}) {
102+
for err := s.Ready(); err != nil; err = s.Ready() {
103+
select {
104+
case <-done:
105+
assert.NoError(tb, err, "service never ready")
106+
return
107+
case <-time.After(time.Second):
108+
}
109+
}
110+
}
111+
112+
func (cfg RunCfg) healthCheck(tb TestingT, s services.Service) {
113+
tb.Helper()
44114

45115
done := make(chan struct{})
46116
tb.Cleanup(func() {
@@ -57,6 +127,9 @@ func RunHealthy[S services.Service](tb TestingT, s S) S {
57127
}
58128
return
59129
}
130+
if !cfg.WaitForReady {
131+
cfg.waitForReady(tb, s, done)
132+
}
60133
for s.Ready() != nil {
61134
select {
62135
case <-done:
@@ -77,5 +150,4 @@ func RunHealthy[S services.Service](tb TestingT, s S) S {
77150
}
78151
}
79152
}()
80-
return s
81153
}

0 commit comments

Comments
 (0)