opentofu/states/state_test.go
James Bardin f915c5d957 remove EachMode from resource state
Due to the fact that resources can transition between each modes, trying
to track the mode for a resource as a whole in state doesn't work,
because there may be instances with a mode different from the resource
as a whole. This is difficult for core to track, as this metadata being
changed as a side effect from multiple places often causes core to see
the incorrect mode when evaluating instances.

Since core can always determine the correct mode to evaluate from the
configuration, we don't need to interrogate the state to know the mode.
Once core no longer needs to reference EachMode from states, the
resource state can simply be a container for instances, and doesn't need
to try and track the "current" mode.
2020-04-30 09:22:14 -04:00

259 lines
7.2 KiB
Go

package states
import (
"testing"
"github.com/go-test/deep"
"github.com/zclconf/go-cty/cty"
"github.com/hashicorp/terraform/addrs"
)
func TestState(t *testing.T) {
// This basic tests exercises the main mutation methods to construct
// a state. It is not fully comprehensive, so other tests should visit
// more esoteric codepaths.
state := NewState()
rootModule := state.RootModule()
if rootModule == nil {
t.Errorf("root module is nil; want valid object")
}
rootModule.SetLocalValue("foo", cty.StringVal("foo value"))
rootModule.SetOutputValue("bar", cty.StringVal("bar value"), false)
rootModule.SetOutputValue("secret", cty.StringVal("secret value"), true)
rootModule.SetResourceInstanceCurrent(
addrs.Resource{
Mode: addrs.ManagedResourceMode,
Type: "test_thing",
Name: "baz",
}.Instance(addrs.IntKey(0)),
&ResourceInstanceObjectSrc{
Status: ObjectReady,
SchemaVersion: 1,
AttrsJSON: []byte(`{"woozles":"confuzles"}`),
},
addrs.AbsProviderConfig{
Provider: addrs.NewDefaultProvider("test"),
Module: addrs.RootModule,
},
)
childModule := state.EnsureModule(addrs.RootModuleInstance.Child("child", addrs.NoKey))
childModule.SetOutputValue("pizza", cty.StringVal("hawaiian"), false)
multiModA := state.EnsureModule(addrs.RootModuleInstance.Child("multi", addrs.StringKey("a")))
multiModA.SetOutputValue("pizza", cty.StringVal("cheese"), false)
multiModB := state.EnsureModule(addrs.RootModuleInstance.Child("multi", addrs.StringKey("b")))
multiModB.SetOutputValue("pizza", cty.StringVal("sausage"), false)
want := &State{
Modules: map[string]*Module{
"": {
Addr: addrs.RootModuleInstance,
LocalValues: map[string]cty.Value{
"foo": cty.StringVal("foo value"),
},
OutputValues: map[string]*OutputValue{
"bar": {
Addr: addrs.AbsOutputValue{
OutputValue: addrs.OutputValue{
Name: "bar",
},
},
Value: cty.StringVal("bar value"),
Sensitive: false,
},
"secret": {
Addr: addrs.AbsOutputValue{
OutputValue: addrs.OutputValue{
Name: "secret",
},
},
Value: cty.StringVal("secret value"),
Sensitive: true,
},
},
Resources: map[string]*Resource{
"test_thing.baz": {
Addr: addrs.Resource{
Mode: addrs.ManagedResourceMode,
Type: "test_thing",
Name: "baz",
}.Absolute(addrs.RootModuleInstance),
Instances: map[addrs.InstanceKey]*ResourceInstance{
addrs.IntKey(0): {
Current: &ResourceInstanceObjectSrc{
SchemaVersion: 1,
Status: ObjectReady,
AttrsJSON: []byte(`{"woozles":"confuzles"}`),
},
Deposed: map[DeposedKey]*ResourceInstanceObjectSrc{},
},
},
ProviderConfig: addrs.AbsProviderConfig{
Provider: addrs.NewDefaultProvider("test"),
Module: addrs.RootModule,
},
},
},
},
"module.child": {
Addr: addrs.RootModuleInstance.Child("child", addrs.NoKey),
LocalValues: map[string]cty.Value{},
OutputValues: map[string]*OutputValue{
"pizza": {
Addr: addrs.AbsOutputValue{
Module: addrs.RootModuleInstance.Child("child", addrs.NoKey),
OutputValue: addrs.OutputValue{
Name: "pizza",
},
},
Value: cty.StringVal("hawaiian"),
Sensitive: false,
},
},
Resources: map[string]*Resource{},
},
`module.multi["a"]`: {
Addr: addrs.RootModuleInstance.Child("multi", addrs.StringKey("a")),
LocalValues: map[string]cty.Value{},
OutputValues: map[string]*OutputValue{
"pizza": {
Addr: addrs.AbsOutputValue{
Module: addrs.RootModuleInstance.Child("multi", addrs.StringKey("a")),
OutputValue: addrs.OutputValue{
Name: "pizza",
},
},
Value: cty.StringVal("cheese"),
Sensitive: false,
},
},
Resources: map[string]*Resource{},
},
`module.multi["b"]`: {
Addr: addrs.RootModuleInstance.Child("multi", addrs.StringKey("b")),
LocalValues: map[string]cty.Value{},
OutputValues: map[string]*OutputValue{
"pizza": {
Addr: addrs.AbsOutputValue{
Module: addrs.RootModuleInstance.Child("multi", addrs.StringKey("b")),
OutputValue: addrs.OutputValue{
Name: "pizza",
},
},
Value: cty.StringVal("sausage"),
Sensitive: false,
},
},
Resources: map[string]*Resource{},
},
},
}
{
// Our structure goes deep, so we need to temporarily override the
// deep package settings to ensure that we visit the full structure.
oldDeepDepth := deep.MaxDepth
oldDeepCompareUnexp := deep.CompareUnexportedFields
deep.MaxDepth = 50
deep.CompareUnexportedFields = true
defer func() {
deep.MaxDepth = oldDeepDepth
deep.CompareUnexportedFields = oldDeepCompareUnexp
}()
}
for _, problem := range deep.Equal(state, want) {
t.Error(problem)
}
expectedOutputs := map[string]string{
`module.multi["a"].output.pizza`: "cheese",
`module.multi["b"].output.pizza`: "sausage",
}
for _, o := range state.ModuleOutputs(addrs.RootModuleInstance, addrs.ModuleCall{Name: "multi"}) {
addr := o.Addr.String()
expected := expectedOutputs[addr]
delete(expectedOutputs, addr)
if expected != o.Value.AsString() {
t.Fatalf("expected %q:%q, got %q", addr, expected, o.Value.AsString())
}
}
for addr, o := range expectedOutputs {
t.Fatalf("missing output %q:%q", addr, o)
}
}
func TestStateDeepCopy(t *testing.T) {
state := NewState()
rootModule := state.RootModule()
if rootModule == nil {
t.Errorf("root module is nil; want valid object")
}
rootModule.SetLocalValue("foo", cty.StringVal("foo value"))
rootModule.SetOutputValue("bar", cty.StringVal("bar value"), false)
rootModule.SetOutputValue("secret", cty.StringVal("secret value"), true)
rootModule.SetResourceInstanceCurrent(
addrs.Resource{
Mode: addrs.ManagedResourceMode,
Type: "test_thing",
Name: "baz",
}.Instance(addrs.IntKey(0)),
&ResourceInstanceObjectSrc{
Status: ObjectReady,
SchemaVersion: 1,
AttrsJSON: []byte(`{"woozles":"confuzles"}`),
Private: []byte("private data"),
Dependencies: []addrs.ConfigResource{},
},
addrs.AbsProviderConfig{
Provider: addrs.NewDefaultProvider("test"),
Module: addrs.RootModule,
},
)
rootModule.SetResourceInstanceCurrent(
addrs.Resource{
Mode: addrs.ManagedResourceMode,
Type: "test_thing",
Name: "bar",
}.Instance(addrs.IntKey(0)),
&ResourceInstanceObjectSrc{
Status: ObjectReady,
SchemaVersion: 1,
AttrsJSON: []byte(`{"woozles":"confuzles"}`),
Private: []byte("private data"),
Dependencies: []addrs.ConfigResource{
{
Module: addrs.RootModule,
Resource: addrs.Resource{
Mode: addrs.ManagedResourceMode,
Type: "test_thing",
Name: "baz",
},
},
},
},
addrs.AbsProviderConfig{
Provider: addrs.NewDefaultProvider("test"),
Module: addrs.RootModule,
},
)
childModule := state.EnsureModule(addrs.RootModuleInstance.Child("child", addrs.NoKey))
childModule.SetOutputValue("pizza", cty.StringVal("hawaiian"), false)
stateCopy := state.DeepCopy()
if !state.Equal(stateCopy) {
t.Fatalf("\nexpected:\n%q\ngot:\n%q\n", state, stateCopy)
}
}