Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Start migration from float64 to big.Float #232

Open
wants to merge 38 commits into
base: master
Choose a base branch
from
Open
Show file tree
Hide file tree
Changes from all commits
Commits
Show all changes
38 commits
Select commit Hold shift + click to select a range
68a85b9
WIP: Migration from float64 to big.Float
angshumanHalder Jan 24, 2023
9de4544
resolved conflicts
angshumanHalder Jan 24, 2023
01eb6bf
resolved conflicts
angshumanHalder Jan 25, 2023
f76c5cf
clean up - replace n.chain with opChain
angshumanHalder Jan 25, 2023
e2b9c22
replaced json.Unmarshal with json.Decoder
angshumanHalder Feb 4, 2023
2b1790e
resolved conflicts
angshumanHalder Feb 4, 2023
06aabdd
fix: failing test cases on array_test.go
angshumanHalder Feb 7, 2023
56ab052
fix: failing test cases on canon_test.go
angshumanHalder Feb 11, 2023
5aaf7a7
resolved merge conflicts
angshumanHalder Feb 11, 2023
f399363
WIP: Fixing test cases
angshumanHalder Feb 22, 2023
fc335cd
resolved conflicts
angshumanHalder Feb 23, 2023
38c7870
WIP: fixing test cases
angshumanHalder Feb 23, 2023
18dca91
WIP: fixing test cases
angshumanHalder Mar 3, 2023
dd855ea
fixed test cases
angshumanHalder Mar 4, 2023
f4cd44f
resolved conflicts
angshumanHalder Mar 4, 2023
5663666
fix lint issue (lll)
angshumanHalder Mar 4, 2023
85de1f6
fix lint issue on array_test, canon
angshumanHalder Mar 4, 2023
353fcae
fix: undefined reflect.Pointer
angshumanHalder Mar 4, 2023
5b192d0
uncommented NaN tests on number_test.go
angshumanHalder Mar 4, 2023
d662983
uncommented empty test in TestObject_IsEqual on object_test.go
angshumanHalder Mar 4, 2023
6f64208
conversion of json.Numbers to float64/big.Float
angshumanHalder Apr 1, 2023
17b813e
test: fixed test cases in array_test
angshumanHalder Apr 1, 2023
8916289
test: fixed remaining tests in TestArray_Decode
angshumanHalder Apr 2, 2023
ce890c0
fix: test cases
angshumanHalder Apr 6, 2023
f26bea6
fix: test cases
angshumanHalder Apr 7, 2023
a7d956a
Merge: master
angshumanHalder Apr 7, 2023
d0f425b
fix: number test case in canon_test.go
angshumanHalder Apr 7, 2023
4256c48
fix: lint errors exhaustive case on convertJSONNumberToFloatOrBigFloat
angshumanHalder Apr 7, 2023
fdcf15a
Merge branch 'master' of https://github.com/gavv/httpexpect into 191-…
angshumanHalder Apr 7, 2023
05d0620
fix: test case on e2e
angshumanHalder Apr 7, 2023
d482f3c
resolve conflicts
angshumanHalder Apr 9, 2023
7ba40dd
formatting fix and remove commented code
angshumanHalder Apr 13, 2023
d46c59c
Merge: resolved conflicts
angshumanHalder Apr 13, 2023
5fc1eda
reset test cases
angshumanHalder Apr 16, 2023
703d2fa
reset test cases and code formatting
angshumanHalder Apr 16, 2023
bfbf826
replace websocket json decoding with jsonDecode
angshumanHalder Apr 16, 2023
81f2e05
fix formatting in expect_test.go
angshumanHalder Apr 16, 2023
ca0e087
fix spacing issue expect_test.go
angshumanHalder Apr 16, 2023
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Jump to
Jump to file
Failed to load files.
Diff view
Diff view
9 changes: 8 additions & 1 deletion array.go
Original file line number Diff line number Diff line change
@@ -1,6 +1,7 @@
package httpexpect

