Skip to content

Commit 67bf475

Browse files
authored
Merge pull request #32 from jaypipes/timeout-plugin-specified
allow plugin-defaulted timeout values
2 parents c4a51f5 + 1ac476c commit 67bf475

File tree

13 files changed

+120
-26
lines changed

13 files changed

+120
-26
lines changed

README.md

Lines changed: 1 addition & 6 deletions
Original file line numberDiff line numberDiff line change
@@ -458,12 +458,8 @@ All test specs have the following fields:
458458

459459
* `name`: (optional) string describing the test unit.
460460
* `description`: (optional) string with longer description of the test unit.
461-
* `timeout`: (optional) an object containing [timeout information][timeout] for the test
462-
unit.
463-
* `timeout.after`: a string duration of time the test unit is expected to
461+
* `timeout`: (optional) a string duration of time the test unit is expected to
464462
complete within.
465-
* `timeout.expected`: a bool indicating that the test unit is expected to not
466-
complete before `timeout.after`. This is really only useful in unit testing.
467463
* `retry`: (optional) an object containing retry configurationu for the test
468464
unit. Some plugins will automatically attempt to retry the test action when
469465
an assertion fails. This field allows you to control this retry behaviour for
@@ -497,7 +493,6 @@ All test specs have the following fields:
497493
[exec-plugin]: https://github.com/gdt-dev/gdt/tree/ecee17249e1fa10147cf9191be0358923da44094/plugin/exec
498494
[http-plugin]: https://github.com/gdt-dev/http
499495
[kube-plugin]: https://github.com/gdt-dev/kube
500-
[timeout]: https://github.com/gdt-dev/gdt/blob/2791e11105fd3c36d1f11a7d111e089be7cdc84c/types/timeout.go#L11-L22
501496
[wait]: https://github.com/gdt-dev/gdt/blob/2791e11105fd3c36d1f11a7d111e089be7cdc84c/types/wait.go#L11-L25
502497

503498
#### `exec` test spec structure

plugin/exec/eval_test.go

Lines changed: 46 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -16,10 +16,15 @@ import (
1616
"testing"
1717

1818
gdtcontext "github.com/gdt-dev/gdt/context"
19+
execplugin "github.com/gdt-dev/gdt/plugin/exec"
1920
"github.com/gdt-dev/gdt/scenario"
2021
"github.com/stretchr/testify/require"
2122
)
2223

24+
func init() {
25+
execplugin.OverrideDefaultTimeout("0.5s")
26+
}
27+
2328
var failFlag = flag.Bool("fail", false, "run tests expected to fail")
2429

