gojay

high performance JSON encoder/decoder with stream API for Golang
git clone git://git.lair.cx/gojay
Log | Files | Refs | README | LICENSE

commit 2f9e3aba9ce5220ddae44a60ef8760c1b4b25440
parent 1da60bb4601ddd313012d7e9bc0e5d1deba2cbc3
Author: francoispqt <francois@parquet.ninja>
Date:   Tue,  1 May 2018 11:32:34 +0800

add tests for encode api, update make cover to use atomic covermode

Diffstat:
MMakefile | 2+-
Mdecode_pool.go | 2+-
Mdecode_stream_pool.go | 2+-
Mencode.go | 23++++++++++-------------
Mencode_array.go | 10++++++++--
Mencode_array_test.go | 24++++++++++++++++++------
Mencode_bool.go | 10++++++++--
Mencode_bool_test.go | 28++++++++++++++++++++--------
Mencode_interface.go | 62+++++++++++++++++++++++++++++++-------------------------------
Mencode_interface_test.go | 99++++++++++++++++++++++++++++++++++++++++++++++++++++---------------------------
Mencode_number.go | 56++++++++++++++++++++++++++++++++++++++++++--------------
Mencode_number_test.go | 124++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++-
Mencode_object_test.go | 9+--------
Mencode_pool.go | 2+-
Mencode_stream.go | 6+-----
Mencode_stream_test.go | 36++++++++++++++++++++++++++++++++++++
Mencode_string.go | 10++++++++--
Mencode_string_test.go | 23++++++++++++++++++++++-
Mencode_test.go | 8++++++++
19 files changed, 405 insertions(+), 131 deletions(-)

