terraform/state/remote/state_test.go

156 lines
4.0 KiB
Go

package remote
import (
"sync"
"testing"
"github.com/google/go-cmp/cmp"
"github.com/zclconf/go-cty/cty"
"github.com/hashicorp/terraform/states"
"github.com/hashicorp/terraform/states/statemgr"
"github.com/hashicorp/terraform/version"
)
func TestState_impl(t *testing.T) {
var _ statemgr.Reader = new(State)
var _ statemgr.Writer = new(State)
var _ statemgr.Persister = new(State)
var _ statemgr.Refresher = new(State)
var _ statemgr.Locker = new(State)
}
func TestStateRace(t *testing.T) {
s := &State{
Client: nilClient{},
}
current := states.NewState()
var wg sync.WaitGroup
for i := 0; i < 100; i++ {
wg.Add(1)
go func() {
defer wg.Done()
s.WriteState(current)
s.PersistState()
s.RefreshState()
}()
}
wg.Wait()
}
func TestStatePersist(t *testing.T) {
mgr := &State{
Client: &mockClient{
// Initial state just to give us a fixed starting point for our
// test assertions below, or else we'd need to deal with
// random lineage.
current: []byte(`
{
"version": 4,
"lineage": "mock-lineage",
"serial": 1,
"terraform_version":"0.0.0",
"outputs": {},
"resources": []
}
`),
},
}
// In normal use (during a Terraform operation) we always refresh and read
// before any writes would happen, so we'll mimic that here for realism.
if err := mgr.RefreshState(); err != nil {
t.Fatalf("failed to RefreshState: %s", err)
}
s := mgr.State()
s.RootModule().SetOutputValue("foo", cty.StringVal("bar"), false)
if err := mgr.WriteState(s); err != nil {
t.Fatalf("failed to WriteState: %s", err)
}
if err := mgr.PersistState(); err != nil {
t.Fatalf("failed to PersistState: %s", err)
}
// Persisting the same state again should be a no-op: it doesn't fail,
// but it ought not to appear in the client's log either.
if err := mgr.WriteState(s); err != nil {
t.Fatalf("failed to WriteState: %s", err)
}
if err := mgr.PersistState(); err != nil {
t.Fatalf("failed to PersistState: %s", err)
}
// ...but if we _do_ change something in the state then we should see
// it re-persist.
s.RootModule().SetOutputValue("foo", cty.StringVal("baz"), false)
if err := mgr.WriteState(s); err != nil {
t.Fatalf("failed to WriteState: %s", err)
}
if err := mgr.PersistState(); err != nil {
t.Fatalf("failed to PersistState: %s", err)
}
got := mgr.Client.(*mockClient).log
want := []mockClientRequest{
// The initial fetch from mgr.RefreshState above.
{
Method: "Get",
Content: map[string]interface{}{
"version": 4.0, // encoding/json decodes this as float64 by default
"lineage": "mock-lineage",
"serial": 1.0, // encoding/json decodes this as float64 by default
"terraform_version": "0.0.0",
"outputs": map[string]interface{}{},
"resources": []interface{}{},
},
},
// First call to PersistState, with output "foo" set to "bar".
{
Method: "Put",
Content: map[string]interface{}{
"version": 4.0,
"lineage": "mock-lineage",
"serial": 2.0, // serial increases because the outputs changed
"terraform_version": version.Version,
"outputs": map[string]interface{}{
"foo": map[string]interface{}{
"type": "string",
"value": "bar",
},
},
"resources": []interface{}{},
},
},
// Second call to PersistState generates no client requests, because
// nothing changed in the state itself.
// Third call to PersistState, with the "foo" output value updated
// to "baz".
{
Method: "Put",
Content: map[string]interface{}{
"version": 4.0,
"lineage": "mock-lineage",
"serial": 3.0, // serial increases because the outputs changed
"terraform_version": version.Version,
"outputs": map[string]interface{}{
"foo": map[string]interface{}{
"type": "string",
"value": "baz",
},
},
"resources": []interface{}{},
},
},
}
if diff := cmp.Diff(want, got); len(diff) > 0 {
t.Errorf("incorrect client requests\n%s", diff)
}
}