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  }