github.com/hashicorp/hcl/v2@v2.20.0/hclwrite/round_trip_test.go (about)

     1  // Copyright (c) HashiCorp, Inc.
     2  // SPDX-License-Identifier: MPL-2.0
     3  
     4  package hclwrite
     5  
     6  import (
     7  	"bytes"
     8  	"testing"
     9  
    10  	"github.com/sergi/go-diff/diffmatchpatch"
    11  	"github.com/zclconf/go-cty/cty"
    12  	"github.com/zclconf/go-cty/cty/function"
    13  	"github.com/zclconf/go-cty/cty/function/stdlib"
    14  
    15  	"github.com/hashicorp/hcl/v2"
    16  	"github.com/hashicorp/hcl/v2/hclsyntax"
    17  )
    18  
    19  func TestRoundTripVerbatim(t *testing.T) {
    20  	tests := []string{
    21  		``,
    22  		`foo = 1
    23  `,
    24  		`
    25  foobar = 1
    26  baz    = 1
    27  `,
    28  		`
    29  # this file is awesome
    30  
    31  # tossed salads and scrambled eggs
    32  foobar = 1
    33  baz    = 1
    34  
    35  block {
    36    a = "a"
    37    b = "b"
    38    c = "c"
    39    d = "d"
    40  
    41    subblock {
    42    }
    43  
    44    subblock {
    45      e = "e"
    46    }
    47  }
    48  
    49  # and they all lived happily ever after
    50  `,
    51  	}
    52  
    53  	for _, test := range tests {
    54  		t.Run(test, func(t *testing.T) {
    55  			src := []byte(test)
    56  			file, diags := parse(src, "", hcl.Pos{Line: 1, Column: 1})
    57  			if len(diags) != 0 {
    58  				for _, diag := range diags {
    59  					t.Logf(" - %s", diag.Error())
    60  				}
    61  				t.Fatalf("unexpected diagnostics")
    62  			}
    63  
    64  			wr := &bytes.Buffer{}
    65  			n, err := file.WriteTo(wr)
    66  			if n != int64(len(test)) {
    67  				t.Errorf("wrong number of bytes %d; want %d", n, len(test))
    68  			}
    69  			if err != nil {
    70  				t.Fatalf("error from WriteTo")
    71  			}
    72  
    73  			result := wr.Bytes()
    74  
    75  			if !bytes.Equal(result, src) {
    76  				dmp := diffmatchpatch.New()
    77  				diffs := dmp.DiffMain(string(src), string(result), false)
    78  				// t.Errorf("wrong result\nresult:\n%s\ninput:\n%s", result, src)
    79  				t.Errorf("wrong result\ndiff: (red indicates missing lines, and green indicates unexpected lines)\n%s", dmp.DiffPrettyText(diffs))
    80  			}
    81  		})
    82  	}
    83  }
    84  
    85  func TestRoundTripFormat(t *testing.T) {
    86  	// The goal of this test is to verify that the formatter doesn't change
    87  	// the semantics of any expressions when it adds and removes whitespace.
    88  	// String templates are the primary area of concern here, but we also
    89  	// test some other things for completeness sake.
    90  	//
    91  	// The tests here must define zero or more attributes, which will be
    92  	// extract with JustAttributes and evaluated both before and after
    93  	// formatting.
    94  
    95  	tests := []string{
    96  		"",
    97  		"\n\n\n",
    98  		"a=1\n",
    99  		"a=\"hello\"\n",
   100  		"a=\"${hello} world\"\n",
   101  		"a=upper(\"hello\")\n",
   102  		"a=upper(hello)\n",
   103  		"a=[1,2,3,4,five]\n",
   104  		"a={greeting=hello}\n",
   105  		"a={\ngreeting=hello\n}\n",
   106  		"a={\ngreeting=hello}\n",
   107  		"a={greeting=hello\n}\n",
   108  		"a={greeting=hello,number=five,sarcastic=\"${upper(hello)}\"\n}\n",
   109  		"a={\ngreeting=hello\nnumber=five\nsarcastic=\"${upper(hello)}\"\n}\n",
   110  		"a=<<EOT\nhello\nEOT\n\n",
   111  		"a=[<<EOT\nhello\nEOT\n]\n",
   112  		"a=[\n<<EOT\nhello\nEOT\n]\n",
   113  		"a=[\n]\n",
   114  		"a=1\nb=2\nc=3\n",
   115  		"a=\"${\n5\n}\"\n",
   116  	}
   117  
   118  	ctx := &hcl.EvalContext{
   119  		Variables: map[string]cty.Value{
   120  			"hello": cty.StringVal("hello"),
   121  			"five":  cty.NumberIntVal(5),
   122  		},
   123  		Functions: map[string]function.Function{
   124  			"upper": stdlib.UpperFunc,
   125  		},
   126  	}
   127  
   128  	for _, test := range tests {
   129  		t.Run(test, func(t *testing.T) {
   130  			attrsAsObj := func(src []byte, phase string) cty.Value {
   131  				t.Logf("source %s:\n%s", phase, src)
   132  				f, diags := hclsyntax.ParseConfig(src, "", hcl.Pos{Line: 1, Column: 1})
   133  				if len(diags) != 0 {
   134  					for _, diag := range diags {
   135  						t.Logf(" - %s", diag.Error())
   136  					}
   137  					t.Fatalf("unexpected diagnostics in parse %s", phase)
   138  				}
   139  
   140  				attrs, diags := f.Body.JustAttributes()
   141  				if len(diags) != 0 {
   142  					for _, diag := range diags {
   143  						t.Logf(" - %s", diag.Error())
   144  					}
   145  					t.Fatalf("unexpected diagnostics in JustAttributes %s", phase)
   146  				}
   147  
   148  				vals := map[string]cty.Value{}
   149  				for k, attr := range attrs {
   150  					val, diags := attr.Expr.Value(ctx)
   151  					if len(diags) != 0 {
   152  						for _, diag := range diags {
   153  							t.Logf(" - %s", diag.Error())
   154  						}
   155  						t.Fatalf("unexpected diagnostics evaluating %s", phase)
   156  					}
   157  					vals[k] = val
   158  				}
   159  				return cty.ObjectVal(vals)
   160  			}
   161  
   162  			src := []byte(test)
   163  			before := attrsAsObj(src, "before")
   164  
   165  			formatted := Format(src)
   166  			after := attrsAsObj(formatted, "after")
   167  
   168  			if !after.RawEquals(before) {
   169  				t.Errorf("mismatching after format\nbefore: %#v\nafter:  %#v", before, after)
   170  			}
   171  		})
   172  	}
   173  }
   174  
   175  // TestRoundTripSafeConcurrent concurrently generates a new file. When run with
   176  // the race detector it will fail if a data race is detected.
   177  func TestRoundTripSafeConcurrent(t *testing.T) {
   178  	for i := 0; i < 2; i++ {
   179  		go func() {
   180  			f := NewEmptyFile()
   181  			b := f.Body()
   182  			b.SetAttributeValue("foo", cty.StringVal("bar"))
   183  		}()
   184  	}
   185  }