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 }