// 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 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+`"}`) } }