github.com/terramate-io/tf@v0.0.0-20230830114523-fce866b4dfcd/lang/funcs/filesystem_test.go (about)

     1  // Copyright (c) HashiCorp, Inc.
     2  // SPDX-License-Identifier: MPL-2.0
     3  
     4  package funcs
     5  
     6  import (
     7  	"fmt"
     8  	"os"
     9  	"path/filepath"
    10  	"testing"
    11  
    12  	homedir "github.com/mitchellh/go-homedir"
    13  	"github.com/terramate-io/tf/lang/marks"
    14  	"github.com/zclconf/go-cty/cty"
    15  	"github.com/zclconf/go-cty/cty/function"
    16  	"github.com/zclconf/go-cty/cty/function/stdlib"
    17  )
    18  
    19  func TestFile(t *testing.T) {
    20  	tests := []struct {
    21  		Path cty.Value
    22  		Want cty.Value
    23  		Err  string
    24  	}{
    25  		{
    26  			cty.StringVal("testdata/hello.txt"),
    27  			cty.StringVal("Hello World"),
    28  			``,
    29  		},
    30  		{
    31  			cty.StringVal("testdata/icon.png"),
    32  			cty.NilVal,
    33  			`contents of "testdata/icon.png" are not valid UTF-8; use the filebase64 function to obtain the Base64 encoded contents or the other file functions (e.g. filemd5, filesha256) to obtain file hashing results instead`,
    34  		},
    35  		{
    36  			cty.StringVal("testdata/icon.png").Mark(marks.Sensitive),
    37  			cty.NilVal,
    38  			`contents of (sensitive value) are not valid UTF-8; use the filebase64 function to obtain the Base64 encoded contents or the other file functions (e.g. filemd5, filesha256) to obtain file hashing results instead`,
    39  		},
    40  		{
    41  			cty.StringVal("testdata/missing"),
    42  			cty.NilVal,
    43  			`no file exists at "testdata/missing"; this function works only with files that are distributed as part of the configuration source code, so if this file will be created by a resource in this configuration you must instead obtain this result from an attribute of that resource`,
    44  		},
    45  		{
    46  			cty.StringVal("testdata/missing").Mark(marks.Sensitive),
    47  			cty.NilVal,
    48  			`no file exists at (sensitive value); this function works only with files that are distributed as part of the configuration source code, so if this file will be created by a resource in this configuration you must instead obtain this result from an attribute of that resource`,
    49  		},
    50  	}
    51  
    52  	for _, test := range tests {
    53  		t.Run(fmt.Sprintf("File(\".\", %#v)", test.Path), func(t *testing.T) {
    54  			got, err := File(".", test.Path)
    55  
    56  			if test.Err != "" {
    57  				if err == nil {
    58  					t.Fatal("succeeded; want error")
    59  				}
    60  				if got, want := err.Error(), test.Err; got != want {
    61  					t.Errorf("wrong error\ngot:  %s\nwant: %s", got, want)
    62  				}
    63  				return
    64  			} else if err != nil {
    65  				t.Fatalf("unexpected error: %s", err)
    66  			}
    67  
    68  			if !got.RawEquals(test.Want) {
    69  				t.Errorf("wrong result\ngot:  %#v\nwant: %#v", got, test.Want)
    70  			}
    71  		})
    72  	}
    73  }
    74  
    75  func TestTemplateFile(t *testing.T) {
    76  	tests := []struct {
    77  		Path cty.Value
    78  		Vars cty.Value
    79  		Want cty.Value
    80  		Err  string
    81  	}{
    82  		{
    83  			cty.StringVal("testdata/hello.txt"),
    84  			cty.EmptyObjectVal,
    85  			cty.StringVal("Hello World"),
    86  			``,
    87  		},
    88  		{
    89  			cty.StringVal("testdata/icon.png"),
    90  			cty.EmptyObjectVal,
    91  			cty.NilVal,
    92  			`contents of "testdata/icon.png" are not valid UTF-8; use the filebase64 function to obtain the Base64 encoded contents or the other file functions (e.g. filemd5, filesha256) to obtain file hashing results instead`,
    93  		},
    94  		{
    95  			cty.StringVal("testdata/missing"),
    96  			cty.EmptyObjectVal,
    97  			cty.NilVal,
    98  			`no file exists at "testdata/missing"; this function works only with files that are distributed as part of the configuration source code, so if this file will be created by a resource in this configuration you must instead obtain this result from an attribute of that resource`,
    99  		},
   100  		{
   101  			cty.StringVal("testdata/secrets.txt").Mark(marks.Sensitive),
   102  			cty.EmptyObjectVal,
   103  			cty.NilVal,
   104  			`no file exists at (sensitive value); this function works only with files that are distributed as part of the configuration source code, so if this file will be created by a resource in this configuration you must instead obtain this result from an attribute of that resource`,
   105  		},
   106  		{
   107  			cty.StringVal("testdata/hello.tmpl"),
   108  			cty.MapVal(map[string]cty.Value{
   109  				"name": cty.StringVal("Jodie"),
   110  			}),
   111  			cty.StringVal("Hello, Jodie!"),
   112  			``,
   113  		},
   114  		{
   115  			cty.StringVal("testdata/hello.tmpl"),
   116  			cty.MapVal(map[string]cty.Value{
   117  				"name!": cty.StringVal("Jodie"),
   118  			}),
   119  			cty.NilVal,
   120  			`invalid template variable name "name!": must start with a letter, followed by zero or more letters, digits, and underscores`,
   121  		},
   122  		{
   123  			cty.StringVal("testdata/hello.tmpl"),
   124  			cty.ObjectVal(map[string]cty.Value{
   125  				"name": cty.StringVal("Jimbo"),
   126  			}),
   127  			cty.StringVal("Hello, Jimbo!"),
   128  			``,
   129  		},
   130  		{
   131  			cty.StringVal("testdata/hello.tmpl"),
   132  			cty.EmptyObjectVal,
   133  			cty.NilVal,
   134  			`vars map does not contain key "name", referenced at testdata/hello.tmpl:1,10-14`,
   135  		},
   136  		{
   137  			cty.StringVal("testdata/func.tmpl"),
   138  			cty.ObjectVal(map[string]cty.Value{
   139  				"list": cty.ListVal([]cty.Value{
   140  					cty.StringVal("a"),
   141  					cty.StringVal("b"),
   142  					cty.StringVal("c"),
   143  				}),
   144  			}),
   145  			cty.StringVal("The items are a, b, c"),
   146  			``,
   147  		},
   148  		{
   149  			cty.StringVal("testdata/recursive.tmpl"),
   150  			cty.MapValEmpty(cty.String),
   151  			cty.NilVal,
   152  			`testdata/recursive.tmpl:1,3-16: Error in function call; Call to function "templatefile" failed: cannot recursively call templatefile from inside templatefile call.`,
   153  		},
   154  		{
   155  			cty.StringVal("testdata/list.tmpl"),
   156  			cty.ObjectVal(map[string]cty.Value{
   157  				"list": cty.ListVal([]cty.Value{
   158  					cty.StringVal("a"),
   159  					cty.StringVal("b"),
   160  					cty.StringVal("c"),
   161  				}),
   162  			}),
   163  			cty.StringVal("- a\n- b\n- c\n"),
   164  			``,
   165  		},
   166  		{
   167  			cty.StringVal("testdata/list.tmpl"),
   168  			cty.ObjectVal(map[string]cty.Value{
   169  				"list": cty.True,
   170  			}),
   171  			cty.NilVal,
   172  			`testdata/list.tmpl:1,13-17: Iteration over non-iterable value; A value of type bool cannot be used as the collection in a 'for' expression.`,
   173  		},
   174  		{
   175  			cty.StringVal("testdata/bare.tmpl"),
   176  			cty.ObjectVal(map[string]cty.Value{
   177  				"val": cty.True,
   178  			}),
   179  			cty.True, // since this template contains only an interpolation, its true value shines through
   180  			``,
   181  		},
   182  	}
   183  
   184  	templateFileFn := MakeTemplateFileFunc(".", func() map[string]function.Function {
   185  		return map[string]function.Function{
   186  			"join":         stdlib.JoinFunc,
   187  			"templatefile": MakeFileFunc(".", false), // just a placeholder, since templatefile itself overrides this
   188  		}
   189  	})
   190  
   191  	for _, test := range tests {
   192  		t.Run(fmt.Sprintf("TemplateFile(%#v, %#v)", test.Path, test.Vars), func(t *testing.T) {
   193  			got, err := templateFileFn.Call([]cty.Value{test.Path, test.Vars})
   194  
   195  			if argErr, ok := err.(function.ArgError); ok {
   196  				if argErr.Index < 0 || argErr.Index > 1 {
   197  					t.Errorf("ArgError index %d is out of range for templatefile (must be 0 or 1)", argErr.Index)
   198  				}
   199  			}
   200  
   201  			if test.Err != "" {
   202  				if err == nil {
   203  					t.Fatal("succeeded; want error")
   204  				}
   205  				if got, want := err.Error(), test.Err; got != want {
   206  					t.Errorf("wrong error\ngot:  %s\nwant: %s", got, want)
   207  				}
   208  				return
   209  			} else if err != nil {
   210  				t.Fatalf("unexpected error: %s", err)
   211  			}
   212  
   213  			if !got.RawEquals(test.Want) {
   214  				t.Errorf("wrong result\ngot:  %#v\nwant: %#v", got, test.Want)
   215  			}
   216  		})
   217  	}
   218  }
   219  
   220  func TestFileExists(t *testing.T) {
   221  	tests := []struct {
   222  		Path cty.Value
   223  		Want cty.Value
   224  		Err  string
   225  	}{
   226  		{
   227  			cty.StringVal("testdata/hello.txt"),
   228  			cty.BoolVal(true),
   229  			``,
   230  		},
   231  		{
   232  			cty.StringVal(""),
   233  			cty.BoolVal(false),
   234  			`"." is a directory, not a file`,
   235  		},
   236  		{
   237  			cty.StringVal("testdata").Mark(marks.Sensitive),
   238  			cty.BoolVal(false),
   239  			`(sensitive value) is a directory, not a file`,
   240  		},
   241  		{
   242  			cty.StringVal("testdata/missing"),
   243  			cty.BoolVal(false),
   244  			``,
   245  		},
   246  		{
   247  			cty.StringVal("testdata/unreadable/foobar"),
   248  			cty.BoolVal(false),
   249  			`failed to stat "testdata/unreadable/foobar"`,
   250  		},
   251  		{
   252  			cty.StringVal("testdata/unreadable/foobar").Mark(marks.Sensitive),
   253  			cty.BoolVal(false),
   254  			`failed to stat (sensitive value)`,
   255  		},
   256  	}
   257  
   258  	// Ensure "unreadable" directory cannot be listed during the test run
   259  	fi, err := os.Lstat("testdata/unreadable")
   260  	if err != nil {
   261  		t.Fatal(err)
   262  	}
   263  	os.Chmod("testdata/unreadable", 0000)
   264  	defer func(mode os.FileMode) {
   265  		os.Chmod("testdata/unreadable", mode)
   266  	}(fi.Mode())
   267  
   268  	for _, test := range tests {
   269  		t.Run(fmt.Sprintf("FileExists(\".\", %#v)", test.Path), func(t *testing.T) {
   270  			got, err := FileExists(".", test.Path)
   271  
   272  			if test.Err != "" {
   273  				if err == nil {
   274  					t.Fatal("succeeded; want error")
   275  				}
   276  				if got, want := err.Error(), test.Err; got != want {
   277  					t.Errorf("wrong error\ngot:  %s\nwant: %s", got, want)
   278  				}
   279  				return
   280  			} else if err != nil {
   281  				t.Fatalf("unexpected error: %s", err)
   282  			}
   283  
   284  			if !got.RawEquals(test.Want) {
   285  				t.Errorf("wrong result\ngot:  %#v\nwant: %#v", got, test.Want)
   286  			}
   287  		})
   288  	}
   289  }
   290  
   291  func TestFileSet(t *testing.T) {
   292  	tests := []struct {
   293  		Path    cty.Value
   294  		Pattern cty.Value
   295  		Want    cty.Value
   296  		Err     string
   297  	}{
   298  		{
   299  			cty.StringVal("."),
   300  			cty.StringVal("testdata*"),
   301  			cty.SetValEmpty(cty.String),
   302  			``,
   303  		},
   304  		{
   305  			cty.StringVal("."),
   306  			cty.StringVal("testdata"),
   307  			cty.SetValEmpty(cty.String),
   308  			``,
   309  		},
   310  		{
   311  			cty.StringVal("."),
   312  			cty.StringVal("{testdata,missing}"),
   313  			cty.SetValEmpty(cty.String),
   314  			``,
   315  		},
   316  		{
   317  			cty.StringVal("."),
   318  			cty.StringVal("testdata/missing"),
   319  			cty.SetValEmpty(cty.String),
   320  			``,
   321  		},
   322  		{
   323  			cty.StringVal("."),
   324  			cty.StringVal("testdata/missing*"),
   325  			cty.SetValEmpty(cty.String),
   326  			``,
   327  		},
   328  		{
   329  			cty.StringVal("."),
   330  			cty.StringVal("*/missing"),
   331  			cty.SetValEmpty(cty.String),
   332  			``,
   333  		},
   334  		{
   335  			cty.StringVal("."),
   336  			cty.StringVal("**/missing"),
   337  			cty.SetValEmpty(cty.String),
   338  			``,
   339  		},
   340  		{
   341  			cty.StringVal("."),
   342  			cty.StringVal("testdata/*.txt"),
   343  			cty.SetVal([]cty.Value{
   344  				cty.StringVal("testdata/hello.txt"),
   345  			}),
   346  			``,
   347  		},
   348  		{
   349  			cty.StringVal("."),
   350  			cty.StringVal("testdata/hello.txt"),
   351  			cty.SetVal([]cty.Value{
   352  				cty.StringVal("testdata/hello.txt"),
   353  			}),
   354  			``,
   355  		},
   356  		{
   357  			cty.StringVal("."),
   358  			cty.StringVal("testdata/hello.???"),
   359  			cty.SetVal([]cty.Value{
   360  				cty.StringVal("testdata/hello.txt"),
   361  			}),
   362  			``,
   363  		},
   364  		{
   365  			cty.StringVal("."),
   366  			cty.StringVal("testdata/hello*"),
   367  			cty.SetVal([]cty.Value{
   368  				cty.StringVal("testdata/hello.tmpl"),
   369  				cty.StringVal("testdata/hello.txt"),
   370  			}),
   371  			``,
   372  		},
   373  		{
   374  			cty.StringVal("."),
   375  			cty.StringVal("testdata/hello.{tmpl,txt}"),
   376  			cty.SetVal([]cty.Value{
   377  				cty.StringVal("testdata/hello.tmpl"),
   378  				cty.StringVal("testdata/hello.txt"),
   379  			}),
   380  			``,
   381  		},
   382  		{
   383  			cty.StringVal("."),
   384  			cty.StringVal("*/hello.txt"),
   385  			cty.SetVal([]cty.Value{
   386  				cty.StringVal("testdata/hello.txt"),
   387  			}),
   388  			``,
   389  		},
   390  		{
   391  			cty.StringVal("."),
   392  			cty.StringVal("*/*.txt"),
   393  			cty.SetVal([]cty.Value{
   394  				cty.StringVal("testdata/hello.txt"),
   395  			}),
   396  			``,
   397  		},
   398  		{
   399  			cty.StringVal("."),
   400  			cty.StringVal("*/hello*"),
   401  			cty.SetVal([]cty.Value{
   402  				cty.StringVal("testdata/hello.tmpl"),
   403  				cty.StringVal("testdata/hello.txt"),
   404  			}),
   405  			``,
   406  		},
   407  		{
   408  			cty.StringVal("."),
   409  			cty.StringVal("**/hello*"),
   410  			cty.SetVal([]cty.Value{
   411  				cty.StringVal("testdata/hello.tmpl"),
   412  				cty.StringVal("testdata/hello.txt"),
   413  			}),
   414  			``,
   415  		},
   416  		{
   417  			cty.StringVal("."),
   418  			cty.StringVal("**/hello.{tmpl,txt}"),
   419  			cty.SetVal([]cty.Value{
   420  				cty.StringVal("testdata/hello.tmpl"),
   421  				cty.StringVal("testdata/hello.txt"),
   422  			}),
   423  			``,
   424  		},
   425  		{
   426  			cty.StringVal("."),
   427  			cty.StringVal("["),
   428  			cty.SetValEmpty(cty.String),
   429  			`failed to glob pattern "[": syntax error in pattern`,
   430  		},
   431  		{
   432  			cty.StringVal("."),
   433  			cty.StringVal("[").Mark(marks.Sensitive),
   434  			cty.SetValEmpty(cty.String),
   435  			`failed to glob pattern (sensitive value): syntax error in pattern`,
   436  		},
   437  		{
   438  			cty.StringVal("."),
   439  			cty.StringVal("\\"),
   440  			cty.SetValEmpty(cty.String),
   441  			`failed to glob pattern "\\": syntax error in pattern`,
   442  		},
   443  		{
   444  			cty.StringVal("testdata"),
   445  			cty.StringVal("missing"),
   446  			cty.SetValEmpty(cty.String),
   447  			``,
   448  		},
   449  		{
   450  			cty.StringVal("testdata"),
   451  			cty.StringVal("missing*"),
   452  			cty.SetValEmpty(cty.String),
   453  			``,
   454  		},
   455  		{
   456  			cty.StringVal("testdata"),
   457  			cty.StringVal("*.txt"),
   458  			cty.SetVal([]cty.Value{
   459  				cty.StringVal("hello.txt"),
   460  			}),
   461  			``,
   462  		},
   463  		{
   464  			cty.StringVal("testdata"),
   465  			cty.StringVal("hello.txt"),
   466  			cty.SetVal([]cty.Value{
   467  				cty.StringVal("hello.txt"),
   468  			}),
   469  			``,
   470  		},
   471  		{
   472  			cty.StringVal("testdata"),
   473  			cty.StringVal("hello.???"),
   474  			cty.SetVal([]cty.Value{
   475  				cty.StringVal("hello.txt"),
   476  			}),
   477  			``,
   478  		},
   479  		{
   480  			cty.StringVal("testdata"),
   481  			cty.StringVal("hello*"),
   482  			cty.SetVal([]cty.Value{
   483  				cty.StringVal("hello.tmpl"),
   484  				cty.StringVal("hello.txt"),
   485  			}),
   486  			``,
   487  		},
   488  	}
   489  
   490  	for _, test := range tests {
   491  		t.Run(fmt.Sprintf("FileSet(\".\", %#v, %#v)", test.Path, test.Pattern), func(t *testing.T) {
   492  			got, err := FileSet(".", test.Path, test.Pattern)
   493  
   494  			if test.Err != "" {
   495  				if err == nil {
   496  					t.Fatal("succeeded; want error")
   497  				}
   498  				if got, want := err.Error(), test.Err; got != want {
   499  					t.Errorf("wrong error\ngot:  %s\nwant: %s", got, want)
   500  				}
   501  				return
   502  			} else if err != nil {
   503  				t.Fatalf("unexpected error: %s", err)
   504  			}
   505  
   506  			if !got.RawEquals(test.Want) {
   507  				t.Errorf("wrong result\ngot:  %#v\nwant: %#v", got, test.Want)
   508  			}
   509  		})
   510  	}
   511  }
   512  
   513  func TestFileBase64(t *testing.T) {
   514  	tests := []struct {
   515  		Path cty.Value
   516  		Want cty.Value
   517  		Err  bool
   518  	}{
   519  		{
   520  			cty.StringVal("testdata/hello.txt"),
   521  			cty.StringVal("SGVsbG8gV29ybGQ="),
   522  			false,
   523  		},
   524  		{
   525  			cty.StringVal("testdata/icon.png"),
   526  			cty.StringVal("iVBORw0KGgoAAAANSUhEUgAAABAAAAAQCAMAAAAoLQ9TAAAABGdBTUEAALGPC/xhBQAAACBjSFJNAAB6JgAAgIQAAPoAAACA6AAAdTAAAOpgAAA6mAAAF3CculE8AAAAq1BMVEX///9cTuVeUeRcTuZcTuZcT+VbSe1cTuVdT+MAAP9JSbZcT+VcTuZAQLFAQLJcTuVcTuZcUuBBQbA/P7JAQLJaTuRcT+RcTuVGQ7xAQLJVVf9cTuVcTuVGRMFeUeRbTeJcTuU/P7JeTeZbTOVcTeZAQLJBQbNAQLNaUORcTeZbT+VcTuRAQLNAQLRdTuRHR8xgUOdgUN9cTuVdTeRdT+VZTulcTuVAQLL///8+GmETAAAANnRSTlMApibw+osO6DcBB3fIX87+oRk3yehB0/Nj/gNs7nsTRv3dHmu//JYUMLVr3bssjxkgEK5CaxeK03nIAAAAAWJLR0QAiAUdSAAAAAlwSFlzAAADoQAAA6EBvJf9gwAAAAd0SU1FB+EEBRIQDxZNTKsAAACCSURBVBjTfc7JFsFQEATQQpCYxyBEzJ55rvf/f0ZHcyQLvelTd1GngEwWycs5+UISyKLraSi9geWKK9Gr1j7AeqOJVtt2XtD1Bchef2BjQDAcCTC0CsA4mihMtXw2XwgsV2sFw812F+4P3y2GdI6nn3FGSs//4HJNAXDzU4Dg/oj/E+bsEbhf5cMsAAAAJXRFWHRkYXRlOmNyZWF0ZQAyMDE3LTA0LTA1VDE4OjE2OjE1KzAyOjAws5bLVQAAACV0RVh0ZGF0ZTptb2RpZnkAMjAxNy0wNC0wNVQxODoxNjoxNSswMjowMMLLc+kAAAAZdEVYdFNvZnR3YXJlAHd3dy5pbmtzY2FwZS5vcmeb7jwaAAAAC3RFWHRUaXRsZQBHcm91cJYfIowAAABXelRYdFJhdyBwcm9maWxlIHR5cGUgaXB0YwAAeJzj8gwIcVYoKMpPy8xJ5VIAAyMLLmMLEyMTS5MUAxMgRIA0w2QDI7NUIMvY1MjEzMQcxAfLgEigSi4A6hcRdPJCNZUAAAAASUVORK5CYII="),
   527  			false,
   528  		},
   529  		{
   530  			cty.StringVal("testdata/missing"),
   531  			cty.NilVal,
   532  			true, // no file exists
   533  		},
   534  	}
   535  
   536  	for _, test := range tests {
   537  		t.Run(fmt.Sprintf("FileBase64(\".\", %#v)", test.Path), func(t *testing.T) {
   538  			got, err := FileBase64(".", test.Path)
   539  
   540  			if test.Err {
   541  				if err == nil {
   542  					t.Fatal("succeeded; want error")
   543  				}
   544  				return
   545  			} else if err != nil {
   546  				t.Fatalf("unexpected error: %s", err)
   547  			}
   548  
   549  			if !got.RawEquals(test.Want) {
   550  				t.Errorf("wrong result\ngot:  %#v\nwant: %#v", got, test.Want)
   551  			}
   552  		})
   553  	}
   554  }
   555  
   556  func TestBasename(t *testing.T) {
   557  	tests := []struct {
   558  		Path cty.Value
   559  		Want cty.Value
   560  		Err  bool
   561  	}{
   562  		{
   563  			cty.StringVal("testdata/hello.txt"),
   564  			cty.StringVal("hello.txt"),
   565  			false,
   566  		},
   567  		{
   568  			cty.StringVal("hello.txt"),
   569  			cty.StringVal("hello.txt"),
   570  			false,
   571  		},
   572  		{
   573  			cty.StringVal(""),
   574  			cty.StringVal("."),
   575  			false,
   576  		},
   577  	}
   578  
   579  	for _, test := range tests {
   580  		t.Run(fmt.Sprintf("Basename(%#v)", test.Path), func(t *testing.T) {
   581  			got, err := Basename(test.Path)
   582  
   583  			if test.Err {
   584  				if err == nil {
   585  					t.Fatal("succeeded; want error")
   586  				}
   587  				return
   588  			} else if err != nil {
   589  				t.Fatalf("unexpected error: %s", err)
   590  			}
   591  
   592  			if !got.RawEquals(test.Want) {
   593  				t.Errorf("wrong result\ngot:  %#v\nwant: %#v", got, test.Want)
   594  			}
   595  		})
   596  	}
   597  }
   598  
   599  func TestDirname(t *testing.T) {
   600  	tests := []struct {
   601  		Path cty.Value
   602  		Want cty.Value
   603  		Err  bool
   604  	}{
   605  		{
   606  			cty.StringVal("testdata/hello.txt"),
   607  			cty.StringVal("testdata"),
   608  			false,
   609  		},
   610  		{
   611  			cty.StringVal("testdata/foo/hello.txt"),
   612  			cty.StringVal("testdata/foo"),
   613  			false,
   614  		},
   615  		{
   616  			cty.StringVal("hello.txt"),
   617  			cty.StringVal("."),
   618  			false,
   619  		},
   620  		{
   621  			cty.StringVal(""),
   622  			cty.StringVal("."),
   623  			false,
   624  		},
   625  	}
   626  
   627  	for _, test := range tests {
   628  		t.Run(fmt.Sprintf("Dirname(%#v)", test.Path), func(t *testing.T) {
   629  			got, err := Dirname(test.Path)
   630  
   631  			if test.Err {
   632  				if err == nil {
   633  					t.Fatal("succeeded; want error")
   634  				}
   635  				return
   636  			} else if err != nil {
   637  				t.Fatalf("unexpected error: %s", err)
   638  			}
   639  
   640  			if !got.RawEquals(test.Want) {
   641  				t.Errorf("wrong result\ngot:  %#v\nwant: %#v", got, test.Want)
   642  			}
   643  		})
   644  	}
   645  }
   646  
   647  func TestPathExpand(t *testing.T) {
   648  	homePath, err := homedir.Dir()
   649  	if err != nil {
   650  		t.Fatalf("Error getting home directory: %v", err)
   651  	}
   652  
   653  	tests := []struct {
   654  		Path cty.Value
   655  		Want cty.Value
   656  		Err  bool
   657  	}{
   658  		{
   659  			cty.StringVal("~/test-file"),
   660  			cty.StringVal(filepath.Join(homePath, "test-file")),
   661  			false,
   662  		},
   663  		{
   664  			cty.StringVal("~/another/test/file"),
   665  			cty.StringVal(filepath.Join(homePath, "another/test/file")),
   666  			false,
   667  		},
   668  		{
   669  			cty.StringVal("/root/file"),
   670  			cty.StringVal("/root/file"),
   671  			false,
   672  		},
   673  		{
   674  			cty.StringVal("/"),
   675  			cty.StringVal("/"),
   676  			false,
   677  		},
   678  	}
   679  
   680  	for _, test := range tests {
   681  		t.Run(fmt.Sprintf("Dirname(%#v)", test.Path), func(t *testing.T) {
   682  			got, err := Pathexpand(test.Path)
   683  
   684  			if test.Err {
   685  				if err == nil {
   686  					t.Fatal("succeeded; want error")
   687  				}
   688  				return
   689  			} else if err != nil {
   690  				t.Fatalf("unexpected error: %s", err)
   691  			}
   692  
   693  			if !got.RawEquals(test.Want) {
   694  				t.Errorf("wrong result\ngot:  %#v\nwant: %#v", got, test.Want)
   695  			}
   696  		})
   697  	}
   698  }