Skip to content

Commit 88c14fc

Browse files
committed
Add more test coverage of backend.Backend methods implemented in local-state
1 parent 39f6248 commit 88c14fc

File tree

2 files changed

+305
-22
lines changed

2 files changed

+305
-22
lines changed

internal/backend/local-state/backend_test.go

+305-1
Original file line numberDiff line numberDiff line change
@@ -1,21 +1,303 @@
11
package local_state
22

33
import (
4+
"fmt"
5+
"os"
46
"reflect"
7+
"strings"
58
"testing"
69

710
"github.com/hashicorp/terraform/internal/backend"
11+
"github.com/hashicorp/terraform/internal/states"
12+
"github.com/zclconf/go-cty/cty"
813
)
914

1015
func TestLocal_backend(t *testing.T) {
11-
backend.TestTmpDir(t)
16+
_ = testTmpDir(t)
1217
b := New()
1318
backend.TestBackendStates(t, b)
1419
backend.TestBackendStateLocks(t, b, b)
1520
}
1621

22+
func TestLocal_PrepareConfig(t *testing.T) {
23+
// Setup
24+
_ = testTmpDir(t)
25+
26+
b := New()
27+
28+
// PATH ATTR
29+
// Empty string path attribute isn't valid
30+
config := cty.ObjectVal(map[string]cty.Value{
31+
"path": cty.StringVal(""),
32+
"workspace_dir": cty.NullVal(cty.String),
33+
})
34+
_, diags := b.PrepareConfig(config)
35+
if !diags.HasErrors() {
36+
t.Fatalf("expected an error from PrepareConfig but got none")
37+
}
38+
expectedErr := `The "path" attribute value must not be empty`
39+
if !strings.Contains(diags.Err().Error(), expectedErr) {
40+
t.Fatalf("expected an error containing %q, got: %q", expectedErr, diags.Err())
41+
}
42+
43+
// PrepareConfig doesn't enforce the path value has .tfstate extension
44+
config = cty.ObjectVal(map[string]cty.Value{
45+
"path": cty.StringVal("path/to/state/my-state.docx"),
46+
"workspace_dir": cty.NullVal(cty.String),
47+
})
48+
_, diags = b.PrepareConfig(config)
49+
if diags.HasErrors() {
50+
t.Fatalf("unexpected error returned from PrepareConfig")
51+
}
52+
53+
// WORKSPACE_DIR ATTR
54+
// Empty string workspace_dir attribute isn't valid
55+
config = cty.ObjectVal(map[string]cty.Value{
56+
"path": cty.NullVal(cty.String),
57+
"workspace_dir": cty.StringVal(""),
58+
})
59+
_, diags = b.PrepareConfig(config)
60+
if !diags.HasErrors() {
61+
t.Fatalf("expected an error from PrepareConfig but got none")
62+
}
63+
expectedErr = `The "workspace_dir" attribute value must not be empty`
64+
if !strings.Contains(diags.Err().Error(), expectedErr) {
65+
t.Fatalf("expected an error containing %q, got: %q", expectedErr, diags.Err())
66+
}
67+
68+
// Existence of directory isn't checked during PrepareConfig
69+
// (Non-existent directories are created as a side-effect of WriteState)
70+
config = cty.ObjectVal(map[string]cty.Value{
71+
"path": cty.NullVal(cty.String),
72+
"workspace_dir": cty.StringVal("this/does/not/exist"),
73+
})
74+
_, diags = b.PrepareConfig(config)
75+
if diags.HasErrors() {
76+
t.Fatalf("unexpected error returned from PrepareConfig")
77+
}
78+
}
79+
80+
// The `path` attribute should only affect the default workspace's state
81+
// file location and name.
82+
//
83+
// Non-default workspaces' states names and locations are unaffected.
84+
func TestLocal_useOfPathAttribute(t *testing.T) {
85+
// Setup
86+
td := testTmpDir(t)
87+
88+
b := New()
89+
90+
// Configure local state-storage backend (skip call to PrepareConfig)
91+
path := "path/to/foobar.tfstate"
92+
config := cty.ObjectVal(map[string]cty.Value{
93+
"path": cty.StringVal(path), // Set
94+
"workspace_dir": cty.NullVal(cty.String),
95+
})
96+
diags := b.Configure(config)
97+
if diags.HasErrors() {
98+
t.Fatalf("unexpected error returned from Configure")
99+
}
100+
101+
// State file at the `path` location doesn't exist yet
102+
workspace := backend.DefaultStateName
103+
stmgr, err := b.StateMgr(workspace)
104+
if err != nil {
105+
t.Fatalf("unexpected error returned from StateMgr")
106+
}
107+
defaultStatePath := fmt.Sprintf("%s/%s", td, path)
108+
if _, err := os.Stat(defaultStatePath); !strings.Contains(err.Error(), "no such file or directory") {
109+
if err != nil {
110+
t.Fatalf("expected \"no such file or directory\" error when accessing file %q, got: %s", path, err)
111+
}
112+
t.Fatalf("expected the state file %q to not exist, but it did", path)
113+
}
114+
115+
// Writing to the default workspace's state creates a file
116+
// at the `path` location.
117+
// Directories are created to enable the path.
118+
s := states.NewState()
119+
s.RootOutputValues = map[string]*states.OutputValue{
120+
"foobar": {
121+
Value: cty.StringVal("foobar"),
122+
},
123+
}
124+
err = stmgr.WriteState(s)
125+
if err != nil {
126+
t.Fatalf("unexpected error returned from WriteState")
127+
}
128+
_, err = os.Stat(defaultStatePath)
129+
if err != nil {
130+
// The file should exist post-WriteState
131+
t.Fatalf("unexpected error when getting stats on the state file %q", path)
132+
}
133+
134+
// Writing to a non-default workspace's state creates a file
135+
// that's unaffected by the `path` location
136+
workspace = "fizzbuzz"
137+
stmgr, err = b.StateMgr(workspace)
138+
if err != nil {
139+
t.Fatalf("unexpected error returned from StateMgr")
140+
}
141+
fizzbuzzStatePath := fmt.Sprintf("%s/terraform.tfstate.d/%s/terraform.tfstate", td, workspace)
142+
err = stmgr.WriteState(s)
143+
if err != nil {
144+
t.Fatalf("unexpected error returned from WriteState")
145+
}
146+
_, err = os.Stat(fizzbuzzStatePath)
147+
if err != nil {
148+
t.Fatalf("unexpected error when getting stats on the state file \"terraform.tfstate.d/%s/terraform.tfstate\"", workspace)
149+
}
150+
}
151+
152+
// Using non-tfstate file extensions in the value of the `path` attribute
153+
// doesn't affect writing to state
154+
func TestLocal_pathAttributeWrongExtension(t *testing.T) {
155+
// Setup
156+
td := testTmpDir(t)
157+
158+
b := New()
159+
160+
// The path value doesn't have the expected .tfstate file extension
161+
path := "foobar.docx"
162+
fullPath := fmt.Sprintf("%s/%s", td, path)
163+
config := cty.ObjectVal(map[string]cty.Value{
164+
"path": cty.StringVal(path), // Set
165+
"workspace_dir": cty.NullVal(cty.String),
166+
})
167+
diags := b.Configure(config)
168+
if diags.HasErrors() {
169+
t.Fatalf("unexpected error returned from Configure")
170+
}
171+
172+
// Writing to the default workspace's state creates a file
173+
workspace := backend.DefaultStateName
174+
stmgr, err := b.StateMgr(workspace)
175+
if err != nil {
176+
t.Fatalf("unexpected error returned from StateMgr")
177+
}
178+
s := states.NewState()
179+
s.RootOutputValues = map[string]*states.OutputValue{
180+
"foobar": {
181+
Value: cty.StringVal("foobar"),
182+
},
183+
}
184+
err = stmgr.WriteState(s)
185+
if err != nil {
186+
t.Fatalf("unexpected error returned from WriteState")
187+
}
188+
_, err = os.Stat(fullPath)
189+
if err != nil {
190+
// The file should exist post-WriteState, despite the odd file extension
191+
t.Fatalf("unexpected error when getting stats on the state file %q", path)
192+
}
193+
}
194+
195+
// The `workspace_dir` attribute should only affect where non-default workspaces'
196+
// state files are saved.
197+
//
198+
// The default workspace's name and location are unaffected by this attribute.
199+
func TestLocal_useOfWorkspaceDirAttribute(t *testing.T) {
200+
// Setup
201+
td := testTmpDir(t)
202+
203+
b := New()
204+
205+
// Configure local state-storage backend (skip call to PrepareConfig)
206+
workspaceDir := "path/to/workspaces"
207+
config := cty.ObjectVal(map[string]cty.Value{
208+
"path": cty.NullVal(cty.String),
209+
"workspace_dir": cty.StringVal(workspaceDir), // set
210+
})
211+
diags := b.Configure(config)
212+
if diags.HasErrors() {
213+
t.Fatalf("unexpected error returned from Configure")
214+
}
215+
216+
// Writing to the default workspace's state creates a file.
217+
// As path attribute was left null, the default location
218+
// ./terraform.tfstate is used.
219+
// Unaffected by the `workspace_dir` location.
220+
workspace := backend.DefaultStateName
221+
defaultStatePath := fmt.Sprintf("%s/terraform.tfstate", td)
222+
stmgr, err := b.StateMgr(workspace)
223+
if err != nil {
224+
t.Fatalf("unexpected error returned from StateMgr")
225+
}
226+
s := states.NewState()
227+
s.RootOutputValues = map[string]*states.OutputValue{
228+
"foobar": {
229+
Value: cty.StringVal("foobar"),
230+
},
231+
}
232+
err = stmgr.WriteState(s)
233+
if err != nil {
234+
t.Fatalf("unexpected error returned from WriteState")
235+
}
236+
_, err = os.Stat(defaultStatePath)
237+
if err != nil {
238+
// The file should exist post-WriteState
239+
t.Fatal("unexpected error when getting stats on the state file for the default state")
240+
}
241+
242+
// Writing to a non-default workspace's state creates a file
243+
// that's affected by the `workspace_dir` location
244+
workspace = "fizzbuzz"
245+
fizzbuzzStatePath := fmt.Sprintf("%s/%s/%s/terraform.tfstate", td, workspaceDir, workspace)
246+
stmgr, err = b.StateMgr(workspace)
247+
if err != nil {
248+
t.Fatalf("unexpected error returned from StateMgr")
249+
}
250+
err = stmgr.WriteState(s)
251+
if err != nil {
252+
t.Fatalf("unexpected error returned from WriteState")
253+
}
254+
_, err = os.Stat(fizzbuzzStatePath)
255+
if err != nil {
256+
// The file should exist post-WriteState
257+
t.Fatalf("unexpected error when getting stats on the state file \"%s/%s/terraform.tfstate\"", workspaceDir, workspace)
258+
}
259+
}
260+
261+
func TestLocal_cannotDeleteDefaultState(t *testing.T) {
262+
// Setup
263+
_ = testTmpDir(t)
264+
dflt := backend.DefaultStateName
265+
expectedStates := []string{dflt}
266+
267+
b := New()
268+
269+
// Only default workspace exists initially.
270+
states, err := b.Workspaces()
271+
if err != nil {
272+
t.Fatal(err)
273+
}
274+
if !reflect.DeepEqual(states, expectedStates) {
275+
t.Fatalf("expected []string{%q}, got %q", dflt, states)
276+
}
277+
278+
// Attempt to delete default state - force=false
279+
err = b.DeleteWorkspace(dflt, false)
280+
if err == nil {
281+
t.Fatal("expected error but there was none")
282+
}
283+
expectedErr := "cannot delete default state"
284+
if err.Error() != expectedErr {
285+
t.Fatalf("expected error %q, got: %q", expectedErr, err)
286+
}
287+
288+
// Setting force=true doesn't change outcome
289+
err = b.DeleteWorkspace(dflt, true)
290+
if err == nil {
291+
t.Fatal("expected error but there was none")
292+
}
293+
if err.Error() != expectedErr {
294+
t.Fatalf("expected error %q, got: %q", expectedErr, err)
295+
}
296+
}
297+
17298
func TestLocal_addAndRemoveStates(t *testing.T) {
18299
// Setup
300+
_ = testTmpDir(t)
19301
dflt := backend.DefaultStateName
20302
expectedStates := []string{dflt}
21303

@@ -96,3 +378,25 @@ func TestLocal_addAndRemoveStates(t *testing.T) {
96378
t.Fatal("expected error deleting default state")
97379
}
98380
}
381+
382+
// testTmpDir changes into a tmp dir and change back automatically when the test
383+
// and all its subtests complete.
384+
func testTmpDir(t *testing.T) string {
385+
tmp := t.TempDir()
386+
387+
old, err := os.Getwd()
388+
if err != nil {
389+
t.Fatal(err)
390+
}
391+
392+
if err := os.Chdir(tmp); err != nil {
393+
t.Fatal(err)
394+
}
395+
396+
t.Cleanup(func() {
397+
// ignore errors and try to clean up
398+
os.Chdir(old)
399+
})
400+
401+
return tmp
402+
}

internal/backend/testing.go

-21
Original file line numberDiff line numberDiff line change
@@ -4,7 +4,6 @@
44
package backend
55

66
import (
7-
"os"
87
"reflect"
98
"sort"
109
"testing"
@@ -427,23 +426,3 @@ func testLocksInWorkspace(t *testing.T, b1, b2 Backend, testForceUnlock bool, wo
427426
t.Fatalf("could not unlock with the reported ID %q: %s", infoErr.Info.ID, err)
428427
}
429428
}
430-
431-
// testTmpDir changes into a tmp dir and change back automatically when the test
432-
// and all its subtests complete.
433-
func TestTmpDir(t *testing.T) {
434-
tmp := t.TempDir()
435-
436-
old, err := os.Getwd()
437-
if err != nil {
438-
t.Fatal(err)
439-
}
440-
441-
if err := os.Chdir(tmp); err != nil {
442-
t.Fatal(err)
443-
}
444-
445-
t.Cleanup(func() {
446-
// ignore errors and try to clean up
447-
os.Chdir(old)
448-
})
449-
}

0 commit comments

Comments
 (0)