package state import ( "reflect" "testing" "github.com/hashicorp/terraform/terraform" ) // TestState is a helper for testing state implementations. It is expected // that the given implementation is pre-loaded with the TestStateInitial // state. func TestState(t *testing.T, s State) { t.Helper() if err := s.RefreshState(); err != nil { t.Fatalf("err: %s", err) } // Check that the initial state is correct. // These do have different Lineages, but we will replace current below. initial := TestStateInitial() if state := s.State(); !state.Equal(initial) { t.Fatalf("state does not match expected initial state:\n%#v\n\n%#v", state, initial) } // Now we've proven that the state we're starting with is an initial // state, we'll complete our work here with that state, since otherwise // further writes would violate the invariant that we only try to write // states that share the same lineage as what was initially written. current := s.State() // Write a new state and verify that we have it current.AddModuleState(&terraform.ModuleState{ Path: []string{"root"}, Outputs: map[string]*terraform.OutputState{ "bar": &terraform.OutputState{ Type: "string", Sensitive: false, Value: "baz", }, }, }) if err := s.WriteState(current); err != nil { t.Fatalf("err: %s", err) } if actual := s.State(); !actual.Equal(current) { t.Fatalf("bad:\n%#v\n\n%#v", actual, current) } // Test persistence if err := s.PersistState(); err != nil { t.Fatalf("err: %s", err) } // Refresh if we got it if err := s.RefreshState(); err != nil { t.Fatalf("err: %s", err) } if s.State().Lineage != current.Lineage { t.Fatalf("Lineage changed from %s to %s", s.State().Lineage, current.Lineage) } // Just set the serials the same... Then compare. actual := s.State() if !actual.Equal(current) { t.Fatalf("bad: %#v\n\n%#v", actual, current) } // Same serial serial := s.State().Serial if err := s.WriteState(current); err != nil { t.Fatalf("err: %s", err) } if err := s.PersistState(); err != nil { t.Fatalf("err: %s", err) } if s.State().Serial != serial { t.Fatalf("serial changed after persisting with no changes: got %d, want %d", s.State().Serial, serial) } // Change the serial current = current.DeepCopy() current.Modules = []*terraform.ModuleState{ &terraform.ModuleState{ Path: []string{"root", "somewhere"}, Outputs: map[string]*terraform.OutputState{ "serialCheck": &terraform.OutputState{ Type: "string", Sensitive: false, Value: "true", }, }, }, } if err := s.WriteState(current); err != nil { t.Fatalf("err: %s", err) } if err := s.PersistState(); err != nil { t.Fatalf("err: %s", err) } if s.State().Serial <= serial { t.Fatalf("serial incorrect after persisting with changes: got %d, want > %d", s.State().Serial, serial) } if s.State().Version != current.Version { t.Fatalf("Version changed from %d to %d", s.State().Version, current.Version) } if s.State().TFVersion != current.TFVersion { t.Fatalf("TFVersion changed from %s to %s", s.State().TFVersion, current.TFVersion) } // verify that Lineage doesn't change along with Serial, or during copying. if s.State().Lineage != current.Lineage { t.Fatalf("Lineage changed from %s to %s", s.State().Lineage, current.Lineage) } // Check that State() returns a copy by modifying the copy and comparing // to the current state. stateCopy := s.State() stateCopy.Serial++ if reflect.DeepEqual(stateCopy, s.State()) { t.Fatal("State() should return a copy") } // our current expected state should also marhsal identically to the persisted state if current.MarshalEqual(s.State()) { t.Fatalf("Persisted state altered unexpectedly. Expected: %#v\b Got: %#v", current, s.State()) } } // TestStateInitial is the initial state that a State should have // for TestState. func TestStateInitial() *terraform.State { initial := &terraform.State{ Modules: []*terraform.ModuleState{ &terraform.ModuleState{ Path: []string{"root", "child"}, Outputs: map[string]*terraform.OutputState{ "foo": &terraform.OutputState{ Type: "string", Sensitive: false, Value: "bar", }, }, }, }, } initial.Init() return initial }