github.com/ethersphere/bee/v2@v2.2.0/pkg/file/splitter/splitter_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 splitter_test
     6  
     7  import (
     8  	"bytes"
     9  	"context"
    10  	"strconv"
    11  	"testing"
    12  	"time"
    13  
    14  	"github.com/ethersphere/bee/v2/pkg/file"
    15  	"github.com/ethersphere/bee/v2/pkg/file/splitter"
    16  	"github.com/ethersphere/bee/v2/pkg/storage/inmemchunkstore"
    17  	"github.com/ethersphere/bee/v2/pkg/swarm"
    18  	"github.com/ethersphere/bee/v2/pkg/util/testutil"
    19  	mockbytes "gitlab.com/nolash/go-mockbytes"
    20  )
    21  
    22  // TestSplitIncomplete tests that the Split method returns an error if
    23  // the amounts of bytes written does not match the data length passed to the method.
    24  func TestSplitIncomplete(t *testing.T) {
    25  	t.Parallel()
    26  
    27  	testData := make([]byte, 42)
    28  	store := inmemchunkstore.New()
    29  	s := splitter.NewSimpleSplitter(store)
    30  
    31  	testDataReader := file.NewSimpleReadCloser(testData)
    32  	_, err := s.Split(context.Background(), testDataReader, 41, false)
    33  	if err == nil {
    34  		t.Fatalf("expected error on EOF before full length write")
    35  	}
    36  }
    37  
    38  // TestSplitSingleChunk hashes one single chunk and verifies
    39  // that that corresponding chunk exist in the store afterwards.
    40  func TestSplitSingleChunk(t *testing.T) {
    41  	t.Parallel()
    42  
    43  	g := mockbytes.New(0, mockbytes.MockTypeStandard).WithModulus(255)
    44  	testData, err := g.SequentialBytes(swarm.ChunkSize)
    45  	if err != nil {
    46  		t.Fatal(err)
    47  	}
    48  
    49  	store := inmemchunkstore.New()
    50  	s := splitter.NewSimpleSplitter(store)
    51  
    52  	testDataReader := file.NewSimpleReadCloser(testData)
    53  	resultAddress, err := s.Split(context.Background(), testDataReader, int64(len(testData)), false)
    54  	if err != nil {
    55  		t.Fatal(err)
    56  	}
    57  
    58  	testHashHex := "c10090961e7682a10890c334d759a28426647141213abda93b096b892824d2ef"
    59  	testHashAddress := swarm.MustParseHexAddress(testHashHex)
    60  	if !testHashAddress.Equal(resultAddress) {
    61  		t.Fatalf("expected %v, got %v", testHashAddress, resultAddress)
    62  	}
    63  
    64  	_, err = store.Get(context.Background(), resultAddress)
    65  	if err != nil {
    66  		t.Fatal(err)
    67  	}
    68  }
    69  
    70  // TestSplitThreeLevels hashes enough data chunks in order to
    71  // create a full chunk of intermediate hashes.
    72  // It verifies that all created chunks exist in the store afterwards.
    73  func TestSplitThreeLevels(t *testing.T) {
    74  	t.Parallel()
    75  
    76  	// edge case selected from internal/job_test.go
    77  	g := mockbytes.New(0, mockbytes.MockTypeStandard).WithModulus(255)
    78  	testData, err := g.SequentialBytes(swarm.ChunkSize * 128)
    79  	if err != nil {
    80  		t.Fatal(err)
    81  	}
    82  
    83  	store := inmemchunkstore.New()
    84  	s := splitter.NewSimpleSplitter(store)
    85  
    86  	testDataReader := file.NewSimpleReadCloser(testData)
    87  	resultAddress, err := s.Split(context.Background(), testDataReader, int64(len(testData)), false)
    88  	if err != nil {
    89  		t.Fatal(err)
    90  	}
    91  
    92  	testHashHex := "3047d841077898c26bbe6be652a2ec590a5d9bd7cd45d290ea42511b48753c09"
    93  	testHashAddress := swarm.MustParseHexAddress(testHashHex)
    94  	if !testHashAddress.Equal(resultAddress) {
    95  		t.Fatalf("expected %v, got %v", testHashAddress, resultAddress)
    96  	}
    97  
    98  	_, err = store.Get(context.Background(), resultAddress)
    99  	if err != nil {
   100  		t.Fatal(err)
   101  	}
   102  
   103  	rootChunk, err := store.Get(context.Background(), resultAddress)
   104  	if err != nil {
   105  		t.Fatal(err)
   106  	}
   107  
   108  	rootData := rootChunk.Data()[8:]
   109  	for i := 0; i < swarm.ChunkSize; i += swarm.SectionSize {
   110  		dataAddressBytes := rootData[i : i+swarm.SectionSize]
   111  		dataAddress := swarm.NewAddress(dataAddressBytes)
   112  		_, err := store.Get(context.Background(), dataAddress)
   113  		if err != nil {
   114  			t.Fatal(err)
   115  		}
   116  	}
   117  }
   118  
   119  // TestUnalignedSplit tests that correct hash is generated regardless of
   120  // individual write sizes at the source of the data.
   121  func TestUnalignedSplit(t *testing.T) {
   122  	t.Parallel()
   123  
   124  	var (
   125  		storer    = inmemchunkstore.New()
   126  		chunkPipe = file.NewChunkPipe()
   127  	)
   128  
   129  	// test vector taken from pkg/file/testing/vector.go
   130  	var (
   131  		dataLen       int64 = swarm.ChunkSize*2 + 32
   132  		expectAddrHex       = "61416726988f77b874435bdd89a419edc3861111884fd60e8adf54e2f299efd6"
   133  		g                   = mockbytes.New(0, mockbytes.MockTypeStandard).WithModulus(255)
   134  	)
   135  
   136  	// generate test vector data content
   137  	content, err := g.SequentialBytes(int(dataLen))
   138  	if err != nil {
   139  		t.Fatal(err)
   140  	}
   141  
   142  	// perform the split in a separate thread
   143  	sp := splitter.NewSimpleSplitter(storer)
   144  	ctx := context.Background()
   145  	doneC := make(chan swarm.Address)
   146  	errC := make(chan error)
   147  	go func() {
   148  		addr, err := sp.Split(ctx, chunkPipe, dataLen, false)
   149  		if err != nil {
   150  			errC <- err
   151  		} else {
   152  			doneC <- addr
   153  		}
   154  		close(doneC)
   155  		close(errC)
   156  	}()
   157  
   158  	// perform the writes in unaligned bursts
   159  	writeSizes := []int{swarm.ChunkSize - 40, 40 + 32, swarm.ChunkSize}
   160  	contentBuf := bytes.NewReader(content)
   161  	cursor := 0
   162  	for _, writeSize := range writeSizes {
   163  		data := make([]byte, writeSize)
   164  		_, err = contentBuf.Read(data)
   165  		if err != nil {
   166  			t.Fatal(err)
   167  		}
   168  		c, err := chunkPipe.Write(data)
   169  		if err != nil {
   170  			t.Fatal(err)
   171  		}
   172  		cursor += c
   173  	}
   174  	err = chunkPipe.Close()
   175  	if err != nil {
   176  		t.Fatal(err)
   177  	}
   178  
   179  	// read and hopefully not weep
   180  	timer := time.NewTimer(time.Millisecond * 100)
   181  	select {
   182  	case addr := <-doneC:
   183  		expectAddr := swarm.MustParseHexAddress(expectAddrHex)
   184  		if !expectAddr.Equal(addr) {
   185  			t.Fatalf("addr mismatch, expected %s, got %s", expectAddr, addr)
   186  		}
   187  	case err := <-errC:
   188  		t.Fatal(err)
   189  	case <-timer.C:
   190  		t.Fatal("timeout")
   191  	}
   192  }
   193  
   194  /*
   195  go test -v -bench=. -run Bench -benchmem
   196  goos: linux
   197  goarch: amd64
   198  pkg: github.com/ethersphere/bee/pkg/file/splitter
   199  BenchmarkSplitter
   200  BenchmarkSplitter/1000-bytes
   201  BenchmarkSplitter/1000-bytes-4         	   12667	     95965 ns/op	  154870 B/op	     367 allocs/op
   202  BenchmarkSplitter/10000-bytes
   203  BenchmarkSplitter/10000-bytes-4        	    2808	    418753 ns/op	  369764 B/op	    1624 allocs/op
   204  BenchmarkSplitter/100000-bytes
   205  BenchmarkSplitter/100000-bytes-4       	     349	   3342003 ns/op	 2042891 B/op	   11810 allocs/op
   206  BenchmarkSplitter/1000000-bytes
   207  BenchmarkSplitter/1000000-bytes-4      	      33	  30905753 ns/op	18825910 B/op	  113721 allocs/op
   208  BenchmarkSplitter/10000000-bytes
   209  BenchmarkSplitter/10000000-bytes-4     	       4	 295615658 ns/op	186417904 B/op	 1132527 allocs/op
   210  BenchmarkSplitter/100000000-bytes
   211  BenchmarkSplitter/100000000-bytes-4    	       1	2972826021 ns/op	1861374352 B/op	11321235 allocs/op
   212  PASS
   213  ok  	github.com/ethersphere/bee/pkg/file/splitter	22.476s
   214  */
   215  
   216  func BenchmarkSplitter(b *testing.B) {
   217  	for _, count := range []int{
   218  		1000,      // 1k
   219  		10000,     // 10 k
   220  		100000,    // 100 k
   221  		1000000,   // 1 mb
   222  		10000000,  // 10 mb
   223  		100000000, // 100 mb
   224  	} {
   225  		b.Run(strconv.Itoa(count)+"-bytes", func(b *testing.B) {
   226  			for n := 0; n < b.N; n++ {
   227  				benchmarkSplitter(b, count)
   228  			}
   229  		})
   230  	}
   231  }
   232  
   233  func benchmarkSplitter(b *testing.B, count int) {
   234  	b.Helper()
   235  
   236  	b.StopTimer()
   237  
   238  	data := testutil.RandBytes(b, count)
   239  	m := inmemchunkstore.New()
   240  	s := splitter.NewSimpleSplitter(m)
   241  
   242  	testDataReader := file.NewSimpleReadCloser(data)
   243  	b.StartTimer()
   244  
   245  	_, err := s.Split(context.Background(), testDataReader, int64(len(data)), false)
   246  	if err != nil {
   247  		b.Fatal(err)
   248  	}
   249  }