github.com/ethersphere/bee/v2@v2.2.0/pkg/replicas/putter_test.go (about)

     1  // Copyright 2023 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 replicas_test
     6  
     7  import (
     8  	"context"
     9  	"crypto/rand"
    10  	"errors"
    11  	"fmt"
    12  	"io"
    13  	"sync/atomic"
    14  	"testing"
    15  	"time"
    16  
    17  	"github.com/ethersphere/bee/v2/pkg/cac"
    18  	"github.com/ethersphere/bee/v2/pkg/file/redundancy"
    19  	"github.com/ethersphere/bee/v2/pkg/replicas"
    20  	"github.com/ethersphere/bee/v2/pkg/storage"
    21  	"github.com/ethersphere/bee/v2/pkg/storage/inmemchunkstore"
    22  	"github.com/ethersphere/bee/v2/pkg/swarm"
    23  )
    24  
    25  var (
    26  	errTestA = errors.New("A")
    27  	errTestB = errors.New("B")
    28  )
    29  
    30  type testBasePutter struct {
    31  	getErrors func(context.Context, swarm.Address) error
    32  	putErrors func(context.Context, swarm.Address) error
    33  	store     storage.ChunkStore
    34  }
    35  
    36  func (tbp *testBasePutter) Get(ctx context.Context, addr swarm.Address) (swarm.Chunk, error) {
    37  
    38  	g := tbp.getErrors
    39  	if g != nil {
    40  		return nil, g(ctx, addr)
    41  	}
    42  	return tbp.store.Get(ctx, addr)
    43  }
    44  
    45  func (tbp *testBasePutter) Put(ctx context.Context, ch swarm.Chunk) error {
    46  
    47  	g := tbp.putErrors
    48  	if g != nil {
    49  		return g(ctx, ch.Address())
    50  	}
    51  	return tbp.store.Put(ctx, ch)
    52  }
    53  
    54  func TestPutter(t *testing.T) {
    55  	t.Parallel()
    56  	tcs := []struct {
    57  		level  redundancy.Level
    58  		length int
    59  	}{
    60  		{0, 1},
    61  		{1, 1},
    62  		{2, 1},
    63  		{3, 1},
    64  		{4, 1},
    65  		{0, 4096},
    66  		{1, 4096},
    67  		{2, 4096},
    68  		{3, 4096},
    69  		{4, 4096},
    70  	}
    71  	for _, tc := range tcs {
    72  		t.Run(fmt.Sprintf("redundancy:%d, size:%d", tc.level, tc.length), func(t *testing.T) {
    73  			buf := make([]byte, tc.length)
    74  			if _, err := io.ReadFull(rand.Reader, buf); err != nil {
    75  				t.Fatal(err)
    76  			}
    77  			ctx := context.Background()
    78  			ctx = redundancy.SetLevelInContext(ctx, tc.level)
    79  
    80  			ch, err := cac.New(buf)
    81  			if err != nil {
    82  				t.Fatal(err)
    83  			}
    84  			store := inmemchunkstore.New()
    85  			defer store.Close()
    86  			p := replicas.NewPutter(store)
    87  
    88  			// original chunk
    89  			if err := store.Put(ctx, ch); err != nil {
    90  				t.Fatalf("expected no error. got %v", err)
    91  			}
    92  			if err := p.Put(ctx, ch); err != nil {
    93  				t.Fatalf("expected no error. got %v", err)
    94  			}
    95  			var addrs []swarm.Address
    96  			orig := false
    97  			_ = store.Iterate(ctx, func(chunk swarm.Chunk) (stop bool, err error) {
    98  				if ch.Address().Equal(chunk.Address()) {
    99  					orig = true
   100  					return false, nil
   101  				}
   102  				addrs = append(addrs, chunk.Address())
   103  				return false, nil
   104  			})
   105  			if !orig {
   106  				t.Fatal("original chunk missing")
   107  			}
   108  			t.Run("dispersion", func(t *testing.T) {
   109  				if err := dispersed(tc.level, ch, addrs); err != nil {
   110  					t.Fatalf("addresses are not dispersed: %v", err)
   111  				}
   112  			})
   113  			t.Run("attempts", func(t *testing.T) {
   114  				count := tc.level.GetReplicaCount()
   115  				if len(addrs) != count {
   116  					t.Fatalf("incorrect number of attempts. want %v, got %v", count, len(addrs))
   117  				}
   118  			})
   119  
   120  			t.Run("replication", func(t *testing.T) {
   121  				if err := replicated(store, ch, addrs); err != nil {
   122  					t.Fatalf("chunks are not replicas: %v", err)
   123  				}
   124  			})
   125  		})
   126  	}
   127  	t.Run("error handling", func(t *testing.T) {
   128  		tcs := []struct {
   129  			name   string
   130  			level  redundancy.Level
   131  			length int
   132  			f      func(*testBasePutter) *testBasePutter
   133  			err    []error
   134  		}{
   135  			{"put errors", 4, 4096, func(tbp *testBasePutter) *testBasePutter {
   136  				var j int32
   137  				i := &j
   138  				atomic.StoreInt32(i, 0)
   139  				tbp.putErrors = func(ctx context.Context, _ swarm.Address) error {
   140  					j := atomic.AddInt32(i, 1)
   141  					if j == 6 {
   142  						return errTestA
   143  					}
   144  					if j == 12 {
   145  						return errTestB
   146  					}
   147  					return nil
   148  				}
   149  				return tbp
   150  			}, []error{errTestA, errTestB}},
   151  			{"put latencies", 4, 4096, func(tbp *testBasePutter) *testBasePutter {
   152  				var j int32
   153  				i := &j
   154  				atomic.StoreInt32(i, 0)
   155  				tbp.putErrors = func(ctx context.Context, _ swarm.Address) error {
   156  					j := atomic.AddInt32(i, 1)
   157  					if j == 6 {
   158  						select {
   159  						case <-time.After(100 * time.Millisecond):
   160  						case <-ctx.Done():
   161  							return ctx.Err()
   162  						}
   163  					}
   164  					if j == 12 {
   165  						return errTestA
   166  					}
   167  					return nil
   168  				}
   169  				return tbp
   170  			}, []error{errTestA, context.DeadlineExceeded}},
   171  		}
   172  		for _, tc := range tcs {
   173  			t.Run(tc.name, func(t *testing.T) {
   174  				buf := make([]byte, tc.length)
   175  				if _, err := io.ReadFull(rand.Reader, buf); err != nil {
   176  					t.Fatal(err)
   177  				}
   178  				ctx := context.Background()
   179  				ctx, cancel := context.WithTimeout(ctx, 50*time.Millisecond)
   180  				defer cancel()
   181  				ctx = redundancy.SetLevelInContext(ctx, tc.level)
   182  				ch, err := cac.New(buf)
   183  				if err != nil {
   184  					t.Fatal(err)
   185  				}
   186  				store := inmemchunkstore.New()
   187  				defer store.Close()
   188  				p := replicas.NewPutter(tc.f(&testBasePutter{store: store}))
   189  				errs := p.Put(ctx, ch)
   190  				for _, err := range tc.err {
   191  					if !errors.Is(errs, err) {
   192  						t.Fatalf("incorrect error. want it to contain %v. got %v.", tc.err, errs)
   193  					}
   194  				}
   195  			})
   196  		}
   197  	})
   198  
   199  }