Skip to content

Commit 35a5d58

Browse files
authored
Merge pull request #2 from gdt-dev/issue-8
fix: call t.Error() from test spec
2 parents 044c1fe + cd15a65 commit 35a5d58

10 files changed

+158
-86
lines changed

errors.go

+18
Original file line numberDiff line numberDiff line change
@@ -13,6 +13,15 @@ import (
1313
)
1414

1515
var (
16+
// ErrExpectedMapOrYAMLString is returned when a field that can contain a
17+
// map[string]interface{} or an embedded YAML string did not contain either
18+
// of those things.
19+
// TODO(jaypipes): Move to gdt core?
20+
ErrExpectedMapOrYAMLString = fmt.Errorf(
21+
"%w: expected either map[string]interface{} "+
22+
"or a string with embedded YAML",
23+
gdterrors.ErrParse,
24+
)
1625
// ErrMoreThanOneShortcut is returned when the test author included
1726
// more than one shortcut (e.g. `kube.create` or `kube.apply`) in the same
1827
// test spec.
@@ -119,6 +128,15 @@ var (
119128
)
120129
)
121130

131+
// ExpectedMapOrYAMLStringAt returns ErrExpectedMapOrYAMLString for a given
132+
// YAML node
133+
func ExpectedMapOrYAMLStringAt(node *yaml.Node) error {
134+
return fmt.Errorf(
135+
"%w at line %d, column %d",
136+
ErrExpectedMapOrYAMLString, node.Line, node.Column,
137+
)
138+
}
139+
122140
// KubeConfigNotFound returns ErrKubeConfigNotFound for a given filepath
123141
func KubeConfigNotFound(path string) error {
124142
return fmt.Errorf("%w: %s", ErrKubeConfigNotFound, path)

eval.go

+11
Original file line numberDiff line numberDiff line change
@@ -16,6 +16,7 @@ import (
1616
"time"
1717

1818
backoff "github.com/cenkalti/backoff/v4"
19+
gdtcontext "github.com/gdt-dev/gdt/context"
1920
"github.com/gdt-dev/gdt/debug"
2021
gdterrors "github.com/gdt-dev/gdt/errors"
2122
"github.com/gdt-dev/gdt/parse"
@@ -60,6 +61,16 @@ func (s *Spec) Eval(ctx context.Context, t *testing.T) *result.Result {
6061
if s.Kube.Apply != "" {
6162
res = s.apply(ctx, t, c)
6263
}
64+
for _, failure := range res.Failures() {
65+
if gdtcontext.TimedOut(ctx, failure) {
66+
to := s.Timeout
67+
if to != nil && !to.Expected {
68+
t.Error(gdterrors.TimeoutExceeded(to.After, failure))
69+
}
70+
} else {
71+
t.Error(failure)
72+
}
73+
}
6374
})
6475
return res
6576
}

eval_test.go

-32
Original file line numberDiff line numberDiff line change
@@ -5,48 +5,16 @@
55
package kube_test
66

77
import (
8-
"context"
98
"os"
109
"path/filepath"
1110
"testing"
1211

1312
"github.com/gdt-dev/gdt"
1413
gdtcontext "github.com/gdt-dev/gdt/context"
1514
kindfix "github.com/gdt-dev/kube/fixtures/kind"
16-
"github.com/stretchr/testify/assert"
1715
"github.com/stretchr/testify/require"
1816
)
1917

