hashicorp / hcl2

Former temporary home for experimental new version of HCL
https://github.com/hashicorp/hcl
Mozilla Public License 2.0
373 stars 66 forks source link

hclpack: Attributes being mixed up in content() #72

Closed akupila closed 5 years ago

akupila commented 5 years ago

When multiple blocks have the same type, the attributes within the block are somehow set on the previously decoded blocks too. This is a bit tricky to explain so an example is better:

package main

import (
    "fmt"
    "log"

    "github.com/hashicorp/hcl2/gohcl"
    "github.com/hashicorp/hcl2/hcl"
    "github.com/hashicorp/hcl2/hclpack"
)

func main() {
    src := `
foo {
    bar = "123"
}

foo {
    bar = "abc"
}
`

    body, diags := hclpack.PackNativeFile([]byte(src), "file.hcl", hcl.Pos{Line: 1, Column: 1})
    if diags.HasErrors() {
        log.Fatal(diags)
    }

    got := struct {
        Foos []struct {
            Bar string `hcl:"bar"`
        } `hcl:"foo,block"`
    }{}

    diags = gohcl.DecodeBody(body, nil, &got)
    if diags.HasErrors() {
        log.Fatal(diags)
    }

    fmt.Printf("Result: %+v\n", got)

    // Output:
    // Result: {Foos:[{Bar:abc} {Bar:abc}]}
}

Here I would expect the output to be Result: {Foos:[{Bar:123} {Bar:abc}]}.

If we change lines 23-26 to this:

file, diags := hclsyntax.ParseConfig([]byte(src), "file.hcl", hcl.Pos{Byte: 0, Line: 1, Column: 1})
if diags.HasErrors() {
    log.Fatal(diags)
}
body := file.Body

The result is what I would expect.

This seems like a bug to me but it's possible I've misunderstood something.

Here's a failing test case:

diff --git hclpack/structure_test.go hclpack/structure_test.go
index 0d5f11c..78b1037 100644
--- a/hclpack/structure_test.go
+++ b/hclpack/structure_test.go
@@ -75,6 +75,73 @@ func TestBodyContent(t *testing.T) {
                },
            },
        },
+       "block attributes": {
+           &Body{
+               ChildBlocks: []Block{
+                   {
+                       Type: "foo",
+                       Body: Body{
+                           Attributes: map[string]Attribute{
+                               "bar": {
+                                   Expr: Expression{
+                                       Source:     []byte(`"hello"`),
+                                       SourceType: ExprNative,
+                                   },
+                               },
+                           },
+                       },
+                   },
+                   {
+                       Type: "foo",
+                       Body: Body{
+                           Attributes: map[string]Attribute{
+                               "bar": {
+                                   Expr: Expression{
+                                       Source:     []byte(`"world"`),
+                                       SourceType: ExprNative,
+                                   },
+                               },
+                           },
+                       },
+                   },
+               },
+           },
+           &hcl.BodySchema{
+               Blocks: []hcl.BlockHeaderSchema{
+                   {Type: "foo"},
+               },
+           },
+           &hcl.BodyContent{
+               Blocks: hcl.Blocks{
+                   {
+                       Type: "foo",
+                       Body: &Body{
+                           Attributes: map[string]Attribute{
+                               "bar": {
+                                   Expr: Expression{
+                                       Source:     []byte(`"hello"`),
+                                       SourceType: ExprNative,
+                                   },
+                               },
+                           },
+                       },
+                   },
+                   {
+                       Type: "foo",
+                       Body: &Body{
+                           Attributes: map[string]Attribute{
+                               "bar": {
+                                   Expr: Expression{
+                                       Source:     []byte(`"world"`),
+                                       SourceType: ExprNative,
+                                   },
+                               },
+                           },
+                       },
+                   },
+               },
+           },
+       },
    }

    for name, test := range tests {
@@ -85,7 +152,13 @@ func TestBodyContent(t *testing.T) {
            }

            if !cmp.Equal(test.Want, got) {
-               t.Errorf("wrong result\n%s", cmp.Diff(test.Want, got))
+               bytesAsString := func(s []byte) string {
+                   return string(s)
+               }
+               t.Errorf("wrong result\n%s", cmp.Diff(
+                   test.Want, got,
+                   cmp.Transformer("bytesAsString", bytesAsString),
+               ))
            }
        })
    }