2530
func TestNoExitCodeSimpleCommand(t *testing.T) {
@@ -161,6 +166,45 @@ func TestContainsNoneOf(t *testing.T) {
161166
require.Nil(err)
162167
}
163168

169+
func TestFailExecTimeoutPluginDefault(t *testing.T) {
170+
if !*failFlag {
171+
t.Skip("skipping without -fail flag")
172+
}
173+
require := require.New(t)
174+
175+
fp := filepath.Join("testdata", "timeout-plugin-default.yaml")
176+
f, err := os.Open(fp)
177+
require.Nil(err)
178+
179+
s, err := scenario.FromReader(
180+
f,
181+
scenario.WithPath(fp),
182+
)
183+
require.Nil(err)
184+
require.NotNil(s)
185+
186+
ctx := gdtcontext.New(gdtcontext.WithDebug())
187+
err = s.Run(ctx, t)
188+
require.Nil(err)
189+
}
190+
191+
func TestExecTimeoutPluginDefault(t *testing.T) {
192+
require := require.New(t)
193+
target := os.Args[0]
194+
failArgs := []string{
195+
"-test.v",
196+
"-test.run=FailExecTimeoutPluginDefault",
197+
"-fail",
198+
}
199+
outerr, err := exec.Command(target, failArgs...).CombinedOutput()
200+
201+
// The test should have failed...
202+
require.NotNil(err)
203+
debugout := string(outerr)
204+
require.Contains(debugout, "using timeout of 0.5s [plugin default]")
205+
require.Contains(debugout, "assertion failed: timeout exceeded")
206+
}
207+
164208
func TestFailExecSleepTimeout(t *testing.T) {
165209
if !*failFlag {
166210
t.Skip("skipping without -fail flag")
@@ -289,8 +333,8 @@ func TestExecTimeoutCascade(t *testing.T) {
289333
require.NotNil(err)
290334

291335
debugout := string(outerr)
292-
require.Contains(debugout, "using timeout of 500ms (expected: false) [scenario default]")
293-
require.Contains(debugout, "using timeout of 20ms (expected: true)")
336+
require.Contains(debugout, "using timeout of 500ms [scenario default]")
337+
require.Contains(debugout, "using timeout of 20ms")
294338
}
295339

296340
func TestFailExecOnFail(t *testing.T) {

plugin/exec/plugin.go

Lines changed: 12 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -11,6 +11,15 @@ import (
1111
gdttypes "github.com/gdt-dev/gdt/types"
1212
)
1313

14+
var (
15+
DefaultTimeout = "10s"
16+
)
17+
18+
// OverrideDefaultTimeout is only used in testing...
19+
func OverrideDefaultTimeout(d string) {
20+
DefaultTimeout = d
21+
}
22+
1423
func init() {
1524
gdtplugin.Register(Plugin())
1625
}
@@ -24,6 +33,9 @@ type plugin struct{}
2433
func (p *plugin) Info() gdttypes.PluginInfo {
2534
return gdttypes.PluginInfo{
2635
Name: pluginName,
36+
Timeout: &gdttypes.Timeout{
37+
After: DefaultTimeout,
38+
},
2739
}
2840
}
2941

plugin/exec/testdata/sleep-timeout.yaml

Lines changed: 0 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -7,4 +7,3 @@ tests:
77
- exec: sleep 5
88
timeout:
99
after: 50ms
10-
expected: true

plugin/exec/testdata/timeout-cascade.yaml

Lines changed: 0 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -10,4 +10,3 @@ tests:
1010
exec: sleep .25
1111
timeout:
1212
after: 20ms
13-
expected: true
Lines changed: 5 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,5 @@
1+
name: timeout-plugin-default
2+
description: a scenario that tests the plugin default timeout
3+
tests:
4+
- name: uses plugin default timeout
5+
exec: sleep 1

scenario/parse_test.go

Lines changed: 13 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -92,6 +92,18 @@ func TestUnknownSpec(t *testing.T) {
9292
assert.Nil(s)
9393
}
9494

95+
func TestTimeoutScalarOrMap(t *testing.T) {
96+
assert := assert.New(t)
97+
require := require.New(t)
98+
99+
fp := filepath.Join("testdata", "parse", "timeout-scalar-or-map.yaml")
100+
f, err := os.Open(fp)
101+
require.Nil(err)
102+
103+
_, err = scenario.FromReader(f, scenario.WithPath(fp))
104+
assert.Nil(err)
105+
}
106+
95107
func TestBadTimeout(t *testing.T) {
96108
assert := assert.New(t)
97109
require := require.New(t)
@@ -101,7 +113,7 @@ func TestBadTimeout(t *testing.T) {
101113
require.Nil(err)
102114

103115
s, err := scenario.FromReader(f, scenario.WithPath(fp))
104-
assert.ErrorIs(err, errors.ErrExpectedMap)
116+
assert.ErrorIs(err, errors.ErrExpectedScalarOrMap)
105117
assert.Nil(s)
106118
}
107119

scenario/run.go

Lines changed: 17 additions & 6 deletions
Original file line numberDiff line numberDiff line change
@@ -73,6 +73,7 @@ func (s *Scenario) Run(ctx context.Context, t *testing.T) error {
7373
plugin := s.evalPlugins[idx]
7474
pinfo := plugin.Info()
7575
pretry := pinfo.Retry
76+
ptimeout := pinfo.Timeout
7677

7778
// Create a brand new context that inherits the top-level context's
7879
// cancel func. We want to set deadlines for each test spec and if
@@ -88,7 +89,7 @@ func (s *Scenario) Run(ctx context.Context, t *testing.T) error {
8889
time.Sleep(wait.BeforeDuration())
8990
}
9091

91-
to := getTimeout(ctx, sb.Timeout, scDefaults)
92+
to := getTimeout(ctx, sb.Timeout, ptimeout, scDefaults)
9293
if to != nil {
9394
var cancel context.CancelFunc
9495
specCtx, cancel = context.WithTimeout(specCtx, to.Duration())
@@ -191,26 +192,36 @@ func (s *Scenario) Run(ctx context.Context, t *testing.T) error {
191192

192193
// getTimeout returns the timeout value for the test spec. If the spec has a
193194
// timeout override, we use that. Otherwise, we inspect the scenario's defaults
194-
// and, if present, use that timeout.
195+
// and, if present, use that timeout. If the scenario's defaults for not
196+
// indicate a timeout configuration, we ask the plugin if it has timeout
197+
// defaults and use that.
195198
func getTimeout(
196199
ctx context.Context,
197200
specTimeout *gdttypes.Timeout,
201+
pluginTimeout *gdttypes.Timeout,
198202
scenDefaults *Defaults,
199203
) *gdttypes.Timeout {
200204
if specTimeout != nil {
201205
debug.Println(
202-
ctx, "using timeout of %s (expected: %t)",
203-
specTimeout.After, specTimeout.Expected,
206+
ctx, "using timeout of %s",
207+
specTimeout.After,
204208
)
205209
return specTimeout
206210
}
207211
if scenDefaults != nil && scenDefaults.Timeout != nil {
208212
debug.Println(
209-
ctx, "using timeout of %s (expected: %t) [scenario default]",
210-
scenDefaults.Timeout.After, scenDefaults.Timeout.Expected,
213+
ctx, "using timeout of %s [scenario default]",
214+
scenDefaults.Timeout.After,
211215
)
212216
return scenDefaults.Timeout
213217
}
218+
if pluginTimeout != nil {
219+
debug.Println(
220+
ctx, "using timeout of %s [plugin default]",
221+
pluginTimeout.After,
222+
)
223+
return pluginTimeout
224+
}
214225
return nil
215226
}
216227

scenario/testdata/parse/fail/bad-timeout.yaml

Lines changed: 2 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -2,4 +2,5 @@ name: bad-timeout
22
description: a scenario with an invalid timeout spec
33
tests:
44
- foo: baz
5-
timeout: notatimeout
5+
timeout:
6+
- one
Lines changed: 8 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,8 @@
1+
name: timeout-scalar-or-map
2+
description: a scenario with both scalar and object timeout spec
3+
tests:
4+
- foo: baz
5+
timeout: 1s
6+
- foo: bar
7+
timeout:
8+
after: 1s

types/plugin.go

Lines changed: 3 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -15,6 +15,9 @@ type PluginInfo struct {
1515
Aliases []string
1616
// Description describes what types of tests the plugin can handle.
1717
Description string
18+
// Timeout is a Timeout that should be used by default for test specs of
19+
// this plugin.
20+
Timeout *Timeout
1821
// Retry is a Retry that should be used by default for test specs of this
1922
// plugin.
2023
Retry *Retry

types/spec.go

Lines changed: 13 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -105,12 +105,20 @@ func (s *Spec) UnmarshalYAML(node *yaml.Node) error {
105105
}
106106
s.Description = valNode.Value
107107
case "timeout":
108-
if valNode.Kind != yaml.MappingNode {
109-
return errors.ExpectedMapAt(valNode)
110-
}
111108
var to *Timeout
112-
if err := valNode.Decode(&to); err != nil {
113-
return errors.ExpectedTimeoutAt(valNode)
109+
switch valNode.Kind {
110+
case yaml.MappingNode:
111+
// We support the old-style timeout:after
112+
if err := valNode.Decode(&to); err != nil {
113+
return errors.ExpectedTimeoutAt(valNode)
114+
}
115+
case yaml.ScalarNode:
116+
// We also support a straight string duration
117+
to = &Timeout{
118+
After: valNode.Value,
119+
}
120+
default:
121+
return errors.ExpectedScalarOrMapAt(valNode)
114122
}
115123
_, err := time.ParseDuration(to.After)
116124
if err != nil {

types/timeout.go

Lines changed: 0 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -16,9 +16,6 @@ type Timeout struct {
1616
// Specify a duration using Go's time duration string.
1717
// See https://pkg.go.dev/time#ParseDuration
1818
After string `yaml:"after,omitempty"`
19-
// Expected indicates whether the timeout is expected to be exceeded. This
20-
// is mostly useful for unit testing of the timeout functionality itself.
21-
Expected bool `yaml:"expected,omitempty"`
2219
}
2320

2421
// Duration returns the time duration of the Timeout

0 commit comments

Comments
 (0)