1// Copyright 2016 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
5package http2
6
7import (
8	"bytes"
9	"fmt"
10	"sort"
11	"testing"
12)
13
14func defaultPriorityWriteScheduler() *priorityWriteScheduler {
15	return NewPriorityWriteScheduler(nil).(*priorityWriteScheduler)
16}
17
18func checkPriorityWellFormed(ws *priorityWriteScheduler) error {
19	for id, n := range ws.nodes {
20		if id != n.id {
21			return fmt.Errorf("bad ws.nodes: ws.nodes[%d] = %d", id, n.id)
22		}
23		if n.parent == nil {
24			if n.next != nil || n.prev != nil {
25				return fmt.Errorf("bad node %d: nil parent but prev/next not nil", id)
26			}
27			continue
28		}
29		found := false
30		for k := n.parent.kids; k != nil; k = k.next {
31			if k.id == id {
32				found = true
33				break
34			}
35		}
36		if !found {
37			return fmt.Errorf("bad node %d: not found in parent %d kids list", id, n.parent.id)
38		}
39	}
40	return nil
41}
42
43func fmtTree(ws *priorityWriteScheduler, fmtNode func(*priorityNode) string) string {
44	var ids []int
45	for _, n := range ws.nodes {
46		ids = append(ids, int(n.id))
47	}
48	sort.Ints(ids)
49
50	var buf bytes.Buffer
51	for _, id := range ids {
52		if buf.Len() != 0 {
53			buf.WriteString(" ")
54		}
55		if id == 0 {
56			buf.WriteString(fmtNode(&ws.root))
57		} else {
58			buf.WriteString(fmtNode(ws.nodes[uint32(id)]))
59		}
60	}
61	return buf.String()
62}
63
64func fmtNodeParentSkipRoot(n *priorityNode) string {
65	switch {
66	case n.id == 0:
67		return ""
68	case n.parent == nil:
69		return fmt.Sprintf("%d{parent:nil}", n.id)
70	default:
71		return fmt.Sprintf("%d{parent:%d}", n.id, n.parent.id)
72	}
73}
74
75func fmtNodeWeightParentSkipRoot(n *priorityNode) string {
76	switch {
77	case n.id == 0:
78		return ""
79	case n.parent == nil:
80		return fmt.Sprintf("%d{weight:%d,parent:nil}", n.id, n.weight)
81	default:
82		return fmt.Sprintf("%d{weight:%d,parent:%d}", n.id, n.weight, n.parent.id)
83	}
84}
85
86func TestPriorityTwoStreams(t *testing.T) {
87	ws := defaultPriorityWriteScheduler()
88	ws.OpenStream(1, OpenStreamOptions{})
89	ws.OpenStream(2, OpenStreamOptions{})
90
91	want := "1{weight:15,parent:0} 2{weight:15,parent:0}"
92	if got := fmtTree(ws, fmtNodeWeightParentSkipRoot); got != want {
93		t.Errorf("After open\ngot  %q\nwant %q", got, want)
94	}
95
96	// Move 1's parent to 2.
97	ws.AdjustStream(1, PriorityParam{
98		StreamDep: 2,
99		Weight:    32,
100		Exclusive: false,
101	})
102	want = "1{weight:32,parent:2} 2{weight:15,parent:0}"
103	if got := fmtTree(ws, fmtNodeWeightParentSkipRoot); got != want {
104		t.Errorf("After adjust\ngot  %q\nwant %q", got, want)
105	}
106
107	if err := checkPriorityWellFormed(ws); err != nil {
108		t.Error(err)
109	}
110}
111
112func TestPriorityAdjustExclusiveZero(t *testing.T) {
113	// 1, 2, and 3 are all children of the 0 stream.
114	// Exclusive reprioritization to any of the streams should bring
115	// the rest of the streams under the reprioritized stream.
116	ws := defaultPriorityWriteScheduler()
117	ws.OpenStream(1, OpenStreamOptions{})
118	ws.OpenStream(2, OpenStreamOptions{})
119	ws.OpenStream(3, OpenStreamOptions{})
120
121	want := "1{weight:15,parent:0} 2{weight:15,parent:0} 3{weight:15,parent:0}"
122	if got := fmtTree(ws, fmtNodeWeightParentSkipRoot); got != want {
123		t.Errorf("After open\ngot  %q\nwant %q", got, want)
124	}
125
126	ws.AdjustStream(2, PriorityParam{
127		StreamDep: 0,
128		Weight:    20,
129		Exclusive: true,
130	})
131	want = "1{weight:15,parent:2} 2{weight:20,parent:0} 3{weight:15,parent:2}"
132	if got := fmtTree(ws, fmtNodeWeightParentSkipRoot); got != want {
133		t.Errorf("After adjust\ngot  %q\nwant %q", got, want)
134	}
135
136	if err := checkPriorityWellFormed(ws); err != nil {
137		t.Error(err)
138	}
139}
140
141func TestPriorityAdjustOwnParent(t *testing.T) {
142	// Assigning a node as its own parent should have no effect.
143	ws := defaultPriorityWriteScheduler()
144	ws.OpenStream(1, OpenStreamOptions{})
145	ws.OpenStream(2, OpenStreamOptions{})
146	ws.AdjustStream(2, PriorityParam{
147		StreamDep: 2,
148		Weight:    20,
149		Exclusive: true,
150	})
151	want := "1{weight:15,parent:0} 2{weight:15,parent:0}"
152	if got := fmtTree(ws, fmtNodeWeightParentSkipRoot); got != want {
153		t.Errorf("After adjust\ngot  %q\nwant %q", got, want)
154	}
155	if err := checkPriorityWellFormed(ws); err != nil {
156		t.Error(err)
157	}
158}
159
160func TestPriorityClosedStreams(t *testing.T) {
161	ws := NewPriorityWriteScheduler(&PriorityWriteSchedulerConfig{MaxClosedNodesInTree: 2}).(*priorityWriteScheduler)
162	ws.OpenStream(1, OpenStreamOptions{})
163	ws.OpenStream(2, OpenStreamOptions{PusherID: 1})
164	ws.OpenStream(3, OpenStreamOptions{PusherID: 2})
165	ws.OpenStream(4, OpenStreamOptions{PusherID: 3})
166
167	// Close the first three streams. We lose 1, but keep 2 and 3.
168	ws.CloseStream(1)
169	ws.CloseStream(2)
170	ws.CloseStream(3)
171
172	want := "2{weight:15,parent:0} 3{weight:15,parent:2} 4{weight:15,parent:3}"
173	if got := fmtTree(ws, fmtNodeWeightParentSkipRoot); got != want {
174		t.Errorf("After close\ngot  %q\nwant %q", got, want)
175	}
176	if err := checkPriorityWellFormed(ws); err != nil {
177		t.Error(err)
178	}
179
180	// Adding a stream as an exclusive child of 1 gives it default
181	// priorities, since 1 is gone.
182	ws.OpenStream(5, OpenStreamOptions{})
183	ws.AdjustStream(5, PriorityParam{StreamDep: 1, Weight: 15, Exclusive: true})
184
185	// Adding a stream as an exclusive child of 2 should work, since 2 is not gone.
186	ws.OpenStream(6, OpenStreamOptions{})
187	ws.AdjustStream(6, PriorityParam{StreamDep: 2, Weight: 15, Exclusive: true})
188
189	want = "2{weight:15,parent:0} 3{weight:15,parent:6} 4{weight:15,parent:3} 5{weight:15,parent:0} 6{weight:15,parent:2}"
190	if got := fmtTree(ws, fmtNodeWeightParentSkipRoot); got != want {
191		t.Errorf("After add streams\ngot  %q\nwant %q", got, want)
192	}
193	if err := checkPriorityWellFormed(ws); err != nil {
194		t.Error(err)
195	}
196}
197
198func TestPriorityClosedStreamsDisabled(t *testing.T) {
199	ws := NewPriorityWriteScheduler(&PriorityWriteSchedulerConfig{}).(*priorityWriteScheduler)
200	ws.OpenStream(1, OpenStreamOptions{})
201	ws.OpenStream(2, OpenStreamOptions{PusherID: 1})
202	ws.OpenStream(3, OpenStreamOptions{PusherID: 2})
203
204	// Close the first two streams. We keep only 3.
205	ws.CloseStream(1)
206	ws.CloseStream(2)
207
208	want := "3{weight:15,parent:0}"
209	if got := fmtTree(ws, fmtNodeWeightParentSkipRoot); got != want {
210		t.Errorf("After close\ngot  %q\nwant %q", got, want)
211	}
212	if err := checkPriorityWellFormed(ws); err != nil {
213		t.Error(err)
214	}
215}
216
217func TestPriorityIdleStreams(t *testing.T) {
218	ws := NewPriorityWriteScheduler(&PriorityWriteSchedulerConfig{MaxIdleNodesInTree: 2}).(*priorityWriteScheduler)
219	ws.AdjustStream(1, PriorityParam{StreamDep: 0, Weight: 15}) // idle
220	ws.AdjustStream(2, PriorityParam{StreamDep: 0, Weight: 15}) // idle
221	ws.AdjustStream(3, PriorityParam{StreamDep: 2, Weight: 20}) // idle
222	ws.OpenStream(4, OpenStreamOptions{})
223	ws.OpenStream(5, OpenStreamOptions{})
224	ws.OpenStream(6, OpenStreamOptions{})
225	ws.AdjustStream(4, PriorityParam{StreamDep: 1, Weight: 15})
226	ws.AdjustStream(5, PriorityParam{StreamDep: 2, Weight: 15})
227	ws.AdjustStream(6, PriorityParam{StreamDep: 3, Weight: 15})
228
229	want := "2{weight:15,parent:0} 3{weight:20,parent:2} 4{weight:15,parent:0} 5{weight:15,parent:2} 6{weight:15,parent:3}"
230	if got := fmtTree(ws, fmtNodeWeightParentSkipRoot); got != want {
231		t.Errorf("After open\ngot  %q\nwant %q", got, want)
232	}
233	if err := checkPriorityWellFormed(ws); err != nil {
234		t.Error(err)
235	}
236}
237
238func TestPriorityIdleStreamsDisabled(t *testing.T) {
239	ws := NewPriorityWriteScheduler(&PriorityWriteSchedulerConfig{}).(*priorityWriteScheduler)
240	ws.AdjustStream(1, PriorityParam{StreamDep: 0, Weight: 15}) // idle
241	ws.AdjustStream(2, PriorityParam{StreamDep: 0, Weight: 15}) // idle
242	ws.AdjustStream(3, PriorityParam{StreamDep: 2, Weight: 20}) // idle
243	ws.OpenStream(4, OpenStreamOptions{})
244
245	want := "4{weight:15,parent:0}"
246	if got := fmtTree(ws, fmtNodeWeightParentSkipRoot); got != want {
247		t.Errorf("After open\ngot  %q\nwant %q", got, want)
248	}
249	if err := checkPriorityWellFormed(ws); err != nil {
250		t.Error(err)
251	}
252}
253
254func TestPrioritySection531NonExclusive(t *testing.T) {
255	// Example from RFC 7540 Section 5.3.1.
256	// A,B,C,D = 1,2,3,4
257	ws := defaultPriorityWriteScheduler()
258	ws.OpenStream(1, OpenStreamOptions{})
259	ws.OpenStream(2, OpenStreamOptions{PusherID: 1})
260	ws.OpenStream(3, OpenStreamOptions{PusherID: 1})
261	ws.OpenStream(4, OpenStreamOptions{})
262	ws.AdjustStream(4, PriorityParam{
263		StreamDep: 1,
264		Weight:    15,
265		Exclusive: false,
266	})
267	want := "1{parent:0} 2{parent:1} 3{parent:1} 4{parent:1}"
268	if got := fmtTree(ws, fmtNodeParentSkipRoot); got != want {
269		t.Errorf("After adjust\ngot  %q\nwant %q", got, want)
270	}
271	if err := checkPriorityWellFormed(ws); err != nil {
272		t.Error(err)
273	}
274}
275
276func TestPrioritySection531Exclusive(t *testing.T) {
277	// Example from RFC 7540 Section 5.3.1.
278	// A,B,C,D = 1,2,3,4
279	ws := defaultPriorityWriteScheduler()
280	ws.OpenStream(1, OpenStreamOptions{})
281	ws.OpenStream(2, OpenStreamOptions{PusherID: 1})
282	ws.OpenStream(3, OpenStreamOptions{PusherID: 1})
283	ws.OpenStream(4, OpenStreamOptions{})
284	ws.AdjustStream(4, PriorityParam{
285		StreamDep: 1,
286		Weight:    15,
287		Exclusive: true,
288	})
289	want := "1{parent:0} 2{parent:4} 3{parent:4} 4{parent:1}"
290	if got := fmtTree(ws, fmtNodeParentSkipRoot); got != want {
291		t.Errorf("After adjust\ngot  %q\nwant %q", got, want)
292	}
293	if err := checkPriorityWellFormed(ws); err != nil {
294		t.Error(err)
295	}
296}
297
298func makeSection533Tree() *priorityWriteScheduler {
299	// Initial tree from RFC 7540 Section 5.3.3.
300	// A,B,C,D,E,F = 1,2,3,4,5,6
301	ws := defaultPriorityWriteScheduler()
302	ws.OpenStream(1, OpenStreamOptions{})
303	ws.OpenStream(2, OpenStreamOptions{PusherID: 1})
304	ws.OpenStream(3, OpenStreamOptions{PusherID: 1})
305	ws.OpenStream(4, OpenStreamOptions{PusherID: 3})
306	ws.OpenStream(5, OpenStreamOptions{PusherID: 3})
307	ws.OpenStream(6, OpenStreamOptions{PusherID: 4})
308	return ws
309}
310
311func TestPrioritySection533NonExclusive(t *testing.T) {
312	// Example from RFC 7540 Section 5.3.3.
313	// A,B,C,D,E,F = 1,2,3,4,5,6
314	ws := defaultPriorityWriteScheduler()
315	ws.OpenStream(1, OpenStreamOptions{})
316	ws.OpenStream(2, OpenStreamOptions{PusherID: 1})
317	ws.OpenStream(3, OpenStreamOptions{PusherID: 1})
318	ws.OpenStream(4, OpenStreamOptions{PusherID: 3})
319	ws.OpenStream(5, OpenStreamOptions{PusherID: 3})
320	ws.OpenStream(6, OpenStreamOptions{PusherID: 4})
321	ws.AdjustStream(1, PriorityParam{
322		StreamDep: 4,
323		Weight:    15,
324		Exclusive: false,
325	})
326	want := "1{parent:4} 2{parent:1} 3{parent:1} 4{parent:0} 5{parent:3} 6{parent:4}"
327	if got := fmtTree(ws, fmtNodeParentSkipRoot); got != want {
328		t.Errorf("After adjust\ngot  %q\nwant %q", got, want)
329	}
330	if err := checkPriorityWellFormed(ws); err != nil {
331		t.Error(err)
332	}
333}
334
335func TestPrioritySection533Exclusive(t *testing.T) {
336	// Example from RFC 7540 Section 5.3.3.
337	// A,B,C,D,E,F = 1,2,3,4,5,6
338	ws := defaultPriorityWriteScheduler()
339	ws.OpenStream(1, OpenStreamOptions{})
340	ws.OpenStream(2, OpenStreamOptions{PusherID: 1})
341	ws.OpenStream(3, OpenStreamOptions{PusherID: 1})
342	ws.OpenStream(4, OpenStreamOptions{PusherID: 3})
343	ws.OpenStream(5, OpenStreamOptions{PusherID: 3})
344	ws.OpenStream(6, OpenStreamOptions{PusherID: 4})
345	ws.AdjustStream(1, PriorityParam{
346		StreamDep: 4,
347		Weight:    15,
348		Exclusive: true,
349	})
350	want := "1{parent:4} 2{parent:1} 3{parent:1} 4{parent:0} 5{parent:3} 6{parent:1}"
351	if got := fmtTree(ws, fmtNodeParentSkipRoot); got != want {
352		t.Errorf("After adjust\ngot  %q\nwant %q", got, want)
353	}
354	if err := checkPriorityWellFormed(ws); err != nil {
355		t.Error(err)
356	}
357}
358
359func checkPopAll(ws WriteScheduler, order []uint32) error {
360	for k, id := range order {
361		wr, ok := ws.Pop()
362		if !ok {
363			return fmt.Errorf("Pop[%d]: got ok=false, want %d (order=%v)", k, id, order)
364		}
365		if got := wr.StreamID(); got != id {
366			return fmt.Errorf("Pop[%d]: got %v, want %d (order=%v)", k, got, id, order)
367		}
368	}
369	wr, ok := ws.Pop()
370	if ok {
371		return fmt.Errorf("Pop[%d]: got %v, want ok=false (order=%v)", len(order), wr.StreamID(), order)
372	}
373	return nil
374}
375
376func TestPriorityPopFrom533Tree(t *testing.T) {
377	ws := makeSection533Tree()
378
379	ws.Push(makeWriteHeadersRequest(3 /*C*/))
380	ws.Push(makeWriteNonStreamRequest())
381	ws.Push(makeWriteHeadersRequest(5 /*E*/))
382	ws.Push(makeWriteHeadersRequest(1 /*A*/))
383	t.Log("tree:", fmtTree(ws, fmtNodeParentSkipRoot))
384
385	if err := checkPopAll(ws, []uint32{0 /*NonStream*/, 1, 3, 5}); err != nil {
386		t.Error(err)
387	}
388}
389
390func TestPriorityPopFromLinearTree(t *testing.T) {
391	ws := defaultPriorityWriteScheduler()
392	ws.OpenStream(1, OpenStreamOptions{})
393	ws.OpenStream(2, OpenStreamOptions{PusherID: 1})
394	ws.OpenStream(3, OpenStreamOptions{PusherID: 2})
395	ws.OpenStream(4, OpenStreamOptions{PusherID: 3})
396
397	ws.Push(makeWriteHeadersRequest(3))
398	ws.Push(makeWriteHeadersRequest(4))
399	ws.Push(makeWriteHeadersRequest(1))
400	ws.Push(makeWriteHeadersRequest(2))
401	ws.Push(makeWriteNonStreamRequest())
402	ws.Push(makeWriteNonStreamRequest())
403	t.Log("tree:", fmtTree(ws, fmtNodeParentSkipRoot))
404
405	if err := checkPopAll(ws, []uint32{0, 0 /*NonStreams*/, 1, 2, 3, 4}); err != nil {
406		t.Error(err)
407	}
408}
409
410func TestPriorityFlowControl(t *testing.T) {
411	ws := NewPriorityWriteScheduler(&PriorityWriteSchedulerConfig{ThrottleOutOfOrderWrites: false})
412	ws.OpenStream(1, OpenStreamOptions{})
413	ws.OpenStream(2, OpenStreamOptions{PusherID: 1})
414
415	sc := &serverConn{maxFrameSize: 16}
416	st1 := &stream{id: 1, sc: sc}
417	st2 := &stream{id: 2, sc: sc}
418
419	ws.Push(FrameWriteRequest{&writeData{1, make([]byte, 16), false}, st1, nil})
420	ws.Push(FrameWriteRequest{&writeData{2, make([]byte, 16), false}, st2, nil})
421	ws.AdjustStream(2, PriorityParam{StreamDep: 1})
422
423	// No flow-control bytes available.
424	if wr, ok := ws.Pop(); ok {
425		t.Fatalf("Pop(limited by flow control)=%v,true, want false", wr)
426	}
427
428	// Add enough flow-control bytes to write st2 in two Pop calls.
429	// Should write data from st2 even though it's lower priority than st1.
430	for i := 1; i <= 2; i++ {
431		st2.flow.add(8)
432		wr, ok := ws.Pop()
433		if !ok {
434			t.Fatalf("Pop(%d)=false, want true", i)
435		}
436		if got, want := wr.DataSize(), 8; got != want {
437			t.Fatalf("Pop(%d)=%d bytes, want %d bytes", i, got, want)
438		}
439	}
440}
441
442func TestPriorityThrottleOutOfOrderWrites(t *testing.T) {
443	ws := NewPriorityWriteScheduler(&PriorityWriteSchedulerConfig{ThrottleOutOfOrderWrites: true})
444	ws.OpenStream(1, OpenStreamOptions{})
445	ws.OpenStream(2, OpenStreamOptions{PusherID: 1})
446
447	sc := &serverConn{maxFrameSize: 4096}
448	st1 := &stream{id: 1, sc: sc}
449	st2 := &stream{id: 2, sc: sc}
450	st1.flow.add(4096)
451	st2.flow.add(4096)
452	ws.Push(FrameWriteRequest{&writeData{2, make([]byte, 4096), false}, st2, nil})
453	ws.AdjustStream(2, PriorityParam{StreamDep: 1})
454
455	// We have enough flow-control bytes to write st2 in a single Pop call.
456	// However, due to out-of-order write throttling, the first call should
457	// only write 1KB.
458	wr, ok := ws.Pop()
459	if !ok {
460		t.Fatalf("Pop(st2.first)=false, want true")
461	}
462	if got, want := wr.StreamID(), uint32(2); got != want {
463		t.Fatalf("Pop(st2.first)=stream %d, want stream %d", got, want)
464	}
465	if got, want := wr.DataSize(), 1024; got != want {
466		t.Fatalf("Pop(st2.first)=%d bytes, want %d bytes", got, want)
467	}
468
469	// Now add data on st1. This should take precedence.
470	ws.Push(FrameWriteRequest{&writeData{1, make([]byte, 4096), false}, st1, nil})
471	wr, ok = ws.Pop()
472	if !ok {
473		t.Fatalf("Pop(st1)=false, want true")
474	}
475	if got, want := wr.StreamID(), uint32(1); got != want {
476		t.Fatalf("Pop(st1)=stream %d, want stream %d", got, want)
477	}
478	if got, want := wr.DataSize(), 4096; got != want {
479		t.Fatalf("Pop(st1)=%d bytes, want %d bytes", got, want)
480	}
481
482	// Should go back to writing 1KB from st2.
483	wr, ok = ws.Pop()
484	if !ok {
485		t.Fatalf("Pop(st2.last)=false, want true")
486	}
487	if got, want := wr.StreamID(), uint32(2); got != want {
488		t.Fatalf("Pop(st2.last)=stream %d, want stream %d", got, want)
489	}
490	if got, want := wr.DataSize(), 1024; got != want {
491		t.Fatalf("Pop(st2.last)=%d bytes, want %d bytes", got, want)
492	}
493}
494
495func TestPriorityWeights(t *testing.T) {
496	ws := defaultPriorityWriteScheduler()
497	ws.OpenStream(1, OpenStreamOptions{})
498	ws.OpenStream(2, OpenStreamOptions{})
499
500	sc := &serverConn{maxFrameSize: 8}
501	st1 := &stream{id: 1, sc: sc}
502	st2 := &stream{id: 2, sc: sc}
503	st1.flow.add(40)
504	st2.flow.add(40)
505
506	ws.Push(FrameWriteRequest{&writeData{1, make([]byte, 40), false}, st1, nil})
507	ws.Push(FrameWriteRequest{&writeData{2, make([]byte, 40), false}, st2, nil})
508	ws.AdjustStream(1, PriorityParam{StreamDep: 0, Weight: 34})
509	ws.AdjustStream(2, PriorityParam{StreamDep: 0, Weight: 9})
510
511	// st1 gets 3.5x the bandwidth of st2 (3.5 = (34+1)/(9+1)).
512	// The maximum frame size is 8 bytes. The write sequence should be:
513	//   st1, total bytes so far is (st1=8,  st=0)
514	//   st2, total bytes so far is (st1=8,  st=8)
515	//   st1, total bytes so far is (st1=16, st=8)
516	//   st1, total bytes so far is (st1=24, st=8)   // 3x bandwidth
517	//   st1, total bytes so far is (st1=32, st=8)   // 4x bandwidth
518	//   st2, total bytes so far is (st1=32, st=16)  // 2x bandwidth
519	//   st1, total bytes so far is (st1=40, st=16)
520	//   st2, total bytes so far is (st1=40, st=24)
521	//   st2, total bytes so far is (st1=40, st=32)
522	//   st2, total bytes so far is (st1=40, st=40)
523	if err := checkPopAll(ws, []uint32{1, 2, 1, 1, 1, 2, 1, 2, 2, 2}); err != nil {
524		t.Error(err)
525	}
526}
527
528func TestPriorityRstStreamOnNonOpenStreams(t *testing.T) {
529	ws := NewPriorityWriteScheduler(&PriorityWriteSchedulerConfig{
530		MaxClosedNodesInTree: 0,
531		MaxIdleNodesInTree:   0,
532	})
533	ws.OpenStream(1, OpenStreamOptions{})
534	ws.CloseStream(1)
535	ws.Push(FrameWriteRequest{write: streamError(1, ErrCodeProtocol)})
536	ws.Push(FrameWriteRequest{write: streamError(2, ErrCodeProtocol)})
537
538	if err := checkPopAll(ws, []uint32{1, 2}); err != nil {
539		t.Error(err)
540	}
541}
542