github.com/neilotoole/jsoncolor@v0.7.2-0.20231115150201-1637fae69be1/jsoncolor_test.go (about)

     1  package jsoncolor_test
     2  
     3  import (
     4  	"bytes"
     5  	"fmt"
     6  	"io/ioutil"
     7  	"testing"
     8  
     9  	"github.com/segmentio/encoding/json"
    10  
    11  	"github.com/neilotoole/jsoncolor"
    12  	"github.com/stretchr/testify/require"
    13  
    14  	stdjson "encoding/json"
    15  )
    16  
    17  // TestPackageDropIn checks that jsoncolor satisfies basic requirements
    18  // to be a drop-in for encoding/json.
    19  func TestPackageDropIn(t *testing.T) {
    20  	// Verify encoding/json types exists
    21  	var (
    22  		_ = jsoncolor.Decoder{}
    23  		_ = jsoncolor.Delim(0)
    24  		_ = jsoncolor.Encoder{}
    25  		_ = jsoncolor.InvalidUnmarshalError{}
    26  		_ = jsoncolor.Marshaler(nil)
    27  		_ = jsoncolor.MarshalerError{}
    28  		_ = jsoncolor.Number("0")
    29  		_ = jsoncolor.RawMessage{}
    30  		_ = jsoncolor.SyntaxError{}
    31  		_ = jsoncolor.Token(nil)
    32  		_ = jsoncolor.UnmarshalTypeError{}
    33  		_ = jsoncolor.Unmarshaler(nil)
    34  		_ = jsoncolor.UnsupportedTypeError{}
    35  		_ = jsoncolor.UnsupportedValueError{}
    36  	)
    37  
    38  	const prefix, indent = "", "  "
    39  
    40  	testCases := []string{"testdata/sakila_actor.json", "testdata/sakila_payment.json"}
    41  	for _, tc := range testCases {
    42  		tc := tc
    43  		t.Run(tc, func(t *testing.T) {
    44  			b, readErr := ioutil.ReadFile(tc)
    45  			require.NoError(t, readErr)
    46  
    47  			// Test json.Valid equivalence
    48  			fv1, fv2 := json.Valid, jsoncolor.Valid
    49  			require.Equal(t, fv1(b), fv2(b))
    50  
    51  			// Test json.Unmarshal equivalence
    52  			fu1, fu2 := json.Unmarshal, jsoncolor.Unmarshal
    53  			var m1, m2 interface{}
    54  			err1, err2 := fu1(b, &m1), fu2(b, &m2)
    55  			require.NoError(t, err1)
    56  			require.NoError(t, err2)
    57  			require.EqualValues(t, m1, m2)
    58  
    59  			// Test json.Marshal equivalence
    60  			fm1, fm2 := json.Marshal, jsoncolor.Marshal
    61  			gotMarshalB1, err1 := fm1(m1)
    62  			require.NoError(t, err1)
    63  			gotMarshalB2, err2 := fm2(m1)
    64  			require.NoError(t, err2)
    65  			require.Equal(t, gotMarshalB1, gotMarshalB2)
    66  
    67  			// Test json.MarshalIndent equivalence
    68  			fmi1, fmi2 := json.MarshalIndent, jsoncolor.MarshalIndent
    69  			gotMarshallIndentB1, err1 := fmi1(m1, prefix, indent)
    70  			require.NoError(t, err1)
    71  			gotMarshalIndentB2, err2 := fmi2(m1, prefix, indent)
    72  			require.NoError(t, err2)
    73  			require.Equal(t, gotMarshallIndentB1, gotMarshalIndentB2)
    74  
    75  			// Test json.Compact equivalence
    76  			fc1, fc2 := json.Compact, jsoncolor.Compact
    77  			buf1, buf2 := &bytes.Buffer{}, &bytes.Buffer{}
    78  			err1 = fc1(buf1, gotMarshallIndentB1)
    79  			require.NoError(t, err1)
    80  			err2 = fc2(buf2, gotMarshalIndentB2)
    81  			require.NoError(t, err2)
    82  			require.Equal(t, buf1.Bytes(), buf2.Bytes())
    83  			// Double-check
    84  			require.Equal(t, buf1.Bytes(), gotMarshalB1)
    85  			require.Equal(t, buf2.Bytes(), gotMarshalB2)
    86  			buf1.Reset()
    87  			buf2.Reset()
    88  
    89  			// Test json.Indent equivalence
    90  			fi1, fi2 := json.Indent, jsoncolor.Indent
    91  			err1 = fi1(buf1, gotMarshalB1, prefix, indent)
    92  			require.NoError(t, err1)
    93  			err2 = fi2(buf2, gotMarshalB2, prefix, indent)
    94  			require.NoError(t, err2)
    95  			require.Equal(t, buf1.Bytes(), buf2.Bytes())
    96  			buf1.Reset()
    97  			buf2.Reset()
    98  
    99  			// Test json.HTMLEscape equivalence
   100  			fh1, fh2 := json.HTMLEscape, jsoncolor.HTMLEscape
   101  			fh1(buf1, gotMarshalB1)
   102  			fh2(buf2, gotMarshalB2)
   103  			require.Equal(t, buf1.Bytes(), buf2.Bytes())
   104  		})
   105  	}
   106  }
   107  
   108  func TestEncode(t *testing.T) {
   109  	testCases := []struct {
   110  		name    string
   111  		pretty  bool
   112  		color   bool
   113  		sortMap bool
   114  		v       interface{}
   115  		want    string
   116  	}{
   117  		{name: "nil", pretty: false, v: nil, want: "null\n"},
   118  		{name: "slice_empty", pretty: true, v: []int{}, want: "[]\n"},
   119  		{name: "slice_1_pretty", pretty: true, v: []interface{}{1}, want: "[\n  1\n]\n"},
   120  		{name: "slice_1_no_pretty", v: []interface{}{1}, want: "[1]\n"},
   121  		{name: "slice_2_pretty", pretty: true, v: []interface{}{1, true}, want: "[\n  1,\n  true\n]\n"},
   122  		{name: "slice_2_no_pretty", v: []interface{}{1, true}, want: "[1,true]\n"},
   123  		{name: "map_int_empty", pretty: true, v: map[string]int{}, want: "{}\n"},
   124  		{name: "map_interface_empty", pretty: true, v: map[string]interface{}{}, want: "{}\n"},
   125  		{name: "map_interface_empty_sorted", pretty: true, sortMap: true, v: map[string]interface{}{}, want: "{}\n"},
   126  		{name: "map_1_pretty", pretty: true, sortMap: true, v: map[string]interface{}{"one": 1}, want: "{\n  \"one\": 1\n}\n"},
   127  		{name: "map_1_no_pretty", sortMap: true, v: map[string]interface{}{"one": 1}, want: "{\"one\":1}\n"},
   128  		{name: "map_2_pretty", pretty: true, sortMap: true, v: map[string]interface{}{"one": 1, "two": 2}, want: "{\n  \"one\": 1,\n  \"two\": 2\n}\n"},
   129  		{name: "map_2_no_pretty", sortMap: true, v: map[string]interface{}{"one": 1, "two": 2}, want: "{\"one\":1,\"two\":2}\n"},
   130  		{name: "tinystruct", pretty: true, v: TinyStruct{FBool: true}, want: "{\n  \"f_bool\": true\n}\n"},
   131  	}
   132  
   133  	for _, tc := range testCases {
   134  		tc := tc
   135  
   136  		t.Run(tc.name, func(t *testing.T) {
   137  			buf := &bytes.Buffer{}
   138  			enc := jsoncolor.NewEncoder(buf)
   139  			enc.SetEscapeHTML(false)
   140  			enc.SetSortMapKeys(tc.sortMap)
   141  			if tc.pretty {
   142  				enc.SetIndent("", "  ")
   143  			}
   144  			if tc.color {
   145  				clrs := jsoncolor.DefaultColors()
   146  				enc.SetColors(clrs)
   147  			}
   148  
   149  			require.NoError(t, enc.Encode(tc.v))
   150  			require.True(t, stdjson.Valid(buf.Bytes()))
   151  			require.Equal(t, tc.want, buf.String())
   152  		})
   153  	}
   154  }
   155  
   156  func TestEncode_Slice(t *testing.T) {
   157  	testCases := []struct {
   158  		name   string
   159  		pretty bool
   160  		color  bool
   161  		v      []interface{}
   162  		want   string
   163  	}{
   164  		{name: "nil", pretty: true, v: nil, want: "null\n"},
   165  		{name: "empty", pretty: true, v: []interface{}{}, want: "[]\n"},
   166  		{name: "one", pretty: true, v: []interface{}{1}, want: "[\n  1\n]\n"},
   167  		{name: "two", pretty: true, v: []interface{}{1, true}, want: "[\n  1,\n  true\n]\n"},
   168  		{name: "three", pretty: true, v: []interface{}{1, true, "hello"}, want: "[\n  1,\n  true,\n  \"hello\"\n]\n"},
   169  	}
   170  
   171  	for _, tc := range testCases {
   172  		tc := tc
   173  
   174  		t.Run(tc.name, func(t *testing.T) {
   175  			buf := &bytes.Buffer{}
   176  			enc := jsoncolor.NewEncoder(buf)
   177  			enc.SetEscapeHTML(false)
   178  			if tc.pretty {
   179  				enc.SetIndent("", "  ")
   180  			}
   181  			if tc.color {
   182  				enc.SetColors(jsoncolor.DefaultColors())
   183  			}
   184  
   185  			require.NoError(t, enc.Encode(tc.v))
   186  			require.True(t, stdjson.Valid(buf.Bytes()))
   187  			require.Equal(t, tc.want, buf.String())
   188  		})
   189  	}
   190  }
   191  
   192  func TestEncode_SmallStruct(t *testing.T) {
   193  	v := SmallStruct{
   194  		FInt:   7,
   195  		FSlice: []interface{}{64, true},
   196  		FMap: map[string]interface{}{
   197  			"m_float64": 64.64,
   198  			"m_string":  "hello",
   199  		},
   200  		FTinyStruct: TinyStruct{FBool: true},
   201  		FString:     "hello",
   202  	}
   203  
   204  	testCases := []struct {
   205  		pretty bool
   206  		color  bool
   207  		want   string
   208  	}{
   209  		{pretty: false, color: false, want: "{\"f_int\":7,\"f_slice\":[64,true],\"f_map\":{\"m_float64\":64.64,\"m_string\":\"hello\"},\"f_tinystruct\":{\"f_bool\":true},\"f_string\":\"hello\"}\n"},
   210  		{pretty: true, color: false, want: "{\n  \"f_int\": 7,\n  \"f_slice\": [\n    64,\n    true\n  ],\n  \"f_map\": {\n    \"m_float64\": 64.64,\n    \"m_string\": \"hello\"\n  },\n  \"f_tinystruct\": {\n    \"f_bool\": true\n  },\n  \"f_string\": \"hello\"\n}\n"},
   211  	}
   212  
   213  	for _, tc := range testCases {
   214  		tc := tc
   215  
   216  		t.Run(fmt.Sprintf("pretty_%v__color_%v", tc.pretty, tc.color), func(t *testing.T) {
   217  			buf := &bytes.Buffer{}
   218  			enc := jsoncolor.NewEncoder(buf)
   219  			enc.SetEscapeHTML(false)
   220  			enc.SetSortMapKeys(true)
   221  
   222  			if tc.pretty {
   223  				enc.SetIndent("", "  ")
   224  			}
   225  			if tc.color {
   226  				enc.SetColors(jsoncolor.DefaultColors())
   227  			}
   228  
   229  			require.NoError(t, enc.Encode(v))
   230  			require.True(t, stdjson.Valid(buf.Bytes()))
   231  			require.Equal(t, tc.want, buf.String())
   232  		})
   233  	}
   234  }
   235  
   236  func TestEncode_Map_Nested(t *testing.T) {
   237  	v := map[string]interface{}{
   238  		"m_bool1": true,
   239  		"m_nest1": map[string]interface{}{
   240  			"m_nest1_bool": true,
   241  			"m_nest2": map[string]interface{}{
   242  				"m_nest2_bool": true,
   243  				"m_nest3": map[string]interface{}{
   244  					"m_nest3_bool": true,
   245  				},
   246  			},
   247  		},
   248  		"m_string1": "hello",
   249  	}
   250  
   251  	testCases := []struct {
   252  		pretty bool
   253  		color  bool
   254  		want   string
   255  	}{
   256  		{pretty: false, want: "{\"m_bool1\":true,\"m_nest1\":{\"m_nest1_bool\":true,\"m_nest2\":{\"m_nest2_bool\":true,\"m_nest3\":{\"m_nest3_bool\":true}}},\"m_string1\":\"hello\"}\n"},
   257  		{pretty: true, want: "{\n  \"m_bool1\": true,\n  \"m_nest1\": {\n    \"m_nest1_bool\": true,\n    \"m_nest2\": {\n      \"m_nest2_bool\": true,\n      \"m_nest3\": {\n        \"m_nest3_bool\": true\n      }\n    }\n  },\n  \"m_string1\": \"hello\"\n}\n"},
   258  	}
   259  
   260  	for _, tc := range testCases {
   261  		tc := tc
   262  
   263  		t.Run(fmt.Sprintf("pretty_%v__color_%v", tc.pretty, tc.color), func(t *testing.T) {
   264  			buf := &bytes.Buffer{}
   265  			enc := jsoncolor.NewEncoder(buf)
   266  			enc.SetEscapeHTML(false)
   267  			enc.SetSortMapKeys(true)
   268  			if tc.pretty {
   269  				enc.SetIndent("", "  ")
   270  			}
   271  			if tc.color {
   272  				enc.SetColors(jsoncolor.DefaultColors())
   273  			}
   274  
   275  			require.NoError(t, enc.Encode(v))
   276  			require.True(t, stdjson.Valid(buf.Bytes()))
   277  			require.Equal(t, tc.want, buf.String())
   278  		})
   279  	}
   280  }
   281  
   282  // TestEncode_Map_StringNotInterface tests maps with a string key
   283  // but the value type is not interface{}.
   284  // For example, map[string]bool. This test is necessary because the
   285  // encoder has a fast path for map[string]interface{}
   286  func TestEncode_Map_StringNotInterface(t *testing.T) {
   287  	testCases := []struct {
   288  		pretty  bool
   289  		color   bool
   290  		sortMap bool
   291  		v       map[string]bool
   292  		want    string
   293  	}{
   294  		{pretty: false, sortMap: true, v: map[string]bool{}, want: "{}\n"},
   295  		{pretty: false, sortMap: false, v: map[string]bool{}, want: "{}\n"},
   296  		{pretty: true, sortMap: true, v: map[string]bool{}, want: "{}\n"},
   297  		{pretty: true, sortMap: false, v: map[string]bool{}, want: "{}\n"},
   298  		{pretty: false, sortMap: true, v: map[string]bool{"one": true}, want: "{\"one\":true}\n"},
   299  		{pretty: false, sortMap: false, v: map[string]bool{"one": true}, want: "{\"one\":true}\n"},
   300  		{pretty: false, sortMap: true, v: map[string]bool{"one": true, "two": false}, want: "{\"one\":true,\"two\":false}\n"},
   301  		{pretty: true, sortMap: true, v: map[string]bool{"one": true, "two": false}, want: "{\n  \"one\": true,\n  \"two\": false\n}\n"},
   302  	}
   303  
   304  	for _, tc := range testCases {
   305  		tc := tc
   306  
   307  		t.Run(fmt.Sprintf("size_%d__pretty_%v__color_%v", len(tc.v), tc.pretty, tc.color), func(t *testing.T) {
   308  			buf := &bytes.Buffer{}
   309  			enc := jsoncolor.NewEncoder(buf)
   310  			enc.SetEscapeHTML(false)
   311  			enc.SetSortMapKeys(tc.sortMap)
   312  			if tc.pretty {
   313  				enc.SetIndent("", "  ")
   314  			}
   315  			if tc.color {
   316  				enc.SetColors(jsoncolor.DefaultColors())
   317  			}
   318  
   319  			require.NoError(t, enc.Encode(tc.v))
   320  			require.True(t, stdjson.Valid(buf.Bytes()))
   321  			require.Equal(t, tc.want, buf.String())
   322  		})
   323  	}
   324  }
   325  
   326  func TestEncode_RawMessage(t *testing.T) {
   327  	type RawStruct struct {
   328  		FString string               `json:"f_string"`
   329  		FRaw    jsoncolor.RawMessage `json:"f_raw"`
   330  	}
   331  
   332  	raw := jsoncolor.RawMessage(`{"one":1,"two":2}`)
   333  
   334  	testCases := []struct {
   335  		name   string
   336  		pretty bool
   337  		color  bool
   338  		v      interface{}
   339  		want   string
   340  	}{
   341  		{name: "empty", pretty: false, v: jsoncolor.RawMessage(`{}`), want: "{}\n"},
   342  		{name: "no_pretty", pretty: false, v: raw, want: "{\"one\":1,\"two\":2}\n"},
   343  		{name: "pretty", pretty: true, v: raw, want: "{\n  \"one\": 1,\n  \"two\": 2\n}\n"},
   344  		{name: "pretty_struct", pretty: true, v: RawStruct{FString: "hello", FRaw: raw}, want: "{\n  \"f_string\": \"hello\",\n  \"f_raw\": {\n    \"one\": 1,\n    \"two\": 2\n  }\n}\n"},
   345  	}
   346  
   347  	for _, tc := range testCases {
   348  		tc := tc
   349  
   350  		t.Run(tc.name, func(t *testing.T) {
   351  			buf := &bytes.Buffer{}
   352  			enc := jsoncolor.NewEncoder(buf)
   353  			enc.SetEscapeHTML(false)
   354  			enc.SetSortMapKeys(true)
   355  			if tc.pretty {
   356  				enc.SetIndent("", "  ")
   357  			}
   358  			if tc.color {
   359  				enc.SetColors(jsoncolor.DefaultColors())
   360  			}
   361  
   362  			err := enc.Encode(tc.v)
   363  			require.NoError(t, err)
   364  			require.True(t, stdjson.Valid(buf.Bytes()))
   365  			require.Equal(t, tc.want, buf.String())
   366  		})
   367  	}
   368  }
   369  
   370  // TestEncode_Map_StringNotInterface tests map[string]json.RawMessage.
   371  // This test is necessary because the encoder has a fast path
   372  // for map[string]interface{}.
   373  func TestEncode_Map_StringRawMessage(t *testing.T) {
   374  	t.Skipf(`Skipping due to intermittent behavior.
   375  See: https://github.com/neilotoole/jsoncolor/issues/19`)
   376  	raw := jsoncolor.RawMessage(`{"one":1,"two":2}`)
   377  
   378  	testCases := []struct {
   379  		pretty  bool
   380  		color   bool
   381  		sortMap bool
   382  		v       map[string]jsoncolor.RawMessage
   383  		want    string
   384  	}{
   385  		{pretty: false, sortMap: true, v: map[string]jsoncolor.RawMessage{}, want: "{}\n"},
   386  		{pretty: false, sortMap: false, v: map[string]jsoncolor.RawMessage{}, want: "{}\n"},
   387  		{pretty: true, sortMap: true, v: map[string]jsoncolor.RawMessage{}, want: "{}\n"},
   388  		{pretty: true, sortMap: false, v: map[string]jsoncolor.RawMessage{}, want: "{}\n"},
   389  		{pretty: false, sortMap: true, v: map[string]jsoncolor.RawMessage{"msg1": raw, "msg2": raw}, want: "{\"msg1\":{\"one\":1,\"two\":2},\"msg2\":{\"one\":1,\"two\":2}}\n"},
   390  		{pretty: true, sortMap: true, v: map[string]jsoncolor.RawMessage{"msg1": raw, "msg2": raw}, want: "{\n  \"msg1\": {\n    \"one\": 1,\n    \"two\": 2\n  },\n  \"msg2\": {\n    \"one\": 1,\n    \"two\": 2\n  }\n}\n"},
   391  		{pretty: true, sortMap: false, v: map[string]jsoncolor.RawMessage{"msg1": raw}, want: "{\n  \"msg1\": {\n    \"one\": 1,\n    \"two\": 2\n  }\n}\n"},
   392  	}
   393  
   394  	for _, tc := range testCases {
   395  		tc := tc
   396  
   397  		name := fmt.Sprintf("size_%d__pretty_%v__color_%v__sort_%v", len(tc.v), tc.pretty, tc.color, tc.sortMap)
   398  		t.Run(name, func(t *testing.T) {
   399  			buf := &bytes.Buffer{}
   400  			enc := jsoncolor.NewEncoder(buf)
   401  			enc.SetEscapeHTML(false)
   402  			enc.SetSortMapKeys(tc.sortMap)
   403  			if tc.pretty {
   404  				enc.SetIndent("", "  ")
   405  			}
   406  			if tc.color {
   407  				enc.SetColors(jsoncolor.DefaultColors())
   408  			}
   409  
   410  			require.NoError(t, enc.Encode(tc.v))
   411  			require.True(t, stdjson.Valid(buf.Bytes()))
   412  			require.Equal(t, tc.want, buf.String())
   413  		})
   414  	}
   415  }
   416  
   417  func TestEncode_BigStruct(t *testing.T) {
   418  	v := newBigStruct()
   419  
   420  	testCases := []struct {
   421  		pretty bool
   422  		color  bool
   423  		want   string
   424  	}{
   425  		{pretty: false, want: "{\"f_int\":-7,\"f_int8\":-8,\"f_int16\":-16,\"f_int32\":-32,\"f_int64\":-64,\"f_uint\":7,\"f_uint8\":8,\"f_uint16\":16,\"f_uint32\":32,\"f_uint64\":64,\"f_float32\":32.32,\"f_float64\":64.64,\"f_bool\":true,\"f_bytes\":\"aGVsbG8=\",\"f_nil\":null,\"f_string\":\"hello\",\"f_map\":{\"m_bool\":true,\"m_int64\":64,\"m_nil\":null,\"m_smallstruct\":{\"f_int\":7,\"f_slice\":[64,true],\"f_map\":{\"m_float64\":64.64,\"m_string\":\"hello\"},\"f_tinystruct\":{\"f_bool\":true},\"f_string\":\"hello\"},\"m_string\":\"hello\"},\"f_smallstruct\":{\"f_int\":7,\"f_slice\":[64,true],\"f_map\":{\"m_float64\":64.64,\"m_string\":\"hello\"},\"f_tinystruct\":{\"f_bool\":true},\"f_string\":\"hello\"},\"f_interface\":\"hello\",\"f_interfaces\":[64,\"hello\",true]}\n"},
   426  		{pretty: true, want: "{\n  \"f_int\": -7,\n  \"f_int8\": -8,\n  \"f_int16\": -16,\n  \"f_int32\": -32,\n  \"f_int64\": -64,\n  \"f_uint\": 7,\n  \"f_uint8\": 8,\n  \"f_uint16\": 16,\n  \"f_uint32\": 32,\n  \"f_uint64\": 64,\n  \"f_float32\": 32.32,\n  \"f_float64\": 64.64,\n  \"f_bool\": true,\n  \"f_bytes\": \"aGVsbG8=\",\n  \"f_nil\": null,\n  \"f_string\": \"hello\",\n  \"f_map\": {\n    \"m_bool\": true,\n    \"m_int64\": 64,\n    \"m_nil\": null,\n    \"m_smallstruct\": {\n      \"f_int\": 7,\n      \"f_slice\": [\n        64,\n        true\n      ],\n      \"f_map\": {\n        \"m_float64\": 64.64,\n        \"m_string\": \"hello\"\n      },\n      \"f_tinystruct\": {\n        \"f_bool\": true\n      },\n      \"f_string\": \"hello\"\n    },\n    \"m_string\": \"hello\"\n  },\n  \"f_smallstruct\": {\n    \"f_int\": 7,\n    \"f_slice\": [\n      64,\n      true\n    ],\n    \"f_map\": {\n      \"m_float64\": 64.64,\n      \"m_string\": \"hello\"\n    },\n    \"f_tinystruct\": {\n      \"f_bool\": true\n    },\n    \"f_string\": \"hello\"\n  },\n  \"f_interface\": \"hello\",\n  \"f_interfaces\": [\n    64,\n    \"hello\",\n    true\n  ]\n}\n"},
   427  	}
   428  
   429  	for _, tc := range testCases {
   430  		tc := tc
   431  
   432  		t.Run(fmt.Sprintf("pretty_%v__color_%v", tc.pretty, tc.color), func(t *testing.T) {
   433  			buf := &bytes.Buffer{}
   434  			enc := jsoncolor.NewEncoder(buf)
   435  			enc.SetEscapeHTML(false)
   436  			enc.SetSortMapKeys(true)
   437  			if tc.pretty {
   438  				enc.SetIndent("", "  ")
   439  			}
   440  			if tc.color {
   441  				enc.SetColors(jsoncolor.DefaultColors())
   442  			}
   443  
   444  			require.NoError(t, enc.Encode(v))
   445  			require.True(t, stdjson.Valid(buf.Bytes()))
   446  			require.Equal(t, tc.want, buf.String())
   447  		})
   448  	}
   449  }
   450  
   451  // TestEncode_Map_Not_StringInterface tests map encoding where
   452  // the map is not map[string]interface{} (for which the encoder
   453  // has a fast path).
   454  //
   455  // NOTE: Currently the encoder is broken wrt colors enabled
   456  // for non-string map keys, though that is kinda JSON-illegal anyway.
   457  func TestEncode_Map_Not_StringInterface(t *testing.T) {
   458  	buf := &bytes.Buffer{}
   459  	enc := jsoncolor.NewEncoder(buf)
   460  	enc.SetEscapeHTML(false)
   461  	enc.SetSortMapKeys(true)
   462  	enc.SetColors(jsoncolor.DefaultColors())
   463  	enc.SetIndent("", "  ")
   464  
   465  	v := map[int32]string{
   466  		0: "zero",
   467  		1: "one",
   468  		2: "two",
   469  	}
   470  
   471  	require.NoError(t, enc.Encode(v))
   472  	require.False(t, stdjson.Valid(buf.Bytes()),
   473  		"expected to be invalid JSON because the encoder currently doesn't handle maps with non-string keys")
   474  }
   475  
   476  // BigStruct is a big test struct.
   477  type BigStruct struct {
   478  	FInt         int                    `json:"f_int"`
   479  	FInt8        int8                   `json:"f_int8"`
   480  	FInt16       int16                  `json:"f_int16"`
   481  	FInt32       int32                  `json:"f_int32"`
   482  	FInt64       int64                  `json:"f_int64"`
   483  	FUint        uint                   `json:"f_uint"`
   484  	FUint8       uint8                  `json:"f_uint8"`
   485  	FUint16      uint16                 `json:"f_uint16"`
   486  	FUint32      uint32                 `json:"f_uint32"`
   487  	FUint64      uint64                 `json:"f_uint64"`
   488  	FFloat32     float32                `json:"f_float32"`
   489  	FFloat64     float64                `json:"f_float64"`
   490  	FBool        bool                   `json:"f_bool"`
   491  	FBytes       []byte                 `json:"f_bytes"`
   492  	FNil         interface{}            `json:"f_nil"`
   493  	FString      string                 `json:"f_string"`
   494  	FMap         map[string]interface{} `json:"f_map"`
   495  	FSmallStruct SmallStruct            `json:"f_smallstruct"`
   496  	FInterface   interface{}            `json:"f_interface"`
   497  	FInterfaces  []interface{}          `json:"f_interfaces"`
   498  }
   499  
   500  // SmallStruct is a small test struct.
   501  type SmallStruct struct {
   502  	FInt        int                    `json:"f_int"`
   503  	FSlice      []interface{}          `json:"f_slice"`
   504  	FMap        map[string]interface{} `json:"f_map"`
   505  	FTinyStruct TinyStruct             `json:"f_tinystruct"`
   506  	FString     string                 `json:"f_string"`
   507  }
   508  
   509  // Tiny Struct is a tiny test struct.
   510  type TinyStruct struct {
   511  	FBool bool `json:"f_bool"`
   512  }
   513  
   514  func newBigStruct() BigStruct {
   515  	return BigStruct{
   516  		FInt:     -7,
   517  		FInt8:    -8,
   518  		FInt16:   -16,
   519  		FInt32:   -32,
   520  		FInt64:   -64,
   521  		FUint:    7,
   522  		FUint8:   8,
   523  		FUint16:  16,
   524  		FUint32:  32,
   525  		FUint64:  64,
   526  		FFloat32: 32.32,
   527  		FFloat64: 64.64,
   528  		FBool:    true,
   529  		FBytes:   []byte("hello"),
   530  		FNil:     nil,
   531  		FString:  "hello",
   532  		FMap: map[string]interface{}{
   533  			"m_int64":       int64(64),
   534  			"m_string":      "hello",
   535  			"m_bool":        true,
   536  			"m_nil":         nil,
   537  			"m_smallstruct": newSmallStruct(),
   538  		},
   539  		FSmallStruct: newSmallStruct(),
   540  		FInterface:   interface{}("hello"),
   541  		FInterfaces:  []interface{}{int64(64), "hello", true},
   542  	}
   543  }
   544  
   545  func newSmallStruct() SmallStruct {
   546  	return SmallStruct{
   547  		FInt:   7,
   548  		FSlice: []interface{}{64, true},
   549  		FMap: map[string]interface{}{
   550  			"m_float64": 64.64,
   551  			"m_string":  "hello",
   552  		},
   553  		FTinyStruct: TinyStruct{FBool: true},
   554  		FString:     "hello",
   555  	}
   556  }
   557  
   558  func TestEquivalenceRecords(t *testing.T) {
   559  	rec := makeRecords(t, 10000)[0]
   560  
   561  	bufStdj := &bytes.Buffer{}
   562  	err := stdjson.NewEncoder(bufStdj).Encode(rec)
   563  	require.NoError(t, err)
   564  
   565  	bufSegmentj := &bytes.Buffer{}
   566  	err = json.NewEncoder(bufSegmentj).Encode(rec)
   567  	require.NoError(t, err)
   568  	require.NotEqual(t, bufStdj.String(), bufSegmentj.String(), "segmentj encodes time.Duration to string; stdlib does not")
   569  
   570  	bufJ := &bytes.Buffer{}
   571  	err = jsoncolor.NewEncoder(bufJ).Encode(rec)
   572  	require.Equal(t, bufStdj.String(), bufJ.String())
   573  }
   574  
   575  // TextMarshaler implements encoding.TextMarshaler
   576  type TextMarshaler struct {
   577  	Text string
   578  }
   579  
   580  func (t TextMarshaler) MarshalText() ([]byte, error) {
   581  	return []byte(t.Text), nil
   582  }
   583  
   584  func TestEncode_TextMarshaler(t *testing.T) {
   585  	buf := &bytes.Buffer{}
   586  	enc := jsoncolor.NewEncoder(buf)
   587  	enc.SetColors(&jsoncolor.Colors{
   588  		TextMarshaler: jsoncolor.Color("\x1b[36m"),
   589  	})
   590  
   591  	text := TextMarshaler{Text: "example text"}
   592  
   593  	require.NoError(t, enc.Encode(text))
   594  	require.Equal(t, "\x1b[36m\"example text\"\x1b[0m\n", buf.String(),
   595  		"expected TextMarshaler encoding to use Colors.TextMarshaler")
   596  }