pulumi/pkg/backend/display/detailedDiff_test.go
Pat Gavlin ed46891693
Compute nested diffs in translateDetailedDiff. (#2911)
Instead of simply converting a detailed diff entry that indicates an
update to an entire composite value as a simple old/new value diff,
compute the nested diff. This alllows us to render a per-element diff
for the nested object rather than simply displaying the new and the old
composite values.

This is necessary in order to improve diff rendering once
pulumi/pulumi-terraform#403 has been rolled out.
2019-07-08 16:33:21 -07:00

798 lines
19 KiB
Go

package display
import (
"testing"
"github.com/pulumi/pulumi/pkg/engine"
"github.com/pulumi/pulumi/pkg/resource"
"github.com/pulumi/pulumi/pkg/resource/plugin"
"github.com/stretchr/testify/assert"
)
func TestParseDiffPath(t *testing.T) {
cases := []struct {
path string
elements []interface{}
}{
{
"root",
[]interface{}{"root"},
},
{
"root.nested",
[]interface{}{"root", "nested"},
},
{
`root["nested"]`,
[]interface{}{"root", "nested"},
},
{
"root.double.nest",
[]interface{}{"root", "double", "nest"},
},
{
`root["double"].nest`,
[]interface{}{"root", "double", "nest"},
},
{
`root["double"]["nest"]`,
[]interface{}{"root", "double", "nest"},
},
{
"root.array[0]",
[]interface{}{"root", "array", 0},
},
{
"root.array[100]",
[]interface{}{"root", "array", 100},
},
{
"root.array[0].nested",
[]interface{}{"root", "array", 0, "nested"},
},
{
"root.array[0][1].nested",
[]interface{}{"root", "array", 0, 1, "nested"},
},
{
"root.nested.array[0].double[1]",
[]interface{}{"root", "nested", "array", 0, "double", 1},
},
{
`root["key with \"escaped\" quotes"]`,
[]interface{}{"root", `key with "escaped" quotes`},
},
{
`root["key with a ."]`,
[]interface{}{"root", "key with a ."},
},
{
`["root key with \"escaped\" quotes"].nested`,
[]interface{}{`root key with "escaped" quotes`, "nested"},
},
{
`["root key with a ."][100]`,
[]interface{}{"root key with a .", 100},
},
}
for _, c := range cases {
elements, err := parseDiffPath(c.path)
assert.NoError(t, err)
assert.Equal(t, c.elements, elements)
}
}
func TestTranslateDetailedDiff(t *testing.T) {
var (
A = plugin.PropertyDiff{Kind: plugin.DiffAdd}
D = plugin.PropertyDiff{Kind: plugin.DiffDelete}
U = plugin.PropertyDiff{Kind: plugin.DiffUpdate}
)
cases := []struct {
state map[string]interface{}
oldInputs map[string]interface{}
inputs map[string]interface{}
detailedDiff map[string]plugin.PropertyDiff
expected *resource.ObjectDiff
}{
{
state: map[string]interface{}{
"foo": 42,
},
inputs: map[string]interface{}{
"foo": 24,
},
detailedDiff: map[string]plugin.PropertyDiff{
"foo": U,
},
expected: &resource.ObjectDiff{
Adds: resource.PropertyMap{},
Deletes: resource.PropertyMap{},
Sames: resource.PropertyMap{},
Updates: map[resource.PropertyKey]resource.ValueDiff{
"foo": {
Old: resource.NewNumberProperty(42),
New: resource.NewNumberProperty(24),
},
},
},
},
{
state: map[string]interface{}{
"foo": 42,
},
inputs: map[string]interface{}{
"foo": 42,
},
detailedDiff: map[string]plugin.PropertyDiff{
"foo": U,
},
expected: &resource.ObjectDiff{
Adds: resource.PropertyMap{},
Deletes: resource.PropertyMap{},
Sames: resource.PropertyMap{},
Updates: map[resource.PropertyKey]resource.ValueDiff{
"foo": {
Old: resource.NewNumberProperty(42),
New: resource.NewNumberProperty(42),
},
},
},
},
{
state: map[string]interface{}{
"foo": 42,
"bar": "hello",
},
inputs: map[string]interface{}{
"foo": 24,
"bar": "hello",
},
detailedDiff: map[string]plugin.PropertyDiff{
"foo": U,
},
expected: &resource.ObjectDiff{
Adds: resource.PropertyMap{},
Deletes: resource.PropertyMap{},
Sames: resource.PropertyMap{},
Updates: map[resource.PropertyKey]resource.ValueDiff{
"foo": {
Old: resource.NewNumberProperty(42),
New: resource.NewNumberProperty(24),
},
},
},
},
{
state: map[string]interface{}{
"foo": 42,
"bar": "hello",
},
inputs: map[string]interface{}{
"foo": 24,
"bar": "world",
},
detailedDiff: map[string]plugin.PropertyDiff{
"foo": U,
},
expected: &resource.ObjectDiff{
Adds: resource.PropertyMap{},
Deletes: resource.PropertyMap{},
Sames: resource.PropertyMap{},
Updates: map[resource.PropertyKey]resource.ValueDiff{
"foo": {
Old: resource.NewNumberProperty(42),
New: resource.NewNumberProperty(24),
},
},
},
},
{
state: map[string]interface{}{},
inputs: map[string]interface{}{
"foo": 24,
},
detailedDiff: map[string]plugin.PropertyDiff{
"foo": A,
},
expected: &resource.ObjectDiff{
Adds: resource.PropertyMap{
"foo": resource.NewNumberProperty(24),
},
Deletes: resource.PropertyMap{},
Sames: resource.PropertyMap{},
Updates: map[resource.PropertyKey]resource.ValueDiff{},
},
},
{
state: map[string]interface{}{
"foo": 24,
},
inputs: map[string]interface{}{},
detailedDiff: map[string]plugin.PropertyDiff{
"foo": D,
},
expected: &resource.ObjectDiff{
Adds: resource.PropertyMap{},
Deletes: resource.PropertyMap{
"foo": resource.NewNumberProperty(24),
},
Sames: resource.PropertyMap{},
Updates: map[resource.PropertyKey]resource.ValueDiff{},
},
},
{
state: map[string]interface{}{
"foo": 24,
},
oldInputs: map[string]interface{}{
"foo": 42,
},
inputs: map[string]interface{}{},
detailedDiff: map[string]plugin.PropertyDiff{
"foo": {
Kind: plugin.DiffDelete,
InputDiff: true,
},
},
expected: &resource.ObjectDiff{
Adds: resource.PropertyMap{},
Deletes: resource.PropertyMap{
"foo": resource.NewNumberProperty(42),
},
Sames: resource.PropertyMap{},
Updates: map[resource.PropertyKey]resource.ValueDiff{},
},
},
{
state: map[string]interface{}{
"foo": []interface{}{
"bar",
"baz",
},
},
inputs: map[string]interface{}{
"foo": []interface{}{
"bar",
"qux",
},
},
detailedDiff: map[string]plugin.PropertyDiff{
"foo[1]": U,
},
expected: &resource.ObjectDiff{
Adds: resource.PropertyMap{},
Deletes: resource.PropertyMap{},
Sames: resource.PropertyMap{},
Updates: map[resource.PropertyKey]resource.ValueDiff{
"foo": {
Array: &resource.ArrayDiff{
Adds: map[int]resource.PropertyValue{},
Deletes: map[int]resource.PropertyValue{},
Sames: map[int]resource.PropertyValue{},
Updates: map[int]resource.ValueDiff{
1: {
Old: resource.NewStringProperty("baz"),
New: resource.NewStringProperty("qux"),
},
},
},
},
},
},
},
{
state: map[string]interface{}{
"foo": []interface{}{
"bar",
"baz",
},
},
inputs: map[string]interface{}{
"foo": []interface{}{
"bar",
"qux",
},
},
detailedDiff: map[string]plugin.PropertyDiff{
"foo": U,
},
expected: &resource.ObjectDiff{
Adds: resource.PropertyMap{},
Deletes: resource.PropertyMap{},
Sames: resource.PropertyMap{},
Updates: map[resource.PropertyKey]resource.ValueDiff{
"foo": {
Old: resource.NewPropertyValue([]interface{}{
"bar",
"baz",
}),
New: resource.NewPropertyValue([]interface{}{
"bar",
"qux",
}),
Array: &resource.ArrayDiff{
Adds: map[int]resource.PropertyValue{},
Deletes: map[int]resource.PropertyValue{},
Sames: map[int]resource.PropertyValue{
0: resource.NewPropertyValue("bar"),
},
Updates: map[int]resource.ValueDiff{
1: {
Old: resource.NewStringProperty("baz"),
New: resource.NewStringProperty("qux"),
},
},
},
},
},
},
},
{
state: map[string]interface{}{
"foo": []interface{}{
"bar",
},
},
inputs: map[string]interface{}{
"foo": []interface{}{
"bar",
"baz",
},
},
detailedDiff: map[string]plugin.PropertyDiff{
"foo[1]": A,
},
expected: &resource.ObjectDiff{
Adds: resource.PropertyMap{},
Deletes: resource.PropertyMap{},
Sames: resource.PropertyMap{},
Updates: map[resource.PropertyKey]resource.ValueDiff{
"foo": {
Array: &resource.ArrayDiff{
Adds: map[int]resource.PropertyValue{
1: resource.NewStringProperty("baz"),
},
Deletes: map[int]resource.PropertyValue{},
Sames: map[int]resource.PropertyValue{},
Updates: map[int]resource.ValueDiff{},
},
},
},
},
},
{
state: map[string]interface{}{
"foo": []interface{}{
"bar",
"baz",
},
},
inputs: map[string]interface{}{
"foo": []interface{}{
"bar",
},
},
detailedDiff: map[string]plugin.PropertyDiff{
"foo[1]": D,
},
expected: &resource.ObjectDiff{
Adds: resource.PropertyMap{},
Deletes: resource.PropertyMap{},
Sames: resource.PropertyMap{},
Updates: map[resource.PropertyKey]resource.ValueDiff{
"foo": {
Array: &resource.ArrayDiff{
Adds: map[int]resource.PropertyValue{},
Deletes: map[int]resource.PropertyValue{
1: resource.NewStringProperty("baz"),
},
Sames: map[int]resource.PropertyValue{},
Updates: map[int]resource.ValueDiff{},
},
},
},
},
},
{
state: map[string]interface{}{
"foo": []interface{}{
"bar",
"baz",
},
},
inputs: map[string]interface{}{
"foo": []interface{}{
"bar",
"qux",
},
},
detailedDiff: map[string]plugin.PropertyDiff{
"foo[100]": U,
},
expected: &resource.ObjectDiff{
Adds: resource.PropertyMap{},
Deletes: resource.PropertyMap{},
Sames: resource.PropertyMap{},
Updates: map[resource.PropertyKey]resource.ValueDiff{
"foo": {
Array: &resource.ArrayDiff{
Adds: map[int]resource.PropertyValue{},
Deletes: map[int]resource.PropertyValue{},
Sames: map[int]resource.PropertyValue{},
Updates: map[int]resource.ValueDiff{
100: {
Old: resource.PropertyValue{},
New: resource.PropertyValue{},
},
},
},
},
},
},
},
{
state: map[string]interface{}{
"foo": []interface{}{
"bar",
"baz",
},
},
inputs: map[string]interface{}{
"foo": []interface{}{
"bar",
"qux",
},
},
detailedDiff: map[string]plugin.PropertyDiff{
"foo[100][200]": U,
},
expected: &resource.ObjectDiff{
Adds: resource.PropertyMap{},
Deletes: resource.PropertyMap{},
Sames: resource.PropertyMap{},
Updates: map[resource.PropertyKey]resource.ValueDiff{
"foo": {
Array: &resource.ArrayDiff{
Adds: map[int]resource.PropertyValue{},
Deletes: map[int]resource.PropertyValue{},
Sames: map[int]resource.PropertyValue{},
Updates: map[int]resource.ValueDiff{
100: {
Array: &resource.ArrayDiff{
Adds: map[int]resource.PropertyValue{},
Deletes: map[int]resource.PropertyValue{},
Sames: map[int]resource.PropertyValue{},
Updates: map[int]resource.ValueDiff{
200: {
Old: resource.PropertyValue{},
New: resource.PropertyValue{},
},
},
},
},
},
},
},
},
},
},
{
state: map[string]interface{}{
"foo": []interface{}{
map[string]interface{}{
"baz": 42,
},
},
},
inputs: map[string]interface{}{
"foo": []interface{}{},
},
detailedDiff: map[string]plugin.PropertyDiff{
"foo[0].baz": D,
},
expected: &resource.ObjectDiff{
Adds: resource.PropertyMap{},
Deletes: resource.PropertyMap{},
Sames: resource.PropertyMap{},
Updates: map[resource.PropertyKey]resource.ValueDiff{
"foo": {
Array: &resource.ArrayDiff{
Adds: map[int]resource.PropertyValue{},
Deletes: map[int]resource.PropertyValue{
0: resource.NewObjectProperty(resource.PropertyMap{
"baz": resource.NewNumberProperty(42),
}),
},
Sames: map[int]resource.PropertyValue{},
Updates: map[int]resource.ValueDiff{},
},
},
},
},
},
{
state: map[string]interface{}{
"foo": map[string]interface{}{
"bar": "baz",
"qux": "zed",
},
},
inputs: map[string]interface{}{
"foo": map[string]interface{}{
"bar": "baz",
"qux": "alpha",
},
},
detailedDiff: map[string]plugin.PropertyDiff{
"foo.qux": U,
},
expected: &resource.ObjectDiff{
Adds: resource.PropertyMap{},
Deletes: resource.PropertyMap{},
Sames: resource.PropertyMap{},
Updates: map[resource.PropertyKey]resource.ValueDiff{
"foo": {
Object: &resource.ObjectDiff{
Adds: resource.PropertyMap{},
Deletes: resource.PropertyMap{},
Sames: resource.PropertyMap{},
Updates: map[resource.PropertyKey]resource.ValueDiff{
"qux": {
Old: resource.NewStringProperty("zed"),
New: resource.NewStringProperty("alpha"),
},
},
},
},
},
},
},
{
state: map[string]interface{}{
"foo": map[string]interface{}{
"bar": "baz",
"qux": "zed",
},
},
inputs: map[string]interface{}{
"foo": map[string]interface{}{
"bar": "baz",
"qux": "alpha",
},
},
detailedDiff: map[string]plugin.PropertyDiff{
"foo": U,
},
expected: &resource.ObjectDiff{
Adds: resource.PropertyMap{},
Deletes: resource.PropertyMap{},
Sames: resource.PropertyMap{},
Updates: map[resource.PropertyKey]resource.ValueDiff{
"foo": {
Old: resource.NewPropertyValue(map[string]interface{}{
"bar": "baz",
"qux": "zed",
}),
New: resource.NewPropertyValue(map[string]interface{}{
"bar": "baz",
"qux": "alpha",
}),
Object: &resource.ObjectDiff{
Adds: resource.PropertyMap{},
Deletes: resource.PropertyMap{},
Sames: resource.PropertyMap{
"bar": resource.NewPropertyValue("baz"),
},
Updates: map[resource.PropertyKey]resource.ValueDiff{
"qux": {
Old: resource.NewStringProperty("zed"),
New: resource.NewStringProperty("alpha"),
},
},
},
},
},
},
},
{
state: map[string]interface{}{
"foo": map[string]interface{}{
"bar": "baz",
},
},
inputs: map[string]interface{}{
"foo": map[string]interface{}{
"bar": "baz",
"qux": "alpha",
},
},
detailedDiff: map[string]plugin.PropertyDiff{
"foo.qux": A,
},
expected: &resource.ObjectDiff{
Adds: resource.PropertyMap{},
Deletes: resource.PropertyMap{},
Sames: resource.PropertyMap{},
Updates: map[resource.PropertyKey]resource.ValueDiff{
"foo": {
Object: &resource.ObjectDiff{
Adds: resource.PropertyMap{
"qux": resource.NewStringProperty("alpha"),
},
Deletes: resource.PropertyMap{},
Sames: resource.PropertyMap{},
Updates: map[resource.PropertyKey]resource.ValueDiff{},
},
},
},
},
},
{
state: map[string]interface{}{
"foo": map[string]interface{}{
"bar": "baz",
"qux": "zed",
},
},
inputs: map[string]interface{}{
"foo": map[string]interface{}{
"bar": "baz",
},
},
detailedDiff: map[string]plugin.PropertyDiff{
"foo.qux": D,
},
expected: &resource.ObjectDiff{
Adds: resource.PropertyMap{},
Deletes: resource.PropertyMap{},
Sames: resource.PropertyMap{},
Updates: map[resource.PropertyKey]resource.ValueDiff{
"foo": {
Object: &resource.ObjectDiff{
Adds: resource.PropertyMap{},
Deletes: resource.PropertyMap{
"qux": resource.NewStringProperty("zed"),
},
Sames: resource.PropertyMap{},
Updates: map[resource.PropertyKey]resource.ValueDiff{},
},
},
},
},
},
{
state: map[string]interface{}{
"foo": map[string]interface{}{
"bar": "baz",
"qux": "zed",
},
},
inputs: map[string]interface{}{
"foo": map[string]interface{}{
"bar": "baz",
"qux": "alpha",
},
},
detailedDiff: map[string]plugin.PropertyDiff{
"foo.missing": U,
},
expected: &resource.ObjectDiff{
Adds: resource.PropertyMap{},
Deletes: resource.PropertyMap{},
Sames: resource.PropertyMap{},
Updates: map[resource.PropertyKey]resource.ValueDiff{
"foo": {
Object: &resource.ObjectDiff{
Adds: resource.PropertyMap{},
Deletes: resource.PropertyMap{},
Sames: resource.PropertyMap{},
Updates: map[resource.PropertyKey]resource.ValueDiff{
"missing": {
Old: resource.PropertyValue{},
New: resource.PropertyValue{},
},
},
},
},
},
},
},
{
state: map[string]interface{}{
"foo": map[string]interface{}{
"bar": "baz",
"qux": "zed",
},
},
inputs: map[string]interface{}{
"foo": map[string]interface{}{
"bar": "baz",
"qux": "alpha",
},
},
detailedDiff: map[string]plugin.PropertyDiff{
"foo.nested.missing": U,
},
expected: &resource.ObjectDiff{
Adds: resource.PropertyMap{},
Deletes: resource.PropertyMap{},
Sames: resource.PropertyMap{},
Updates: map[resource.PropertyKey]resource.ValueDiff{
"foo": {
Object: &resource.ObjectDiff{
Adds: resource.PropertyMap{},
Deletes: resource.PropertyMap{},
Sames: resource.PropertyMap{},
Updates: map[resource.PropertyKey]resource.ValueDiff{
"nested": {
Object: &resource.ObjectDiff{
Adds: resource.PropertyMap{},
Deletes: resource.PropertyMap{},
Sames: resource.PropertyMap{},
Updates: map[resource.PropertyKey]resource.ValueDiff{
"missing": {
Old: resource.PropertyValue{},
New: resource.PropertyValue{},
},
},
},
},
},
},
},
},
},
},
{
state: map[string]interface{}{
"foo": []interface{}{
map[string]interface{}{
"baz": 42,
},
},
},
inputs: map[string]interface{}{
"foo": []interface{}{},
},
detailedDiff: map[string]plugin.PropertyDiff{
"foo[0].baz": D,
},
expected: &resource.ObjectDiff{
Adds: resource.PropertyMap{},
Deletes: resource.PropertyMap{},
Sames: resource.PropertyMap{},
Updates: map[resource.PropertyKey]resource.ValueDiff{
"foo": {
Array: &resource.ArrayDiff{
Adds: map[int]resource.PropertyValue{},
Deletes: map[int]resource.PropertyValue{
0: resource.NewObjectProperty(resource.PropertyMap{
"baz": resource.NewNumberProperty(42),
}),
},
Sames: map[int]resource.PropertyValue{},
Updates: map[int]resource.ValueDiff{},
},
},
},
},
},
}
for _, c := range cases {
oldInputs := resource.NewPropertyMapFromMap(c.oldInputs)
state := resource.NewPropertyMapFromMap(c.state)
inputs := resource.NewPropertyMapFromMap(c.inputs)
diff := translateDetailedDiff(engine.StepEventMetadata{
Old: &engine.StepEventStateMetadata{Inputs: oldInputs, Outputs: state},
New: &engine.StepEventStateMetadata{Inputs: inputs},
DetailedDiff: c.detailedDiff,
})
assert.Equal(t, c.expected, diff)
}
}