From 10c5e226bbf48a873a8209b73f4c3696970b4ad0 Mon Sep 17 00:00:00 2001 From: Sarah French Date: Tue, 11 Nov 2025 17:11:14 +0000 Subject: [PATCH 01/18] test: Add E2E tests for `state list` and `state show` commands --- .../e2etest/pluggable_state_store_test.go | 78 +++++++++++++++++++ .../.terraform.lock.hcl | 6 ++ .../hashicorp/simple6/0.0.1/.gitkeep | 0 .../.terraform/terraform.tfstate | 16 ++++ .../main.tf | 25 ++++++ .../states/default/terraform.tfstate | 40 ++++++++++ 6 files changed, 165 insertions(+) create mode 100644 internal/command/e2etest/testdata/initialized-directory-with-state-store-fs/.terraform.lock.hcl create mode 100644 internal/command/e2etest/testdata/initialized-directory-with-state-store-fs/.terraform/providers/registry.terraform.io/hashicorp/simple6/0.0.1/.gitkeep create mode 100644 internal/command/e2etest/testdata/initialized-directory-with-state-store-fs/.terraform/terraform.tfstate create mode 100644 internal/command/e2etest/testdata/initialized-directory-with-state-store-fs/main.tf create mode 100644 internal/command/e2etest/testdata/initialized-directory-with-state-store-fs/states/default/terraform.tfstate diff --git a/internal/command/e2etest/pluggable_state_store_test.go b/internal/command/e2etest/pluggable_state_store_test.go index a8f59183b988..09f802ce8cca 100644 --- a/internal/command/e2etest/pluggable_state_store_test.go +++ b/internal/command/e2etest/pluggable_state_store_test.go @@ -11,10 +11,12 @@ import ( "strings" "testing" + "github.com/google/go-cmp/cmp" "github.com/hashicorp/terraform/internal/e2e" "github.com/hashicorp/terraform/internal/getproviders" ) +// Tests using `terraform workspace` commands in combination with pluggable state storage. func TestPrimary_stateStore_workspaceCmd(t *testing.T) { if !canRunGoBuild { // We're running in a separate-build-then-run context, so we can't @@ -119,3 +121,79 @@ func TestPrimary_stateStore_workspaceCmd(t *testing.T) { t.Errorf("unexpected output, expected %q, but got:\n%s", expectedMsg, stdout) } } + +// Tests using `terraform state` subcommands in combination with pluggable state storage: +// > `terraform state show` +// > `terraform state list` +func TestPrimary_stateStore_stateCmds(t *testing.T) { + + if !canRunGoBuild { + // We're running in a separate-build-then-run context, so we can't + // currently execute this test which depends on being able to build + // new executable at runtime. + // + // (See the comment on canRunGoBuild's declaration for more information.) + t.Skip("can't run without building a new provider executable") + } + + t.Setenv(e2e.TestExperimentFlag, "true") + tfBin := e2e.GoBuild("github.com/hashicorp/terraform", "terraform") + + fixturePath := filepath.Join("testdata", "initialized-directory-with-state-store-fs") + tf := e2e.NewBinary(t, tfBin, fixturePath) + + workspaceDirName := "states" // see test fixture value for workspace_dir + + // In order to test integration with PSS we need a provider plugin implementing a state store. + // Here will build the simple6 (built with protocol v6) provider, which implements PSS. + simple6Provider := filepath.Join(tf.WorkDir(), "terraform-provider-simple6") + simple6ProviderExe := e2e.GoBuild("github.com/hashicorp/terraform/internal/provider-simple-v6/main", simple6Provider) + + // Move the provider binaries into the correct directory .terraform/providers/ directory + // that will contain provider binaries in an initialized working directory. + platform := getproviders.CurrentPlatform.String() + if err := os.MkdirAll(tf.Path(".terraform/providers/registry.terraform.io/hashicorp/simple6/0.0.1/", platform), os.ModePerm); err != nil { + t.Fatal(err) + } + if err := os.Rename(simple6ProviderExe, tf.Path(".terraform/providers/registry.terraform.io/hashicorp/simple6/0.0.1/", platform, "terraform-provider-simple6")); err != nil { + t.Fatal(err) + } + + // Assert that the test starts with the default state present from test fixtures + defaultStateId := "default" + fi, err := os.Stat(path.Join(tf.WorkDir(), workspaceDirName, defaultStateId, "terraform.tfstate")) + if err != nil { + t.Fatalf("failed to open default workspace's state file: %s", err) + } + if fi.Size() == 0 { + t.Fatal("default workspace's state file should not have size 0 bytes") + } + + //// List State: terraform state list + expectedResourceAddr := "terraform_data.my-data" + stdout, stderr, err := tf.Run("state", "list", "-no-color") + if err != nil { + t.Fatalf("unexpected error: %s\nstderr:\n%s", err, stderr) + } + expectedMsg := expectedResourceAddr + "\n" // This is the only resource instance in the test fixture state + if stdout != expectedMsg { + t.Errorf("unexpected output, expected %q, but got:\n%s", expectedMsg, stdout) + } + + //// Show State: terraform state show + stdout, stderr, err = tf.Run("state", "show", expectedResourceAddr, "-no-color") + if err != nil { + t.Fatalf("unexpected error: %s\nstderr:\n%s", err, stderr) + } + // show displays the state for the specified resource + expectedMsg = `# terraform_data.my-data: +resource "terraform_data" "my-data" { + id = "d71fb368-2ba1-fb4c-5bd9-6a2b7f05d60c" + input = "hello world" + output = "hello world" +} +` + if diff := cmp.Diff(stdout, expectedMsg); diff != "" { + t.Errorf("wrong result, diff:\n%s", diff) + } +} diff --git a/internal/command/e2etest/testdata/initialized-directory-with-state-store-fs/.terraform.lock.hcl b/internal/command/e2etest/testdata/initialized-directory-with-state-store-fs/.terraform.lock.hcl new file mode 100644 index 000000000000..7a0db0a25a93 --- /dev/null +++ b/internal/command/e2etest/testdata/initialized-directory-with-state-store-fs/.terraform.lock.hcl @@ -0,0 +1,6 @@ +# This file is maintained automatically by "terraform init". +# Manual edits may be lost in future updates. + +provider "registry.terraform.io/hashicorp/simple6" { + version = "0.0.1" +} diff --git a/internal/command/e2etest/testdata/initialized-directory-with-state-store-fs/.terraform/providers/registry.terraform.io/hashicorp/simple6/0.0.1/.gitkeep b/internal/command/e2etest/testdata/initialized-directory-with-state-store-fs/.terraform/providers/registry.terraform.io/hashicorp/simple6/0.0.1/.gitkeep new file mode 100644 index 000000000000..e69de29bb2d1 diff --git a/internal/command/e2etest/testdata/initialized-directory-with-state-store-fs/.terraform/terraform.tfstate b/internal/command/e2etest/testdata/initialized-directory-with-state-store-fs/.terraform/terraform.tfstate new file mode 100644 index 000000000000..e297b792ce7b --- /dev/null +++ b/internal/command/e2etest/testdata/initialized-directory-with-state-store-fs/.terraform/terraform.tfstate @@ -0,0 +1,16 @@ +{ + "version": 3, + "terraform_version": "1.15.0", + "state_store": { + "type": "simple6_fs", + "provider": { + "version": "0.0.1", + "source": "registry.terraform.io/hashicorp/simple6", + "config": {} + }, + "config": { + "workspace_dir": "states" + }, + "hash": 3942813381 + } +} \ No newline at end of file diff --git a/internal/command/e2etest/testdata/initialized-directory-with-state-store-fs/main.tf b/internal/command/e2etest/testdata/initialized-directory-with-state-store-fs/main.tf new file mode 100644 index 000000000000..d2c773a6fca9 --- /dev/null +++ b/internal/command/e2etest/testdata/initialized-directory-with-state-store-fs/main.tf @@ -0,0 +1,25 @@ +terraform { + required_providers { + simple6 = { + source = "registry.terraform.io/hashicorp/simple6" + } + } + + state_store "simple6_fs" { + provider "simple6" {} + + workspace_dir = "states" + } +} + +variable "name" { + default = "world" +} + +resource "terraform_data" "my-data" { + input = "hello ${var.name}" +} + +output "greeting" { + value = resource.terraform_data.my-data.output +} diff --git a/internal/command/e2etest/testdata/initialized-directory-with-state-store-fs/states/default/terraform.tfstate b/internal/command/e2etest/testdata/initialized-directory-with-state-store-fs/states/default/terraform.tfstate new file mode 100644 index 000000000000..4feaaed87a08 --- /dev/null +++ b/internal/command/e2etest/testdata/initialized-directory-with-state-store-fs/states/default/terraform.tfstate @@ -0,0 +1,40 @@ +{ + "version": 4, + "terraform_version": "1.15.0", + "serial": 1, + "lineage": "9e13d881-e480-7a63-d47a-b4f5224e6743", + "outputs": { + "greeting": { + "value": "hello world", + "type": "string" + } + }, + "resources": [ + { + "mode": "managed", + "type": "terraform_data", + "name": "my-data", + "provider": "provider[\"terraform.io/builtin/terraform\"]", + "instances": [ + { + "schema_version": 0, + "attributes": { + "id": "d71fb368-2ba1-fb4c-5bd9-6a2b7f05d60c", + "input": { + "value": "hello world", + "type": "string" + }, + "output": { + "value": "hello world", + "type": "string" + }, + "triggers_replace": null + }, + "sensitive_attributes": [], + "identity_schema_version": 0 + } + ] + } + ], + "check_results": null +} \ No newline at end of file From 9aa680d53b2b7c4e8bbe6a18983146f8d9032c31 Mon Sep 17 00:00:00 2001 From: Sarah French Date: Fri, 21 Nov 2025 17:41:05 +0000 Subject: [PATCH 02/18] test: Update `mockPluggableStateStorageProvider` to log a warning during tests where the values in `MockStates` aren't compatible with the `ReadStateBytesFn` default function. Make existing test set an appropriate value in `MockStates`. --- internal/command/autocomplete_test.go | 4 +-- internal/command/init_test.go | 42 +++++++++++++++------------ 2 files changed, 26 insertions(+), 20 deletions(-) diff --git a/internal/command/autocomplete_test.go b/internal/command/autocomplete_test.go index c13be9fbb06d..97d84a30c3d0 100644 --- a/internal/command/autocomplete_test.go +++ b/internal/command/autocomplete_test.go @@ -42,7 +42,7 @@ func TestMetaCompletePredictWorkspaceName(t *testing.T) { t.Chdir(td) // Set up pluggable state store provider mock - mockProvider := mockPluggableStateStorageProvider() + mockProvider := mockPluggableStateStorageProvider(t) // Mock the existence of workspaces mockProvider.MockStates = map[string]interface{}{ "default": true, @@ -89,7 +89,7 @@ func TestMetaCompletePredictWorkspaceName(t *testing.T) { t.Chdir(td) // Set up pluggable state store provider mock - mockProvider := mockPluggableStateStorageProvider() + mockProvider := mockPluggableStateStorageProvider(t) // No workspaces exist in the mock mockProvider.MockStates = map[string]interface{}{} mockProviderAddress := addrs.NewDefaultProvider("test") diff --git a/internal/command/init_test.go b/internal/command/init_test.go index 829b73734b27..b245863acd46 100644 --- a/internal/command/init_test.go +++ b/internal/command/init_test.go @@ -3238,7 +3238,7 @@ func TestInit_stateStore_newWorkingDir(t *testing.T) { testCopyDir(t, testFixturePath("init-with-state-store"), td) t.Chdir(td) - mockProvider := mockPluggableStateStorageProvider() + mockProvider := mockPluggableStateStorageProvider(t) mockProviderAddress := addrs.NewDefaultProvider("test") providerSource, close := newMockProviderSource(t, map[string][]string{ // The test fixture config has no version constraints, so the latest version will @@ -3325,7 +3325,7 @@ func TestInit_stateStore_newWorkingDir(t *testing.T) { testCopyDir(t, testFixturePath("init-with-state-store"), td) t.Chdir(td) - mockProvider := mockPluggableStateStorageProvider() + mockProvider := mockPluggableStateStorageProvider(t) mockProviderAddress := addrs.NewDefaultProvider("test") providerSource, close := newMockProviderSource(t, map[string][]string{ "hashicorp/test": {"1.0.0"}, @@ -3374,7 +3374,7 @@ func TestInit_stateStore_newWorkingDir(t *testing.T) { testCopyDir(t, testFixturePath("init-with-state-store"), td) t.Chdir(td) - mockProvider := mockPluggableStateStorageProvider() + mockProvider := mockPluggableStateStorageProvider(t) mockProviderAddress := addrs.NewDefaultProvider("test") providerSource, close := newMockProviderSource(t, map[string][]string{ "hashicorp/test": {"1.0.0"}, @@ -3429,7 +3429,7 @@ func TestInit_stateStore_newWorkingDir(t *testing.T) { customWorkspace := "my-custom-workspace" t.Setenv(WorkspaceNameEnvVar, customWorkspace) - mockProvider := mockPluggableStateStorageProvider() + mockProvider := mockPluggableStateStorageProvider(t) mockProviderAddress := addrs.NewDefaultProvider("test") providerSource, close := newMockProviderSource(t, map[string][]string{ "hashicorp/test": {"1.0.0"}, @@ -3492,7 +3492,7 @@ func TestInit_stateStore_newWorkingDir(t *testing.T) { testCopyDir(t, testFixturePath("init-with-state-store"), td) t.Chdir(td) - mockProvider := mockPluggableStateStorageProvider() + mockProvider := mockPluggableStateStorageProvider(t) mockProvider.GetStatesResponse = &providers.GetStatesResponse{ States: []string{ "foobar1", @@ -3583,7 +3583,7 @@ func TestInit_stateStore_configUnchanged(t *testing.T) { testCopyDir(t, testFixturePath("state-store-unchanged"), td) t.Chdir(td) - mockProvider := mockPluggableStateStorageProvider() + mockProvider := mockPluggableStateStorageProvider(t) // If the working directory was previously initialized successfully then at least // one workspace is guaranteed to exist when a user is re-running init with no config // changes since last init. So this test says `default` exists. @@ -3670,11 +3670,11 @@ func TestInit_stateStore_configChanges(t *testing.T) { testCopyDir(t, testFixturePath("state-store-changed/store-config"), td) t.Chdir(td) - mockProvider := mockPluggableStateStorageProvider() + mockProvider := mockPluggableStateStorageProvider(t) // The previous init implied by this test scenario would have created this. mockProvider.GetStatesResponse = &providers.GetStatesResponse{States: []string{"default"}} - mockProvider.MockStates = map[string]interface{}{"default": true} + mockProvider.MockStates = map[string]interface{}{"default": []byte(`{"version": 4,"terraform_version":"1.15.0","serial": 1,"lineage": "","outputs": {},"resources": [],"checks":[]}`)} mockProviderAddress := addrs.NewDefaultProvider("test") providerSource, close := newMockProviderSource(t, map[string][]string{ @@ -3758,7 +3758,7 @@ func TestInit_stateStore_configChanges(t *testing.T) { testCopyDir(t, testFixturePath("state-store-changed/store-config"), td) t.Chdir(td) - mockProvider := mockPluggableStateStorageProvider() + mockProvider := mockPluggableStateStorageProvider(t) mockProvider.GetStatesResponse = &providers.GetStatesResponse{States: []string{"default"}} // The previous init implied by this test scenario would have created the default workspace. mockProviderAddress := addrs.NewDefaultProvider("test") providerSource, close := newMockProviderSource(t, map[string][]string{ @@ -3808,7 +3808,7 @@ func TestInit_stateStore_configChanges(t *testing.T) { testCopyDir(t, testFixturePath("state-store-changed/provider-config"), td) t.Chdir(td) - mockProvider := mockPluggableStateStorageProvider() + mockProvider := mockPluggableStateStorageProvider(t) mockProvider.GetStatesResponse = &providers.GetStatesResponse{States: []string{"default"}} // The previous init implied by this test scenario would have created the default workspace. mockProviderAddress := addrs.NewDefaultProvider("test") providerSource, close := newMockProviderSource(t, map[string][]string{ @@ -3857,7 +3857,7 @@ func TestInit_stateStore_configChanges(t *testing.T) { testCopyDir(t, testFixturePath("state-store-changed/state-store-type"), td) t.Chdir(td) - mockProvider := mockPluggableStateStorageProvider() + mockProvider := mockPluggableStateStorageProvider(t) storeName := "test_store" otherStoreName := "test_otherstore" // Make the provider report that it contains a 2nd storage implementation with the above name @@ -3909,11 +3909,11 @@ func TestInit_stateStore_configChanges(t *testing.T) { testCopyDir(t, testFixturePath("state-store-changed/provider-used"), td) t.Chdir(td) - mockProvider := mockPluggableStateStorageProvider() + mockProvider := mockPluggableStateStorageProvider(t) mockProvider.GetStatesResponse = &providers.GetStatesResponse{States: []string{"default"}} // The previous init implied by this test scenario would have created the default workspace. // Make a mock that implies its name is test2 based on returned schemas - mockProvider2 := mockPluggableStateStorageProvider() + mockProvider2 := mockPluggableStateStorageProvider(t) mockProvider2.GetProviderSchemaResponse.StateStores["test2_store"] = mockProvider.GetProviderSchemaResponse.StateStores["test_store"] delete(mockProvider2.GetProviderSchemaResponse.StateStores, "test_store") @@ -3974,7 +3974,7 @@ func TestInit_stateStore_providerUpgrade(t *testing.T) { testCopyDir(t, testFixturePath("state-store-changed/provider-upgraded"), td) t.Chdir(td) - mockProvider := mockPluggableStateStorageProvider() + mockProvider := mockPluggableStateStorageProvider(t) mockProviderAddress := addrs.NewDefaultProvider("test") providerSource, close := newMockProviderSource(t, map[string][]string{ "hashicorp/test": {"1.2.3", "9.9.9"}, // 1.2.3 is the version used in the backend state file, 9.9.9 is the version being upgraded to @@ -4023,7 +4023,7 @@ func TestInit_stateStore_unset(t *testing.T) { testCopyDir(t, testFixturePath("init-state-store"), td) t.Chdir(td) - mockProvider := mockPluggableStateStorageProvider() + mockProvider := mockPluggableStateStorageProvider(t) storeName := "test_store" otherStoreName := "test_otherstore" // Make the provider report that it contains a 2nd storage implementation with the above name @@ -4121,7 +4121,7 @@ func TestInit_stateStore_unset_withoutProviderRequirements(t *testing.T) { testCopyDir(t, testFixturePath("init-state-store"), td) t.Chdir(td) - mockProvider := mockPluggableStateStorageProvider() + mockProvider := mockPluggableStateStorageProvider(t) storeName := "test_store" otherStoreName := "test_otherstore" // Make the provider report that it contains a 2nd storage implementation with the above name @@ -4355,7 +4355,7 @@ func expectedPackageInstallPath(name, version string, exe bool) string { )) } -func mockPluggableStateStorageProvider() *testing_provider.MockProvider { +func mockPluggableStateStorageProvider(t *testing.T) *testing_provider.MockProvider { // Create a mock provider to use for PSS // Get mock provider factory to be used during init // @@ -4412,8 +4412,14 @@ func mockPluggableStateStorageProvider() *testing_provider.MockProvider { mock.ReadStateBytesFn = func(req providers.ReadStateBytesRequest) providers.ReadStateBytesResponse { state := []byte{} if v, exist := mock.MockStates[req.StateId]; exist { - if s, ok := v.([]byte); ok { + s, ok := v.([]byte) + if ok { state = s + } else { + // Test setup is incorrect if this happens + t.Logf("Warning: The mock provider is set up to return state bytes from the MockStates map, but the mock encountered a value that wasn't a slice of bytes: %#v", + mock.MockStates, + ) } } return providers.ReadStateBytesResponse{ From 33635cdda7db8e18906e5bce35b01e0639e09664 Mon Sep 17 00:00:00 2001 From: Sarah French Date: Fri, 21 Nov 2025 17:44:30 +0000 Subject: [PATCH 03/18] test: Update `mockPluggableStateStorageProvider` helper to include a resource schema --- internal/command/init_test.go | 13 +++++++++++-- 1 file changed, 11 insertions(+), 2 deletions(-) diff --git a/internal/command/init_test.go b/internal/command/init_test.go index b245863acd46..99a035bcfa99 100644 --- a/internal/command/init_test.go +++ b/internal/command/init_test.go @@ -4371,8 +4371,17 @@ func mockPluggableStateStorageProvider(t *testing.T) *testing_provider.MockProvi }, }, }, - DataSources: map[string]providers.Schema{}, - ResourceTypes: map[string]providers.Schema{}, + DataSources: map[string]providers.Schema{}, + ResourceTypes: map[string]providers.Schema{ + "test_instance": { + Body: &configschema.Block{ + Attributes: map[string]*configschema.Attribute{ + "input": {Type: cty.String, Optional: true}, + "id": {Type: cty.String, Computed: true}, + }, + }, + }, + }, ListResourceTypes: map[string]providers.Schema{}, StateStores: map[string]providers.Schema{ pssName: { From 4d94e87c799311606286ea89a4c2fe9152861ffd Mon Sep 17 00:00:00 2001 From: Sarah French Date: Fri, 21 Nov 2025 17:59:43 +0000 Subject: [PATCH 04/18] test: Add command-level test for `state list` showing integration with pluggable state storage code. --- internal/command/state_list_test.go | 82 +++++++++++++++++++ .../.terraform.lock.hcl | 6 ++ .../hashicorp/simple6/0.0.1/.gitkeep | 0 .../.terraform/terraform.tfstate | 19 +++++ .../testdata/state-list-state-store/main.tf | 25 ++++++ .../state-list-state-store/terraform.tfstate | 40 +++++++++ 6 files changed, 172 insertions(+) create mode 100644 internal/command/testdata/state-list-state-store/.terraform.lock.hcl create mode 100644 internal/command/testdata/state-list-state-store/.terraform/providers/registry.terraform.io/hashicorp/simple6/0.0.1/.gitkeep create mode 100644 internal/command/testdata/state-list-state-store/.terraform/terraform.tfstate create mode 100644 internal/command/testdata/state-list-state-store/main.tf create mode 100644 internal/command/testdata/state-list-state-store/terraform.tfstate diff --git a/internal/command/state_list_test.go b/internal/command/state_list_test.go index c667c11754c8..494e21dbf978 100644 --- a/internal/command/state_list_test.go +++ b/internal/command/state_list_test.go @@ -4,10 +4,15 @@ package command import ( + "bytes" "strings" "testing" "github.com/hashicorp/cli" + "github.com/hashicorp/terraform/internal/addrs" + "github.com/hashicorp/terraform/internal/providers" + "github.com/hashicorp/terraform/internal/states" + "github.com/hashicorp/terraform/internal/states/statefile" ) func TestStateList(t *testing.T) { @@ -153,6 +158,83 @@ func TestStateList_backendCustomState(t *testing.T) { } } +// Tests using `terraform state list` subcommand in combination with pluggable state storage +// +// Note: Whereas other tests in this file use the local backend and require a state file in the test fixures, +// with pluggable state storage we can define the state via the mocked provider. +func TestStateList_stateStore(t *testing.T) { + // Create a temporary working directory that is empty + td := t.TempDir() + testCopyDir(t, testFixturePath("state-list-state-store"), td) + t.Chdir(td) + + // Get bytes describing a state containing a resource + state := states.NewState() + rootModule := state.RootModule() + rootModule.SetResourceInstanceCurrent( + addrs.Resource{ + Mode: addrs.ManagedResourceMode, + Type: "test_instance", + Name: "foo", + }.Instance(addrs.NoKey), + &states.ResourceInstanceObjectSrc{ + Status: states.ObjectReady, + AttrsJSON: []byte(`{ + "ami": "bar", + "network_interface": [{ + "device_index": 0, + "description": "Main network interface" + }] + }`), + }, + addrs.AbsProviderConfig{ + Provider: addrs.NewDefaultProvider("test"), + Module: addrs.RootModule, + }, + ) + var stateBuf bytes.Buffer + if err := statefile.Write(statefile.New(state, "", 1), &stateBuf); err != nil { + t.Fatal(err) + } + + // Create a mock that contains a persisted "default" state that uses the bytes from above. + mockProvider := mockPluggableStateStorageProvider(t) + mockProvider.MockStates = map[string]interface{}{ + "default": stateBuf.Bytes(), + } + mockProviderAddress := addrs.NewDefaultProvider("test") + providerSource, close := newMockProviderSource(t, map[string][]string{ + "hashicorp/test": {"1.0.0"}, + }) + defer close() + + ui := cli.NewMockUi() + c := &StateListCommand{ + Meta: Meta{ + AllowExperimentalFeatures: true, + testingOverrides: &testingOverrides{ + Providers: map[addrs.Provider]providers.Factory{ + mockProviderAddress: providers.FactoryFixed(mockProvider), + }, + }, + ProviderSource: providerSource, + Ui: ui, + }, + } + + args := []string{} + if code := c.Run(args); code != 0 { + t.Fatalf("bad: %d\n\n%s", code, ui.ErrorWriter.String()) + } + + // Test that outputs were displayed + expected := "test_instance.foo\n" + actual := ui.OutputWriter.String() + if actual != expected { + t.Fatalf("Expected:\n%q\n\nTo equal: %q", actual, expected) + } +} + func TestStateList_backendOverrideState(t *testing.T) { // Create a temporary working directory that is empty td := t.TempDir() diff --git a/internal/command/testdata/state-list-state-store/.terraform.lock.hcl b/internal/command/testdata/state-list-state-store/.terraform.lock.hcl new file mode 100644 index 000000000000..e5c03757a7fa --- /dev/null +++ b/internal/command/testdata/state-list-state-store/.terraform.lock.hcl @@ -0,0 +1,6 @@ +# This file is maintained automatically by "terraform init". +# Manual edits may be lost in future updates. + +provider "registry.terraform.io/hashicorp/test" { + version = "1.2.3" +} diff --git a/internal/command/testdata/state-list-state-store/.terraform/providers/registry.terraform.io/hashicorp/simple6/0.0.1/.gitkeep b/internal/command/testdata/state-list-state-store/.terraform/providers/registry.terraform.io/hashicorp/simple6/0.0.1/.gitkeep new file mode 100644 index 000000000000..e69de29bb2d1 diff --git a/internal/command/testdata/state-list-state-store/.terraform/terraform.tfstate b/internal/command/testdata/state-list-state-store/.terraform/terraform.tfstate new file mode 100644 index 000000000000..4f96aa73ee7d --- /dev/null +++ b/internal/command/testdata/state-list-state-store/.terraform/terraform.tfstate @@ -0,0 +1,19 @@ +{ + "version": 3, + "serial": 0, + "lineage": "666f9301-7e65-4b19-ae23-71184bb19b03", + "state_store": { + "type": "test_store", + "config": { + "value": "foobar" + }, + "provider": { + "version": "1.2.3", + "source": "registry.terraform.io/hashicorp/test", + "config": { + "region": null + } + }, + "hash": 4158988729 + } +} \ No newline at end of file diff --git a/internal/command/testdata/state-list-state-store/main.tf b/internal/command/testdata/state-list-state-store/main.tf new file mode 100644 index 000000000000..84f54d6c1a4a --- /dev/null +++ b/internal/command/testdata/state-list-state-store/main.tf @@ -0,0 +1,25 @@ +terraform { + required_providers { + test = { + source = "registry.terraform.io/hashicorp/test" + } + } + + state_store "test_store" { + provider "test" {} + + value = "foobar" + } +} + +variable "name" { + default = "world" +} + +resource "test_instance" "my-data" { + input = "hello ${var.name}" +} + +output "greeting" { + value = resource.terraform_data.my-data.output +} diff --git a/internal/command/testdata/state-list-state-store/terraform.tfstate b/internal/command/testdata/state-list-state-store/terraform.tfstate new file mode 100644 index 000000000000..4feaaed87a08 --- /dev/null +++ b/internal/command/testdata/state-list-state-store/terraform.tfstate @@ -0,0 +1,40 @@ +{ + "version": 4, + "terraform_version": "1.15.0", + "serial": 1, + "lineage": "9e13d881-e480-7a63-d47a-b4f5224e6743", + "outputs": { + "greeting": { + "value": "hello world", + "type": "string" + } + }, + "resources": [ + { + "mode": "managed", + "type": "terraform_data", + "name": "my-data", + "provider": "provider[\"terraform.io/builtin/terraform\"]", + "instances": [ + { + "schema_version": 0, + "attributes": { + "id": "d71fb368-2ba1-fb4c-5bd9-6a2b7f05d60c", + "input": { + "value": "hello world", + "type": "string" + }, + "output": { + "value": "hello world", + "type": "string" + }, + "triggers_replace": null + }, + "sensitive_attributes": [], + "identity_schema_version": 0 + } + ] + } + ], + "check_results": null +} \ No newline at end of file From b9a1d3ff3acd3d8032abd90d74b02f52f8ac0fd6 Mon Sep 17 00:00:00 2001 From: Sarah French Date: Fri, 21 Nov 2025 17:59:55 +0000 Subject: [PATCH 05/18] test: Add command-level test for `state show` showing integration with pluggable state storage code. --- internal/command/state_show_test.go | 82 +++++++++++++++++++++++++++++ 1 file changed, 82 insertions(+) diff --git a/internal/command/state_show_test.go b/internal/command/state_show_test.go index de8386795e6a..c7b47d0da88f 100644 --- a/internal/command/state_show_test.go +++ b/internal/command/state_show_test.go @@ -4,13 +4,16 @@ package command import ( + "bytes" "strings" "testing" + "github.com/hashicorp/cli" "github.com/hashicorp/terraform/internal/addrs" "github.com/hashicorp/terraform/internal/configs/configschema" "github.com/hashicorp/terraform/internal/providers" "github.com/hashicorp/terraform/internal/states" + "github.com/hashicorp/terraform/internal/states/statefile" "github.com/hashicorp/terraform/internal/terminal" "github.com/zclconf/go-cty/cty" ) @@ -332,6 +335,85 @@ func TestStateShow_configured_provider(t *testing.T) { } } +// Tests using `terraform state show` subcommand in combination with pluggable state storage +// +// Note: Whereas other tests in this file use the local backend and require a state file in the test fixures, +// with pluggable state storage we can define the state via the mocked provider. +func TestStateShow_stateStore(t *testing.T) { + // Create a temporary working directory that is empty + td := t.TempDir() + testCopyDir(t, testFixturePath("state-list-state-store"), td) + t.Chdir(td) + + // Get bytes describing a state containing a resource + state := states.NewState() + rootModule := state.RootModule() + rootModule.SetResourceInstanceCurrent( + addrs.Resource{ + Mode: addrs.ManagedResourceMode, + Type: "test_instance", + Name: "foo", + }.Instance(addrs.NoKey), + &states.ResourceInstanceObjectSrc{ + Status: states.ObjectReady, + AttrsJSON: []byte(`{ + "input": "foobar" + }`), + }, + addrs.AbsProviderConfig{ + Provider: addrs.NewDefaultProvider("test"), + Module: addrs.RootModule, + }, + ) + var stateBuf bytes.Buffer + if err := statefile.Write(statefile.New(state, "", 1), &stateBuf); err != nil { + t.Fatalf("error during test setup: %s", err) + } + + // Create a mock that contains a persisted "default" state that uses the bytes from above. + mockProvider := mockPluggableStateStorageProvider(t) + mockProvider.MockStates = map[string]interface{}{ + "default": stateBuf.Bytes(), + } + mockProviderAddress := addrs.NewDefaultProvider("test") + providerSource, close := newMockProviderSource(t, map[string][]string{ + "hashicorp/test": {"1.0.0"}, + }) + defer close() + + ui := cli.NewMockUi() + streams, done := terminal.StreamsForTesting(t) + c := &StateShowCommand{ + Meta: Meta{ + AllowExperimentalFeatures: true, + testingOverrides: &testingOverrides{ + Providers: map[addrs.Provider]providers.Factory{ + mockProviderAddress: providers.FactoryFixed(mockProvider), + }, + }, + ProviderSource: providerSource, + Ui: ui, + Streams: streams, + }, + } + + // `terraform show` command specifying a given resource addr + expectedResourceAddr := "test_instance.foo" + args := []string{expectedResourceAddr} + code := c.Run(args) + output := done(t) + if code != 0 { + t.Fatalf("bad: %d\n\n%s", code, output.Stderr()) + } + + // Test that outputs were displayed + expected := "# test_instance.foo:\nresource \"test_instance\" \"foo\" {\n input = \"foobar\"\n}\n" + actual := output.Stdout() + if actual != expected { + t.Fatalf("Expected:\n%q\n\nTo equal: %q", actual, expected) + } +} + const testStateShowOutput = ` # test_instance.foo: resource "test_instance" "foo" { From 6cb56253d9f8eb8c2f80899000e2250783f62e42 Mon Sep 17 00:00:00 2001 From: Sarah French Date: Fri, 21 Nov 2025 18:48:43 +0000 Subject: [PATCH 06/18] test: Add command-level test for `state pull` showing integration with pluggable state storage code. --- internal/command/state_pull_test.go | 83 +++++++++++++++++++++++++++++ 1 file changed, 83 insertions(+) diff --git a/internal/command/state_pull_test.go b/internal/command/state_pull_test.go index a4079152aa29..55d2983ea68a 100644 --- a/internal/command/state_pull_test.go +++ b/internal/command/state_pull_test.go @@ -10,6 +10,11 @@ import ( "testing" "github.com/hashicorp/cli" + "github.com/hashicorp/terraform/internal/addrs" + "github.com/hashicorp/terraform/internal/providers" + "github.com/hashicorp/terraform/internal/states" + "github.com/hashicorp/terraform/internal/states/statefile" + "github.com/hashicorp/terraform/internal/terminal" ) func TestStatePull(t *testing.T) { @@ -43,6 +48,84 @@ func TestStatePull(t *testing.T) { } } +// Tests using `terraform state pull` subcommand in combination with pluggable state storage +// +// Note: Whereas other tests in this file use the local backend and require a state file in the test fixures, +// with pluggable state storage we can define the state via the mocked provider. +func TestStatePull_stateStore(t *testing.T) { + // Create a temporary working directory that is empty + td := t.TempDir() + testCopyDir(t, testFixturePath("state-list-state-store"), td) + t.Chdir(td) + + // Get bytes describing a state containing a resource + state := states.NewState() + rootModule := state.RootModule() + rootModule.SetResourceInstanceCurrent( + addrs.Resource{ + Mode: addrs.ManagedResourceMode, + Type: "test_instance", + Name: "foo", + }.Instance(addrs.NoKey), + &states.ResourceInstanceObjectSrc{ + Status: states.ObjectReady, + AttrsJSON: []byte(`{ + "input": "foobar" + }`), + }, + addrs.AbsProviderConfig{ + Provider: addrs.NewDefaultProvider("test"), + Module: addrs.RootModule, + }, + ) + var stateBuf bytes.Buffer + if err := statefile.Write(statefile.New(state, "", 1), &stateBuf); err != nil { + t.Fatalf("error during test setup: %s", err) + } + stateBytes := stateBuf.Bytes() + + // Create a mock that contains a persisted "default" state that uses the bytes from above. + mockProvider := mockPluggableStateStorageProvider(t) + mockProvider.MockStates = map[string]interface{}{ + "default": stateBytes, + } + mockProviderAddress := addrs.NewDefaultProvider("test") + providerSource, close := newMockProviderSource(t, map[string][]string{ + "hashicorp/test": {"1.0.0"}, + }) + defer close() + + ui := cli.NewMockUi() + streams, _ := terminal.StreamsForTesting(t) + c := &StatePullCommand{ + Meta: Meta{ + AllowExperimentalFeatures: true, + testingOverrides: &testingOverrides{ + Providers: map[addrs.Provider]providers.Factory{ + mockProviderAddress: providers.FactoryFixed(mockProvider), + }, + }, + ProviderSource: providerSource, + Ui: ui, + Streams: streams, + }, + } + + // `terraform show` command specifying a given resource addr + expectedResourceAddr := "test_instance.foo" + args := []string{expectedResourceAddr} + code := c.Run(args) + if code != 0 { + t.Fatalf("bad: %d\n\n%s", code, ui.ErrorWriter.String()) + } + + // Test that the state in the output matches the original state + actual := ui.OutputWriter.Bytes() + if bytes.Equal(actual, stateBytes) { + t.Fatalf("expected:\n%s\n\nto include: %q", actual, stateBytes) + } +} + func TestStatePull_noState(t *testing.T) { tmp := t.TempDir() t.Chdir(tmp) From 1bdfba534912323be3de706569193ffdd1a42d4d Mon Sep 17 00:00:00 2001 From: Sarah French Date: Fri, 21 Nov 2025 19:07:56 +0000 Subject: [PATCH 07/18] test: Add command-level test for `state identities` showing integration with pluggable state storage code. --- internal/command/state_identities_test.go | 74 +++++++++++++++++++ .../.terraform.lock.hcl | 6 ++ .../.terraform/terraform.tfstate | 19 +++++ .../state-identities-state-store/main.tf | 13 ++++ 4 files changed, 112 insertions(+) create mode 100644 internal/command/testdata/state-identities-state-store/.terraform.lock.hcl create mode 100644 internal/command/testdata/state-identities-state-store/.terraform/terraform.tfstate create mode 100644 internal/command/testdata/state-identities-state-store/main.tf diff --git a/internal/command/state_identities_test.go b/internal/command/state_identities_test.go index 64dfb5c1593c..b6d7c59c6528 100644 --- a/internal/command/state_identities_test.go +++ b/internal/command/state_identities_test.go @@ -4,6 +4,7 @@ package command import ( + "bytes" "encoding/json" "os" "path/filepath" @@ -11,6 +12,9 @@ import ( "testing" "github.com/hashicorp/cli" + "github.com/hashicorp/terraform/internal/addrs" + "github.com/hashicorp/terraform/internal/providers" + "github.com/hashicorp/terraform/internal/states/statefile" ) func TestStateIdentities(t *testing.T) { @@ -423,3 +427,73 @@ func TestStateIdentities_modules(t *testing.T) { }) } + +func TestStateIdentities_stateStore(t *testing.T) { + // We need configuration present to force pluggable state storage to be used + td := t.TempDir() + testCopyDir(t, testFixturePath("state-identities-state-store"), td) + t.Chdir(td) + + // Get a state file, that contains identity information,as bytes + state := testStateWithIdentity() + var stateBuf bytes.Buffer + if err := statefile.Write(statefile.New(state, "", 1), &stateBuf); err != nil { + t.Fatalf("error during test setup: %s", err) + } + stateBytes := stateBuf.Bytes() + + // Create a mock that contains a persisted "default" state that uses the bytes from above. + mockProvider := mockPluggableStateStorageProvider(t) + mockProvider.MockStates = map[string]interface{}{ + "default": stateBytes, + } + mockProviderAddress := addrs.NewDefaultProvider("test") + providerSource, close := newMockProviderSource(t, map[string][]string{ + "hashicorp/test": {"1.0.0"}, + }) + defer close() + + ui := cli.NewMockUi() + c := &StateIdentitiesCommand{ + Meta: Meta{ + AllowExperimentalFeatures: true, + testingOverrides: &testingOverrides{ + Providers: map[addrs.Provider]providers.Factory{ + mockProviderAddress: providers.FactoryFixed(mockProvider), + }, + }, + ProviderSource: providerSource, + Ui: ui, + }, + } + + args := []string{"-json"} + if code := c.Run(args); code != 0 { + t.Fatalf("bad: %d\n\n%s", code, ui.ErrorWriter.String()) + } + + // Test that outputs were displayed + expected := `{ + "test_instance.bar": { + "id": "my-bar-id" + }, + "test_instance.foo": { + "id": "my-foo-id" + } +} +` + actual := ui.OutputWriter.String() + + // Normalize JSON strings + var expectedJSON, actualJSON map[string]interface{} + if err := json.Unmarshal([]byte(expected), &expectedJSON); err != nil { + t.Fatalf("Failed to unmarshal expected JSON: %s", err) + } + if err := json.Unmarshal([]byte(actual), &actualJSON); err != nil { + t.Fatalf("Failed to unmarshal actual JSON: %s", err) + } + + if !reflect.DeepEqual(expectedJSON, actualJSON) { + t.Fatalf("Expected:\n%q\n\nTo equal: %q", expected, actual) + } +} diff --git a/internal/command/testdata/state-identities-state-store/.terraform.lock.hcl b/internal/command/testdata/state-identities-state-store/.terraform.lock.hcl new file mode 100644 index 000000000000..e5c03757a7fa --- /dev/null +++ b/internal/command/testdata/state-identities-state-store/.terraform.lock.hcl @@ -0,0 +1,6 @@ +# This file is maintained automatically by "terraform init". +# Manual edits may be lost in future updates. + +provider "registry.terraform.io/hashicorp/test" { + version = "1.2.3" +} diff --git a/internal/command/testdata/state-identities-state-store/.terraform/terraform.tfstate b/internal/command/testdata/state-identities-state-store/.terraform/terraform.tfstate new file mode 100644 index 000000000000..4f96aa73ee7d --- /dev/null +++ b/internal/command/testdata/state-identities-state-store/.terraform/terraform.tfstate @@ -0,0 +1,19 @@ +{ + "version": 3, + "serial": 0, + "lineage": "666f9301-7e65-4b19-ae23-71184bb19b03", + "state_store": { + "type": "test_store", + "config": { + "value": "foobar" + }, + "provider": { + "version": "1.2.3", + "source": "registry.terraform.io/hashicorp/test", + "config": { + "region": null + } + }, + "hash": 4158988729 + } +} \ No newline at end of file diff --git a/internal/command/testdata/state-identities-state-store/main.tf b/internal/command/testdata/state-identities-state-store/main.tf new file mode 100644 index 000000000000..34b58fdc0e2e --- /dev/null +++ b/internal/command/testdata/state-identities-state-store/main.tf @@ -0,0 +1,13 @@ +terraform { + required_providers { + test = { + source = "registry.terraform.io/hashicorp/test" + } + } + + state_store "test_store" { + provider "test" {} + + value = "foobar" + } +} From bbfbf633c425f815f2aa2e0679443d1efdc70cc2 Mon Sep 17 00:00:00 2001 From: Sarah French Date: Mon, 24 Nov 2025 11:49:01 +0000 Subject: [PATCH 08/18] test: Add command-level test for `state rm` showing integration with pluggable state storage code. --- internal/command/state_rm_test.go | 106 ++++++++++++++++++++++++++++++ 1 file changed, 106 insertions(+) diff --git a/internal/command/state_rm_test.go b/internal/command/state_rm_test.go index 7aece89f7d4a..c940168ee0c9 100644 --- a/internal/command/state_rm_test.go +++ b/internal/command/state_rm_test.go @@ -4,6 +4,7 @@ package command import ( + "bytes" "os" "path/filepath" "strings" @@ -12,7 +13,9 @@ import ( "github.com/hashicorp/cli" "github.com/hashicorp/terraform/internal/addrs" + "github.com/hashicorp/terraform/internal/providers" "github.com/hashicorp/terraform/internal/states" + "github.com/hashicorp/terraform/internal/states/statefile" ) func TestStateRm(t *testing.T) { @@ -82,6 +85,109 @@ func TestStateRm(t *testing.T) { testStateOutput(t, backups[0], testStateRmOutputOriginal) } +func TestStateRm_stateStore(t *testing.T) { + // Create a temporary working directory + td := t.TempDir() + testCopyDir(t, testFixturePath("state-list-state-store"), td) + t.Chdir(td) + + // Get bytes describing a state containing resources + state := states.BuildState(func(s *states.SyncState) { + s.SetResourceInstanceCurrent( + addrs.Resource{ + Mode: addrs.ManagedResourceMode, + Type: "test_instance", + Name: "foo", + }.Instance(addrs.NoKey).Absolute(addrs.RootModuleInstance), + &states.ResourceInstanceObjectSrc{ + AttrsJSON: []byte(`{"id":"bar","foo":"value","bar":"value"}`), + Status: states.ObjectReady, + }, + addrs.AbsProviderConfig{ + Provider: addrs.NewDefaultProvider("test"), + Module: addrs.RootModule, + }, + ) + s.SetResourceInstanceCurrent( + addrs.Resource{ + Mode: addrs.ManagedResourceMode, + Type: "test_instance", + Name: "bar", + }.Instance(addrs.NoKey).Absolute(addrs.RootModuleInstance), + &states.ResourceInstanceObjectSrc{ + AttrsJSON: []byte(`{"id":"foo","foo":"value","bar":"value"}`), + Status: states.ObjectReady, + }, + addrs.AbsProviderConfig{ + Provider: addrs.NewDefaultProvider("test"), + Module: addrs.RootModule, + }, + ) + }) + var stateBuf bytes.Buffer + if err := statefile.Write(statefile.New(state, "", 1), &stateBuf); err != nil { + t.Fatalf("error during test setup: %s", err) + } + + // Create a mock that contains a persisted "default" state that uses the bytes from above. + mockProvider := mockPluggableStateStorageProvider(t) + mockProvider.MockStates = map[string]interface{}{ + "default": stateBuf.Bytes(), + } + mockProviderAddress := addrs.NewDefaultProvider("test") + providerSource, close := newMockProviderSource(t, map[string][]string{ + "hashicorp/test": {"1.0.0"}, + }) + defer close() + + // Make the mock assert that the removed resource is not present when the new state is persisted + keptResource := "test_instance.bar" + removedResource := "test_instance.foo" + mockProvider.WriteStateBytesFn = func(req providers.WriteStateBytesRequest) providers.WriteStateBytesResponse { + r := bytes.NewReader(req.Bytes) + file, err := statefile.Read(r) + if err != nil { + t.Fatal(err) + } + + root := file.State.Modules[""] + if _, ok := root.Resources[keptResource]; !ok { + t.Fatalf("expected the new state to keep the %s resource, but it couldn't be found", keptResource) + } + if _, ok := root.Resources[removedResource]; ok { + t.Fatalf("expected the %s resource to be removed from the state, but it is present", removedResource) + } + return providers.WriteStateBytesResponse{} + } + + ui := new(cli.MockUi) + view, _ := testView(t) + c := &StateRmCommand{ + StateMeta{ + Meta: Meta{ + AllowExperimentalFeatures: true, + testingOverrides: &testingOverrides{ + Providers: map[addrs.Provider]providers.Factory{ + mockProviderAddress: providers.FactoryFixed(mockProvider), + }, + }, + ProviderSource: providerSource, + Ui: ui, + View: view, + }, + }, + } + + args := []string{ + removedResource, + } + if code := c.Run(args); code != 0 { + t.Fatalf("bad: %d\n\n%s", code, ui.ErrorWriter.String()) + } + + // See the mock definition above for logic that asserts what the new state will look like after removing the resource. +} + func TestStateRmNotChildModule(t *testing.T) { state := states.BuildState(func(s *states.SyncState) { s.SetResourceInstanceCurrent( From 0536d8274a1701524a2bd587e6e4614265b0d120 Mon Sep 17 00:00:00 2001 From: Sarah French Date: Mon, 24 Nov 2025 12:58:03 +0000 Subject: [PATCH 09/18] test: Add command-level test for `state mv` showing integration with pluggable state storage code. --- internal/command/state_mv_test.go | 135 ++++++++++++++++++++++++++++++ 1 file changed, 135 insertions(+) diff --git a/internal/command/state_mv_test.go b/internal/command/state_mv_test.go index 9d6c2aa9034b..a3788faf79ac 100644 --- a/internal/command/state_mv_test.go +++ b/internal/command/state_mv_test.go @@ -4,6 +4,8 @@ package command import ( + "bytes" + "encoding/json" "fmt" "os" "path/filepath" @@ -14,7 +16,9 @@ import ( "github.com/hashicorp/cli" "github.com/hashicorp/terraform/internal/addrs" + "github.com/hashicorp/terraform/internal/providers" "github.com/hashicorp/terraform/internal/states" + "github.com/hashicorp/terraform/internal/states/statefile" ) func TestStateMv(t *testing.T) { @@ -153,6 +157,137 @@ func TestStateMv(t *testing.T) { } +func TestStateMv_stateStore(t *testing.T) { + // Create a temporary working directory + td := t.TempDir() + testCopyDir(t, testFixturePath("state-list-state-store"), td) + t.Chdir(td) + + // Get bytes describing a state containing resources + state := states.BuildState(func(s *states.SyncState) { + s.SetResourceInstanceCurrent( + addrs.Resource{ + Mode: addrs.ManagedResourceMode, + Type: "test_instance", + Name: "foo", + }.Instance(addrs.NoKey).Absolute(addrs.RootModuleInstance), + &states.ResourceInstanceObjectSrc{ + AttrsJSON: []byte(`{"id":"foo","foo":"value","bar":"value"}`), + Status: states.ObjectReady, + }, + addrs.AbsProviderConfig{ + Provider: addrs.NewDefaultProvider("test"), + Module: addrs.RootModule, + }, + ) + s.SetResourceInstanceCurrent( + addrs.Resource{ + Mode: addrs.ManagedResourceMode, + Type: "test_instance", + Name: "baz", + }.Instance(addrs.NoKey).Absolute(addrs.RootModuleInstance), + &states.ResourceInstanceObjectSrc{ + AttrsJSON: []byte(`{"id":"baz","foo":"value","bar":"value"}`), + Status: states.ObjectReady, + }, + addrs.AbsProviderConfig{ + Provider: addrs.NewDefaultProvider("test"), + Module: addrs.RootModule, + }, + ) + }) + var stateBuf bytes.Buffer + if err := statefile.Write(statefile.New(state, "", 1), &stateBuf); err != nil { + t.Fatalf("error during test setup: %s", err) + } + + // Create a mock that contains a persisted "default" state that uses the bytes from above. + mockProvider := mockPluggableStateStorageProvider(t) + mockProvider.MockStates = map[string]interface{}{ + "default": stateBuf.Bytes(), + } + mockProviderAddress := addrs.NewDefaultProvider("test") + providerSource, close := newMockProviderSource(t, map[string][]string{ + "hashicorp/test": {"1.0.0"}, + }) + defer close() + + // Make the mock assert that the resource has been moved when the new state is persisted + oldAddr := "test_instance.foo" + newAddr := "test_instance.bar" + mockProvider.WriteStateBytesFn = func(req providers.WriteStateBytesRequest) providers.WriteStateBytesResponse { + r := bytes.NewReader(req.Bytes) + file, err := statefile.Read(r) + if err != nil { + t.Fatal(err) + } + + root := file.State.Modules[""] + if _, ok := root.Resources[oldAddr]; ok { + t.Fatalf("expected the new state to have moved the %s resource to the new addr %s, but the old addr is still present", + newAddr, + oldAddr, + ) + } + resource, ok := root.Resources[newAddr] + if !ok { + t.Fatalf("expected the moved resource to be at addr %s, but it isn't present", newAddr) + } + + // Check that the moved resource has the same state. + var key addrs.InstanceKey + type attrsJson struct { + Id string `json:"id"` + Foo string `json:"foo"` + Bar string `json:"bar"` + } + var data attrsJson + attrs := resource.Instances[key].Current.AttrsJSON + err = json.Unmarshal(attrs, &data) + if err != nil { + t.Fatal(err) + } + expectedData := attrsJson{ + Id: "foo", + Foo: "value", + Bar: "value", + } + if diff := cmp.Diff(expectedData, data); diff != "" { + t.Fatalf("the state of the moved resource doesn't match the original state:\nDiff = %s", diff) + } + + return providers.WriteStateBytesResponse{} + } + + ui := new(cli.MockUi) + view, _ := testView(t) + c := &StateMvCommand{ + StateMeta{ + Meta: Meta{ + AllowExperimentalFeatures: true, + testingOverrides: &testingOverrides{ + Providers: map[addrs.Provider]providers.Factory{ + mockProviderAddress: providers.FactoryFixed(mockProvider), + }, + }, + ProviderSource: providerSource, + Ui: ui, + View: view, + }, + }, + } + + args := []string{ + oldAddr, + newAddr, + } + if code := c.Run(args); code != 0 { + t.Fatalf("return code: %d\n\n%s", code, ui.ErrorWriter.String()) + } + + // See the mock definition above for logic that asserts what the new state will look like after moving the resource. +} + func TestStateMv_backupAndBackupOutOptionsWithNonLocalBackend(t *testing.T) { state := states.BuildState(func(s *states.SyncState) { s.SetResourceInstanceCurrent( From 4804cc3c63b56988a7e539fe26584fca88f48cfc Mon Sep 17 00:00:00 2001 From: Sarah French Date: Mon, 24 Nov 2025 13:44:53 +0000 Subject: [PATCH 10/18] test: Add command-level test for `state push` showing integration with pluggable state storage code. --- internal/command/state_push_test.go | 52 +++++++++++++++++++ .../.terraform.lock.hcl | 6 +++ .../.terraform/terraform.tfstate | 19 +++++++ .../state-push-state-store-good/main.tf | 13 +++++ .../replace.tfstate | 23 ++++++++ 5 files changed, 113 insertions(+) create mode 100644 internal/command/testdata/state-push-state-store-good/.terraform.lock.hcl create mode 100644 internal/command/testdata/state-push-state-store-good/.terraform/terraform.tfstate create mode 100644 internal/command/testdata/state-push-state-store-good/main.tf create mode 100644 internal/command/testdata/state-push-state-store-good/replace.tfstate diff --git a/internal/command/state_push_test.go b/internal/command/state_push_test.go index fb4475c7a9ec..9356e9342ca3 100644 --- a/internal/command/state_push_test.go +++ b/internal/command/state_push_test.go @@ -9,9 +9,12 @@ import ( "testing" "github.com/hashicorp/cli" + "github.com/hashicorp/terraform/internal/addrs" "github.com/hashicorp/terraform/internal/backend" "github.com/hashicorp/terraform/internal/backend/remote-state/inmem" + "github.com/hashicorp/terraform/internal/providers" "github.com/hashicorp/terraform/internal/states" + "github.com/hashicorp/terraform/internal/states/statefile" ) func TestStatePush_empty(t *testing.T) { @@ -44,6 +47,55 @@ func TestStatePush_empty(t *testing.T) { } } +func TestStatePush_stateStore(t *testing.T) { + // Create a temporary working directory that is empty + td := t.TempDir() + testCopyDir(t, testFixturePath("state-push-state-store-good"), td) + t.Chdir(td) + + expected := testStateRead(t, "replace.tfstate") + + // Create a mock that doesn't have any internal states. + mockProvider := mockPluggableStateStorageProvider(t) + mockProviderAddress := addrs.NewDefaultProvider("test") + providerSource, close := newMockProviderSource(t, map[string][]string{ + "hashicorp/test": {"1.0.0"}, + }) + defer close() + + ui := new(cli.MockUi) + view, _ := testView(t) + c := &StatePushCommand{ + Meta: Meta{ + AllowExperimentalFeatures: true, + testingOverrides: &testingOverrides{ + Providers: map[addrs.Provider]providers.Factory{ + mockProviderAddress: providers.FactoryFixed(mockProvider), + }, + }, + ProviderSource: providerSource, + Ui: ui, + View: view, + }, + } + + args := []string{"replace.tfstate"} + if code := c.Run(args); code != 0 { + t.Fatalf("bad: %d\n\n%s", code, ui.ErrorWriter.String()) + } + + // Access the pushed state from the mock's internal store + r := bytes.NewReader(mockProvider.MockStates["default"].([]byte)) + actual, err := statefile.Read(r) + if err != nil { + t.Fatal(err) + } + + if !actual.State.Equal(expected) { + t.Fatalf("bad: %#v", actual) + } +} + func TestStatePush_lockedState(t *testing.T) { // Create a temporary working directory that is empty td := t.TempDir() diff --git a/internal/command/testdata/state-push-state-store-good/.terraform.lock.hcl b/internal/command/testdata/state-push-state-store-good/.terraform.lock.hcl new file mode 100644 index 000000000000..e5c03757a7fa --- /dev/null +++ b/internal/command/testdata/state-push-state-store-good/.terraform.lock.hcl @@ -0,0 +1,6 @@ +# This file is maintained automatically by "terraform init". +# Manual edits may be lost in future updates. + +provider "registry.terraform.io/hashicorp/test" { + version = "1.2.3" +} diff --git a/internal/command/testdata/state-push-state-store-good/.terraform/terraform.tfstate b/internal/command/testdata/state-push-state-store-good/.terraform/terraform.tfstate new file mode 100644 index 000000000000..4f96aa73ee7d --- /dev/null +++ b/internal/command/testdata/state-push-state-store-good/.terraform/terraform.tfstate @@ -0,0 +1,19 @@ +{ + "version": 3, + "serial": 0, + "lineage": "666f9301-7e65-4b19-ae23-71184bb19b03", + "state_store": { + "type": "test_store", + "config": { + "value": "foobar" + }, + "provider": { + "version": "1.2.3", + "source": "registry.terraform.io/hashicorp/test", + "config": { + "region": null + } + }, + "hash": 4158988729 + } +} \ No newline at end of file diff --git a/internal/command/testdata/state-push-state-store-good/main.tf b/internal/command/testdata/state-push-state-store-good/main.tf new file mode 100644 index 000000000000..34b58fdc0e2e --- /dev/null +++ b/internal/command/testdata/state-push-state-store-good/main.tf @@ -0,0 +1,13 @@ +terraform { + required_providers { + test = { + source = "registry.terraform.io/hashicorp/test" + } + } + + state_store "test_store" { + provider "test" {} + + value = "foobar" + } +} diff --git a/internal/command/testdata/state-push-state-store-good/replace.tfstate b/internal/command/testdata/state-push-state-store-good/replace.tfstate new file mode 100644 index 000000000000..9921bc076254 --- /dev/null +++ b/internal/command/testdata/state-push-state-store-good/replace.tfstate @@ -0,0 +1,23 @@ +{ + "version": 4, + "serial": 0, + "lineage": "hello", + "outputs": {}, + "resources": [ + { + "mode": "managed", + "type": "null_resource", + "name": "b", + "provider": "provider.null", + "instances": [ + { + "schema_version": 0, + "attributes": { + "id": "9051675049789185374", + "triggers": null + } + } + ] + } + ] +} From 55bfebc91c152258d3772d11cbff709328ad3684 Mon Sep 17 00:00:00 2001 From: Sarah French Date: Mon, 24 Nov 2025 15:50:33 +0000 Subject: [PATCH 11/18] test: Add command-level test for `state replace-provider` showing integration with pluggable state storage code. --- .../command/state_replace_provider_test.go | 100 ++++++++++++++++++ 1 file changed, 100 insertions(+) diff --git a/internal/command/state_replace_provider_test.go b/internal/command/state_replace_provider_test.go index 06508fd9a435..6abd706240e5 100644 --- a/internal/command/state_replace_provider_test.go +++ b/internal/command/state_replace_provider_test.go @@ -12,7 +12,9 @@ import ( "github.com/hashicorp/cli" "github.com/hashicorp/terraform/internal/addrs" + "github.com/hashicorp/terraform/internal/providers" "github.com/hashicorp/terraform/internal/states" + "github.com/hashicorp/terraform/internal/states/statefile" ) func TestStateReplaceProvider(t *testing.T) { @@ -285,6 +287,104 @@ func TestStateReplaceProvider(t *testing.T) { }) } +func TestStateReplaceProvider_stateStore(t *testing.T) { + // Create a temporary working directory + td := t.TempDir() + testCopyDir(t, testFixturePath("state-list-state-store"), td) + t.Chdir(td) + + // Get bytes describing a state containing resources + state := states.BuildState(func(s *states.SyncState) { + s.SetResourceInstanceCurrent( + addrs.Resource{ + Mode: addrs.ManagedResourceMode, + Type: "test_instance", + Name: "foo", + }.Instance(addrs.NoKey).Absolute(addrs.RootModuleInstance), + &states.ResourceInstanceObjectSrc{ + AttrsJSON: []byte(`{"id":"foo","foo":"value","bar":"value"}`), + Status: states.ObjectReady, + }, + addrs.AbsProviderConfig{ + Provider: addrs.NewDefaultProvider("test"), + Module: addrs.RootModule, + }, + ) + s.SetResourceInstanceCurrent( + addrs.Resource{ + Mode: addrs.ManagedResourceMode, + Type: "test_instance", + Name: "baz", + }.Instance(addrs.NoKey).Absolute(addrs.RootModuleInstance), + &states.ResourceInstanceObjectSrc{ + AttrsJSON: []byte(`{"id":"baz","foo":"value","bar":"value"}`), + Status: states.ObjectReady, + }, + addrs.AbsProviderConfig{ + Provider: addrs.NewDefaultProvider("test"), + Module: addrs.RootModule, + }, + ) + }) + var stateBuf bytes.Buffer + if err := statefile.Write(statefile.New(state, "", 1), &stateBuf); err != nil { + t.Fatalf("error during test setup: %s", err) + } + + // Create a mock that contains a persisted "default" state that uses the bytes from above. + mockProvider := mockPluggableStateStorageProvider(t) + mockProvider.MockStates = map[string]interface{}{ + "default": stateBuf.Bytes(), + } + mockProviderAddress := addrs.NewDefaultProvider("test") + providerSource, close := newMockProviderSource(t, map[string][]string{ + "hashicorp/test": {"1.0.0"}, + }) + defer close() + + ui := new(cli.MockUi) + view, _ := testView(t) + c := &StateReplaceProviderCommand{ + StateMeta{ + Meta: Meta{ + AllowExperimentalFeatures: true, + testingOverrides: &testingOverrides{ + Providers: map[addrs.Provider]providers.Factory{ + mockProviderAddress: providers.FactoryFixed(mockProvider), + }, + }, + ProviderSource: providerSource, + Ui: ui, + View: view, + }, + }, + } + + inputBuf := &bytes.Buffer{} + ui.InputReader = inputBuf + inputBuf.WriteString("yes\n") + + args := []string{ + "hashicorp/test", + "testing-corp/test", + } + if code := c.Run(args); code != 0 { + t.Fatalf("return code: %d\n\n%s", code, ui.ErrorWriter.String()) + } + + // For the two resources in the mocked state, we expect them both to be changed. + expectedOutputMsgs := []string{ + "- registry.terraform.io/hashicorp/test\n + registry.terraform.io/testing-corp/test\n", + "Successfully replaced provider for 2 resources.", + } + for _, msg := range expectedOutputMsgs { + if !strings.Contains(ui.OutputWriter.String(), msg) { + t.Fatalf("expected command output to include %q but it's not present in the output:\nOutput = %s", + msg, ui.OutputWriter.String()) + } + } +} + func TestStateReplaceProvider_docs(t *testing.T) { c := &StateReplaceProviderCommand{} From 723c90d4179450b9d3c0691200641517836e931b Mon Sep 17 00:00:00 2001 From: Sarah French Date: Mon, 24 Nov 2025 16:48:22 +0000 Subject: [PATCH 12/18] test: Change shared test fixture to not be named after a specific command under test. This test fixure is reused across tests that need the config to define a state store but otherwise rely on the mock provider to set up the test scenario. --- internal/command/state_list_test.go | 2 +- internal/command/state_mv_test.go | 2 +- internal/command/state_pull_test.go | 2 +- internal/command/state_replace_provider_test.go | 2 +- internal/command/state_rm_test.go | 2 +- internal/command/state_show_test.go | 2 +- .../.terraform.lock.hcl | 0 .../.terraform/terraform.tfstate | 0 .../main.tf | 0 .../terraform.tfstate | 0 .../registry.terraform.io/hashicorp/simple6/0.0.1/.gitkeep | 0 11 files changed, 6 insertions(+), 6 deletions(-) rename internal/command/testdata/{state-list-state-store => state-commands-state-store}/.terraform.lock.hcl (100%) rename internal/command/testdata/{state-list-state-store => state-commands-state-store}/.terraform/terraform.tfstate (100%) rename internal/command/testdata/{state-list-state-store => state-commands-state-store}/main.tf (100%) rename internal/command/testdata/{state-list-state-store => state-commands-state-store}/terraform.tfstate (100%) delete mode 100644 internal/command/testdata/state-list-state-store/.terraform/providers/registry.terraform.io/hashicorp/simple6/0.0.1/.gitkeep diff --git a/internal/command/state_list_test.go b/internal/command/state_list_test.go index 494e21dbf978..7b6046f8dd0d 100644 --- a/internal/command/state_list_test.go +++ b/internal/command/state_list_test.go @@ -165,7 +165,7 @@ func TestStateList_backendCustomState(t *testing.T) { func TestStateList_stateStore(t *testing.T) { // Create a temporary working directory that is empty td := t.TempDir() - testCopyDir(t, testFixturePath("state-list-state-store"), td) + testCopyDir(t, testFixturePath("state-commands-state-store"), td) t.Chdir(td) // Get bytes describing a state containing a resource diff --git a/internal/command/state_mv_test.go b/internal/command/state_mv_test.go index a3788faf79ac..82157f97df21 100644 --- a/internal/command/state_mv_test.go +++ b/internal/command/state_mv_test.go @@ -160,7 +160,7 @@ func TestStateMv(t *testing.T) { func TestStateMv_stateStore(t *testing.T) { // Create a temporary working directory td := t.TempDir() - testCopyDir(t, testFixturePath("state-list-state-store"), td) + testCopyDir(t, testFixturePath("state-commands-state-store"), td) t.Chdir(td) // Get bytes describing a state containing resources diff --git a/internal/command/state_pull_test.go b/internal/command/state_pull_test.go index 55d2983ea68a..a46aed77aa4d 100644 --- a/internal/command/state_pull_test.go +++ b/internal/command/state_pull_test.go @@ -55,7 +55,7 @@ func TestStatePull(t *testing.T) { func TestStatePull_stateStore(t *testing.T) { // Create a temporary working directory that is empty td := t.TempDir() - testCopyDir(t, testFixturePath("state-list-state-store"), td) + testCopyDir(t, testFixturePath("state-commands-state-store"), td) t.Chdir(td) // Get bytes describing a state containing a resource diff --git a/internal/command/state_replace_provider_test.go b/internal/command/state_replace_provider_test.go index 6abd706240e5..e040eb1f67ae 100644 --- a/internal/command/state_replace_provider_test.go +++ b/internal/command/state_replace_provider_test.go @@ -290,7 +290,7 @@ func TestStateReplaceProvider(t *testing.T) { func TestStateReplaceProvider_stateStore(t *testing.T) { // Create a temporary working directory td := t.TempDir() - testCopyDir(t, testFixturePath("state-list-state-store"), td) + testCopyDir(t, testFixturePath("state-commands-state-store"), td) t.Chdir(td) // Get bytes describing a state containing resources diff --git a/internal/command/state_rm_test.go b/internal/command/state_rm_test.go index c940168ee0c9..dcf69ba268ab 100644 --- a/internal/command/state_rm_test.go +++ b/internal/command/state_rm_test.go @@ -88,7 +88,7 @@ func TestStateRm(t *testing.T) { func TestStateRm_stateStore(t *testing.T) { // Create a temporary working directory td := t.TempDir() - testCopyDir(t, testFixturePath("state-list-state-store"), td) + testCopyDir(t, testFixturePath("state-commands-state-store"), td) t.Chdir(td) // Get bytes describing a state containing resources diff --git a/internal/command/state_show_test.go b/internal/command/state_show_test.go index c7b47d0da88f..3a359efcec3e 100644 --- a/internal/command/state_show_test.go +++ b/internal/command/state_show_test.go @@ -342,7 +342,7 @@ func TestStateShow_configured_provider(t *testing.T) { func TestStateShow_stateStore(t *testing.T) { // Create a temporary working directory that is empty td := t.TempDir() - testCopyDir(t, testFixturePath("state-list-state-store"), td) + testCopyDir(t, testFixturePath("state-commands-state-store"), td) t.Chdir(td) // Get bytes describing a state containing a resource diff --git a/internal/command/testdata/state-list-state-store/.terraform.lock.hcl b/internal/command/testdata/state-commands-state-store/.terraform.lock.hcl similarity index 100% rename from internal/command/testdata/state-list-state-store/.terraform.lock.hcl rename to internal/command/testdata/state-commands-state-store/.terraform.lock.hcl diff --git a/internal/command/testdata/state-list-state-store/.terraform/terraform.tfstate b/internal/command/testdata/state-commands-state-store/.terraform/terraform.tfstate similarity index 100% rename from internal/command/testdata/state-list-state-store/.terraform/terraform.tfstate rename to internal/command/testdata/state-commands-state-store/.terraform/terraform.tfstate diff --git a/internal/command/testdata/state-list-state-store/main.tf b/internal/command/testdata/state-commands-state-store/main.tf similarity index 100% rename from internal/command/testdata/state-list-state-store/main.tf rename to internal/command/testdata/state-commands-state-store/main.tf diff --git a/internal/command/testdata/state-list-state-store/terraform.tfstate b/internal/command/testdata/state-commands-state-store/terraform.tfstate similarity index 100% rename from internal/command/testdata/state-list-state-store/terraform.tfstate rename to internal/command/testdata/state-commands-state-store/terraform.tfstate diff --git a/internal/command/testdata/state-list-state-store/.terraform/providers/registry.terraform.io/hashicorp/simple6/0.0.1/.gitkeep b/internal/command/testdata/state-list-state-store/.terraform/providers/registry.terraform.io/hashicorp/simple6/0.0.1/.gitkeep deleted file mode 100644 index e69de29bb2d1..000000000000 From c3b7923a30c043daf322bdbf8c56fa76d2657ce6 Mon Sep 17 00:00:00 2001 From: Sarah French Date: Mon, 24 Nov 2025 16:50:54 +0000 Subject: [PATCH 13/18] test: Update test to use shared test fixture --- internal/command/state_identities_test.go | 2 +- .../.terraform.lock.hcl | 6 ------ .../.terraform/terraform.tfstate | 19 ------------------- .../state-identities-state-store/main.tf | 13 ------------- 4 files changed, 1 insertion(+), 39 deletions(-) delete mode 100644 internal/command/testdata/state-identities-state-store/.terraform.lock.hcl delete mode 100644 internal/command/testdata/state-identities-state-store/.terraform/terraform.tfstate delete mode 100644 internal/command/testdata/state-identities-state-store/main.tf diff --git a/internal/command/state_identities_test.go b/internal/command/state_identities_test.go index b6d7c59c6528..0073fb3ccb68 100644 --- a/internal/command/state_identities_test.go +++ b/internal/command/state_identities_test.go @@ -431,7 +431,7 @@ func TestStateIdentities_modules(t *testing.T) { func TestStateIdentities_stateStore(t *testing.T) { // We need configuration present to force pluggable state storage to be used td := t.TempDir() - testCopyDir(t, testFixturePath("state-identities-state-store"), td) + testCopyDir(t, testFixturePath("state-commands-state-store"), td) t.Chdir(td) // Get a state file, that contains identity information,as bytes diff --git a/internal/command/testdata/state-identities-state-store/.terraform.lock.hcl b/internal/command/testdata/state-identities-state-store/.terraform.lock.hcl deleted file mode 100644 index e5c03757a7fa..000000000000 --- a/internal/command/testdata/state-identities-state-store/.terraform.lock.hcl +++ /dev/null @@ -1,6 +0,0 @@ -# This file is maintained automatically by "terraform init". -# Manual edits may be lost in future updates. - -provider "registry.terraform.io/hashicorp/test" { - version = "1.2.3" -} diff --git a/internal/command/testdata/state-identities-state-store/.terraform/terraform.tfstate b/internal/command/testdata/state-identities-state-store/.terraform/terraform.tfstate deleted file mode 100644 index 4f96aa73ee7d..000000000000 --- a/internal/command/testdata/state-identities-state-store/.terraform/terraform.tfstate +++ /dev/null @@ -1,19 +0,0 @@ -{ - "version": 3, - "serial": 0, - "lineage": "666f9301-7e65-4b19-ae23-71184bb19b03", - "state_store": { - "type": "test_store", - "config": { - "value": "foobar" - }, - "provider": { - "version": "1.2.3", - "source": "registry.terraform.io/hashicorp/test", - "config": { - "region": null - } - }, - "hash": 4158988729 - } -} \ No newline at end of file diff --git a/internal/command/testdata/state-identities-state-store/main.tf b/internal/command/testdata/state-identities-state-store/main.tf deleted file mode 100644 index 34b58fdc0e2e..000000000000 --- a/internal/command/testdata/state-identities-state-store/main.tf +++ /dev/null @@ -1,13 +0,0 @@ -terraform { - required_providers { - test = { - source = "registry.terraform.io/hashicorp/test" - } - } - - state_store "test_store" { - provider "test" {} - - value = "foobar" - } -} From c392a6f81f3a567b9b8a2eccccb708905ad62eb2 Mon Sep 17 00:00:00 2001 From: Sarah French Date: Mon, 24 Nov 2025 17:01:11 +0000 Subject: [PATCH 14/18] test: Remove redundant test fixture The internal/command/testdata/state-commands-state-store and internal/command/testdata/state-store-unchanged test fixtures are the same. --- internal/command/state_identities_test.go | 2 +- internal/command/state_list_test.go | 2 +- internal/command/state_mv_test.go | 2 +- internal/command/state_pull_test.go | 2 +- .../command/state_replace_provider_test.go | 2 +- internal/command/state_rm_test.go | 2 +- internal/command/state_show_test.go | 2 +- .../.terraform.lock.hcl | 6 --- .../.terraform/terraform.tfstate | 19 --------- .../state-commands-state-store/main.tf | 25 ------------ .../terraform.tfstate | 40 ------------------- 11 files changed, 7 insertions(+), 97 deletions(-) delete mode 100644 internal/command/testdata/state-commands-state-store/.terraform.lock.hcl delete mode 100644 internal/command/testdata/state-commands-state-store/.terraform/terraform.tfstate delete mode 100644 internal/command/testdata/state-commands-state-store/main.tf delete mode 100644 internal/command/testdata/state-commands-state-store/terraform.tfstate diff --git a/internal/command/state_identities_test.go b/internal/command/state_identities_test.go index 0073fb3ccb68..6a9aae85877b 100644 --- a/internal/command/state_identities_test.go +++ b/internal/command/state_identities_test.go @@ -431,7 +431,7 @@ func TestStateIdentities_modules(t *testing.T) { func TestStateIdentities_stateStore(t *testing.T) { // We need configuration present to force pluggable state storage to be used td := t.TempDir() - testCopyDir(t, testFixturePath("state-commands-state-store"), td) + testCopyDir(t, testFixturePath("state-store-unchanged"), td) t.Chdir(td) // Get a state file, that contains identity information,as bytes diff --git a/internal/command/state_list_test.go b/internal/command/state_list_test.go index 7b6046f8dd0d..ce065f0a96e2 100644 --- a/internal/command/state_list_test.go +++ b/internal/command/state_list_test.go @@ -165,7 +165,7 @@ func TestStateList_backendCustomState(t *testing.T) { func TestStateList_stateStore(t *testing.T) { // Create a temporary working directory that is empty td := t.TempDir() - testCopyDir(t, testFixturePath("state-commands-state-store"), td) + testCopyDir(t, testFixturePath("state-store-unchanged"), td) t.Chdir(td) // Get bytes describing a state containing a resource diff --git a/internal/command/state_mv_test.go b/internal/command/state_mv_test.go index 82157f97df21..07f4cbc0dd51 100644 --- a/internal/command/state_mv_test.go +++ b/internal/command/state_mv_test.go @@ -160,7 +160,7 @@ func TestStateMv(t *testing.T) { func TestStateMv_stateStore(t *testing.T) { // Create a temporary working directory td := t.TempDir() - testCopyDir(t, testFixturePath("state-commands-state-store"), td) + testCopyDir(t, testFixturePath("state-store-unchanged"), td) t.Chdir(td) // Get bytes describing a state containing resources diff --git a/internal/command/state_pull_test.go b/internal/command/state_pull_test.go index a46aed77aa4d..e0e2c06548b8 100644 --- a/internal/command/state_pull_test.go +++ b/internal/command/state_pull_test.go @@ -55,7 +55,7 @@ func TestStatePull(t *testing.T) { func TestStatePull_stateStore(t *testing.T) { // Create a temporary working directory that is empty td := t.TempDir() - testCopyDir(t, testFixturePath("state-commands-state-store"), td) + testCopyDir(t, testFixturePath("state-store-unchanged"), td) t.Chdir(td) // Get bytes describing a state containing a resource diff --git a/internal/command/state_replace_provider_test.go b/internal/command/state_replace_provider_test.go index e040eb1f67ae..65e900fe73c0 100644 --- a/internal/command/state_replace_provider_test.go +++ b/internal/command/state_replace_provider_test.go @@ -290,7 +290,7 @@ func TestStateReplaceProvider(t *testing.T) { func TestStateReplaceProvider_stateStore(t *testing.T) { // Create a temporary working directory td := t.TempDir() - testCopyDir(t, testFixturePath("state-commands-state-store"), td) + testCopyDir(t, testFixturePath("state-store-unchanged"), td) t.Chdir(td) // Get bytes describing a state containing resources diff --git a/internal/command/state_rm_test.go b/internal/command/state_rm_test.go index dcf69ba268ab..90ab21cb2a4e 100644 --- a/internal/command/state_rm_test.go +++ b/internal/command/state_rm_test.go @@ -88,7 +88,7 @@ func TestStateRm(t *testing.T) { func TestStateRm_stateStore(t *testing.T) { // Create a temporary working directory td := t.TempDir() - testCopyDir(t, testFixturePath("state-commands-state-store"), td) + testCopyDir(t, testFixturePath("state-store-unchanged"), td) t.Chdir(td) // Get bytes describing a state containing resources diff --git a/internal/command/state_show_test.go b/internal/command/state_show_test.go index 3a359efcec3e..a686deecbf42 100644 --- a/internal/command/state_show_test.go +++ b/internal/command/state_show_test.go @@ -342,7 +342,7 @@ func TestStateShow_configured_provider(t *testing.T) { func TestStateShow_stateStore(t *testing.T) { // Create a temporary working directory that is empty td := t.TempDir() - testCopyDir(t, testFixturePath("state-commands-state-store"), td) + testCopyDir(t, testFixturePath("state-store-unchanged"), td) t.Chdir(td) // Get bytes describing a state containing a resource diff --git a/internal/command/testdata/state-commands-state-store/.terraform.lock.hcl b/internal/command/testdata/state-commands-state-store/.terraform.lock.hcl deleted file mode 100644 index e5c03757a7fa..000000000000 --- a/internal/command/testdata/state-commands-state-store/.terraform.lock.hcl +++ /dev/null @@ -1,6 +0,0 @@ -# This file is maintained automatically by "terraform init". -# Manual edits may be lost in future updates. - -provider "registry.terraform.io/hashicorp/test" { - version = "1.2.3" -} diff --git a/internal/command/testdata/state-commands-state-store/.terraform/terraform.tfstate b/internal/command/testdata/state-commands-state-store/.terraform/terraform.tfstate deleted file mode 100644 index 4f96aa73ee7d..000000000000 --- a/internal/command/testdata/state-commands-state-store/.terraform/terraform.tfstate +++ /dev/null @@ -1,19 +0,0 @@ -{ - "version": 3, - "serial": 0, - "lineage": "666f9301-7e65-4b19-ae23-71184bb19b03", - "state_store": { - "type": "test_store", - "config": { - "value": "foobar" - }, - "provider": { - "version": "1.2.3", - "source": "registry.terraform.io/hashicorp/test", - "config": { - "region": null - } - }, - "hash": 4158988729 - } -} \ No newline at end of file diff --git a/internal/command/testdata/state-commands-state-store/main.tf b/internal/command/testdata/state-commands-state-store/main.tf deleted file mode 100644 index 84f54d6c1a4a..000000000000 --- a/internal/command/testdata/state-commands-state-store/main.tf +++ /dev/null @@ -1,25 +0,0 @@ -terraform { - required_providers { - test = { - source = "registry.terraform.io/hashicorp/test" - } - } - - state_store "test_store" { - provider "test" {} - - value = "foobar" - } -} - -variable "name" { - default = "world" -} - -resource "test_instance" "my-data" { - input = "hello ${var.name}" -} - -output "greeting" { - value = resource.terraform_data.my-data.output -} diff --git a/internal/command/testdata/state-commands-state-store/terraform.tfstate b/internal/command/testdata/state-commands-state-store/terraform.tfstate deleted file mode 100644 index 4feaaed87a08..000000000000 --- a/internal/command/testdata/state-commands-state-store/terraform.tfstate +++ /dev/null @@ -1,40 +0,0 @@ -{ - "version": 4, - "terraform_version": "1.15.0", - "serial": 1, - "lineage": "9e13d881-e480-7a63-d47a-b4f5224e6743", - "outputs": { - "greeting": { - "value": "hello world", - "type": "string" - } - }, - "resources": [ - { - "mode": "managed", - "type": "terraform_data", - "name": "my-data", - "provider": "provider[\"terraform.io/builtin/terraform\"]", - "instances": [ - { - "schema_version": 0, - "attributes": { - "id": "d71fb368-2ba1-fb4c-5bd9-6a2b7f05d60c", - "input": { - "value": "hello world", - "type": "string" - }, - "output": { - "value": "hello world", - "type": "string" - }, - "triggers_replace": null - }, - "sensitive_attributes": [], - "identity_schema_version": 0 - } - ] - } - ], - "check_results": null -} \ No newline at end of file From 101f432c69219501721738c2fa8b311eea809955 Mon Sep 17 00:00:00 2001 From: Sarah French Date: Wed, 26 Nov 2025 15:03:07 +0000 Subject: [PATCH 15/18] fix: Re-add logic for setting chunk size in the context of E2E tests using grpcwrap package This was removed, incorrectly, in https://github.com/hashicorp/terraform/pull/37899 --- internal/grpcwrap/provider6.go | 4 ++++ 1 file changed, 4 insertions(+) diff --git a/internal/grpcwrap/provider6.go b/internal/grpcwrap/provider6.go index 2f0d0f7395f3..35a6679860da 100644 --- a/internal/grpcwrap/provider6.go +++ b/internal/grpcwrap/provider6.go @@ -982,6 +982,10 @@ func (p *provider6) ConfigureStateStore(ctx context.Context, req *tfplugin6.Conf return resp, nil } + // If this isn't present, chunk size is 0 and downstream code + // acts like there is no state at all. + p.chunkSize = configureResp.Capabilities.ChunkSize + resp.Diagnostics = convert.AppendProtoDiag(resp.Diagnostics, configureResp.Diagnostics) resp.Capabilities = &tfplugin6.StateStoreServerCapabilities{ ChunkSize: configureResp.Capabilities.ChunkSize, From 1b0d141b1d6edf2533ad8d749de52855ada053df Mon Sep 17 00:00:00 2001 From: Sarah French Date: Thu, 4 Dec 2025 20:51:47 +0000 Subject: [PATCH 16/18] refactor: Let panic happen if there's incompatibility between mock returned from `mockPluggableStateStorageProvider` and the `MockStates` that's been set in the mock --- internal/command/autocomplete_test.go | 4 +- internal/command/init_test.go | 42 ++++++++----------- internal/command/output.go | 4 +- internal/command/state_identities_test.go | 2 +- internal/command/state_list_test.go | 2 +- internal/command/state_mv_test.go | 2 +- internal/command/state_pull_test.go | 2 +- internal/command/state_push_test.go | 2 +- .../command/state_replace_provider_test.go | 2 +- internal/command/state_rm_test.go | 2 +- internal/command/state_show_test.go | 2 +- 11 files changed, 29 insertions(+), 37 deletions(-) diff --git a/internal/command/autocomplete_test.go b/internal/command/autocomplete_test.go index 97d84a30c3d0..c13be9fbb06d 100644 --- a/internal/command/autocomplete_test.go +++ b/internal/command/autocomplete_test.go @@ -42,7 +42,7 @@ func TestMetaCompletePredictWorkspaceName(t *testing.T) { t.Chdir(td) // Set up pluggable state store provider mock - mockProvider := mockPluggableStateStorageProvider(t) + mockProvider := mockPluggableStateStorageProvider() // Mock the existence of workspaces mockProvider.MockStates = map[string]interface{}{ "default": true, @@ -89,7 +89,7 @@ func TestMetaCompletePredictWorkspaceName(t *testing.T) { t.Chdir(td) // Set up pluggable state store provider mock - mockProvider := mockPluggableStateStorageProvider(t) + mockProvider := mockPluggableStateStorageProvider() // No workspaces exist in the mock mockProvider.MockStates = map[string]interface{}{} mockProviderAddress := addrs.NewDefaultProvider("test") diff --git a/internal/command/init_test.go b/internal/command/init_test.go index 99a035bcfa99..ed2c60e85968 100644 --- a/internal/command/init_test.go +++ b/internal/command/init_test.go @@ -3238,7 +3238,7 @@ func TestInit_stateStore_newWorkingDir(t *testing.T) { testCopyDir(t, testFixturePath("init-with-state-store"), td) t.Chdir(td) - mockProvider := mockPluggableStateStorageProvider(t) + mockProvider := mockPluggableStateStorageProvider() mockProviderAddress := addrs.NewDefaultProvider("test") providerSource, close := newMockProviderSource(t, map[string][]string{ // The test fixture config has no version constraints, so the latest version will @@ -3325,7 +3325,7 @@ func TestInit_stateStore_newWorkingDir(t *testing.T) { testCopyDir(t, testFixturePath("init-with-state-store"), td) t.Chdir(td) - mockProvider := mockPluggableStateStorageProvider(t) + mockProvider := mockPluggableStateStorageProvider() mockProviderAddress := addrs.NewDefaultProvider("test") providerSource, close := newMockProviderSource(t, map[string][]string{ "hashicorp/test": {"1.0.0"}, @@ -3374,7 +3374,7 @@ func TestInit_stateStore_newWorkingDir(t *testing.T) { testCopyDir(t, testFixturePath("init-with-state-store"), td) t.Chdir(td) - mockProvider := mockPluggableStateStorageProvider(t) + mockProvider := mockPluggableStateStorageProvider() mockProviderAddress := addrs.NewDefaultProvider("test") providerSource, close := newMockProviderSource(t, map[string][]string{ "hashicorp/test": {"1.0.0"}, @@ -3429,7 +3429,7 @@ func TestInit_stateStore_newWorkingDir(t *testing.T) { customWorkspace := "my-custom-workspace" t.Setenv(WorkspaceNameEnvVar, customWorkspace) - mockProvider := mockPluggableStateStorageProvider(t) + mockProvider := mockPluggableStateStorageProvider() mockProviderAddress := addrs.NewDefaultProvider("test") providerSource, close := newMockProviderSource(t, map[string][]string{ "hashicorp/test": {"1.0.0"}, @@ -3492,7 +3492,7 @@ func TestInit_stateStore_newWorkingDir(t *testing.T) { testCopyDir(t, testFixturePath("init-with-state-store"), td) t.Chdir(td) - mockProvider := mockPluggableStateStorageProvider(t) + mockProvider := mockPluggableStateStorageProvider() mockProvider.GetStatesResponse = &providers.GetStatesResponse{ States: []string{ "foobar1", @@ -3583,7 +3583,7 @@ func TestInit_stateStore_configUnchanged(t *testing.T) { testCopyDir(t, testFixturePath("state-store-unchanged"), td) t.Chdir(td) - mockProvider := mockPluggableStateStorageProvider(t) + mockProvider := mockPluggableStateStorageProvider() // If the working directory was previously initialized successfully then at least // one workspace is guaranteed to exist when a user is re-running init with no config // changes since last init. So this test says `default` exists. @@ -3670,7 +3670,7 @@ func TestInit_stateStore_configChanges(t *testing.T) { testCopyDir(t, testFixturePath("state-store-changed/store-config"), td) t.Chdir(td) - mockProvider := mockPluggableStateStorageProvider(t) + mockProvider := mockPluggableStateStorageProvider() // The previous init implied by this test scenario would have created this. mockProvider.GetStatesResponse = &providers.GetStatesResponse{States: []string{"default"}} @@ -3758,7 +3758,7 @@ func TestInit_stateStore_configChanges(t *testing.T) { testCopyDir(t, testFixturePath("state-store-changed/store-config"), td) t.Chdir(td) - mockProvider := mockPluggableStateStorageProvider(t) + mockProvider := mockPluggableStateStorageProvider() mockProvider.GetStatesResponse = &providers.GetStatesResponse{States: []string{"default"}} // The previous init implied by this test scenario would have created the default workspace. mockProviderAddress := addrs.NewDefaultProvider("test") providerSource, close := newMockProviderSource(t, map[string][]string{ @@ -3808,7 +3808,7 @@ func TestInit_stateStore_configChanges(t *testing.T) { testCopyDir(t, testFixturePath("state-store-changed/provider-config"), td) t.Chdir(td) - mockProvider := mockPluggableStateStorageProvider(t) + mockProvider := mockPluggableStateStorageProvider() mockProvider.GetStatesResponse = &providers.GetStatesResponse{States: []string{"default"}} // The previous init implied by this test scenario would have created the default workspace. mockProviderAddress := addrs.NewDefaultProvider("test") providerSource, close := newMockProviderSource(t, map[string][]string{ @@ -3857,7 +3857,7 @@ func TestInit_stateStore_configChanges(t *testing.T) { testCopyDir(t, testFixturePath("state-store-changed/state-store-type"), td) t.Chdir(td) - mockProvider := mockPluggableStateStorageProvider(t) + mockProvider := mockPluggableStateStorageProvider() storeName := "test_store" otherStoreName := "test_otherstore" // Make the provider report that it contains a 2nd storage implementation with the above name @@ -3909,11 +3909,11 @@ func TestInit_stateStore_configChanges(t *testing.T) { testCopyDir(t, testFixturePath("state-store-changed/provider-used"), td) t.Chdir(td) - mockProvider := mockPluggableStateStorageProvider(t) + mockProvider := mockPluggableStateStorageProvider() mockProvider.GetStatesResponse = &providers.GetStatesResponse{States: []string{"default"}} // The previous init implied by this test scenario would have created the default workspace. // Make a mock that implies its name is test2 based on returned schemas - mockProvider2 := mockPluggableStateStorageProvider(t) + mockProvider2 := mockPluggableStateStorageProvider() mockProvider2.GetProviderSchemaResponse.StateStores["test2_store"] = mockProvider.GetProviderSchemaResponse.StateStores["test_store"] delete(mockProvider2.GetProviderSchemaResponse.StateStores, "test_store") @@ -3974,7 +3974,7 @@ func TestInit_stateStore_providerUpgrade(t *testing.T) { testCopyDir(t, testFixturePath("state-store-changed/provider-upgraded"), td) t.Chdir(td) - mockProvider := mockPluggableStateStorageProvider(t) + mockProvider := mockPluggableStateStorageProvider() mockProviderAddress := addrs.NewDefaultProvider("test") providerSource, close := newMockProviderSource(t, map[string][]string{ "hashicorp/test": {"1.2.3", "9.9.9"}, // 1.2.3 is the version used in the backend state file, 9.9.9 is the version being upgraded to @@ -4023,7 +4023,7 @@ func TestInit_stateStore_unset(t *testing.T) { testCopyDir(t, testFixturePath("init-state-store"), td) t.Chdir(td) - mockProvider := mockPluggableStateStorageProvider(t) + mockProvider := mockPluggableStateStorageProvider() storeName := "test_store" otherStoreName := "test_otherstore" // Make the provider report that it contains a 2nd storage implementation with the above name @@ -4121,7 +4121,7 @@ func TestInit_stateStore_unset_withoutProviderRequirements(t *testing.T) { testCopyDir(t, testFixturePath("init-state-store"), td) t.Chdir(td) - mockProvider := mockPluggableStateStorageProvider(t) + mockProvider := mockPluggableStateStorageProvider() storeName := "test_store" otherStoreName := "test_otherstore" // Make the provider report that it contains a 2nd storage implementation with the above name @@ -4355,7 +4355,7 @@ func expectedPackageInstallPath(name, version string, exe bool) string { )) } -func mockPluggableStateStorageProvider(t *testing.T) *testing_provider.MockProvider { +func mockPluggableStateStorageProvider() *testing_provider.MockProvider { // Create a mock provider to use for PSS // Get mock provider factory to be used during init // @@ -4421,15 +4421,7 @@ func mockPluggableStateStorageProvider(t *testing.T) *testing_provider.MockProvi mock.ReadStateBytesFn = func(req providers.ReadStateBytesRequest) providers.ReadStateBytesResponse { state := []byte{} if v, exist := mock.MockStates[req.StateId]; exist { - s, ok := v.([]byte) - if ok { - state = s - } else { - // Test setup is incorrect if this happens - t.Logf("Warning: The mock provider is set up to return state bytes from the MockStates map, but the mock encountered a value that wasn't a slice of bytes: %#v", - mock.MockStates, - ) - } + state = v.([]byte) // If this panics, the mock has been set up with a bad MockStates value } return providers.ReadStateBytesResponse{ Bytes: state, diff --git a/internal/command/output.go b/internal/command/output.go index 1fb1d6ebcc51..df5a20c304c9 100644 --- a/internal/command/output.go +++ b/internal/command/output.go @@ -83,13 +83,13 @@ func (c *OutputCommand) Outputs(statePath string, view arguments.ViewType) (map[ } // Get the state - stateStore, sDiags := b.StateMgr(env) + sMgr, sDiags := b.StateMgr(env) if sDiags.HasErrors() { diags = diags.Append(fmt.Errorf("Failed to load state: %s", sDiags.Err())) return nil, diags } - output, err := stateStore.GetRootOutputValues(ctx) + output, err := sMgr.GetRootOutputValues(ctx) if err != nil { return nil, diags.Append(err) } diff --git a/internal/command/state_identities_test.go b/internal/command/state_identities_test.go index 6a9aae85877b..549735459922 100644 --- a/internal/command/state_identities_test.go +++ b/internal/command/state_identities_test.go @@ -443,7 +443,7 @@ func TestStateIdentities_stateStore(t *testing.T) { stateBytes := stateBuf.Bytes() // Create a mock that contains a persisted "default" state that uses the bytes from above. - mockProvider := mockPluggableStateStorageProvider(t) + mockProvider := mockPluggableStateStorageProvider() mockProvider.MockStates = map[string]interface{}{ "default": stateBytes, } diff --git a/internal/command/state_list_test.go b/internal/command/state_list_test.go index ce065f0a96e2..0c5b350c95eb 100644 --- a/internal/command/state_list_test.go +++ b/internal/command/state_list_test.go @@ -198,7 +198,7 @@ func TestStateList_stateStore(t *testing.T) { } // Create a mock that contains a persisted "default" state that uses the bytes from above. - mockProvider := mockPluggableStateStorageProvider(t) + mockProvider := mockPluggableStateStorageProvider() mockProvider.MockStates = map[string]interface{}{ "default": stateBuf.Bytes(), } diff --git a/internal/command/state_mv_test.go b/internal/command/state_mv_test.go index 07f4cbc0dd51..80b3c4acff6f 100644 --- a/internal/command/state_mv_test.go +++ b/internal/command/state_mv_test.go @@ -202,7 +202,7 @@ func TestStateMv_stateStore(t *testing.T) { } // Create a mock that contains a persisted "default" state that uses the bytes from above. - mockProvider := mockPluggableStateStorageProvider(t) + mockProvider := mockPluggableStateStorageProvider() mockProvider.MockStates = map[string]interface{}{ "default": stateBuf.Bytes(), } diff --git a/internal/command/state_pull_test.go b/internal/command/state_pull_test.go index e0e2c06548b8..8c712b1821bb 100644 --- a/internal/command/state_pull_test.go +++ b/internal/command/state_pull_test.go @@ -85,7 +85,7 @@ func TestStatePull_stateStore(t *testing.T) { stateBytes := stateBuf.Bytes() // Create a mock that contains a persisted "default" state that uses the bytes from above. - mockProvider := mockPluggableStateStorageProvider(t) + mockProvider := mockPluggableStateStorageProvider() mockProvider.MockStates = map[string]interface{}{ "default": stateBytes, } diff --git a/internal/command/state_push_test.go b/internal/command/state_push_test.go index 9356e9342ca3..032ea45e295a 100644 --- a/internal/command/state_push_test.go +++ b/internal/command/state_push_test.go @@ -56,7 +56,7 @@ func TestStatePush_stateStore(t *testing.T) { expected := testStateRead(t, "replace.tfstate") // Create a mock that doesn't have any internal states. - mockProvider := mockPluggableStateStorageProvider(t) + mockProvider := mockPluggableStateStorageProvider() mockProviderAddress := addrs.NewDefaultProvider("test") providerSource, close := newMockProviderSource(t, map[string][]string{ "hashicorp/test": {"1.0.0"}, diff --git a/internal/command/state_replace_provider_test.go b/internal/command/state_replace_provider_test.go index 65e900fe73c0..c7af70be07ac 100644 --- a/internal/command/state_replace_provider_test.go +++ b/internal/command/state_replace_provider_test.go @@ -332,7 +332,7 @@ func TestStateReplaceProvider_stateStore(t *testing.T) { } // Create a mock that contains a persisted "default" state that uses the bytes from above. - mockProvider := mockPluggableStateStorageProvider(t) + mockProvider := mockPluggableStateStorageProvider() mockProvider.MockStates = map[string]interface{}{ "default": stateBuf.Bytes(), } diff --git a/internal/command/state_rm_test.go b/internal/command/state_rm_test.go index 90ab21cb2a4e..a479f15c9133 100644 --- a/internal/command/state_rm_test.go +++ b/internal/command/state_rm_test.go @@ -130,7 +130,7 @@ func TestStateRm_stateStore(t *testing.T) { } // Create a mock that contains a persisted "default" state that uses the bytes from above. - mockProvider := mockPluggableStateStorageProvider(t) + mockProvider := mockPluggableStateStorageProvider() mockProvider.MockStates = map[string]interface{}{ "default": stateBuf.Bytes(), } diff --git a/internal/command/state_show_test.go b/internal/command/state_show_test.go index a686deecbf42..97589fb67069 100644 --- a/internal/command/state_show_test.go +++ b/internal/command/state_show_test.go @@ -371,7 +371,7 @@ func TestStateShow_stateStore(t *testing.T) { } // Create a mock that contains a persisted "default" state that uses the bytes from above. - mockProvider := mockPluggableStateStorageProvider(t) + mockProvider := mockPluggableStateStorageProvider() mockProvider.MockStates = map[string]interface{}{ "default": stateBuf.Bytes(), } From 6ecb90a5089d6621af8d7438ae92f4472570b25e Mon Sep 17 00:00:00 2001 From: Sarah French Date: Thu, 4 Dec 2025 21:10:03 +0000 Subject: [PATCH 17/18] test: Refactor to contain paths in reused variable, remove unnecessary .gitkeep --- .../command/e2etest/pluggable_state_store_test.go | 13 +++++++------ .../hashicorp/simple6/0.0.1/.gitkeep | 0 2 files changed, 7 insertions(+), 6 deletions(-) delete mode 100644 internal/command/e2etest/testdata/initialized-directory-with-state-store-fs/.terraform/providers/registry.terraform.io/hashicorp/simple6/0.0.1/.gitkeep diff --git a/internal/command/e2etest/pluggable_state_store_test.go b/internal/command/e2etest/pluggable_state_store_test.go index 09f802ce8cca..92238bd0fd3b 100644 --- a/internal/command/e2etest/pluggable_state_store_test.go +++ b/internal/command/e2etest/pluggable_state_store_test.go @@ -42,11 +42,11 @@ func TestPrimary_stateStore_workspaceCmd(t *testing.T) { // Move the provider binaries into a directory that we will point terraform // to using the -plugin-dir cli flag. platform := getproviders.CurrentPlatform.String() - hashiDir := "cache/registry.terraform.io/hashicorp/" - if err := os.MkdirAll(tf.Path(hashiDir, "simple6/0.0.1/", platform), os.ModePerm); err != nil { + fsMirrorPath := "cache/registry.terraform.io/hashicorp/simple6/0.0.1/" + if err := os.MkdirAll(tf.Path(fsMirrorPath, platform), os.ModePerm); err != nil { t.Fatal(err) } - if err := os.Rename(simple6ProviderExe, tf.Path(hashiDir, "simple6/0.0.1/", platform, "terraform-provider-simple6")); err != nil { + if err := os.Rename(simple6ProviderExe, tf.Path(fsMirrorPath, platform, "terraform-provider-simple6")); err != nil { t.Fatal(err) } @@ -149,13 +149,14 @@ func TestPrimary_stateStore_stateCmds(t *testing.T) { simple6Provider := filepath.Join(tf.WorkDir(), "terraform-provider-simple6") simple6ProviderExe := e2e.GoBuild("github.com/hashicorp/terraform/internal/provider-simple-v6/main", simple6Provider) - // Move the provider binaries into the correct directory .terraform/providers/ directory + // Move the provider binaries into the correct .terraform/providers/ directory // that will contain provider binaries in an initialized working directory. platform := getproviders.CurrentPlatform.String() - if err := os.MkdirAll(tf.Path(".terraform/providers/registry.terraform.io/hashicorp/simple6/0.0.1/", platform), os.ModePerm); err != nil { + providerCachePath := ".terraform/providers/registry.terraform.io/hashicorp/simple6/0.0.1/" + if err := os.MkdirAll(tf.Path(providerCachePath, platform), os.ModePerm); err != nil { t.Fatal(err) } - if err := os.Rename(simple6ProviderExe, tf.Path(".terraform/providers/registry.terraform.io/hashicorp/simple6/0.0.1/", platform, "terraform-provider-simple6")); err != nil { + if err := os.Rename(simple6ProviderExe, tf.Path(providerCachePath, platform, "terraform-provider-simple6")); err != nil { t.Fatal(err) } diff --git a/internal/command/e2etest/testdata/initialized-directory-with-state-store-fs/.terraform/providers/registry.terraform.io/hashicorp/simple6/0.0.1/.gitkeep b/internal/command/e2etest/testdata/initialized-directory-with-state-store-fs/.terraform/providers/registry.terraform.io/hashicorp/simple6/0.0.1/.gitkeep deleted file mode 100644 index e69de29bb2d1..000000000000 From c2d9c4288d39f806d8b801b7fa95c1013d49c84b Mon Sep 17 00:00:00 2001 From: Sarah French Date: Thu, 4 Dec 2025 21:21:45 +0000 Subject: [PATCH 18/18] test: Remove unneeded test code --- internal/command/state_identities_test.go | 7 +------ internal/command/state_list_test.go | 7 +------ internal/command/state_mv_test.go | 9 +-------- internal/command/state_push_test.go | 9 +-------- internal/command/state_replace_provider_test.go | 9 +-------- internal/command/state_rm_test.go | 9 +-------- internal/command/state_show_test.go | 9 ++------- 7 files changed, 8 insertions(+), 51 deletions(-) diff --git a/internal/command/state_identities_test.go b/internal/command/state_identities_test.go index 549735459922..aa3a3d809e88 100644 --- a/internal/command/state_identities_test.go +++ b/internal/command/state_identities_test.go @@ -448,10 +448,6 @@ func TestStateIdentities_stateStore(t *testing.T) { "default": stateBytes, } mockProviderAddress := addrs.NewDefaultProvider("test") - providerSource, close := newMockProviderSource(t, map[string][]string{ - "hashicorp/test": {"1.0.0"}, - }) - defer close() ui := cli.NewMockUi() c := &StateIdentitiesCommand{ @@ -462,8 +458,7 @@ func TestStateIdentities_stateStore(t *testing.T) { mockProviderAddress: providers.FactoryFixed(mockProvider), }, }, - ProviderSource: providerSource, - Ui: ui, + Ui: ui, }, } diff --git a/internal/command/state_list_test.go b/internal/command/state_list_test.go index 0c5b350c95eb..f482259d6221 100644 --- a/internal/command/state_list_test.go +++ b/internal/command/state_list_test.go @@ -203,10 +203,6 @@ func TestStateList_stateStore(t *testing.T) { "default": stateBuf.Bytes(), } mockProviderAddress := addrs.NewDefaultProvider("test") - providerSource, close := newMockProviderSource(t, map[string][]string{ - "hashicorp/test": {"1.0.0"}, - }) - defer close() ui := cli.NewMockUi() c := &StateListCommand{ @@ -217,8 +213,7 @@ func TestStateList_stateStore(t *testing.T) { mockProviderAddress: providers.FactoryFixed(mockProvider), }, }, - ProviderSource: providerSource, - Ui: ui, + Ui: ui, }, } diff --git a/internal/command/state_mv_test.go b/internal/command/state_mv_test.go index 80b3c4acff6f..76deb8060535 100644 --- a/internal/command/state_mv_test.go +++ b/internal/command/state_mv_test.go @@ -207,10 +207,6 @@ func TestStateMv_stateStore(t *testing.T) { "default": stateBuf.Bytes(), } mockProviderAddress := addrs.NewDefaultProvider("test") - providerSource, close := newMockProviderSource(t, map[string][]string{ - "hashicorp/test": {"1.0.0"}, - }) - defer close() // Make the mock assert that the resource has been moved when the new state is persisted oldAddr := "test_instance.foo" @@ -260,7 +256,6 @@ func TestStateMv_stateStore(t *testing.T) { } ui := new(cli.MockUi) - view, _ := testView(t) c := &StateMvCommand{ StateMeta{ Meta: Meta{ @@ -270,9 +265,7 @@ func TestStateMv_stateStore(t *testing.T) { mockProviderAddress: providers.FactoryFixed(mockProvider), }, }, - ProviderSource: providerSource, - Ui: ui, - View: view, + Ui: ui, }, }, } diff --git a/internal/command/state_push_test.go b/internal/command/state_push_test.go index 032ea45e295a..bfe822ebfd0e 100644 --- a/internal/command/state_push_test.go +++ b/internal/command/state_push_test.go @@ -58,13 +58,8 @@ func TestStatePush_stateStore(t *testing.T) { // Create a mock that doesn't have any internal states. mockProvider := mockPluggableStateStorageProvider() mockProviderAddress := addrs.NewDefaultProvider("test") - providerSource, close := newMockProviderSource(t, map[string][]string{ - "hashicorp/test": {"1.0.0"}, - }) - defer close() ui := new(cli.MockUi) - view, _ := testView(t) c := &StatePushCommand{ Meta: Meta{ AllowExperimentalFeatures: true, @@ -73,9 +68,7 @@ func TestStatePush_stateStore(t *testing.T) { mockProviderAddress: providers.FactoryFixed(mockProvider), }, }, - ProviderSource: providerSource, - Ui: ui, - View: view, + Ui: ui, }, } diff --git a/internal/command/state_replace_provider_test.go b/internal/command/state_replace_provider_test.go index c7af70be07ac..e7afaa59812c 100644 --- a/internal/command/state_replace_provider_test.go +++ b/internal/command/state_replace_provider_test.go @@ -337,13 +337,8 @@ func TestStateReplaceProvider_stateStore(t *testing.T) { "default": stateBuf.Bytes(), } mockProviderAddress := addrs.NewDefaultProvider("test") - providerSource, close := newMockProviderSource(t, map[string][]string{ - "hashicorp/test": {"1.0.0"}, - }) - defer close() ui := new(cli.MockUi) - view, _ := testView(t) c := &StateReplaceProviderCommand{ StateMeta{ Meta: Meta{ @@ -353,9 +348,7 @@ func TestStateReplaceProvider_stateStore(t *testing.T) { mockProviderAddress: providers.FactoryFixed(mockProvider), }, }, - ProviderSource: providerSource, - Ui: ui, - View: view, + Ui: ui, }, }, } diff --git a/internal/command/state_rm_test.go b/internal/command/state_rm_test.go index a479f15c9133..60efc678c883 100644 --- a/internal/command/state_rm_test.go +++ b/internal/command/state_rm_test.go @@ -135,10 +135,6 @@ func TestStateRm_stateStore(t *testing.T) { "default": stateBuf.Bytes(), } mockProviderAddress := addrs.NewDefaultProvider("test") - providerSource, close := newMockProviderSource(t, map[string][]string{ - "hashicorp/test": {"1.0.0"}, - }) - defer close() // Make the mock assert that the removed resource is not present when the new state is persisted keptResource := "test_instance.bar" @@ -161,7 +157,6 @@ func TestStateRm_stateStore(t *testing.T) { } ui := new(cli.MockUi) - view, _ := testView(t) c := &StateRmCommand{ StateMeta{ Meta: Meta{ @@ -171,9 +166,7 @@ func TestStateRm_stateStore(t *testing.T) { mockProviderAddress: providers.FactoryFixed(mockProvider), }, }, - ProviderSource: providerSource, - Ui: ui, - View: view, + Ui: ui, }, }, } diff --git a/internal/command/state_show_test.go b/internal/command/state_show_test.go index 97589fb67069..5324fdb56332 100644 --- a/internal/command/state_show_test.go +++ b/internal/command/state_show_test.go @@ -376,10 +376,6 @@ func TestStateShow_stateStore(t *testing.T) { "default": stateBuf.Bytes(), } mockProviderAddress := addrs.NewDefaultProvider("test") - providerSource, close := newMockProviderSource(t, map[string][]string{ - "hashicorp/test": {"1.0.0"}, - }) - defer close() ui := cli.NewMockUi() streams, done := terminal.StreamsForTesting(t) @@ -391,9 +387,8 @@ func TestStateShow_stateStore(t *testing.T) { mockProviderAddress: providers.FactoryFixed(mockProvider), }, }, - ProviderSource: providerSource, - Ui: ui, - Streams: streams, + Ui: ui, + Streams: streams, }, }