Skip to content

Commit bb0b060

Browse files
committed
feat(test-structure): add save test data if dne
SaveTestData is a powerful feature. But with great power comes great responsibility. If a user is not careful, this feature can cause unintended side effects including duplicating resources and overwriting state. Adding support for only saving if a file does not exists prevent overwriting contents and provides terratest users protection from themselves. resolves #1318
1 parent 64b4cee commit bb0b060

2 files changed

Lines changed: 77 additions & 11 deletions

File tree

modules/test-structure/save_test_data.go

Lines changed: 24 additions & 10 deletions
Original file line numberDiff line numberDiff line change
@@ -21,7 +21,14 @@ import (
2121
// SaveTerraformOptions serializes and saves TerraformOptions into the given folder. This allows you to create TerraformOptions during setup
2222
// and to reuse that TerraformOptions later during validation and teardown.
2323
func SaveTerraformOptions(t testing.TestingT, testFolder string, terraformOptions *terraform.Options) {
24-
SaveTestData(t, formatTerraformOptionsPath(testFolder), terraformOptions)
24+
SaveTestData(t, formatTerraformOptionsPath(testFolder), true, terraformOptions)
25+
}
26+
27+
// SaveTerraformOptionsIfNotPresent serializes and saves TerraformOptions into the given folder if the file does not exist or the json is
28+
// empty. This allows you to create TerraformOptions during setup and to reuse that TerraformOptions later during validation and teardown,
29+
// but will prevent overwritting the contents and potentially duplicating resources.
30+
func SaveTerraformOptionsIfNotPresent(t testing.TestingT, testFolder string, terraformOptions *terraform.Options) {
31+
SaveTestData(t, formatTerraformOptionsPath(testFolder), false, terraformOptions)
2532
}
2633

2734
// LoadTerraformOptions loads and unserializes TerraformOptions from the given folder. This allows you to reuse a TerraformOptions that was
@@ -40,7 +47,7 @@ func formatTerraformOptionsPath(testFolder string) string {
4047
// SavePackerOptions serializes and saves PackerOptions into the given folder. This allows you to create PackerOptions during setup
4148
// and to reuse that PackerOptions later during validation and teardown.
4249
func SavePackerOptions(t testing.TestingT, testFolder string, packerOptions *packer.Options) {
43-
SaveTestData(t, formatPackerOptionsPath(testFolder), packerOptions)
50+
SaveTestData(t, formatPackerOptionsPath(testFolder), true, packerOptions)
4451
}
4552

4653
// LoadPackerOptions loads and unserializes PackerOptions from the given folder. This allows you to reuse a PackerOptions that was
@@ -59,7 +66,7 @@ func formatPackerOptionsPath(testFolder string) string {
5966
// SaveEc2KeyPair serializes and saves an Ec2KeyPair into the given folder. This allows you to create an Ec2KeyPair during setup
6067
// and to reuse that Ec2KeyPair later during validation and teardown.
6168
func SaveEc2KeyPair(t testing.TestingT, testFolder string, keyPair *aws.Ec2Keypair) {
62-
SaveTestData(t, formatEc2KeyPairPath(testFolder), keyPair)
69+
SaveTestData(t, formatEc2KeyPairPath(testFolder), true, keyPair)
6370
}
6471

6572
// LoadEc2KeyPair loads and unserializes an Ec2KeyPair from the given folder. This allows you to reuse an Ec2KeyPair that was
@@ -78,7 +85,7 @@ func formatEc2KeyPairPath(testFolder string) string {
7885
// SaveSshKeyPair serializes and saves an SshKeyPair into the given folder. This allows you to create an SshKeyPair during setup
7986
// and to reuse that SshKeyPair later during validation and teardown.
8087
func SaveSshKeyPair(t testing.TestingT, testFolder string, keyPair *ssh.KeyPair) {
81-
SaveTestData(t, formatSshKeyPairPath(testFolder), keyPair)
88+
SaveTestData(t, formatSshKeyPairPath(testFolder), true, keyPair)
8289
}
8390

8491
// LoadSshKeyPair loads and unserializes an SshKeyPair from the given folder. This allows you to reuse an SshKeyPair that was
@@ -97,7 +104,7 @@ func formatSshKeyPairPath(testFolder string) string {
97104
// SaveKubectlOptions serializes and saves KubectlOptions into the given folder. This allows you to create a KubectlOptions during setup
98105
// and reuse that KubectlOptions later during validation and teardown.
99106
func SaveKubectlOptions(t testing.TestingT, testFolder string, kubectlOptions *k8s.KubectlOptions) {
100-
SaveTestData(t, formatKubectlOptionsPath(testFolder), kubectlOptions)
107+
SaveTestData(t, formatKubectlOptionsPath(testFolder), true, kubectlOptions)
101108
}
102109

103110
// LoadKubectlOptions loads and unserializes a KubectlOptions from the given folder. This allows you to reuse a KubectlOptions that was
@@ -117,7 +124,7 @@ func formatKubectlOptionsPath(testFolder string) string {
117124
// values during one stage -- each with a unique name -- and to reuse those values during later stages.
118125
func SaveString(t testing.TestingT, testFolder string, name string, val string) {
119126
path := formatNamedTestDataPath(testFolder, name)
120-
SaveTestData(t, path, val)
127+
SaveTestData(t, path, true, val)
121128
}
122129

123130
// LoadString loads and unserializes a uniquely named string value from the given folder. This allows you to reuse one or more string
@@ -132,7 +139,7 @@ func LoadString(t testing.TestingT, testFolder string, name string) string {
132139
// values during one stage -- each with a unique name -- and to reuse those values during later stages.
133140
func SaveInt(t testing.TestingT, testFolder string, name string, val int) {
134141
path := formatNamedTestDataPath(testFolder, name)
135-
SaveTestData(t, path, val)
142+
SaveTestData(t, path, true, val)
136143
}
137144

138145
// LoadInt loads a uniquely named int value from the given folder. This allows you to reuse one or more int
@@ -183,12 +190,19 @@ func FormatTestDataPath(testFolder string, filename string) string {
183190
}
184191

185192
// SaveTestData serializes and saves a value used at test time to the given path. This allows you to create some sort of test data
186-
// (e.g., TerraformOptions) during setup and to reuse this data later during validation and teardown.
187-
func SaveTestData(t testing.TestingT, path string, value interface{}) {
193+
// (e.g., TerraformOptions) during setup and to reuse this data later during validation and teardown. If `overwrite` is `true`,
194+
// any contents that exist in the file found at `path` will be overwritten. This has the potential for causing duplicated resources
195+
// and should be used with caution. If `overwrite` is `false`, the save will be skipped and a warning will be logged.
196+
func SaveTestData(t testing.TestingT, path string, overwrite bool, value interface{}) {
188197
logger.Logf(t, "Storing test data in %s so it can be reused later", path)
189198

190199
if IsTestDataPresent(t, path) {
191-
logger.Logf(t, "[WARNING] The named test data at path %s is non-empty. Save operation will overwrite existing value with \"%v\".\n.", path, value)
200+
if overwrite {
201+
logger.Logf(t, "[WARNING] The named test data at path %s is non-empty. Save operation will overwrite existing value with \"%v\".\n.", path, value)
202+
} else {
203+
logger.Logf(t, "[WARNING] The named test data at path %s is non-empty. Skipping save operation to prevent overwriting existing value with \"%v\".\n.", path, value)
204+
return
205+
}
192206
}
193207

194208
bytes, err := json.Marshal(value)

modules/test-structure/save_test_data_test.go

Lines changed: 53 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -36,7 +36,8 @@ func TestSaveAndLoadTestData(t *testing.T) {
3636
isTestDataPresent = IsTestDataPresent(t, tmpFile.Name())
3737
assert.False(t, isTestDataPresent, "Expected no test data would be present because file exists but no data has been written yet.")
3838

39-
SaveTestData(t, tmpFile.Name(), expectedData)
39+
overwrite := true
40+
SaveTestData(t, tmpFile.Name(), overwrite, expectedData)
4041

4142
isTestDataPresent = IsTestDataPresent(t, tmpFile.Name())
4243
assert.True(t, isTestDataPresent, "Expected test data would be present because file exists and data has been written to file.")
@@ -45,6 +46,15 @@ func TestSaveAndLoadTestData(t *testing.T) {
4546
LoadTestData(t, tmpFile.Name(), &actualData)
4647
assert.Equal(t, expectedData, actualData)
4748

49+
overwritingData := testData{
50+
Foo: "foo",
51+
Bar: false,
52+
Baz: map[string]interface{}{"123": "456", "789": 1.0, "0": false},
53+
}
54+
SaveTestData(t, tmpFile.Name(), !overwrite, overwritingData)
55+
LoadTestData(t, tmpFile.Name(), &actualData)
56+
assert.Equal(t, expectedData, actualData)
57+
4858
CleanupTestData(t, tmpFile.Name())
4959
assert.False(t, files.FileExists(tmpFile.Name()))
5060
}
@@ -107,6 +117,48 @@ func TestSaveAndLoadTerraformOptions(t *testing.T) {
107117
assert.Equal(t, expectedData, actualData)
108118
}
109119

120+
func TestSaveTerraformOptionsIfNotPresent(t *testing.T) {
121+
t.Parallel()
122+
123+
tmpFolder := t.TempDir()
124+
125+
expectedData := &terraform.Options{
126+
TerraformDir: "/abc/def/ghi",
127+
Vars: map[string]interface{}{},
128+
}
129+
SaveTerraformOptionsIfNotPresent(t, tmpFolder, expectedData)
130+
131+
overwritingData := &terraform.Options{
132+
TerraformDir: "/123/456/789",
133+
Vars: map[string]interface{}{},
134+
}
135+
SaveTerraformOptionsIfNotPresent(t, tmpFolder, overwritingData)
136+
137+
actualData := LoadTerraformOptions(t, tmpFolder)
138+
assert.Equal(t, expectedData, actualData)
139+
}
140+
141+
func TestSaveTerraformOptionsOverwrite(t *testing.T) {
142+
t.Parallel()
143+
144+
tmpFolder := t.TempDir()
145+
146+
originaData := &terraform.Options{
147+
TerraformDir: "/abc/def/ghi",
148+
Vars: map[string]interface{}{},
149+
}
150+
SaveTerraformOptions(t, tmpFolder, originaData)
151+
152+
overwritingData := &terraform.Options{
153+
TerraformDir: "/123/456/789",
154+
Vars: map[string]interface{}{},
155+
}
156+
SaveTerraformOptions(t, tmpFolder, overwritingData)
157+
158+
actualData := LoadTerraformOptions(t, tmpFolder)
159+
assert.Equal(t, overwritingData, actualData)
160+
}
161+
110162
func TestSaveAndLoadAmiId(t *testing.T) {
111163
t.Parallel()
112164

0 commit comments

Comments
 (0)