| /* |
| * |
| * Copyright 2024 gRPC authors. |
| * |
| * Licensed under the Apache License, Version 2.0 (the "License"); |
| * you may not use this file except in compliance with the License. |
| * You may obtain a copy of the License at |
| * |
| * http://www.apache.org/licenses/LICENSE-2.0 |
| * |
| * Unless required by applicable law or agreed to in writing, software |
| * distributed under the License is distributed on an "AS IS" BASIS, |
| * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. |
| * See the License for the specific language governing permissions and |
| * limitations under the License. |
| * |
| */ |
| |
| package mem_test |
| |
| import ( |
| "bytes" |
| "crypto/rand" |
| "errors" |
| "fmt" |
| "io" |
| "testing" |
| |
| "google.golang.org/grpc/mem" |
| ) |
| |
| const ( |
| minReadSize = 1 |
| // Should match the constant in buffer_slice.go (another package) |
| readAllBufSize = 32 * 1024 // 32 KiB |
| ) |
| |
| func newBuffer(data []byte, pool mem.BufferPool) mem.Buffer { |
| return mem.NewBuffer(&data, pool) |
| } |
| |
| func (s) TestBufferSlice_Len(t *testing.T) { |
| tests := []struct { |
| name string |
| in mem.BufferSlice |
| want int |
| }{ |
| { |
| name: "empty", |
| in: nil, |
| want: 0, |
| }, |
| { |
| name: "single", |
| in: mem.BufferSlice{newBuffer([]byte("abcd"), nil)}, |
| want: 4, |
| }, |
| { |
| name: "multiple", |
| in: mem.BufferSlice{ |
| newBuffer([]byte("abcd"), nil), |
| newBuffer([]byte("abcd"), nil), |
| newBuffer([]byte("abcd"), nil), |
| }, |
| want: 12, |
| }, |
| } |
| for _, tt := range tests { |
| t.Run(tt.name, func(t *testing.T) { |
| if got := tt.in.Len(); got != tt.want { |
| t.Errorf("BufferSlice.Len() = %v, want %v", got, tt.want) |
| } |
| }) |
| } |
| } |
| |
| func (s) TestBufferSlice_Ref(t *testing.T) { |
| // Create a new buffer slice and a reference to it. |
| bs := mem.BufferSlice{ |
| newBuffer([]byte("abcd"), nil), |
| newBuffer([]byte("abcd"), nil), |
| } |
| bs.Ref() |
| |
| // Free the original buffer slice and verify that the reference can still |
| // read data from it. |
| bs.Free() |
| got := bs.Materialize() |
| want := []byte("abcdabcd") |
| if !bytes.Equal(got, want) { |
| t.Errorf("BufferSlice.Materialize() = %s, want %s", string(got), string(want)) |
| } |
| } |
| |
| func (s) TestBufferSlice_MaterializeToBuffer(t *testing.T) { |
| tests := []struct { |
| name string |
| in mem.BufferSlice |
| pool mem.BufferPool |
| wantData []byte |
| }{ |
| { |
| name: "single", |
| in: mem.BufferSlice{newBuffer([]byte("abcd"), nil)}, |
| pool: nil, // MaterializeToBuffer should not use the pool in this case. |
| wantData: []byte("abcd"), |
| }, |
| { |
| name: "multiple", |
| in: mem.BufferSlice{ |
| newBuffer([]byte("abcd"), nil), |
| newBuffer([]byte("abcd"), nil), |
| newBuffer([]byte("abcd"), nil), |
| }, |
| pool: mem.DefaultBufferPool(), |
| wantData: []byte("abcdabcdabcd"), |
| }, |
| } |
| for _, tt := range tests { |
| t.Run(tt.name, func(t *testing.T) { |
| defer tt.in.Free() |
| got := tt.in.MaterializeToBuffer(tt.pool) |
| defer got.Free() |
| if !bytes.Equal(got.ReadOnlyData(), tt.wantData) { |
| t.Errorf("BufferSlice.MaterializeToBuffer() = %s, want %s", string(got.ReadOnlyData()), string(tt.wantData)) |
| } |
| }) |
| } |
| } |
| |
| func (s) TestBufferSlice_Reader(t *testing.T) { |
| bs := mem.BufferSlice{ |
| newBuffer([]byte("abcd"), nil), |
| newBuffer([]byte("abcd"), nil), |
| newBuffer([]byte("abcd"), nil), |
| } |
| wantData := []byte("abcdabcdabcd") |
| |
| reader := bs.Reader() |
| var gotData []byte |
| // Read into a buffer of size 1 until EOF, and verify that the data matches. |
| for { |
| buf := make([]byte, 1) |
| n, err := reader.Read(buf) |
| if n > 0 { |
| gotData = append(gotData, buf[:n]...) |
| } |
| if err == io.EOF { |
| break |
| } |
| if err != nil { |
| t.Fatalf("BufferSlice.Reader() failed unexpectedly: %v", err) |
| } |
| } |
| if !bytes.Equal(gotData, wantData) { |
| t.Errorf("BufferSlice.Reader() returned data %v, want %v", string(gotData), string(wantData)) |
| } |
| |
| // Reader should have released its references to the underlying buffers, but |
| // bs still holds its reference and it should be able to read data from it. |
| gotData = bs.Materialize() |
| if !bytes.Equal(gotData, wantData) { |
| t.Errorf("BufferSlice.Materialize() = %s, want %s", string(gotData), string(wantData)) |
| } |
| } |
| |
| // TestBufferSlice_ReadAll_Reads exercises ReadAll by allowing it to read |
| // various combinations of data, empty data, EOF. |
| func (s) TestBufferSlice_ReadAll_Reads(t *testing.T) { |
| testcases := []struct { |
| name string |
| reads []readStep |
| wantErr string |
| wantBufs int |
| }{ |
| { |
| name: "EOF", |
| reads: []readStep{ |
| { |
| err: io.EOF, |
| }, |
| }, |
| }, |
| { |
| name: "data,EOF", |
| reads: []readStep{ |
| { |
| n: minReadSize, |
| }, |
| { |
| err: io.EOF, |
| }, |
| }, |
| wantBufs: 1, |
| }, |
| { |
| name: "data+EOF", |
| reads: []readStep{ |
| { |
| n: minReadSize, |
| err: io.EOF, |
| }, |
| }, |
| wantBufs: 1, |
| }, |
| { |
| name: "0,data+EOF", |
| reads: []readStep{ |
| {}, |
| { |
| n: minReadSize, |
| err: io.EOF, |
| }, |
| }, |
| wantBufs: 1, |
| }, |
| { |
| name: "0,data,EOF", |
| reads: []readStep{ |
| {}, |
| { |
| n: minReadSize, |
| }, |
| { |
| err: io.EOF, |
| }, |
| }, |
| wantBufs: 1, |
| }, |
| { |
| name: "data,data+EOF", |
| reads: []readStep{ |
| { |
| n: minReadSize, |
| }, |
| { |
| n: minReadSize, |
| err: io.EOF, |
| }, |
| }, |
| wantBufs: 1, |
| }, |
| { |
| name: "error", |
| reads: []readStep{ |
| { |
| err: errors.New("boom"), |
| }, |
| }, |
| wantErr: "boom", |
| }, |
| { |
| name: "data+error", |
| reads: []readStep{ |
| { |
| n: minReadSize, |
| err: errors.New("boom"), |
| }, |
| }, |
| wantErr: "boom", |
| wantBufs: 1, |
| }, |
| { |
| name: "data,data+error", |
| reads: []readStep{ |
| { |
| n: minReadSize, |
| }, |
| { |
| n: minReadSize, |
| err: errors.New("boom"), |
| }, |
| }, |
| wantErr: "boom", |
| wantBufs: 1, |
| }, |
| { |
| name: "data,data+EOF - whole buf", |
| reads: []readStep{ |
| { |
| n: minReadSize, |
| }, |
| { |
| n: readAllBufSize - minReadSize, |
| err: io.EOF, |
| }, |
| }, |
| wantBufs: 1, |
| }, |
| { |
| name: "data,data,EOF - whole buf", |
| reads: []readStep{ |
| { |
| n: minReadSize, |
| }, |
| { |
| n: readAllBufSize - minReadSize, |
| }, |
| { |
| err: io.EOF, |
| }, |
| }, |
| wantBufs: 1, |
| }, |
| { |
| name: "data,data,EOF - 2 bufs", |
| reads: []readStep{ |
| { |
| n: readAllBufSize, |
| }, |
| { |
| n: minReadSize, |
| }, |
| { |
| n: readAllBufSize - minReadSize, |
| }, |
| { |
| n: minReadSize, |
| }, |
| { |
| err: io.EOF, |
| }, |
| }, |
| wantBufs: 3, |
| }, |
| } |
| |
| for _, tc := range testcases { |
| t.Run(tc.name, func(t *testing.T) { |
| pool := &testPool{ |
| allocated: make(map[*[]byte]struct{}), |
| } |
| r := &stepReader{ |
| reads: tc.reads, |
| } |
| data, err := mem.ReadAll(r, pool) |
| if tc.wantErr != "" { |
| if err == nil || err.Error() != tc.wantErr { |
| t.Fatalf("ReadAll() returned err %v, wanted %q", err, tc.wantErr) |
| } |
| } else { |
| if err != nil { |
| t.Fatal(err) |
| } |
| } |
| gotData := data.Materialize() |
| if !bytes.Equal(r.read, gotData) { |
| t.Fatalf("ReadAll() returned data %q, wanted %q", gotData, r.read) |
| } |
| if len(data) != tc.wantBufs { |
| t.Fatalf("ReadAll() returned %d bufs, wanted %d bufs", len(data), tc.wantBufs) |
| } |
| // all but last should be full buffers |
| for i := 0; i < len(data)-1; i++ { |
| if data[i].Len() != readAllBufSize { |
| t.Fatalf("ReadAll() returned data length %d, wanted %d", data[i].Len(), readAllBufSize) |
| } |
| } |
| data.Free() |
| if len(pool.allocated) > 0 { |
| t.Fatalf("got %d allocated buffers, wanted none", len(pool.allocated)) |
| } |
| }) |
| } |
| } |
| |
| func (s) TestBufferSlice_ReadAll_WriteTo(t *testing.T) { |
| testcases := []struct { |
| name string |
| size int |
| }{ |
| { |
| name: "small", |
| size: minReadSize, |
| }, |
| { |
| name: "exact size", |
| size: readAllBufSize, |
| }, |
| { |
| name: "big", |
| size: readAllBufSize * 3, |
| }, |
| } |
| for _, tc := range testcases { |
| t.Run(tc.name, func(t *testing.T) { |
| pool := &testPool{ |
| allocated: make(map[*[]byte]struct{}), |
| } |
| buf := make([]byte, tc.size) |
| _, err := rand.Read(buf) |
| if err != nil { |
| t.Fatal(err) |
| } |
| r := bytes.NewBuffer(buf) |
| data, err := mem.ReadAll(r, pool) |
| if err != nil { |
| t.Fatal(err) |
| } |
| |
| gotData := data.Materialize() |
| if !bytes.Equal(buf, gotData) { |
| t.Fatalf("ReadAll() = %q, wanted %q", gotData, buf) |
| } |
| data.Free() |
| if len(pool.allocated) > 0 { |
| t.Fatalf("wanted no allocated buffers, got %d", len(pool.allocated)) |
| } |
| }) |
| } |
| } |
| |
| func ExampleNewWriter() { |
| var bs mem.BufferSlice |
| pool := mem.DefaultBufferPool() |
| writer := mem.NewWriter(&bs, pool) |
| |
| for _, data := range [][]byte{ |
| []byte("abcd"), |
| []byte("abcd"), |
| []byte("abcd"), |
| } { |
| n, err := writer.Write(data) |
| fmt.Printf("Wrote %d bytes, err: %v\n", n, err) |
| } |
| fmt.Println(string(bs.Materialize())) |
| // Output: |
| // Wrote 4 bytes, err: <nil> |
| // Wrote 4 bytes, err: <nil> |
| // Wrote 4 bytes, err: <nil> |
| // abcdabcdabcd |
| } |
| |
| var ( |
| _ io.Reader = (*stepReader)(nil) |
| _ mem.BufferPool = (*testPool)(nil) |
| ) |
| |
| // readStep describes what a single stepReader.Read should do - how much data |
| // to return and what error to return. |
| type readStep struct { |
| n int |
| err error |
| } |
| |
| // stepReader implements io.Reader that reads specified amount of data and/or |
| // returns the specified error in specified steps. |
| // The read data is accumulated in the read field. |
| type stepReader struct { |
| reads []readStep |
| read []byte |
| } |
| |
| func (s *stepReader) Read(buf []byte) (int, error) { |
| if len(s.reads) == 0 { |
| panic("unexpected Read() call") |
| } |
| read := s.reads[0] |
| s.reads = s.reads[1:] |
| _, err := rand.Read(buf[:read.n]) |
| if err != nil { |
| panic(err) |
| } |
| s.read = append(s.read, buf[:read.n]...) |
| return read.n, read.err |
| } |
| |
| // testPool is an implementation of BufferPool that allows to ensure that: |
| // - there are matching Put calls for all Get calls. |
| // - there are no unexpected Put calls. |
| type testPool struct { |
| allocated map[*[]byte]struct{} |
| } |
| |
| func (t *testPool) Get(length int) *[]byte { |
| buf := make([]byte, length) |
| t.allocated[&buf] = struct{}{} |
| return &buf |
| } |
| |
| func (t *testPool) Put(buf *[]byte) { |
| if _, ok := t.allocated[buf]; !ok { |
| panic("unexpected put") |
| } |
| delete(t.allocated, buf) |
| } |
| |
| func (s) TestBufferSlice_Iteration(t *testing.T) { |
| tests := []struct { |
| name string |
| buffers [][]byte |
| operations func(t *testing.T, c *mem.Reader) |
| }{ |
| { |
| name: "empty", |
| operations: func(t *testing.T, r *mem.Reader) { |
| if r.Remaining() != 0 { |
| t.Fatalf("Remaining() = %v, want 0", r.Remaining()) |
| } |
| _, err := r.Peek(1, nil) |
| if err == nil { |
| t.Fatalf("Peek(1) returned error <nil>, want non-nil") |
| } |
| discarded, err := r.Discard(1) |
| if got, want := discarded, 0; got != want { |
| t.Fatalf("Discard(1) = %d, want %d", got, want) |
| } |
| if err == nil { |
| t.Fatalf("Discard(1) returned error <nil>, want non-nil") |
| } |
| if r.Remaining() != 0 { |
| t.Fatalf("Remaining() after Discard = %v, want 0", r.Remaining()) |
| } |
| }, |
| }, |
| { |
| name: "single_buffer", |
| buffers: [][]byte{[]byte("0123456789")}, |
| operations: func(t *testing.T, r *mem.Reader) { |
| if r.Remaining() != 10 { |
| t.Fatalf("Remaining() = %v, want 10", r.Remaining()) |
| } |
| |
| res := make([][]byte, 0, 10) |
| res, err := r.Peek(5, res) |
| if err != nil { |
| t.Fatalf("Peek(5) return error %v, want <nil>", err) |
| } |
| if len(res) != 1 || !bytes.Equal(res[0], []byte("01234")) { |
| t.Fatalf("Peek(5) = %v, want [[01234]]", res) |
| } |
| if cap(res) != 10 { |
| t.Fatalf("Peek(5) did not use the provided slice.") |
| } |
| |
| discarded, err := r.Discard(5) |
| if got, want := discarded, 5; got != want { |
| t.Fatalf("Discard(5) = %d, want %d", got, want) |
| } |
| if err != nil { |
| t.Fatalf("Discard(5) return error %v, want <nil>", err) |
| } |
| if r.Remaining() != 5 { |
| t.Fatalf("Remaining() after Discard(5) = %v, want 5", r.Remaining()) |
| } |
| res, err = r.Peek(5, res[:0]) |
| if err != nil { |
| t.Fatalf("Peek(5) return error %v, want <nil>", err) |
| } |
| if len(res) != 1 || !bytes.Equal(res[0], []byte("56789")) { |
| t.Fatalf("Peek(5) after Discard(5) = %v, want [[56789]]", res) |
| } |
| |
| discarded, err = r.Discard(100) |
| if got, want := discarded, 5; got != want { |
| t.Fatalf("Discard(100) = %d, want %d", got, want) |
| } |
| if err == nil { |
| t.Fatalf("Discard(100) returned error <nil>, want non-nil") |
| } |
| if r.Remaining() != 0 { |
| t.Fatalf("Remaining() after Discard(100) = %v, want 0", r.Remaining()) |
| } |
| }, |
| }, |
| { |
| name: "multiple_buffers", |
| buffers: [][]byte{[]byte("012"), []byte("345"), []byte("6789")}, |
| operations: func(t *testing.T, r *mem.Reader) { |
| if r.Remaining() != 10 { |
| t.Fatalf("Remaining() = %v, want 10", r.Remaining()) |
| } |
| |
| res, err := r.Peek(5, nil) |
| if err != nil { |
| t.Fatalf("Peek(5) return error %v, want <nil>", err) |
| } |
| if len(res) != 2 || !bytes.Equal(res[0], []byte("012")) || !bytes.Equal(res[1], []byte("34")) { |
| t.Fatalf("Peek(5) = %v, want [[012] [34]]", res) |
| } |
| |
| discarded, err := r.Discard(5) |
| if got, want := discarded, 5; got != want { |
| t.Fatalf("Discard(5) = %d, want %d", got, want) |
| } |
| if err != nil { |
| t.Fatalf("Discard(5) return error %v, want <nil>", err) |
| } |
| if r.Remaining() != 5 { |
| t.Fatalf("Remaining() after Discard(5) = %v, want 5", r.Remaining()) |
| } |
| |
| res, err = r.Peek(5, res[:0]) |
| if err != nil { |
| t.Fatalf("Peek(5) return error %v, want <nil>", err) |
| } |
| if len(res) != 2 || !bytes.Equal(res[0], []byte("5")) || !bytes.Equal(res[1], []byte("6789")) { |
| t.Fatalf("Peek(5) after advance = %v, want [[5] [6789]]", res) |
| } |
| }, |
| }, |
| { |
| name: "close", |
| buffers: [][]byte{[]byte("0123456789")}, |
| operations: func(t *testing.T, r *mem.Reader) { |
| r.Close() |
| if r.Remaining() != 0 { |
| t.Fatalf("Remaining() after Close = %v, want 0", r.Remaining()) |
| } |
| }, |
| }, |
| { |
| name: "reset", |
| buffers: [][]byte{[]byte("0123")}, |
| operations: func(t *testing.T, r *mem.Reader) { |
| newSlice := mem.BufferSlice{mem.SliceBuffer([]byte("56789"))} |
| r.Reset(newSlice) |
| if r.Remaining() != 5 { |
| t.Fatalf("Remaining() after Reset = %v, want 5", r.Remaining()) |
| } |
| res, err := r.Peek(5, nil) |
| if err != nil { |
| t.Fatalf("Peek(5) return error %v, want <nil>", err) |
| } |
| if len(res) != 1 || !bytes.Equal(res[0], []byte("56789")) { |
| t.Fatalf("Peek(5) after Reset = %v, want [[56789]]", res) |
| } |
| }, |
| }, |
| { |
| name: "zero_ops", |
| buffers: [][]byte{[]byte("01234")}, |
| operations: func(t *testing.T, c *mem.Reader) { |
| if c.Remaining() != 5 { |
| t.Fatalf("Remaining() = %v, want 5", c.Remaining()) |
| } |
| res, err := c.Peek(0, nil) |
| if err != nil { |
| t.Fatalf("Peek(0) return error %v, want <nil>", err) |
| } |
| if len(res) != 0 { |
| t.Fatalf("Peek(0) got slices: %v, want empty", res) |
| } |
| discarded, err := c.Discard(0) |
| if err != nil { |
| t.Fatalf("Discard(0) return error %v, want <nil>", err) |
| } |
| if got, want := discarded, 0; got != want { |
| t.Fatalf("Discard(0) = %d, want %d", got, want) |
| } |
| if c.Remaining() != 5 { |
| t.Fatalf("Remaining() after Discard(0) = %v, want 5", c.Remaining()) |
| } |
| res, err = c.Peek(2, res[:0]) |
| if err != nil { |
| t.Fatalf("Peek(2) return error %v, want <nil>", err) |
| } |
| if len(res) != 1 || !bytes.Equal(res[0], []byte("01")) { |
| t.Fatalf("Peek(2) after zero ops = %v, want [[01]]", res) |
| } |
| }, |
| }, |
| } |
| for _, tt := range tests { |
| t.Run(tt.name, func(t *testing.T) { |
| var slice mem.BufferSlice |
| for _, b := range tt.buffers { |
| slice = append(slice, mem.SliceBuffer(b)) |
| } |
| c := slice.Reader() |
| slice.Free() |
| defer c.Close() |
| tt.operations(t, c) |
| }) |
| } |
| } |