From 798d598920f181159b5e4b5efc585c61934e8c48 Mon Sep 17 00:00:00 2001 From: srfrog Date: Mon, 18 Mar 2019 13:27:41 -0700 Subject: [PATCH 1/9] types/conversion.go: dont convert datetime to datetime when empty If the stored value of datetime predicate is empty, types.Marshal will return empty when converting to binary. When we return the result the empty binary is converted to datetime, this causes time.UnmarshalBinary() to fail. --- types/conversion.go | 7 +++++-- 1 file changed, 5 insertions(+), 2 deletions(-) diff --git a/types/conversion.go b/types/conversion.go index 403afe9d4a8..dc719866334 100644 --- a/types/conversion.go +++ b/types/conversion.go @@ -252,8 +252,11 @@ func Convert(from Val, toID TypeID) (Val, error) { case DateTimeID: { var t time.Time - if err := t.UnmarshalBinary(data); err != nil { - return to, err + // We must inverse the binary->datetime for zero-time values. + if !bytes.Equal(data, []byte("")) { + if err := t.UnmarshalBinary(data); err != nil { + return to, err + } } // NOTE: when converting datetime values to anything else, we must // check for zero-time value and return the zero value of the new type. From ecf699a27f79ce679a52f52ae11e6f3fd6376fba Mon Sep 17 00:00:00 2001 From: srfrog Date: Mon, 18 Mar 2019 13:30:14 -0700 Subject: [PATCH 2/9] query/outputnode.go: return empty string when result is zero-time value The zero-time value is an internal values that should be exposed to the client. --- query/outputnode.go | 7 ++++++- 1 file changed, 6 insertions(+), 1 deletion(-) diff --git a/query/outputnode.go b/query/outputnode.go index d94bcb6db07..5193ab67029 100644 --- a/query/outputnode.go +++ b/query/outputnode.go @@ -159,7 +159,12 @@ func valToBytes(v types.Val) ([]byte, error) { } return []byte("false"), nil case types.DateTimeID: - return v.Value.(time.Time).MarshalJSON() + // Return empty string instead of zero-time value string - issue#3166 + t := v.Value.(time.Time) + if t.IsZero() { + return []byte(`""`), nil + } + return t.MarshalJSON() case types.GeoID: return geojson.Marshal(v.Value.(geom.T)) case types.UidID: From 060a622da3ecaff39cafc6c27dbbe80b4af6a22b Mon Sep 17 00:00:00 2001 From: srfrog Date: Mon, 18 Mar 2019 13:31:26 -0700 Subject: [PATCH 3/9] types/conversion_test.go: add tests for empty values Added tests to preserve the behavior with empty values. --- types/conversion_test.go | 30 ++++++++++++++++++++++++++++++ 1 file changed, 30 insertions(+) diff --git a/types/conversion_test.go b/types/conversion_test.go index 44f17093808..6e29164e828 100644 --- a/types/conversion_test.go +++ b/types/conversion_test.go @@ -79,6 +79,36 @@ func TestSameConversionDateTime(t *testing.T) { } } +func TestConversionEmpty(t *testing.T) { + tests := []struct { + in, out Val + }{ + {in: Val{Tid: BinaryID, Value: []byte{}}, + out: Val{Tid: DateTimeID, Value: time.Time{}}}, + {in: Val{Tid: BinaryID, Value: bs("")}, + out: Val{Tid: DateTimeID, Value: time.Time{}}}, + {in: Val{Tid: DateTimeID, Value: bs(time.Time{})}, + out: Val{Tid: BinaryID, Value: []byte{}}}, + {in: Val{Tid: StringID, Value: bs("")}, + out: Val{Tid: DateTimeID, Value: time.Time{}}}, + {in: Val{Tid: DateTimeID, Value: bs(time.Time{})}, + out: Val{Tid: StringID, Value: ""}}, + {in: Val{Tid: DefaultID, Value: bs("")}, + out: Val{Tid: DateTimeID, Value: time.Time{}}}, + {in: Val{Tid: DateTimeID, Value: bs(time.Time{})}, + out: Val{Tid: DefaultID, Value: ""}}, + {in: Val{Tid: DateTimeID, Value: bs("")}, + out: Val{Tid: DateTimeID, Value: time.Time{}}}, + {in: Val{Tid: DateTimeID, Value: []byte{}}, + out: Val{Tid: DateTimeID, Value: time.Time{}}}, + } + for _, tc := range tests { + out, err := Convert(tc.in, tc.out.Tid) + require.NoError(t, err) + require.EqualValues(t, tc.out, out) + } +} + func TestConvertToDefault(t *testing.T) { tests := []struct { in Val From 32b0eaac6042d0cd2b3ab1c74e26dcb9f836813f Mon Sep 17 00:00:00 2001 From: srfrog Date: Wed, 20 Mar 2019 15:42:15 -0700 Subject: [PATCH 4/9] types/conversion.go: expand the comment so it is clear --- types/conversion.go | 5 ++++- 1 file changed, 4 insertions(+), 1 deletion(-) diff --git a/types/conversion.go b/types/conversion.go index dc719866334..b80b8425fbb 100644 --- a/types/conversion.go +++ b/types/conversion.go @@ -252,7 +252,10 @@ func Convert(from Val, toID TypeID) (Val, error) { case DateTimeID: { var t time.Time - // We must inverse the binary->datetime for zero-time values. + // When we use Convert(BinaryID, DateTimeID) to store values if the value is empty, + // the conversion yields a zero time value. Here we check if that's the case and skip + // marshaling and return the ztv. Then we can handle it better if we need to return it + // in a result. if !bytes.Equal(data, []byte("")) { if err := t.UnmarshalBinary(data); err != nil { return to, err From 7193c848f13a7e0b6442a83850e68cb47795b3d0 Mon Sep 17 00:00:00 2001 From: srfrog Date: Mon, 25 Mar 2019 20:58:07 -0700 Subject: [PATCH 5/9] types/conversion.go: add specific conversion checks for datetime conversions Conversion from binary to datetime should happen for value length greater than zero. Convertion from empty string to int or float results their zero value. Conversion from datetime binary value (stored) will unmarshal only if the length is correct. Fixed a panic when bool is invalid, which obscures the real reason. --- types/conversion.go | 44 ++++++++++++++++++++++++-------------------- 1 file changed, 24 insertions(+), 20 deletions(-) diff --git a/types/conversion.go b/types/conversion.go index b80b8425fbb..47b4f63cd97 100644 --- a/types/conversion.go +++ b/types/conversion.go @@ -72,7 +72,9 @@ func Convert(from Val, toID TypeID) (Val, error) { return to, x.Errorf("Invalid value for bool %v", data[0]) case DateTimeID: var t time.Time - if !bytes.Equal(data, []byte("")) { + // Only try to convert binary data with length > 0. If the value is null, + // convert to zero-time value. + if len(data) != 0 { if err := t.UnmarshalBinary(data); err != nil { return to, err } @@ -97,20 +99,26 @@ func Convert(from Val, toID TypeID) (Val, error) { case BinaryID: *res = []byte(vc) case IntID: - val, err := strconv.ParseInt(vc, 10, 64) - if err != nil { - return to, err + *res = int64(0) + if vc != "" { + val, err := strconv.ParseInt(vc, 10, 64) + if err != nil { + return to, err + } + *res = val } - *res = val case FloatID: - val, err := strconv.ParseFloat(vc, 64) - if err != nil { - return to, err - } - if math.IsNaN(val) { - return to, fmt.Errorf("Got invalid value: NaN") + *res = float64(0) + if vc != "" { + val, err := strconv.ParseFloat(vc, 64) + if err != nil { + return to, err + } + if math.IsNaN(val) { + return to, fmt.Errorf("Got invalid value: NaN") + } + *res = val } - *res = val case StringID, DefaultID: *res = vc case BoolID: @@ -216,14 +224,10 @@ func Convert(from Val, toID TypeID) (Val, error) { case BoolID: { var vc bool - switch { - case data[0] == 0: - vc = false - case data[0] == 1: - vc = true - default: - return to, x.Errorf("Invalid value for bool %v", data[0]) + if len(data) == 0 || data[0] > 1 { + return to, x.Errorf("Invalid value for bool %v", data) } + vc = data[0] == 1 switch toID { case BoolID: @@ -256,7 +260,7 @@ func Convert(from Val, toID TypeID) (Val, error) { // the conversion yields a zero time value. Here we check if that's the case and skip // marshaling and return the ztv. Then we can handle it better if we need to return it // in a result. - if !bytes.Equal(data, []byte("")) { + if len(data) == 15 { if err := t.UnmarshalBinary(data); err != nil { return to, err } From b5bd72448b2bbdcaacd87404e0a296e82c0465ec Mon Sep 17 00:00:00 2001 From: srfrog Date: Mon, 25 Mar 2019 21:02:07 -0700 Subject: [PATCH 6/9] types/conversion_test.go: add test for conversion edge cases The edge test cases are for unexpected values that must be handled nicely. Updated float and int tests when converting from empty string value. --- types/conversion_test.go | 92 +++++++++++++++++++++++++++++++--------- 1 file changed, 73 insertions(+), 19 deletions(-) diff --git a/types/conversion_test.go b/types/conversion_test.go index 6e29164e828..d4acfd5eabf 100644 --- a/types/conversion_test.go +++ b/types/conversion_test.go @@ -79,33 +79,93 @@ func TestSameConversionDateTime(t *testing.T) { } } -func TestConversionEmpty(t *testing.T) { +func TestConversionEdgeCases(t *testing.T) { tests := []struct { in, out Val + failure string }{ + // From BinaryID to X + {in: Val{Tid: BinaryID, Value: []byte{}}, + out: Val{Tid: IntID, Value: int64(0)}, + failure: "Invalid data for int64"}, + {in: Val{Tid: BinaryID, Value: []byte{}}, + out: Val{Tid: FloatID, Value: int64(0)}, + failure: "Invalid data for float"}, {in: Val{Tid: BinaryID, Value: []byte{}}, + out: Val{Tid: BoolID, Value: false}}, + {in: Val{Tid: BinaryID, Value: []byte{2}}, + out: Val{Tid: BoolID, Value: false}, + failure: "Invalid value for bool"}, + {in: Val{Tid: BinaryID, Value: []byte{8}}, + out: Val{Tid: DateTimeID, Value: time.Time{}}, + failure: "Time.UnmarshalBinary:"}, + {in: Val{Tid: BinaryID, Value: []byte{}}, + out: Val{Tid: DateTimeID, Value: time.Time{}}}, + + // From StringID|DefaultID to X + {in: Val{Tid: StringID, Value: []byte{}}, + out: Val{Tid: IntID, Value: int64(0)}}, + {in: Val{Tid: StringID, Value: []byte{}}, + out: Val{Tid: FloatID, Value: float64(0)}}, + {in: Val{Tid: StringID, Value: []byte{}}, + out: Val{Tid: BoolID, Value: false}}, + {in: Val{Tid: StringID, Value: []byte{}}, out: Val{Tid: DateTimeID, Value: time.Time{}}}, - {in: Val{Tid: BinaryID, Value: bs("")}, + + // From IntID to X + {in: Val{Tid: IntID, Value: []byte{}}, + failure: "Invalid data for int64"}, + {in: Val{Tid: IntID, Value: bs(int64(0))}, + out: Val{Tid: DateTimeID, Value: time.Time{}}}, + + // From FloatID to X + {in: Val{Tid: FloatID, Value: []byte{}}, + failure: "Invalid data for float"}, + {in: Val{Tid: FloatID, Value: bs(float64(0))}, + out: Val{Tid: DateTimeID, Value: time.Time{}}}, + + // From BoolID to X + {in: Val{Tid: BoolID, Value: []byte{}}, + failure: "Invalid value for bool"}, + {in: Val{Tid: BoolID, Value: []byte{8}}, + failure: "Invalid value for bool"}, + + // From DateTimeID to X + {in: Val{Tid: DateTimeID, Value: []byte{}}, out: Val{Tid: DateTimeID, Value: time.Time{}}}, {in: Val{Tid: DateTimeID, Value: bs(time.Time{})}, - out: Val{Tid: BinaryID, Value: []byte{}}}, - {in: Val{Tid: StringID, Value: bs("")}, out: Val{Tid: DateTimeID, Value: time.Time{}}}, + {in: Val{Tid: DateTimeID, Value: []byte{}}, + out: Val{Tid: BinaryID, Value: []byte{}}}, {in: Val{Tid: DateTimeID, Value: bs(time.Time{})}, + out: Val{Tid: BinaryID, Value: []byte{}}}, + {in: Val{Tid: DateTimeID, Value: []byte{}}, out: Val{Tid: StringID, Value: ""}}, - {in: Val{Tid: DefaultID, Value: bs("")}, - out: Val{Tid: DateTimeID, Value: time.Time{}}}, + {in: Val{Tid: DateTimeID, Value: bs(time.Time{})}, + out: Val{Tid: StringID, Value: ""}}, + {in: Val{Tid: DateTimeID, Value: []byte{}}, + out: Val{Tid: DefaultID, Value: ""}}, {in: Val{Tid: DateTimeID, Value: bs(time.Time{})}, out: Val{Tid: DefaultID, Value: ""}}, - {in: Val{Tid: DateTimeID, Value: bs("")}, - out: Val{Tid: DateTimeID, Value: time.Time{}}}, {in: Val{Tid: DateTimeID, Value: []byte{}}, - out: Val{Tid: DateTimeID, Value: time.Time{}}}, + out: Val{Tid: IntID, Value: int64(0)}}, + {in: Val{Tid: DateTimeID, Value: bs(time.Time{})}, + out: Val{Tid: IntID, Value: int64(0)}}, + {in: Val{Tid: DateTimeID, Value: []byte{}}, + out: Val{Tid: FloatID, Value: float64(0)}}, + {in: Val{Tid: DateTimeID, Value: bs(time.Time{})}, + out: Val{Tid: FloatID, Value: float64(0)}}, } for _, tc := range tests { + // t.Logf("%s to %s != %v", tc.in.Tid.Name(), tc.out.Tid.Name(), tc.out.Value) out, err := Convert(tc.in, tc.out.Tid) - require.NoError(t, err) - require.EqualValues(t, tc.out, out) + if tc.failure != "" { + require.Error(t, err) + require.Contains(t, err.Error(), tc.failure) + } else { + require.NoError(t, err) + require.EqualValues(t, tc.out, out) + } } } @@ -608,6 +668,7 @@ func TestConvertStringToInt(t *testing.T) { failure string }{ {in: "1", out: int64(1)}, + {in: "", out: int64(0)}, {in: "13816", out: int64(13816)}, {in: "-1221", out: int64(-1221)}, {in: "0", out: int64(0)}, @@ -616,10 +677,6 @@ func TestConvertStringToInt(t *testing.T) { in: "srfrog", failure: `strconv.ParseInt: parsing "srfrog": invalid syntax`, }, - { - in: "", - failure: `strconv.ParseInt: parsing "": invalid syntax`, - }, { in: "3.0", failure: `strconv.ParseInt: parsing "3.0": invalid syntax`, @@ -649,6 +706,7 @@ func TestConvertStringToFloat(t *testing.T) { failure string }{ {in: "1", out: float64(1)}, + {in: "", out: float64(0)}, {in: "13816.251", out: float64(13816.251)}, {in: "-1221.12", out: float64(-1221.12)}, {in: "-0.0", out: float64(-0.0)}, @@ -658,10 +716,6 @@ func TestConvertStringToFloat(t *testing.T) { in: "srfrog", failure: `strconv.ParseFloat: parsing "srfrog": invalid syntax`, }, - { - in: "", - failure: `strconv.ParseFloat: parsing "": invalid syntax`, - }, { in: "-3a.5", failure: `strconv.ParseFloat: parsing "-3a.5": invalid syntax`, From ca6a5e023f6236eb6020604393952e8911359bea Mon Sep 17 00:00:00 2001 From: srfrog Date: Mon, 25 Mar 2019 21:12:50 -0700 Subject: [PATCH 7/9] types/conversion.go: add sanity check for data type assertion --- types/conversion.go | 11 +++++++++-- types/conversion_test.go | 4 ++++ 2 files changed, 13 insertions(+), 2 deletions(-) diff --git a/types/conversion.go b/types/conversion.go index 47b4f63cd97..1e5959b132f 100644 --- a/types/conversion.go +++ b/types/conversion.go @@ -36,10 +36,17 @@ import ( // Convert converts the value to given scalar type. func Convert(from Val, toID TypeID) (Val, error) { - to := ValueForType(toID) + var to Val + + // sanity: we expect a value + data, ok := from.Value.([]byte) + if !ok { + return to, x.Errorf("Invalid data to convert to %s", toID.Name()) + } + to = ValueForType(toID) fromID := from.Tid - data := from.Value.([]byte) res := &to.Value + // Convert from-type to to-type and store in the result interface. switch fromID { case BinaryID: diff --git a/types/conversion_test.go b/types/conversion_test.go index d4acfd5eabf..dacefcc01b4 100644 --- a/types/conversion_test.go +++ b/types/conversion_test.go @@ -84,6 +84,10 @@ func TestConversionEdgeCases(t *testing.T) { in, out Val failure string }{ + {in: Val{Tid: BinaryID, Value: nil}, + out: Val{Tid: BinaryID, Value: nil}, + failure: "Invalid data to convert to binary"}, + // From BinaryID to X {in: Val{Tid: BinaryID, Value: []byte{}}, out: Val{Tid: IntID, Value: int64(0)}, From ff91e4e13182bc4e2cca317c352f271f0c91964d Mon Sep 17 00:00:00 2001 From: srfrog Date: Wed, 27 Mar 2019 17:39:16 -0700 Subject: [PATCH 8/9] types/conversion.go: remove any default value conversions and return errors instead Remove all handling of default values for types that can return error when syntax fails. Update the tests to check for behavior. --- types/conversion.go | 140 +++++++++++++-------------------------- types/conversion_test.go | 78 ++++++++++++---------- 2 files changed, 89 insertions(+), 129 deletions(-) diff --git a/types/conversion.go b/types/conversion.go index 1e5959b132f..d39526a760c 100644 --- a/types/conversion.go +++ b/types/conversion.go @@ -79,12 +79,8 @@ func Convert(from Val, toID TypeID) (Val, error) { return to, x.Errorf("Invalid value for bool %v", data[0]) case DateTimeID: var t time.Time - // Only try to convert binary data with length > 0. If the value is null, - // convert to zero-time value. - if len(data) != 0 { - if err := t.UnmarshalBinary(data); err != nil { - return to, err - } + if err := t.UnmarshalBinary(data); err != nil { + return to, err } *res = t case GeoID: @@ -106,46 +102,34 @@ func Convert(from Val, toID TypeID) (Val, error) { case BinaryID: *res = []byte(vc) case IntID: - *res = int64(0) - if vc != "" { - val, err := strconv.ParseInt(vc, 10, 64) - if err != nil { - return to, err - } - *res = val + val, err := strconv.ParseInt(vc, 10, 64) + if err != nil { + return to, err } + *res = val case FloatID: - *res = float64(0) - if vc != "" { - val, err := strconv.ParseFloat(vc, 64) - if err != nil { - return to, err - } - if math.IsNaN(val) { - return to, fmt.Errorf("Got invalid value: NaN") - } - *res = val + val, err := strconv.ParseFloat(vc, 64) + if err != nil { + return to, err + } + if math.IsNaN(val) { + return to, fmt.Errorf("Got invalid value: NaN") } + *res = val case StringID, DefaultID: *res = vc case BoolID: - *res = false - if vc != "" { - val, err := strconv.ParseBool(vc) - if err != nil { - return to, err - } - *res = val + val, err := strconv.ParseBool(vc) + if err != nil { + return to, err } + *res = val case DateTimeID: - *res = time.Time{} - if vc != "" { - t, err := ParseTime(vc) - if err != nil { - return to, err - } - *res = t + t, err := ParseTime(vc) + if err != nil { + return to, err } + *res = t case GeoID: var g geom.T text := bytes.Replace([]byte(vc), []byte("'"), []byte("\""), -1) @@ -184,10 +168,7 @@ func Convert(from Val, toID TypeID) (Val, error) { case StringID, DefaultID: *res = strconv.FormatInt(vc, 10) case DateTimeID: - *res = time.Time{} - if vc != 0 { - *res = time.Unix(vc, 0).UTC() - } + *res = time.Unix(vc, 0).UTC() default: return to, cantConvert(fromID, toID) } @@ -217,13 +198,10 @@ func Convert(from Val, toID TypeID) (Val, error) { case StringID, DefaultID: *res = strconv.FormatFloat(vc, 'G', -1, 64) case DateTimeID: - *res = time.Time{} - if vc != 0 { - secs := int64(vc) - fracSecs := vc - float64(secs) - nsecs := int64(fracSecs * nanoSecondsInSec) - *res = time.Unix(secs, nsecs).UTC() - } + secs := int64(vc) + fracSecs := vc - float64(secs) + nsecs := int64(fracSecs * nanoSecondsInSec) + *res = time.Unix(secs, nsecs).UTC() default: return to, cantConvert(fromID, toID) } @@ -263,48 +241,28 @@ func Convert(from Val, toID TypeID) (Val, error) { case DateTimeID: { var t time.Time - // When we use Convert(BinaryID, DateTimeID) to store values if the value is empty, - // the conversion yields a zero time value. Here we check if that's the case and skip - // marshaling and return the ztv. Then we can handle it better if we need to return it - // in a result. - if len(data) == 15 { - if err := t.UnmarshalBinary(data); err != nil { - return to, err - } + if err := t.UnmarshalBinary(data); err != nil { + return to, err } - // NOTE: when converting datetime values to anything else, we must - // check for zero-time value and return the zero value of the new type. switch toID { case DateTimeID: *res = t case BinaryID: - *res = []byte("") - if !t.IsZero() { - r, err := t.MarshalBinary() - if err != nil { - return to, err - } - *res = r + r, err := t.MarshalBinary() + if err != nil { + return to, err } + *res = r case StringID, DefaultID: - *res = "" - if !t.IsZero() { - val, err := t.MarshalText() - if err != nil { - return to, err - } - *res = string(val) + val, err := t.MarshalText() + if err != nil { + return to, err } + *res = string(val) case IntID: - *res = int64(0) - if !t.IsZero() { - *res = t.Unix() - } + *res = t.Unix() case FloatID: - *res = float64(0) - if !t.IsZero() { - *res = float64(t.UnixNano()) / float64(nanoSecondsInSec) - } + *res = float64(t.UnixNano()) / float64(nanoSecondsInSec) default: return to, cantConvert(fromID, toID) } @@ -432,23 +390,17 @@ func Marshal(from Val, to *Val) error { vc := val.(time.Time) switch toID { case StringID, DefaultID: - *res = "" - if !vc.IsZero() { - val, err := vc.MarshalText() - if err != nil { - return err - } - *res = string(val) + val, err := vc.MarshalText() + if err != nil { + return err } + *res = string(val) case BinaryID: - *res = []byte("") - if !vc.IsZero() { - r, err := vc.MarshalBinary() - if err != nil { - return err - } - *res = r + r, err := vc.MarshalBinary() + if err != nil { + return err } + *res = r default: return cantConvert(fromID, toID) } diff --git a/types/conversion_test.go b/types/conversion_test.go index dacefcc01b4..50fcf812794 100644 --- a/types/conversion_test.go +++ b/types/conversion_test.go @@ -104,29 +104,34 @@ func TestConversionEdgeCases(t *testing.T) { out: Val{Tid: DateTimeID, Value: time.Time{}}, failure: "Time.UnmarshalBinary:"}, {in: Val{Tid: BinaryID, Value: []byte{}}, - out: Val{Tid: DateTimeID, Value: time.Time{}}}, + out: Val{Tid: DateTimeID, Value: time.Time{}}, + failure: "Time.UnmarshalBinary:"}, // From StringID|DefaultID to X {in: Val{Tid: StringID, Value: []byte{}}, - out: Val{Tid: IntID, Value: int64(0)}}, + out: Val{Tid: IntID, Value: int64(0)}, + failure: "strconv.ParseInt"}, {in: Val{Tid: StringID, Value: []byte{}}, - out: Val{Tid: FloatID, Value: float64(0)}}, + out: Val{Tid: FloatID, Value: float64(0)}, + failure: "strconv.ParseFloat"}, {in: Val{Tid: StringID, Value: []byte{}}, - out: Val{Tid: BoolID, Value: false}}, + out: Val{Tid: BoolID, Value: false}, + failure: "strconv.ParseBool"}, {in: Val{Tid: StringID, Value: []byte{}}, - out: Val{Tid: DateTimeID, Value: time.Time{}}}, + out: Val{Tid: DateTimeID, Value: time.Time{}}, + failure: `parsing time "" as "2006": cannot parse "" as "2006"`}, // From IntID to X {in: Val{Tid: IntID, Value: []byte{}}, failure: "Invalid data for int64"}, {in: Val{Tid: IntID, Value: bs(int64(0))}, - out: Val{Tid: DateTimeID, Value: time.Time{}}}, + out: Val{Tid: DateTimeID, Value: time.Unix(0, 0).UTC()}}, // From FloatID to X {in: Val{Tid: FloatID, Value: []byte{}}, failure: "Invalid data for float"}, {in: Val{Tid: FloatID, Value: bs(float64(0))}, - out: Val{Tid: DateTimeID, Value: time.Time{}}}, + out: Val{Tid: DateTimeID, Value: time.Unix(0, 0).UTC()}}, // From BoolID to X {in: Val{Tid: BoolID, Value: []byte{}}, @@ -136,32 +141,38 @@ func TestConversionEdgeCases(t *testing.T) { // From DateTimeID to X {in: Val{Tid: DateTimeID, Value: []byte{}}, - out: Val{Tid: DateTimeID, Value: time.Time{}}}, + out: Val{Tid: DateTimeID, Value: time.Time{}}, + failure: "Time.UnmarshalBinary:"}, {in: Val{Tid: DateTimeID, Value: bs(time.Time{})}, out: Val{Tid: DateTimeID, Value: time.Time{}}}, {in: Val{Tid: DateTimeID, Value: []byte{}}, - out: Val{Tid: BinaryID, Value: []byte{}}}, + out: Val{Tid: BinaryID, Value: []byte{}}, + failure: "Time.UnmarshalBinary"}, {in: Val{Tid: DateTimeID, Value: bs(time.Time{})}, - out: Val{Tid: BinaryID, Value: []byte{}}}, + out: Val{Tid: BinaryID, Value: bs(time.Time{})}}, {in: Val{Tid: DateTimeID, Value: []byte{}}, - out: Val{Tid: StringID, Value: ""}}, + out: Val{Tid: StringID, Value: ""}, + failure: "Time.UnmarshalBinary: no data"}, {in: Val{Tid: DateTimeID, Value: bs(time.Time{})}, - out: Val{Tid: StringID, Value: ""}}, + out: Val{Tid: StringID, Value: "0001-01-01T00:00:00Z"}}, {in: Val{Tid: DateTimeID, Value: []byte{}}, - out: Val{Tid: DefaultID, Value: ""}}, + out: Val{Tid: DefaultID, Value: ""}, + failure: "Time.UnmarshalBinary: no data"}, {in: Val{Tid: DateTimeID, Value: bs(time.Time{})}, - out: Val{Tid: DefaultID, Value: ""}}, + out: Val{Tid: DefaultID, Value: "0001-01-01T00:00:00Z"}}, {in: Val{Tid: DateTimeID, Value: []byte{}}, - out: Val{Tid: IntID, Value: int64(0)}}, + out: Val{Tid: IntID, Value: int64(0)}, + failure: "Time.UnmarshalBinary: no data"}, {in: Val{Tid: DateTimeID, Value: bs(time.Time{})}, - out: Val{Tid: IntID, Value: int64(0)}}, + out: Val{Tid: IntID, Value: time.Time{}.Unix()}}, {in: Val{Tid: DateTimeID, Value: []byte{}}, - out: Val{Tid: FloatID, Value: float64(0)}}, + out: Val{Tid: FloatID, Value: float64(0)}, + failure: "Time.UnmarshalBinary: no data"}, {in: Val{Tid: DateTimeID, Value: bs(time.Time{})}, - out: Val{Tid: FloatID, Value: float64(0)}}, + out: Val{Tid: FloatID, Value: float64(time.Time{}.UnixNano()) / float64(nanoSecondsInSec)}}, } for _, tc := range tests { - // t.Logf("%s to %s != %v", tc.in.Tid.Name(), tc.out.Tid.Name(), tc.out.Value) + t.Logf("%s to %s != %v", tc.in.Tid.Name(), tc.out.Tid.Name(), tc.out.Value) out, err := Convert(tc.in, tc.out.Tid) if tc.failure != "" { require.Error(t, err) @@ -185,7 +196,7 @@ func TestConvertToDefault(t *testing.T) { {in: Val{IntID, bs(int64(3))}, out: "3"}, {in: Val{FloatID, bs(float64(-3.5))}, out: "-3.5"}, {in: Val{DateTimeID, bs(time.Date(2006, 01, 02, 15, 04, 05, 0, time.UTC))}, out: "2006-01-02T15:04:05Z"}, - {in: Val{DateTimeID, bs(time.Time{})}, out: ""}, + {in: Val{DateTimeID, bs(time.Time{})}, out: "0001-01-01T00:00:00Z"}, } for _, tc := range tests { @@ -208,7 +219,6 @@ func TestConvertFromDefault(t *testing.T) { {in: "hello", out: Val{StringID, "hello"}}, {in: "", out: Val{StringID, ""}}, {in: "2016", out: Val{DateTimeID, time.Date(2016, 1, 1, 0, 0, 0, 0, time.UTC)}}, - {in: "", out: Val{DateTimeID, time.Time{}}}, } for _, tc := range tests { @@ -230,7 +240,6 @@ func TestConvertToBinary(t *testing.T) { {in: Val{IntID, bs(int64(3))}, out: bs(int64(3))}, {in: Val{FloatID, bs(float64(-3.5))}, out: bs(float64(-3.5))}, {in: Val{DateTimeID, bs(time.Date(2006, 01, 02, 15, 04, 05, 0, time.UTC))}, out: bs(time.Date(2006, 01, 02, 15, 04, 05, 0, time.UTC))}, - {in: Val{DateTimeID, bs(time.Time{})}, out: []byte("")}, } for _, tc := range tests { @@ -257,7 +266,6 @@ func TestConvertFromBinary(t *testing.T) { {in: []byte(""), out: Val{StringID, ""}}, {in: bs(time.Date(2016, 1, 1, 0, 0, 0, 0, time.UTC)), out: Val{DateTimeID, time.Date(2016, 1, 1, 0, 0, 0, 0, time.UTC)}}, {in: bs(time.Time{}), out: Val{DateTimeID, time.Time{}}}, - {in: []byte(""), out: Val{DateTimeID, time.Time{}}}, } for _, tc := range tests { @@ -276,7 +284,6 @@ func TestConvertStringToDateTime(t *testing.T) { {in: "2006-01-02", out: time.Date(2006, 01, 02, 0, 0, 0, 0, time.UTC)}, {in: "2006-01", out: time.Date(2006, 01, 01, 0, 0, 0, 0, time.UTC)}, {in: "2006", out: time.Date(2006, 01, 01, 0, 0, 0, 0, time.UTC)}, - {in: "", out: time.Time{}}, } for _, tc := range tests { @@ -294,7 +301,6 @@ func TestConvertDateTimeToString(t *testing.T) { {in: time.Date(2006, 01, 02, 15, 04, 05, 0, time.UTC), out: "2006-01-02T15:04:05Z"}, {in: time.Date(2006, 01, 02, 0, 0, 0, 0, time.UTC), out: "2006-01-02T00:00:00Z"}, {in: time.Date(2006, 01, 01, 0, 0, 0, 0, time.UTC), out: "2006-01-01T00:00:00Z"}, - {in: time.Time{}, out: ""}, } for _, tc := range tests { @@ -454,7 +460,10 @@ func TestConvertStringToBool(t *testing.T) { {in: "0", out: false}, {in: "false", out: false}, {in: "False", out: false}, - {in: "", out: false}, + { + in: "", + failure: `strconv.ParseBool: parsing "": invalid syntax`, + }, { in: "srfrog", failure: `strconv.ParseBool: parsing "srfrog": invalid syntax`, @@ -532,9 +541,7 @@ func TestFalsy(t *testing.T) { in Val }{ {in: Val{Tid: StringID, Value: []byte("false")}}, - {in: Val{Tid: StringID, Value: []byte("")}}, {in: Val{Tid: DefaultID, Value: []byte("false")}}, - {in: Val{Tid: DefaultID, Value: []byte("")}}, {in: Val{Tid: IntID, Value: bs(int64(0))}}, {in: Val{Tid: FloatID, Value: bs(float64(0.0))}}, {in: Val{Tid: BinaryID, Value: []byte("")}}, @@ -672,11 +679,14 @@ func TestConvertStringToInt(t *testing.T) { failure string }{ {in: "1", out: int64(1)}, - {in: "", out: int64(0)}, {in: "13816", out: int64(13816)}, {in: "-1221", out: int64(-1221)}, {in: "0", out: int64(0)}, {in: "203716381366627", out: int64(203716381366627)}, + { + in: "", + failure: `strconv.ParseInt: parsing "": invalid syntax`, + }, { in: "srfrog", failure: `strconv.ParseInt: parsing "srfrog": invalid syntax`, @@ -710,12 +720,15 @@ func TestConvertStringToFloat(t *testing.T) { failure string }{ {in: "1", out: float64(1)}, - {in: "", out: float64(0)}, {in: "13816.251", out: float64(13816.251)}, {in: "-1221.12", out: float64(-1221.12)}, {in: "-0.0", out: float64(-0.0)}, {in: "1e10", out: float64(1e10)}, {in: "1e-2", out: float64(0.01)}, + { + in: "", + failure: `strconv.ParseFloat: parsing "": invalid syntax`, + }, { in: "srfrog", failure: `strconv.ParseFloat: parsing "srfrog": invalid syntax`, @@ -753,7 +766,6 @@ func TestConvertFloatToDateTime(t *testing.T) { {in: float64(-2150326800), out: time.Date(1901, time.November, 10, 23, 0, 0, 0, time.UTC)}, {in: float64(1257894000.001), out: time.Date(2009, time.November, 10, 23, 0, 0, 999927, time.UTC)}, {in: float64(-4409999.999), out: time.Date(1969, time.November, 10, 23, 0, 0, 1000001, time.UTC)}, - {in: float64(0), out: time.Time{}}, } for _, tc := range tests { @@ -774,7 +786,6 @@ func TestConvertDateTimeToFloat(t *testing.T) { {in: time.Date(1901, time.November, 10, 23, 0, 0, 0, time.UTC), out: float64(-2150326800)}, {in: time.Date(2009, time.November, 10, 23, 0, 0, 1000000, time.UTC), out: float64(1257894000.001)}, {in: time.Date(1969, time.November, 10, 23, 0, 0, 1000000, time.UTC), out: float64(-4409999.999)}, - {in: time.Time{}, out: float64(0)}, } for _, tc := range tests { @@ -791,7 +802,6 @@ func TestConvertIntToDateTime(t *testing.T) { }{ {in: int64(1257894000), out: time.Date(2009, time.November, 10, 23, 0, 0, 0, time.UTC)}, {in: int64(-4410000), out: time.Date(1969, time.November, 10, 23, 0, 0, 0, time.UTC)}, - {in: int64(0), out: time.Time{}}, } for _, tc := range tests { @@ -810,7 +820,6 @@ func TestConvertDateTimeToInt(t *testing.T) { {in: time.Date(1969, time.November, 10, 23, 0, 0, 0, time.UTC), out: int64(-4410000)}, {in: time.Date(2039, time.November, 10, 23, 0, 0, 0, time.UTC), out: int64(2204578800)}, {in: time.Date(1901, time.November, 10, 23, 0, 0, 0, time.UTC), out: int64(-2150326800)}, - {in: time.Time{}, out: int64(0)}, } for _, tc := range tests { @@ -831,7 +840,6 @@ func TestConvertToString(t *testing.T) { {in: Val{Tid: StringID, Value: []byte("srfrog")}, out: "srfrog"}, {in: Val{Tid: PasswordID, Value: []byte("password")}, out: "password"}, {in: Val{Tid: DateTimeID, Value: bs(time.Date(2006, time.January, 2, 15, 4, 5, 0, time.UTC))}, out: "2006-01-02T15:04:05Z"}, - {in: Val{Tid: DateTimeID, Value: bs(time.Time{})}, out: ""}, } for _, tc := range tests { From d6fa2c7a406f92e6ec142616ad622b4aa0f497dc Mon Sep 17 00:00:00 2001 From: srfrog Date: Wed, 10 Apr 2019 17:30:33 -0700 Subject: [PATCH 9/9] types/conversion_test.go: remove extra value in test, fix 100 char length. --- types/conversion_test.go | 7 ++++--- 1 file changed, 4 insertions(+), 3 deletions(-) diff --git a/types/conversion_test.go b/types/conversion_test.go index 50fcf812794..ccaadd9c891 100644 --- a/types/conversion_test.go +++ b/types/conversion_test.go @@ -84,8 +84,8 @@ func TestConversionEdgeCases(t *testing.T) { in, out Val failure string }{ - {in: Val{Tid: BinaryID, Value: nil}, - out: Val{Tid: BinaryID, Value: nil}, + {in: Val{Tid: BinaryID}, + out: Val{Tid: BinaryID}, failure: "Invalid data to convert to binary"}, // From BinaryID to X @@ -169,7 +169,8 @@ func TestConversionEdgeCases(t *testing.T) { out: Val{Tid: FloatID, Value: float64(0)}, failure: "Time.UnmarshalBinary: no data"}, {in: Val{Tid: DateTimeID, Value: bs(time.Time{})}, - out: Val{Tid: FloatID, Value: float64(time.Time{}.UnixNano()) / float64(nanoSecondsInSec)}}, + out: Val{Tid: FloatID, + Value: float64(time.Time{}.UnixNano()) / float64(nanoSecondsInSec)}}, } for _, tc := range tests { t.Logf("%s to %s != %v", tc.in.Tid.Name(), tc.out.Tid.Name(), tc.out.Value)