github.com/ethersphere/bee/v2@v2.2.0/pkg/file/buffer_test.go (about) 1 // Copyright 2020 The Swarm 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 5 package file_test 6 7 import ( 8 "bytes" 9 "errors" 10 "fmt" 11 "io" 12 "strconv" 13 "testing" 14 15 "github.com/ethersphere/bee/v2/pkg/file" 16 "github.com/ethersphere/bee/v2/pkg/swarm" 17 "github.com/ethersphere/bee/v2/pkg/util/testutil" 18 ) 19 20 // TestChunkPipe verifies that the reads are correctly buffered for 21 // various write length combinations. 22 func TestChunkPipe(t *testing.T) { 23 t.Parallel() 24 25 dataWrites := [][]int{ 26 {swarm.ChunkSize - 2}, // short 27 {swarm.ChunkSize - 2, 4}, // short, over 28 {swarm.ChunkSize - 2, 4, swarm.ChunkSize - 6}, // short, over, short 29 {swarm.ChunkSize - 2, 4, swarm.ChunkSize - 4}, // short, over, onononon 30 {swarm.ChunkSize, 2, swarm.ChunkSize - 4}, // on, short, short 31 {swarm.ChunkSize, 2, swarm.ChunkSize - 2}, // on, short, on 32 {swarm.ChunkSize, 2, swarm.ChunkSize}, // on, short, over 33 {swarm.ChunkSize, 2, swarm.ChunkSize - 2, 4}, // on, short, on, short 34 {swarm.ChunkSize, swarm.ChunkSize}, // on, on 35 } 36 for i, tc := range dataWrites { 37 tc := tc 38 t.Run(strconv.Itoa(i), func(t *testing.T) { 39 t.Parallel() 40 41 buf := file.NewChunkPipe() 42 readResultC := make(chan readResult, 16) 43 go func() { 44 data := make([]byte, swarm.ChunkSize) 45 for { 46 // get buffered chunkpipe read 47 n, err := buf.Read(data) 48 readResultC <- readResult{n: n, err: err} 49 50 // only the last read should be smaller than chunk size 51 if n < swarm.ChunkSize { 52 return 53 } 54 } 55 }() 56 57 // do the writes 58 writeTotal := 0 59 for _, l := range tc { 60 data := make([]byte, l) 61 c, err := buf.Write(data) 62 if err != nil { 63 t.Fatal(err) 64 } 65 if c != l { 66 t.Fatalf("short write") 67 } 68 writeTotal += l 69 } 70 71 // finish up (last unfinished chunk write will be flushed) 72 err := buf.Close() 73 if err != nil { 74 t.Fatal(err) 75 } 76 77 // receive the writes 78 // err may or may not be EOF, depending on whether writes end on 79 // chunk boundary 80 readTotal := 0 81 for res := range readResultC { 82 if res.err != nil && !errors.Is(res.err, io.EOF) { 83 t.Fatal(res.err) 84 } 85 86 readTotal += res.n 87 if readTotal == writeTotal { 88 return 89 } 90 } 91 }) 92 } 93 } 94 95 func TestCopyBuffer(t *testing.T) { 96 t.Parallel() 97 98 readBufferSizes := []int{ 99 64, 100 1024, 101 swarm.ChunkSize, 102 } 103 dataSizes := []int{ 104 1, 105 64, 106 1024, 107 swarm.ChunkSize - 1, 108 swarm.ChunkSize, 109 swarm.ChunkSize + 1, 110 swarm.ChunkSize * 2, 111 swarm.ChunkSize*2 + 3, 112 swarm.ChunkSize * 5, 113 swarm.ChunkSize*5 + 3, 114 swarm.ChunkSize * 17, 115 swarm.ChunkSize*17 + 3, 116 } 117 118 testCases := []struct { 119 readBufferSize int 120 dataSize int 121 }{} 122 123 for i := 0; i < len(readBufferSizes); i++ { 124 for j := 0; j < len(dataSizes); j++ { 125 testCases = append(testCases, struct { 126 readBufferSize int 127 dataSize int 128 }{readBufferSizes[i], dataSizes[j]}) 129 } 130 } 131 132 for _, tc := range testCases { 133 tc := tc 134 t.Run(fmt.Sprintf("buf_%-4d/data_size_%d", tc.readBufferSize, tc.dataSize), func(t *testing.T) { 135 t.Parallel() 136 137 readBufferSize := tc.readBufferSize 138 dataSize := tc.dataSize 139 chunkPipe := file.NewChunkPipe() 140 srcBytes := testutil.RandBytes(t, dataSize) 141 142 // destination 143 resultC := make(chan readResult, 1) 144 go reader(t, readBufferSize, chunkPipe, resultC) 145 146 // source 147 errC := make(chan error, 16) 148 go func() { 149 src := bytes.NewReader(srcBytes) 150 151 buf := make([]byte, swarm.ChunkSize) 152 c, err := io.CopyBuffer(chunkPipe, src, buf) 153 errC <- err 154 155 if c != int64(dataSize) { 156 errC <- errors.New("read count mismatch") 157 } 158 159 errC <- chunkPipe.Close() 160 }() 161 162 // receive the writes 163 // err may or may not be EOF, depending on whether writes end on chunk boundary 164 readData := []byte{} 165 for { 166 select { 167 case res, ok := <-resultC: 168 if !ok { 169 // when resultC is closed (there is no more data to read) 170 // assert if read data is same as source 171 if !bytes.Equal(srcBytes, readData) { 172 t.Fatal("invalid byte content received") 173 } 174 175 return 176 } 177 178 readData = append(readData, res.data...) 179 case err := <-errC: 180 if err != nil && !errors.Is(err, io.EOF) { 181 t.Fatal(err) 182 } 183 } 184 } 185 }) 186 } 187 } 188 189 type readResult struct { 190 data []byte 191 n int 192 err error 193 } 194 195 func reader(t *testing.T, bufferSize int, r io.Reader, c chan<- readResult) { 196 t.Helper() 197 198 defer close(c) 199 200 var buf = make([]byte, bufferSize) 201 for { 202 n, err := r.Read(buf) 203 if errors.Is(err, io.EOF) { 204 c <- readResult{n: n} 205 return 206 } 207 208 if err != nil { 209 t.Errorf("read: %v", err) 210 } 211 212 data := make([]byte, n) 213 copy(data, buf) 214 215 c <- readResult{n: n, data: data} 216 } 217 }