// Copyright 2020 The Swarm Authors. All rights reserved. // Use of this source code is governed by a BSD-style // license that can be found in the LICENSE file. package feeder_test import ( "bytes" "encoding/binary" "errors" "testing" "github.com/ethersphere/bee/pkg/file/pipeline" "github.com/ethersphere/bee/pkg/file/pipeline/feeder" ) // TestFeeder tests that partial writes work correctly. func TestFeeder(t *testing.T) { var ( chunkSize = 5 data = []byte{1, 2, 3, 4, 5, 6, 7, 8, 9, 10, 11, 12, 13} ) for _, tc := range []struct { name string // name dataSize []int // how big each write is expWrites int // expected number of writes writeData []byte // expected data in last write buffer span uint64 // expected span of written data }{ { name: "empty write", dataSize: []int{0}, expWrites: 0, }, { name: "less than chunk, no writes", dataSize: []int{3}, expWrites: 0, }, { name: "one chunk, one write", dataSize: []int{5}, expWrites: 1, writeData: []byte{1, 2, 3, 4, 5}, span: 5, }, { name: "two chunks, two writes", dataSize: []int{10}, expWrites: 2, writeData: []byte{6, 7, 8, 9, 10}, span: 5, }, { name: "half chunk, then full one, one write", dataSize: []int{3, 5}, expWrites: 1, writeData: []byte{1, 2, 3, 4, 5}, span: 5, }, { name: "half chunk, another two halves, one write", dataSize: []int{3, 2, 3}, expWrites: 1, writeData: []byte{1, 2, 3, 4, 5}, span: 5, }, { name: "half chunk, another two halves, another full, two writes", dataSize: []int{3, 2, 3, 5}, expWrites: 2, writeData: []byte{6, 7, 8, 9, 10}, span: 5, }, } { t.Run(tc.name, func(t *testing.T) { var results pipeline.PipeWriteArgs rr := newMockResultWriter(&results) cf := feeder.NewChunkFeederWriter(chunkSize, rr) i := 0 for _, v := range tc.dataSize { d := data[i : i+v] n, err := cf.Write(d) if err != nil { t.Fatal(err) } if n != v { t.Fatalf("wrote %d bytes but expected %d bytes", n, v) } i += v } if tc.expWrites == 0 && results.Data != nil { t.Fatal("expected no write but got one") } if rr.count != tc.expWrites { t.Fatalf("expected %d writes but got %d", tc.expWrites, rr.count) } if results.Data != nil && !bytes.Equal(tc.writeData, results.Data[8:]) { t.Fatalf("expected write data %v but got %v", tc.writeData, results.Data[8:]) } if tc.span > 0 { v := binary.LittleEndian.Uint64(results.Data[:8]) if v != tc.span { t.Fatalf("span mismatch, got %d want %d", v, tc.span) } } }) } } // TestFeederFlush tests that the feeder flushes the data in the buffer correctly // on Sum(). func TestFeederFlush(t *testing.T) { var ( chunkSize = 5 data = []byte{1, 2, 3, 4, 5, 6, 7, 8, 9, 10, 11, 12, 13} ) for _, tc := range []struct { name string // name dataSize []int // how big each write is expWrites int // expected number of writes writeData []byte // expected data in last write buffer span uint64 // expected span of written data }{ { name: "empty write", dataSize: []int{0}, expWrites: 0, }, { name: "less than chunk, one write", dataSize: []int{3}, expWrites: 1, writeData: []byte{1, 2, 3}, }, { name: "one chunk, one write", dataSize: []int{5}, expWrites: 1, writeData: []byte{1, 2, 3, 4, 5}, span: 5, }, { name: "two chunks, two writes", dataSize: []int{10}, expWrites: 2, writeData: []byte{6, 7, 8, 9, 10}, span: 5, }, { name: "half chunk, then full one, two writes", dataSize: []int{3, 5}, expWrites: 2, writeData: []byte{6, 7, 8}, span: 3, }, { name: "half chunk, another two halves, two writes", dataSize: []int{3, 2, 3}, expWrites: 2, writeData: []byte{6, 7, 8}, span: 3, }, { name: "half chunk, another two halves, another full, three writes", dataSize: []int{3, 2, 3, 5}, expWrites: 3, writeData: []byte{11, 12, 13}, span: 3, }, } { t.Run(tc.name, func(t *testing.T) { var results pipeline.PipeWriteArgs rr := newMockResultWriter(&results) cf := feeder.NewChunkFeederWriter(chunkSize, rr) i := 0 for _, v := range tc.dataSize { d := data[i : i+v] n, err := cf.Write(d) if err != nil { t.Fatal(err) } if n != v { t.Fatalf("wrote %d bytes but expected %d bytes", n, v) } i += v } _, _ = cf.Sum() if tc.expWrites == 0 && results.Data != nil { t.Fatal("expected no write but got one") } if rr.count != tc.expWrites { t.Fatalf("expected %d writes but got %d", tc.expWrites, rr.count) } if results.Data != nil && !bytes.Equal(tc.writeData, results.Data[8:]) { t.Fatalf("expected write data %v but got %v", tc.writeData, results.Data[8:]) } if tc.span > 0 { v := binary.LittleEndian.Uint64(results.Data[:8]) if v != tc.span { t.Fatalf("span mismatch, got %d want %d", v, tc.span) } } }) } } // countingResultWriter counts how many writes were done to it // and passes the results to the caller using the pointer provided // in the constructor. type countingResultWriter struct { target *pipeline.PipeWriteArgs count int } func newMockResultWriter(b *pipeline.PipeWriteArgs) *countingResultWriter { return &countingResultWriter{target: b} } func (w *countingResultWriter) ChainWrite(p *pipeline.PipeWriteArgs) error { w.count++ *w.target = *p return nil } func (w *countingResultWriter) Sum() ([]byte, error) { return nil, errors.New("not implemented") }