github.com/hashicorp/hcl/v2@v2.20.0/hclsyntax/structure_test.go (about) 1 // Copyright (c) HashiCorp, Inc. 2 // SPDX-License-Identifier: MPL-2.0 3 4 package hclsyntax 5 6 import ( 7 "fmt" 8 "reflect" 9 "testing" 10 11 "github.com/hashicorp/hcl/v2" 12 "github.com/kylelemons/godebug/pretty" 13 "github.com/zclconf/go-cty/cty" 14 ) 15 16 func TestBodyContent(t *testing.T) { 17 tests := []struct { 18 body *Body 19 schema *hcl.BodySchema 20 partial bool 21 want *hcl.BodyContent 22 diagCount int 23 }{ 24 { 25 &Body{}, 26 &hcl.BodySchema{}, 27 false, 28 &hcl.BodyContent{ 29 Attributes: hcl.Attributes{}, 30 }, 31 0, 32 }, 33 34 // Attributes 35 { 36 &Body{ 37 Attributes: Attributes{ 38 "foo": &Attribute{ 39 Name: "foo", 40 }, 41 }, 42 }, 43 &hcl.BodySchema{ 44 Attributes: []hcl.AttributeSchema{ 45 { 46 Name: "foo", 47 }, 48 }, 49 }, 50 false, 51 &hcl.BodyContent{ 52 Attributes: hcl.Attributes{ 53 "foo": &hcl.Attribute{ 54 Name: "foo", 55 }, 56 }, 57 }, 58 0, 59 }, 60 { 61 &Body{ 62 Attributes: Attributes{ 63 "foo": &Attribute{ 64 Name: "foo", 65 }, 66 }, 67 }, 68 &hcl.BodySchema{}, 69 false, 70 &hcl.BodyContent{ 71 Attributes: hcl.Attributes{}, 72 }, 73 1, // attribute "foo" is not expected 74 }, 75 { 76 &Body{ 77 Attributes: Attributes{ 78 "foo": &Attribute{ 79 Name: "foo", 80 }, 81 }, 82 }, 83 &hcl.BodySchema{}, 84 true, 85 &hcl.BodyContent{ 86 Attributes: hcl.Attributes{}, 87 }, 88 0, // in partial mode, so extra "foo" is acceptable 89 }, 90 { 91 &Body{ 92 Attributes: Attributes{}, 93 }, 94 &hcl.BodySchema{ 95 Attributes: []hcl.AttributeSchema{ 96 { 97 Name: "foo", 98 }, 99 }, 100 }, 101 false, 102 &hcl.BodyContent{ 103 Attributes: hcl.Attributes{}, 104 }, 105 0, // "foo" not required, so no error 106 }, 107 { 108 &Body{ 109 Attributes: Attributes{}, 110 }, 111 &hcl.BodySchema{ 112 Attributes: []hcl.AttributeSchema{ 113 { 114 Name: "foo", 115 Required: true, 116 }, 117 }, 118 }, 119 false, 120 &hcl.BodyContent{ 121 Attributes: hcl.Attributes{}, 122 }, 123 1, // "foo" is required 124 }, 125 { 126 &Body{ 127 Attributes: Attributes{ 128 "foo": &Attribute{ 129 Name: "foo", 130 }, 131 }, 132 }, 133 &hcl.BodySchema{ 134 Blocks: []hcl.BlockHeaderSchema{ 135 { 136 Type: "foo", 137 }, 138 }, 139 }, 140 false, 141 &hcl.BodyContent{ 142 Attributes: hcl.Attributes{}, 143 }, 144 1, // attribute "foo" not expected (it's defined as a block) 145 }, 146 147 // Blocks 148 { 149 &Body{ 150 Blocks: Blocks{ 151 &Block{ 152 Type: "foo", 153 }, 154 }, 155 }, 156 &hcl.BodySchema{ 157 Blocks: []hcl.BlockHeaderSchema{ 158 { 159 Type: "foo", 160 }, 161 }, 162 }, 163 false, 164 &hcl.BodyContent{ 165 Attributes: hcl.Attributes{}, 166 Blocks: hcl.Blocks{ 167 { 168 Type: "foo", 169 Body: (*Body)(nil), 170 }, 171 }, 172 }, 173 0, 174 }, 175 { 176 &Body{ 177 Blocks: Blocks{ 178 &Block{ 179 Type: "foo", 180 }, 181 &Block{ 182 Type: "foo", 183 }, 184 }, 185 }, 186 &hcl.BodySchema{ 187 Blocks: []hcl.BlockHeaderSchema{ 188 { 189 Type: "foo", 190 }, 191 }, 192 }, 193 false, 194 &hcl.BodyContent{ 195 Attributes: hcl.Attributes{}, 196 Blocks: hcl.Blocks{ 197 { 198 Type: "foo", 199 Body: (*Body)(nil), 200 }, 201 { 202 Type: "foo", 203 Body: (*Body)(nil), 204 }, 205 }, 206 }, 207 0, 208 }, 209 { 210 &Body{ 211 Blocks: Blocks{ 212 &Block{ 213 Type: "foo", 214 }, 215 &Block{ 216 Type: "bar", 217 }, 218 }, 219 }, 220 &hcl.BodySchema{ 221 Blocks: []hcl.BlockHeaderSchema{ 222 { 223 Type: "foo", 224 }, 225 }, 226 }, 227 false, 228 &hcl.BodyContent{ 229 Attributes: hcl.Attributes{}, 230 Blocks: hcl.Blocks{ 231 { 232 Type: "foo", 233 Body: (*Body)(nil), 234 }, 235 }, 236 }, 237 1, // blocks of type "bar" not expected 238 }, 239 { 240 &Body{ 241 Blocks: Blocks{ 242 &Block{ 243 Type: "foo", 244 }, 245 &Block{ 246 Type: "bar", 247 }, 248 }, 249 }, 250 &hcl.BodySchema{ 251 Blocks: []hcl.BlockHeaderSchema{ 252 { 253 Type: "foo", 254 }, 255 }, 256 }, 257 true, 258 &hcl.BodyContent{ 259 Attributes: hcl.Attributes{}, 260 Blocks: hcl.Blocks{ 261 { 262 Type: "foo", 263 Body: (*Body)(nil), 264 }, 265 }, 266 }, 267 0, // extra "bar" allowed because we're in partial mode 268 }, 269 { 270 &Body{ 271 Blocks: Blocks{ 272 &Block{ 273 Type: "foo", 274 Labels: []string{"bar"}, 275 }, 276 }, 277 }, 278 &hcl.BodySchema{ 279 Blocks: []hcl.BlockHeaderSchema{ 280 { 281 Type: "foo", 282 LabelNames: []string{"name"}, 283 }, 284 }, 285 }, 286 false, 287 &hcl.BodyContent{ 288 Attributes: hcl.Attributes{}, 289 Blocks: hcl.Blocks{ 290 { 291 Type: "foo", 292 Labels: []string{"bar"}, 293 Body: (*Body)(nil), 294 }, 295 }, 296 }, 297 0, 298 }, 299 { 300 &Body{ 301 Blocks: Blocks{ 302 &Block{ 303 Type: "foo", 304 }, 305 }, 306 }, 307 &hcl.BodySchema{ 308 Blocks: []hcl.BlockHeaderSchema{ 309 { 310 Type: "foo", 311 LabelNames: []string{"name"}, 312 }, 313 }, 314 }, 315 false, 316 &hcl.BodyContent{ 317 Attributes: hcl.Attributes{}, 318 }, 319 1, // missing label "name" 320 }, 321 { 322 &Body{ 323 Blocks: Blocks{ 324 &Block{ 325 Type: "foo", 326 Labels: []string{"bar"}, 327 328 LabelRanges: []hcl.Range{{}}, 329 }, 330 }, 331 }, 332 &hcl.BodySchema{ 333 Blocks: []hcl.BlockHeaderSchema{ 334 { 335 Type: "foo", 336 }, 337 }, 338 }, 339 false, 340 &hcl.BodyContent{ 341 Attributes: hcl.Attributes{}, 342 }, 343 1, // no labels expected 344 }, 345 { 346 &Body{ 347 Blocks: Blocks{ 348 &Block{ 349 Type: "foo", 350 Labels: []string{"bar", "baz"}, 351 352 LabelRanges: []hcl.Range{{}, {}}, 353 }, 354 }, 355 }, 356 &hcl.BodySchema{ 357 Blocks: []hcl.BlockHeaderSchema{ 358 { 359 Type: "foo", 360 LabelNames: []string{"name"}, 361 }, 362 }, 363 }, 364 false, 365 &hcl.BodyContent{ 366 Attributes: hcl.Attributes{}, 367 }, 368 1, // too many labels 369 }, 370 { 371 &Body{ 372 Attributes: Attributes{ 373 "foo": &Attribute{ 374 Name: "foo", 375 }, 376 }, 377 }, 378 &hcl.BodySchema{ 379 Blocks: []hcl.BlockHeaderSchema{ 380 { 381 Type: "foo", 382 }, 383 }, 384 }, 385 false, 386 &hcl.BodyContent{ 387 Attributes: hcl.Attributes{}, 388 }, 389 1, // should've been a block, not an attribute 390 }, 391 } 392 393 prettyConfig := &pretty.Config{ 394 Diffable: true, 395 IncludeUnexported: true, 396 PrintStringers: true, 397 } 398 399 for i, test := range tests { 400 t.Run(fmt.Sprintf("%02d", i), func(t *testing.T) { 401 var got *hcl.BodyContent 402 var diags hcl.Diagnostics 403 if test.partial { 404 got, _, diags = test.body.PartialContent(test.schema) 405 } else { 406 got, diags = test.body.Content(test.schema) 407 } 408 409 if len(diags) != test.diagCount { 410 t.Errorf("wrong number of diagnostics %d; want %d", len(diags), test.diagCount) 411 for _, diag := range diags { 412 t.Logf(" - %s", diag.Error()) 413 } 414 } 415 416 if !reflect.DeepEqual(got, test.want) { 417 t.Errorf( 418 "wrong result\ndiff: %s", 419 prettyConfig.Compare(test.want, got), 420 ) 421 } 422 }) 423 } 424 } 425 426 func TestBodyJustAttributes(t *testing.T) { 427 tests := []struct { 428 body *Body 429 want hcl.Attributes 430 diagCount int 431 }{ 432 { 433 &Body{}, 434 hcl.Attributes{}, 435 0, 436 }, 437 { 438 &Body{ 439 Attributes: Attributes{}, 440 }, 441 hcl.Attributes{}, 442 0, 443 }, 444 { 445 &Body{ 446 Attributes: Attributes{ 447 "foo": &Attribute{ 448 Name: "foo", 449 Expr: &LiteralValueExpr{ 450 Val: cty.StringVal("bar"), 451 }, 452 }, 453 }, 454 }, 455 hcl.Attributes{ 456 "foo": &hcl.Attribute{ 457 Name: "foo", 458 Expr: &LiteralValueExpr{ 459 Val: cty.StringVal("bar"), 460 }, 461 }, 462 }, 463 0, 464 }, 465 { 466 &Body{ 467 Attributes: Attributes{ 468 "foo": &Attribute{ 469 Name: "foo", 470 Expr: &LiteralValueExpr{ 471 Val: cty.StringVal("bar"), 472 }, 473 }, 474 }, 475 Blocks: Blocks{ 476 { 477 Type: "foo", 478 }, 479 }, 480 }, 481 hcl.Attributes{ 482 "foo": &hcl.Attribute{ 483 Name: "foo", 484 Expr: &LiteralValueExpr{ 485 Val: cty.StringVal("bar"), 486 }, 487 }, 488 }, 489 1, // blocks are not allowed here 490 }, 491 { 492 &Body{ 493 Attributes: Attributes{ 494 "foo": &Attribute{ 495 Name: "foo", 496 Expr: &LiteralValueExpr{ 497 Val: cty.StringVal("bar"), 498 }, 499 }, 500 }, 501 hiddenAttrs: map[string]struct{}{ 502 "foo": struct{}{}, 503 }, 504 }, 505 hcl.Attributes{}, 506 0, 507 }, 508 } 509 510 prettyConfig := &pretty.Config{ 511 Diffable: true, 512 IncludeUnexported: true, 513 PrintStringers: true, 514 } 515 516 for i, test := range tests { 517 t.Run(fmt.Sprintf("%02d", i), func(t *testing.T) { 518 got, diags := test.body.JustAttributes() 519 520 if len(diags) != test.diagCount { 521 t.Errorf("wrong number of diagnostics %d; want %d", len(diags), test.diagCount) 522 for _, diag := range diags { 523 t.Logf(" - %s", diag.Error()) 524 } 525 } 526 527 if !reflect.DeepEqual(got, test.want) { 528 t.Errorf( 529 "wrong result\nbody: %s\ndiff: %s", 530 prettyConfig.Sprint(test.body), 531 prettyConfig.Compare(test.want, got), 532 ) 533 } 534 }) 535 } 536 }