github.com/ethersphere/bee/v2@v2.2.0/pkg/sharky/shard_slots_test.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  package sharky
     5  
     6  import (
     7  	"bytes"
     8  	"context"
     9  	"fmt"
    10  	"io/fs"
    11  	"os"
    12  	"path/filepath"
    13  	"sync"
    14  	"testing"
    15  	"time"
    16  )
    17  
    18  // TestShard ensures that released slots eventually become available for writes
    19  func TestShard(t *testing.T) {
    20  	t.Parallel()
    21  
    22  	shard := newShard(t)
    23  
    24  	payload := write{buf: []byte{0xff}, res: make(chan entry)}
    25  	loc := writePayload(t, shard, payload)
    26  	buf := readFromLocation(t, shard, loc)
    27  
    28  	if !bytes.Equal(buf, payload.buf) {
    29  		t.Fatalf("want %x, got %x", buf, payload.buf)
    30  	}
    31  
    32  	if loc.Slot != 0 {
    33  		t.Fatalf("expected to write to slot 0, got %d", loc.Slot)
    34  	}
    35  
    36  	// in order for the test to succeed this slot is expected to become available before test finishes
    37  	releaseSlot(t, shard, loc)
    38  
    39  	payload = write{buf: []byte{0xff >> 1}, res: make(chan entry)}
    40  	loc = writePayload(t, shard, payload)
    41  
    42  	// immediate write should pick the next slot
    43  	if loc.Slot != 1 {
    44  		t.Fatalf("expected to write to slot 1, got %d", loc.Slot)
    45  	}
    46  
    47  	releaseSlot(t, shard, loc)
    48  
    49  	// we make ten writes expecting that slot 0 is released and becomes available for writing eventually
    50  	i, runs := 0, 10
    51  	for ; i < runs; i++ {
    52  		payload = write{buf: []byte{0x01 << i}, res: make(chan entry)}
    53  		loc = writePayload(t, shard, payload)
    54  		releaseSlot(t, shard, loc)
    55  		if loc.Slot == 0 {
    56  			break
    57  		}
    58  	}
    59  
    60  	if i == runs {
    61  		t.Errorf("expected to write to slot 0 within %d runs, write did not occur", runs)
    62  	}
    63  }
    64  
    65  func writePayload(t *testing.T, shard *shard, payload write) (loc Location) {
    66  	t.Helper()
    67  
    68  	select {
    69  	case shard.writes <- payload:
    70  		e := <-payload.res
    71  		if e.err != nil {
    72  			t.Fatal("write entry", e.err)
    73  		}
    74  		loc = e.loc
    75  	case <-time.After(100 * time.Millisecond):
    76  		t.Fatal("write timeout")
    77  	}
    78  
    79  	return loc
    80  }
    81  
    82  func readFromLocation(t *testing.T, shard *shard, loc Location) []byte {
    83  	t.Helper()
    84  	buf := make([]byte, loc.Length)
    85  
    86  	select {
    87  	case shard.reads <- read{ctx: context.Background(), buf: buf[:loc.Length], slot: loc.Slot}:
    88  		if err := <-shard.errc; err != nil {
    89  			t.Fatal("read", err)
    90  		}
    91  	case <-time.After(100 * time.Millisecond):
    92  		t.Fatal("timeout reading")
    93  	}
    94  
    95  	return buf
    96  }
    97  
    98  func releaseSlot(t *testing.T, shard *shard, loc Location) {
    99  	t.Helper()
   100  	ctx := context.Background()
   101  	cctx, cancel := context.WithTimeout(ctx, 500*time.Millisecond)
   102  	if err := shard.release(cctx, loc.Slot); err != nil {
   103  		t.Fatal("release slot", loc.Slot, "err", err)
   104  	}
   105  	cancel()
   106  }
   107  
   108  type dirFS string
   109  
   110  func (d *dirFS) Open(path string) (fs.File, error) {
   111  	return os.OpenFile(filepath.Join(string(*d), path), os.O_RDWR|os.O_CREATE, 0644)
   112  }
   113  
   114  func newShard(t *testing.T) *shard {
   115  	t.Helper()
   116  
   117  	basedir := dirFS(t.TempDir())
   118  	index := 1
   119  
   120  	file, err := basedir.Open(fmt.Sprintf("shard_%03d", index))
   121  	if err != nil {
   122  		t.Fatal(err)
   123  	}
   124  
   125  	ffile, err := basedir.Open(fmt.Sprintf("free_%03d", index))
   126  	if err != nil {
   127  		t.Fatal(err)
   128  	}
   129  
   130  	var wg sync.WaitGroup
   131  
   132  	slots := newSlots(ffile.(sharkyFile), &wg)
   133  	err = slots.load()
   134  	if err != nil {
   135  		t.Fatal(err)
   136  	}
   137  
   138  	quit := make(chan struct{})
   139  	shard := &shard{
   140  		reads:       make(chan read),
   141  		errc:        make(chan error),
   142  		writes:      make(chan write),
   143  		index:       uint8(index),
   144  		maxDataSize: 1,
   145  		file:        file.(sharkyFile),
   146  		slots:       slots,
   147  		quit:        quit,
   148  	}
   149  
   150  	t.Cleanup(func() {
   151  		close(quit)
   152  		if err := shard.close(); err != nil {
   153  			t.Fatal("close shard", err)
   154  		}
   155  	})
   156  
   157  	terminated := make(chan struct{})
   158  
   159  	wg.Add(1)
   160  	go func() {
   161  		defer wg.Done()
   162  		shard.process()
   163  		close(terminated)
   164  	}()
   165  
   166  	wg.Add(1)
   167  	go func() {
   168  		defer wg.Done()
   169  		slots.process(terminated)
   170  	}()
   171  
   172  	return shard
   173  }