blob: 03cf6947ceb3ddfc41aafa8cddb5ddc1e277c7d2 [file] [log] [blame]
// Copyright 2023 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.
//go:build go1.21
package qlog
import (
"bytes"
"errors"
"fmt"
"log/slog"
"strings"
"sync"
"testing"
"time"
)
type testJSONOut struct {
bytes.Buffer
}
func (o *testJSONOut) Close() error { return nil }
func newTestJSONWriter() *jsonWriter {
return &jsonWriter{w: &testJSONOut{}}
}
func wantJSONRecord(t *testing.T, w *jsonWriter, want string) {
t.Helper()
want = "\x1e" + want + "\n"
got := w.w.(*testJSONOut).String()
if got != want {
t.Errorf("jsonWriter contains unexpected output\ngot: %q\nwant: %q", got, want)
}
}
func TestJSONWriterWriteConcurrentRecords(t *testing.T) {
w := newTestJSONWriter()
var wg sync.WaitGroup
for i := 0; i < 3; i++ {
wg.Add(1)
go func() {
defer wg.Done()
w.writeRecordStart()
w.writeInt64Field("field", 0)
w.writeRecordEnd()
}()
}
wg.Wait()
wantJSONRecord(t, w, strings.Join([]string{
`{"field":0}`,
`{"field":0}`,
`{"field":0}`,
}, "\n\x1e"))
}
func TestJSONWriterAttrs(t *testing.T) {
w := newTestJSONWriter()
w.writeRecordStart()
w.writeAttrsField("field", []slog.Attr{
slog.Any("any", errors.New("value")),
slog.Bool("bool", true),
slog.Duration("duration", 1*time.Second),
slog.Float64("float64", 1),
slog.Int64("int64", 1),
slog.String("string", "value"),
slog.Time("time", time.Date(2000, 1, 1, 0, 0, 0, 0, time.UTC)),
slog.Uint64("uint64", 1),
slog.Group("group", "a", 1),
})
w.writeRecordEnd()
wantJSONRecord(t, w,
`{"field":{`+
`"any":"value",`+
`"bool":true,`+
`"duration":1000.000000,`+
`"float64":1,`+
`"int64":1,`+
`"string":"value",`+
`"time":946684800000.000000,`+
`"uint64":1,`+
`"group":{"a":1}`+
`}}`)
}
func TestJSONWriterAttrEmpty(t *testing.T) {
w := newTestJSONWriter()
w.writeRecordStart()
var a slog.Attr
w.writeAttr(a)
w.writeRecordEnd()
wantJSONRecord(t, w, `{}`)
}
func TestJSONWriterObjectEmpty(t *testing.T) {
w := newTestJSONWriter()
w.writeRecordStart()
w.writeObjectField("field", func() {})
w.writeRecordEnd()
wantJSONRecord(t, w, `{"field":{}}`)
}
func TestJSONWriterObjectFields(t *testing.T) {
w := newTestJSONWriter()
w.writeRecordStart()
w.writeObjectField("field", func() {
w.writeStringField("a", "value")
w.writeInt64Field("b", 10)
})
w.writeRecordEnd()
wantJSONRecord(t, w, `{"field":{"a":"value","b":10}}`)
}
func TestJSONWriterRawField(t *testing.T) {
w := newTestJSONWriter()
w.writeRecordStart()
w.writeRawField("field", `[1]`)
w.writeRecordEnd()
wantJSONRecord(t, w, `{"field":[1]}`)
}
func TestJSONWriterBoolField(t *testing.T) {
w := newTestJSONWriter()
w.writeRecordStart()
w.writeBoolField("true", true)
w.writeBoolField("false", false)
w.writeRecordEnd()
wantJSONRecord(t, w, `{"true":true,"false":false}`)
}
func TestJSONWriterDurationField(t *testing.T) {
w := newTestJSONWriter()
w.writeRecordStart()
w.writeDurationField("field1", (10*time.Millisecond)+(2*time.Nanosecond))
w.writeDurationField("field2", -((10 * time.Millisecond) + (2 * time.Nanosecond)))
w.writeRecordEnd()
wantJSONRecord(t, w, `{"field1":10.000002,"field2":-10.000002}`)
}
func TestJSONWriterFloat64Field(t *testing.T) {
w := newTestJSONWriter()
w.writeRecordStart()
w.writeFloat64Field("field", 1.1)
w.writeRecordEnd()
wantJSONRecord(t, w, `{"field":1.1}`)
}
func TestJSONWriterInt64Field(t *testing.T) {
w := newTestJSONWriter()
w.writeRecordStart()
w.writeInt64Field("field", 1234)
w.writeRecordEnd()
wantJSONRecord(t, w, `{"field":1234}`)
}
func TestJSONWriterUint64Field(t *testing.T) {
w := newTestJSONWriter()
w.writeRecordStart()
w.writeUint64Field("field", 1234)
w.writeRecordEnd()
wantJSONRecord(t, w, `{"field":1234}`)
}
func TestJSONWriterStringField(t *testing.T) {
w := newTestJSONWriter()
w.writeRecordStart()
w.writeStringField("field", "value")
w.writeRecordEnd()
wantJSONRecord(t, w, `{"field":"value"}`)
}
func TestJSONWriterStringFieldEscaped(t *testing.T) {
w := newTestJSONWriter()
w.writeRecordStart()
w.writeStringField("field", "va\x00ue")
w.writeRecordEnd()
wantJSONRecord(t, w, `{"field":"va\u0000ue"}`)
}
func TestJSONWriterStringEscaping(t *testing.T) {
for c := 0; c <= 0xff; c++ {
w := newTestJSONWriter()
w.writeRecordStart()
w.writeStringField("field", string([]byte{byte(c)}))
w.writeRecordEnd()
var want string
if (c >= 0x20 && c <= 0x21) || (c >= 0x23 && c <= 0x5b) || (c >= 0x5d && c <= 0x7e) {
want = fmt.Sprintf(`%c`, c)
} else {
want = fmt.Sprintf(`\u%04x`, c)
}
wantJSONRecord(t, w, `{"field":"`+want+`"}`)
}
}