├── Makefile ├── buffer.go ├── license.txt ├── pool.go ├── pool_test.go ├── fixed.go ├── bytes.go ├── readme.md └── bytes_test.go /Makefile: -------------------------------------------------------------------------------- 1 | t: 2 | go test ./... -v 3 | 4 | f: 5 | go fmt ./... 6 | -------------------------------------------------------------------------------- /buffer.go: -------------------------------------------------------------------------------- 1 | package bytepool 2 | 3 | import ( 4 | stdbytes "bytes" 5 | "io" 6 | ) 7 | 8 | type buffer struct { 9 | *stdbytes.Buffer 10 | } 11 | 12 | func (b *buffer) write(data []byte) (bytes, int, error) { 13 | n, err := b.Write(data) 14 | return b, n, err 15 | } 16 | 17 | func (b *buffer) writeByte(data byte) (bytes, error) { 18 | err := b.WriteByte(data) 19 | return b, err 20 | } 21 | 22 | func (b *buffer) position(n uint) bytes { 23 | s := b.Len() 24 | nn := int(n) 25 | t := nn - s 26 | if t == 0 { 27 | return b 28 | } 29 | if t < 0 { 30 | b.Truncate(nn) 31 | return b 32 | } 33 | b.Grow(t) 34 | bytes := b.Bytes() 35 | bytes = bytes[:nn] 36 | b.Write(bytes[s:nn]) 37 | return b 38 | } 39 | 40 | func (b *buffer) readNFrom(n int64, r io.Reader) (bytes, int64, error) { 41 | if n == 0 { 42 | m, err := b.ReadFrom(r) 43 | return b, m, err 44 | } 45 | s := b.Len() 46 | t := int(n) + s 47 | b.Grow(t) 48 | bytes := b.Bytes() 49 | bytes = bytes[:t] 50 | m, err := io.ReadFull(r, bytes[s:t]) 51 | b.Write(bytes[s:t]) 52 | return b, int64(m), err 53 | } 54 | -------------------------------------------------------------------------------- /license.txt: -------------------------------------------------------------------------------- 1 | Copyright (c) 2014 Karl Seguin. 2 | 3 | Permission is hereby granted, free of charge, to any person obtaining a copy 4 | of this software and associated documentation files (the "Software"), to deal 5 | in the Software without restriction, including without limitation the rights 6 | to use, copy, modify, merge, publish, distribute, sublicense, and/or sell 7 | copies of the Software, and to permit persons to whom the Software is 8 | furnished to do so, subject to the following conditions: 9 | 10 | The above copyright notice and this permission notice shall be included in 11 | all copies or substantial portions of the Software. 12 | 13 | THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR 14 | IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, 15 | FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE 16 | AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER 17 | LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, 18 | OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN 19 | THE SOFTWARE. 20 | -------------------------------------------------------------------------------- /pool.go: -------------------------------------------------------------------------------- 1 | // Package bytepool provides a pool of []byte 2 | package bytepool 3 | 4 | import ( 5 | "encoding/binary" 6 | "sync/atomic" 7 | ) 8 | 9 | type Pool struct { 10 | depleted int64 11 | expanded int64 12 | size int 13 | list chan *Bytes 14 | enc binary.ByteOrder 15 | stats map[string]int64 16 | } 17 | 18 | // Create a new pool. The pool contains count items. Each item allocates 19 | // an array of size bytes (but can dynamically grow) 20 | func New(size, count int) *Pool { 21 | return NewEndian(size, count, binary.BigEndian) 22 | } 23 | 24 | func NewEndian(size, count int, enc binary.ByteOrder) *Pool { 25 | pool := &Pool{ 26 | enc: enc, 27 | size: size, 28 | list: make(chan *Bytes, count), 29 | stats: map[string]int64{"depleted": 0, "expanded": 0}, 30 | } 31 | for i := 0; i < count; i++ { 32 | pool.list <- newPooled(pool, size, enc) 33 | } 34 | return pool 35 | } 36 | 37 | // Get an item from the pool 38 | func (p *Pool) Checkout() *Bytes { 39 | select { 40 | case bytes := <-p.list: 41 | return bytes 42 | default: 43 | atomic.AddInt64(&p.depleted, 1) 44 | return NewEndianBytes(p.size, p.enc) 45 | } 46 | } 47 | 48 | // Exposes every item currently in the pool 49 | // If an item is checkout, each won't see it. 50 | // As such, though thread-safe, you probably only 51 | // want to call this method on init/startup. 52 | func (p *Pool) Each(f func(*Bytes)) { 53 | l := len(p.list) 54 | t := make([]*Bytes, l) 55 | defer func() { 56 | for i := 0; i < len(t); i++ { 57 | t[i].Release() 58 | } 59 | }() 60 | for i := 0; i < l; i++ { 61 | b := <-p.list 62 | t[i] = b 63 | f(b) 64 | } 65 | } 66 | 67 | // Get a count of how often Checkout() was called 68 | // but no item was available (thus causing an item to be 69 | // created on the fly) 70 | // Calling this resets the counter 71 | func (p *Pool) Depleted() int64 { 72 | return atomic.SwapInt64(&p.depleted, 0) 73 | } 74 | 75 | // Get a count of how often we had to expand an item 76 | // beyond the initially specified size 77 | // Calling this resets the counter 78 | func (p *Pool) Expanded() int64 { 79 | return atomic.SwapInt64(&p.expanded, 0) 80 | } 81 | 82 | // A map containing the "expanded" and "depleted" count 83 | // Call this resets both counters 84 | func (p *Pool) Stats() map[string]int64 { 85 | p.stats["depleted"] = p.Depleted() 86 | p.stats["expanded"] = p.Expanded() 87 | return p.stats 88 | } 89 | 90 | func (p *Pool) onExpand() { 91 | atomic.AddInt64(&p.expanded, 1) 92 | } 93 | -------------------------------------------------------------------------------- /pool_test.go: -------------------------------------------------------------------------------- 1 | package bytepool 2 | 3 | import ( 4 | . "github.com/karlseguin/expect" 5 | "reflect" 6 | "strings" 7 | "testing" 8 | ) 9 | 10 | type PoolTests struct{} 11 | 12 | func Test_Pool(t *testing.T) { 13 | Expectify(new(PoolTests), t) 14 | } 15 | 16 | func (_ PoolTests) EachItemIsOfASpecifiedSize() { 17 | p := New(9, 1) 18 | bytes := p.Checkout() 19 | Expect(cap(bytes.fixed.bytes)).To.Equal(9) 20 | } 21 | 22 | func (_ PoolTests) HasTheSpecifiedNumberOfItems() { 23 | p := New(23, 3) 24 | Expect(cap(p.list)).To.Equal(3) 25 | } 26 | 27 | func (_ PoolTests) DynamicallyCreatesAnItemWhenPoolIsEmpty() { 28 | p := New(23, 1) 29 | bytes1 := p.Checkout() 30 | bytes2 := p.Checkout() 31 | Expect(cap(bytes2.fixed.bytes)).To.Equal(23) 32 | Expect(bytes2.pool).To.Equal(nil) 33 | bytes1.Release() 34 | bytes2.Release() 35 | Expect(len(p.list)).To.Equal(1) 36 | Expect(p.Depleted()).To.Equal(int64(1)) 37 | } 38 | 39 | func (_ PoolTests) ReleasesAnItemBackIntoThePool() { 40 | p := New(20, 1) 41 | bytes := p.Checkout() 42 | pointer := reflect.ValueOf(bytes).Pointer() 43 | bytes.Release() 44 | 45 | if reflect.ValueOf(p.Checkout()).Pointer() != pointer { 46 | Fail("Pool returned an unexected item") 47 | } 48 | } 49 | 50 | func (_ PoolTests) StatsTracksAndResetMisses() { 51 | p := New(1, 1) 52 | p.Checkout() 53 | p.Checkout() 54 | p.Checkout() 55 | 56 | Expect(p.Stats()["depleted"]).To.Equal(int64(2)) 57 | //calling stats should reset this 58 | Expect(p.Stats()["depleted"]).To.Equal(int64(0)) 59 | } 60 | 61 | func (_ PoolTests) TracksExpansion() { 62 | p := New(2, 1) 63 | for i := 1; i < 6; i++ { 64 | bytes := p.Checkout() 65 | bytes.WriteString(strings.Repeat("!", i)) 66 | bytes.Release() 67 | } 68 | Expect(p.Stats()["expanded"]).To.Equal(int64(3)) 69 | //calling stats should reset this 70 | Expect(p.Stats()["expanded"]).To.Equal(int64(0)) 71 | } 72 | 73 | // why? because it's hard 74 | func (_ PoolTests) DoesNotTrackUnpooledExpansion() { 75 | p := New(2, 1) 76 | for i := 1; i < 6; i++ { 77 | bytes := p.Checkout() 78 | bytes.WriteString(strings.Repeat("!", i)) 79 | } 80 | Expect(p.Stats()["expanded"]).To.Equal(int64(0)) 81 | } 82 | 83 | func (_ PoolTests) Each() { 84 | p := New(10, 4) 85 | i := byte(0) 86 | p.Each(func(b *Bytes) { 87 | b.WriteByte(i) 88 | i++ 89 | }) 90 | b := p.Checkout() 91 | b.Position(1) 92 | Expect(b.Bytes()).To.Equal([]byte{0}) 93 | 94 | b = p.Checkout() 95 | b.Position(1) 96 | Expect(b.Bytes()).To.Equal([]byte{1}) 97 | 98 | b = p.Checkout() 99 | b.Position(1) 100 | Expect(b.Bytes()).To.Equal([]byte{2}) 101 | 102 | b = p.Checkout() 103 | b.Position(1) 104 | Expect(b.Bytes()).To.Equal([]byte{3}) 105 | } 106 | -------------------------------------------------------------------------------- /fixed.go: -------------------------------------------------------------------------------- 1 | package bytepool 2 | 3 | import ( 4 | stdbytes "bytes" 5 | "io" 6 | ) 7 | 8 | type fixed struct { 9 | r int 10 | length int 11 | capacity int 12 | bytes []byte 13 | onExpand func() 14 | } 15 | 16 | func (f *fixed) Len() int { 17 | return f.length 18 | } 19 | 20 | func (f *fixed) Bytes() []byte { 21 | return f.bytes[:f.length] 22 | } 23 | 24 | func (f *fixed) String() string { 25 | return string(f.Bytes()) 26 | } 27 | 28 | func (f *fixed) write(data []byte) (bytes, int, error) { 29 | if l := len(data); f.hasSpace(l) == false { 30 | buf := f.toBuffer() 31 | n, err := buf.Write(data) 32 | return buf, n, err 33 | } 34 | n := copy(f.bytes[f.length:], data) 35 | f.length += n 36 | return f, n, nil 37 | } 38 | 39 | func (f *fixed) writeByte(data byte) (bytes, error) { 40 | if f.length == f.capacity { 41 | buf := f.toBuffer() 42 | err := buf.WriteByte(data) 43 | return buf, err 44 | } 45 | f.bytes[f.length] = data 46 | f.length++ 47 | return f, nil 48 | } 49 | 50 | func (f *fixed) position(n uint) bytes { 51 | nn := int(n) 52 | if nn >= f.capacity { 53 | return f.toBuffer().position(n) 54 | } 55 | f.length = nn 56 | return f 57 | } 58 | 59 | func (f *fixed) readNFrom(expected int64, reader io.Reader) (bytes, int64, error) { 60 | ex := int(expected) 61 | if f.hasSpace(ex) == false { 62 | return f.toBuffer().readNFrom(expected, reader) 63 | } 64 | end := f.capacity 65 | if ex != 0 { 66 | end = ex + f.length 67 | } 68 | 69 | read := 0 70 | for { 71 | if f.full() { 72 | buf := f.toBuffer() 73 | _, n, err := buf.readNFrom(expected, reader) 74 | return buf, int64(read) + n, err 75 | } 76 | r, err := reader.Read(f.bytes[f.length:end]) 77 | read += r 78 | f.length += r 79 | if err == io.EOF || (expected != 0 && read == ex) { 80 | return f, int64(read), err 81 | } 82 | if err != nil { 83 | return f, int64(read), err 84 | } 85 | } 86 | } 87 | 88 | func (f *fixed) WriteTo(w io.Writer) (n int64, err error) { 89 | r := f.r 90 | l := f.length 91 | for r < l { 92 | n, err := w.Write(f.bytes[r:l]) 93 | if err != nil { 94 | break 95 | } 96 | r += n 97 | } 98 | f.reset() 99 | f.r += int(n) 100 | return n, err 101 | } 102 | 103 | func (f *fixed) Read(data []byte) (int, error) { 104 | if f.r == f.length { 105 | return 0, io.EOF 106 | } 107 | n := copy(data, f.bytes[f.r:f.length]) 108 | f.r += n 109 | if f.r == f.length { 110 | return n, io.EOF 111 | } 112 | return n, nil 113 | } 114 | 115 | func (f *fixed) ReadByte() (byte, error) { 116 | if f.r == f.length { 117 | return 0, io.EOF 118 | } 119 | b := f.bytes[f.r] 120 | f.r += 1 121 | return b, nil 122 | } 123 | 124 | func (f *fixed) toBuffer() *buffer { 125 | if f.onExpand != nil { 126 | f.onExpand() 127 | } 128 | buf := &buffer{stdbytes.NewBuffer(f.bytes[f.r:])} 129 | buf.Truncate(f.length - f.r) 130 | return buf 131 | } 132 | 133 | func (f *fixed) hasSpace(toAdd int) bool { 134 | return f.length+toAdd <= f.capacity 135 | } 136 | 137 | func (f *fixed) full() bool { 138 | return f.length == f.capacity 139 | } 140 | 141 | func (f *fixed) reset() { 142 | f.r = 0 143 | f.length = 0 144 | } 145 | -------------------------------------------------------------------------------- /bytes.go: -------------------------------------------------------------------------------- 1 | package bytepool 2 | 3 | import ( 4 | "encoding/binary" 5 | "io" 6 | ) 7 | 8 | type bytes interface { 9 | position(n uint) bytes 10 | writeByte(b byte) (bytes, error) 11 | write(b []byte) (bytes, int, error) 12 | readNFrom(n int64, r io.Reader) (bytes, int64, error) 13 | 14 | Len() int 15 | Bytes() []byte 16 | String() string 17 | ReadByte() (byte, error) 18 | Read(b []byte) (int, error) 19 | WriteTo(w io.Writer) (int64, error) 20 | } 21 | 22 | type Bytes struct { 23 | bytes 24 | pool *Pool 25 | fixed *fixed 26 | scratch []byte 27 | enc binary.ByteOrder 28 | } 29 | 30 | func NewBytes(capacity int) *Bytes { 31 | return NewEndianBytes(capacity, binary.BigEndian) 32 | } 33 | 34 | func NewEndianBytes(capacity int, enc binary.ByteOrder) *Bytes { 35 | return newPooled(nil, capacity, enc) 36 | } 37 | 38 | func newPooled(pool *Pool, capacity int, enc binary.ByteOrder) *Bytes { 39 | b := &Bytes{ 40 | enc: enc, 41 | pool: pool, 42 | fixed: &fixed{ 43 | capacity: capacity, 44 | bytes: make([]byte, capacity), 45 | }, 46 | scratch: make([]byte, 8), 47 | } 48 | if pool != nil { 49 | b.fixed.onExpand = pool.onExpand 50 | } 51 | b.bytes = b.fixed 52 | return b 53 | } 54 | 55 | // Set a custom OnExpand callback (overwriting the one already set). 56 | // Only useful for when NewBytes is called directly as opposed 57 | // to through the pool. 58 | func (b *Bytes) SetOnExpand(callback func()) { 59 | b.fixed.onExpand = callback 60 | } 61 | 62 | // Write the bytes 63 | func (b *Bytes) Write(data []byte) (n int, err error) { 64 | b.bytes, n, err = b.write(data) 65 | return n, err 66 | } 67 | 68 | // Write a byte 69 | func (b *Bytes) WriteByte(d byte) (err error) { 70 | b.bytes, err = b.writeByte(d) 71 | return err 72 | } 73 | 74 | func (b *Bytes) WriteUint16(n uint16) { 75 | b.enc.PutUint16(b.scratch, n) 76 | b.bytes, _, _ = b.write(b.scratch[:2]) 77 | } 78 | 79 | func (b *Bytes) WriteUint32(n uint32) { 80 | b.enc.PutUint32(b.scratch, n) 81 | b.bytes, _, _ = b.write(b.scratch[:4]) 82 | } 83 | 84 | func (b *Bytes) WriteUint64(n uint64) { 85 | b.enc.PutUint64(b.scratch, n) 86 | b.bytes, _, _ = b.write(b.scratch[:8]) 87 | } 88 | 89 | func (b *Bytes) ReadByte() (byte, error) { 90 | bt, err := b.bytes.ReadByte() 91 | return bt, err 92 | } 93 | 94 | func (b *Bytes) ReadUint16() (uint16, error) { 95 | n, _ := b.bytes.Read(b.scratch[:2]) 96 | if n == 2 { 97 | return b.enc.Uint16(b.scratch), nil 98 | } 99 | return 0, io.EOF 100 | } 101 | 102 | func (b *Bytes) ReadUint32() (uint32, error) { 103 | n, _ := b.bytes.Read(b.scratch[:4]) 104 | if n == 4 { 105 | return b.enc.Uint32(b.scratch), nil 106 | } 107 | return 0, io.EOF 108 | } 109 | 110 | func (b *Bytes) ReadUint64() (uint64, error) { 111 | n, _ := b.bytes.Read(b.scratch[:8]) 112 | if n == 8 { 113 | return b.enc.Uint64(b.scratch), nil 114 | } 115 | return 0, io.EOF 116 | } 117 | 118 | // Write a string 119 | func (b *Bytes) WriteString(str string) (int, error) { 120 | return b.Write([]byte(str)) 121 | } 122 | 123 | // Read from the io.Reader 124 | func (b *Bytes) ReadFrom(r io.Reader) (n int64, err error) { 125 | return b.ReadNFrom(0, r) 126 | } 127 | 128 | // Read N bytes from the io.Reader 129 | func (b *Bytes) ReadNFrom(n int64, r io.Reader) (m int64, err error) { 130 | b.bytes, m, err = b.readNFrom(n, r) 131 | return m, err 132 | } 133 | 134 | func (b *Bytes) Position(n uint) { 135 | b.bytes = b.position(n) 136 | } 137 | 138 | // Reset the object without releasing it 139 | func (b *Bytes) Reset() { 140 | b.fixed.reset() 141 | b.bytes = b.fixed 142 | } 143 | 144 | // Release the item back into the pool 145 | func (b *Bytes) Release() { 146 | if b.pool != nil { 147 | b.Reset() 148 | b.pool.list <- b 149 | } 150 | } 151 | 152 | // Alias for Release 153 | func (b *Bytes) Close() error { 154 | b.Release() 155 | return nil 156 | } 157 | -------------------------------------------------------------------------------- /readme.md: -------------------------------------------------------------------------------- 1 | # BytePool 2 | 3 | A pool for `[]byte`. 4 | 5 | ## Usage: 6 | 7 | The pool is thread-safe. 8 | 9 | ```go 10 | // create a pool of 16 items, each item has a size of 1024 11 | var pool = bytepool.New(1024, 16) 12 | 13 | 14 | bytes := pool.Checkout() 15 | defer bytes.Release() 16 | bytes.Write([]byte("hello")) 17 | fmt.Prinltn(bytes.String()) 18 | ``` 19 | 20 | ## Pool Growth 21 | Getting an item from the pool is non-blocking. If the pool is depleted, a new item will be created. However, such dynamically created items are not added back to the pool on release. In other words, the # of items within the pool is fixed. 22 | 23 | You can call the `Depleted()` method for count of how often the pool was depleted. If it's frequently greater than 0, consider increasing the count of items your pool holds. 24 | 25 | ## Item Growth 26 | Items are created with an initial size. Adding more data than this size will cause the item to internally and efficiently convert itself to a `bytes.Buffer`. However, the growth is not permanent: on `Release` the initial allocation is re-established (without needing to do a new allocation). 27 | 28 | In other words, the total memory used by the bytepool should be size * count. For our above example, that's 16KB. The size will increase as needed, but will always revert back to 16KB (and that 16KB is only initiated once, on startup). 29 | 30 | You can call the `Expanded()` method for a count of how often items were forced to grow beyond the initial size. If it's frequently greater than 0, consider increasing the initial size of the items. 31 | 32 | 33 | ## Pool Methods: 34 | * `New(size, count)` - Creates a new pool of `count` items, each initialize to hold `size` bytes (but able to grow as needed) 35 | * `Checkout() *Bytes` - Gets a item which you can write/read from 36 | * `Depleted() int64` - How often the pool was empty. Calling this resets the counter 37 | * `Expanded() int64` - How often items were forced to grow beyond their initial size. Calling this resets the counter 38 | * `Stats() map[string]int64` - `Depleted` and `Expanded` in a map 39 | 40 | ## Item Methods: 41 | * `Write(data []byte) (n int, err error)` 42 | * `WriteByte(data byte) error` 43 | * `WriteString(data string) (n int, err error)` 44 | * `Bytes() []byte` 45 | * `String() string` 46 | * `Len() int` 47 | * `ReadFrom(r io.Reader) (n int64, err error)` 48 | * `ReadNFrom(n int64, r io.Reader) (m int64, err error)` 49 | * `Read(data []byte) (int, error)` 50 | * `Release()` or `Close()` - Resets and releases the item back to the pool. 51 | * `Reset()` - Resets the item without releasing it back to the pool 52 | * `Position(n uint)` - Moves to the specified absolute position. This will grow the buffer if needed. 53 | 54 | ## Numeric Encoding 55 | The `WriteUint16`, `WriteUint32` and `WriteUint64` methods can be used to write integers 56 | in big endian. 57 | 58 | To write using little endian, create a pool using `NewEndian` or an individual item using `NewEndianBytes` and pass the `binary.LittleEndian` object from the stdlib "encoding/binary" package. 59 | 60 | Corresponding `ReadUint16`, `ReadUint32` and `ReadUint64` are availabl. They return `(n, error)` where error will be `io.EOF` if not enough data is available. 61 | 62 | # Each 63 | It's possible to pre-fill byte items within the pool through the use of the pool's `Each` and the item's `Position` functions. You have to take special care to properly `Position` the item on each checkout. 64 | 65 | If you pre-fill the item with more data than the initial capacity, the data will be lost. It makes no sense that you'd define a pool with items having a capacity of 50 bytes., but pre-fill 100 bytes. 66 | 67 | For example, say we were writing into a buffer where bytes 4-8 were always the value 30, we could do: 68 | 69 | ```go 70 | //setup 71 | pool := bytepool.New(256, 10) 72 | pool.Each(func(b *bytepool.Bytes) { 73 | b.Position(4) 74 | b.WriteInt32(30) 75 | }) 76 | 77 | 78 | // code that uses the buffer 79 | bytes := pool.Checkout() 80 | bytes.WriteInt32(size) //write into bytes[0:4] 81 | bytes.Position(8) //skip bytes[4:8] which we already filled 82 | //continue 83 | ``` 84 | -------------------------------------------------------------------------------- /bytes_test.go: -------------------------------------------------------------------------------- 1 | package bytepool 2 | 3 | import ( 4 | stdbytes "bytes" 5 | "encoding/binary" 6 | "io" 7 | "testing" 8 | 9 | . "github.com/karlseguin/expect" 10 | ) 11 | 12 | type BytesTest struct{} 13 | 14 | func Test_Bytes(t *testing.T) { 15 | Expectify(new(BytesTest), t) 16 | } 17 | 18 | func (_ BytesTest) WriteByte() { 19 | bytes := NewBytes(1) 20 | bytes.WriteByte('!') 21 | Expect(bytes.String()).To.Equal("!") 22 | bytes.WriteByte('?') 23 | Expect(bytes.String()).To.Equal("!?") 24 | } 25 | 26 | func (_ BytesTest) WriteWithinCapacity() { 27 | bytes := NewBytes(16) 28 | bytes.Write([]byte("it's over 9000!")) 29 | Expect(bytes.String()).To.Equal("it's over 9000!") 30 | Expect(bytes.Len()).To.Equal(15) 31 | } 32 | 33 | func (_ BytesTest) WriteAtCapacity() { 34 | bytes := NewBytes(16) 35 | bytes.Write([]byte("it's over 9000!!")) 36 | Expect(bytes.String()).To.Equal("it's over 9000!!") 37 | Expect(bytes.Len()).To.Equal(16) 38 | } 39 | 40 | func (_ BytesTest) WriteOverCapacity1() { 41 | bytes := NewBytes(16) 42 | bytes.Write([]byte("it's over 9000!!!")) 43 | Expect(bytes.String()).To.Equal("it's over 9000!!!") 44 | Expect(bytes.Len()).To.Equal(17) 45 | } 46 | 47 | func (_ BytesTest) WriteOverCapacity2() { 48 | bytes := NewBytes(16) 49 | bytes.Write([]byte("it's over 9000")) 50 | bytes.Write([]byte("!!!")) 51 | Expect(bytes.String()).To.Equal("it's over 9000!!!") 52 | Expect(bytes.Len()).To.Equal(17) 53 | } 54 | 55 | func (_ BytesTest) WriteOverCapacity3() { 56 | bytes := NewBytes(15) 57 | bytes.Write([]byte("it's over 9000")) 58 | bytes.Write([]byte("!!")) 59 | bytes.WriteString("!") 60 | bytes.WriteByte('.') 61 | Expect(bytes.String()).To.Equal("it's over 9000!!!.") 62 | Expect(bytes.Len()).To.Equal(18) 63 | } 64 | 65 | func (_ BytesTest) ReleasesWhenNoOverflow() { 66 | bytes := New(20, 1).Checkout() 67 | bytes.Write([]byte("it's over 9000!")) 68 | bytes.Release() 69 | Expect(bytes.String()).To.Equal("") 70 | Expect(bytes.Len()).To.Equal(0) 71 | Expect(cap(bytes.bytes.(*fixed).bytes)).To.Equal(20) 72 | } 73 | 74 | func (_ BytesTest) ReleasesWhenOverflow() { 75 | bytes := New(10, 1).Checkout() 76 | bytes.Write([]byte("it's over 9000!")) 77 | bytes.Release() 78 | Expect(bytes.String()).To.Equal("") 79 | Expect(bytes.Len()).To.Equal(0) 80 | Expect(cap(bytes.bytes.(*fixed).bytes)).To.Equal(10) 81 | } 82 | 83 | func (_ BytesTest) ReadFrom() { 84 | bytes := NewBytes(10) 85 | bytes.ReadFrom(stdbytes.NewBufferString("hello")) 86 | Expect(bytes.String()).To.Equal("hello") 87 | bytes.ReadFrom(stdbytes.NewBufferString("world")) 88 | Expect(bytes.String()).To.Equal("helloworld") 89 | bytes.ReadFrom(stdbytes.NewBufferString("how")) 90 | Expect(bytes.String()).To.Equal("helloworldhow") 91 | bytes.ReadFrom(stdbytes.NewBufferString("goes")) 92 | Expect(bytes.String()).To.Equal("helloworldhowgoes") 93 | } 94 | 95 | func (_ BytesTest) WritesTo() { 96 | bytes := NewBytes(10) 97 | bytes.WriteString("over 9000") 98 | buffer := new(stdbytes.Buffer) 99 | bytes.WriteTo(buffer) 100 | Expect(bytes.Len()).To.Equal(0) 101 | Expect(bytes.fixed.r).To.Equal(0) 102 | Expect(buffer.String()).To.Equal("over 9000") 103 | } 104 | 105 | func (_ BytesTest) ReadNFrom() { 106 | bytes := NewBytes(10) 107 | bytes.ReadNFrom(4, stdbytes.NewBufferString("hello")) 108 | Expect(bytes.String()).To.Equal("hell") 109 | bytes.ReadNFrom(4, stdbytes.NewBufferString("world")) 110 | Expect(bytes.String()).To.Equal("hellworl") 111 | bytes.ReadNFrom(6, stdbytes.NewBufferString("thisisfun")) 112 | Expect(bytes.String()).To.Equal("hellworlthisis") 113 | bytes.ReadNFrom(2, stdbytes.NewBufferString("go")) 114 | Expect(bytes.String()).To.Equal("hellworlthisisgo") 115 | } 116 | 117 | func (_ BytesTest) ReadNFromExact() { 118 | bytes := NewBytes(3) 119 | bytes.ReadNFrom(3, stdbytes.NewBufferString("hello")) 120 | Expect(bytes.String()).To.Equal("hel") 121 | } 122 | 123 | func (_ BytesTest) FullRead() { 124 | bytes := NewBytes(10) 125 | bytes.WriteString("hello!") 126 | data := make([]byte, 10) 127 | n, err := bytes.Read(data) 128 | Expect(n, err).To.Equal(6, io.EOF) 129 | Expect(string(data[:n])).To.Equal("hello!") 130 | } 131 | 132 | func (_ BytesTest) Partial() { 133 | bytes := NewBytes(10) 134 | bytes.WriteString("hello!") 135 | data := make([]byte, 4) 136 | n, err := bytes.Read(data) 137 | Expect(n, err).To.Equal(4, nil) 138 | Expect(string(data)).To.Equal("hell") 139 | } 140 | 141 | func (_ BytesTest) Reset() { 142 | bytes := NewBytes(10) 143 | bytes.WriteString("hello!") 144 | bytes.Reset() 145 | bytes.WriteString("spice") 146 | Expect(bytes.String()).To.Equal("spice") 147 | } 148 | 149 | func (_ BytesTest) ResetFromExpansion() { 150 | bytes := NewBytes(2) 151 | bytes.WriteString("hello!") 152 | _, is := bytes.bytes.(*buffer) 153 | Expect(is).To.Equal(true) 154 | bytes.Reset() 155 | _, is = bytes.bytes.(*fixed) 156 | Expect(is).To.Equal(true) 157 | } 158 | 159 | func (_ BytesTest) WriteBigEndian() { 160 | p := New(10, 1) 161 | b := p.Checkout() 162 | b.WriteUint64(2933) 163 | Expect(b.Bytes()).To.Equal([]byte{0, 0, 0, 0, 0, 0, 11, 117}) 164 | b.WriteUint32(8484848) 165 | Expect(b.Bytes()).To.Equal([]byte{0, 0, 0, 0, 0, 0, 11, 117, 0, 129, 119, 240}) 166 | } 167 | 168 | func (_ BytesTest) WriteLittleEndian() { 169 | p := NewEndian(10, 1, binary.LittleEndian) 170 | b := p.Checkout() 171 | b.WriteUint64(2933) 172 | Expect(b.Bytes()).To.Equal([]byte{117, 11, 0, 0, 0, 0, 0, 0}) 173 | b.WriteUint32(8484848) 174 | Expect(b.Bytes()).To.Equal([]byte{117, 11, 0, 0, 0, 0, 0, 0, 240, 119, 129, 0}) 175 | } 176 | 177 | func (_ BytesTest) ReadBigEndian() { 178 | p := New(12, 1) 179 | b := p.Checkout() 180 | b.WriteUint64(2933) 181 | b.WriteUint32(10) 182 | Expect(b.ReadUint64()).To.Equal(uint64(2933), nil) 183 | Expect(b.ReadUint32()).To.Equal(uint32(10), nil) 184 | b.WriteUint16(1234) 185 | Expect(b.ReadUint16()).To.Equal(uint16(1234), nil) 186 | b.WriteUint64(94994949) 187 | Expect(b.ReadUint64()).To.Equal(uint64(94994949), nil) 188 | } 189 | 190 | func (_ BytesTest) ReadIntsEOF() { 191 | p := New(4, 1) 192 | b := p.Checkout() 193 | Expect(b.ReadUint64()).To.Equal(uint64(0), io.EOF) 194 | Expect(b.ReadUint32()).To.Equal(uint32(0), io.EOF) 195 | Expect(b.ReadUint16()).To.Equal(uint16(0), io.EOF) 196 | } 197 | 198 | func (_ BytesTest) ReadIntsEOFBuffer() { 199 | p := New(4, 1) 200 | b := p.Checkout() 201 | b.WriteUint64(23) 202 | b.ReadUint64() 203 | Expect(b.ReadUint64()).To.Equal(uint64(0), io.EOF) 204 | Expect(b.ReadUint32()).To.Equal(uint32(0), io.EOF) 205 | Expect(b.ReadUint16()).To.Equal(uint16(0), io.EOF) 206 | } 207 | 208 | func (_ BytesTest) ReadAndWriteByte() { 209 | p := New(3, 1) 210 | b := p.Checkout() 211 | b.WriteByte(9) 212 | b.WriteByte(4) 213 | Expect(b.ReadByte()).To.Equal(byte(9), nil) 214 | Expect(b.ReadByte()).To.Equal(byte(4), nil) 215 | Expect(b.ReadByte()).To.Equal(byte(0), io.EOF) 216 | } 217 | 218 | func (_ BytesTest) ReadAndWriteByteForBuffer() { 219 | p := New(2, 1) 220 | b := p.Checkout() 221 | b.WriteByte(9) 222 | b.WriteByte(4) 223 | b.WriteByte(39) 224 | Expect(b.ReadByte()).To.Equal(byte(9), nil) 225 | Expect(b.ReadByte()).To.Equal(byte(4), nil) 226 | Expect(b.ReadByte()).To.Equal(byte(39), nil) 227 | Expect(b.ReadByte()).To.Equal(byte(0), io.EOF) 228 | } 229 | 230 | func (_ BytesTest) PositionFixed() { 231 | bytes := NewBytes(10) 232 | bytes.Position(6) 233 | bytes.WriteString("abc") 234 | Expect(bytes.Bytes()).To.Equal([]byte{0, 0, 0, 0, 0, 0, 97, 98, 99}) 235 | bytes.Position(2) 236 | bytes.WriteByte(4) 237 | Expect(bytes.Bytes()).To.Equal([]byte{0, 0, 4}) 238 | } 239 | 240 | func (_ BytesTest) NegativePositionFixed() { 241 | bytes := NewBytes(10) 242 | bytes.WriteString("abc") 243 | bytes.Position(2) 244 | bytes.WriteString("bd") 245 | Expect(bytes.Bytes()).To.Equal([]byte{97, 98, 98, 100}) 246 | bytes.Position(1) 247 | bytes.WriteByte(4) 248 | Expect(bytes.Bytes()).To.Equal([]byte{97, 4}) 249 | } 250 | 251 | func (_ BytesTest) PositionBuffer() { 252 | bytes := NewBytes(2) 253 | bytes.Position(4) 254 | Expect(bytes.Bytes()).To.Equal([]byte{0, 0, 0, 0}) 255 | bytes.WriteString("12") 256 | Expect(bytes.Bytes()).To.Equal([]byte{0, 0, 0, 0, 49, 50}) 257 | bytes.Position(10) 258 | Expect(bytes.Bytes()).To.Equal([]byte{0, 0, 0, 0, 49, 50, 0, 0, 0, 0}) 259 | bytes.Position(5) 260 | Expect(bytes.Bytes()).To.Equal([]byte{0, 0, 0, 0, 49}) 261 | } 262 | 263 | func (_ BytesTest) NegativePositionBuffer() { 264 | bytes := NewBytes(2) 265 | bytes.WriteString("abc") 266 | bytes.Position(2) 267 | bytes.WriteString("bd") 268 | Expect(bytes.Bytes()).To.Equal([]byte{97, 98, 98, 100}) 269 | bytes.Position(1) 270 | bytes.WriteByte(4) 271 | Expect(bytes.Bytes()).To.Equal([]byte{97, 4}) 272 | } 273 | 274 | func (_ BytesTest) CustomOnExpand() { 275 | expanded := 0 276 | bytes := NewBytes(7) 277 | bytes.SetOnExpand(func() { expanded++ }) 278 | bytes.WriteString("hello") 279 | Expect(expanded).To.Equal(0) 280 | bytes.WriteString("world") 281 | Expect(expanded).To.Equal(1) 282 | bytes.WriteString("world") 283 | Expect(expanded).To.Equal(1) 284 | } 285 | --------------------------------------------------------------------------------