From ee8f7abc040224e1997b7575b89a87a99f656732 Mon Sep 17 00:00:00 2001 From: Daniel Banck Date: Tue, 3 Mar 2026 16:45:22 +0100 Subject: [PATCH] Add snapshot-related test for the new graph-based config loader This ensures we test snapshot related configuration loading. These tests previously lived in the `configload` package. --- internal/terraform/config_graph_build_test.go | 166 ++++++++++++++++++ .../.terraform/modules/modules.json | 7 + .../foo/bar/main.tf | 3 + .../already-installed-now-invalid/foo/main.tf | 3 + .../already-installed-now-invalid/root.tf | 3 + .../.terraform/modules/child_a/child_a.tf | 4 + .../modules/child_a/child_c/child_c.tf | 4 + .../modules/child_b.child_d/child_d.tf | 4 + .../.terraform/modules/child_b/child_b.tf | 5 + .../.terraform/modules/modules.json | 32 ++++ .../config-graph/already-installed/root.tf | 10 ++ 11 files changed, 241 insertions(+) create mode 100644 internal/terraform/config_graph_build_test.go create mode 100644 internal/terraform/testdata/config-graph/already-installed-now-invalid/.terraform/modules/modules.json create mode 100644 internal/terraform/testdata/config-graph/already-installed-now-invalid/foo/bar/main.tf create mode 100644 internal/terraform/testdata/config-graph/already-installed-now-invalid/foo/main.tf create mode 100644 internal/terraform/testdata/config-graph/already-installed-now-invalid/root.tf create mode 100644 internal/terraform/testdata/config-graph/already-installed/.terraform/modules/child_a/child_a.tf create mode 100644 internal/terraform/testdata/config-graph/already-installed/.terraform/modules/child_a/child_c/child_c.tf create mode 100644 internal/terraform/testdata/config-graph/already-installed/.terraform/modules/child_b.child_d/child_d.tf create mode 100644 internal/terraform/testdata/config-graph/already-installed/.terraform/modules/child_b/child_b.tf create mode 100644 internal/terraform/testdata/config-graph/already-installed/.terraform/modules/modules.json create mode 100644 internal/terraform/testdata/config-graph/already-installed/root.tf diff --git a/internal/terraform/config_graph_build_test.go b/internal/terraform/config_graph_build_test.go new file mode 100644 index 0000000000..1853ba3e5a --- /dev/null +++ b/internal/terraform/config_graph_build_test.go @@ -0,0 +1,166 @@ +// Copyright IBM Corp. 2014, 2026 +// SPDX-License-Identifier: BUSL-1.1 + +package terraform + +import ( + "os" + "path/filepath" + "reflect" + "testing" + + "github.com/davecgh/go-spew/spew" + "github.com/go-test/deep" + "github.com/hashicorp/hcl/v2" + "github.com/hashicorp/terraform/internal/configs" + "github.com/hashicorp/terraform/internal/configs/configload" + "github.com/hashicorp/terraform/internal/tfdiags" +) + +func TestLoadConfigWithSnapshot(t *testing.T) { + fixtureDir := filepath.Clean("testdata/config-graph/already-installed") + loader, err := configload.NewLoader(&configload.Config{ + ModulesDir: filepath.Join(fixtureDir, ".terraform/modules"), + }) + if err != nil { + t.Fatalf("unexpected error from NewLoader: %s", err) + } + + _, got, diags := testLoadWithSnapshot(fixtureDir, loader, nil) + assertNoDiagnostics(t, diags) + if got == nil { + t.Fatalf("snapshot is nil; want non-nil") + } + + t.Log(spew.Sdump(got)) + + { + gotModuleDirs := map[string]string{} + for k, m := range got.Modules { + gotModuleDirs[k] = m.Dir + } + wantModuleDirs := map[string]string{ + "": "testdata/config-graph/already-installed", + "child_a": "testdata/config-graph/already-installed/.terraform/modules/child_a", + "child_a.child_c": "testdata/config-graph/already-installed/.terraform/modules/child_a/child_c", + "child_b": "testdata/config-graph/already-installed/.terraform/modules/child_b", + "child_b.child_d": "testdata/config-graph/already-installed/.terraform/modules/child_b.child_d", + } + + problems := deep.Equal(wantModuleDirs, gotModuleDirs) + for _, problem := range problems { + t.Error(problem) + } + if len(problems) > 0 { + return + } + } + + gotRoot := got.Modules[""] + wantRoot := &configload.SnapshotModule{ + Dir: "testdata/config-graph/already-installed", + Files: map[string][]byte{ + "root.tf": []byte(` +module "child_a" { + source = "example.com/foo/bar_a/baz" + version = ">= 1.0.0" +} + +module "child_b" { + source = "example.com/foo/bar_b/baz" + version = ">= 1.0.0" +} +`), + }, + } + if !reflect.DeepEqual(gotRoot, wantRoot) { + t.Errorf("wrong root module snapshot\ngot: %swant: %s", spew.Sdump(gotRoot), spew.Sdump(wantRoot)) + } + +} + +func TestLoadConfigWithSnapshot_invalidSource(t *testing.T) { + fixtureDir := filepath.Clean("testdata/config-graph/already-installed-now-invalid") + + old, _ := os.Getwd() + os.Chdir(fixtureDir) + defer os.Chdir(old) + + loader, err := configload.NewLoader(&configload.Config{ + ModulesDir: ".terraform/modules", + }) + if err != nil { + t.Fatalf("unexpected error from NewLoader: %s", err) + } + + _, _, diags := testLoadWithSnapshot(".", loader, nil) + if !diags.HasErrors() { + t.Error("LoadConfigWithSnapshot succeeded; want errors") + } +} + +func TestSnapshotRoundtrip(t *testing.T) { + fixtureDir := filepath.Clean("testdata/config-graph/already-installed") + loader, err := configload.NewLoader(&configload.Config{ + ModulesDir: filepath.Join(fixtureDir, ".terraform/modules"), + }) + if err != nil { + t.Fatalf("unexpected error from NewLoader: %s", err) + } + + _, snap, diags := testLoadWithSnapshot(fixtureDir, loader, nil) + assertNoDiagnostics(t, diags) + if snap == nil { + t.Fatalf("snapshot is nil; want non-nil") + } + + snapLoader := configload.NewLoaderFromSnapshot(snap) + if loader == nil { + t.Fatalf("loader is nil; want non-nil") + } + rootMod, rootDiags := snapLoader.LoadRootModule(snap.Modules[""].Dir) + assertNoDiagnostics(t, rootDiags) + + config, diags := BuildConfigWithGraph( + rootMod, + snapLoader.ModuleWalker(), + nil, + configs.MockDataLoaderFunc(snapLoader.LoadExternalMockData), + ) + assertNoDiagnostics(t, diags) + if config == nil { + t.Fatalf("config is nil; want non-nil") + } + if config.Module == nil { + t.Fatalf("config has no root module") + } + if got, want := config.Module.SourceDir, "testdata/config-graph/already-installed"; got != want { + t.Errorf("wrong root module sourcedir %q; want %q", got, want) + } + if got, want := len(config.Module.ModuleCalls), 2; got != want { + t.Errorf("wrong number of module calls in root module %d; want %d", got, want) + } + childA := config.Children["child_a"] + if childA == nil { + t.Fatalf("child_a config is nil; want non-nil") + } + if childA.Module == nil { + t.Fatalf("child_a config has no module") + } + if got, want := childA.Module.SourceDir, "testdata/config-graph/already-installed/.terraform/modules/child_a"; got != want { + t.Errorf("wrong child_a sourcedir %q; want %q", got, want) + } + if got, want := len(childA.Module.ModuleCalls), 1; got != want { + t.Errorf("wrong number of module calls in child_a %d; want %d", got, want) + } +} + +func assertNoDiagnostics[D hcl.Diagnostics | tfdiags.Diagnostics](t *testing.T, diags D) bool { + t.Helper() + + if len(diags) != 0 { + t.Errorf("wrong number of diagnostics %d; want %d", len(diags), 0) + return true + } + return false +} diff --git a/internal/terraform/testdata/config-graph/already-installed-now-invalid/.terraform/modules/modules.json b/internal/terraform/testdata/config-graph/already-installed-now-invalid/.terraform/modules/modules.json new file mode 100644 index 0000000000..a09a3f4826 --- /dev/null +++ b/internal/terraform/testdata/config-graph/already-installed-now-invalid/.terraform/modules/modules.json @@ -0,0 +1,7 @@ +{ + "Modules": [ + { "Key": "", "Source": "", "Dir": "." }, + { "Key": "foo", "Source": "./foo", "Dir": "foo" }, + { "Key": "foo.bar", "Source": "./bar", "Dir": "foo/bar" } + ] +} diff --git a/internal/terraform/testdata/config-graph/already-installed-now-invalid/foo/bar/main.tf b/internal/terraform/testdata/config-graph/already-installed-now-invalid/foo/bar/main.tf new file mode 100644 index 0000000000..48b5e2e067 --- /dev/null +++ b/internal/terraform/testdata/config-graph/already-installed-now-invalid/foo/bar/main.tf @@ -0,0 +1,3 @@ +output "hello" { + value = "Hello from foo/bar" +} diff --git a/internal/terraform/testdata/config-graph/already-installed-now-invalid/foo/main.tf b/internal/terraform/testdata/config-graph/already-installed-now-invalid/foo/main.tf new file mode 100644 index 0000000000..9fba57235c --- /dev/null +++ b/internal/terraform/testdata/config-graph/already-installed-now-invalid/foo/main.tf @@ -0,0 +1,3 @@ +module "bar" { + source = "${path.module}/bar" +} diff --git a/internal/terraform/testdata/config-graph/already-installed-now-invalid/root.tf b/internal/terraform/testdata/config-graph/already-installed-now-invalid/root.tf new file mode 100644 index 0000000000..020494e84d --- /dev/null +++ b/internal/terraform/testdata/config-graph/already-installed-now-invalid/root.tf @@ -0,0 +1,3 @@ +module "foo" { + source = "./foo" +} diff --git a/internal/terraform/testdata/config-graph/already-installed/.terraform/modules/child_a/child_a.tf b/internal/terraform/testdata/config-graph/already-installed/.terraform/modules/child_a/child_a.tf new file mode 100644 index 0000000000..2f4d0f1a0b --- /dev/null +++ b/internal/terraform/testdata/config-graph/already-installed/.terraform/modules/child_a/child_a.tf @@ -0,0 +1,4 @@ + +module "child_c" { + source = "./child_c" +} diff --git a/internal/terraform/testdata/config-graph/already-installed/.terraform/modules/child_a/child_c/child_c.tf b/internal/terraform/testdata/config-graph/already-installed/.terraform/modules/child_a/child_c/child_c.tf new file mode 100644 index 0000000000..785d98d98a --- /dev/null +++ b/internal/terraform/testdata/config-graph/already-installed/.terraform/modules/child_a/child_c/child_c.tf @@ -0,0 +1,4 @@ + +output "hello" { + value = "Hello from child_c" +} diff --git a/internal/terraform/testdata/config-graph/already-installed/.terraform/modules/child_b.child_d/child_d.tf b/internal/terraform/testdata/config-graph/already-installed/.terraform/modules/child_b.child_d/child_d.tf new file mode 100644 index 0000000000..145576a365 --- /dev/null +++ b/internal/terraform/testdata/config-graph/already-installed/.terraform/modules/child_b.child_d/child_d.tf @@ -0,0 +1,4 @@ + +output "hello" { + value = "Hello from child_d" +} diff --git a/internal/terraform/testdata/config-graph/already-installed/.terraform/modules/child_b/child_b.tf b/internal/terraform/testdata/config-graph/already-installed/.terraform/modules/child_b/child_b.tf new file mode 100644 index 0000000000..4a1b247d39 --- /dev/null +++ b/internal/terraform/testdata/config-graph/already-installed/.terraform/modules/child_b/child_b.tf @@ -0,0 +1,5 @@ + +module "child_d" { + source = "example.com/foo/bar_d/baz" + # Intentionally no version here +} diff --git a/internal/terraform/testdata/config-graph/already-installed/.terraform/modules/modules.json b/internal/terraform/testdata/config-graph/already-installed/.terraform/modules/modules.json new file mode 100644 index 0000000000..957a8aebed --- /dev/null +++ b/internal/terraform/testdata/config-graph/already-installed/.terraform/modules/modules.json @@ -0,0 +1,32 @@ +{ + "Modules": [ + { + "Key": "", + "Source": "", + "Dir": "testdata/config-graph/already-installed" + }, + { + "Key": "child_a", + "Source": "example.com/foo/bar_a/baz", + "Version": "1.0.1", + "Dir": "testdata/config-graph/already-installed/.terraform/modules/child_a" + }, + { + "Key": "child_b", + "Source": "example.com/foo/bar_b/baz", + "Version": "1.0.0", + "Dir": "testdata/config-graph/already-installed/.terraform/modules/child_b" + }, + { + "Key": "child_a.child_c", + "Source": "./child_c", + "Dir": "testdata/config-graph/already-installed/.terraform/modules/child_a/child_c" + }, + { + "Key": "child_b.child_d", + "Source": "example.com/foo/bar_d/baz", + "Version": "1.2.0", + "Dir": "testdata/config-graph/already-installed/.terraform/modules/child_b.child_d" + } + ] +} diff --git a/internal/terraform/testdata/config-graph/already-installed/root.tf b/internal/terraform/testdata/config-graph/already-installed/root.tf new file mode 100644 index 0000000000..8a4473942d --- /dev/null +++ b/internal/terraform/testdata/config-graph/already-installed/root.tf @@ -0,0 +1,10 @@ + +module "child_a" { + source = "example.com/foo/bar_a/baz" + version = ">= 1.0.0" +} + +module "child_b" { + source = "example.com/foo/bar_b/baz" + version = ">= 1.0.0" +}