1package term
2
3import (
4 "errors"
5 "testing"
6)
7
8// recordingWriter records each Write call it receives, copying the bytes
9// so later mutations by the caller (e.g. buffer reuse) don't corrupt the
10// recorded history.
11type recordingWriter struct {
12 writes [][]byte
13 failNext bool
14}
15
16func (r *recordingWriter) Write(p []byte) (int, error) {
17 if r.failNext {
18 r.failNext = false
19 return 0, errors.New("boom")
20 }
21 cp := make([]byte, len(p))
22 copy(cp, p)
23 r.writes = append(r.writes, cp)
24 return len(p), nil
25}
26
27func (r *recordingWriter) all() []byte {
28 var out []byte
29 for _, w := range r.writes {
30 out = append(out, w...)
31 }
32 return out
33}
34
35func TestBatchWriter_BufferedUntilFlush(t *testing.T) {
36 rw := &recordingWriter{}
37 bw := newBatchWriter(rw, 64)
38
39 n, err := bw.Write([]byte("hello"))
40 if err != nil || n != 5 {
41 t.Fatalf("Write() = %d, %v; want 5, nil", n, err)
42 }
43 if len(rw.writes) != 0 {
44 t.Fatalf("expected no writes reaching sink before Flush, got %d", len(rw.writes))
45 }
46 if got := bw.Buffered(); got != 5 {
47 t.Fatalf("Buffered() = %d, want 5", got)
48 }
49
50 if err := bw.Flush(); err != nil {
51 t.Fatalf("Flush() = %v, want nil", err)
52 }
53 if got := string(rw.all()); got != "hello" {
54 t.Fatalf("sink got %q, want %q", got, "hello")
55 }
56 if got := bw.Buffered(); got != 0 {
57 t.Fatalf("Buffered() after Flush = %d, want 0", got)
58 }
59}
60
61func TestBatchWriter_MultipleWritesCoalesceUntilFlush(t *testing.T) {
62 rw := &recordingWriter{}
63 bw := newBatchWriter(rw, 64)
64
65 for _, s := range []string{"a", "b", "c"} {
66 if _, err := bw.WriteString(s); err != nil {
67 t.Fatalf("WriteString(%q): %v", s, err)
68 }
69 }
70 if len(rw.writes) != 0 {
71 t.Fatalf("expected 0 writes to sink before Flush, got %d", len(rw.writes))
72 }
73
74 if err := bw.Flush(); err != nil {
75 t.Fatalf("Flush(): %v", err)
76 }
77 if got := string(rw.all()); got != "abc" {
78 t.Fatalf("sink got %q, want %q", got, "abc")
79 }
80 // bufio.Writer.Flush issues a single underlying Write.
81 if len(rw.writes) != 1 {
82 t.Fatalf("expected exactly 1 write to sink on Flush, got %d", len(rw.writes))
83 }
84}
85
86func TestBatchWriter_LargeWritePassesThrough(t *testing.T) {
87 rw := &recordingWriter{}
88 bw := newBatchWriter(rw, 8) // small buffer
89
90 big := make([]byte, 64)
91 for i := range big {
92 big[i] = 'x'
93 }
94
95 n, err := bw.Write(big)
96 if err != nil || n != len(big) {
97 t.Fatalf("Write(big) = %d, %v; want %d, nil", n, err, len(big))
98 }
99 // A write larger than the buffer bypasses buffering entirely.
100 if len(rw.writes) == 0 {
101 t.Fatalf("expected large write to pass through immediately, got 0 sink writes")
102 }
103 if got := string(rw.all()); got != string(big) {
104 t.Fatalf("sink content mismatch: got %d bytes, want %d bytes", len(got), len(big))
105 }
106}
107
108func TestBatchWriter_FlushPropagatesError(t *testing.T) {
109 rw := &recordingWriter{failNext: true}
110 bw := newBatchWriter(rw, 64)
111
112 if _, err := bw.WriteString("x"); err != nil {
113 t.Fatalf("WriteString: %v", err)
114 }
115 if err := bw.Flush(); err == nil {
116 t.Fatalf("expected Flush error, got nil")
117 }
118}
119
120func TestBatchWriter_FlushOnEmptyBufferIsNoop(t *testing.T) {
121 rw := &recordingWriter{}
122 bw := newBatchWriter(rw, 64)
123
124 if err := bw.Flush(); err != nil {
125 t.Fatalf("Flush() on empty buffer: %v", err)
126 }
127 if len(rw.writes) != 0 {
128 t.Fatalf("expected no sink writes, got %d", len(rw.writes))
129 }
130}