diff --git a/Makefile b/Makefile @@ -4,7 +4,7 @@ test: .PHONY: cover cover: - go test -coverprofile=coverage.out + go test -coverprofile=coverage.out -covermode=atomic .PHONY: coverhtml coverhtml: diff --git a/decode_pool.go b/decode_pool.go @@ -59,9 +59,9 @@ func borrowDecoder(r io.Reader, bufSize int) *Decoder { // If a decoder is used after calling Release // a panic will be raised with an InvalidUsagePooledDecoderError error. func (dec *Decoder) Release() { + dec.isPooled = 1 select { case decPool <- dec: - dec.isPooled = 1 default: } } diff --git a/decode_stream_pool.go b/decode_stream_pool.go @@ -58,9 +58,9 @@ func (s stream) borrowDecoder(r io.Reader, bufSize int) *StreamDecoder { // If a decoder is used after calling Release // a panic will be raised with an InvalidUsagePooledDecoderError error. func (dec *StreamDecoder) Release() { + dec.isPooled = 1 select { case streamDecPool <- dec: - dec.isPooled = 1 default: } } diff --git a/encode.go b/encode.go @@ -126,39 +126,39 @@ func Marshal(v interface{}) ([]byte, error) { defer enc.Release() case int: enc := BorrowEncoder(nil) - b, err = enc.encodeInt(int64(vt)) + b, err = enc.encodeInt(vt) defer enc.Release() case int64: enc := BorrowEncoder(nil) defer enc.Release() - return enc.encodeInt(vt) + return enc.encodeInt64(vt) case int32: enc := BorrowEncoder(nil) defer enc.Release() - return enc.encodeInt(int64(vt)) + return enc.encodeInt(int(vt)) case int16: enc := BorrowEncoder(nil) defer enc.Release() - return enc.encodeInt(int64(vt)) + return enc.encodeInt(int(vt)) case int8: enc := BorrowEncoder(nil) defer enc.Release() - return enc.encodeInt(int64(vt)) + return enc.encodeInt(int(vt)) case uint64: enc := BorrowEncoder(nil) defer enc.Release() - return enc.encodeInt(int64(vt)) + return enc.encodeInt(int(vt)) case uint32: enc := BorrowEncoder(nil) defer enc.Release() - return enc.encodeInt(int64(vt)) + return enc.encodeInt(int(vt)) case uint16: enc := BorrowEncoder(nil) defer enc.Release() - return enc.encodeInt(int64(vt)) + return enc.encodeInt(int(vt)) case uint8: enc := BorrowEncoder(nil) - b, err = enc.encodeInt(int64(vt)) + b, err = enc.encodeInt(int(vt)) defer enc.Release() case float64: enc := BorrowEncoder(nil) @@ -167,7 +167,7 @@ func Marshal(v interface{}) ([]byte, error) { case float32: enc := BorrowEncoder(nil) defer enc.Release() - return enc.encodeFloat(float64(vt)) + return enc.encodeFloat32(vt) default: err = InvalidMarshalError(fmt.Sprintf(invalidMarshalErrorMsg, reflect.TypeOf(vt).String())) } @@ -204,9 +204,6 @@ func (enc *Encoder) getPreviousRune() (byte, bool) { func (enc *Encoder) write() (int, error) { i, err := enc.w.Write(enc.buf) - if err != nil { - enc.err = err - } enc.buf = make([]byte, 0, 512) return i, err } diff --git a/encode_array.go b/encode_array.go @@ -1,11 +1,17 @@ package gojay // EncodeArray encodes an implementation of MarshalerArray to JSON -func (enc *Encoder) EncodeArray(v MarshalerArray) ([]byte, error) { +func (enc *Encoder) EncodeArray(v MarshalerArray) error { if enc.isPooled == 1 { panic(InvalidUsagePooledEncoderError("Invalid usage of pooled encoder")) } - return enc.encodeArray(v) + _, _ = enc.encodeArray(v) + _, err := enc.write() + if err != nil { + enc.err = err + return err + } + return nil } func (enc *Encoder) encodeArray(v MarshalerArray) ([]byte, error) { enc.grow(200) diff --git a/encode_array_test.go b/encode_array_test.go @@ -1,6 +1,7 @@ package gojay import ( + "strings" "testing" "github.com/stretchr/testify/assert" @@ -93,9 +94,10 @@ func TestEncoderArrayInterfaces(t *testing.T) { uint16(1), uint8(1), float64(1.31), - // float32(1.31), + float32(1.31), &TestEncodingArr{}, true, + false, "test", &TestEncoding{ test: "hello world", @@ -108,7 +110,7 @@ func TestEncoderArrayInterfaces(t *testing.T) { assert.Nil(t, err, "Error should be nil") assert.Equal( t, - `[1,1,1,1,1,1,1,1,1.31,[],true,"test",{"test":"hello world","test2":"foobar","testInt":1,"testBool":true,"testArr":[],"testF64":0,"testF32":0}]`, + `[1,1,1,1,1,1,1,1,1.31,1.31,[],true,false,"test",{"test":"hello world","test2":"foobar","testInt":1,"testBool":true,"testArr":[],"testF64":0,"testF32":0}]`, string(r), "Result of marshalling is different as the one expected") } @@ -136,17 +138,27 @@ func TestEncoderArrayInterfacesEncoderAPI(t *testing.T) { testBool: true, }, } - enc := BorrowEncoder(nil) + builder := &strings.Builder{} + enc := BorrowEncoder(builder) defer enc.Release() - r, err := enc.EncodeArray(v) + err := enc.EncodeArray(v) assert.Nil(t, err, "Error should be nil") assert.Equal( t, `[1,1,1,1,1,1,1,1,1.31,[],true,"test",{"test":"hello world","test2":"foobar","testInt":1,"testBool":true,"testArr":[],"testF64":0,"testF32":0}]`, - string(r), + builder.String(), "Result of marshalling is different as the one expected") } +func TestEncoderArrayInterfacesEncoderAPIWriteError(t *testing.T) { + v := &testEncodingArrInterfaces{} + w := TestWriterError("") + enc := BorrowEncoder(w) + defer enc.Release() + err := enc.EncodeArray(v) + assert.NotNil(t, err, "err should not be nil") +} + func TestEncoderArrayPooledError(t *testing.T) { v := &testEncodingArrInterfaces{} enc := BorrowEncoder(nil) @@ -157,6 +169,6 @@ func TestEncoderArrayPooledError(t *testing.T) { assert.IsType(t, InvalidUsagePooledEncoderError(""), err, "err should be of type InvalidUsagePooledEncoderError") assert.Equal(t, "Invalid usage of pooled encoder", err.(InvalidUsagePooledEncoderError).Error(), "err should be of type InvalidUsagePooledDecoderError") }() - _, _ = enc.EncodeArray(v) + _ = enc.EncodeArray(v) assert.True(t, false, "should not be called as it should have panicked") } diff --git a/encode_bool.go b/encode_bool.go @@ -3,11 +3,17 @@ package gojay import "strconv" // EncodeBool encodes a bool to JSON -func (enc *Encoder) EncodeBool(v bool) ([]byte, error) { +func (enc *Encoder) EncodeBool(v bool) error { if enc.isPooled == 1 { panic(InvalidUsagePooledEncoderError("Invalid usage of pooled encoder")) } - return enc.encodeBool(v) + _, _ = enc.encodeBool(v) + _, err := enc.write() + if err != nil { + enc.err = err + return err + } + return nil } // encodeBool encodes a bool to JSON diff --git a/encode_bool_test.go b/encode_bool_test.go @@ -1,29 +1,33 @@ package gojay import ( + "strings" "testing" "github.com/stretchr/testify/assert" ) func TestEncoderBoolTrue(t *testing.T) { - enc := BorrowEncoder(nil) + builder := &strings.Builder{} + enc := BorrowEncoder(builder) defer enc.Release() - b, err := enc.EncodeBool(true) + err := enc.EncodeBool(true) assert.Nil(t, err, "err must be nil") - assert.Equal(t, "true", string(b), "string(b) must be equal to 'true'") + assert.Equal(t, "true", builder.String(), "string(b) must be equal to 'true'") } func TestEncoderBoolFalse(t *testing.T) { - enc := BorrowEncoder(nil) + builder := &strings.Builder{} + enc := BorrowEncoder(builder) defer enc.Release() - b, err := enc.EncodeBool(false) + err := enc.EncodeBool(false) assert.Nil(t, err, "err must be nil") - assert.Equal(t, "false", string(b), "string(b) must be equal to 'false'") + assert.Equal(t, "false", builder.String(), "string(b) must be equal to 'false'") } func TestEncoderBoolPoolError(t *testing.T) { - enc := BorrowEncoder(nil) + builder := &strings.Builder{} + enc := BorrowEncoder(builder) enc.Release() defer func() { err := recover() @@ -31,6 +35,14 @@ func TestEncoderBoolPoolError(t *testing.T) { assert.IsType(t, InvalidUsagePooledEncoderError(""), err, "err should be of type InvalidUsagePooledEncoderError") assert.Equal(t, "Invalid usage of pooled encoder", err.(InvalidUsagePooledEncoderError).Error(), "err should be of type InvalidUsagePooledEncoderError") }() - _, _ = enc.EncodeBool(false) + _ = enc.EncodeBool(false) assert.True(t, false, "should not be called as it should have panicked") } +func TestEncoderBoolPoolEncoderAPIWriteError(t *testing.T) { + v := true + w := TestWriterError("") + enc := BorrowEncoder(w) + defer enc.Release() + err := enc.EncodeBool(v) + assert.NotNil(t, err, "err should not be nil") +} diff --git a/encode_interface.go b/encode_interface.go @@ -9,75 +9,75 @@ import ( // // If Encode cannot find a way to encode the type to JSON // it will return an InvalidMarshalError. -func (enc *Encoder) Encode(v interface{}) ([]byte, error) { +func (enc *Encoder) Encode(v interface{}) error { if enc.isPooled == 1 { panic(InvalidUsagePooledEncoderError("Invalid usage of pooled encoder")) } switch vt := v.(type) { case string: - return enc.encodeString(vt) + return enc.EncodeString(vt) case bool: - return enc.encodeBool(vt) + return enc.EncodeBool(vt) case MarshalerArray: - return enc.encodeArray(vt) + return enc.EncodeArray(vt) case MarshalerObject: - return enc.encodeObject(vt) + return enc.EncodeObject(vt) case int: - return enc.encodeInt(int64(vt)) + return enc.EncodeInt(vt) case int64: - return enc.encodeInt(vt) + return enc.EncodeInt64(vt) case int32: - return enc.encodeInt(int64(vt)) + return enc.EncodeInt(int(vt)) case int8: - return enc.encodeInt(int64(vt)) + return enc.EncodeInt(int(vt)) case uint64: - return enc.encodeInt(int64(vt)) + return enc.EncodeInt(int(vt)) case uint32: - return enc.encodeInt(int64(vt)) + return enc.EncodeInt(int(vt)) case uint16: - return enc.encodeInt(int64(vt)) + return enc.EncodeInt(int(vt)) case uint8: - return enc.encodeInt(int64(vt)) + return enc.EncodeInt(int(vt)) case float64: - return enc.encodeFloat(vt) + return enc.EncodeFloat(vt) case float32: - return enc.encodeFloat(float64(vt)) + return enc.EncodeFloat32(vt) default: - return nil, InvalidMarshalError(fmt.Sprintf(invalidMarshalErrorMsg, reflect.TypeOf(vt).String())) + return InvalidMarshalError(fmt.Sprintf(invalidMarshalErrorMsg, reflect.TypeOf(vt).String())) } } // AddInterface adds an interface{} to be encoded, must be used inside a slice or array encoding (does not encode a key) func (enc *Encoder) AddInterface(value interface{}) error { - switch value.(type) { + switch vt := value.(type) { case string: - return enc.AddString(value.(string)) + return enc.AddString(vt) case bool: - return enc.AddBool(value.(bool)) + return enc.AddBool(vt) case MarshalerArray: - return enc.AddArray(value.(MarshalerArray)) + return enc.AddArray(vt) case MarshalerObject: - return enc.AddObject(value.(MarshalerObject)) + return enc.AddObject(vt) case int: - return enc.AddInt(value.(int)) + return enc.AddInt(vt) case int64: - return enc.AddInt(int(value.(int64))) + return enc.AddInt(int(vt)) case int32: - return enc.AddInt(int(value.(int32))) + return enc.AddInt(int(vt)) case int8: - return enc.AddInt(int(value.(int8))) + return enc.AddInt(int(vt)) case uint64: - return enc.AddInt(int(value.(uint64))) + return enc.AddInt(int(vt)) case uint32: - return enc.AddInt(int(value.(uint32))) + return enc.AddInt(int(vt)) case uint16: - return enc.AddInt(int(value.(uint16))) + return enc.AddInt(int(vt)) case uint8: - return enc.AddInt(int(value.(uint8))) + return enc.AddInt(int(vt)) case float64: - return enc.AddFloat(value.(float64)) + return enc.AddFloat(vt) case float32: - return enc.AddFloat(float64(value.(float32))) + return enc.AddFloat32(vt) } return nil diff --git a/encode_interface_test.go b/encode_interface_test.go @@ -3,6 +3,7 @@ package gojay import ( "fmt" "reflect" + "strings" "testing" "github.com/stretchr/testify/assert" @@ -10,109 +11,116 @@ import ( var encoderTestCases = []struct { v interface{} - expectations func(t *testing.T, b []byte, err error) + expectations func(t *testing.T, b string, err error) }{ { v: 100, - expectations: func(t *testing.T, b []byte, err error) { + expectations: func(t *testing.T, b string, err error) { assert.Nil(t, err, "err should be nil") - assert.Equal(t, "100", string(b), "string(b) should equal 100") + assert.Equal(t, "100", b, "b should equal 100") }, }, { v: int64(100), - expectations: func(t *testing.T, b []byte, err error) { + expectations: func(t *testing.T, b string, err error) { assert.Nil(t, err, "err should be nil") - assert.Equal(t, "100", string(b), "string(b) should equal 100") + assert.Equal(t, "100", b, "b should equal 100") }, }, { v: int32(100), - expectations: func(t *testing.T, b []byte, err error) { + expectations: func(t *testing.T, b string, err error) { assert.Nil(t, err, "err should be nil") - assert.Equal(t, "100", string(b), "string(b) should equal 100") + assert.Equal(t, "100", b, "b should equal 100") }, }, { v: int8(100), - expectations: func(t *testing.T, b []byte, err error) { + expectations: func(t *testing.T, b string, err error) { assert.Nil(t, err, "err should be nil") - assert.Equal(t, "100", string(b), "string(b) should equal 100") + assert.Equal(t, "100", b, "b should equal 100") }, }, { v: uint64(100), - expectations: func(t *testing.T, b []byte, err error) { + expectations: func(t *testing.T, b string, err error) { assert.Nil(t, err, "err should be nil") - assert.Equal(t, "100", string(b), "string(b) should equal 100") + assert.Equal(t, "100", b, "b should equal 100") }, }, { v: uint32(100), - expectations: func(t *testing.T, b []byte, err error) { + expectations: func(t *testing.T, b string, err error) { assert.Nil(t, err, "err should be nil") - assert.Equal(t, "100", string(b), "string(b) should equal 100") + assert.Equal(t, "100", b, "b should equal 100") }, }, { v: uint16(100), - expectations: func(t *testing.T, b []byte, err error) { + expectations: func(t *testing.T, b string, err error) { assert.Nil(t, err, "err should be nil") - assert.Equal(t, "100", string(b), "string(b) should equal 100") + assert.Equal(t, "100", b, "b should equal 100") }, }, { v: uint8(100), - expectations: func(t *testing.T, b []byte, err error) { + expectations: func(t *testing.T, b string, err error) { assert.Nil(t, err, "err should be nil") - assert.Equal(t, "100", string(b), "string(b) should equal 100") + assert.Equal(t, "100", b, "b should equal 100") }, }, { v: float64(100.12), - expectations: func(t *testing.T, b []byte, err error) { + expectations: func(t *testing.T, b string, err error) { assert.Nil(t, err, "err should be nil") - assert.Equal(t, "100.12", string(b), "string(b) should equal 100.12") + assert.Equal(t, "100.12", b, "b should equal 100.12") + }, + }, + { + v: float32(100.12), + expectations: func(t *testing.T, b string, err error) { + assert.Nil(t, err, "err should be nil") + assert.Equal(t, "100.12", b, "b should equal 100.12") }, }, { v: true, - expectations: func(t *testing.T, b []byte, err error) { + expectations: func(t *testing.T, b string, err error) { assert.Nil(t, err, "err should be nil") - assert.Equal(t, "true", string(b), "string(b) should equal true") + assert.Equal(t, "true", b, "b should equal true") }, }, { v: "hello world", - expectations: func(t *testing.T, b []byte, err error) { + expectations: func(t *testing.T, b string, err error) { assert.Nil(t, err, "err should be nil") - assert.Equal(t, `"hello world"`, string(b), `string(b) should equal "hello world"`) + assert.Equal(t, `"hello world"`, b, `b should equal "hello world"`) }, }, { v: "hello world", - expectations: func(t *testing.T, b []byte, err error) { + expectations: func(t *testing.T, b string, err error) { assert.Nil(t, err, "err should be nil") - assert.Equal(t, `"hello world"`, string(b), `string(b) should equal "hello world"`) + assert.Equal(t, `"hello world"`, b, `b should equal "hello world"`) }, }, { v: &TestEncodingArrStrings{"hello world", "foo bar"}, - expectations: func(t *testing.T, b []byte, err error) { + expectations: func(t *testing.T, b string, err error) { assert.Nil(t, err, "err should be nil") - assert.Equal(t, `["hello world","foo bar"]`, string(b), `string(b) should equal ["hello world","foo bar"]`) + assert.Equal(t, `["hello world","foo bar"]`, b, `b should equal ["hello world","foo bar"]`) }, }, { v: &testObject{"漢字", 1, 1, 1, 1, 1, 1, 1, 1, 1, 1.1, 1.1, true}, - expectations: func(t *testing.T, b []byte, err error) { + expectations: func(t *testing.T, b string, err error) { assert.Nil(t, err, "err should be nil") assert.Equal(t, `{"testStr":"漢字","testInt":1,"testInt64":1,"testInt32":1,"testInt16":1,"testInt8":1,"testUint64":1,"testUint32":1,"testUint16":1,"testUint8":1,"testFloat64":1.1,"testFloat32":1.1,"testBool":true}`, string(b), `string(b) should equal {"testStr":"漢字","testInt":1,"testInt64":1,"testInt32":1,"testInt16":1,"testInt8":1,"testUint64":1,"testUint32":1,"testUint16":1,"testUint8":1,"testFloat64":1.1,"testFloat32":1.1,"testBool":true}`) }, }, { v: &struct{}{}, - expectations: func(t *testing.T, b []byte, err error) { + expectations: func(t *testing.T, b string, err error) { assert.NotNil(t, err, "err should be nil") assert.IsType(t, InvalidMarshalError(""), err, "err should be of type InvalidMarshalError") assert.Equal(t, fmt.Sprintf(invalidMarshalErrorMsg, reflect.TypeOf(&struct{}{}).String()), err.Error(), "err message should be equal to invalidMarshalErrorMsg") @@ -120,18 +128,41 @@ var encoderTestCases = []struct { }, } -func TestEncoderInterfaceAllTypesDecoderAPI(t *testing.T) { +func TestEncoderInterfaceAllTypesEncoderAPI(t *testing.T) { for _, test := range encoderTestCases { - enc := BorrowEncoder(nil) - b, err := enc.Encode(test.v) + builder := &strings.Builder{} + enc := BorrowEncoder(builder) + err := enc.Encode(test.v) enc.Release() - test.expectations(t, b, err) + test.expectations(t, builder.String(), err) } } +func TestEncoderInterfaceAllTypesEncoderAPIWriteError(t *testing.T) { + v := "" + w := TestWriterError("") + enc := BorrowEncoder(w) + err := enc.Encode(v) + assert.NotNil(t, err, "err should not be nil") +} + +func TestEncoderInterfaceAllTypesEncoderAPIPoolError(t *testing.T) { + v := "" + w := TestWriterError("") + enc := BorrowEncoder(w) + enc.Release() + defer func() { + err := recover() + assert.NotNil(t, err, "err should not be nil") + assert.IsType(t, InvalidUsagePooledEncoderError(""), err, "err should be of type InvalidUsagePooledEncoderError") + }() + _ = enc.Encode(v) + assert.True(t, false, "should not be called as decoder should have panicked") +} + func TestEncoderInterfaceAllTypesMarshalAPI(t *testing.T) { for _, test := range encoderTestCases { b, err := Marshal(test.v) - test.expectations(t, b, err) + test.expectations(t, string(b), err) } } diff --git a/encode_number.go b/encode_number.go @@ -3,30 +3,50 @@ package gojay import "strconv" // EncodeInt encodes an int to JSON -func (enc *Encoder) EncodeInt(n int64) ([]byte, error) { +func (enc *Encoder) EncodeInt(n int) error { if enc.isPooled == 1 { panic(InvalidUsagePooledEncoderError("Invalid usage of pooled encoder")) } - return enc.encodeInt(n) + _, _ = enc.encodeInt(n) + _, err := enc.write() + if err != nil { + return err + } + return nil } // encodeInt encodes an int to JSON -func (enc *Encoder) encodeInt(n int64) ([]byte, error) { - s := strconv.Itoa(int(n)) - enc.writeString(s) +func (enc *Encoder) encodeInt(n int) ([]byte, error) { + enc.buf = strconv.AppendInt(enc.buf, int64(n), 10) return enc.buf, nil } -// EncodeFloat encodes a float64 to JSON -func (enc *Encoder) EncodeFloat(n float64) error { +// EncodeInt64 encodes an int64 to JSON +func (enc *Encoder) EncodeInt64(n int64) error { if enc.isPooled == 1 { panic(InvalidUsagePooledEncoderError("Invalid usage of pooled encoder")) } - _, err := enc.encodeFloat(n) + _, _ = enc.encodeInt64(n) + _, err := enc.write() if err != nil { return err } - _, err = enc.write() + return nil +} + +// encodeInt64 encodes an int to JSON +func (enc *Encoder) encodeInt64(n int64) ([]byte, error) { + enc.buf = strconv.AppendInt(enc.buf, n, 10) + return enc.buf, nil +} + +// EncodeFloat encodes a float64 to JSON +func (enc *Encoder) EncodeFloat(n float64) error { + if enc.isPooled == 1 { + panic(InvalidUsagePooledEncoderError("Invalid usage of pooled encoder")) + } + _, _ = enc.encodeFloat(n) + _, err := enc.write() if err != nil { return err } @@ -44,11 +64,8 @@ func (enc *Encoder) EncodeFloat32(n float32) error { if enc.isPooled == 1 { panic(InvalidUsagePooledEncoderError("Invalid usage of pooled encoder")) } - _, err := enc.encodeFloat32(n) - if err != nil { - return err - } - _, err = enc.write() + _, _ = enc.encodeFloat32(n) + _, err := enc.write() if err != nil { return err } @@ -81,6 +98,17 @@ func (enc *Encoder) AddFloat(value float64) error { return nil } +// AddFloat adds a float32 to be encoded, must be used inside a slice or array encoding (does not encode a key) +func (enc *Encoder) AddFloat32(value float32) error { + r, ok := enc.getPreviousRune() + if ok && r != '[' { + enc.writeByte(',') + } + enc.buf = strconv.AppendFloat(enc.buf, float64(value), 'f', -1, 32) + + return nil +} + // AddIntKey adds an int to be encoded, must be used inside an object as it will encode a key func (enc *Encoder) AddIntKey(key string, value int) error { r, ok := enc.getPreviousRune() diff --git a/encode_number_test.go b/encode_number_test.go @@ -1,6 +1,7 @@ package gojay import ( + "strings" "testing" "github.com/stretchr/testify/assert" @@ -15,6 +16,36 @@ func TestEncoderInt(t *testing.T) { string(r), "Result of marshalling is different as the one expected") } +func TestEncoderIntEncodeAPI(t *testing.T) { + builder := &strings.Builder{} + enc := NewEncoder(builder) + err := enc.EncodeInt(1) + assert.Nil(t, err, "Error should be nil") + assert.Equal( + t, + `1`, + builder.String(), + "Result of marshalling is different as the one expected") +} +func TestEncoderIntEncodeAPIPoolError(t *testing.T) { + builder := &strings.Builder{} + enc := NewEncoder(builder) + enc.Release() + defer func() { + err := recover() + assert.NotNil(t, err, "err should not be nil") + assert.IsType(t, InvalidUsagePooledEncoderError(""), err, "err should be of type InvalidUsagePooledEncoderError") + }() + _ = enc.EncodeInt(1) + assert.True(t, false, "should not be called as decoder should have panicked") +} +func TestEncoderIntEncodeAPIWriteError(t *testing.T) { + w := TestWriterError("") + enc := NewEncoder(w) + err := enc.EncodeInt(1) + assert.NotNil(t, err, "err should not be nil") + assert.Equal(t, "Test Error", err.Error(), "err should be of type InvalidUsagePooledEncoderError") +} func TestEncoderInt64(t *testing.T) { r, err := Marshal(int64(1)) @@ -25,6 +56,36 @@ func TestEncoderInt64(t *testing.T) { string(r), "Result of marshalling is different as the one expected") } +func TestEncoderInt64EncodeAPI(t *testing.T) { + builder := &strings.Builder{} + enc := NewEncoder(builder) + err := enc.EncodeInt64(int64(1)) + assert.Nil(t, err, "Error should be nil") + assert.Equal( + t, + `1`, + builder.String(), + "Result of marshalling is different as the one expected") +} +func TestEncoderInt64EncodeAPIPoolError(t *testing.T) { + builder := &strings.Builder{} + enc := NewEncoder(builder) + enc.Release() + defer func() { + err := recover() + assert.NotNil(t, err, "err should not be nil") + assert.IsType(t, InvalidUsagePooledEncoderError(""), err, "err should be of type InvalidUsagePooledEncoderError") + }() + _ = enc.EncodeInt64(1) + assert.True(t, false, "should not be called as decoder should have panicked") +} +func TestEncoderInt64EncodeAPIWriteError(t *testing.T) { + w := TestWriterError("") + enc := NewEncoder(w) + err := enc.EncodeInt64(1) + assert.NotNil(t, err, "err should not be nil") + assert.Equal(t, "Test Error", err.Error(), "err should be of type InvalidUsagePooledEncoderError") +} func TestEncoderInt32(t *testing.T) { r, err := Marshal(int32(1)) @@ -101,6 +162,67 @@ func TestEncoderFloat(t *testing.T) { string(r), "Result of marshalling is different as the one expected") } +func TestEncoderFloatEncodeAPI(t *testing.T) { + builder := &strings.Builder{} + enc := NewEncoder(builder) + err := enc.EncodeFloat(float64(1.1)) + assert.Nil(t, err, "Error should be nil") + assert.Equal( + t, + `1.1`, + builder.String(), + "Result of marshalling is different as the one expected") +} +func TestEncoderFloatEncodeAPIPoolError(t *testing.T) { + builder := &strings.Builder{} + enc := NewEncoder(builder) + enc.Release() + defer func() { + err := recover() + assert.NotNil(t, err, "err should not be nil") + assert.IsType(t, InvalidUsagePooledEncoderError(""), err, "err should be of type InvalidUsagePooledEncoderError") + }() + _ = enc.EncodeFloat(1.1) + assert.True(t, false, "should not be called as decoder should have panicked") +} +func TestEncoderFloatEncodeAPIWriteError(t *testing.T) { + w := TestWriterError("") + enc := NewEncoder(w) + err := enc.EncodeFloat(1.1) + assert.NotNil(t, err, "err should not be nil") + assert.Equal(t, "Test Error", err.Error(), "err should be of type InvalidUsagePooledEncoderError") +} + +func TestEncoderFloat32EncodeAPI(t *testing.T) { + builder := &strings.Builder{} + enc := NewEncoder(builder) + err := enc.EncodeFloat32(float32(1.12)) + assert.Nil(t, err, "Error should be nil") + assert.Equal( + t, + `1.12`, + builder.String(), + "Result of marshalling is different as the one expected") +} +func TestEncoderFloat32EncodeAPIPoolError(t *testing.T) { + builder := &strings.Builder{} + enc := NewEncoder(builder) + enc.Release() + defer func() { + err := recover() + assert.NotNil(t, err, "err should not be nil") + assert.IsType(t, InvalidUsagePooledEncoderError(""), err, "err should be of type InvalidUsagePooledEncoderError") + }() + _ = enc.EncodeFloat32(float32(1.1)) + assert.True(t, false, "should not be called as decoder should have panicked") +} +func TestEncoderFloat32EncodeAPIWriteError(t *testing.T) { + w := TestWriterError("") + enc := NewEncoder(w) + err := enc.EncodeFloat32(float32(1.1)) + assert.NotNil(t, err, "err should not be nil") + assert.Equal(t, "Test Error", err.Error(), "err should be of type InvalidUsagePooledEncoderError") +} func TestEncoderIntPooledError(t *testing.T) { v := 1 @@ -112,7 +234,7 @@ func TestEncoderIntPooledError(t *testing.T) { assert.IsType(t, InvalidUsagePooledEncoderError(""), err, "err should be of type InvalidUsagePooledEncoderError") assert.Equal(t, "Invalid usage of pooled encoder", err.(InvalidUsagePooledEncoderError).Error(), "err should be of type InvalidUsagePooledDecoderError") }() - _, _ = enc.EncodeInt(int64(v)) + _ = enc.EncodeInt(v) assert.True(t, false, "should not be called as it should have panicked") } diff --git a/encode_object_test.go b/encode_object_test.go @@ -1,7 +1,6 @@ package gojay import ( - "errors" "strings" "testing" @@ -67,14 +66,8 @@ func TestEncoderObjectBasicEncoderApi(t *testing.T) { ) } -type TestWiterError string - -func (t TestWiterError) Write(b []byte) (int, error) { - return 0, errors.New("Test Error") -} - func TestEncoderObjectBasicEncoderApiError(t *testing.T) { - w := TestWiterError("") + w := TestWriterError("") enc := NewEncoder(w) err := enc.EncodeObject(&testObject{"漢字", 1, 1, 1, 1, 1, 1, 1, 1, 1, 1.1, 1.1, true}) assert.NotNil(t, err, "Error should not be nil") diff --git a/encode_pool.go b/encode_pool.go @@ -29,9 +29,9 @@ func BorrowEncoder(w io.Writer) *Encoder { // Release sends back a Encoder to the pool. func (enc *Encoder) Release() { enc.buf = nil + enc.isPooled = 1 select { case encPool <- enc: - enc.isPooled = 1 default: } } diff --git a/encode_stream.go b/encode_stream.go @@ -145,11 +145,7 @@ func consume(init *StreamEncoder, s *StreamEncoder, m MarshalerStream) { return } i, err := s.Encoder.write() - if i == 0 { - init.Cancel(err) - return - } - if err != nil { + if err != nil || i == 0 { init.Cancel(err) return } diff --git a/encode_stream_test.go b/encode_stream_test.go @@ -110,6 +110,36 @@ func TestEncodeStreamSingleConsumerMarshalError(t *testing.T) { assert.NotNil(t, enc.Err(), "enc.Err() should not be nil") } } + +func TestEncodeStreamSingleConsumerWriteError(t *testing.T) { + // create our writer + w := TestWriterError("") + enc := Stream.NewEncoder(w).LineDelimited() + s := StreamChanObject(make(chan *testObject)) + go enc.EncodeStream(s) + go feedStream(s, 100) + select { + case <-enc.Done(): + assert.NotNil(t, enc.Err(), "enc.Err() should not be nil") + } +} +func TestEncodeStreamSingleConsumerNilValue(t *testing.T) { + expectedStr := `` + // create our writer + w := &TestWriter{target: 100, mux: &sync.RWMutex{}} + enc := Stream.NewEncoder(w).LineDelimited() + w.enc = enc + s := StreamChanObject(make(chan *testObject)) + go enc.EncodeStream(s) + go feedStreamNil(s, 100) + select { + case <-enc.Done(): + assert.Nil(t, enc.Err(), "enc.Err() should not be nil") + for _, b := range w.result { + assert.Equal(t, expectedStr, string(b), "every byte buffer should be equal to expected string") + } + } +} func TestEncodeStreamSingleConsumerCommaDelimited(t *testing.T) { expectedStr := `{"testStr":"","testInt":0,"testInt64":0,"testInt32":0,"testInt16":0,"testInt8":0,"testUint64":0,"testUint32":0,"testUint16":0,"testUint8":0,"testFloat64":0,"testFloat32":0,"testBool":false},` @@ -170,6 +200,12 @@ func (w *TestWriter) Write(b []byte) (int, error) { return len(b), nil } +func feedStreamNil(s chan *testObject, target int) { + for i := 0; i < target; i++ { + s <- nil + } +} + func feedStream(s chan *testObject, target int) { for i := 0; i < target; i++ { s <- &testObject{} diff --git a/encode_string.go b/encode_string.go @@ -1,11 +1,17 @@ package gojay // EncodeString encodes a string to -func (enc *Encoder) EncodeString(s string) ([]byte, error) { +func (enc *Encoder) EncodeString(s string) error { if enc.isPooled == 1 { panic(InvalidUsagePooledEncoderError("Invalid usage of pooled encoder")) } - return enc.encodeString(s) + _, _ = enc.encodeString(s) + _, err := enc.write() + if err != nil { + enc.err = err + return err + } + return nil } // encodeString encodes a string to diff --git a/encode_string_test.go b/encode_string_test.go @@ -1,6 +1,7 @@ package gojay import ( + "strings" "testing" "github.com/stretchr/testify/assert" @@ -15,6 +16,17 @@ func TestEncoderString(t *testing.T) { string(r), "Result of marshalling is different as the one expected") } +func TestEncoderStringEncodeAPI(t *testing.T) { + builder := &strings.Builder{} + enc := NewEncoder(builder) + err := enc.EncodeString("漢字") + assert.Nil(t, err, "Error should be nil") + assert.Equal( + t, + `"漢字"`, + builder.String(), + "Result of marshalling is different as the one expected") +} func TestEncoderStringUTF8(t *testing.T) { r, err := Marshal("漢字") @@ -36,6 +48,15 @@ func TestEncoderStringPooledError(t *testing.T) { assert.IsType(t, InvalidUsagePooledEncoderError(""), err, "err should be of type InvalidUsagePooledEncoderError") assert.Equal(t, "Invalid usage of pooled encoder", err.(InvalidUsagePooledEncoderError).Error(), "err should be of type InvalidUsagePooledDecoderError") }() - _, _ = enc.EncodeString(v) + _ = enc.EncodeString(v) assert.True(t, false, "should not be called as it should have panicked") } + +func TestEncoderStringPoolEncoderAPIWriteError(t *testing.T) { + v := "test" + w := TestWriterError("") + enc := BorrowEncoder(w) + defer enc.Release() + err := enc.EncodeString(v) + assert.NotNil(t, err, "err should not be nil") +} diff --git a/encode_test.go b/encode_test.go @@ -1 +1,9 @@ package gojay + +import "errors" + +type TestWriterError string + +func (t TestWriterError) Write(b []byte) (int, error) { + return 0, errors.New("Test Error") +}