Skip to content

Commit cafcf9a

Browse files
author
Antoine Pelisse
committed
Allow tests to specify objects in different versions
This will make it possible to parse input objects with different schemas, and test what happens if the schema changes, especially the topology. A few things need to happen in order to make this possible: 1. We need to be able to parse multiple different versions. A ParseableType can only parse one type, so we need to pass in a parser instead. The problem with the parser was that we needed a mapping to know what was the name of the root type. Let's use the version name as the root type. Now we can have multiple parseable type in the test. 2. Since most test actually use the same type with multiple different version, we add a wrapper that let's you re-use the same type with different versions. 3. DeducedType are usually all parsed the same for all versions, so let's also create a helper for that. 4. Now we need to know which version of the object we want to compare to at the end. We need to specify that version in each individual test, but that's better becasue we don't have to necessarily compare in the version of the last operation (implicit becomes explicit).
1 parent 446b504 commit cafcf9a

File tree

11 files changed

+184
-88
lines changed

11 files changed

+184
-88
lines changed

internal/fixture/state.go

Lines changed: 62 additions & 29 deletions
Original file line numberDiff line numberDiff line change
@@ -26,11 +26,39 @@ import (
2626
"sigs.k8s.io/structured-merge-diff/v3/value"
2727
)
2828

29+
// For the sake of tests, a parser is something that can retrieve a
30+
// ParseableType.
31+
type Parser interface {
32+
Type(string) typed.ParseableType
33+
}
34+
35+
// SameVersionParser can be used if all the versions are actually using the same type.
36+
type SameVersionParser struct {
37+
T typed.ParseableType
38+
}
39+
40+
func (p SameVersionParser) Type(_ string) typed.ParseableType {
41+
return p.T
42+
}
43+
44+
// DeducedParser is a parser that is deduced no matter what the version
45+
// specified.
46+
var DeducedParser = SameVersionParser{
47+
T: typed.DeducedParseableType,
48+
}
49+
2950
// State of the current test in terms of live object. One can check at
3051
// any time that Live and Managers match the expectations.
52+
//
53+
// The parser will look for the type by using the APIVersion of the
54+
// object it's trying to parse. If trying to parse a "v1" object, a
55+
// corresponding "v1" type should exist in the schema. If all the
56+
// versions should map to the same type, or to a DeducedParseableType,
57+
// one can use the SameVersionParser or the DeducedParser types defined
58+
// in this package.
3159
type State struct {
3260
Live *typed.TypedValue
33-
Parser typed.ParseableType
61+
Parser Parser
3462
Managers fieldpath.ManagedFields
3563
Updater *merge.Updater
3664
}
@@ -70,9 +98,9 @@ func FixTabsOrDie(in typed.YAMLObject) typed.YAMLObject {
7098
return typed.YAMLObject(bytes.Join(lines, []byte{'\n'}))
7199
}
72100

