github.com/ssgreg/logf@v1.4.1/json_encoder_test.go (about) 1 package logf 2 3 import ( 4 "bytes" 5 "encoding/json" 6 "sync/atomic" 7 "testing" 8 "time" 9 10 "github.com/stretchr/testify/assert" 11 "github.com/stretchr/testify/require" 12 ) 13 14 type encoderTestCase struct { 15 Name string 16 Entry Entry 17 Golden string 18 } 19 20 var loggerID = int32(0) 21 22 func newLoggerID() int32 { 23 atomic.AddInt32(&loggerID, 1) 24 25 return loggerID 26 } 27 28 func TestEncoder(t *testing.T) { 29 testCases := []encoderTestCase{ 30 { 31 "Message", 32 Entry{ 33 Text: "m", 34 }, 35 `{"level":"error","ts":"0001-01-01T00:00:00Z","msg":"m"}` + "\n", 36 }, 37 { 38 "LevelDebug", 39 Entry{ 40 Level: LevelInfo, 41 }, 42 `{"level":"info","ts":"0001-01-01T00:00:00Z","msg":""}` + "\n", 43 }, 44 { 45 "LevelInfo", 46 Entry{ 47 Level: LevelDebug, 48 }, 49 `{"level":"debug","ts":"0001-01-01T00:00:00Z","msg":""}` + "\n", 50 }, 51 { 52 "LevelWarn", 53 Entry{ 54 Level: LevelWarn, 55 }, 56 `{"level":"warn","ts":"0001-01-01T00:00:00Z","msg":""}` + "\n", 57 }, 58 { 59 "LevelError", 60 Entry{ 61 Level: LevelError, 62 }, 63 `{"level":"error","ts":"0001-01-01T00:00:00Z","msg":""}` + "\n", 64 }, 65 { 66 "LoggerName", 67 Entry{ 68 LoggerName: "logger.name", 69 }, 70 `{"level":"error","ts":"0001-01-01T00:00:00Z","logger":"logger.name","msg":""}` + "\n", 71 }, 72 { 73 "LoggerName", 74 Entry{ 75 Caller: EntryCaller{ 76 File: "/a/b/c/f.go", 77 Line: 6, 78 Specified: true, 79 }, 80 }, 81 `{"level":"error","ts":"0001-01-01T00:00:00Z","msg":"","caller":"c/f.go:6"}` + "\n", 82 }, 83 { 84 "FieldsNumbers", 85 Entry{ 86 Fields: []Field{ 87 Bool("bool", true), 88 Int("int", 42), Int64("int64", 42), Int32("int32", 42), Int16("int16", 42), Int8("int8", 42), 89 Uint("uint", 42), Uint64("uint64", 42), Uint32("uint32", 42), Uint16("uint16", 42), Uint8("uint8", 42), 90 Float64("float64", 4.2), Float32("float32", 4.2), 91 }, 92 }, 93 `{"level":"error","ts":"0001-01-01T00:00:00Z","msg":"","bool":true,"int":42,"int64":42,"int32":42,"int16":42,"int8":42,"uint":42,"uint64":42,"uint32":42,"uint16":42,"uint8":42,"float64":4.2,"float32":4.2}` + "\n", 94 }, 95 { 96 "FieldsSlicesWithNumbers", 97 Entry{ 98 Fields: []Field{ 99 ConstBools("bools", []bool{true}), 100 ConstInts("ints", []int{42}), ConstInts64("ints64", []int64{42}), ConstInts32("ints32", []int32{42}), ConstInts16("ints16", []int16{42}), ConstInts8("ints8", []int8{42}), 101 ConstUints("uints", []uint{42}), ConstUints64("uints64", []uint64{42}), ConstUints32("uints32", []uint32{42}), ConstUints16("uints16", []uint16{42}), ConstUints8("uints8", []uint8{42}), 102 ConstFloats64("floats64", []float64{4.2}), ConstFloats32("floats32", []float32{4.2}), 103 }, 104 }, 105 `{"level":"error","ts":"0001-01-01T00:00:00Z","msg":"","bools":[true],"ints":[42],"ints64":[42],"ints32":[42],"ints16":[42],"ints8":[42],"uints":[42],"uints64":[42],"uints32":[42],"uints16":[42],"uints8":[42],"floats64":[4.2],"floats32":[4.2]}` + "\n", 106 }, 107 { 108 "FieldsDuration", 109 Entry{ 110 Fields: []Field{ 111 Duration("duration", time.Second), 112 ConstDurations("durations", []time.Duration{time.Second}), 113 }, 114 }, 115 `{"level":"error","ts":"0001-01-01T00:00:00Z","msg":"","duration":"1s","durations":["1s"]}` + "\n", 116 }, 117 { 118 "FieldsTime", 119 Entry{ 120 Fields: []Field{ 121 Time("time", time.Unix(320836234, 0).UTC()), 122 }, 123 }, 124 `{"level":"error","ts":"0001-01-01T00:00:00Z","msg":"","time":"1980-03-02T09:10:34Z"}` + "\n", 125 }, 126 { 127 "FieldsArray", 128 Entry{ 129 Fields: []Field{ 130 Array("array", &testArrayEncoder{}), 131 }, 132 }, 133 `{"level":"error","ts":"0001-01-01T00:00:00Z","msg":"","array":[42]}` + "\n", 134 }, 135 { 136 "FieldsObject", 137 Entry{ 138 Fields: []Field{ 139 Object("object", &testObjectEncoder{}), 140 }, 141 }, 142 `{"level":"error","ts":"0001-01-01T00:00:00Z","msg":"","object":{"username":"username","code":42}}` + "\n", 143 }, 144 { 145 "FieldsError", 146 Entry{ 147 Fields: []Field{ 148 Error(&verboseError{"short", "verbose"}), 149 }, 150 }, 151 `{"level":"error","ts":"0001-01-01T00:00:00Z","msg":"","error":"short","error.verbose":"verbose"}` + "\n", 152 }, 153 { 154 "FieldsBytes", 155 Entry{ 156 Fields: []Field{ 157 ConstBytes("bytes", []byte{0x42}), 158 }, 159 }, 160 `{"level":"error","ts":"0001-01-01T00:00:00Z","msg":"","bytes":"Qg=="}` + "\n", 161 }, 162 { 163 "FieldsAny", 164 Entry{ 165 Fields: []Field{ 166 Any("any", &struct{ Field string }{Field: "42"}), 167 }, 168 }, 169 `{"level":"error","ts":"0001-01-01T00:00:00Z","msg":"","any":{"Field":"42"}}` + "\n", 170 }, 171 { 172 "FieldsDerivedFields", 173 Entry{ 174 DerivedFields: []Field{ 175 Int("int", 42), 176 }, 177 }, 178 `{"level":"error","ts":"0001-01-01T00:00:00Z","msg":"","int":42}` + "\n", 179 }, 180 { 181 "FieldsDerivedFieldsFirst", 182 Entry{ 183 DerivedFields: []Field{ 184 Int("int", 42), 185 }, 186 Fields: []Field{ 187 String("string", "42"), 188 }, 189 }, 190 `{"level":"error","ts":"0001-01-01T00:00:00Z","msg":"","int":42,"string":"42"}` + "\n", 191 }, 192 } 193 194 enc := NewJSONEncoder.Default() 195 196 for _, tc := range testCases { 197 t.Run(tc.Name, func(t *testing.T) { 198 // Setup skipped fields (skipped for short test case description). 199 tc.Entry.LoggerID = newLoggerID() 200 201 b := NewBuffer() 202 enc.Encode(b, tc.Entry) 203 require.EqualValues(t, tc.Golden, b.String()) 204 205 // Check for correct json. 206 var a map[string]interface{} 207 require.NoError(t, json.NewDecoder(bytes.NewBuffer(b.Bytes())).Decode(&a), "generated json expected to be parsed by native golang json encoder") 208 }) 209 } 210 } 211 212 func TestEscapeString(t *testing.T) { 213 testCases := []struct { 214 golden string 215 source string 216 }{ 217 {`кириллица`, "кириллица"}, 218 {`not<escape>html`, `not<escape>html`}, 219 {`badtext\ufffd`, "badtext\xc5"}, 220 {`ошибка\ufffdошибка`, "ошибка\xc5ошибка"}, 221 {`测试`, "测试"}, 222 {`测\ufffd试`, "测\xc5试"}, 223 {`\u0008\\\r\n\t\"`, "\b\\\r\n\t\""}, 224 } 225 226 for _, tc := range testCases { 227 b := NewBuffer() 228 assert.NoError(t, EscapeString(b, tc.source)) 229 assert.Equal(t, tc.golden, b.String()) 230 } 231 } 232 233 func TestEscapeByteString(t *testing.T) { 234 testCases := []struct { 235 golden string 236 source string 237 }{ 238 {`кириллица`, "кириллица"}, 239 {`not<escape>html`, `not<escape>html`}, 240 {`测试`, "测试"}, 241 {`\u0008\\\r\n\t\"`, "\b\\\r\n\t\""}, 242 } 243 244 for _, tc := range testCases { 245 b := NewBuffer() 246 assert.NoError(t, EscapeByteString(b, []byte(tc.source))) 247 assert.Equal(t, tc.golden, b.String()) 248 } 249 } 250 251 func TestEncoderFactory(t *testing.T) { 252 b := NewBuffer() 253 ef := NewJSONTypeEncoderFactory.Default() 254 te := ef.TypeEncoder(b) 255 256 te.EncodeTypeString("42") 257 assert.Equal(t, `"42"`, b.String()) 258 }