diff --git a/tfexec/fmt.go b/tfexec/fmt.go index d80a39b7..de30890a 100644 --- a/tfexec/fmt.go +++ b/tfexec/fmt.go @@ -4,6 +4,7 @@ import ( "bytes" "context" "fmt" + "io" "os/exec" "path/filepath" "strings" @@ -42,22 +43,27 @@ func FormatString(ctx context.Context, execPath string, content string) (string, // FormatString formats a passed string. func (tf *Terraform) FormatString(ctx context.Context, content string) (string, error) { - cmd, err := tf.formatCmd(ctx, nil, Dir("-")) + in := strings.NewReader(content) + var outBuf bytes.Buffer + err := tf.Format(ctx, in, &outBuf) if err != nil { return "", err } + return outBuf.String(), nil +} - cmd.Stdin = strings.NewReader(content) - - var outBuf bytes.Buffer - cmd.Stdout = mergeWriters(cmd.Stdout, &outBuf) - - err = tf.runTerraformCmd(cmd) +// Format performs formatting on the unformatted io.Reader (as stdin to the CLI) and returns +// the formatted result on the formatted io.Writer. +func (tf *Terraform) Format(ctx context.Context, unformatted io.Reader, formatted io.Writer) error { + cmd, err := tf.formatCmd(ctx, nil, Dir("-")) if err != nil { - return "", err + return err } - return outBuf.String(), nil + cmd.Stdin = unformatted + cmd.Stdout = mergeWriters(cmd.Stdout, formatted) + + return tf.runTerraformCmd(cmd) } // FormatWrite attempts to format and modify all config files in the working or selected (via DirOption) directory. diff --git a/tfexec/internal/e2etest/fmt_test.go b/tfexec/internal/e2etest/fmt_test.go index f081b007..c1622ded 100644 --- a/tfexec/internal/e2etest/fmt_test.go +++ b/tfexec/internal/e2etest/fmt_test.go @@ -1,11 +1,15 @@ package e2etest import ( + "bytes" "context" + "io" + "io/ioutil" "path/filepath" "reflect" "strings" "testing" + "time" "github.com/hashicorp/go-version" @@ -84,3 +88,89 @@ func TestFormatWrite(t *testing.T) { } }) } + +func TestFormat(t *testing.T) { + runTest(t, "", func(t *testing.T, tfv *version.Version, tf *tfexec.Terraform) { + unformatted := strings.TrimSpace(` +resource "foo" "bar" { + baz = 1 + qux = 2 +} +`) + + expected := strings.TrimSpace(` +resource "foo" "bar" { + baz = 1 + qux = 2 +} +`) + + start := time.Now() + var actual bytes.Buffer + err := tf.Format(context.Background(), strings.NewReader(unformatted), &actual) + if err != nil { + t.Fatal(err) + } + duration := time.Since(start) + t.Logf("formatting took %dms", duration.Milliseconds()) + + actualString := strings.TrimSpace(actual.String()) + if actualString != expected { + t.Fatalf("expected:\n%s\ngot:\n%s\n", expected, actualString) + } + }) +} + +func TestFormat_warmFormatter(t *testing.T) { + runTest(t, "", func(t *testing.T, tfv *version.Version, tf *tfexec.Terraform) { + unformatted := strings.TrimSpace(` +resource "foo" "bar" { + baz = 1 + qux = 2 +} +`) + + expected := strings.TrimSpace(` +resource "foo" "bar" { + baz = 1 + qux = 2 +} +`) + + inR, inW := io.Pipe() + outR, outW := io.Pipe() + + go func() { + err := tf.Format(context.Background(), inR, outW) + if err != nil { + outW.CloseWithError(err) + } + _ = outW.Close() + }() + + t.Log("Sleeping while CLI is warmed...") + time.Sleep(5 * time.Second) + t.Log("Sending unformatted data...") + start := time.Now() + _, err := inW.Write([]byte(unformatted)) + if err != nil { + t.Fatal(err) + } + err = inW.Close() + if err != nil { + t.Fatal(err) + } + + actual, err := ioutil.ReadAll(outR) + if err != nil { + t.Fatal(err) + } + duration := time.Since(start) + t.Logf("formatting took %dms", duration.Milliseconds()) + + actualString := strings.TrimSpace(string(actual)) + if actualString != expected { + t.Fatalf("expected:\n%s\ngot:\n%s\n", expected, actualString) + } + }) +}