From bb0b060a78b4c1355baa0948dfd6662a4812a655 Mon Sep 17 00:00:00 2001 From: Mason Cole <117116981+bt-macole@users.noreply.github.com> Date: Tue, 27 Jun 2023 19:02:41 -0700 Subject: [PATCH] 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 --- modules/test-structure/save_test_data.go | 34 ++++++++---- modules/test-structure/save_test_data_test.go | 54 ++++++++++++++++++- 2 files changed, 77 insertions(+), 11 deletions(-) diff --git a/modules/test-structure/save_test_data.go b/modules/test-structure/save_test_data.go index 602790915..02005b9a9 100644 --- a/modules/test-structure/save_test_data.go +++ b/modules/test-structure/save_test_data.go @@ -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 @@ -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 @@ -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 @@ -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 @@ -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 @@ -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 @@ -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 @@ -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) diff --git a/modules/test-structure/save_test_data_test.go b/modules/test-structure/save_test_data_test.go index 26599eefd..7f2d8120e 100644 --- a/modules/test-structure/save_test_data_test.go +++ b/modules/test-structure/save_test_data_test.go @@ -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.") @@ -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())) } @@ -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()