diff --git a/command/apply_test.go b/command/apply_test.go index b50b9cd14f..3981b01ad1 100644 --- a/command/apply_test.go +++ b/command/apply_test.go @@ -8,12 +8,10 @@ import ( "net/http" "net/url" "os" - "os/exec" "path/filepath" "reflect" "strings" "sync" - "syscall" "testing" "time" @@ -65,13 +63,11 @@ func TestApply(t *testing.T) { func TestApply_lockedState(t *testing.T) { statePath := testTempFile(t) - locker := exec.Command("go", "run", "testadata/statelocker.go", statePath) - locker.Stderr = os.Stderr - if err := locker.Start(); err != nil { + unlock, err := testLockState(statePath) + if err != nil { t.Fatal(err) } - - defer locker.Process.Signal(syscall.SIGTERM) + defer unlock() p := testProvider() ui := new(cli.MockUi) @@ -86,8 +82,13 @@ func TestApply_lockedState(t *testing.T) { "-state", statePath, testFixturePath("apply"), } - if code := c.Run(args); code != 0 { - t.Fatalf("bad: %d\n\n%s", code, ui.ErrorWriter.String()) + if code := c.Run(args); code == 0 { + t.Fatal("expected error") + } + + output := ui.ErrorWriter.String() + if !strings.Contains(output, "locked") { + t.Fatal("command output does not look like a lock error:", output) } } diff --git a/command/command_test.go b/command/command_test.go index 7be56bcda0..9418e85711 100644 --- a/command/command_test.go +++ b/command/command_test.go @@ -6,14 +6,17 @@ import ( "encoding/base64" "encoding/json" "flag" + "fmt" "io" "io/ioutil" "log" "net/http" "net/http/httptest" "os" + "os/exec" "path/filepath" "strings" + "syscall" "testing" "github.com/hashicorp/go-getter" @@ -529,3 +532,37 @@ func testRemoteState(t *testing.T, s *terraform.State, c int) (*terraform.Remote return remote, srv } + +// testlockState calls a separate process to the lock the state file at path. +// deferFunc should be called in the caller to properly unlock the file. +func testLockState(path string) (func(), error) { + locker := exec.Command("go", "run", "testdata/statelocker.go", path) + pr, pw, err := os.Pipe() + if err != nil { + return nil, err + } + defer pr.Close() + defer pw.Close() + locker.Stderr = pw + locker.Stdout = pw + + if err := locker.Start(); err != nil { + return nil, err + } + deferFunc := func() { + locker.Process.Signal(syscall.SIGTERM) + locker.Wait() + } + + // wait for the process to lock + buf := make([]byte, 1024) + n, err := pr.Read(buf) + if err != nil { + return deferFunc, fmt.Errorf("read from statelocker returned: %s", err) + } + + if string(buf[:n]) != "LOCKED" { + return deferFunc, fmt.Errorf("statelocker wrote", string(buf[:n])) + } + return deferFunc, nil +} diff --git a/command/refresh_test.go b/command/refresh_test.go index b5bfe7950a..4c79aec686 100644 --- a/command/refresh_test.go +++ b/command/refresh_test.go @@ -59,6 +59,43 @@ func TestRefresh(t *testing.T) { } } +func TestRefresh_lockedState(t *testing.T) { + state := testState() + statePath := testStateFile(t, state) + + unlock, err := testLockState(statePath) + if err != nil { + t.Fatal(err) + } + defer unlock() + + p := testProvider() + ui := new(cli.MockUi) + c := &RefreshCommand{ + Meta: Meta{ + ContextOpts: testCtxConfig(p), + Ui: ui, + }, + } + + p.RefreshFn = nil + p.RefreshReturn = &terraform.InstanceState{ID: "yes"} + + args := []string{ + "-state", statePath, + testFixturePath("refresh"), + } + + if code := c.Run(args); code == 0 { + t.Fatal("expected error") + } + + output := ui.ErrorWriter.String() + if !strings.Contains(output, "locked") { + t.Fatal("command output does not look like a lock error:", output) + } +} + func TestRefresh_badState(t *testing.T) { p := testProvider() ui := new(cli.MockUi)