github.com/terraform-linters/tflint@v0.51.2-0.20240520175844-3750771571b6/terraform/lang/funcs/filesystem_test.go (about)

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