Skip to content

Commit

Permalink
feat(test-structure): add save test data if dne
Browse files Browse the repository at this point in the history
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
  • Loading branch information
bt-macole committed Jul 21, 2023
1 parent 64b4cee commit bb0b060
Show file tree
Hide file tree
Showing 2 changed files with 77 additions and 11 deletions.
34 changes: 24 additions & 10 deletions modules/test-structure/save_test_data.go
Original file line number Diff line number Diff line change
Expand Up @@ -21,7 +21,14 @@ import (
// SaveTerraformOptions serializes and saves TerraformOptions into the given folder. This allows you to create TerraformOptions during setup
// and to reuse that TerraformOptions later during validation and teardown.
func SaveTerraformOptions(t testing.TestingT, testFolder string, terraformOptions *terraform.Options) {
SaveTestData(t, formatTerraformOptionsPath(testFolder), terraformOptions)
SaveTestData(t, formatTerraformOptionsPath(testFolder), true, terraformOptions)
}

// SaveTerraformOptionsIfNotPresent serializes and saves TerraformOptions into the given folder if the file does not exist or the json is
// empty. This allows you to create TerraformOptions during setup and to reuse that TerraformOptions later during validation and teardown,
// but will prevent overwritting the contents and potentially duplicating resources.
func SaveTerraformOptionsIfNotPresent(t testing.TestingT, testFolder string, terraformOptions *terraform.Options) {
SaveTestData(t, formatTerraformOptionsPath(testFolder), false, terraformOptions)
}

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

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

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

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

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

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

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

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

if IsTestDataPresent(t, path) {
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)
if overwrite {
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)
} else {
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)
return
}
}

bytes, err := json.Marshal(value)
Expand Down
54 changes: 53 additions & 1 deletion modules/test-structure/save_test_data_test.go
Original file line number Diff line number Diff line change
Expand Up @@ -36,7 +36,8 @@ func TestSaveAndLoadTestData(t *testing.T) {
isTestDataPresent = IsTestDataPresent(t, tmpFile.Name())
assert.False(t, isTestDataPresent, "Expected no test data would be present because file exists but no data has been written yet.")

SaveTestData(t, tmpFile.Name(), expectedData)
overwrite := true
SaveTestData(t, tmpFile.Name(), overwrite, expectedData)

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

overwritingData := testData{
Foo: "foo",
Bar: false,
Baz: map[string]interface{}{"123": "456", "789": 1.0, "0": false},
}
SaveTestData(t, tmpFile.Name(), !overwrite, overwritingData)
LoadTestData(t, tmpFile.Name(), &actualData)
assert.Equal(t, expectedData, actualData)

CleanupTestData(t, tmpFile.Name())
assert.False(t, files.FileExists(tmpFile.Name()))
}
Expand Down Expand Up @@ -107,6 +117,48 @@ func TestSaveAndLoadTerraformOptions(t *testing.T) {
assert.Equal(t, expectedData, actualData)
}

func TestSaveTerraformOptionsIfNotPresent(t *testing.T) {
t.Parallel()

tmpFolder := t.TempDir()

expectedData := &terraform.Options{
TerraformDir: "/abc/def/ghi",
Vars: map[string]interface{}{},
}
SaveTerraformOptionsIfNotPresent(t, tmpFolder, expectedData)

overwritingData := &terraform.Options{
TerraformDir: "/123/456/789",
Vars: map[string]interface{}{},
}
SaveTerraformOptionsIfNotPresent(t, tmpFolder, overwritingData)

actualData := LoadTerraformOptions(t, tmpFolder)
assert.Equal(t, expectedData, actualData)
}

func TestSaveTerraformOptionsOverwrite(t *testing.T) {
t.Parallel()

tmpFolder := t.TempDir()

originaData := &terraform.Options{
TerraformDir: "/abc/def/ghi",
Vars: map[string]interface{}{},
}
SaveTerraformOptions(t, tmpFolder, originaData)

overwritingData := &terraform.Options{
TerraformDir: "/123/456/789",
Vars: map[string]interface{}{},
}
SaveTerraformOptions(t, tmpFolder, overwritingData)

actualData := LoadTerraformOptions(t, tmpFolder)
assert.Equal(t, overwritingData, actualData)
}

func TestSaveAndLoadAmiId(t *testing.T) {
t.Parallel()

Expand Down

0 comments on commit bb0b060

Please sign in to comment.