mirror of
https://github.com/opentofu/opentofu.git
synced 2024-12-28 01:41:48 -06:00
e0ecd2ebb3
Signed-off-by: RLRabinowitz <rlrabinowitz2@gmail.com>
277 lines
9.5 KiB
Go
277 lines
9.5 KiB
Go
// Copyright (c) HashiCorp, Inc.
|
|
// SPDX-License-Identifier: MPL-2.0
|
|
|
|
package depsfile
|
|
|
|
import (
|
|
"bufio"
|
|
"os"
|
|
"path/filepath"
|
|
"strings"
|
|
"testing"
|
|
|
|
"github.com/google/go-cmp/cmp"
|
|
"github.com/opentofu/opentofu/internal/addrs"
|
|
"github.com/opentofu/opentofu/internal/getproviders"
|
|
"github.com/opentofu/opentofu/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 := os.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 OpenTofu 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 := os.ReadFile(filename)
|
|
if err != nil {
|
|
t.Fatalf(err.Error())
|
|
}
|
|
gotContent := string(gotContentBytes)
|
|
wantContent := `# This file is maintained automatically by "tofu init".
|
|
# Manual edits may be lost in future updates.
|
|
|
|
provider "registry.opentofu.org/test/bar" {
|
|
version = "1.2.0"
|
|
constraints = "~> 1.0"
|
|
}
|
|
|
|
provider "registry.opentofu.org/test/baz" {
|
|
version = "1.2.0"
|
|
}
|
|
|
|
provider "registry.opentofu.org/test/boo" {
|
|
version = "1.2.0"
|
|
constraints = "1.2.0"
|
|
}
|
|
|
|
provider "registry.opentofu.org/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)
|
|
}
|
|
}
|