//go:build go1.21 package indenthandler import ( "bufio" "bytes" "fmt" "reflect" "regexp" "strconv" "strings" "testing" "unicode" "log/slog" "testing/slogtest" ) func TestSlogtest(t *testing.T) { var buf bytes.Buffer err := slogtest.TestHandler(New(&buf, nil), func() []map[string]any { return parseLogEntries(buf.String()) }) if err != nil { t.Error(err) } } func Test(t *testing.T) { var buf bytes.Buffer l := slog.New(New(&buf, nil)) l.Info("hello", "a", 1, "b", true, "c", 3.14, slog.Group("g", "h", 1, "i", 2), "d", "NO") got := buf.String() wantre := `time: [-0-9T:.]+Z? level: INFO source: ".*/indent_handler_test.go:\d+" msg: "hello" a: 1 b: true c: 3.14 g: h: 1 i: 2 d: "NO" ` re := regexp.MustCompile(wantre) if !re.MatchString(got) { t.Errorf("\ngot:\n%q\nwant:\n%q", got, wantre) } buf.Reset() l.Debug("test") if got := buf.Len(); got != 0 { t.Errorf("got buf.Len() = %d, want 0", got) } } func parseLogEntries(s string) []map[string]any { var ms []map[string]any scan := bufio.NewScanner(strings.NewReader(s)) for scan.Scan() { m := parseGroup(scan) ms = append(ms, m) } if scan.Err() != nil { panic(scan.Err()) } return ms } func parseGroup(scan *bufio.Scanner) map[string]any { m := map[string]any{} groupIndent := -1 for { line := scan.Text() if line == "---" { // end of entry break } k, v, found := strings.Cut(line, ":") if !found { panic(fmt.Sprintf("no ':' in line %q", line)) } indent := strings.IndexFunc(k, func(r rune) bool { return !unicode.IsSpace(r) }) if indent < 0 { panic("blank line") } if groupIndent < 0 { // First line in group; remember the indent. groupIndent = indent } else if indent < groupIndent { // End of group break } else if indent > groupIndent { panic(fmt.Sprintf("indent increased on line %q", line)) } key := strings.TrimSpace(k) if v == "" { // Just a key: start of a group. if !scan.Scan() { panic("empty group") } m[key] = parseGroup(scan) } else { v = strings.TrimSpace(v) if len(v) > 0 && v[0] == '"' { var err error v, err = strconv.Unquote(v) if err != nil { panic(err) } } m[key] = v if !scan.Scan() { break } } } return m } func TestParseLogEntries(t *testing.T) { in := ` a: 1 b: 2 c: 3 g: h: 4 i: 5 d: 6 --- e: 7 --- ` want := []map[string]any{ { "a": "1", "b": "2", "c": "3", "g": map[string]any{ "h": "4", "i": "5", }, "d": "6", }, { "e": "7", }, } got := parseLogEntries(in[1:]) if !reflect.DeepEqual(got, want) { t.Errorf("\ngot:\n%v\nwant:\n%v", got, want) } }