slog: improve handler test

Write output to a string instead of a map.
It's more accurate (e.g. order matters) and closer
to a real handler.

Change-Id: Ia410ff0dd99bb93db39a6954f7ffd1c5709555cc
Reviewed-on: https://go-review.googlesource.com/c/exp/+/432182
TryBot-Result: Gopher Robot <gobot@golang.org>
Reviewed-by: Alan Donovan <adonovan@google.com>
Run-TryBot: Jonathan Amsterdam <jba@google.com>
diff --git a/slog/handler_test.go b/slog/handler_test.go
index 8f72301..a3e3ec7 100644
--- a/slog/handler_test.go
+++ b/slog/handler_test.go
@@ -9,7 +9,6 @@
 import (
 	"bytes"
 	"fmt"
-	"reflect"
 	"strings"
 	"testing"
 	"time"
@@ -17,7 +16,7 @@
 	"golang.org/x/exp/slog/internal/buffer"
 )
 
-func TestWith(t *testing.T) {
+func TestDefaultWith(t *testing.T) {
 	d := &defaultHandler{}
 	if g := len(d.attrs); g != 0 {
 		t.Errorf("got %d, want 0", g)
@@ -35,25 +34,19 @@
 }
 
 func TestCommonHandle(t *testing.T) {
-	tm := time.Now()
+	tm := time.Date(2022, 9, 18, 8, 26, 33, 0, time.UTC)
 	r := NewRecord(tm, InfoLevel, "message", 1)
 	r.AddAttrs(String("a", "one"), Int("b", 2), Any("", "ignore me"))
 
 	for _, test := range []struct {
 		name string
 		h    *commonHandler
-		want map[string]any
+		want string
 	}{
 		{
 			name: "basic",
 			h:    &commonHandler{},
-			want: map[string]any{
-				"msg":   "message",
-				"time":  tm.Round(0), // strip monotonic
-				"level": "INFO",
-				"a":     "one",
-				"b":     int64(2),
-			},
+			want: "(time=2022-09-18T08:26:33.000Z;level=INFO;msg=message;a=one;b=2)",
 		},
 		{
 			name: "cap keys",
@@ -64,13 +57,7 @@
 					},
 				},
 			},
-			want: map[string]any{
-				"MSG":   "message",
-				"TIME":  tm.Round(0), // strip monotonic
-				"LEVEL": "INFO",
-				"A":     "one",
-				"B":     int64(2),
-			},
+			want: "(TIME=2022-09-18T08:26:33.000Z;LEVEL=INFO;MSG=message;A=one;B=2)",
 		},
 		{
 			name: "remove all",
@@ -79,47 +66,59 @@
 					ReplaceAttr: func(a Attr) Attr { return Attr{} },
 				},
 			},
-			want: map[string]any{},
+			// TODO: fix this. The correct result is "()".
+			want: "(;;)",
 		},
 	} {
 		t.Run(test.name, func(t *testing.T) {
-			ma := &memAppender{m: map[string]any{}}
-			test.h.w = &bytes.Buffer{}
-			test.h.newAppender = func(*buffer.Buffer) appender { return ma }
+			var buf bytes.Buffer
+			test.h.w = &buf
+			test.h.newAppender = func(buf *buffer.Buffer) appender {
+				return &testAppender{buf}
+			}
 			if err := test.h.handle(r); err != nil {
 				t.Fatal(err)
 			}
-			if got := ma.m; !reflect.DeepEqual(got, test.want) {
+			got := strings.TrimSuffix(buf.String(), "\n")
+			if got != test.want {
 				t.Errorf("\ngot  %#v\nwant %#v\n", got, test.want)
 			}
 		})
 	}
 }
 
-type memAppender struct {
-	key string
-	m   map[string]any
+type testAppender struct {
+	buf *buffer.Buffer
 }
 
-func (a *memAppender) set(v any) { a.m[a.key] = v }
+func (a *testAppender) appendStart() { a.buf.WriteByte('(') }
+func (a *testAppender) appendSep()   { a.buf.WriteByte(';') }
+func (a *testAppender) appendEnd()   { a.buf.WriteByte(')') }
 
-func (a *memAppender) appendStart()          {}
-func (a *memAppender) appendSep()            {}
-func (a *memAppender) appendEnd()            {}
-func (a *memAppender) appendKey(key string)  { a.key = key }
-func (a *memAppender) appendString(s string) { a.set(s) }
+func (a *testAppender) appendKey(key string) {
+	a.appendString(key)
+	a.buf.WriteByte('=')
+}
+func (a *testAppender) appendString(s string) { a.buf.WriteString(s) }
 
-func (a *memAppender) appendTime(t time.Time) error {
-	a.set(t)
+func (a *testAppender) appendTime(t time.Time) error {
+	*a.buf = appendTimeRFC3339Millis(*a.buf, t)
 	return nil
 }
 
-func (a *memAppender) appendSource(file string, line int) {
-	a.set(fmt.Sprintf("%s:%d", file, line))
+func (a *testAppender) appendSource(file string, line int) {
+	a.appendString(fmt.Sprintf("%s:%d", file, line))
 }
 
-func (a *memAppender) appendAttrValue(at Attr) error {
-	a.set(at.Value())
+func (a *testAppender) appendAttrValue(at Attr) error {
+	switch at.Kind() {
+	case StringKind:
+		a.appendString(at.str())
+	case TimeKind:
+		a.appendTime(at.Time())
+	default:
+		*a.buf = at.appendValue(*a.buf)
+	}
 	return nil
 }