package hcl import ( "fmt" "reflect" "testing" "github.com/davecgh/go-spew/spew" ) func TestMergedBodiesContent(t *testing.T) { tests := []struct { Bodies []Body Schema *BodySchema Want *BodyContent DiagCount int }{ { []Body{}, &BodySchema{}, &BodyContent{ Attributes: map[string]*Attribute{}, }, 0, }, { []Body{}, &BodySchema{ Attributes: []AttributeSchema{ { Name: "name", }, }, }, &BodyContent{ Attributes: map[string]*Attribute{}, }, 0, }, { []Body{}, &BodySchema{ Attributes: []AttributeSchema{ { Name: "name", Required: true, }, }, }, &BodyContent{ Attributes: map[string]*Attribute{}, }, 1, }, { []Body{ &testMergedBodiesVictim{ HasAttributes: []string{"name"}, }, }, &BodySchema{ Attributes: []AttributeSchema{ { Name: "name", }, }, }, &BodyContent{ Attributes: map[string]*Attribute{ "name": &Attribute{ Name: "name", }, }, }, 0, }, { []Body{ &testMergedBodiesVictim{ Name: "first", HasAttributes: []string{"name"}, }, &testMergedBodiesVictim{ Name: "second", HasAttributes: []string{"name"}, }, }, &BodySchema{ Attributes: []AttributeSchema{ { Name: "name", }, }, }, &BodyContent{ Attributes: map[string]*Attribute{ "name": &Attribute{ Name: "name", NameRange: Range{Filename: "first"}, }, }, }, 1, }, { []Body{ &testMergedBodiesVictim{ Name: "first", HasAttributes: []string{"name"}, }, &testMergedBodiesVictim{ Name: "second", HasAttributes: []string{"age"}, }, }, &BodySchema{ Attributes: []AttributeSchema{ { Name: "name", }, { Name: "age", }, }, }, &BodyContent{ Attributes: map[string]*Attribute{ "name": &Attribute{ Name: "name", NameRange: Range{Filename: "first"}, }, "age": &Attribute{ Name: "age", NameRange: Range{Filename: "second"}, }, }, }, 0, }, { []Body{}, &BodySchema{ Blocks: []BlockHeaderSchema{ { Type: "pizza", }, }, }, &BodyContent{ Attributes: map[string]*Attribute{}, }, 0, }, { []Body{ &testMergedBodiesVictim{ HasBlocks: map[string]int{ "pizza": 1, }, }, }, &BodySchema{ Blocks: []BlockHeaderSchema{ { Type: "pizza", }, }, }, &BodyContent{ Attributes: map[string]*Attribute{}, Blocks: Blocks{ { Type: "pizza", }, }, }, 0, }, { []Body{ &testMergedBodiesVictim{ HasBlocks: map[string]int{ "pizza": 2, }, }, }, &BodySchema{ Blocks: []BlockHeaderSchema{ { Type: "pizza", }, }, }, &BodyContent{ Attributes: map[string]*Attribute{}, Blocks: Blocks{ { Type: "pizza", }, { Type: "pizza", }, }, }, 0, }, { []Body{ &testMergedBodiesVictim{ Name: "first", HasBlocks: map[string]int{ "pizza": 1, }, }, &testMergedBodiesVictim{ Name: "second", HasBlocks: map[string]int{ "pizza": 1, }, }, }, &BodySchema{ Blocks: []BlockHeaderSchema{ { Type: "pizza", }, }, }, &BodyContent{ Attributes: map[string]*Attribute{}, Blocks: Blocks{ { Type: "pizza", DefRange: Range{Filename: "first"}, }, { Type: "pizza", DefRange: Range{Filename: "second"}, }, }, }, 0, }, { []Body{ &testMergedBodiesVictim{ Name: "first", }, &testMergedBodiesVictim{ Name: "second", HasBlocks: map[string]int{ "pizza": 2, }, }, }, &BodySchema{ Blocks: []BlockHeaderSchema{ { Type: "pizza", }, }, }, &BodyContent{ Attributes: map[string]*Attribute{}, Blocks: Blocks{ { Type: "pizza", DefRange: Range{Filename: "second"}, }, { Type: "pizza", DefRange: Range{Filename: "second"}, }, }, }, 0, }, { []Body{ &testMergedBodiesVictim{ Name: "first", HasBlocks: map[string]int{ "pizza": 2, }, }, &testMergedBodiesVictim{ Name: "second", }, }, &BodySchema{ Blocks: []BlockHeaderSchema{ { Type: "pizza", }, }, }, &BodyContent{ Attributes: map[string]*Attribute{}, Blocks: Blocks{ { Type: "pizza", DefRange: Range{Filename: "first"}, }, { Type: "pizza", DefRange: Range{Filename: "first"}, }, }, }, 0, }, { []Body{ &testMergedBodiesVictim{ Name: "first", }, &testMergedBodiesVictim{ Name: "second", }, }, &BodySchema{ Blocks: []BlockHeaderSchema{ { Type: "pizza", }, }, }, &BodyContent{ Attributes: map[string]*Attribute{}, }, 0, }, } for i, test := range tests { t.Run(fmt.Sprintf("%02d", i), func(t *testing.T) { merged := MergeBodies(test.Bodies) got, diags := merged.Content(test.Schema) if len(diags) != test.DiagCount { t.Errorf("Wrong number of diagnostics %d; want %d", len(diags), test.DiagCount) for _, diag := range diags { t.Logf(" - %s", diag) } } if !reflect.DeepEqual(got, test.Want) { t.Errorf("wrong result\ngot: %s\nwant: %s", spew.Sdump(got), spew.Sdump(test.Want)) } }) } } func TestMergeBodiesPartialContent(t *testing.T) { tests := []struct { Bodies []Body Schema *BodySchema WantContent *BodyContent WantRemain Body DiagCount int }{ { []Body{}, &BodySchema{}, &BodyContent{ Attributes: map[string]*Attribute{}, }, mergedBodies{}, 0, }, { []Body{ &testMergedBodiesVictim{ Name: "first", HasAttributes: []string{"name", "age"}, }, }, &BodySchema{ Attributes: []AttributeSchema{ { Name: "name", }, }, }, &BodyContent{ Attributes: map[string]*Attribute{ "name": &Attribute{ Name: "name", NameRange: Range{Filename: "first"}, }, }, }, mergedBodies{ &testMergedBodiesVictim{ Name: "first", HasAttributes: []string{"age"}, }, }, 0, }, { []Body{ &testMergedBodiesVictim{ Name: "first", HasAttributes: []string{"name", "age"}, }, &testMergedBodiesVictim{ Name: "second", HasAttributes: []string{"name", "pizza"}, }, }, &BodySchema{ Attributes: []AttributeSchema{ { Name: "name", }, }, }, &BodyContent{ Attributes: map[string]*Attribute{ "name": &Attribute{ Name: "name", NameRange: Range{Filename: "first"}, }, }, }, mergedBodies{ &testMergedBodiesVictim{ Name: "first", HasAttributes: []string{"age"}, }, &testMergedBodiesVictim{ Name: "second", HasAttributes: []string{"pizza"}, }, }, 1, }, { []Body{ &testMergedBodiesVictim{ Name: "first", HasAttributes: []string{"name", "age"}, }, &testMergedBodiesVictim{ Name: "second", HasAttributes: []string{"pizza", "soda"}, }, }, &BodySchema{ Attributes: []AttributeSchema{ { Name: "name", }, { Name: "soda", }, }, }, &BodyContent{ Attributes: map[string]*Attribute{ "name": &Attribute{ Name: "name", NameRange: Range{Filename: "first"}, }, "soda": &Attribute{ Name: "soda", NameRange: Range{Filename: "second"}, }, }, }, mergedBodies{ &testMergedBodiesVictim{ Name: "first", HasAttributes: []string{"age"}, }, &testMergedBodiesVictim{ Name: "second", HasAttributes: []string{"pizza"}, }, }, 0, }, { []Body{ &testMergedBodiesVictim{ Name: "first", HasBlocks: map[string]int{ "pizza": 1, }, }, &testMergedBodiesVictim{ Name: "second", HasBlocks: map[string]int{ "pizza": 1, "soda": 2, }, }, }, &BodySchema{ Blocks: []BlockHeaderSchema{ { Type: "pizza", }, }, }, &BodyContent{ Attributes: map[string]*Attribute{}, Blocks: Blocks{ { Type: "pizza", DefRange: Range{Filename: "first"}, }, { Type: "pizza", DefRange: Range{Filename: "second"}, }, }, }, mergedBodies{ &testMergedBodiesVictim{ Name: "first", HasAttributes: []string{}, HasBlocks: map[string]int{}, }, &testMergedBodiesVictim{ Name: "second", HasAttributes: []string{}, HasBlocks: map[string]int{ "soda": 2, }, }, }, 0, }, } for i, test := range tests { t.Run(fmt.Sprintf("%02d", i), func(t *testing.T) { merged := MergeBodies(test.Bodies) got, gotRemain, diags := merged.PartialContent(test.Schema) if len(diags) != test.DiagCount { t.Errorf("Wrong number of diagnostics %d; want %d", len(diags), test.DiagCount) for _, diag := range diags { t.Logf(" - %s", diag) } } if !reflect.DeepEqual(got, test.WantContent) { t.Errorf("wrong content result\ngot: %s\nwant: %s", spew.Sdump(got), spew.Sdump(test.WantContent)) } if !reflect.DeepEqual(gotRemain, test.WantRemain) { t.Errorf("wrong remaining result\ngot: %s\nwant: %s", spew.Sdump(gotRemain), spew.Sdump(test.WantRemain)) } }) } } type testMergedBodiesVictim struct { Name string HasAttributes []string HasBlocks map[string]int DiagCount int } func (v *testMergedBodiesVictim) Content(schema *BodySchema) (*BodyContent, Diagnostics) { c, _, d := v.PartialContent(schema) return c, d } func (v *testMergedBodiesVictim) PartialContent(schema *BodySchema) (*BodyContent, Body, Diagnostics) { remain := &testMergedBodiesVictim{ Name: v.Name, HasAttributes: []string{}, } hasAttrs := map[string]struct{}{} for _, n := range v.HasAttributes { hasAttrs[n] = struct{}{} var found bool for _, attrS := range schema.Attributes { if n == attrS.Name { found = true break } } if !found { remain.HasAttributes = append(remain.HasAttributes, n) } } content := &BodyContent{ Attributes: map[string]*Attribute{}, } rng := Range{ Filename: v.Name, } for _, attrS := range schema.Attributes { _, has := hasAttrs[attrS.Name] if has { content.Attributes[attrS.Name] = &Attribute{ Name: attrS.Name, NameRange: rng, } } } if v.HasBlocks != nil { for _, blockS := range schema.Blocks { num := v.HasBlocks[blockS.Type] for i := 0; i < num; i++ { content.Blocks = append(content.Blocks, &Block{ Type: blockS.Type, DefRange: rng, }) } } remain.HasBlocks = map[string]int{} for n := range v.HasBlocks { var found bool for _, blockS := range schema.Blocks { if blockS.Type == n { found = true break } } if !found { remain.HasBlocks[n] = v.HasBlocks[n] } } } diags := make(Diagnostics, v.DiagCount) for i := range diags { diags[i] = &Diagnostic{ Severity: DiagError, Summary: fmt.Sprintf("Fake diagnostic %d", i), Detail: "For testing only.", Context: &rng, } } return content, remain, diags } func (v *testMergedBodiesVictim) JustAttributes() (Attributes, Diagnostics) { attrs := make(map[string]*Attribute) rng := Range{ Filename: v.Name, } for _, name := range v.HasAttributes { attrs[name] = &Attribute{ Name: name, NameRange: rng, } } diags := make(Diagnostics, v.DiagCount) for i := range diags { diags[i] = &Diagnostic{ Severity: DiagError, Summary: fmt.Sprintf("Fake diagnostic %d", i), Detail: "For testing only.", Context: &rng, } } return attrs, diags } func (v *testMergedBodiesVictim) MissingItemRange() Range { return Range{ Filename: v.Name, } }