import (
"encoding/json"
"errors"
"fmt"
"reflect"
Expand Down Expand Up @@ -1745,7 +1746,7 @@ func builtinComparator(opChain *chain, array []interface{}) func(x, y *Value) bo
var prev interface{}
for index, curr := range array {
switch curr.(type) {
case bool, float64, string, nil:
case bool, float64, string, nil, json.Number:
// ok, do nothing

default:
Expand Down Expand Up @@ -1817,6 +1818,12 @@ func builtinComparator(opChain *chain, array []interface{}) func(x, y *Value) bo
yVal := y.Raw().(string)
return xVal < yVal
}
case json.Number:
return func(x, y *Value) bool {
xVal := x.Raw().(json.Number)
yVal := y.Raw().(json.Number)
return xVal < yVal
}
case nil:
return func(x, y *Value) bool {
// `nil` is never less than `nil`
Expand Down
263 changes: 246 additions & 17 deletions canon.go
Original file line number Diff line number Diff line change
@@ -1,13 +1,17 @@
package httpexpect

import (
"bytes"
"encoding/json"
"errors"
"fmt"
"io"
"math/big"
"reflect"
"strconv"
)

func canonNumber(opChain *chain, in interface{}) (out float64, ok bool) {
func canonNumber(opChain *chain, in interface{}) (out *big.Float, ok bool) {
ok = true
defer func() {
if err := recover(); err != nil {
Expand All @@ -22,10 +26,79 @@ func canonNumber(opChain *chain, in interface{}) (out float64, ok bool) {
ok = false
}
}()
out = reflect.ValueOf(in).Convert(reflect.TypeOf(float64(0))).Float()

if in != in {
out, ok = nil, false
return
}

out, ok = canonNumberConvert(in)
if !ok {
opChain.fail(AssertionFailure{
Type: AssertValid,
Actual: &AssertionValue{in},
Errors: []error{
errors.New("expected: valid number"),
},
})
ok = false
}
return
}

func canonNumberConvert(in interface{}) (out *big.Float, ok bool) {
value := reflect.ValueOf(in)
switch in := in.(type) {
case big.Int:
val := in
return big.NewFloat(0).SetInt(&val), true
case big.Float:
return &in, true
case json.Number:
data := in.String()
num, ok := big.NewFloat(0).SetString(data)
return num, ok
default:
return canonConvertNumberNative(value, in)
}
}

func canonConvertNumberNative(
value reflect.Value,
in interface{},
) (out *big.Float, ok bool) {
t := reflect.TypeOf(in).Kind()
switch t {
case reflect.Float64, reflect.Float32:
float := value.Float()
return big.NewFloat(float), true
case reflect.Int8, reflect.Int16, reflect.Int32, reflect.Int64, reflect.Int:
int := value.Int()
return big.NewFloat(0).SetInt64(int), true
case reflect.Uint8, reflect.Uint16, reflect.Uint32, reflect.Uint64, reflect.Uint:
int := value.Uint()
return big.NewFloat(0).SetUint64(int), true
case reflect.Invalid,
reflect.Bool,
reflect.Uintptr,
reflect.Complex64,
reflect.Complex128,
reflect.Array,
reflect.Chan,
reflect.Func,
reflect.Interface,
reflect.Map,
reflect.Slice,
reflect.String,
reflect.Struct,
reflect.Ptr,
reflect.UnsafePointer:
return big.NewFloat(0), false
default:
return big.NewFloat(0), false
}
}

func canonArray(opChain *chain, in interface{}) ([]interface{}, bool) {
var out []interface{}
data, ok := canonValue(opChain, in)
Expand Down Expand Up @@ -77,17 +150,9 @@ func canonValue(opChain *chain, in interface{}) (interface{}, bool) {
}

var out interface{}
if err := json.Unmarshal(b, &out); err != nil {
opChain.fail(AssertionFailure{
Type: AssertValid,
Actual: &AssertionValue{in},
Errors: []error{
errors.New("expected: unmarshalable value"),
err,
},
})
return nil, false
}

jsonDecode(opChain, b, &out)
out = convertJSONNumberToFloatOrBigFloat(out)

return out, true
}
Expand Down Expand Up @@ -115,14 +180,178 @@ func canonDecode(opChain *chain, value interface{}, target interface{}) {
return
}

if err := json.Unmarshal(b, target); err != nil {
jsonDecode(opChain, b, target)
switch t := target.(type) {
case *interface{}:
*t = convertJSONNumberToFloatOrBigFloat(*t)
case *[]interface{}:
for i, val := range *t {
(*t)[i] = convertJSONNumberToFloatOrBigFloat(val)
}
case *map[string]interface{}:
for key, val := range *t {
(*t)[key] = convertJSONNumberToFloatOrBigFloat(val)
}
default:
v := reflect.ValueOf(t)
if v.Kind() == reflect.Ptr {
v = v.Elem()
}
if v.Kind() == reflect.Struct {
for i := 0; i < v.NumField(); i++ {
field := v.Field(i)
if field.CanInterface() {
val := field.Interface()
newVal := convertJSONNumberToFloatOrBigFloat(val)
field.Set(reflect.ValueOf(newVal))
}
}
}
}
}

func jsonDecode(opChain *chain, b []byte, target interface{}) {
reader := bytes.NewReader(b)
dec := json.NewDecoder(reader)
dec.UseNumber()

for {
if err := dec.Decode(target); err == io.EOF || target == nil {
break
} else if err != nil {
opChain.fail(AssertionFailure{
Type: AssertValid,
Actual: &AssertionValue{target},
Errors: []error{
errors.New("expected: value can be decoded into target argument"),
},
})
return
}
}
}

func canonNumberDecode(opChain *chain, value big.Float, target interface{}) {
if target == nil {
opChain.fail(AssertionFailure{
Type: AssertValid,
Actual: &AssertionValue{target},
Type: AssertUsage,
Errors: []error{
errors.New("expected: value can be unmarshaled into target argument"),
errors.New("unexpected nil target argument"),
},
})
return
}
t := reflect.Indirect(reflect.ValueOf(target)).Kind()
switch t {
case reflect.Float64, reflect.Interface:
f, _ := value.Float64()
canonDecode(opChain, f, target)
case reflect.Float32:
f, _ := value.Float32()
canonDecode(opChain, f, target)
case reflect.Int8, reflect.Int16, reflect.Int32, reflect.Int64, reflect.Int:
i, _ := value.Int64()
canonDecode(opChain, i, target)
case reflect.Uint8, reflect.Uint16, reflect.Uint32, reflect.Uint64, reflect.Uint:
i, _ := value.Int64()
canonDecode(opChain, i, target)
case reflect.Invalid,
reflect.Bool,
reflect.Uintptr,
reflect.Complex64,
reflect.Complex128,
reflect.Array,
reflect.Chan,
reflect.Func,
reflect.Map,
reflect.Slice,
reflect.String,
reflect.Struct,
reflect.Ptr,
reflect.UnsafePointer:
canonDecode(opChain, value, target)
default:
canonDecode(opChain, value, target)
}
}

func convertJSONNumberToFloatOrBigFloat(data interface{}) interface{} {
v := reflect.ValueOf(data)
switch v.Kind() {
case reflect.Map:
for _, key := range v.MapKeys() {
val := v.MapIndex(key)
if val.IsNil() {
continue
}
newVal := convertJSONNumberToFloatOrBigFloat(val.Interface())
v.SetMapIndex(key, reflect.ValueOf(newVal))
}
return v.Interface()
case reflect.Slice, reflect.Array:
for i := 0; i < v.Len(); i++ {
if v.Index(i).IsNil() {
continue
}
newVal := convertJSONNumberToFloatOrBigFloat(v.Index(i).Interface())
v.Index(i).Set(reflect.ValueOf(newVal))
}
return v.Interface()
case reflect.Ptr:
if v.IsNil() {
return nil
}
newVal := convertJSONNumberToFloatOrBigFloat(v.Elem().Interface())
newV := reflect.New(v.Type().Elem())
newV.Elem().Set(reflect.ValueOf(newVal))
return newV.Interface()
case reflect.Interface:
newVal := convertJSONNumberToFloatOrBigFloat(v.Elem().Interface())
return reflect.ValueOf(newVal).Interface()
case reflect.String:
if jsonNum, ok := v.Interface().(json.Number); ok {
if hasPrecisionLoss(jsonNum) {
newVal := big.NewFloat(0)
newVal, _ = newVal.SetString(v.String())
return newVal
}
newVal, _ := strconv.ParseFloat(v.String(), 64)
return newVal
}
return data
case reflect.Struct:
newVal := reflect.New(v.Type()).Elem()
for i := 0; i < v.NumField(); i++ {
field := v.Field(i)
if !field.CanInterface() {
continue
}
newField := convertJSONNumberToFloatOrBigFloat(field.Interface())
newVal.Field(i).Set(reflect.ValueOf(newField))
}
return newVal.Interface()
case reflect.Invalid,
reflect.Bool,
reflect.Uintptr,
reflect.Complex64,
reflect.Complex128,
reflect.Chan,
reflect.Func,
reflect.Int,
reflect.Int8,
reflect.Int16,
reflect.Int32,
reflect.Int64,
reflect.Uint,
reflect.Uint8,
reflect.Uint16,
reflect.Uint32,
reflect.Uint64,
reflect.Float32,
reflect.Float64,
reflect.UnsafePointer:
return data
default:
return data
}
}
7 changes: 4 additions & 3 deletions canon_test.go
Original file line number Diff line number Diff line change
@@ -1,6 +1,7 @@
package httpexpect

import (
"math/big"
"testing"

"github.com/stretchr/testify/assert"
Expand All @@ -18,19 +19,19 @@ func TestCanon_Number(t *testing.T) {
{
name: "input is int",
in: 123,
out: 123.0,
out: big.NewFloat(123.0),
result: success,
},
{
name: "input is float",
in: 123.0,
out: 123.0,
out: big.NewFloat(123.0),
result: success,
},
{
name: "input is myInt",
in: myInt(123),
out: 123.0,
out: big.NewFloat(123.0),
result: success,
},
{
Expand Down
2 changes: 1 addition & 1 deletion expect_test.go
Original file line number Diff line number Diff line change
Expand Up @@ -279,7 +279,7 @@ func TestExpect_Traverse(t *testing.T) {

m.ContainsKey("aaa")
m.ContainsKey("bbb")
m.ContainsKey("aaa")
m.ContainsKey("ccc")

m.HasValue("aaa", data["aaa"])
m.HasValue("bbb", data["bbb"])
Expand Down