/
save_test_data.go
313 lines (260 loc) · 11.8 KB
/
save_test_data.go
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
202
203
204
205
206
207
208
209
210
211
212
213
214
215
216
217
218
219
220
221
222
223
224
225
226
227
228
229
230
231
232
233
234
235
236
237
238
239
240
241
242
243
244
245
246
247
248
249
250
251
252
253
254
255
256
257
258
259
260
261
262
263
264
265
266
267
268
269
270
271
272
273
274
275
276
277
278
279
280
281
282
283
284
285
286
287
288
289
290
291
292
293
294
295
296
297
298
299
300
301
302
303
304
305
306
307
308
309
310
311
312
313
package test_structure
import (
"encoding/json"
"fmt"
"io/ioutil"
"os"
"path/filepath"
"testing"
"github.com/gruntwork-io/terratest/modules/aws"
"github.com/gruntwork-io/terratest/modules/files"
"github.com/gruntwork-io/terratest/modules/k8s"
"github.com/gruntwork-io/terratest/modules/logger"
"github.com/gruntwork-io/terratest/modules/packer"
"github.com/gruntwork-io/terratest/modules/terraform"
"github.com/stretchr/testify/require"
)
// 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.T, testFolder string, terraformOptions *terraform.Options) {
SaveTestData(t, formatTerraformOptionsPath(testFolder), terraformOptions)
}
// LoadTerraformOptions loads and unserializes TerraformOptions from the given folder. This allows you to reuse a TerraformOptions that was
// created during an earlier setup step in later validation and teardown steps.
func LoadTerraformOptions(t *testing.T, testFolder string) *terraform.Options {
var terraformOptions terraform.Options
LoadTestData(t, formatTerraformOptionsPath(testFolder), &terraformOptions)
return &terraformOptions
}
// formatTerraformOptionsPath formats a path to save TerraformOptions in the given folder.
func formatTerraformOptionsPath(testFolder string) string {
return FormatTestDataPath(testFolder, "TerraformOptions.json")
}
// 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.T, testFolder string, packerOptions *packer.Options) {
SaveTestData(t, formatPackerOptionsPath(testFolder), packerOptions)
}
// LoadPackerOptions loads and unserializes PackerOptions from the given folder. This allows you to reuse a PackerOptions that was
// created during an earlier setup step in later validation and teardown steps.
func LoadPackerOptions(t *testing.T, testFolder string) *packer.Options {
var packerOptions packer.Options
LoadTestData(t, formatPackerOptionsPath(testFolder), &packerOptions)
return &packerOptions
}
// formatPackerOptionsPath formats a path to save PackerOptions in the given folder.
func formatPackerOptionsPath(testFolder string) string {
return FormatTestDataPath(testFolder, "PackerOptions.json")
}
// 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.T, testFolder string, keyPair *aws.Ec2Keypair) {
SaveTestData(t, formatEc2KeyPairPath(testFolder), keyPair)
}
// LoadEc2KeyPair loads and unserializes an Ec2KeyPair from the given folder. This allows you to reuse an Ec2KeyPair that was
// created during an earlier setup step in later validation and teardown steps.
func LoadEc2KeyPair(t *testing.T, testFolder string) *aws.Ec2Keypair {
var keyPair aws.Ec2Keypair
LoadTestData(t, formatEc2KeyPairPath(testFolder), &keyPair)
return &keyPair
}
// formatEc2KeyPairPath formats a path to save an Ec2KeyPair in the given folder.
func formatEc2KeyPairPath(testFolder string) string {
return FormatTestDataPath(testFolder, "Ec2KeyPair.json")
}
// 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.T, testFolder string, kubectlOptions *k8s.KubectlOptions) {
SaveTestData(t, formatKubectlOptionsPath(testFolder), kubectlOptions)
}
// LoadKubectlOptions loads and unserializes a KubectlOptions from the given folder. This allows you to reuse a KubectlOptions that was
// created during an earlier setup step in later validation and teardown steps.
func LoadKubectlOptions(t *testing.T, testFolder string) *k8s.KubectlOptions {
var kubectlOptions k8s.KubectlOptions
LoadTestData(t, formatKubectlOptionsPath(testFolder), &kubectlOptions)
return &kubectlOptions
}
// formatKubectlOptionsPath formats a path to save a KubectlOptions in the given folder.
func formatKubectlOptionsPath(testFolder string) string {
return FormatTestDataPath(testFolder, "KubectlOptions.json")
}
// SaveString serializes and saves a uniquely named string value into the given folder. This allows you to create one or more string
// values during one stage -- each with a unique name -- and to reuse those values during later stages.
func SaveString(t *testing.T, testFolder string, name string, val string) {
path := formatNamedTestDataPath(testFolder, name)
SaveTestData(t, path, val)
}
// LoadString loads and unserializes a uniquely named string value from the given folder. This allows you to reuse one or more string
// values that were created during an earlier setup step in later steps.
func LoadString(t *testing.T, testFolder string, name string) string {
var val string
LoadTestData(t, formatNamedTestDataPath(testFolder, name), &val)
return val
}
// SaveInt saves a uniquely named int value into the given folder. This allows you to create one or more int
// values during one stage -- each with a unique name -- and to reuse those values during later stages.
func SaveInt(t *testing.T, testFolder string, name string, val int) {
path := formatNamedTestDataPath(testFolder, name)
SaveTestData(t, path, val)
}
// LoadInt loads a uniquely named int value from the given folder. This allows you to reuse one or more int
// values that were created during an earlier setup step in later steps.
func LoadInt(t *testing.T, testFolder string, name string) int {
var val int
LoadTestData(t, formatNamedTestDataPath(testFolder, name), &val)
return val
}
// SaveArtifactID serializes and saves an Artifact ID into the given folder. This allows you to build an Artifact during setup and to reuse that
// Artifact later during validation and teardown.
func SaveArtifactID(t *testing.T, testFolder string, artifactID string) {
SaveString(t, testFolder, "Artifact", artifactID)
}
// LoadArtifactID loads and unserializes an Artifact ID from the given folder. This allows you to reuse an Artifact that was created during an
// earlier setup step in later validation and teardown steps.
func LoadArtifactID(t *testing.T, testFolder string) string {
return LoadString(t, testFolder, "Artifact")
}
// SaveAmiId serializes and saves an AMI ID into the given folder. This allows you to build an AMI during setup and to reuse that
// AMI later during validation and teardown.
//
// Deprecated: Use SaveArtifactID instead.
func SaveAmiId(t *testing.T, testFolder string, amiId string) {
SaveString(t, testFolder, "AMI", amiId)
}
// LoadAmiId loads and unserializes an AMI ID from the given folder. This allows you to reuse an AMI that was created during an
// earlier setup step in later validation and teardown steps.
//
// Deprecated: Use LoadArtifactID instead.
func LoadAmiId(t *testing.T, testFolder string) string {
return LoadString(t, testFolder, "AMI")
}
// formatNamedTestDataPath formats a path to save an arbitrary named value in the given folder.
func formatNamedTestDataPath(testFolder string, name string) string {
filename := fmt.Sprintf("%s.json", name)
return FormatTestDataPath(testFolder, filename)
}
// FormatTestDataPath formats a path to save test data.
func FormatTestDataPath(testFolder string, filename string) string {
return filepath.Join(testFolder, ".test-data", filename)
}
// 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.T, path string, 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)
}
bytes, err := json.Marshal(value)
if err != nil {
t.Fatalf("Failed to convert value %s to JSON: %v", path, err)
}
t.Logf("Marshalled JSON: %s", string(bytes))
parentDir := filepath.Dir(path)
if err := os.MkdirAll(parentDir, 0777); err != nil {
t.Fatalf("Failed to create folder %s: %v", parentDir, err)
}
if err := ioutil.WriteFile(path, bytes, 0644); err != nil {
t.Fatalf("Failed to save value %s: %v", path, err)
}
}
// LoadTestData loads and unserializes a value stored at the given path. The value should be a pointer to a struct into which the
// value will be deserialized. This allows you to reuse some sort of test data (e.g., TerraformOptions) from earlier
// setup steps in later validation and teardown steps.
func LoadTestData(t *testing.T, path string, value interface{}) {
logger.Logf(t, "Loading test data from %s", path)
bytes, err := ioutil.ReadFile(path)
if err != nil {
t.Fatalf("Failed to load value from %s: %v", path, err)
}
if err := json.Unmarshal(bytes, value); err != nil {
t.Fatalf("Failed to parse JSON for value %s: %v", path, err)
}
}
// IsTestDataPresent returns true if a file exists at $path and the test data there is non-empty.
func IsTestDataPresent(t *testing.T, path string) bool {
exists, err := files.FileExistsE(path)
if err != nil {
t.Fatalf("Failed to load test data from %s due to unexpected error: %v", path, err)
}
if !exists {
return false
}
bytes, err := ioutil.ReadFile(path)
if err != nil {
t.Fatalf("Failed to load test data from %s due to unexpected error: %v", path, err)
}
if isEmptyJSON(t, bytes) {
return false
}
return true
}
// isEmptyJSON returns true if the given bytes are empty, or in a valid JSON format that can reasonably be considered empty.
// The types used are based on the type possibilities listed at https://golang.org/src/encoding/json/decode.go?s=4062:4110#L51
func isEmptyJSON(t *testing.T, bytes []byte) bool {
var value interface{}
if len(bytes) == 0 {
return true
}
if err := json.Unmarshal(bytes, &value); err != nil {
t.Fatalf("Failed to parse JSON while testing whether it is empty: %v", err)
}
if value == nil {
return true
}
valueBool, ok := value.(bool)
if ok && !valueBool {
return true
}
valueFloat64, ok := value.(float64)
if ok && valueFloat64 == 0 {
return true
}
valueString, ok := value.(string)
if ok && valueString == "" {
return true
}
valueSlice, ok := value.([]interface{})
if ok && len(valueSlice) == 0 {
return true
}
valueMap, ok := value.(map[string]interface{})
if ok && len(valueMap) == 0 {
return true
}
return false
}
// CleanupTestData cleans up the test data at the given path.
func CleanupTestData(t *testing.T, path string) {
if files.FileExists(path) {
logger.Logf(t, "Cleaning up test data from %s", path)
if err := os.Remove(path); err != nil {
t.Fatalf("Failed to clean up file at %s: %v", path, err)
}
} else {
logger.Logf(t, "%s does not exist. Nothing to cleanup.", path)
}
}
// CleanupTestDataFolder cleans up the .test-data folder inside the given folder.
// If there are any errors, fail the test.
func CleanupTestDataFolder(t *testing.T, path string) {
err := CleanupTestDataFolderE(t, path)
require.NoError(t, err)
}
// CleanupTestDataFolderE cleans up the .test-data folder inside the given folder.
func CleanupTestDataFolderE(t *testing.T, path string) error {
path = filepath.Join(path, ".test-data")
exists, err := files.FileExistsE(path)
if err != nil {
logger.Logf(t, "Failed to clean up test data folder at %s: %v", path, err)
return err
}
if !exists {
logger.Logf(t, "%s does not exist. Nothing to cleanup.", path)
return nil
}
if err := os.RemoveAll(path); err != nil {
logger.Logf(t, "Failed to clean up test data folder at %s: %v", path, err)
return err
}
return nil
}