github.com/ethersphere/bee/v2@v2.2.0/pkg/feeds/testing/lookup.go (about)

     1  // Copyright 2021 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 testing provides tests for update  and resolution of time-based feeds
     6  package testing
     7  
     8  import (
     9  	"bytes"
    10  	"context"
    11  	"encoding/binary"
    12  	"errors"
    13  	"fmt"
    14  	"math/rand"
    15  	"testing"
    16  	"time"
    17  
    18  	"github.com/ethersphere/bee/v2/pkg/crypto"
    19  	"github.com/ethersphere/bee/v2/pkg/feeds"
    20  	storage "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  type Timeout struct {
    26  	storage.ChunkStore
    27  }
    28  
    29  var searchTimeout = 30 * time.Millisecond
    30  
    31  // Get overrides the mock storer and introduces latency
    32  func (t *Timeout) Get(ctx context.Context, addr swarm.Address) (swarm.Chunk, error) {
    33  	ch, err := t.ChunkStore.Get(ctx, addr)
    34  	if err != nil {
    35  		if errors.Is(err, storage.ErrNotFound) {
    36  			time.Sleep(searchTimeout)
    37  		}
    38  		return ch, err
    39  	}
    40  	time.Sleep(time.Duration(rand.Intn(10)) * time.Millisecond)
    41  	return ch, nil
    42  }
    43  
    44  // nolint:tparallel
    45  func TestFinderBasic(t *testing.T, finderf func(storage.Getter, *feeds.Feed) feeds.Lookup, updaterf func(putter storage.Putter, signer crypto.Signer, topic []byte) (feeds.Updater, error)) {
    46  	t.Parallel()
    47  
    48  	storer := &Timeout{inmemchunkstore.New()}
    49  	topicStr := "testtopic"
    50  	topic, err := crypto.LegacyKeccak256([]byte(topicStr))
    51  	if err != nil {
    52  		t.Fatal(err)
    53  	}
    54  
    55  	pk, _ := crypto.GenerateSecp256k1Key()
    56  	signer := crypto.NewDefaultSigner(pk)
    57  
    58  	updater, err := updaterf(storer, signer, topic)
    59  	if err != nil {
    60  		t.Fatal(err)
    61  	}
    62  	ctx := context.Background()
    63  	finder := finderf(storer, updater.Feed())
    64  	t.Run("no update", func(t *testing.T) {
    65  		ch, err := feeds.Latest(ctx, finder, 0)
    66  		if err != nil {
    67  			t.Fatal(err)
    68  		}
    69  		if ch != nil {
    70  			t.Fatalf("expected no update, got addr %v", ch.Address())
    71  		}
    72  	})
    73  	t.Run("first update", func(t *testing.T) {
    74  		payload := []byte("payload")
    75  		at := time.Now().Unix()
    76  		err = updater.Update(ctx, at, payload)
    77  		if err != nil {
    78  			t.Fatal(err)
    79  		}
    80  		ch, err := feeds.Latest(ctx, finder, 0)
    81  		if err != nil {
    82  			t.Fatal(err)
    83  		}
    84  		if ch == nil {
    85  			t.Fatalf("expected to find update, got none")
    86  		}
    87  		exp := payload
    88  		ts, payload, err := feeds.FromChunk(ch)
    89  		if err != nil {
    90  			t.Fatal(err)
    91  		}
    92  		if !bytes.Equal(payload, exp) {
    93  			t.Fatalf("result mismatch. want %8x... got %8x...", exp, payload)
    94  		}
    95  		if ts != uint64(at) {
    96  			t.Fatalf("timestamp mismatch: expected %v, got %v", at, ts)
    97  		}
    98  	})
    99  }
   100  
   101  // nolint:tparallel
   102  func TestFinderFixIntervals(t *testing.T, nextf func() (bool, int64), finderf func(storage.Getter, *feeds.Feed) feeds.Lookup, updaterf func(putter storage.Putter, signer crypto.Signer, topic []byte) (feeds.Updater, error)) {
   103  	t.Parallel()
   104  
   105  	var stop bool
   106  	for j := 10; !stop; j += 10 {
   107  		t.Run(fmt.Sprintf("custom intervals up to %d", j), func(t *testing.T) {
   108  			var i int64
   109  			var n int
   110  			f := func() (bool, int64) {
   111  				n++
   112  				stop, i = nextf()
   113  				return n == j || stop, i
   114  			}
   115  			TestFinderIntervals(t, f, finderf, updaterf)
   116  		})
   117  	}
   118  }
   119  
   120  func TestFinderIntervals(t *testing.T, nextf func() (bool, int64), finderf func(storage.Getter, *feeds.Feed) feeds.Lookup, updaterf func(putter storage.Putter, signer crypto.Signer, topic []byte) (feeds.Updater, error)) {
   121  
   122  	storer := &Timeout{inmemchunkstore.New()}
   123  	topicStr := "testtopic"
   124  	topic, err := crypto.LegacyKeccak256([]byte(topicStr))
   125  	if err != nil {
   126  		t.Fatal(err)
   127  	}
   128  	pk, _ := crypto.GenerateSecp256k1Key()
   129  	signer := crypto.NewDefaultSigner(pk)
   130  
   131  	updater, err := updaterf(storer, signer, topic)
   132  	if err != nil {
   133  		t.Fatal(err)
   134  	}
   135  	finder := finderf(storer, updater.Feed())
   136  
   137  	ctx := context.Background()
   138  	var ats []int64
   139  	for stop, at := nextf(); !stop; stop, at = nextf() {
   140  		ats = append(ats, at)
   141  		payload := make([]byte, 8)
   142  		binary.BigEndian.PutUint64(payload, uint64(at))
   143  		err = updater.Update(ctx, at, payload)
   144  		if err != nil {
   145  			t.Fatal(err)
   146  		}
   147  	}
   148  	for j := 0; j < len(ats)-1; j++ {
   149  		at := ats[j]
   150  		diff := ats[j+1] - at
   151  		for now := at; now < ats[j+1]; now += int64(rand.Intn(int(diff)) + 1) {
   152  			after := uint64(0)
   153  			ch, current, next, err := finder.At(ctx, now, after)
   154  			if err != nil {
   155  				t.Fatal(err)
   156  			}
   157  			if ch == nil {
   158  				t.Fatalf("expected to find update, got none")
   159  			}
   160  			ts, payload, err := feeds.FromChunk(ch)
   161  			if err != nil {
   162  				t.Fatal(err)
   163  			}
   164  			content := binary.BigEndian.Uint64(payload)
   165  			if content != uint64(at) {
   166  				t.Fatalf("payload mismatch: expected %v, got %v", at, content)
   167  			}
   168  
   169  			if ts != uint64(at) {
   170  				t.Fatalf("timestamp mismatch: expected %v, got %v", at, ts)
   171  			}
   172  
   173  			if current != nil {
   174  				expectedId := ch.Data()[:32]
   175  				id, err := feeds.Id(topic, current)
   176  				if err != nil {
   177  					t.Fatal(err)
   178  				}
   179  				if !bytes.Equal(id, expectedId) {
   180  					t.Fatalf("current mismatch: expected %x, got %x", expectedId, id)
   181  				}
   182  			}
   183  			if next != nil {
   184  				expectedNext := current.Next(at, uint64(now))
   185  				expectedIdx, err := expectedNext.MarshalBinary()
   186  				if err != nil {
   187  					t.Fatal(err)
   188  				}
   189  				idx, err := next.MarshalBinary()
   190  				if err != nil {
   191  					t.Fatal(err)
   192  				}
   193  				if !bytes.Equal(idx, expectedIdx) {
   194  					t.Fatalf("next mismatch: expected %x, got %x", expectedIdx, idx)
   195  				}
   196  			}
   197  		}
   198  	}
   199  }
   200  
   201  func TestFinderRandomIntervals(t *testing.T, finderf func(storage.Getter, *feeds.Feed) feeds.Lookup, updaterf func(putter storage.Putter, signer crypto.Signer, topic []byte) (feeds.Updater, error)) {
   202  	t.Parallel()
   203  
   204  	for j := 0; j < 3; j++ {
   205  		j := j
   206  		t.Run(fmt.Sprintf("random intervals %d", j), func(t *testing.T) {
   207  			t.Parallel()
   208  
   209  			var i int64
   210  			var n int
   211  			nextf := func() (bool, int64) {
   212  				i += int64(rand.Intn(1<<10) + 1)
   213  				n++
   214  				return n == 40, i
   215  			}
   216  			TestFinderIntervals(t, nextf, finderf, updaterf)
   217  		})
   218  	}
   219  }