Add tests to check Backend delegation

Ensure that when MultiState methods are properly delegated when there is
a defined Local.Backend.
This commit is contained in:
James Bardin 2017-02-22 14:53:43 -05:00
parent 0933541a8c
commit e6eb71dde5
2 changed files with 128 additions and 9 deletions

View File

@ -82,6 +82,10 @@ type Local struct {
schema *schema.Backend schema *schema.Backend
opLock sync.Mutex opLock sync.Mutex
once sync.Once once sync.Once
// workingDir is where the State* paths should be relative to.
// This is currently only used for tests.
workingDir string
} }
func (b *Local) Input( func (b *Local) Input(
@ -140,7 +144,7 @@ func (b *Local) States() ([]string, string, error) {
current = name current = name
} }
entries, err := ioutil.ReadDir(DefaultEnvDir) entries, err := ioutil.ReadDir(filepath.Join(b.workingDir, DefaultEnvDir))
// no error if there's no envs configured // no error if there's no envs configured
if os.IsNotExist(err) { if os.IsNotExist(err) {
return envs, current, nil return envs, current, nil
@ -182,14 +186,19 @@ func (b *Local) DeleteState(name string) error {
return errors.New("cannot delete default state") return errors.New("cannot delete default state")
} }
_, current, err := b.States()
if err != nil {
return err
}
// if we're deleting the current state, we change back to the default // if we're deleting the current state, we change back to the default
if name == b.currentState { if name == current {
if err := b.ChangeState(backend.DefaultStateName); err != nil { if err := b.ChangeState(backend.DefaultStateName); err != nil {
return err return err
} }
} }
return os.RemoveAll(filepath.Join(DefaultEnvDir, name)) return os.RemoveAll(filepath.Join(b.workingDir, DefaultEnvDir, name))
} }
// Change to the named state, creating it if it doesn't exist. // Change to the named state, creating it if it doesn't exist.
@ -231,13 +240,13 @@ func (b *Local) ChangeState(name string) error {
} }
} }
err = os.MkdirAll(DefaultDataDir, 0755) err = os.MkdirAll(filepath.Join(b.workingDir, DefaultDataDir), 0755)
if err != nil { if err != nil {
return err return err
} }
err = ioutil.WriteFile( err = ioutil.WriteFile(
filepath.Join(DefaultDataDir, DefaultEnvFile), filepath.Join(b.workingDir, DefaultDataDir, DefaultEnvFile),
[]byte(name), []byte(name),
0644, 0644,
) )
@ -412,7 +421,7 @@ func (b *Local) statePath() (string, error) {
path := DefaultStateFilename path := DefaultStateFilename
if current != backend.DefaultStateName && current != "" { if current != backend.DefaultStateName && current != "" {
path = filepath.Join(DefaultEnvDir, b.currentState, DefaultStateFilename) path = filepath.Join(b.workingDir, DefaultEnvDir, b.currentState, DefaultStateFilename)
} }
return path, nil return path, nil
} }
@ -430,7 +439,7 @@ func (b *Local) createState(name string) error {
} }
} }
err = os.MkdirAll(filepath.Join(DefaultEnvDir, name), 0755) err = os.MkdirAll(filepath.Join(b.workingDir, DefaultEnvDir, name), 0755)
if err != nil { if err != nil {
return err return err
} }
@ -442,7 +451,7 @@ func (b *Local) createState(name string) error {
// configuration files. // configuration files.
// If there are no configured environments, currentStateName returns "default" // If there are no configured environments, currentStateName returns "default"
func (b *Local) currentStateName() (string, error) { func (b *Local) currentStateName() (string, error) {
contents, err := ioutil.ReadFile(filepath.Join(DefaultDataDir, DefaultEnvFile)) contents, err := ioutil.ReadFile(filepath.Join(b.workingDir, DefaultDataDir, DefaultEnvFile))
if os.IsNotExist(err) { if os.IsNotExist(err) {
return backend.DefaultStateName, nil return backend.DefaultStateName, nil
} }

View File

@ -1,8 +1,10 @@
package local package local
import ( import (
"fmt"
"io/ioutil" "io/ioutil"
"os" "os"
"path/filepath"
"reflect" "reflect"
"strings" "strings"
"testing" "testing"
@ -15,6 +17,7 @@ func TestLocal_impl(t *testing.T) {
var _ backend.Enhanced = new(Local) var _ backend.Enhanced = new(Local)
var _ backend.Local = new(Local) var _ backend.Local = new(Local)
var _ backend.CLI = new(Local) var _ backend.CLI = new(Local)
var _ backend.MultiState = new(Local)
} }
func checkState(t *testing.T, path, expected string) { func checkState(t *testing.T, path, expected string) {
@ -53,7 +56,7 @@ func TestLocal_addAndRemoveStates(t *testing.T) {
} }
if !reflect.DeepEqual(states, expectedStates) { if !reflect.DeepEqual(states, expectedStates) {
t.Fatal("expected []string{%q}, got %q", dflt, states) t.Fatalf("expected []string{%q}, got %q", dflt, states)
} }
expectedA := "test_A" expectedA := "test_A"
@ -62,6 +65,9 @@ func TestLocal_addAndRemoveStates(t *testing.T) {
} }
states, current, err = b.States() states, current, err = b.States()
if err != nil {
t.Fatal(err)
}
if current != expectedA { if current != expectedA {
t.Fatalf("expected %q, got %q", expectedA, current) t.Fatalf("expected %q, got %q", expectedA, current)
} }
@ -77,6 +83,9 @@ func TestLocal_addAndRemoveStates(t *testing.T) {
} }
states, current, err = b.States() states, current, err = b.States()
if err != nil {
t.Fatal(err)
}
if current != expectedB { if current != expectedB {
t.Fatalf("expected %q, got %q", expectedB, current) t.Fatalf("expected %q, got %q", expectedB, current)
} }
@ -91,6 +100,9 @@ func TestLocal_addAndRemoveStates(t *testing.T) {
} }
states, current, err = b.States() states, current, err = b.States()
if err != nil {
t.Fatal(err)
}
if current != expectedB { if current != expectedB {
t.Fatalf("expected %q, got %q", dflt, current) t.Fatalf("expected %q, got %q", dflt, current)
} }
@ -105,6 +117,9 @@ func TestLocal_addAndRemoveStates(t *testing.T) {
} }
states, current, err = b.States() states, current, err = b.States()
if err != nil {
t.Fatal(err)
}
if current != dflt { if current != dflt {
t.Fatalf("expected %q, got %q", dflt, current) t.Fatalf("expected %q, got %q", dflt, current)
} }
@ -119,6 +134,101 @@ func TestLocal_addAndRemoveStates(t *testing.T) {
} }
} }
// verify the behavior with a backend that doesn't support multiple states
func TestLocal_noMultiStateBackend(t *testing.T) {
type noMultiState struct {
backend.Backend
}
b := &Local{
Backend: &noMultiState{},
}
_, _, err := b.States()
if err != ErrEnvNotSupported {
t.Fatal("backend does not support environments.", err)
}
err = b.ChangeState("test")
if err != ErrEnvNotSupported {
t.Fatal("backend does not support environments.", err)
}
err = b.ChangeState("test")
if err != ErrEnvNotSupported {
t.Fatal("backend does not support environments.", err)
}
}
// verify that the MultiState methods are dispatched to the correct Backend.
func TestLocal_multiStateBackend(t *testing.T) {
defer testTmpDir(t)()
dflt := backend.DefaultStateName
expectedStates := []string{dflt}
// make a second tmp dir for the sub-Backend.
// we verify the corret backend was called by checking the paths.
tmp, err := ioutil.TempDir("", "tf")
if err != nil {
t.Fatal(err)
}
defer os.RemoveAll(tmp)
fmt.Println("second tmp:", tmp)
b := &Local{
Backend: &Local{
workingDir: tmp,
},
}
testA := "test_A"
if err := b.ChangeState(testA); err != nil {
t.Fatal(err)
}
states, current, err := b.States()
if err != nil {
t.Fatal(err)
}
if current != testA {
t.Fatalf("expected %q, got %q", testA, current)
}
expectedStates = append(expectedStates, testA)
if !reflect.DeepEqual(states, expectedStates) {
t.Fatalf("expected %q, got %q", expectedStates, states)
}
// verify that no environment paths were created for the top-level Backend
if _, err := os.Stat(DefaultDataDir); !os.IsNotExist(err) {
t.Fatal("remote state operations should not have written local files")
}
if _, err := os.Stat(filepath.Join(DefaultEnvDir, testA)); !os.IsNotExist(err) {
t.Fatal("remote state operations should not have written local files")
}
// remove the new state
if err := b.DeleteState(testA); err != nil {
t.Fatal(err)
}
states, current, err = b.States()
if err != nil {
t.Fatal(err)
}
if current != dflt {
t.Fatalf("expected %q, got %q", dflt, current)
}
if !reflect.DeepEqual(states, expectedStates[:1]) {
t.Fatalf("expected %q, got %q", expectedStates, states)
}
}
// change into a tmp dir and return a deferable func to change back and cleanup // change into a tmp dir and return a deferable func to change back and cleanup
func testTmpDir(t *testing.T) func() { func testTmpDir(t *testing.T) func() {
tmp, err := ioutil.TempDir("", "tf") tmp, err := ioutil.TempDir("", "tf")