func TestDB_ReserveGC_Unreserve(t *testing.T) {
	chunkCount := 100
	var closed chan struct{}
	testHookCollectGarbageChan := make(chan uint64)
	testHookEvictChan := make(chan uint64)
	t.Cleanup(setTestHookCollectGarbage(func(collectedCount uint64) {
		select {
		case testHookCollectGarbageChan <- collectedCount:
		case <-closed:
		}
	}))
	t.Cleanup(setTestHookEviction(func(collectedCount uint64) {
		select {
		case testHookEvictChan <- collectedCount:
		case <-closed:
		}
	}))
	var (
		mtx      sync.Mutex
		batchIDs [][]byte
		addrs    []swarm.Address
	)
	unres := func(f postage.UnreserveIteratorFn) error {
		mtx.Lock()
		defer mtx.Unlock()
		for i := 0; i < len(batchIDs); i++ {
			item := batchIDs[i]
			stop, err := f(item, 2)
			if err != nil {
				return err
			}
			if stop {
				return nil
			}
			stop, err = f(item, 4)
			if err != nil {
				return err
			}
			if stop {
				return nil
			}
		}
		batchIDs = nil
		return nil
	}
	db := newTestDB(t, &Options{
		Capacity: 100,
		ReserveCapacity: 100,
		UnreserveFunc:   unres,
	})
	closed = db.close
	for i := 0; i < chunkCount; i++ {
		ch := generateTestRandomChunkAt(swarm.NewAddress(db.baseKey), 2).WithBatch(2, 3, 2, false)
		_, err := db.Put(context.Background(), storage.ModePutUpload, ch)
		if err != nil {
			t.Fatal(err)
		}
		err = db.Set(context.Background(), storage.ModeSetSync, ch.Address())
		if err != nil {
			t.Fatal(err)
		}
		mtx.Lock()
		batchIDs = append(batchIDs, ch.Stamp().BatchID())
		addrs = append(addrs, ch.Address())
		mtx.Unlock()
	}
	evictTarget := db.reserveEvictionTarget()
	for {
		select {
		case <-testHookEvictChan:
		case <-time.After(10 * time.Second):
			t.Fatal("collect garbage timeout")
		}
		resSize, err := db.reserveSize.Get()
		if err != nil {
			t.Fatal(err)
		}
		if resSize == evictTarget {
			break
		}
	}
	for i := 0; i < 90; i++ {
		ch := generateTestRandomChunkAt(swarm.NewAddress(db.baseKey), 2).WithBatch(2, 3, 2, false)
		_, err := db.Put(context.Background(), storage.ModePutUpload, ch)
		if err != nil {
			t.Fatal(err)
		}
		err = db.Set(context.Background(), storage.ModeSetSync, ch.Address())
		if err != nil {
			t.Fatal(err)
		}
		mtx.Lock()
		batchIDs = append(batchIDs, ch.Stamp().BatchID())
		addrs = append(addrs, ch.Address())
		mtx.Unlock()
	}
	for {
		select {
		case <-testHookEvictChan:
		case <-time.After(10 * time.Second):
			t.Fatal("collect garbage timeout")
		}
		resSize, err := db.reserveSize.Get()
		if err != nil {
			t.Fatal(err)
		}
		if resSize == evictTarget {
			break
		}
	}
	gcTarget := db.gcTarget()
	for {
		select {
		case <-testHookCollectGarbageChan:
		case <-time.After(10 * time.Second):
			t.Fatal("collect garbage timeout")
		}
		gcSize, err := db.gcSize.Get()
		if err != nil {
			t.Fatal(err)
		}
		if gcSize == gcTarget {
			break
		}
	}
	t.Run("pull index count", newItemsCountTest(db.pullIndex, chunkCount+90-10))
	t.Run("postage chunks index count", newItemsCountTest(db.postageChunksIndex, chunkCount+90-10))
	t.Run("postage radius index count", newItemsCountTest(db.postageRadiusIndex, chunkCount))
	t.Run("gc index count", newItemsCountTest(db.gcIndex, 90))
	t.Run("gc size", newIndexGCSizeTest(db))
	t.Run("first ten unreserved chunks should not be accessible", func(t *testing.T) {
		for _, a := range addrs[:10] {
			_, err := db.Get(context.Background(), storage.ModeGetRequest, a)
			if err == nil {
				t.Error("got no error, want NotFound")
			}
		}
	})
	t.Run("the rest should be accessible", func(t *testing.T) {
		for _, a := range addrs[10:] {
			_, err := db.Get(context.Background(), storage.ModeGetRequest, a)
			if err != nil {
				t.Errorf("got error %v but want none", err)
			}
		}
	})
}
TestDB_ReserveGC_Unreserve tests that after calling UnreserveBatch
with a certain radius change, the correct chunks get put into the
GC index and eventually get garbage collected.
package localstore
import (
	"context"
	"errors"
	"sync"
	"testing"
	"time"
	"github.com/ethsana/sana/pkg/postage"
	"github.com/ethsana/sana/pkg/shed"
	"github.com/ethsana/sana/pkg/storage"
	"github.com/ethsana/sana/pkg/swarm"
)
func TestDB_ReserveGC_AllOutOfRadius(t *testing.T) {
	chunkCount := 150
	var closed chan struct{}
	testHookCollectGarbageChan := make(chan uint64)
	t.Cleanup(setTestHookCollectGarbage(func(collectedCount uint64) {
		select {
		case testHookCollectGarbageChan <- collectedCount:
		case <-closed:
		}
	}))
	t.Cleanup(setWithinRadiusFunc(func(*DB, shed.Item) bool { return false }))
	db := newTestDB(t, &Options{
		Capacity:        100,
		ReserveCapacity: 200,
	})
	closed = db.close
	addrs := make([]swarm.Address, 0)
	for i := 0; i < chunkCount; i++ {
		ch := generateTestRandomChunkAt(swarm.NewAddress(db.baseKey), 2).WithBatch(5, 3, 2, false)
		_, err := db.Put(context.Background(), storage.ModePutUpload, ch)
		if err != nil {
			t.Fatal(err)
		}
		err = db.Set(context.Background(), storage.ModeSetSync, ch.Address())
		if err != nil {
			t.Fatal(err)
		}
		addrs = append(addrs, ch.Address())
	}
	gcTarget := db.gcTarget()
	for {
		select {
		case <-testHookCollectGarbageChan:
		case <-time.After(10 * time.Second):
			t.Fatal("collect garbage timeout")
		}
		gcSize, err := db.gcSize.Get()
		if err != nil {
			t.Fatal(err)
		}
		if gcSize == gcTarget {
			break
		}
	}
	t.Run("pull index count", newItemsCountTest(db.pullIndex, int(gcTarget)))
	t.Run("postage chunks index count", newItemsCountTest(db.postageChunksIndex, int(gcTarget)))
	t.Run("postage radius index count", newItemsCountTest(db.postageRadiusIndex, 0))
	t.Run("gc index count", newItemsCountTest(db.gcIndex, int(gcTarget)))
	t.Run("gc size", newIndexGCSizeTest(db))
	t.Run("get the first synced chunk", func(t *testing.T) {
		_, err := db.Get(context.Background(), storage.ModeGetRequest, addrs[0])
		if !errors.Is(err, storage.ErrNotFound) {
			t.Errorf("got error %v, want %v", err, storage.ErrNotFound)
		}
	})
	t.Run("only first inserted chunks should be removed", func(t *testing.T) {
		for i := 0; i < (chunkCount - int(gcTarget)); i++ {
			_, err := db.Get(context.Background(), storage.ModeGetRequest, addrs[i])
			if !errors.Is(err, storage.ErrNotFound) {
				t.Errorf("got error %v, want %v", err, storage.ErrNotFound)
			}
		}
	})
	t.Run("get most recent synced chunk", func(t *testing.T) {
		_, err := db.Get(context.Background(), storage.ModeGetRequest, addrs[len(addrs)-1])
		if err != nil {
			t.Fatal(err)
		}
	})
}
func TestDB_ReserveGC_AllWithinRadius(t *testing.T) {
	chunkCount := 150
	var closed chan struct{}
	testHookCollectGarbageChan := make(chan uint64)
	t.Cleanup(setTestHookCollectGarbage(func(collectedCount uint64) {
		select {
		case testHookCollectGarbageChan <- collectedCount:
		case <-closed:
		}
	}))
	var (
		batchIDs        [][]byte
		unreserveCalled bool
		mtx             sync.Mutex
	)
	unres := func(f postage.UnreserveIteratorFn) error {
		mtx.Lock()
		defer mtx.Unlock()
		unreserveCalled = true
		for i := 0; i < len(batchIDs); i++ {
			item := batchIDs[i]
			stop, err := f(item, 4)
			if err != nil {
				return err
			}
			if stop {
				return nil
			}
		}
		return nil
	}
	db := newTestDB(t, &Options{
		Capacity:        100,
		ReserveCapacity: 151,
		UnreserveFunc:   unres,
	})
	closed = db.close
	addrs := make([]swarm.Address, 0)
	for i := 0; i < chunkCount; i++ {
		ch := generateTestRandomChunkAt(swarm.NewAddress(db.baseKey), 2).WithBatch(2, 3, 2, false)
		_, err := db.Put(context.Background(), storage.ModePutUpload, ch)
		if err != nil {
			t.Fatal(err)
		}
		err = db.Set(context.Background(), storage.ModeSetSync, ch.Address())
		if err != nil {
			t.Fatal(err)
		}
		mtx.Lock()
		addrs = append(addrs, ch.Address())
		batchIDs = append(batchIDs, ch.Stamp().BatchID())
		mtx.Unlock()
	}
	select {
	case <-testHookCollectGarbageChan:
		t.Fatal("gc ran but shouldnt have")
	case <-time.After(1 * time.Second):
	}
	t.Run("pull index count", newItemsCountTest(db.pullIndex, chunkCount))
	t.Run("postage chunks index count", newItemsCountTest(db.postageChunksIndex, chunkCount))
	t.Run("postage radius index count", newItemsCountTest(db.postageRadiusIndex, 0))
	t.Run("gc index count", newItemsCountTest(db.gcIndex, 0))
	t.Run("gc size", newIndexGCSizeTest(db))
	t.Run("all chunks should be accessible", func(t *testing.T) {
		for _, a := range addrs {
			_, err := db.Get(context.Background(), storage.ModeGetRequest, a)
			if err != nil {
				t.Errorf("got error %v, want none", err)
			}
		}
	})
	mtx.Lock()
	defer mtx.Unlock()
	if unreserveCalled {
		t.Fatal("unreserveCalled but should not have")
	}
}