// 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 }