| package depsfile |
| |
| import ( |
| "bufio" |
| "io/ioutil" |
| "os" |
| "path/filepath" |
| "strings" |
| "testing" |
| |
| "github.com/google/go-cmp/cmp" |
| "github.com/hashicorp/terraform/internal/addrs" |
| "github.com/hashicorp/terraform/internal/getproviders" |
| "github.com/hashicorp/terraform/internal/tfdiags" |
| ) |
| |
| func TestLoadLocksFromFile(t *testing.T) { |
| // For ease of test maintenance we treat every file under |
| // test-data/locks-files as a test case which is subject |
| // at least to testing that it produces an expected set |
| // of diagnostics represented via specially-formatted comments |
| // in the fixture files (which might be the empty set, if |
| // there are no such comments). |
| // |
| // Some of the files also have additional assertions that |
| // are encoded in the test code below. These must pass |
| // in addition to the standard diagnostics tests, if present. |
| files, err := ioutil.ReadDir("testdata/locks-files") |
| if err != nil { |
| t.Fatal(err.Error()) |
| } |
| |
| for _, info := range files { |
| testName := filepath.Base(info.Name()) |
| filename := filepath.Join("testdata/locks-files", testName) |
| t.Run(testName, func(t *testing.T) { |
| f, err := os.Open(filename) |
| if err != nil { |
| t.Fatal(err.Error()) |
| } |
| defer f.Close() |
| const errorPrefix = "# ERROR: " |
| const warningPrefix = "# WARNING: " |
| wantErrors := map[int]string{} |
| wantWarnings := map[int]string{} |
| sc := bufio.NewScanner(f) |
| lineNum := 1 |
| for sc.Scan() { |
| l := sc.Text() |
| if pos := strings.Index(l, errorPrefix); pos != -1 { |
| wantSummary := l[pos+len(errorPrefix):] |
| wantErrors[lineNum] = wantSummary |
| } |
| if pos := strings.Index(l, warningPrefix); pos != -1 { |
| wantSummary := l[pos+len(warningPrefix):] |
| wantWarnings[lineNum] = wantSummary |
| } |
| lineNum++ |
| } |
| if err := sc.Err(); err != nil { |
| t.Fatal(err.Error()) |
| } |
| |
| locks, diags := LoadLocksFromFile(filename) |
| gotErrors := map[int]string{} |
| gotWarnings := map[int]string{} |
| for _, diag := range diags { |
| summary := diag.Description().Summary |
| if diag.Source().Subject == nil { |
| // We don't expect any sourceless diagnostics here. |
| t.Errorf("unexpected sourceless diagnostic: %s", summary) |
| continue |
| } |
| lineNum := diag.Source().Subject.Start.Line |
| switch sev := diag.Severity(); sev { |
| case tfdiags.Error: |
| gotErrors[lineNum] = summary |
| case tfdiags.Warning: |
| gotWarnings[lineNum] = summary |
| default: |
| t.Errorf("unexpected diagnostic severity %s", sev) |
| } |
| } |
| |
| if diff := cmp.Diff(wantErrors, gotErrors); diff != "" { |
| t.Errorf("wrong errors\n%s", diff) |
| } |
| if diff := cmp.Diff(wantWarnings, gotWarnings); diff != "" { |
| t.Errorf("wrong warnings\n%s", diff) |
| } |
| |
| switch testName { |
| // These are the file-specific test assertions. Not all files |
| // need custom test assertions in addition to the standard |
| // diagnostics assertions implemented above, so the cases here |
| // don't need to be exhaustive for all files. |
| // |
| // Please keep these in alphabetical order so the list is easy |
| // to scan! |
| |
| case "empty.hcl": |
| if got, want := len(locks.providers), 0; got != want { |
| t.Errorf("wrong number of providers %d; want %d", got, want) |
| } |
| |
| case "valid-provider-locks.hcl": |
| if got, want := len(locks.providers), 3; got != want { |
| t.Errorf("wrong number of providers %d; want %d", got, want) |
| } |
| |
| t.Run("version-only", func(t *testing.T) { |
| if lock := locks.Provider(addrs.MustParseProviderSourceString("terraform.io/test/version-only")); lock != nil { |
| if got, want := lock.Version().String(), "1.0.0"; got != want { |
| t.Errorf("wrong version\ngot: %s\nwant: %s", got, want) |
| } |
| if got, want := getproviders.VersionConstraintsString(lock.VersionConstraints()), ""; got != want { |
| t.Errorf("wrong version constraints\ngot: %s\nwant: %s", got, want) |
| } |
| if got, want := len(lock.hashes), 0; got != want { |
| t.Errorf("wrong number of hashes %d; want %d", got, want) |
| } |
| } |
| }) |
| |
| t.Run("version-and-constraints", func(t *testing.T) { |
| if lock := locks.Provider(addrs.MustParseProviderSourceString("terraform.io/test/version-and-constraints")); lock != nil { |
| if got, want := lock.Version().String(), "1.2.0"; got != want { |
| t.Errorf("wrong version\ngot: %s\nwant: %s", got, want) |
| } |
| if got, want := getproviders.VersionConstraintsString(lock.VersionConstraints()), "~> 1.2"; got != want { |
| t.Errorf("wrong version constraints\ngot: %s\nwant: %s", got, want) |
| } |
| if got, want := len(lock.hashes), 0; got != want { |
| t.Errorf("wrong number of hashes %d; want %d", got, want) |
| } |
| } |
| }) |
| |
| t.Run("all-the-things", func(t *testing.T) { |
| if lock := locks.Provider(addrs.MustParseProviderSourceString("terraform.io/test/all-the-things")); lock != nil { |
| if got, want := lock.Version().String(), "3.0.10"; got != want { |
| t.Errorf("wrong version\ngot: %s\nwant: %s", got, want) |
| } |
| if got, want := getproviders.VersionConstraintsString(lock.VersionConstraints()), ">= 3.0.2"; got != want { |
| t.Errorf("wrong version constraints\ngot: %s\nwant: %s", got, want) |
| } |
| wantHashes := []getproviders.Hash{ |
| getproviders.MustParseHash("test:placeholder-hash-1"), |
| getproviders.MustParseHash("test:placeholder-hash-2"), |
| getproviders.MustParseHash("test:placeholder-hash-3"), |
| } |
| if diff := cmp.Diff(wantHashes, lock.hashes); diff != "" { |
| t.Errorf("wrong hashes\n%s", diff) |
| } |
| } |
| }) |
| } |
| }) |
| } |
| } |
| |
| func TestLoadLocksFromFileAbsent(t *testing.T) { |
| t.Run("lock file is a directory", func(t *testing.T) { |
| // This can never happen when Terraform is the one generating the |
| // lock file, but might arise if the user makes a directory with the |
| // lock file's name for some reason. (There is no actual reason to do |
| // so, so that would always be a mistake.) |
| locks, diags := LoadLocksFromFile("testdata") |
| if len(locks.providers) != 0 { |
| t.Errorf("returned locks has providers; expected empty locks") |
| } |
| if !diags.HasErrors() { |
| t.Fatalf("LoadLocksFromFile succeeded; want error") |
| } |
| // This is a generic error message from HCL itself, so upgrading HCL |
| // in future might cause a different error message here. |
| want := `Failed to read file: The configuration file "testdata" could not be read.` |
| got := diags.Err().Error() |
| if got != want { |
| t.Errorf("wrong error message\ngot: %s\nwant: %s", got, want) |
| } |
| }) |
| t.Run("lock file doesn't exist", func(t *testing.T) { |
| locks, diags := LoadLocksFromFile("testdata/nonexist.hcl") |
| if len(locks.providers) != 0 { |
| t.Errorf("returned locks has providers; expected empty locks") |
| } |
| if !diags.HasErrors() { |
| t.Fatalf("LoadLocksFromFile succeeded; want error") |
| } |
| // This is a generic error message from HCL itself, so upgrading HCL |
| // in future might cause a different error message here. |
| want := `Failed to read file: The configuration file "testdata/nonexist.hcl" could not be read.` |
| got := diags.Err().Error() |
| if got != want { |
| t.Errorf("wrong error message\ngot: %s\nwant: %s", got, want) |
| } |
| }) |
| } |
| |
| func TestSaveLocksToFile(t *testing.T) { |
| locks := NewLocks() |
| |
| fooProvider := addrs.MustParseProviderSourceString("test/foo") |
| barProvider := addrs.MustParseProviderSourceString("test/bar") |
| bazProvider := addrs.MustParseProviderSourceString("test/baz") |
| booProvider := addrs.MustParseProviderSourceString("test/boo") |
| oneDotOh := getproviders.MustParseVersion("1.0.0") |
| oneDotTwo := getproviders.MustParseVersion("1.2.0") |
| atLeastOneDotOh := getproviders.MustParseVersionConstraints(">= 1.0.0") |
| pessimisticOneDotOh := getproviders.MustParseVersionConstraints("~> 1") |
| abbreviatedOneDotTwo := getproviders.MustParseVersionConstraints("1.2") |
| hashes := []getproviders.Hash{ |
| getproviders.MustParseHash("test:cccccccccccccccccccccccccccccccccccccccccccccccc"), |
| getproviders.MustParseHash("test:bbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbb"), |
| getproviders.MustParseHash("test:aaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaa"), |
| } |
| locks.SetProvider(fooProvider, oneDotOh, atLeastOneDotOh, hashes) |
| locks.SetProvider(barProvider, oneDotTwo, pessimisticOneDotOh, nil) |
| locks.SetProvider(bazProvider, oneDotTwo, nil, nil) |
| locks.SetProvider(booProvider, oneDotTwo, abbreviatedOneDotTwo, nil) |
| |
| dir := t.TempDir() |
| |
| filename := filepath.Join(dir, LockFilePath) |
| diags := SaveLocksToFile(locks, filename) |
| if diags.HasErrors() { |
| t.Fatalf("unexpected errors\n%s", diags.Err().Error()) |
| } |
| |
| fileInfo, err := os.Stat(filename) |
| if err != nil { |
| t.Fatalf(err.Error()) |
| } |
| if mode := fileInfo.Mode(); mode&0111 != 0 { |
| t.Fatalf("Expected lock file to be non-executable: %o", mode) |
| } |
| |
| gotContentBytes, err := ioutil.ReadFile(filename) |
| if err != nil { |
| t.Fatalf(err.Error()) |
| } |
| gotContent := string(gotContentBytes) |
| wantContent := `# This file is maintained automatically by "terraform init". |
| # Manual edits may be lost in future updates. |
| |
| provider "registry.terraform.io/test/bar" { |
| version = "1.2.0" |
| constraints = "~> 1.0" |
| } |
| |
| provider "registry.terraform.io/test/baz" { |
| version = "1.2.0" |
| } |
| |
| provider "registry.terraform.io/test/boo" { |
| version = "1.2.0" |
| constraints = "1.2.0" |
| } |
| |
| provider "registry.terraform.io/test/foo" { |
| version = "1.0.0" |
| constraints = ">= 1.0.0" |
| hashes = [ |
| "test:aaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaa", |
| "test:bbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbb", |
| "test:cccccccccccccccccccccccccccccccccccccccccccccccc", |
| ] |
| } |
| ` |
| if diff := cmp.Diff(wantContent, gotContent); diff != "" { |
| t.Errorf("wrong result\n%s", diff) |
| } |
| } |