From 995042666afdfd652b79b942204d45e0132a7d0a Mon Sep 17 00:00:00 2001 From: Martin Atkins Date: Tue, 22 Jan 2019 18:18:32 -0800 Subject: [PATCH] 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. --- config/hcl2shim/values_equiv.go | 37 ++++++++++++++++++++++++++++ config/hcl2shim/values_equiv_test.go | 22 +++++++++++++++++ 2 files changed, 59 insertions(+) diff --git a/config/hcl2shim/values_equiv.go b/config/hcl2shim/values_equiv.go index ab437de80..92f0213d7 100644 --- a/config/hcl2shim/values_equiv.go +++ b/config/hcl2shim/values_equiv.go @@ -60,6 +60,8 @@ func ValuesSDKEquivalent(a, b cty.Value) bool { return valuesSDKEquivalentMappings(a, b) case aTy.IsObjectType() && bTy.IsObjectType(): return valuesSDKEquivalentMappings(a, b) + case aTy == cty.Number && bTy == cty.Number: + return valuesSDKEquivalentNumbers(a, b) default: // We've now covered all the interesting cases, so anything that falls // down here cannot be equivalent. @@ -175,3 +177,38 @@ func valuesSDKEquivalentMappings(a, b cty.Value) bool { } return true } + +// valuesSDKEquivalentNumbers decides equivalence for two number values based +// on the fact that the SDK uses int and float64 representations while +// cty (and thus Terraform Core) uses big.Float, and so we expect to lose +// precision in the round-trip. +// +// This does _not_ attempt to allow for an epsilon difference that may be +// caused by accumulated innacuracy in a float calculation, under the +// expectation that providers generally do not actually do compuations on +// floats and instead just pass string representations of them on verbatim +// to remote APIs. A remote API _itself_ may introduce inaccuracy, but that's +// a problem for the provider itself to deal with, based on its knowledge of +// the remote system, e.g. using DiffSuppressFunc. +func valuesSDKEquivalentNumbers(a, b cty.Value) bool { + if a.RawEquals(b) { + return true // easy + } + + af := a.AsBigFloat() + bf := b.AsBigFloat() + + if af.IsInt() != bf.IsInt() { + return false + } + if af.IsInt() && bf.IsInt() { + return false // a.RawEquals(b) test above is good enough for integers + } + + // The SDK supports only int and float64, so if it's not an integer + // we know that only a float64-level of precision can possibly be + // significant. + af64, _ := af.Float64() + bf64, _ := bf.Float64() + return af64 == bf64 +} diff --git a/config/hcl2shim/values_equiv_test.go b/config/hcl2shim/values_equiv_test.go index 443eb6b7d..aff01a9e3 100644 --- a/config/hcl2shim/values_equiv_test.go +++ b/config/hcl2shim/values_equiv_test.go @@ -2,12 +2,19 @@ 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 @@ -55,6 +62,21 @@ func TestValuesSDKEquivalent(t *testing.T) { cty.Zero, true, }, + { + cty.NumberVal(piBig), + cty.Zero, + false, + }, + { + cty.NumberFloatVal(pi64), + cty.Zero, + false, + }, + { + cty.NumberFloatVal(pi64), + cty.NumberVal(piBig), + true, + }, // Bools {