Source file src/log/slog/slogtest_test.go

     1  // Copyright 2023 The Go Authors. All rights reserved.
     2  // Use of this source code is governed by a BSD-style
     3  // license that can be found in the LICENSE file.
     4  
     5  package slog_test
     6  
     7  import (
     8  	"bytes"
     9  	"encoding/json"
    10  	"fmt"
    11  	"io"
    12  	"log/slog"
    13  	"strings"
    14  	"testing"
    15  	"testing/slogtest"
    16  )
    17  
    18  func TestSlogtest(t *testing.T) {
    19  	for _, test := range []struct {
    20  		name  string
    21  		new   func(io.Writer) slog.Handler
    22  		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.Buffer
    29  			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 ms
    36  			}
    37  			if err := slogtest.TestHandler(h, results); err != nil {
    38  				t.Fatal(err)
    39  			}
    40  		})
    41  	}
    42  }
    43  
    44  func parseLines(src []byte, parse func([]byte) (map[string]any, error)) ([]map[string]any, error) {
    45  	var records []map[string]any
    46  	for _, line := range bytes.Split(src, []byte{'\n'}) {
    47  		if len(line) == 0 {
    48  			continue
    49  		}
    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, nil
    57  }
    58  
    59  func parseJSON(bs []byte) (map[string]any, error) {
    60  	var m map[string]any
    61  	if err := json.Unmarshal(bs, &m); err != nil {
    62  		return nil, err
    63  	}
    64  	return m, nil
    65  }
    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 deliberately
    71  // uses simple inputs so handler writers can focus on testing
    72  // handler behavior, not parsing.
    73  func 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 attrs
    78  		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 := top
    85  		for _, key := range keys[:len(keys)-1] {
    86  			x, ok := m[key]
    87  			var m2 map[string]any
    88  			if !ok {
    89  				m2 = map[string]any{}
    90  				m[key] = m2
    91  			} 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 = m2
    99  		}
   100  		m[keys[len(keys)-1]] = value
   101  		s = rest
   102  	}
   103  	return top, nil
   104  }
   105  

View as plain text