codekingpro/portable-devtools
114k
1// Copyright 2023 The Go Authors. All rights reserved.2// Use of this source code is governed by a BSD-style3// license that can be found in the LICENSE file.4 5package slog_test6 7import (8 "bytes"9 "encoding/json"10 "fmt"11 "io"12 "log/slog"13 "strings"14 "testing"15 "testing/slogtest"16)17 18func TestSlogtest(t *testing.T) {19 for _, test := range []struct {20 name string21 new func(io.Writer) slog.Handler22 parse func([]byte) (map[string]any, error)23 }{24 {"JSON", func(w io.Writer) slog.Handler { return slog.NewJSONHandler(w, nil) }, parseJSON},25 {"Text", func(w io.Writer) slog.Handler { return slog.NewTextHandler(w, nil) }, parseText},26 } {27 t.Run(test.name, func(t *testing.T) {28 var buf bytes.Buffer29 h := test.new(&buf)30 results := func() []map[string]any {31 ms, err := parseLines(buf.Bytes(), test.parse)32 if err != nil {33 t.Fatal(err)34 }35 return ms36 }37 if err := slogtest.TestHandler(h, results); err != nil {38 t.Fatal(err)39 }40 })41 }42}43 44func parseLines(src []byte, parse func([]byte) (map[string]any, error)) ([]map[string]any, error) {45 var records []map[string]any46 for line := range bytes.SplitSeq(src, []byte{'\n'}) {47 if len(line) == 0 {48 continue49 }50 m, err := parse(line)51 if err != nil {52 return nil, fmt.Errorf("%s: %w", string(line), err)53 }54 records = append(records, m)55 }56 return records, nil57}58 59func parseJSON(bs []byte) (map[string]any, error) {60 var m map[string]any61 if err := json.Unmarshal(bs, &m); err != nil {62 return nil, err63 }64 return m, nil65}66 67// parseText parses the output of a single call to TextHandler.Handle.68// It can parse the output of the tests in this package,69// but it doesn't handle quoted keys or values.70// It doesn't need to handle all cases, because slogtest deliberately71// uses simple inputs so handler writers can focus on testing72// handler behavior, not parsing.73func parseText(bs []byte) (map[string]any, error) {74 top := map[string]any{}75 s := string(bytes.TrimSpace(bs))76 for len(s) > 0 {77 kv, rest, _ := strings.Cut(s, " ") // assumes exactly one space between attrs78 k, value, found := strings.Cut(kv, "=")79 if !found {80 return nil, fmt.Errorf("no '=' in %q", kv)81 }82 keys := strings.Split(k, ".")83 // Populate a tree of maps for a dotted path such as "a.b.c=x".84 m := top85 for _, key := range keys[:len(keys)-1] {86 x, ok := m[key]87 var m2 map[string]any88 if !ok {89 m2 = map[string]any{}90 m[key] = m291 } else {92 m2, ok = x.(map[string]any)93 if !ok {94 return nil, fmt.Errorf("value for %q in composite key %q is not map[string]any", key, k)95 96 }97 }98 m = m299 }100 m[keys[len(keys)-1]] = value101 s = rest102 }103 return top, nil104}105 