github.com/kevinklinger/open_terraform@v1.3.6/noninternal/tfdiags/contextual_test.go (about)

     1  package tfdiags
     2  
     3  import (
     4  	"fmt"
     5  	"reflect"
     6  	"testing"
     7  
     8  	"github.com/go-test/deep"
     9  	"github.com/hashicorp/hcl/v2"
    10  	"github.com/hashicorp/hcl/v2/hclsyntax"
    11  	"github.com/zclconf/go-cty/cty"
    12  )
    13  
    14  func TestAttributeValue(t *testing.T) {
    15  	testConfig := `
    16  foo {
    17    bar = "hi"
    18  }
    19  foo {
    20    bar = "bar"
    21  }
    22  bar {
    23    bar = "woot"
    24  }
    25  baz "a" {
    26    bar = "beep"
    27  }
    28  baz "b" {
    29    bar = "boop"
    30  }
    31  parent {
    32    nested_str = "hello"
    33    nested_str_tuple = ["aa", "bbb", "cccc"]
    34    nested_num_tuple = [1, 9863, 22]
    35    nested_map = {
    36      first_key  = "first_value"
    37      second_key = "2nd value"
    38    }
    39  }
    40  tuple_of_one = ["one"]
    41  tuple_of_two = ["first", "22222"]
    42  root_map = {
    43    first  = "1st"
    44    second = "2nd"
    45  }
    46  simple_attr = "val"
    47  `
    48  	// TODO: Test ConditionalExpr
    49  	// TODO: Test ForExpr
    50  	// TODO: Test FunctionCallExpr
    51  	// TODO: Test IndexExpr
    52  	// TODO: Test interpolation
    53  	// TODO: Test SplatExpr
    54  
    55  	f, parseDiags := hclsyntax.ParseConfig([]byte(testConfig), "test.tf", hcl.Pos{Line: 1, Column: 1})
    56  	if len(parseDiags) != 0 {
    57  		t.Fatal(parseDiags)
    58  	}
    59  	emptySrcRng := &SourceRange{
    60  		Filename: "test.tf",
    61  		Start:    SourcePos{Line: 1, Column: 1, Byte: 0},
    62  		End:      SourcePos{Line: 1, Column: 1, Byte: 0},
    63  	}
    64  
    65  	testCases := []struct {
    66  		Diag          Diagnostic
    67  		ExpectedRange *SourceRange
    68  	}{
    69  		{
    70  			AttributeValue(
    71  				Error,
    72  				"foo[0].bar",
    73  				"detail",
    74  				cty.Path{
    75  					cty.GetAttrStep{Name: "foo"},
    76  					cty.IndexStep{Key: cty.NumberIntVal(0)},
    77  					cty.GetAttrStep{Name: "bar"},
    78  				},
    79  			),
    80  			&SourceRange{
    81  				Filename: "test.tf",
    82  				Start:    SourcePos{Line: 3, Column: 9, Byte: 15},
    83  				End:      SourcePos{Line: 3, Column: 13, Byte: 19},
    84  			},
    85  		},
    86  		{
    87  			AttributeValue(
    88  				Error,
    89  				"foo[1].bar",
    90  				"detail",
    91  				cty.Path{
    92  					cty.GetAttrStep{Name: "foo"},
    93  					cty.IndexStep{Key: cty.NumberIntVal(1)},
    94  					cty.GetAttrStep{Name: "bar"},
    95  				},
    96  			),
    97  			&SourceRange{
    98  				Filename: "test.tf",
    99  				Start:    SourcePos{Line: 6, Column: 9, Byte: 36},
   100  				End:      SourcePos{Line: 6, Column: 14, Byte: 41},
   101  			},
   102  		},
   103  		{
   104  			AttributeValue(
   105  				Error,
   106  				"foo[99].bar",
   107  				"detail",
   108  				cty.Path{
   109  					cty.GetAttrStep{Name: "foo"},
   110  					cty.IndexStep{Key: cty.NumberIntVal(99)},
   111  					cty.GetAttrStep{Name: "bar"},
   112  				},
   113  			),
   114  			emptySrcRng,
   115  		},
   116  		{
   117  			AttributeValue(
   118  				Error,
   119  				"bar.bar",
   120  				"detail",
   121  				cty.Path{
   122  					cty.GetAttrStep{Name: "bar"},
   123  					cty.GetAttrStep{Name: "bar"},
   124  				},
   125  			),
   126  			&SourceRange{
   127  				Filename: "test.tf",
   128  				Start:    SourcePos{Line: 9, Column: 9, Byte: 58},
   129  				End:      SourcePos{Line: 9, Column: 15, Byte: 64},
   130  			},
   131  		},
   132  		{
   133  			AttributeValue(
   134  				Error,
   135  				`baz["a"].bar`,
   136  				"detail",
   137  				cty.Path{
   138  					cty.GetAttrStep{Name: "baz"},
   139  					cty.IndexStep{Key: cty.StringVal("a")},
   140  					cty.GetAttrStep{Name: "bar"},
   141  				},
   142  			),
   143  			&SourceRange{
   144  				Filename: "test.tf",
   145  				Start:    SourcePos{Line: 12, Column: 9, Byte: 85},
   146  				End:      SourcePos{Line: 12, Column: 15, Byte: 91},
   147  			},
   148  		},
   149  		{
   150  			AttributeValue(
   151  				Error,
   152  				`baz["b"].bar`,
   153  				"detail",
   154  				cty.Path{
   155  					cty.GetAttrStep{Name: "baz"},
   156  					cty.IndexStep{Key: cty.StringVal("b")},
   157  					cty.GetAttrStep{Name: "bar"},
   158  				},
   159  			),
   160  			&SourceRange{
   161  				Filename: "test.tf",
   162  				Start:    SourcePos{Line: 15, Column: 9, Byte: 112},
   163  				End:      SourcePos{Line: 15, Column: 15, Byte: 118},
   164  			},
   165  		},
   166  		{
   167  			AttributeValue(
   168  				Error,
   169  				`baz["not_exists"].bar`,
   170  				"detail",
   171  				cty.Path{
   172  					cty.GetAttrStep{Name: "baz"},
   173  					cty.IndexStep{Key: cty.StringVal("not_exists")},
   174  					cty.GetAttrStep{Name: "bar"},
   175  				},
   176  			),
   177  			emptySrcRng,
   178  		},
   179  		{
   180  			// Attribute value with subject already populated should not be disturbed.
   181  			// (in a real case, this might've been passed through from a deeper function
   182  			// in the call stack, for example.)
   183  			&attributeDiagnostic{
   184  				attrPath: cty.Path{cty.GetAttrStep{Name: "foo"}},
   185  				diagnosticBase: diagnosticBase{
   186  					summary: "preexisting",
   187  					detail:  "detail",
   188  					address: "original",
   189  				},
   190  				subject: &SourceRange{
   191  					Filename: "somewhere_else.tf",
   192  				},
   193  			},
   194  			&SourceRange{
   195  				Filename: "somewhere_else.tf",
   196  			},
   197  		},
   198  		{
   199  			// Missing path
   200  			&attributeDiagnostic{
   201  				diagnosticBase: diagnosticBase{
   202  					summary: "missing path",
   203  				},
   204  			},
   205  			nil,
   206  		},
   207  
   208  		// Nested attributes
   209  		{
   210  			AttributeValue(
   211  				Error,
   212  				"parent.nested_str",
   213  				"detail",
   214  				cty.Path{
   215  					cty.GetAttrStep{Name: "parent"},
   216  					cty.GetAttrStep{Name: "nested_str"},
   217  				},
   218  			),
   219  			&SourceRange{
   220  				Filename: "test.tf",
   221  				Start:    SourcePos{Line: 18, Column: 16, Byte: 145},
   222  				End:      SourcePos{Line: 18, Column: 23, Byte: 152},
   223  			},
   224  		},
   225  		{
   226  			AttributeValue(
   227  				Error,
   228  				"parent.nested_str_tuple[99]",
   229  				"detail",
   230  				cty.Path{
   231  					cty.GetAttrStep{Name: "parent"},
   232  					cty.GetAttrStep{Name: "nested_str_tuple"},
   233  					cty.IndexStep{Key: cty.NumberIntVal(99)},
   234  				},
   235  			),
   236  			&SourceRange{
   237  				Filename: "test.tf",
   238  				Start:    SourcePos{Line: 19, Column: 3, Byte: 155},
   239  				End:      SourcePos{Line: 19, Column: 19, Byte: 171},
   240  			},
   241  		},
   242  		{
   243  			AttributeValue(
   244  				Error,
   245  				"parent.nested_str_tuple[0]",
   246  				"detail",
   247  				cty.Path{
   248  					cty.GetAttrStep{Name: "parent"},
   249  					cty.GetAttrStep{Name: "nested_str_tuple"},
   250  					cty.IndexStep{Key: cty.NumberIntVal(0)},
   251  				},
   252  			),
   253  			&SourceRange{
   254  				Filename: "test.tf",
   255  				Start:    SourcePos{Line: 19, Column: 23, Byte: 175},
   256  				End:      SourcePos{Line: 19, Column: 27, Byte: 179},
   257  			},
   258  		},
   259  		{
   260  			AttributeValue(
   261  				Error,
   262  				"parent.nested_str_tuple[2]",
   263  				"detail",
   264  				cty.Path{
   265  					cty.GetAttrStep{Name: "parent"},
   266  					cty.GetAttrStep{Name: "nested_str_tuple"},
   267  					cty.IndexStep{Key: cty.NumberIntVal(2)},
   268  				},
   269  			),
   270  			&SourceRange{
   271  				Filename: "test.tf",
   272  				Start:    SourcePos{Line: 19, Column: 36, Byte: 188},
   273  				End:      SourcePos{Line: 19, Column: 42, Byte: 194},
   274  			},
   275  		},
   276  		{
   277  			AttributeValue(
   278  				Error,
   279  				"parent.nested_num_tuple[0]",
   280  				"detail",
   281  				cty.Path{
   282  					cty.GetAttrStep{Name: "parent"},
   283  					cty.GetAttrStep{Name: "nested_num_tuple"},
   284  					cty.IndexStep{Key: cty.NumberIntVal(0)},
   285  				},
   286  			),
   287  			&SourceRange{
   288  				Filename: "test.tf",
   289  				Start:    SourcePos{Line: 20, Column: 23, Byte: 218},
   290  				End:      SourcePos{Line: 20, Column: 24, Byte: 219},
   291  			},
   292  		},
   293  		{
   294  			AttributeValue(
   295  				Error,
   296  				"parent.nested_num_tuple[1]",
   297  				"detail",
   298  				cty.Path{
   299  					cty.GetAttrStep{Name: "parent"},
   300  					cty.GetAttrStep{Name: "nested_num_tuple"},
   301  					cty.IndexStep{Key: cty.NumberIntVal(1)},
   302  				},
   303  			),
   304  			&SourceRange{
   305  				Filename: "test.tf",
   306  				Start:    SourcePos{Line: 20, Column: 26, Byte: 221},
   307  				End:      SourcePos{Line: 20, Column: 30, Byte: 225},
   308  			},
   309  		},
   310  		{
   311  			AttributeValue(
   312  				Error,
   313  				"parent.nested_map.first_key",
   314  				"detail",
   315  				cty.Path{
   316  					cty.GetAttrStep{Name: "parent"},
   317  					cty.GetAttrStep{Name: "nested_map"},
   318  					cty.IndexStep{Key: cty.StringVal("first_key")},
   319  				},
   320  			),
   321  			&SourceRange{
   322  				Filename: "test.tf",
   323  				Start:    SourcePos{Line: 22, Column: 19, Byte: 266},
   324  				End:      SourcePos{Line: 22, Column: 30, Byte: 277},
   325  			},
   326  		},
   327  		{
   328  			AttributeValue(
   329  				Error,
   330  				"parent.nested_map.second_key",
   331  				"detail",
   332  				cty.Path{
   333  					cty.GetAttrStep{Name: "parent"},
   334  					cty.GetAttrStep{Name: "nested_map"},
   335  					cty.IndexStep{Key: cty.StringVal("second_key")},
   336  				},
   337  			),
   338  			&SourceRange{
   339  				Filename: "test.tf",
   340  				Start:    SourcePos{Line: 23, Column: 19, Byte: 297},
   341  				End:      SourcePos{Line: 23, Column: 28, Byte: 306},
   342  			},
   343  		},
   344  		{
   345  			AttributeValue(
   346  				Error,
   347  				"parent.nested_map.undefined_key",
   348  				"detail",
   349  				cty.Path{
   350  					cty.GetAttrStep{Name: "parent"},
   351  					cty.GetAttrStep{Name: "nested_map"},
   352  					cty.IndexStep{Key: cty.StringVal("undefined_key")},
   353  				},
   354  			),
   355  			&SourceRange{
   356  				Filename: "test.tf",
   357  				Start:    SourcePos{Line: 21, Column: 3, Byte: 233},
   358  				End:      SourcePos{Line: 21, Column: 13, Byte: 243},
   359  			},
   360  		},
   361  
   362  		// Root attributes of complex types
   363  		{
   364  			AttributeValue(
   365  				Error,
   366  				"tuple_of_one[0]",
   367  				"detail",
   368  				cty.Path{
   369  					cty.GetAttrStep{Name: "tuple_of_one"},
   370  					cty.IndexStep{Key: cty.NumberIntVal(0)},
   371  				},
   372  			),
   373  			&SourceRange{
   374  				Filename: "test.tf",
   375  				Start:    SourcePos{Line: 26, Column: 17, Byte: 330},
   376  				End:      SourcePos{Line: 26, Column: 22, Byte: 335},
   377  			},
   378  		},
   379  		{
   380  			AttributeValue(
   381  				Error,
   382  				"tuple_of_two[0]",
   383  				"detail",
   384  				cty.Path{
   385  					cty.GetAttrStep{Name: "tuple_of_two"},
   386  					cty.IndexStep{Key: cty.NumberIntVal(0)},
   387  				},
   388  			),
   389  			&SourceRange{
   390  				Filename: "test.tf",
   391  				Start:    SourcePos{Line: 27, Column: 17, Byte: 353},
   392  				End:      SourcePos{Line: 27, Column: 24, Byte: 360},
   393  			},
   394  		},
   395  		{
   396  			AttributeValue(
   397  				Error,
   398  				"tuple_of_two[1]",
   399  				"detail",
   400  				cty.Path{
   401  					cty.GetAttrStep{Name: "tuple_of_two"},
   402  					cty.IndexStep{Key: cty.NumberIntVal(1)},
   403  				},
   404  			),
   405  			&SourceRange{
   406  				Filename: "test.tf",
   407  				Start:    SourcePos{Line: 27, Column: 26, Byte: 362},
   408  				End:      SourcePos{Line: 27, Column: 33, Byte: 369},
   409  			},
   410  		},
   411  		{
   412  			AttributeValue(
   413  				Error,
   414  				"tuple_of_one[null]",
   415  				"detail",
   416  				cty.Path{
   417  					cty.GetAttrStep{Name: "tuple_of_one"},
   418  					cty.IndexStep{Key: cty.NullVal(cty.Number)},
   419  				},
   420  			),
   421  			&SourceRange{
   422  				Filename: "test.tf",
   423  				Start:    SourcePos{Line: 26, Column: 1, Byte: 314},
   424  				End:      SourcePos{Line: 26, Column: 13, Byte: 326},
   425  			},
   426  		},
   427  		{
   428  			// index out of range
   429  			AttributeValue(
   430  				Error,
   431  				"tuple_of_two[99]",
   432  				"detail",
   433  				cty.Path{
   434  					cty.GetAttrStep{Name: "tuple_of_two"},
   435  					cty.IndexStep{Key: cty.NumberIntVal(99)},
   436  				},
   437  			),
   438  			&SourceRange{
   439  				Filename: "test.tf",
   440  				Start:    SourcePos{Line: 27, Column: 1, Byte: 337},
   441  				End:      SourcePos{Line: 27, Column: 13, Byte: 349},
   442  			},
   443  		},
   444  		{
   445  			AttributeValue(
   446  				Error,
   447  				"root_map.first",
   448  				"detail",
   449  				cty.Path{
   450  					cty.GetAttrStep{Name: "root_map"},
   451  					cty.IndexStep{Key: cty.StringVal("first")},
   452  				},
   453  			),
   454  			&SourceRange{
   455  				Filename: "test.tf",
   456  				Start:    SourcePos{Line: 29, Column: 13, Byte: 396},
   457  				End:      SourcePos{Line: 29, Column: 16, Byte: 399},
   458  			},
   459  		},
   460  		{
   461  			AttributeValue(
   462  				Error,
   463  				"root_map.second",
   464  				"detail",
   465  				cty.Path{
   466  					cty.GetAttrStep{Name: "root_map"},
   467  					cty.IndexStep{Key: cty.StringVal("second")},
   468  				},
   469  			),
   470  			&SourceRange{
   471  				Filename: "test.tf",
   472  				Start:    SourcePos{Line: 30, Column: 13, Byte: 413},
   473  				End:      SourcePos{Line: 30, Column: 16, Byte: 416},
   474  			},
   475  		},
   476  		{
   477  			AttributeValue(
   478  				Error,
   479  				"root_map.undefined_key",
   480  				"detail",
   481  				cty.Path{
   482  					cty.GetAttrStep{Name: "root_map"},
   483  					cty.IndexStep{Key: cty.StringVal("undefined_key")},
   484  				},
   485  			),
   486  			&SourceRange{
   487  				Filename: "test.tf",
   488  				Start:    SourcePos{Line: 28, Column: 1, Byte: 371},
   489  				End:      SourcePos{Line: 28, Column: 9, Byte: 379},
   490  			},
   491  		},
   492  		{
   493  			AttributeValue(
   494  				Error,
   495  				"simple_attr",
   496  				"detail",
   497  				cty.Path{
   498  					cty.GetAttrStep{Name: "simple_attr"},
   499  				},
   500  			),
   501  			&SourceRange{
   502  				Filename: "test.tf",
   503  				Start:    SourcePos{Line: 32, Column: 15, Byte: 434},
   504  				End:      SourcePos{Line: 32, Column: 20, Byte: 439},
   505  			},
   506  		},
   507  		{
   508  			// This should never happen as error should always point to an attribute
   509  			// or index of an attribute, but we should not crash if it does
   510  			AttributeValue(
   511  				Error,
   512  				"key",
   513  				"index_step",
   514  				cty.Path{
   515  					cty.IndexStep{Key: cty.StringVal("key")},
   516  				},
   517  			),
   518  			emptySrcRng,
   519  		},
   520  		{
   521  			// This should never happen as error should always point to an attribute
   522  			// or index of an attribute, but we should not crash if it does
   523  			AttributeValue(
   524  				Error,
   525  				"key.another",
   526  				"index_step",
   527  				cty.Path{
   528  					cty.IndexStep{Key: cty.StringVal("key")},
   529  					cty.IndexStep{Key: cty.StringVal("another")},
   530  				},
   531  			),
   532  			emptySrcRng,
   533  		},
   534  	}
   535  
   536  	for i, tc := range testCases {
   537  		t.Run(fmt.Sprintf("%d:%s", i, tc.Diag.Description()), func(t *testing.T) {
   538  			var diags Diagnostics
   539  
   540  			origAddr := tc.Diag.Description().Address
   541  			diags = diags.Append(tc.Diag)
   542  
   543  			gotDiags := diags.InConfigBody(f.Body, "test.addr")
   544  			gotRange := gotDiags[0].Source().Subject
   545  			gotAddr := gotDiags[0].Description().Address
   546  
   547  			switch {
   548  			case origAddr != "":
   549  				if gotAddr != origAddr {
   550  					t.Errorf("original diagnostic address modified from %s to %s", origAddr, gotAddr)
   551  				}
   552  			case gotAddr != "test.addr":
   553  				t.Error("missing detail address")
   554  			}
   555  
   556  			for _, problem := range deep.Equal(gotRange, tc.ExpectedRange) {
   557  				t.Error(problem)
   558  			}
   559  		})
   560  	}
   561  }
   562  
   563  func TestGetAttribute(t *testing.T) {
   564  	path := cty.Path{
   565  		cty.GetAttrStep{Name: "foo"},
   566  		cty.IndexStep{Key: cty.NumberIntVal(0)},
   567  		cty.GetAttrStep{Name: "bar"},
   568  	}
   569  
   570  	d := AttributeValue(
   571  		Error,
   572  		"foo[0].bar",
   573  		"detail",
   574  		path,
   575  	)
   576  
   577  	p := GetAttribute(d)
   578  	if !reflect.DeepEqual(path, p) {
   579  		t.Fatalf("paths don't match:\nexpected: %#v\ngot: %#v", path, p)
   580  	}
   581  }