sigs.k8s.io/prow@v0.0.0-20240503223140-c5e374dc7eb1/pkg/genyaml/genyaml_test.go (about) 1 /* 2 Copyright 2019 The Kubernetes Authors. 3 4 Licensed under the Apache License, Version 2.0 (the "License"); 5 you may not use this file except in compliance with the License. 6 You may obtain a copy of the License at 7 8 http://www.apache.org/licenses/LICENSE-2.0 9 10 Unless required by applicable law or agreed to in writing, software 11 distributed under the License is distributed on an "AS IS" BASIS, 12 WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 13 See the License for the specific language governing permissions and 14 limitations under the License. 15 */ 16 17 package genyaml 18 19 import ( 20 "bytes" 21 "encoding/json" 22 "os" 23 "path/filepath" 24 "strings" 25 "testing" 26 27 yaml3 "gopkg.in/yaml.v3" 28 29 simplealiases "sigs.k8s.io/prow/pkg/genyaml/testdata/alias_simple_types" 30 aliases "sigs.k8s.io/prow/pkg/genyaml/testdata/alias_types" 31 embedded "sigs.k8s.io/prow/pkg/genyaml/testdata/embedded_structs" 32 inlines "sigs.k8s.io/prow/pkg/genyaml/testdata/inline_structs" 33 interfaces "sigs.k8s.io/prow/pkg/genyaml/testdata/interface_types" 34 multiline "sigs.k8s.io/prow/pkg/genyaml/testdata/multiline_comments" 35 nested "sigs.k8s.io/prow/pkg/genyaml/testdata/nested_structs" 36 tags "sigs.k8s.io/prow/pkg/genyaml/testdata/no_tags" 37 omit "sigs.k8s.io/prow/pkg/genyaml/testdata/omit_if_empty" 38 pointers "sigs.k8s.io/prow/pkg/genyaml/testdata/pointer_types" 39 primitives "sigs.k8s.io/prow/pkg/genyaml/testdata/primitive_types" 40 private "sigs.k8s.io/prow/pkg/genyaml/testdata/private_members" 41 sequence "sigs.k8s.io/prow/pkg/genyaml/testdata/sequence_items" 42 ) 43 44 const ( 45 testDir = "testdata" 46 ) 47 48 func resolvePath(t *testing.T, filename string) string { 49 name := filepath.Base(t.Name()) 50 return strings.ToLower(filepath.Join(testDir, name, filename)) 51 } 52 53 func fileName(t *testing.T, extension string) string { 54 name := filepath.Base(t.Name()) 55 return strings.ToLower(filepath.Join(testDir, name, name+"."+extension)) 56 } 57 58 func readFile(t *testing.T, extension string) []byte { 59 data, err := os.ReadFile(fileName(t, extension)) 60 if err != nil { 61 t.Errorf("Failed reading .%s file: %v.", extension, err) 62 } 63 64 return data 65 } 66 67 func TestFmtRawDoc(t *testing.T) { 68 tests := []struct { 69 name string 70 rawDoc string 71 expected string 72 }{ 73 { 74 name: "Single line comment", 75 rawDoc: "Owners of the cat.", 76 expected: "Owners of the cat.", 77 }, 78 { 79 name: "Multi line comment", 80 rawDoc: "StringField comment\nsecond line\nthird line", 81 expected: `StringField comment 82 second line 83 third line`, 84 }, 85 { 86 name: "Delete trailing space(s)", 87 rawDoc: "Some comment ", 88 expected: "Some comment", 89 }, 90 { 91 name: "Delete trailing newline(s)", 92 rawDoc: "Some comment\n\n\n\n", 93 expected: "Some comment", 94 }, 95 { 96 name: "Escape double quote(s)", 97 rawDoc: `"Some comment"`, 98 expected: `"Some comment"`, 99 }, 100 { 101 name: "Convert tab to space", 102 rawDoc: "tab tab tabtab", 103 expected: "tab tab tabtab", 104 }, 105 { 106 name: "Strip TODO prefixed comment", 107 rawDoc: "TODO: some future work", 108 expected: "", 109 }, 110 { 111 name: "Strip + prefixed comment", 112 rawDoc: "+: some future work", 113 expected: "", 114 }, 115 { 116 name: "Strip TODO prefixed comment from multi line comment", 117 rawDoc: "TODO: some future work\nmore comment", 118 expected: "more comment", 119 }, 120 { 121 name: "Strip + prefixed comment from multi line comment", 122 rawDoc: "+: some future work\nmore comment", 123 expected: "more comment", 124 }, 125 } 126 127 for _, test := range tests { 128 t.Run(test.name, func(t *testing.T) { 129 actualFormattedRawDoc := fmtRawDoc(test.rawDoc) 130 131 if actualFormattedRawDoc != test.expected { 132 t.Fatalf("Expected %q, but got result %q", test.expected, actualFormattedRawDoc) 133 } 134 }) 135 } 136 } 137 138 func TestInjectComment(t *testing.T) { 139 tests := []struct { 140 name string 141 typeSpec string 142 actualNode *yaml3.Node 143 expectedNode *yaml3.Node 144 }{ 145 { 146 name: "Inject comments", 147 typeSpec: "ExampleStruct", 148 actualNode: &yaml3.Node{ 149 Kind: yaml3.DocumentNode, 150 Content: []*yaml3.Node{ 151 { 152 Kind: yaml3.MappingNode, 153 Tag: "!!map", 154 Content: []*yaml3.Node{ 155 { 156 Kind: yaml3.ScalarNode, 157 Tag: "!!str", 158 Value: "exampleKey", 159 }, 160 { 161 Kind: yaml3.ScalarNode, 162 Tag: "!!bool", 163 Value: "true", 164 }, 165 }, 166 }, 167 }, 168 }, 169 expectedNode: &yaml3.Node{ 170 Kind: yaml3.DocumentNode, 171 Content: []*yaml3.Node{ 172 { 173 Kind: yaml3.MappingNode, 174 Tag: "!!map", 175 Content: []*yaml3.Node{ 176 { 177 Kind: yaml3.ScalarNode, 178 Tag: "!!str", 179 Value: "exampleKey", 180 HeadComment: "Some comment", 181 }, 182 { 183 Kind: yaml3.ScalarNode, 184 Tag: "!!bool", 185 Value: "true", 186 }, 187 }, 188 }, 189 }, 190 }, 191 }, 192 } 193 194 for _, test := range tests { 195 t.Run(test.name, func(t *testing.T) { 196 cm, err := NewCommentMap(nil) 197 if err != nil { 198 t.Fatalf("Failed to construct comment map: %v", err) 199 } 200 201 if err := json.Unmarshal(readFile(t, "json"), &cm.comments); err != nil { 202 t.Errorf("Unexpected error unmarshalling JSON to comments: %v.", err) 203 } 204 205 cm.injectComment(test.actualNode, []string{test.typeSpec}, 0) 206 207 expectedYaml, err := yaml3.Marshal(test.expectedNode) 208 if err != nil { 209 t.Errorf("Unexpected error marshalling Node to YAML: %v.", err) 210 } 211 212 actualYaml, err := yaml3.Marshal(test.actualNode) 213 if err != nil { 214 t.Errorf("Unexpected error marshalling Node to YAML: %v.", err) 215 } 216 217 if !bytes.Equal(expectedYaml, actualYaml) { 218 t.Error("Expected yaml snippets to not be equal.") 219 } 220 }) 221 } 222 } 223 224 func TestAddPath(t *testing.T) { 225 tests := []struct { 226 name string 227 paths []string 228 expected bool 229 }{ 230 { 231 name: "Single path", 232 paths: []string{"example_config.go"}, 233 expected: true, 234 }, 235 { 236 name: "Multiple paths", 237 paths: []string{"example_config1.go", "example_config2.go"}, 238 expected: true, 239 }, 240 } 241 242 for _, test := range tests { 243 t.Run(test.name, func(t *testing.T) { 244 var resolved []string 245 for _, file := range test.paths { 246 resolved = append(resolved, resolvePath(t, file)) 247 } 248 cm, err := NewCommentMap(nil, resolved...) 249 if err != nil { 250 t.Fatalf("failed to construct comment map: %v", err) 251 } 252 253 expectedComments := readFile(t, "json") 254 actualComments, err := json.MarshalIndent(cm.comments, "", " ") 255 256 if err != nil { 257 t.Errorf("Unexpected error generating JSON from comments: %v.", err) 258 } 259 260 equal := bytes.Equal(expectedComments, actualComments) 261 262 if equal != test.expected { 263 t.Errorf("Expected comments equality to be: %t.", test.expected) 264 } 265 }) 266 } 267 268 } 269 270 func TestGenYAML(t *testing.T) { 271 tests := []struct { 272 name string 273 paths []string 274 rawContents map[string][]byte 275 structObj interface{} 276 expectedRawYaml []byte 277 expected bool 278 }{ 279 { 280 name: "alias types", 281 paths: []string{"example_config.go"}, 282 structObj: &aliases.Alias{ 283 StringField: "string", 284 }, 285 expected: true, 286 }, 287 { 288 name: "also-read-raw", 289 rawContents: map[string][]byte{ 290 "alias_types.yaml": []byte(`package alias_types 291 type Alias = AliasedType 292 type AliasedType struct { 293 // StringField comment 294 StringField string ` + "`json:\"string\"`" + ` 295 }`), 296 }, 297 structObj: &aliases.Alias{ 298 StringField: "string", 299 }, 300 expectedRawYaml: []byte(`# StringField comment 301 string: string 302 `), 303 expected: true, 304 }, 305 { 306 name: "alias simple types", 307 paths: []string{"example_config.go"}, 308 structObj: &simplealiases.SimpleAliases{ 309 AliasField: simplealiases.Alias("string"), 310 }, 311 expected: true, 312 }, 313 { 314 name: "primitive types", 315 paths: []string{"example_config.go"}, 316 structObj: &primitives.Primitives{ 317 StringField: "string", 318 BooleanField: true, 319 IntegerField: 1, 320 }, 321 expected: true, 322 }, 323 { 324 name: "multiline comments", 325 paths: []string{"example_config.go"}, 326 structObj: &multiline.Multiline{ 327 StringField1: "string1", 328 StringField2: "string2", 329 StringField3: "string3", 330 StringField4: "string4", 331 StringField5: "string5", 332 StringField6: "string6", 333 }, 334 expected: true, 335 }, 336 { 337 name: "nested structs", 338 paths: []string{"example_config.go"}, 339 structObj: &nested.Parent{ 340 Age: 35, 341 Children: []nested.Child{ 342 {Name: "Jimbo", Age: 4}, 343 {Name: "Jenny", Age: 5}, 344 }, 345 Name: "Mildred", 346 }, 347 expected: true, 348 }, 349 { 350 name: "inline structs", 351 paths: []string{"example_config.go"}, 352 structObj: &inlines.Resource{ 353 Metadata: inlines.Metadata{ 354 Name: "test", 355 }, 356 }, 357 expected: true, 358 }, 359 { 360 name: "embedded structs", 361 paths: []string{"example_config.go"}, 362 structObj: &embedded.Building{ 363 Address: "123 North Main Street", 364 Bathroom: embedded.Bathroom{Width: 100, Height: 200}, 365 Bedroom: embedded.Bedroom{Width: 100, Height: 200}, 366 }, 367 expected: true, 368 }, 369 { 370 name: "no tags", 371 paths: []string{"example_config.go"}, 372 structObj: &tags.Tagless{ 373 StringField: "string", 374 BooleanField: true, 375 IntegerField: 1, 376 }, 377 expected: true, 378 }, 379 { 380 name: "omit if empty", 381 paths: []string{"example_config.go"}, 382 structObj: &omit.OmitEmptyStrings{ 383 StringFieldOmitEmpty: "", 384 StringFieldKeepEmpty: "", 385 BooleanField: true, 386 IntegerField: 1, 387 }, 388 expected: true, 389 }, 390 { 391 name: "pointer types", 392 paths: []string{"example_config.go"}, 393 structObj: &pointers.Zoo{ 394 Employees: []*pointers.Employee{ 395 { 396 Name: "Jim", 397 Age: 22, 398 }, 399 { 400 Name: "Jane", 401 Age: 21, 402 }, 403 }, 404 }, 405 expected: true, 406 }, 407 { 408 name: "private members", 409 paths: []string{"example_config.go"}, 410 structObj: private.NewPerson("gamer123", "password123"), 411 expected: true, 412 }, 413 { 414 name: "sequence items", 415 paths: []string{"example_config.go"}, 416 structObj: &sequence.Recipe{ 417 Ingredients: []sequence.Ingredient{ 418 { 419 Name: "potatoes", 420 Amount: 1, 421 }, 422 { 423 Name: "eggs", 424 Amount: 2, 425 }, 426 }, 427 }, 428 expected: true, 429 }, 430 { 431 name: "interface types", 432 paths: []string{"example_config.go"}, 433 structObj: &interfaces.Zoo{ 434 Animals: []interfaces.Animal{ 435 &interfaces.Lion{ 436 Name: "Leo", 437 }, 438 &interfaces.Cheetah{ 439 Name: "Charles", 440 }, 441 }, 442 }, 443 // INFO: Interface type comments are not implemented. 444 expected: false, 445 }, 446 } 447 448 for _, test := range tests { 449 t.Run(test.name, func(t *testing.T) { 450 var paths []string 451 for _, path := range test.paths { 452 paths = append(paths, resolvePath(t, path)) 453 } 454 cm, err := NewCommentMap(test.rawContents, paths...) 455 if err != nil { 456 t.Fatalf("failed to construct comment map: %v", err) 457 } 458 expectedYaml := test.expectedRawYaml 459 if len(expectedYaml) == 0 { 460 expectedYaml = readFile(t, "yaml") 461 } 462 463 actualYaml, err := cm.GenYaml(test.structObj) 464 465 if err != nil { 466 t.Errorf("Unexpected error generating YAML from struct: %v.", err) 467 } 468 469 equal := bytes.Equal(expectedYaml, []byte(actualYaml)) 470 471 if equal != test.expected { 472 t.Errorf("Expected yaml snippets equality to be: %t.", test.expected) 473 } 474 }) 475 } 476 }