Skip to content

Commit a1a3e84

Browse files
committed
fix(tests): captureOutput refactored to prevent potential deadlocks
Signed-off-by: sbailey <1661003+spbsoluble@users.noreply.github.com>
1 parent 4501be8 commit a1a3e84

File tree

2 files changed

+166
-110
lines changed

2 files changed

+166
-110
lines changed

cmd/stores_test.go

Lines changed: 142 additions & 108 deletions
Original file line numberDiff line numberDiff line change
@@ -17,11 +17,12 @@ package cmd
1717
import (
1818
"encoding/json"
1919
"fmt"
20-
"github.com/stretchr/testify/assert"
2120
"os"
2221
"path/filepath"
2322
"strings"
2423
"testing"
24+
25+
"github.com/stretchr/testify/assert"
2526
)
2627

2728
func Test_Stores_HelpCmd(t *testing.T) {
@@ -51,11 +52,13 @@ func Test_Stores_HelpCmd(t *testing.T) {
5152
func Test_Stores_ListCmd(t *testing.T) {
5253
testCmd := RootCmd
5354
// test
54-
testCmd.SetArgs([]string{"stores", "list", "--exp"})
55-
output := captureOutput(func() {
56-
err := testCmd.Execute()
57-
assert.NoError(t, err)
58-
})
55+
testCmd.SetArgs([]string{"stores", "list"})
56+
output := captureOutput(
57+
func() {
58+
err := testCmd.Execute()
59+
assert.NoError(t, err)
60+
},
61+
)
5962
var stores []interface{}
6063
if err := json.Unmarshal([]byte(output), &stores); err != nil {
6164
t.Fatalf("Error unmarshalling JSON: %v", err)
@@ -80,10 +83,12 @@ func Test_Stores_GetCmd(t *testing.T) {
8083
testCmd := RootCmd
8184
// test
8285
testCmd.SetArgs([]string{"stores", "list", "--exp"})
83-
output := captureOutput(func() {
84-
err := testCmd.Execute()
85-
assert.NoError(t, err)
86-
})
86+
output := captureOutput(
87+
func() {
88+
err := testCmd.Execute()
89+
assert.NoError(t, err)
90+
},
91+
)
8792
var stores []interface{}
8893
if err := json.Unmarshal([]byte(output), &stores); err != nil {
8994
t.Fatalf("Error unmarshalling JSON: %v", err)
@@ -102,10 +107,12 @@ func Test_Stores_GetCmd(t *testing.T) {
102107
assert.NotEmpty(t, store.(map[string]interface{})["CertStoreType"])
103108

104109
testCmd.SetArgs([]string{"stores", "get", "--id", store.(map[string]interface{})["Id"].(string)})
105-
output = captureOutput(func() {
106-
err := testCmd.Execute()
107-
assert.NoError(t, err)
108-
})
110+
output = captureOutput(
111+
func() {
112+
err := testCmd.Execute()
113+
assert.NoError(t, err)
114+
},
115+
)
109116

110117
var store map[string]interface{}
111118
if err := json.Unmarshal([]byte(output), &store); err != nil {
@@ -183,11 +190,24 @@ func Test_Stores_ImportCmd(t *testing.T) {
183190

184191
testCmd := RootCmd
185192
// test
186-
testCmd.SetArgs([]string{"stores", "import", "csv", "--file", outFileName, "--store-type-name", "k8ssecret", "--exp"})
187-
output := captureOutput(func() {
188-
err := testCmd.Execute()
189-
assert.NoError(t, err)
190-
})
193+
testCmd.SetArgs(
194+
[]string{
195+
"stores",
196+
"import",
197+
"csv",
198+
"--file",
199+
outFileName,
200+
"--store-type-name",
201+
"k8ssecret",
202+
"--exp",
203+
},
204+
)
205+
output := captureOutput(
206+
func() {
207+
err := testCmd.Execute()
208+
assert.NoError(t, err)
209+
},
210+
)
191211

192212
assert.Contains(t, output, "records processed")
193213
assert.Contains(t, output, "results written to")
@@ -234,14 +254,16 @@ func Test_Stores_GenerateImportTemplateCmd(t *testing.T) {
234254
testCmd := RootCmd
235255
// test
236256
testCmd.SetArgs([]string{"stores", "import", "generate-template", "--store-type-name", "k8ssecret"})
237-
output := captureOutput(func() {
238-
err := testCmd.Execute()
239-
assert.NoError(t, err)
240-
if err != nil {
241-
t.Errorf("Error: %v", err)
242-
return
243-
}
244-
})
257+
output := captureOutput(
258+
func() {
259+
err := testCmd.Execute()
260+
assert.NoError(t, err)
261+
if err != nil {
262+
t.Errorf("Error: %v", err)
263+
return
264+
}
265+
},
266+
)
245267

246268
assert.Contains(t, output, "Template file for store type with id")
247269
assert.Contains(t, output, "csv")
@@ -312,105 +334,117 @@ func testExportStore(t *testing.T, storeTypeName string) (string, []string) {
312334
files []string
313335
err error
314336
)
315-
t.Run(fmt.Sprintf("Export Stores of type %s", storeTypeName), func(t *testing.T) {
316-
testCmd := RootCmd
317-
testCmd.SetArgs([]string{"stores", "export", "--store-type-name", storeTypeName})
318-
output = captureOutput(func() {
319-
err := testCmd.Execute()
320-
assert.NoError(t, err)
321-
})
322-
323-
// assert that output is not empty
324-
assert.NotEmpty(t, output)
325-
326-
// assert that output is a string
327-
assert.IsType(t, "", output)
328-
329-
// assert that output does not contain 'error'
330-
assert.NotContains(t, output, "error")
331-
332-
// assert that output does not contain 'Error'
333-
assert.NotContains(t, output, "Error")
334-
335-
// assert that output does not contain 'ERROR'
336-
assert.NotContains(t, output, "ERROR")
337-
338-
// assert that contains "exported for store type with id"
339-
assert.Contains(t, output, "exported for store type with id")
340-
341-
// assert that contains .csv
342-
assert.Contains(t, output, ".csv")
343-
344-
// assert that a csv file was created in current working directory with a filename that contains 'export_store_*.csv'
345-
files, err = findMatchingFiles("*stores_export*.csv")
346-
assert.Nil(t, err)
347-
assert.NotEmpty(t, files)
348-
})
337+
t.Run(
338+
fmt.Sprintf("Export Stores of type %s", storeTypeName), func(t *testing.T) {
339+
testCmd := RootCmd
340+
testCmd.SetArgs([]string{"stores", "export", "--store-type-name", storeTypeName})
341+
output = captureOutput(
342+
func() {
343+
err := testCmd.Execute()
344+
assert.NoError(t, err)
345+
},
346+
)
347+
348+
// assert that output is not empty
349+
assert.NotEmpty(t, output)
350+
351+
// assert that output is a string
352+
assert.IsType(t, "", output)
353+
354+
// assert that output does not contain 'error'
355+
assert.NotContains(t, output, "error")
356+
357+
// assert that output does not contain 'Error'
358+
assert.NotContains(t, output, "Error")
359+
360+
// assert that output does not contain 'ERROR'
361+
assert.NotContains(t, output, "ERROR")
362+
363+
// assert that contains "exported for store type with id"
364+
assert.Contains(t, output, "exported for store type with id")
365+
366+
// assert that contains .csv
367+
assert.Contains(t, output, ".csv")
368+
369+
// assert that a csv file was created in current working directory with a filename that contains 'export_store_*.csv'
370+
files, err = findMatchingFiles("*stores_export*.csv")
371+
assert.Nil(t, err)
372+
assert.NotEmpty(t, files)
373+
},
374+
)
349375
return output, files
350376
}
351377

352378
func deleteStoreTest(t *testing.T, storeID string, allowFail bool) {
353-
t.Run(fmt.Sprintf("Delete Store %s", storeID), func(t *testing.T) {
354-
testCmd := RootCmd
355-
testCmd.SetArgs([]string{"stores", "delete", "--id", storeID})
356-
deleteStoreOutput := captureOutput(func() {
357-
err := testCmd.Execute()
379+
t.Run(
380+
fmt.Sprintf("Delete Store %s", storeID), func(t *testing.T) {
381+
testCmd := RootCmd
382+
testCmd.SetArgs([]string{"stores", "delete", "--id", storeID})
383+
deleteStoreOutput := captureOutput(
384+
func() {
385+
err := testCmd.Execute()
386+
if !allowFail {
387+
assert.NoError(t, err)
388+
}
389+
},
390+
)
358391
if !allowFail {
359-
assert.NoError(t, err)
360-
}
361-
})
362-
if !allowFail {
363-
if strings.Contains(deleteStoreOutput, "does not exist") {
364-
t.Errorf("Store %s does not exist", storeID)
365-
}
366-
if strings.Contains(deleteStoreOutput, "cannot be deleted") {
367-
assert.Fail(t, fmt.Sprintf("Store %s already exists", storeID))
392+
if strings.Contains(deleteStoreOutput, "does not exist") {
393+
t.Errorf("Store %s does not exist", storeID)
394+
}
395+
if strings.Contains(deleteStoreOutput, "cannot be deleted") {
396+
assert.Fail(t, fmt.Sprintf("Store %s already exists", storeID))
397+
}
398+
if strings.Contains(deleteStoreOutput, "error processing the request") {
399+
assert.Fail(t, fmt.Sprintf("Store %s was not deleted: %s", storeID, deleteStoreOutput))
400+
}
401+
assert.Contains(t, deleteStoreOutput, "deleted")
402+
assert.Contains(t, deleteStoreOutput, storeID)
368403
}
369-
if strings.Contains(deleteStoreOutput, "error processing the request") {
370-
assert.Fail(t, fmt.Sprintf("Store %s was not deleted: %s", storeID, deleteStoreOutput))
371-
}
372-
assert.Contains(t, deleteStoreOutput, "deleted")
373-
assert.Contains(t, deleteStoreOutput, storeID)
374-
}
375-
})
404+
},
405+
)
376406
}
377407

378408
func testValidateCSVHeader(t *testing.T, filename string, header []string, expected []string) {
379409
// iterate bulkStoreImportCSVHeader and verify that each header is in the csv header
380-
t.Run(fmt.Sprintf("Validate CSV header %s", filename), func(t *testing.T) {
381-
// Check that first col isn't empty
382-
assert.NotEmpty(t, header[0], "First column of CSV is empty")
383-
384-
for _, h := range expected {
385-
if h != "Properties" {
386-
assert.Contains(t, header, h)
410+
t.Run(
411+
fmt.Sprintf("Validate CSV header %s", filename), func(t *testing.T) {
412+
// Check that first col isn't empty
413+
assert.NotEmpty(t, header[0], "First column of CSV is empty")
414+
415+
for _, h := range expected {
416+
if h != "Properties" {
417+
assert.Contains(t, header, h)
418+
}
387419
}
388-
}
389420

390-
var props []string
391-
for _, h := range header {
392-
if strings.Contains(h, "Properties") {
393-
props = append(props, h)
421+
var props []string
422+
for _, h := range header {
423+
if strings.Contains(h, "Properties") {
424+
props = append(props, h)
425+
}
394426
}
395-
}
396-
assert.NotEmpty(t, props)
397-
})
427+
assert.NotEmpty(t, props)
428+
},
429+
)
398430
}
399431

400432
func testDeleteImportResults(t *testing.T, fileName string) {
401433
// get filename from path
402434
fName := filepath.Base(fileName)
403-
t.Run(fmt.Sprintf("Deleting imported stores %s", fName), func(t *testing.T) {
404-
csvData, err := csvToMap(fileName)
405-
assert.NoError(t, err)
435+
t.Run(
436+
fmt.Sprintf("Deleting imported stores %s", fName), func(t *testing.T) {
437+
csvData, err := csvToMap(fileName)
438+
assert.NoError(t, err)
406439

407-
for _, row := range csvData {
408-
// assert that each row has an ID
409-
//assert.NotEmpty(t, row[""])
410-
// delete store
411-
if row["Id"] != "" && row["Id"] != "error" && row["Id"] != "Id" {
412-
deleteStoreTest(t, row["Id"], false)
440+
for _, row := range csvData {
441+
// assert that each row has an ID
442+
//assert.NotEmpty(t, row[""])
443+
// delete store
444+
if row["Id"] != "" && row["Id"] != "error" && row["Id"] != "Id" {
445+
deleteStoreTest(t, row["Id"], false)
446+
}
413447
}
414-
}
415-
})
448+
},
449+
)
416450
}

cmd/test.go

Lines changed: 24 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -21,17 +21,39 @@ import (
2121
)
2222

2323
func captureOutput(f func()) string {
24+
// Save the original os.Stdout
2425
old := os.Stdout
26+
// Create a pipe
2527
r, w, _ := os.Pipe()
28+
// Set os.Stdout to the write end of the pipe
2629
os.Stdout = w
2730

31+
// Create a channel to signal when f() has completed
32+
done := make(chan bool)
33+
34+
// Buffer to store the output
35+
var buf bytes.Buffer
36+
37+
// Start a goroutine to copy from the read end of the pipe to the buffer
38+
go func() {
39+
io.Copy(&buf, r)
40+
// Signal that the copying is done
41+
done <- true
42+
}()
43+
44+
// Run the provided function f
2845
f()
2946

47+
// Close the write end of the pipe to signal EOF to the reader
3048
w.Close()
49+
50+
// Wait for the goroutine to finish copying
51+
<-done
52+
53+
// Restore the original os.Stdout
3154
os.Stdout = old
3255

33-
var buf bytes.Buffer
34-
io.Copy(&buf, r)
56+
// Return the captured output as a string
3557
return buf.String()
3658
}
3759

0 commit comments

Comments
 (0)