diff --git a/command/command_test.go b/command/command_test.go index 098d2eae0..49e616c45 100644 --- a/command/command_test.go +++ b/command/command_test.go @@ -2,6 +2,7 @@ package command import ( "flag" + "io" "io/ioutil" "log" "os" @@ -336,3 +337,67 @@ func testFixCwd(t *testing.T, tmp, cwd string) { t.Fatalf("err: %v", err) } } + +// testStdinPipe changes os.Stdin to be a pipe that sends the data from +// the reader before closing the pipe. +// +// The returned function should be deferred to properly clean up and restore +// the original stdin. +func testStdinPipe(t *testing.T, src io.Reader) func() { + r, w, err := os.Pipe() + if err != nil { + t.Fatalf("err: %s", err) + } + + // Modify stdin to point to our new pipe + old := os.Stdin + os.Stdin = r + + // Copy the data from the reader to the pipe + go func() { + defer w.Close() + io.Copy(w, src) + }() + + return func() { + // Close our read end + r.Close() + + // Reset stdin + os.Stdin = old + } +} + +// Modify os.Stdout to write to the given buffer. Note that this is generally +// not useful since the commands are configured to write to a cli.Ui, not +// Stdout directly. Commands like `console` though use the raw stdout. +func testStdoutCapture(t *testing.T, dst io.Writer) func() { + r, w, err := os.Pipe() + if err != nil { + t.Fatalf("err: %s", err) + } + + // Modify stdout + old := os.Stdout + os.Stdout = w + + // Copy + doneCh := make(chan struct{}) + go func() { + defer close(doneCh) + defer r.Close() + io.Copy(dst, r) + }() + + return func() { + // Close the writer end of the pipe + w.Sync() + w.Close() + + // Reset stdout + os.Stdout = old + + // Wait for the data copy to complete to avoid a race reading data + <-doneCh + } +} diff --git a/command/console_test.go b/command/console_test.go index 184bc7d0f..0c9b0f8d4 100644 --- a/command/console_test.go +++ b/command/console_test.go @@ -1,6 +1,86 @@ package command +import ( + "bytes" + "io/ioutil" + "path/filepath" + "strings" + "testing" + + "github.com/mitchellh/cli" +) + // ConsoleCommand is tested primarily with tests in the "repl" package. // It is not tested here because the Console uses a readline-like library // that takes over stdin/stdout. It is difficult to test directly. The // core logic is tested in "repl" +// +// This file still contains some tests using the stdin-based input. + +func TestConsole_basic(t *testing.T) { + tmp, cwd := testCwd(t) + defer testFixCwd(t, tmp, cwd) + + p := testProvider() + ui := new(cli.MockUi) + c := &ConsoleCommand{ + Meta: Meta{ + ContextOpts: testCtxConfig(p), + Ui: ui, + }, + } + + var output bytes.Buffer + defer testStdinPipe(t, strings.NewReader("1+5\n"))() + outCloser := testStdoutCapture(t, &output) + + args := []string{} + code := c.Run(args) + outCloser() + if code != 0 { + t.Fatalf("bad: %d\n\n%s", code, ui.ErrorWriter.String()) + } + + actual := output.String() + if actual != "6\n" { + t.Fatalf("bad: %q", actual) + } +} + +func TestConsole_tfvars(t *testing.T) { + tmp, cwd := testCwd(t) + defer testFixCwd(t, tmp, cwd) + + // Write a terraform.tvars + varFilePath := filepath.Join(tmp, "terraform.tfvars") + if err := ioutil.WriteFile(varFilePath, []byte(applyVarFile), 0644); err != nil { + t.Fatalf("err: %s", err) + } + + p := testProvider() + ui := new(cli.MockUi) + c := &ConsoleCommand{ + Meta: Meta{ + ContextOpts: testCtxConfig(p), + Ui: ui, + }, + } + + var output bytes.Buffer + defer testStdinPipe(t, strings.NewReader("var.foo\n"))() + outCloser := testStdoutCapture(t, &output) + + args := []string{ + testFixturePath("apply-vars"), + } + code := c.Run(args) + outCloser() + if code != 0 { + t.Fatalf("bad: %d\n\n%s", code, ui.ErrorWriter.String()) + } + + actual := output.String() + if actual != "bar\n" { + t.Fatalf("bad: %q", actual) + } +} diff --git a/terraform/context.go b/terraform/context.go index b95bda40f..e02b85d8f 100644 --- a/terraform/context.go +++ b/terraform/context.go @@ -246,7 +246,7 @@ func (c *Context) Interpolater() *Interpolater { Module: c.module, State: c.state.DeepCopy(), StateLock: &stateLock, - VariableValues: map[string]interface{}{}, + VariableValues: c.variables, VariableValuesLock: &varLock, } }