github.com/hashicorp/hcl/v2@v2.20.0/merged_test.go (about) 1 // Copyright (c) HashiCorp, Inc. 2 // SPDX-License-Identifier: MPL-2.0 3 4 package hcl 5 6 import ( 7 "fmt" 8 "reflect" 9 "testing" 10 11 "github.com/davecgh/go-spew/spew" 12 ) 13 14 func TestMergedBodiesContent(t *testing.T) { 15 tests := []struct { 16 Bodies []Body 17 Schema *BodySchema 18 Want *BodyContent 19 DiagCount int 20 }{ 21 { 22 []Body{}, 23 &BodySchema{}, 24 &BodyContent{ 25 Attributes: map[string]*Attribute{}, 26 }, 27 0, 28 }, 29 { 30 []Body{}, 31 &BodySchema{ 32 Attributes: []AttributeSchema{ 33 { 34 Name: "name", 35 }, 36 }, 37 }, 38 &BodyContent{ 39 Attributes: map[string]*Attribute{}, 40 }, 41 0, 42 }, 43 { 44 []Body{}, 45 &BodySchema{ 46 Attributes: []AttributeSchema{ 47 { 48 Name: "name", 49 Required: true, 50 }, 51 }, 52 }, 53 &BodyContent{ 54 Attributes: map[string]*Attribute{}, 55 }, 56 1, 57 }, 58 { 59 []Body{ 60 &testMergedBodiesVictim{ 61 HasAttributes: []string{"name"}, 62 }, 63 }, 64 &BodySchema{ 65 Attributes: []AttributeSchema{ 66 { 67 Name: "name", 68 }, 69 }, 70 }, 71 &BodyContent{ 72 Attributes: map[string]*Attribute{ 73 "name": &Attribute{ 74 Name: "name", 75 }, 76 }, 77 }, 78 0, 79 }, 80 { 81 []Body{ 82 &testMergedBodiesVictim{ 83 Name: "first", 84 HasAttributes: []string{"name"}, 85 }, 86 &testMergedBodiesVictim{ 87 Name: "second", 88 HasAttributes: []string{"name"}, 89 }, 90 }, 91 &BodySchema{ 92 Attributes: []AttributeSchema{ 93 { 94 Name: "name", 95 }, 96 }, 97 }, 98 &BodyContent{ 99 Attributes: map[string]*Attribute{ 100 "name": &Attribute{ 101 Name: "name", 102 NameRange: Range{Filename: "first"}, 103 }, 104 }, 105 }, 106 1, 107 }, 108 { 109 []Body{ 110 &testMergedBodiesVictim{ 111 Name: "first", 112 HasAttributes: []string{"name"}, 113 }, 114 &testMergedBodiesVictim{ 115 Name: "second", 116 HasAttributes: []string{"age"}, 117 }, 118 }, 119 &BodySchema{ 120 Attributes: []AttributeSchema{ 121 { 122 Name: "name", 123 }, 124 { 125 Name: "age", 126 }, 127 }, 128 }, 129 &BodyContent{ 130 Attributes: map[string]*Attribute{ 131 "name": &Attribute{ 132 Name: "name", 133 NameRange: Range{Filename: "first"}, 134 }, 135 "age": &Attribute{ 136 Name: "age", 137 NameRange: Range{Filename: "second"}, 138 }, 139 }, 140 }, 141 0, 142 }, 143 { 144 []Body{}, 145 &BodySchema{ 146 Blocks: []BlockHeaderSchema{ 147 { 148 Type: "pizza", 149 }, 150 }, 151 }, 152 &BodyContent{ 153 Attributes: map[string]*Attribute{}, 154 }, 155 0, 156 }, 157 { 158 []Body{ 159 &testMergedBodiesVictim{ 160 HasBlocks: map[string]int{ 161 "pizza": 1, 162 }, 163 }, 164 }, 165 &BodySchema{ 166 Blocks: []BlockHeaderSchema{ 167 { 168 Type: "pizza", 169 }, 170 }, 171 }, 172 &BodyContent{ 173 Attributes: map[string]*Attribute{}, 174 Blocks: Blocks{ 175 { 176 Type: "pizza", 177 }, 178 }, 179 }, 180 0, 181 }, 182 { 183 []Body{ 184 &testMergedBodiesVictim{ 185 HasBlocks: map[string]int{ 186 "pizza": 2, 187 }, 188 }, 189 }, 190 &BodySchema{ 191 Blocks: []BlockHeaderSchema{ 192 { 193 Type: "pizza", 194 }, 195 }, 196 }, 197 &BodyContent{ 198 Attributes: map[string]*Attribute{}, 199 Blocks: Blocks{ 200 { 201 Type: "pizza", 202 }, 203 { 204 Type: "pizza", 205 }, 206 }, 207 }, 208 0, 209 }, 210 { 211 []Body{ 212 &testMergedBodiesVictim{ 213 Name: "first", 214 HasBlocks: map[string]int{ 215 "pizza": 1, 216 }, 217 }, 218 &testMergedBodiesVictim{ 219 Name: "second", 220 HasBlocks: map[string]int{ 221 "pizza": 1, 222 }, 223 }, 224 }, 225 &BodySchema{ 226 Blocks: []BlockHeaderSchema{ 227 { 228 Type: "pizza", 229 }, 230 }, 231 }, 232 &BodyContent{ 233 Attributes: map[string]*Attribute{}, 234 Blocks: Blocks{ 235 { 236 Type: "pizza", 237 DefRange: Range{Filename: "first"}, 238 }, 239 { 240 Type: "pizza", 241 DefRange: Range{Filename: "second"}, 242 }, 243 }, 244 }, 245 0, 246 }, 247 { 248 []Body{ 249 &testMergedBodiesVictim{ 250 Name: "first", 251 }, 252 &testMergedBodiesVictim{ 253 Name: "second", 254 HasBlocks: map[string]int{ 255 "pizza": 2, 256 }, 257 }, 258 }, 259 &BodySchema{ 260 Blocks: []BlockHeaderSchema{ 261 { 262 Type: "pizza", 263 }, 264 }, 265 }, 266 &BodyContent{ 267 Attributes: map[string]*Attribute{}, 268 Blocks: Blocks{ 269 { 270 Type: "pizza", 271 DefRange: Range{Filename: "second"}, 272 }, 273 { 274 Type: "pizza", 275 DefRange: Range{Filename: "second"}, 276 }, 277 }, 278 }, 279 0, 280 }, 281 { 282 []Body{ 283 &testMergedBodiesVictim{ 284 Name: "first", 285 HasBlocks: map[string]int{ 286 "pizza": 2, 287 }, 288 }, 289 &testMergedBodiesVictim{ 290 Name: "second", 291 }, 292 }, 293 &BodySchema{ 294 Blocks: []BlockHeaderSchema{ 295 { 296 Type: "pizza", 297 }, 298 }, 299 }, 300 &BodyContent{ 301 Attributes: map[string]*Attribute{}, 302 Blocks: Blocks{ 303 { 304 Type: "pizza", 305 DefRange: Range{Filename: "first"}, 306 }, 307 { 308 Type: "pizza", 309 DefRange: Range{Filename: "first"}, 310 }, 311 }, 312 }, 313 0, 314 }, 315 { 316 []Body{ 317 &testMergedBodiesVictim{ 318 Name: "first", 319 }, 320 &testMergedBodiesVictim{ 321 Name: "second", 322 }, 323 }, 324 &BodySchema{ 325 Blocks: []BlockHeaderSchema{ 326 { 327 Type: "pizza", 328 }, 329 }, 330 }, 331 &BodyContent{ 332 Attributes: map[string]*Attribute{}, 333 }, 334 0, 335 }, 336 } 337 338 for i, test := range tests { 339 t.Run(fmt.Sprintf("%02d", i), func(t *testing.T) { 340 merged := MergeBodies(test.Bodies) 341 got, diags := merged.Content(test.Schema) 342 343 if len(diags) != test.DiagCount { 344 t.Errorf("Wrong number of diagnostics %d; want %d", len(diags), test.DiagCount) 345 for _, diag := range diags { 346 t.Logf(" - %s", diag) 347 } 348 } 349 350 if !reflect.DeepEqual(got, test.Want) { 351 t.Errorf("wrong result\ngot: %s\nwant: %s", spew.Sdump(got), spew.Sdump(test.Want)) 352 } 353 }) 354 } 355 } 356 357 func TestMergeBodiesPartialContent(t *testing.T) { 358 tests := []struct { 359 Bodies []Body 360 Schema *BodySchema 361 WantContent *BodyContent 362 WantRemain Body 363 DiagCount int 364 }{ 365 { 366 []Body{}, 367 &BodySchema{}, 368 &BodyContent{ 369 Attributes: map[string]*Attribute{}, 370 }, 371 mergedBodies{}, 372 0, 373 }, 374 { 375 []Body{ 376 &testMergedBodiesVictim{ 377 Name: "first", 378 HasAttributes: []string{"name", "age"}, 379 }, 380 }, 381 &BodySchema{ 382 Attributes: []AttributeSchema{ 383 { 384 Name: "name", 385 }, 386 }, 387 }, 388 &BodyContent{ 389 Attributes: map[string]*Attribute{ 390 "name": &Attribute{ 391 Name: "name", 392 NameRange: Range{Filename: "first"}, 393 }, 394 }, 395 }, 396 mergedBodies{ 397 &testMergedBodiesVictim{ 398 Name: "first", 399 HasAttributes: []string{"age"}, 400 }, 401 }, 402 0, 403 }, 404 { 405 []Body{ 406 &testMergedBodiesVictim{ 407 Name: "first", 408 HasAttributes: []string{"name", "age"}, 409 }, 410 &testMergedBodiesVictim{ 411 Name: "second", 412 HasAttributes: []string{"name", "pizza"}, 413 }, 414 }, 415 &BodySchema{ 416 Attributes: []AttributeSchema{ 417 { 418 Name: "name", 419 }, 420 }, 421 }, 422 &BodyContent{ 423 Attributes: map[string]*Attribute{ 424 "name": &Attribute{ 425 Name: "name", 426 NameRange: Range{Filename: "first"}, 427 }, 428 }, 429 }, 430 mergedBodies{ 431 &testMergedBodiesVictim{ 432 Name: "first", 433 HasAttributes: []string{"age"}, 434 }, 435 &testMergedBodiesVictim{ 436 Name: "second", 437 HasAttributes: []string{"pizza"}, 438 }, 439 }, 440 1, 441 }, 442 { 443 []Body{ 444 &testMergedBodiesVictim{ 445 Name: "first", 446 HasAttributes: []string{"name", "age"}, 447 }, 448 &testMergedBodiesVictim{ 449 Name: "second", 450 HasAttributes: []string{"pizza", "soda"}, 451 }, 452 }, 453 &BodySchema{ 454 Attributes: []AttributeSchema{ 455 { 456 Name: "name", 457 }, 458 { 459 Name: "soda", 460 }, 461 }, 462 }, 463 &BodyContent{ 464 Attributes: map[string]*Attribute{ 465 "name": &Attribute{ 466 Name: "name", 467 NameRange: Range{Filename: "first"}, 468 }, 469 "soda": &Attribute{ 470 Name: "soda", 471 NameRange: Range{Filename: "second"}, 472 }, 473 }, 474 }, 475 mergedBodies{ 476 &testMergedBodiesVictim{ 477 Name: "first", 478 HasAttributes: []string{"age"}, 479 }, 480 &testMergedBodiesVictim{ 481 Name: "second", 482 HasAttributes: []string{"pizza"}, 483 }, 484 }, 485 0, 486 }, 487 { 488 []Body{ 489 &testMergedBodiesVictim{ 490 Name: "first", 491 HasBlocks: map[string]int{ 492 "pizza": 1, 493 }, 494 }, 495 &testMergedBodiesVictim{ 496 Name: "second", 497 HasBlocks: map[string]int{ 498 "pizza": 1, 499 "soda": 2, 500 }, 501 }, 502 }, 503 &BodySchema{ 504 Blocks: []BlockHeaderSchema{ 505 { 506 Type: "pizza", 507 }, 508 }, 509 }, 510 &BodyContent{ 511 Attributes: map[string]*Attribute{}, 512 Blocks: Blocks{ 513 { 514 Type: "pizza", 515 DefRange: Range{Filename: "first"}, 516 }, 517 { 518 Type: "pizza", 519 DefRange: Range{Filename: "second"}, 520 }, 521 }, 522 }, 523 mergedBodies{ 524 &testMergedBodiesVictim{ 525 Name: "first", 526 HasAttributes: []string{}, 527 HasBlocks: map[string]int{}, 528 }, 529 &testMergedBodiesVictim{ 530 Name: "second", 531 HasAttributes: []string{}, 532 HasBlocks: map[string]int{ 533 "soda": 2, 534 }, 535 }, 536 }, 537 0, 538 }, 539 } 540 541 for i, test := range tests { 542 t.Run(fmt.Sprintf("%02d", i), func(t *testing.T) { 543 merged := MergeBodies(test.Bodies) 544 got, gotRemain, diags := merged.PartialContent(test.Schema) 545 546 if len(diags) != test.DiagCount { 547 t.Errorf("Wrong number of diagnostics %d; want %d", len(diags), test.DiagCount) 548 for _, diag := range diags { 549 t.Logf(" - %s", diag) 550 } 551 } 552 553 if !reflect.DeepEqual(got, test.WantContent) { 554 t.Errorf("wrong content result\ngot: %s\nwant: %s", spew.Sdump(got), spew.Sdump(test.WantContent)) 555 } 556 557 if !reflect.DeepEqual(gotRemain, test.WantRemain) { 558 t.Errorf("wrong remaining result\ngot: %s\nwant: %s", spew.Sdump(gotRemain), spew.Sdump(test.WantRemain)) 559 } 560 }) 561 } 562 } 563 564 type testMergedBodiesVictim struct { 565 Name string 566 HasAttributes []string 567 HasBlocks map[string]int 568 DiagCount int 569 } 570 571 func (v *testMergedBodiesVictim) Content(schema *BodySchema) (*BodyContent, Diagnostics) { 572 c, _, d := v.PartialContent(schema) 573 return c, d 574 } 575 576 func (v *testMergedBodiesVictim) PartialContent(schema *BodySchema) (*BodyContent, Body, Diagnostics) { 577 remain := &testMergedBodiesVictim{ 578 Name: v.Name, 579 HasAttributes: []string{}, 580 } 581 582 hasAttrs := map[string]struct{}{} 583 for _, n := range v.HasAttributes { 584 hasAttrs[n] = struct{}{} 585 586 var found bool 587 for _, attrS := range schema.Attributes { 588 if n == attrS.Name { 589 found = true 590 break 591 } 592 } 593 if !found { 594 remain.HasAttributes = append(remain.HasAttributes, n) 595 } 596 } 597 598 content := &BodyContent{ 599 Attributes: map[string]*Attribute{}, 600 } 601 602 rng := Range{ 603 Filename: v.Name, 604 } 605 606 for _, attrS := range schema.Attributes { 607 _, has := hasAttrs[attrS.Name] 608 if has { 609 content.Attributes[attrS.Name] = &Attribute{ 610 Name: attrS.Name, 611 NameRange: rng, 612 } 613 } 614 } 615 616 if v.HasBlocks != nil { 617 for _, blockS := range schema.Blocks { 618 num := v.HasBlocks[blockS.Type] 619 for i := 0; i < num; i++ { 620 content.Blocks = append(content.Blocks, &Block{ 621 Type: blockS.Type, 622 DefRange: rng, 623 }) 624 } 625 } 626 627 remain.HasBlocks = map[string]int{} 628 for n := range v.HasBlocks { 629 var found bool 630 for _, blockS := range schema.Blocks { 631 if blockS.Type == n { 632 found = true 633 break 634 } 635 } 636 if !found { 637 remain.HasBlocks[n] = v.HasBlocks[n] 638 } 639 } 640 } 641 642 diags := make(Diagnostics, v.DiagCount) 643 for i := range diags { 644 diags[i] = &Diagnostic{ 645 Severity: DiagError, 646 Summary: fmt.Sprintf("Fake diagnostic %d", i), 647 Detail: "For testing only.", 648 Context: &rng, 649 } 650 } 651 652 return content, remain, diags 653 } 654 655 func (v *testMergedBodiesVictim) JustAttributes() (Attributes, Diagnostics) { 656 attrs := make(map[string]*Attribute) 657 658 rng := Range{ 659 Filename: v.Name, 660 } 661 662 for _, name := range v.HasAttributes { 663 attrs[name] = &Attribute{ 664 Name: name, 665 NameRange: rng, 666 } 667 } 668 669 diags := make(Diagnostics, v.DiagCount) 670 for i := range diags { 671 diags[i] = &Diagnostic{ 672 Severity: DiagError, 673 Summary: fmt.Sprintf("Fake diagnostic %d", i), 674 Detail: "For testing only.", 675 Context: &rng, 676 } 677 } 678 679 return attrs, diags 680 } 681 682 func (v *testMergedBodiesVictim) MissingItemRange() Range { 683 return Range{ 684 Filename: v.Name, 685 } 686 }