20-
func TestUnknownKubeContextInSpec(t *testing.T) {
21-
require := require.New(t)
22-
assert := assert.New(t)
23-
24-
fp := filepath.Join("testdata", "parse", "fail", "unknown-context.yaml")
25-
26-
s, err := gdt.From(fp)
27-
require.Nil(err)
28-
require.NotNil(s)
29-
30-
err = s.Run(context.TODO(), t)
31-
assert.NotNil(err)
32-
assert.ErrorContains(err, "context \"unknownctx\" does not exist")
33-
}
34-
35-
func TestUnknownKubeContextInDefaults(t *testing.T) {
36-
require := require.New(t)
37-
assert := assert.New(t)
38-
39-
fp := filepath.Join("testdata", "parse", "fail", "unknown-context-in-defaults.yaml")
40-
41-
s, err := gdt.From(fp)
42-
require.Nil(err)
43-
require.NotNil(s)
44-
45-
err = s.Run(context.TODO(), t)
46-
require.NotNil(err)
47-
assert.ErrorContains(err, "context \"unknownctx\" does not exist")
48-
}
49-
5018
func TestListPodsEmpty(t *testing.T) {
5119
skipIfKind(t)
5220
require := require.New(t)

go.mod

+1-1
Original file line numberDiff line numberDiff line change
@@ -4,7 +4,7 @@ go 1.19
44

55
require (
66
github.com/cenkalti/backoff/v4 v4.2.1
7-
github.com/gdt-dev/gdt v1.1.0
7+
github.com/gdt-dev/gdt v1.1.1
88
github.com/samber/lo v1.38.1
99
github.com/stretchr/testify v1.8.4
1010
gopkg.in/yaml.v3 v3.0.1

go.sum

+2-2
Original file line numberDiff line numberDiff line change
@@ -65,8 +65,8 @@ github.com/envoyproxy/protoc-gen-validate v0.1.0/go.mod h1:iSmxcyjqTsJpI2R4NaDN7
6565
github.com/evanphx/json-patch v4.12.0+incompatible h1:4onqiflcdA9EOZ4RxV643DvftH5pOlLGNtQ5lPWQu84=
6666
github.com/evanphx/json-patch/v5 v5.6.0 h1:b91NhWfaz02IuVxO9faSllyAtNXHMPkC5J8sJCLunww=
6767
github.com/evanphx/json-patch/v5 v5.6.0/go.mod h1:G79N1coSVB93tBe7j6PhzjmR3/2VvlbKOFpnXhI9Bw4=
68-
github.com/gdt-dev/gdt v1.1.0 h1:+eFYFSibOYCTFKqoACx2CefAbErmBmFWXG7kDioR5do=
69-
github.com/gdt-dev/gdt v1.1.0/go.mod h1:StnyGjC/67u59La2u6fh3HwW9MmodVhKdXcLlkgvNSY=
68+
github.com/gdt-dev/gdt v1.1.1 h1:863WjQr2Oa+1eVKJspw1SRqW72S0Y3UydN0j8WwPYJU=
69+
github.com/gdt-dev/gdt v1.1.1/go.mod h1:StnyGjC/67u59La2u6fh3HwW9MmodVhKdXcLlkgvNSY=
7070
github.com/go-gl/glfw v0.0.0-20190409004039-e6da0acd62b1/go.mod h1:vR7hzQXu2zJy9AVAgeJqvqgH9Q5CA+iKCZ2gyEVpxRU=
7171
github.com/go-gl/glfw/v3.3/glfw v0.0.0-20191125211704-12ad95a8df72/go.mod h1:tQ2UAYgL5IevRw8kRxooKSPJfGvJ9fJQFa0TUsXzTg8=
7272
github.com/go-gl/glfw/v3.3/glfw v0.0.0-20200222043503-6f7a984d4dc4/go.mod h1:tQ2UAYgL5IevRw8kRxooKSPJfGvJ9fJQFa0TUsXzTg8=

parse.go

+104-34
Original file line numberDiff line numberDiff line change
@@ -8,6 +8,7 @@ import (
88
"os"
99
"strings"
1010

11+
gdtjson "github.com/gdt-dev/gdt/assertion/json"
1112
"github.com/gdt-dev/gdt/errors"
1213
gdttypes "github.com/gdt-dev/gdt/types"
1314
"github.com/samber/lo"
@@ -84,6 +85,109 @@ func (s *Spec) UnmarshalYAML(node *yaml.Node) error {
8485
return nil
8586
}
8687

88+
func (e *Expect) UnmarshalYAML(node *yaml.Node) error {
89+
if node.Kind != yaml.MappingNode {
90+
return errors.ExpectedMapAt(node)
91+
}
92+
// maps/structs are stored in a top-level Node.Content field which is a
93+
// concatenated slice of Node pointers in pairs of key/values.
94+
for i := 0; i < len(node.Content); i += 2 {
95+
keyNode := node.Content[i]
96+
if keyNode.Kind != yaml.ScalarNode {
97+
return errors.ExpectedScalarAt(keyNode)
98+
}
99+
key := keyNode.Value
100+
valNode := node.Content[i+1]
101+
switch key {
102+
case "error":
103+
if valNode.Kind != yaml.ScalarNode {
104+
return errors.ExpectedScalarAt(valNode)
105+
}
106+
var v string
107+
if err := valNode.Decode(&v); err != nil {
108+
return err
109+
}
110+
e.Error = v
111+
case "len":
112+
if valNode.Kind != yaml.ScalarNode {
113+
return errors.ExpectedScalarAt(valNode)
114+
}
115+
var v *int
116+
if err := valNode.Decode(&v); err != nil {
117+
return err
118+
}
119+
e.Len = v
120+
case "unknown":
121+
if valNode.Kind != yaml.ScalarNode {
122+
return errors.ExpectedScalarAt(valNode)
123+
}
124+
var v bool
125+
if err := valNode.Decode(&v); err != nil {
126+
return err
127+
}
128+
e.Unknown = v
129+
case "notfound":
130+
if valNode.Kind != yaml.ScalarNode {
131+
return errors.ExpectedScalarAt(valNode)
132+
}
133+
var v bool
134+
if err := valNode.Decode(&v); err != nil {
135+
return err
136+
}
137+
e.NotFound = v
138+
case "json":
139+
if valNode.Kind != yaml.MappingNode {
140+
return errors.ExpectedMapAt(valNode)
141+
}
142+
var v *gdtjson.Expect
143+
if err := valNode.Decode(&v); err != nil {
144+
return err
145+
}
146+
e.JSON = v
147+
case "conditions":
148+
if valNode.Kind != yaml.MappingNode {
149+
return errors.ExpectedMapAt(valNode)
150+
}
151+
var v map[string]*ConditionMatch
152+
if err := valNode.Decode(&v); err != nil {
153+
return err
154+
}
155+
e.Conditions = v
156+
case "matches":
157+
if valNode.Kind == yaml.MappingNode {
158+
var v map[string]interface{}
159+
if err := valNode.Decode(&v); err != nil {
160+
return err
161+
}
162+
e.Matches = v
163+
} else if valNode.Kind == yaml.ScalarNode {
164+
if valNode.Tag == "!!null" {
165+
return ExpectedMapOrYAMLStringAt(valNode)
166+
}
167+
var v string
168+
if err := valNode.Decode(&v); err != nil {
169+
return err
170+
}
171+
if err := validateFileExists(v); err != nil {
172+
return err
173+
}
174+
// inline YAML. check it can be unmarshaled into a
175+
// map[string]interface{}
176+
var m map[string]interface{}
177+
if err := yaml.Unmarshal([]byte(v), &m); err != nil {
178+
return MatchesInvalidUnmarshalError(err)
179+
}
180+
e.Matches = m
181+
} else {
182+
return ExpectedMapOrYAMLStringAt(valNode)
183+
}
184+
default:
185+
return errors.UnknownFieldAt(key, keyNode)
186+
}
187+
}
188+
return nil
189+
}
190+
87191
// validateShortcuts ensures that the test author has specified only a single
88192
// shortcut (e.g. `kube.create`) and that if a shortcut is specified, any
89193
// long-form KubeSpec is not present.
@@ -198,14 +302,6 @@ func validateKubeSpec(s *Spec) error {
198302
}
199303
}
200304
}
201-
if s.Assert != nil {
202-
exp := s.Assert
203-
if exp.Matches != nil {
204-
if err := validateMatches(exp.Matches); err != nil {
205-
return err
206-
}
207-
}
208-
}
209305
return nil
210306
}
211307

