diff --git a/shell/run_shell_cmd.go b/shell/run_shell_cmd.go index e9a47ae0c5..af59d77561 100644 --- a/shell/run_shell_cmd.go +++ b/shell/run_shell_cmd.go @@ -2,6 +2,7 @@ package shell import ( "bufio" + "bytes" "fmt" "io" "io/ioutil" @@ -11,6 +12,7 @@ import ( "os/signal" "reflect" "strings" + "sync" "syscall" "github.com/gruntwork-io/terragrunt/errors" @@ -167,13 +169,25 @@ func (signalChannel *SignalsForwarder) Close() error { return nil } +// Scans a Scanner for text lines and appends them to a buffer +func scanScanner(wg *sync.WaitGroup, scanner *bufio.Scanner, writer io.Writer, buffer *bytes.Buffer) { + defer wg.Done() + for scanner.Scan() { + text := scanner.Text() + fmt.Fprintln(writer, text) + fmt.Fprintln(buffer, text) + } +} + // This function captures stdout and stderr while still printing it to the stdout and stderr of this Go program // outToErr flips std out to std err func readStdoutAndStderr(stdout io.ReadCloser, stderr io.ReadCloser, terragruntOptions *options.TerragruntOptions, outToErr bool) (string, error) { - allOutput := []string{} + + output := new(bytes.Buffer) var errWriter = terragruntOptions.ErrWriter var outWriter = terragruntOptions.Writer + var wg sync.WaitGroup if outToErr { outWriter = terragruntOptions.ErrWriter @@ -182,17 +196,12 @@ func readStdoutAndStderr(stdout io.ReadCloser, stderr io.ReadCloser, terragruntO stdoutScanner := bufio.NewScanner(stdout) stderrScanner := bufio.NewScanner(stderr) - for stdoutScanner.Scan() { - text := stdoutScanner.Text() - fmt.Fprintln(outWriter, text) - allOutput = append(allOutput, text) - } + wg.Add(2) - for stderrScanner.Scan() { - text := stderrScanner.Text() - fmt.Fprintln(errWriter, text) - allOutput = append(allOutput, text) - } + go scanScanner(&wg, stdoutScanner, outWriter, output) + go scanScanner(&wg, stderrScanner, errWriter, output) + + wg.Wait() if err := stdoutScanner.Err(); err != nil { return "", err @@ -202,5 +211,5 @@ func readStdoutAndStderr(stdout io.ReadCloser, stderr io.ReadCloser, terragruntO return "", err } - return strings.Join(allOutput, "\n"), nil + return output.String(), nil } diff --git a/shell/run_shell_cmd_output_test.go b/shell/run_shell_cmd_output_test.go new file mode 100644 index 0000000000..24d0396e24 --- /dev/null +++ b/shell/run_shell_cmd_output_test.go @@ -0,0 +1,37 @@ +// +build linux darwin + +package shell + +import ( + "bufio" + "strings" + "testing" + + "github.com/gruntwork-io/terragrunt/options" + "github.com/stretchr/testify/assert" +) + +func TestCommandOutputOrder(t *testing.T) { + t.Parallel() + + terragruntOptions, err := options.NewTerragruntOptionsForTest("") + assert.Nil(t, err, "Unexpected error creating NewTerragruntOptionsForTest: %v", err) + terragruntOptions.TerraformCliArgs = append(terragruntOptions.TerraformCliArgs, "same") + out, err := RunShellCommandWithOutput(terragruntOptions, "../testdata/test_outputs.sh", "same") + + assert.NotNil(t, out, "Should get output") + assert.Nil(t, err, "Should have no error") + + scanner := bufio.NewScanner(strings.NewReader(out)) + var outputs = []string{} + for scanner.Scan() { + outputs = append(outputs, scanner.Text()) + } + + assert.True(t, len(outputs) == 5, "Should have 5 entries") + assert.Equal(t, "stdout1", outputs[0], "First one from stdout") + assert.Equal(t, "stderr1", outputs[1], "First one from stderr") + assert.Equal(t, "stdout2", outputs[2], "Second one from stdout") + assert.Equal(t, "stderr2", outputs[3], "Second one from stderr") + assert.Equal(t, "stderr3", outputs[4], "Third one from stderr") +} diff --git a/testdata/test_outputs.sh b/testdata/test_outputs.sh new file mode 100755 index 0000000000..14d563db50 --- /dev/null +++ b/testdata/test_outputs.sh @@ -0,0 +1,10 @@ +#!/bin/sh +echo 'stdout1' +sleep 1 +>&2 echo 'stderr1' +sleep 1 +echo 'stdout2' +sleep 1 +>&2 echo 'stderr2' +sleep 1 +>&2 echo 'stderr3'