| // Copyright 2019 The Go Authors. All rights reserved. |
| // Use of this source code is governed by a BSD-style |
| // license that can be found in the LICENSE file. |
| |
| package json_test |
| |
| import ( |
| "math" |
| "strings" |
| "testing" |
| |
| "github.com/google/go-cmp/cmp" |
| "github.com/google/go-cmp/cmp/cmpopts" |
| |
| "google.golang.org/protobuf/internal/detrand" |
| "google.golang.org/protobuf/internal/encoding/json" |
| ) |
| |
| // Disable detrand to enable direct comparisons on outputs. |
| func init() { detrand.Disable() } |
| |
| // splitLines is a cmpopts.Option for comparing strings with line breaks. |
| var splitLines = cmpopts.AcyclicTransformer("SplitLines", func(s string) []string { |
| return strings.Split(s, "\n") |
| }) |
| |
| func TestEncoder(t *testing.T) { |
| tests := []struct { |
| desc string |
| write func(*json.Encoder) |
| wantOut string |
| wantOutIndent string |
| }{ |
| { |
| desc: "null", |
| write: func(e *json.Encoder) { |
| e.WriteNull() |
| }, |
| wantOut: `null`, |
| }, |
| { |
| desc: "true", |
| write: func(e *json.Encoder) { |
| e.WriteBool(true) |
| }, |
| wantOut: `true`, |
| }, |
| { |
| desc: "false", |
| write: func(e *json.Encoder) { |
| e.WriteBool(false) |
| }, |
| wantOut: `false`, |
| }, |
| { |
| desc: "string", |
| write: func(e *json.Encoder) { |
| e.WriteString("hello world") |
| }, |
| wantOut: `"hello world"`, |
| }, |
| { |
| desc: "string contains escaped characters", |
| write: func(e *json.Encoder) { |
| e.WriteString("\u0000\"\\/\b\f\n\r\t") |
| }, |
| wantOut: `"\u0000\"\\/\b\f\n\r\t"`, |
| }, |
| { |
| desc: "float64", |
| write: func(e *json.Encoder) { |
| e.WriteFloat(1.0199999809265137, 64) |
| }, |
| wantOut: `1.0199999809265137`, |
| }, |
| { |
| desc: "float64 max value", |
| write: func(e *json.Encoder) { |
| e.WriteFloat(math.MaxFloat64, 64) |
| }, |
| wantOut: `1.7976931348623157e+308`, |
| }, |
| { |
| desc: "float64 min value", |
| write: func(e *json.Encoder) { |
| e.WriteFloat(-math.MaxFloat64, 64) |
| }, |
| wantOut: `-1.7976931348623157e+308`, |
| }, |
| { |
| desc: "float64 NaN", |
| write: func(e *json.Encoder) { |
| e.WriteFloat(math.NaN(), 64) |
| }, |
| wantOut: `"NaN"`, |
| }, |
| { |
| desc: "float64 Infinity", |
| write: func(e *json.Encoder) { |
| e.WriteFloat(math.Inf(+1), 64) |
| }, |
| wantOut: `"Infinity"`, |
| }, |
| { |
| desc: "float64 -Infinity", |
| write: func(e *json.Encoder) { |
| e.WriteFloat(math.Inf(-1), 64) |
| }, |
| wantOut: `"-Infinity"`, |
| }, |
| { |
| desc: "float64 negative zero", |
| write: func(e *json.Encoder) { |
| e.WriteFloat(math.Copysign(0, -1), 64) |
| }, |
| wantOut: `-0`, |
| }, |
| { |
| desc: "float32", |
| write: func(e *json.Encoder) { |
| e.WriteFloat(1.02, 32) |
| }, |
| wantOut: `1.02`, |
| }, |
| { |
| desc: "float32 max value", |
| write: func(e *json.Encoder) { |
| e.WriteFloat(math.MaxFloat32, 32) |
| }, |
| wantOut: `3.4028235e+38`, |
| }, |
| { |
| desc: "float32 min value", |
| write: func(e *json.Encoder) { |
| e.WriteFloat(-math.MaxFloat32, 32) |
| }, |
| wantOut: `-3.4028235e+38`, |
| }, |
| { |
| desc: "float32 negative zero", |
| write: func(e *json.Encoder) { |
| e.WriteFloat(math.Copysign(0, -1), 32) |
| }, |
| wantOut: `-0`, |
| }, |
| { |
| desc: "int", |
| write: func(e *json.Encoder) { |
| e.WriteInt(-math.MaxInt64) |
| }, |
| wantOut: `-9223372036854775807`, |
| }, |
| { |
| desc: "uint", |
| write: func(e *json.Encoder) { |
| e.WriteUint(math.MaxUint64) |
| }, |
| wantOut: `18446744073709551615`, |
| }, |
| { |
| desc: "empty object", |
| write: func(e *json.Encoder) { |
| e.StartObject() |
| e.EndObject() |
| }, |
| wantOut: `{}`, |
| }, |
| { |
| desc: "empty array", |
| write: func(e *json.Encoder) { |
| e.StartArray() |
| e.EndArray() |
| }, |
| wantOut: `[]`, |
| }, |
| { |
| desc: "object with one member", |
| write: func(e *json.Encoder) { |
| e.StartObject() |
| e.WriteName("hello") |
| e.WriteString("world") |
| e.EndObject() |
| }, |
| wantOut: `{"hello":"world"}`, |
| wantOutIndent: `{ |
| "hello": "world" |
| }`, |
| }, |
| { |
| desc: "array with one member", |
| write: func(e *json.Encoder) { |
| e.StartArray() |
| e.WriteNull() |
| e.EndArray() |
| }, |
| wantOut: `[null]`, |
| wantOutIndent: `[ |
| null |
| ]`, |
| }, |
| { |
| desc: "simple object", |
| write: func(e *json.Encoder) { |
| e.StartObject() |
| { |
| e.WriteName("null") |
| e.WriteNull() |
| } |
| { |
| e.WriteName("bool") |
| e.WriteBool(true) |
| } |
| { |
| e.WriteName("string") |
| e.WriteString("hello") |
| } |
| { |
| e.WriteName("float") |
| e.WriteFloat(6.28318, 64) |
| } |
| { |
| e.WriteName("int") |
| e.WriteInt(42) |
| } |
| { |
| e.WriteName("uint") |
| e.WriteUint(47) |
| } |
| e.EndObject() |
| }, |
| wantOut: `{"null":null,"bool":true,"string":"hello","float":6.28318,"int":42,"uint":47}`, |
| wantOutIndent: `{ |
| "null": null, |
| "bool": true, |
| "string": "hello", |
| "float": 6.28318, |
| "int": 42, |
| "uint": 47 |
| }`, |
| }, |
| { |
| desc: "simple array", |
| write: func(e *json.Encoder) { |
| e.StartArray() |
| { |
| e.WriteString("hello") |
| e.WriteFloat(6.28318, 32) |
| e.WriteInt(42) |
| e.WriteUint(47) |
| e.WriteBool(true) |
| e.WriteNull() |
| } |
| e.EndArray() |
| }, |
| wantOut: `["hello",6.28318,42,47,true,null]`, |
| wantOutIndent: `[ |
| "hello", |
| 6.28318, |
| 42, |
| 47, |
| true, |
| null |
| ]`, |
| }, |
| { |
| desc: "fancy object", |
| write: func(e *json.Encoder) { |
| e.StartObject() |
| { |
| e.WriteName("object0") |
| e.StartObject() |
| e.EndObject() |
| } |
| { |
| e.WriteName("array0") |
| e.StartArray() |
| e.EndArray() |
| } |
| { |
| e.WriteName("object1") |
| e.StartObject() |
| { |
| e.WriteName("null") |
| e.WriteNull() |
| } |
| { |
| e.WriteName("object1-1") |
| e.StartObject() |
| { |
| e.WriteName("bool") |
| e.WriteBool(false) |
| } |
| { |
| e.WriteName("float") |
| e.WriteFloat(3.14159, 32) |
| } |
| e.EndObject() |
| } |
| e.EndObject() |
| } |
| { |
| e.WriteName("array1") |
| e.StartArray() |
| { |
| e.WriteNull() |
| e.StartObject() |
| e.EndObject() |
| e.StartObject() |
| { |
| e.WriteName("hello") |
| e.WriteString("world") |
| } |
| { |
| e.WriteName("hola") |
| e.WriteString("mundo") |
| } |
| e.EndObject() |
| e.StartArray() |
| { |
| e.WriteUint(1) |
| e.WriteUint(0) |
| e.WriteUint(1) |
| } |
| e.EndArray() |
| } |
| e.EndArray() |
| } |
| e.EndObject() |
| }, |
| wantOutIndent: `{ |
| "object0": {}, |
| "array0": [], |
| "object1": { |
| "null": null, |
| "object1-1": { |
| "bool": false, |
| "float": 3.14159 |
| } |
| }, |
| "array1": [ |
| null, |
| {}, |
| { |
| "hello": "world", |
| "hola": "mundo" |
| }, |
| [ |
| 1, |
| 0, |
| 1 |
| ] |
| ] |
| }`, |
| }} |
| |
| for _, tc := range tests { |
| t.Run(tc.desc, func(t *testing.T) { |
| if tc.wantOut != "" { |
| enc, err := json.NewEncoder("") |
| if err != nil { |
| t.Fatalf("NewEncoder() returned error: %v", err) |
| } |
| tc.write(enc) |
| got := string(enc.Bytes()) |
| if got != tc.wantOut { |
| t.Errorf("%s:\n<got>:\n%v\n<want>\n%v\n", tc.desc, got, tc.wantOut) |
| } |
| } |
| if tc.wantOutIndent != "" { |
| enc, err := json.NewEncoder("\t") |
| if err != nil { |
| t.Fatalf("NewEncoder() returned error: %v", err) |
| } |
| tc.write(enc) |
| got, want := string(enc.Bytes()), tc.wantOutIndent |
| if got != want { |
| t.Errorf("%s(indent):\n<got>:\n%v\n<want>\n%v\n<diff -want +got>\n%v\n", |
| tc.desc, got, want, cmp.Diff(want, got, splitLines)) |
| } |
| } |
| }) |
| } |
| } |
| |
| func TestWriteStringError(t *testing.T) { |
| tests := []string{"abc\xff"} |
| |
| for _, in := range tests { |
| t.Run(in, func(t *testing.T) { |
| enc, err := json.NewEncoder("") |
| if err != nil { |
| t.Fatalf("NewEncoder() returned error: %v", err) |
| } |
| if err := enc.WriteString(in); err == nil { |
| t.Errorf("WriteString(%v): got nil error, want error", in) |
| } |
| }) |
| } |
| } |