@@ -254,29 +350,3 @@ func validateResourceIdentifier(subject string) error {
254350
}
255351
return nil
256352
}
257-
258-
// validateMatches checks what the test author placed in the `Kube.Matches`
259-
// field to see if it contains one of:
260-
// * file path (and checks existence of this file)
261-
// * inline YAML (and checks that can be unmarshaled)
262-
// * map[string]interface{}
263-
func validateMatches(matches interface{}) error {
264-
switch matches.(type) {
265-
case string:
266-
v := matches.(string)
267-
if probablyFilePath(v) {
268-
return validateFileExists(v)
269-
}
270-
// inline YAML. Let's quickly check it can be unmarshaled into a
271-
// map[string]interface{}
272-
var m map[string]interface{}
273-
if err := yaml.Unmarshal([]byte(v), &m); err != nil {
274-
return MatchesInvalidUnmarshalError(err)
275-
}
276-
case map[string]interface{}:
277-
return nil
278-
default:
279-
return MatchesInvalid(matches)
280-
}
281-
return nil
282-
}

parse_test.go

+15-2
Original file line numberDiff line numberDiff line change
@@ -22,7 +22,7 @@ func currentDir() string {
2222
return filepath.Dir(filename)
2323
}
2424

25-
func TestBadDefaults(t *testing.T) {
25+
func TestFailureBadDefaults(t *testing.T) {
2626
assert := assert.New(t)
2727
require := require.New(t)
2828

@@ -138,7 +138,7 @@ func TestFailureCreateFileNotFound(t *testing.T) {
138138
require.Nil(s)
139139
}
140140

141-
func TestDeleteFileNotFound(t *testing.T) {
141+
func TestFailureDeleteFileNotFound(t *testing.T) {
142142
require := require.New(t)
143143
assert := assert.New(t)
144144

@@ -177,6 +177,19 @@ func TestFailureBadMatchesInvalidYAML(t *testing.T) {
177177
require.Nil(s)
178178
}
179179

180+
func TestFailureBadMatchesEmpty(t *testing.T) {
181+
assert := assert.New(t)
182+
require := require.New(t)
183+
184+
fp := filepath.Join("testdata", "parse", "fail", "bad-matches-empty.yaml")
185+
186+
s, err := gdt.From(fp)
187+
require.NotNil(err)
188+
assert.ErrorIs(err, gdtkube.ErrExpectedMapOrYAMLString)
189+
assert.ErrorIs(err, errors.ErrParse)
190+
require.Nil(s)
191+
}
192+
180193
func TestFailureBadMatchesNotMapAny(t *testing.T) {
181194
assert := assert.New(t)
182195
require := require.New(t)
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,7 @@
1+
name: bad-matches-empty
2+
description: "matches is not a string or map[string]interface{}"
3+
tests:
4+
- kube:
5+
get: pods/mypod
6+
assert:
7+
matches:

testdata/parse/fail/unknown-context-in-defaults.yaml

-9
This file was deleted.

testdata/parse/fail/unknown-context.yaml

-6
This file was deleted.

0 commit comments

Comments
 (0)