opentofu/config/hcl2shim/values_equiv_test.go
Martin Atkins 995042666a config/hcl2shim: ValuesSDKEquivalent float64 comparison of numbers
The SDK uses only the native int and float64 types internally for values
that are specified as being "number" in schema, so for SDK purposes only
a float64 level of precision is significant.

To avoid any weirdness introduced as we shim and un-shim numbers, we'll
reduce floating point numbers to float64 precision before comparing them
to try to mimic the result the SDK itself would've gotten from comparing
its own float64 versions of these values using the Go "==" operator.
2019-01-22 18:45:21 -08:00

430 lines
8.4 KiB
Go

package hcl2shim
import (
"fmt"
"math/big"
"testing"
"github.com/zclconf/go-cty/cty"
)
func TestValuesSDKEquivalent(t *testing.T) {
piBig, _, err := big.ParseFloat("3.14159265358979323846264338327950288419716939937510582097494459", 10, 512, big.ToZero)
if err != nil {
t.Fatal(err)
}
pi64, _ := piBig.Float64()
tests := []struct {
A, B cty.Value
Want bool
}{
// Strings
{
cty.StringVal("hello"),
cty.StringVal("hello"),
true,
},
{
cty.StringVal("hello"),
cty.StringVal("world"),
false,
},
{
cty.StringVal("hello"),
cty.StringVal(""),
false,
},
{
cty.NullVal(cty.String),
cty.StringVal(""),
true,
},
// Numbers
{
cty.NumberIntVal(1),
cty.NumberIntVal(1),
true,
},
{
cty.NumberIntVal(1),
cty.NumberIntVal(2),
false,
},
{
cty.NumberIntVal(1),
cty.Zero,
false,
},
{
cty.NullVal(cty.Number),
cty.Zero,
true,
},
{
cty.NumberVal(piBig),
cty.Zero,
false,
},
{
cty.NumberFloatVal(pi64),
cty.Zero,
false,
},
{
cty.NumberFloatVal(pi64),
cty.NumberVal(piBig),
true,
},
// Bools
{
cty.True,
cty.True,
true,
},
{
cty.True,
cty.False,
false,
},
{
cty.NullVal(cty.Bool),
cty.False,
true,
},
// Mixed primitives
{
cty.StringVal("hello"),
cty.False,
false,
},
{
cty.StringVal(""),
cty.False,
true,
},
{
cty.NumberIntVal(0),
cty.False,
true,
},
{
cty.StringVal(""),
cty.NumberIntVal(0),
true,
},
{
cty.NullVal(cty.Bool),
cty.NullVal(cty.Number),
true,
},
{
cty.StringVal(""),
cty.NullVal(cty.Number),
true,
},
// Lists
{
cty.ListValEmpty(cty.String),
cty.ListValEmpty(cty.String),
true,
},
{
cty.ListValEmpty(cty.String),
cty.NullVal(cty.List(cty.String)),
true,
},
{
cty.ListVal([]cty.Value{cty.StringVal("hello")}),
cty.ListVal([]cty.Value{cty.StringVal("hello"), cty.StringVal("hello")}),
false,
},
{
cty.ListVal([]cty.Value{cty.StringVal("hello")}),
cty.ListValEmpty(cty.String),
false,
},
{
cty.ListVal([]cty.Value{cty.StringVal("hello")}),
cty.ListVal([]cty.Value{cty.StringVal("hello")}),
true,
},
{
cty.ListVal([]cty.Value{cty.StringVal("hello")}),
cty.ListVal([]cty.Value{cty.StringVal("world")}),
false,
},
{
cty.ListVal([]cty.Value{cty.NullVal(cty.String)}),
cty.ListVal([]cty.Value{cty.StringVal("")}),
true,
},
// Tuples
{
cty.EmptyTupleVal,
cty.EmptyTupleVal,
true,
},
{
cty.EmptyTupleVal,
cty.NullVal(cty.EmptyTuple),
true,
},
{
cty.TupleVal([]cty.Value{cty.StringVal("hello")}),
cty.TupleVal([]cty.Value{cty.StringVal("hello"), cty.StringVal("hello")}),
false,
},
{
cty.TupleVal([]cty.Value{cty.StringVal("hello")}),
cty.EmptyTupleVal,
false,
},
{
cty.TupleVal([]cty.Value{cty.StringVal("hello")}),
cty.TupleVal([]cty.Value{cty.StringVal("hello")}),
true,
},
{
cty.TupleVal([]cty.Value{cty.StringVal("hello")}),
cty.TupleVal([]cty.Value{cty.StringVal("world")}),
false,
},
{
cty.TupleVal([]cty.Value{cty.NullVal(cty.String)}),
cty.TupleVal([]cty.Value{cty.StringVal("")}),
true,
},
// Sets
{
cty.SetValEmpty(cty.String),
cty.SetValEmpty(cty.String),
true,
},
{
cty.SetValEmpty(cty.String),
cty.NullVal(cty.Set(cty.String)),
true,
},
{
cty.SetVal([]cty.Value{cty.StringVal("hello")}),
cty.SetValEmpty(cty.String),
false,
},
{
cty.SetVal([]cty.Value{cty.StringVal("hello")}),
cty.SetVal([]cty.Value{cty.StringVal("hello")}),
true,
},
{
cty.SetVal([]cty.Value{cty.StringVal("hello")}),
cty.SetVal([]cty.Value{cty.StringVal("world")}),
false,
},
{
cty.SetVal([]cty.Value{cty.NullVal(cty.String)}),
cty.SetVal([]cty.Value{cty.StringVal("")}),
true,
},
{
cty.SetVal([]cty.Value{
cty.NullVal(cty.String),
cty.StringVal(""),
}),
cty.SetVal([]cty.Value{
cty.NullVal(cty.String),
}),
false, // because the element count is different
},
{
cty.SetVal([]cty.Value{
cty.ObjectVal(map[string]cty.Value{
"a": cty.StringVal(""),
"b": cty.StringVal(""),
}),
cty.ObjectVal(map[string]cty.Value{
"a": cty.NullVal(cty.String),
"b": cty.StringVal(""),
}),
}),
cty.SetVal([]cty.Value{
cty.ObjectVal(map[string]cty.Value{
"a": cty.StringVal(""),
"b": cty.StringVal(""),
}),
cty.ObjectVal(map[string]cty.Value{
"a": cty.StringVal(""),
"b": cty.NullVal(cty.String),
}),
}),
true,
},
{
cty.SetVal([]cty.Value{
cty.ObjectVal(map[string]cty.Value{
"a": cty.StringVal("boop"),
"b": cty.StringVal(""),
}),
cty.ObjectVal(map[string]cty.Value{
"a": cty.NullVal(cty.String),
"b": cty.StringVal(""),
}),
}),
cty.SetVal([]cty.Value{
cty.ObjectVal(map[string]cty.Value{
"a": cty.StringVal("beep"),
"b": cty.StringVal(""),
}),
cty.ObjectVal(map[string]cty.Value{
"a": cty.StringVal(""),
"b": cty.NullVal(cty.String),
}),
}),
false,
},
{
cty.SetVal([]cty.Value{cty.ObjectVal(map[string]cty.Value{
"list": cty.ListValEmpty(cty.String),
"list_block": cty.ListVal([]cty.Value{
cty.ObjectVal(map[string]cty.Value{
"unused": cty.StringVal(""),
}),
}),
})}),
cty.SetVal([]cty.Value{cty.ObjectVal(map[string]cty.Value{
"list": cty.ListValEmpty(cty.String),
"list_block": cty.ListVal([]cty.Value{
cty.ObjectVal(map[string]cty.Value{
"unused": cty.NullVal(cty.String),
}),
}),
})}),
true,
},
// Maps
{
cty.MapValEmpty(cty.String),
cty.MapValEmpty(cty.String),
true,
},
{
cty.MapValEmpty(cty.String),
cty.NullVal(cty.Map(cty.String)),
true,
},
{
cty.MapVal(map[string]cty.Value{"hi": cty.StringVal("hello")}),
cty.MapVal(map[string]cty.Value{"hi": cty.StringVal("hello"), "hey": cty.StringVal("hello")}),
false,
},
{
cty.MapVal(map[string]cty.Value{"hi": cty.StringVal("hello")}),
cty.MapValEmpty(cty.String),
false,
},
{
cty.MapVal(map[string]cty.Value{"hi": cty.StringVal("hello")}),
cty.MapVal(map[string]cty.Value{"hi": cty.StringVal("hello")}),
true,
},
{
cty.MapVal(map[string]cty.Value{"hi": cty.StringVal("hello")}),
cty.MapVal(map[string]cty.Value{"hi": cty.StringVal("world")}),
false,
},
{
cty.MapVal(map[string]cty.Value{"hi": cty.NullVal(cty.String)}),
cty.MapVal(map[string]cty.Value{"hi": cty.StringVal("")}),
true,
},
// Objects
{
cty.EmptyObjectVal,
cty.EmptyObjectVal,
true,
},
{
cty.EmptyObjectVal,
cty.NullVal(cty.EmptyObject),
true,
},
{
cty.ObjectVal(map[string]cty.Value{"hi": cty.StringVal("hello")}),
cty.ObjectVal(map[string]cty.Value{"hi": cty.StringVal("hello"), "hey": cty.StringVal("hello")}),
false,
},
{
cty.ObjectVal(map[string]cty.Value{"hi": cty.StringVal("hello")}),
cty.EmptyObjectVal,
false,
},
{
cty.ObjectVal(map[string]cty.Value{"hi": cty.StringVal("hello")}),
cty.ObjectVal(map[string]cty.Value{"hi": cty.StringVal("hello")}),
true,
},
{
cty.ObjectVal(map[string]cty.Value{"hi": cty.StringVal("hello")}),
cty.ObjectVal(map[string]cty.Value{"hi": cty.StringVal("world")}),
false,
},
{
cty.ObjectVal(map[string]cty.Value{"hi": cty.NullVal(cty.String)}),
cty.ObjectVal(map[string]cty.Value{"hi": cty.StringVal("")}),
true,
},
// Unknown values
{
cty.UnknownVal(cty.String),
cty.UnknownVal(cty.String),
true,
},
{
cty.StringVal("hello"),
cty.UnknownVal(cty.String),
false,
},
{
cty.StringVal(""),
cty.UnknownVal(cty.String),
false,
},
{
cty.NullVal(cty.String),
cty.UnknownVal(cty.String),
false,
},
}
run := func(t *testing.T, a, b cty.Value, want bool) {
got := ValuesSDKEquivalent(a, b)
if got != want {
t.Errorf("wrong result\nfor: %#v ≈ %#v\ngot %#v, but want %#v", a, b, got, want)
}
}
for _, test := range tests {
t.Run(fmt.Sprintf("%#v ≈ %#v", test.A, test.B), func(t *testing.T) {
run(t, test.A, test.B, test.Want)
})
// This function is symmetrical, so we'll also test in reverse so
// we don't need to manually copy all of the test cases. (But this does
// mean that one failure normally becomes two, of course!)
if !test.A.RawEquals(test.B) {
t.Run(fmt.Sprintf("%#v ≈ %#v", test.B, test.A), func(t *testing.T) {
run(t, test.B, test.A, test.Want)
})
}
}
}