Skip to content

Commit a5e6bc6

Browse files
authored
Merge pull request #33 from jaypipes/simplify-eval
simplify Evaluable interface and Scenario.Run
2 parents 67bf475 + 36b2569 commit a5e6bc6

File tree

10 files changed

+272
-185
lines changed

10 files changed

+272
-185
lines changed

README.md

Lines changed: 102 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -540,6 +540,108 @@ test spec also contains these fields:
540540
[execspec]: https://github.com/gdt-dev/gdt/blob/2791e11105fd3c36d1f11a7d111e089be7cdc84c/exec/spec.go#L11-L34
541541
[pipeexpect]: https://github.com/gdt-dev/gdt/blob/2791e11105fd3c36d1f11a7d111e089be7cdc84c/exec/assertions.go#L15-L26
542542

543+
### Timeouts and retrying assertions
544+
545+
When evaluating assertions for a test spec, `gdt` inspects the test's
546+
`timeout` value to determine how long to retry the `get` call and recheck
547+
the assertions.
548+
549+
If a test's `timeout` is empty, `gdt` inspects the scenario's
550+
`defaults.timeout` value. If both of those values are empty, `gdt` will look
551+
for any default `timeout` value that the plugin uses.
552+
553+
If you're interested in seeing the individual results of `gdt`'s
554+
assertion-checks for a single `get` call, you can use the `gdt.WithDebug()`
555+
function, like this test function demonstrates:
556+
557+
file: `testdata/matches.yaml`:
558+
559+
```yaml
560+
name: matches
561+
description: create a deployment and check the matches condition succeeds
562+
fixtures:
563+
- kind
564+
tests:
565+
- name: create-deployment
566+
kube:
567+
create: testdata/manifests/nginx-deployment.yaml
568+
- name: deployment-exists
569+
kube:
570+
get: deployments/nginx
571+
assert:
572+
matches:
573+
spec:
574+
replicas: 2
575+
template:
576+
metadata:
577+
labels:
578+
app: nginx
579+
status:
580+
readyReplicas: 2
581+
- name: delete-deployment
582+
kube:
583+
delete: deployments/nginx
584+
```
585+
586+
file: `matches_test.go`
587+
588+
```go
589+
import (
590+
"github.com/gdt-dev/gdt"
591+
_ "github.com/gdt-dev/kube"
592+
kindfix "github.com/gdt-dev/kube/fixture/kind"
593+
)
594+
595+
func TestMatches(t *testing.T) {
596+
fp := filepath.Join("testdata", "matches.yaml")
597+
598+
kfix := kindfix.New()
599+
600+
s, err := gdt.From(fp)
601+
602+
ctx := gdt.NewContext(gdt.WithDebug())
603+
ctx = gdt.RegisterFixture(ctx, "kind", kfix)
604+
s.Run(ctx, t)
605+
}
606+
```
607+
608+
Here's what running `go test -v matches_test.go` would look like:
609+
610+
```
611+
$ go test -v matches_test.go
612+
=== RUN TestMatches
613+
=== RUN TestMatches/matches
614+
=== RUN TestMatches/matches/create-deployment
615+
=== RUN TestMatches/matches/deployment-exists
616+
deployment-exists (try 1 after 1.303µs) ok: false, terminal: false
617+
deployment-exists (try 1 after 1.303µs) failure: assertion failed: match field not equal: $.status.readyReplicas not present in subject
618+
deployment-exists (try 2 after 595.62786ms) ok: false, terminal: false
619+
deployment-exists (try 2 after 595.62786ms) failure: assertion failed: match field not equal: $.status.readyReplicas not present in subject
620+
deployment-exists (try 3 after 1.020003807s) ok: false, terminal: false
621+
deployment-exists (try 3 after 1.020003807s) failure: assertion failed: match field not equal: $.status.readyReplicas not present in subject
622+
deployment-exists (try 4 after 1.760006109s) ok: false, terminal: false
623+
deployment-exists (try 4 after 1.760006109s) failure: assertion failed: match field not equal: $.status.readyReplicas had different values. expected 2 but found 1
624+
deployment-exists (try 5 after 2.772416449s) ok: true, terminal: false
625+
=== RUN TestMatches/matches/delete-deployment
626+
--- PASS: TestMatches (3.32s)
627+
--- PASS: TestMatches/matches (3.30s)
628+
--- PASS: TestMatches/matches/create-deployment (0.01s)
629+
--- PASS: TestMatches/matches/deployment-exists (2.78s)
630+
--- PASS: TestMatches/matches/delete-deployment (0.02s)
631+
PASS
632+
ok command-line-arguments 3.683s
633+
```
634+
635+
You can see from the debug output above that `gdt` created the Deployment and
636+
then did a `kube.get` for the `deployments/nginx` Deployment. Initially
637+
(attempt 1), the `assert.matches` assertion failed because the
638+
`status.readyReplicas` field was not present in the returned resource. `gdt`
639+
retried the `kube.get` call 4 more times (attempts 2-5), with attempts 2 and 3
640+
failed the existence check for the `status.readyReplicas` field and attempt 4
641+
failing the *value* check for the `status.readyReplicas` field being `1`
642+
instead of the expected `2`. Finally, when the Deployment was completely rolled
643+
out, attempt 5 succeeded in all the `assert.matches` assertions.
644+
543645
## Contributing and acknowledgements
544646

