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