terraform/internal/depsfile/locks_file_test.go

279 lines
9.6 KiB
Go

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, err := ioutil.TempDir("", "terraform-internal-depsfile-savelockstofile")
if err != nil {
t.Fatal(err.Error())
}
defer os.RemoveAll(dir)
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)
}
}