// Copyright 2017 The Go Authors. All rights reserved. // Use of this source code is governed by a BSD-style // license that can be found in the LICENSE file. // +build go1.7 package http2 import ( "bytes" "fmt" "reflect" "testing" ) func fmtDataChunk(chunk []byte) string { out := "" var last byte var count int for _, c := range chunk { if c != last { if count > 0 { out += fmt.Sprintf(" x %d ", count) count = 0 } out += string([]byte{c}) last = c } count++ } if count > 0 { out += fmt.Sprintf(" x %d", count) } return out } func fmtDataChunks(chunks [][]byte) string { var out string for _, chunk := range chunks { out += fmt.Sprintf("{%q}", fmtDataChunk(chunk)) } return out } func testDataBuffer(t *testing.T, wantBytes []byte, setup func(t *testing.T) *dataBuffer) { // Run setup, then read the remaining bytes from the dataBuffer and check // that they match wantBytes. We use different read sizes to check corner // cases in Read. for _, readSize := range []int{1, 2, 1 * 1024, 32 * 1024} { t.Run(fmt.Sprintf("ReadSize=%d", readSize), func(t *testing.T) { b := setup(t) buf := make([]byte, readSize) var gotRead bytes.Buffer for { n, err := b.Read(buf) gotRead.Write(buf[:n]) if err == errReadEmpty { break } if err != nil { t.Fatalf("error after %v bytes: %v", gotRead.Len(), err) } } if got, want := gotRead.Bytes(), wantBytes; !bytes.Equal(got, want) { t.Errorf("FinalRead=%q, want %q", fmtDataChunk(got), fmtDataChunk(want)) } }) } } func TestDataBufferAllocation(t *testing.T) { writes := [][]byte{ bytes.Repeat([]byte("a"), 1*1024-1), []byte("a"), bytes.Repeat([]byte("b"), 4*1024-1), []byte("b"), bytes.Repeat([]byte("c"), 8*1024-1), []byte("c"), bytes.Repeat([]byte("d"), 16*1024-1), []byte("d"), bytes.Repeat([]byte("e"), 32*1024), } var wantRead bytes.Buffer for _, p := range writes { wantRead.Write(p) } testDataBuffer(t, wantRead.Bytes(), func(t *testing.T) *dataBuffer { b := &dataBuffer{} for _, p := range writes { if n, err := b.Write(p); n != len(p) || err != nil { t.Fatalf("Write(%q x %d)=%v,%v want %v,nil", p[:1], len(p), n, err, len(p)) } } want := [][]byte{ bytes.Repeat([]byte("a"), 1*1024), bytes.Repeat([]byte("b"), 4*1024), bytes.Repeat([]byte("c"), 8*1024), bytes.Repeat([]byte("d"), 16*1024), bytes.Repeat([]byte("e"), 16*1024), bytes.Repeat([]byte("e"), 16*1024), } if !reflect.DeepEqual(b.chunks, want) { t.Errorf("dataBuffer.chunks\ngot: %s\nwant: %s", fmtDataChunks(b.chunks), fmtDataChunks(want)) } return b }) } func TestDataBufferAllocationWithExpected(t *testing.T) { writes := [][]byte{ bytes.Repeat([]byte("a"), 1*1024), // allocates 16KB bytes.Repeat([]byte("b"), 14*1024), bytes.Repeat([]byte("c"), 15*1024), // allocates 16KB more bytes.Repeat([]byte("d"), 2*1024), bytes.Repeat([]byte("e"), 1*1024), // overflows 32KB expectation, allocates just 1KB } var wantRead bytes.Buffer for _, p := range writes { wantRead.Write(p) } testDataBuffer(t, wantRead.Bytes(), func(t *testing.T) *dataBuffer { b := &dataBuffer{expected: 32 * 1024} for _, p := range writes { if n, err := b.Write(p); n != len(p) || err != nil { t.Fatalf("Write(%q x %d)=%v,%v want %v,nil", p[:1], len(p), n, err, len(p)) } } want := [][]byte{ append(bytes.Repeat([]byte("a"), 1*1024), append(bytes.Repeat([]byte("b"), 14*1024), bytes.Repeat([]byte("c"), 1*1024)...)...), append(bytes.Repeat([]byte("c"), 14*1024), bytes.Repeat([]byte("d"), 2*1024)...), bytes.Repeat([]byte("e"), 1*1024), } if !reflect.DeepEqual(b.chunks, want) { t.Errorf("dataBuffer.chunks\ngot: %s\nwant: %s", fmtDataChunks(b.chunks), fmtDataChunks(want)) } return b }) } func TestDataBufferWriteAfterPartialRead(t *testing.T) { testDataBuffer(t, []byte("cdxyz"), func(t *testing.T) *dataBuffer { b := &dataBuffer{} if n, err := b.Write([]byte("abcd")); n != 4 || err != nil { t.Fatalf("Write(\"abcd\")=%v,%v want 4,nil", n, err) } p := make([]byte, 2) if n, err := b.Read(p); n != 2 || err != nil || !bytes.Equal(p, []byte("ab")) { t.Fatalf("Read()=%q,%v,%v want \"ab\",2,nil", p, n, err) } if n, err := b.Write([]byte("xyz")); n != 3 || err != nil { t.Fatalf("Write(\"xyz\")=%v,%v want 3,nil", n, err) } return b }) }