-
Notifications
You must be signed in to change notification settings - Fork 0
/
io_test.go
334 lines (308 loc) · 10.7 KB
/
io_test.go
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
202
203
204
205
206
207
208
209
210
211
212
213
214
215
216
217
218
219
220
221
222
223
224
225
226
227
228
229
230
231
232
233
234
235
236
237
238
239
240
241
242
243
244
245
246
247
248
249
250
251
252
253
254
255
256
257
258
259
260
261
262
263
264
265
266
267
268
269
270
271
272
273
274
275
276
277
278
279
280
281
282
283
284
285
286
287
288
289
290
291
292
293
294
295
296
297
298
299
300
301
302
303
304
305
306
307
308
309
310
311
312
313
314
315
316
317
318
319
320
321
322
323
324
325
326
327
328
329
330
331
332
333
334
package arbor_test
import (
"bytes"
"encoding/json"
"io"
"testing"
arbor "github.com/arborchat/arbor-go"
"github.com/jordwest/mock-conn"
"github.com/onsi/gomega"
)
const (
welcomeExample = "{\"Type\":0,\"Root\":\"f4ae0b74-4025-4810-41d6-5148a513c580\",\"Recent\":[\"92d24e9d-12cc-4742-6aaf-ea781a6b09ec\",\"880be029-0d7c-4a3f-558d-d90bf79cbc1d\"],\"Major\":0,\"Minor\":1}"
newExample = "{\"Type\":2,\"UUID\":\"92d24e9d-12cc-4742-6aaf-ea781a6b09ec\",\"Parent\":\"f4ae0b74-4025-4810-41d6-5148a513c580\",\"Content\":\"A riveting example message.\",\"Username\":\"Examplius_Caesar\",\"Timestamp\":1537738224}"
queryExample = "{\"Type\":1,\"UUID\":\"f4ae0b74-4025-4810-41d6-5148a513c580\"}"
)
// TestNilReader ensures that NewProtocolReader correctly handles being provided with a
// nil io.Reader
func TestNilReader(t *testing.T) {
reader, err := arbor.NewProtocolReader(nil)
if err == nil {
t.Error("NewProtocolReader should error when given a nil io.Reader")
}
if reader != nil {
t.Error("NewProtocolReader should return nil ProtocolReader when given a nil io.Reader")
}
}
type badReadWriteCloser struct{ Field int }
func (b *badReadWriteCloser) Read([]byte) (int, error) {
return b.Field, nil // access a property to trigger a nil pointer dereference
}
func (b *badReadWriteCloser) Write([]byte) (int, error) {
return b.Field, nil // access a property to trigger a nil pointer dereference
}
func (b *badReadWriteCloser) Close() error {
return nil
}
type detectableCloser struct {
io.ReadWriter
closed bool
}
var _ io.ReadWriteCloser = &detectableCloser{}
func (c *detectableCloser) Close() error {
c.closed = true
return nil
}
// TestTypedNilReader ensures that NewProtocolReader correctly handles being provided with a
// nil concrete value with a non-nil concrete type wrapped in the io.Reader interface.
func TestTypedNilReader(t *testing.T) {
// create a typed nil
var bad *badReadWriteCloser
var typedBad io.Reader = bad
reader, err := arbor.NewProtocolReader(typedBad)
if err == nil {
t.Error("NewProtocolReader should error when given a nil io.Reader")
}
if reader != nil {
t.Error("NewProtocolReader should return nil ProtocolReader when given a nil io.Reader")
}
}
// TestReaderRead ensures that we can read a message out of a ProtocolReader when
// it is given proper input.
func TestReaderRead(t *testing.T) {
buf := new(bytes.Buffer)
encoder := json.NewEncoder(buf)
welcome := getWelcome()
err := encoder.Encode(welcome)
if err != nil {
t.Skip("Unable to write test data", err)
}
reader, err := arbor.NewProtocolReader(buf)
if err != nil {
t.Error("Unable to construct Reader with valid input", err)
} else if reader == nil {
t.Error("Got nil Reader back when invoking constructor with valid input")
}
proto := arbor.ProtocolMessage{}
err = reader.Read(&proto)
if err != nil {
t.Error("Expected to be able to read message from buffer", err)
}
if !proto.Equals(welcome) {
t.Errorf("Expected %v, got %v", welcome, proto)
}
}
// TestReaderInvalid ensures that we reject incomplete messages when we read them.
func TestReaderInvalid(t *testing.T) {
g := gomega.NewGomegaWithT(t)
buf := new(bytes.Buffer)
for _, bad := range []string{"{\"Type\":1}", "{\"Type\":2}"} {
if _, err := buf.Write([]byte(bad)); err != nil {
t.Skip(err)
}
reader, err := arbor.NewProtocolReader(buf)
if err != nil {
t.Skip("Unable to construct Reader with valid input", err)
} else if reader == nil {
t.Skip("Got nil Reader back when invoking constructor with valid input")
}
proto := arbor.ProtocolMessage{}
err = reader.Read(&proto)
g.Expect(err).ToNot(gomega.BeNil())
}
}
// TestReaderReadNil ensures that we properly handle nil input to Read.
func TestReaderReadNil(t *testing.T) {
buf := new(bytes.Buffer)
encoder := json.NewEncoder(buf)
welcome := getWelcome()
err := encoder.Encode(welcome)
if err != nil {
t.Skip("Unable to write test data", err)
}
reader, err := arbor.NewProtocolReader(buf)
if err != nil {
t.Error("Unable to construct Reader with valid input", err)
} else if reader == nil {
t.Error("Got nil Reader back when invoking constructor with valid input")
}
err = reader.Read(nil)
if err == nil {
t.Error("Expected an error from trying to read into nil pointer")
}
}
// TestNilWriter ensures that NewProtocolWriter correctly handles being provided with a
// nil io.Writer
func TestNilWriter(t *testing.T) {
reader, err := arbor.NewProtocolWriter(nil)
if err == nil {
t.Error("NewProtocolWriter should error when given a nil io.Writer")
}
if reader != nil {
t.Error("NewProtocolWriter should return nil ProtocolWriter when given a nil io.Writer")
}
}
// TestTypedNilWriter ensures that NewProtocolWriter correctly handles being provided with a
// nil concrete value with a non-nil concrete type wrapped in the io.Writer interface.
func TestTypedNilWriter(t *testing.T) {
// create a typed nil
var bad *badReadWriteCloser
var typedBad io.Writer = bad
reader, err := arbor.NewProtocolWriter(typedBad)
if err == nil {
t.Error("NewProtocolWriter should error when given a nil io.Writer")
}
if reader != nil {
t.Error("NewProtocolWriter should return nil ProtocolWriter when given a nil io.Writer")
}
}
// TestWriterWrite ensures that we can write a message with a ProtocolWriter when
// it is given proper input.
func TestWriterWrite(t *testing.T) {
buf := new(bytes.Buffer)
writer, err := arbor.NewProtocolWriter(buf)
if err != nil {
t.Error("Unable to construct Writer with valid input", err)
} else if writer == nil {
t.Error("Got nil Writer back when invoking constructor with valid input")
}
welcome := getWelcome()
err = writer.Write(welcome)
if err != nil {
t.Error("Unable to write message into valid destination", err)
}
proto := arbor.ProtocolMessage{}
decoder := json.NewDecoder(buf)
err = decoder.Decode(&proto)
if err != nil {
t.Skip("Unable to read test data", err)
}
if !proto.Equals(welcome) {
t.Log(proto.ChatMessage, welcome.ChatMessage)
t.Errorf("Expected %v, found %v", welcome, proto)
}
}
// TestWriterWriteNil ensures that we properly handle nil input to Write.
func TestWriterWriteNil(t *testing.T) {
buf := new(bytes.Buffer)
writer, err := arbor.NewProtocolWriter(buf)
if err != nil {
t.Error("Unable to construct Writer with valid input", err)
} else if writer == nil {
t.Error("Got nil Writer back when invoking constructor with valid input")
}
err = writer.Write(nil)
if err == nil {
t.Error("Expected an error from trying to read into nil pointer")
}
}
// TestReadWriter ensures that NewProtocolReadWriter returns a valid ProtocolReadWriter when given
// valid input.
func TestReadWriter(t *testing.T) {
buf := new(bytes.Buffer)
rw, err := arbor.NewProtocolReadWriter(arbor.NoopRWCloser(buf))
if err != nil {
t.Error("NewProtocolReadWriter should not error when given a valid io.ReadWriter")
}
if rw == nil {
t.Error("NewProtocolReadWriter should not return nil when given a valid io.ReadWriter")
}
}
// TestNilReadWriter ensures that NewProtocolReadWriter correctly handles being provided with a
// nil io.ReadWriter
func TestNilReadWriter(t *testing.T) {
rw, err := arbor.NewProtocolReadWriter(nil)
if err == nil {
t.Error("NewProtocolReadWriter should error when given a nil io.ReadWriter")
}
if rw != nil {
t.Error("NewProtocolReadWriter should return nil ProtocolReadWriter when given a nil io.ReadWriter")
}
}
// TestTypedNilReadWriter ensures that NewProtocolReadWriter correctly handles being provided with a
// nil concrete value with a non-nil concrete type wrapped in the io.ReadWriter interface.
func TestTypedNilReadWriter(t *testing.T) {
// create a typed nil
var bad *badReadWriteCloser
var typedBad io.ReadWriteCloser = bad
rw, err := arbor.NewProtocolReadWriter(typedBad)
if err == nil {
t.Error("NewProtocolReadWriter should error when given a nil io.ReadWriter")
}
if rw != nil {
t.Error("NewProtocolReadWriter should return nil ProtocolReadWriter when given a nil io.ReadWriter")
}
}
// TestProtocolRWClose ensures that the Close method on ProtocolReadWriteClosers closes the underlying
// io.ReadWriteCloser and returns an error when you attempt to close it more than once.
func TestProtocolRWClose(t *testing.T) {
detect := &detectableCloser{ReadWriter: new(bytes.Buffer)}
closer, err := arbor.NewProtocolReadWriter(detect)
if err != nil {
t.Skip("Failed to construct ProtocolReadWriteCloser", err)
}
if detect.closed {
t.Error("ProtocolReadWriteCloser should not close underlying io.ReadWriteCloser unasked")
}
err = closer.Close()
if err != nil {
t.Error("Should have been able to close open underlying io.ReadWriteCloser", err)
}
if !detect.closed {
t.Error("After Close(), underlying io.ReadWriteCloser should be closed")
}
err = closer.Close()
if err == nil {
t.Error("closing underlying io.ReadWriteCloser twice should have errored")
}
//ensure can't read or write
err = closer.Read(new(arbor.ProtocolMessage))
if err == nil {
t.Error("Should error trying to read closed RWC")
}
err = closer.Write(new(arbor.ProtocolMessage))
if err == nil {
t.Error("Should error trying to write closed RWC")
}
}
// TestMakeMessageReader checks that MakeMessageReader properly reads messages
// from the input connection.
func TestMakeMessageReader(t *testing.T) {
testMsgs := []string{
newExample + "\n",
welcomeExample + "\n",
queryExample + "\n",
}
conn := mock_conn.NewConn()
recvChan := arbor.MakeMessageReader(conn.Client)
server := conn.Server
for _, msg := range testMsgs {
testMsg := []byte(msg)
n, err := server.Write(testMsg)
if err != nil || n != len(testMsg) {
t.Skipf("Unable to write message \"%s\" into mock connection", msg)
}
parsed := <-recvChan
if parsed == nil {
t.Error("MakeMessageReader sent nil ProtocolMessage")
}
}
}
// TestMakeMessageReaderInvalid checks that MakeMessageReader hangs up when it recieves bad
// input.
func TestMakeMessageReaderInvalid(t *testing.T) {
testMsgs := []string{
string([]byte{0x1b}) + "\n", // this is the ASCII escape character
}
for _, msg := range testMsgs {
conn := mock_conn.NewConn()
recvChan := arbor.MakeMessageReader(conn.Client)
server := conn.Server
testMsg := []byte(msg)
n, err := server.Write(testMsg)
if err != nil || n != len(testMsg) {
t.Skipf("Unable to write message \"%s\" into mock connection", msg)
}
parsed := <-recvChan
if parsed != nil {
t.Error("MakeMessageReader did not close output channel on bad input")
}
if n, err = server.Write(testMsg); err == nil {
// on a tcp connection, we'd get io.EOF, but our mock doesn't work that way.
// just need to make sure that we get an error
t.Error("MakeMessageReader failed to close the connection on bad input, no error on write")
} else if n > 0 {
t.Error("MakeMessageReader failed to close connection on bad input, able to write data")
}
}
}