545647
`gdt` was inspired by [Gabbi](https://github.com/cdent/gabbi), the excellent

context/context_test.go

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -43,8 +43,8 @@ func (s *fooSpec) UnmarshalYAML(node *yaml.Node) error {
4343
return nil
4444
}
4545

46-
func (s *fooSpec) Eval(ctx context.Context, t *testing.T) *result.Result {
47-
return nil
46+
func (s *fooSpec) Eval(ctx context.Context) (*result.Result, error) {
47+
return nil, nil
4848
}
4949

5050
type fooPlugin struct{}

plugin/exec/action.go

Lines changed: 0 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -8,7 +8,6 @@ import (
88
"bytes"
99
"context"
1010
"os/exec"
11-
"testing"
1211

1312
gdtcontext "github.com/gdt-dev/gdt/context"
1413
"github.com/gdt-dev/gdt/debug"
@@ -38,7 +37,6 @@ type Action struct {
3837
// respectively.
3938
func (a *Action) Do(
4039
ctx context.Context,
41-
t *testing.T,
4240
outbuf *bytes.Buffer,
4341
errbuf *bytes.Buffer,
4442
exitcode *int,

plugin/exec/eval.go

Lines changed: 10 additions & 7 deletions
Original file line numberDiff line numberDiff line change
@@ -7,7 +7,6 @@ package exec
77
import (
88
"bytes"
99
"context"
10-
"testing"
1110

1211
"github.com/gdt-dev/gdt/debug"
1312
gdterrors "github.com/gdt-dev/gdt/errors"
@@ -17,30 +16,34 @@ import (
1716
// Eval performs an action and evaluates the results of that action, returning
1817
// a Result that informs the Scenario about what failed or succeeded about the
1918
// Evaluable's conditions.
20-
func (s *Spec) Eval(ctx context.Context, t *testing.T) *result.Result {
19+
//
20+
// Errors returned by Eval() are **RuntimeErrors**, not failures in assertions.
21+
func (s *Spec) Eval(
22+
ctx context.Context,
23+
) (*result.Result, error) {
2124
outbuf := &bytes.Buffer{}
2225
errbuf := &bytes.Buffer{}
2326

2427
var ec int
2528

26-
if err := s.Do(ctx, t, outbuf, errbuf, &ec); err != nil {
29+
if err := s.Do(ctx, outbuf, errbuf, &ec); err != nil {
2730
if err == gdterrors.ErrTimeoutExceeded {
28-
return result.New(result.WithFailures(gdterrors.ErrTimeoutExceeded))
31+
return result.New(result.WithFailures(gdterrors.ErrTimeoutExceeded)), nil
2932
}
30-
return result.New(result.WithRuntimeError(ExecRuntimeError(err)))
33+
return nil, ExecRuntimeError(err)
3134
}
3235
a := newAssertions(s.Assert, ec, outbuf, errbuf)
3336
if !a.OK(ctx) {
3437
if s.On != nil {
3538
if s.On.Fail != nil {
3639
outbuf.Reset()
3740
errbuf.Reset()
38-
err := s.On.Fail.Do(ctx, t, outbuf, errbuf, nil)
41+
err := s.On.Fail.Do(ctx, outbuf, errbuf, nil)
3942
if err != nil {
4043
debug.Println(ctx, "error in on.fail.exec: %s", err)
4144
}
4245
}
4346
}
4447
}
45-
return result.New(result.WithFailures(a.Failures()...))
48+
return result.New(result.WithFailures(a.Failures()...)), nil
4649
}

plugin/registry_test.go

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -36,8 +36,8 @@ func (s *fooSpec) Base() *gdttypes.Spec {
3636
return &s.Spec
3737
}
3838

39-
func (s *fooSpec) Eval(context.Context, *testing.T) *result.Result {
40-
return nil
39+
func (s *fooSpec) Eval(context.Context) (*result.Result, error) {
40+
return nil, nil
4141
}
4242

4343
func (s *fooSpec) UnmarshalYAML(node *yaml.Node) error {

result/result.go

Lines changed: 0 additions & 34 deletions
Original file line numberDiff line numberDiff line change
@@ -4,13 +4,6 @@
44

55
package result
66

7-
import (
8-
"errors"
9-
"fmt"
10-
11-
gdterrors "github.com/gdt-dev/gdt/errors"
12-
)
13-
147
// Result is returned from a `Evaluable.Eval` execution. It serves two
158
// purposes:
169
//
@@ -23,9 +16,6 @@ import (
2316
// returned in the Result and the `Scenario.Run` method injects that
2417
// information into the context that is supplied to the next Spec's `Run`.
2518
type Result struct {
26-
// err is any error that was returned from the Evaluable's execution. This
27-
// is guaranteed to be a `gdterrors.RuntimeError`.
28-
err error
2919
// failures is the collection of error messages from assertion failures
3020
// that occurred during Eval(). These are *not* `gdterrors.RuntimeError`.
3121
failures []error
@@ -36,17 +26,6 @@ type Result struct {
3626
data map[string]interface{}
3727
}
3828

39-
// HasRuntimeError returns true if the Eval() returned a runtime error, false
40-
// otherwise.
41-
func (r *Result) HasRuntimeError() bool {
42-
return r.err != nil
43-
}
44-
45-
// RuntimeError returns the runtime error
46-
func (r *Result) RuntimeError() error {
47-
return r.err
48-
}
49-
5029
// HasData returns true if any of the run data has been set, false otherwise.
5130
func (r *Result) HasData() bool {
5231
return r.data != nil
@@ -86,19 +65,6 @@ func (r *Result) SetFailures(failures ...error) {
8665

8766
type ResultModifier func(*Result)
8867

89-
// WithRuntimeError modifies the Result with the supplied error
90-
func WithRuntimeError(err error) ResultModifier {
91-
if !errors.Is(err, gdterrors.RuntimeError) {
92-
msg := fmt.Sprintf("expected %s to be a gdterrors.RuntimeError", err)
93-
// panic here because a plugin author incorrectly implemented their
94-
// plugin Spec's Eval() method...
95-
panic(msg)
96-
}
97-
return func(r *Result) {
98-
r.err = err
99-
}
100-
}
101-
10268
// WithData modifies the Result with the supplied run data key and value
10369
func WithData(key string, val interface{}) ResultModifier {
10470
return func(r *Result) {

0 commit comments

Comments
 (0)