73-
func (s *State) checkInit() error {
101+
func (s *State) checkInit(version fieldpath.APIVersion) error {
74102
if s.Live == nil {
75-
obj, err := s.Parser.FromUnstructured(nil)
103+
obj, err := s.Parser.Type(string(version)).FromUnstructured(nil)
76104
if err != nil {
77105
return fmt.Errorf("failed to create new empty object: %v", err)
78106
}
@@ -82,7 +110,7 @@ func (s *State) checkInit() error {
82110
}
83111

84112
func (s *State) UpdateObject(tv *typed.TypedValue, version fieldpath.APIVersion, manager string) error {
85-
err := s.checkInit()
113+
err := s.checkInit(version)
86114
if err != nil {
87115
return err
88116
}
@@ -102,15 +130,15 @@ func (s *State) UpdateObject(tv *typed.TypedValue, version fieldpath.APIVersion,
102130

103131
// Update the current state with the passed in object
104132
func (s *State) Update(obj typed.YAMLObject, version fieldpath.APIVersion, manager string) error {
105-
tv, err := s.Parser.FromYAML(FixTabsOrDie(obj))
133+
tv, err := s.Parser.Type(string(version)).FromYAML(FixTabsOrDie(obj))
106134
if err != nil {
107135
return err
108136
}
109137
return s.UpdateObject(tv, version, manager)
110138
}
111139

112140
func (s *State) ApplyObject(tv *typed.TypedValue, version fieldpath.APIVersion, manager string, force bool) error {
113-
err := s.checkInit()
141+
err := s.checkInit(version)
114142
if err != nil {
115143
return err
116144
}
@@ -130,7 +158,7 @@ func (s *State) ApplyObject(tv *typed.TypedValue, version fieldpath.APIVersion,
130158

131159
// Apply the passed in object to the current state
132160
func (s *State) Apply(obj typed.YAMLObject, version fieldpath.APIVersion, manager string, force bool) error {
133-
tv, err := s.Parser.FromYAML(FixTabsOrDie(obj))
161+
tv, err := s.Parser.Type(string(version)).FromYAML(FixTabsOrDie(obj))
134162
if err != nil {
135163
return err
136164
}
@@ -139,16 +167,20 @@ func (s *State) Apply(obj typed.YAMLObject, version fieldpath.APIVersion, manage
139167

140168
// CompareLive takes a YAML string and returns the comparison with the
141169
// current live object or an error.
142-
func (s *State) CompareLive(obj typed.YAMLObject) (*typed.Comparison, error) {
170+
func (s *State) CompareLive(obj typed.YAMLObject, version fieldpath.APIVersion) (*typed.Comparison, error) {
143171
obj = FixTabsOrDie(obj)
144-
if err := s.checkInit(); err != nil {
172+
if err := s.checkInit(version); err != nil {
173+
return nil, err
174+
}
175+
tv, err := s.Parser.Type(string(version)).FromYAML(obj)
176+
if err != nil {
145177
return nil, err
146178
}
147-
tv, err := s.Parser.FromYAML(obj)
179+
live, err := s.Updater.Converter.Convert(s.Live, version)
148180
if err != nil {
149181
return nil, err
150182
}
151-
return s.Live.Compare(tv)
183+
return live.Compare(tv)
152184
}
153185

154186
// dummyConverter doesn't convert, it just returns the same exact object, as long as a version is provided.
@@ -171,7 +203,7 @@ func (dummyConverter) IsMissingVersionError(err error) bool {
171203
// Operation is a step that will run when building a table-driven test.
172204
type Operation interface {
173205
run(*State) error
174-
preprocess(typed.ParseableType) (Operation, error)
206+
preprocess(Parser) (Operation, error)
175207
}
176208

177209
func hasConflict(conflicts merge.Conflicts, conflict merge.Conflict) bool {
@@ -214,8 +246,8 @@ func (a Apply) run(state *State) error {
214246
return p.run(state)
215247
}
216248

217-
func (a Apply) preprocess(parser typed.ParseableType) (Operation, error) {
218-
tv, err := parser.FromYAML(FixTabsOrDie(a.Object))
249+
func (a Apply) preprocess(parser Parser) (Operation, error) {
250+
tv, err := parser.Type(string(a.APIVersion)).FromYAML(FixTabsOrDie(a.Object))
219251
if err != nil {
220252
return nil, err
221253
}
@@ -260,7 +292,7 @@ func (a ApplyObject) run(state *State) error {
260292
return nil
261293
}
262294

263-
func (a ApplyObject) preprocess(parser typed.ParseableType) (Operation, error) {
295+
func (a ApplyObject) preprocess(parser Parser) (Operation, error) {
264296
return a, nil
265297
}
266298

@@ -278,8 +310,8 @@ func (f ForceApply) run(state *State) error {
278310
return state.Apply(f.Object, f.APIVersion, f.Manager, true)
279311
}
280312

281-
func (f ForceApply) preprocess(parser typed.ParseableType) (Operation, error) {
282-
tv, err := parser.FromYAML(FixTabsOrDie(f.Object))
313+
func (f ForceApply) preprocess(parser Parser) (Operation, error) {
314+
tv, err := parser.Type(string(f.APIVersion)).FromYAML(FixTabsOrDie(f.Object))
283315
if err != nil {
284316
return nil, err
285317
}
@@ -304,7 +336,7 @@ func (f ForceApplyObject) run(state *State) error {
304336
return state.ApplyObject(f.Object, f.APIVersion, f.Manager, true)
305337
}
306338

307-
func (f ForceApplyObject) preprocess(parser typed.ParseableType) (Operation, error) {
339+
func (f ForceApplyObject) preprocess(parser Parser) (Operation, error) {
308340
return f, nil
309341
}
310342

@@ -322,8 +354,8 @@ func (u Update) run(state *State) error {
322354
return state.Update(u.Object, u.APIVersion, u.Manager)
323355
}
324356

325-
func (u Update) preprocess(parser typed.ParseableType) (Operation, error) {
326-
tv, err := parser.FromYAML(FixTabsOrDie(u.Object))
357+
func (u Update) preprocess(parser Parser) (Operation, error) {
358+
tv, err := parser.Type(string(u.APIVersion)).FromYAML(FixTabsOrDie(u.Object))
327359
if err != nil {
328360
return nil, err
329361
}
@@ -348,7 +380,7 @@ func (u UpdateObject) run(state *State) error {
348380
return state.UpdateObject(u.Object, u.APIVersion, u.Manager)
349381
}
350382

351-
func (f UpdateObject) preprocess(parser typed.ParseableType) (Operation, error) {
383+
func (f UpdateObject) preprocess(parser Parser) (Operation, error) {
352384
return f, nil
353385
}
354386

@@ -364,6 +396,9 @@ type TestCase struct {
364396
// Object, if not empty, is the object as it's expected to
365397
// be after all the operations are run.
366398
Object typed.YAMLObject
399+
// APIVersion should be set if the object is non-empty and
400+
// describes the version of the object to compare to.
401+
APIVersion fieldpath.APIVersion
367402
// Managed, if not nil, is the ManagedFields as expected
368403
// after all operations are run.
369404
Managed fieldpath.ManagedFields
@@ -372,18 +407,18 @@ type TestCase struct {
372407
}
373408

374409
// Test runs the test-case using the given parser and a dummy converter.
375-
func (tc TestCase) Test(parser typed.ParseableType) error {
410+
func (tc TestCase) Test(parser Parser) error {
376411
return tc.TestWithConverter(parser, &dummyConverter{})
377412
}
378413

379414
// Bench runs the test-case using the given parser and a dummy converter, but
380415
// doesn't check exit conditions--see the comment for BenchWithConverter.
381-
func (tc TestCase) Bench(parser typed.ParseableType) error {
416+
func (tc TestCase) Bench(parser Parser) error {
382417
return tc.BenchWithConverter(parser, &dummyConverter{})
383418
}
384419

385420
// Preprocess all the operations by parsing the yaml before-hand.
386-
func (tc TestCase) PreprocessOperations(parser typed.ParseableType) error {
421+
func (tc TestCase) PreprocessOperations(parser Parser) error {
387422
for i := range tc.Ops {
388423
op, err := tc.Ops[i].preprocess(parser)
389424
if err != nil {
@@ -398,7 +433,7 @@ func (tc TestCase) PreprocessOperations(parser typed.ParseableType) error {
398433
// but doesn't do any comparison operations aftewards; you should probably run
399434
// TestWithConverter once and reset the benchmark, to make sure the test case
400435
// actually passes..
401-
func (tc TestCase) BenchWithConverter(parser typed.ParseableType, converter merge.Converter) error {
436+
func (tc TestCase) BenchWithConverter(parser Parser, converter merge.Converter) error {
402437
state := State{
403438
Updater: &merge.Updater{Converter: converter},
404439
Parser: parser,
@@ -418,7 +453,7 @@ func (tc TestCase) BenchWithConverter(parser typed.ParseableType, converter merg
418453
}
419454

420455
// TestWithConverter runs the test-case using the given parser and converter.
421-
func (tc TestCase) TestWithConverter(parser typed.ParseableType, converter merge.Converter) error {
456+
func (tc TestCase) TestWithConverter(parser Parser, converter merge.Converter) error {
422457
state := State{
423458
Updater: &merge.Updater{Converter: converter},
424459
Parser: parser,
@@ -434,8 +469,6 @@ func (tc TestCase) TestWithConverter(parser typed.ParseableType, converter merge
434469
return fmt.Errorf("fails if unions are on: %v", err)
435470
}
436471
}
437-
// We currently don't have any test that converts, we can take
438-
// care of that later.
439472
for i, ops := range tc.Ops {
440473
err := ops.run(&state)
441474
if err != nil {
@@ -445,7 +478,7 @@ func (tc TestCase) TestWithConverter(parser typed.ParseableType, converter merge
445478

446479
// If LastObject was specified, compare it with LiveState
447480
if tc.Object != typed.YAMLObject("") {
448-
comparison, err := state.CompareLive(tc.Object)
481+
comparison, err := state.CompareLive(tc.Object, tc.APIVersion)
449482
if err != nil {
450483
return fmt.Errorf("failed to compare live with config: %v", err)
451484
}

0 commit comments

Comments
 (0)