From dc0179bfa887aa82ff2cc615d07e52e59e57d204 Mon Sep 17 00:00:00 2001 From: Jonathan Amsterdam Date: Mon, 24 Apr 2023 14:19:20 -0400 Subject: [PATCH] log/slog: test built-in handlers with slogtest Test the built-in handlers using the testing/slogtest package. Change-Id: I6464b6730293461c38b84fb2bf33cdd09173dd6e Reviewed-on: https://go-review.googlesource.com/c/go/+/488255 Run-TryBot: Jonathan Amsterdam Reviewed-by: Alan Donovan TryBot-Result: Gopher Robot --- src/log/slog/slogtest_test.go | 104 ++++++++++++++++++++++++++++++++++ 1 file changed, 104 insertions(+) create mode 100644 src/log/slog/slogtest_test.go diff --git a/src/log/slog/slogtest_test.go b/src/log/slog/slogtest_test.go new file mode 100644 index 0000000000..4887cc8c77 --- /dev/null +++ b/src/log/slog/slogtest_test.go @@ -0,0 +1,104 @@ +// 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. + +package slog_test + +import ( + "bytes" + "encoding/json" + "fmt" + "io" + "log/slog" + "strings" + "testing" + "testing/slogtest" +) + +func TestSlogtest(t *testing.T) { + for _, test := range []struct { + name string + new func(io.Writer) slog.Handler + parse func([]byte) (map[string]any, error) + }{ + {"JSON", func(w io.Writer) slog.Handler { return slog.NewJSONHandler(w, nil) }, parseJSON}, + {"Text", func(w io.Writer) slog.Handler { return slog.NewTextHandler(w, nil) }, parseText}, + } { + t.Run(test.name, func(t *testing.T) { + var buf bytes.Buffer + h := test.new(&buf) + results := func() []map[string]any { + ms, err := parseLines(buf.Bytes(), test.parse) + if err != nil { + t.Fatal(err) + } + return ms + } + if err := slogtest.TestHandler(h, results); err != nil { + t.Fatal(err) + } + }) + } +} + +func parseLines(src []byte, parse func([]byte) (map[string]any, error)) ([]map[string]any, error) { + var records []map[string]any + for _, line := range bytes.Split(src, []byte{'\n'}) { + if len(line) == 0 { + continue + } + m, err := parse(line) + if err != nil { + return nil, fmt.Errorf("%s: %w", string(line), err) + } + records = append(records, m) + } + return records, nil +} + +func parseJSON(bs []byte) (map[string]any, error) { + var m map[string]any + if err := json.Unmarshal(bs, &m); err != nil { + return nil, err + } + return m, nil +} + +// parseText parses the output of a single call to TextHandler.Handle. +// It can parse the output of the tests in this package, +// but it doesn't handle quoted keys or values. +// It doesn't need to handle all cases, because slogtest deliberately +// uses simple inputs so handler writers can focus on testing +// handler behavior, not parsing. +func parseText(bs []byte) (map[string]any, error) { + top := map[string]any{} + s := string(bytes.TrimSpace(bs)) + for len(s) > 0 { + kv, rest, _ := strings.Cut(s, " ") // assumes exactly one space between attrs + k, value, found := strings.Cut(kv, "=") + if !found { + return nil, fmt.Errorf("no '=' in %q", kv) + } + keys := strings.Split(k, ".") + // Populate a tree of maps for a dotted path such as "a.b.c=x". + m := top + for _, key := range keys[:len(keys)-1] { + x, ok := m[key] + var m2 map[string]any + if !ok { + m2 = map[string]any{} + m[key] = m2 + } else { + m2, ok = x.(map[string]any) + if !ok { + return nil, fmt.Errorf("value for %q in composite key %q is not map[string]any", key, k) + + } + } + m = m2 + } + m[keys[len(keys)-1]] = value + s = rest + } + return top, nil +} -- 2.50.0