func TestDB_ReserveGC_EvictMaxPO(t *testing.T) {
	var (
		mtx        sync.Mutex
		batchIDs   [][]byte
		addrs      []swarm.Address
		chunkCount = 100
		testHookCollectGarbageChan = make(chan uint64)
		testHookEvictChan          = make(chan uint64)
		closed                     chan struct{}
	)
	t.Cleanup(setTestHookCollectGarbage(func(collectedCount uint64) {
		if collectedCount == 0 {
			return
		}
		select {
		case testHookCollectGarbageChan <- collectedCount:
		case <-closed:
		}
	}))
	t.Cleanup(setTestHookEviction(func(collectedCount uint64) {
		if collectedCount == 0 {
			return
		}
		select {
		case testHookEvictChan <- collectedCount:
		case <-closed:
		}
	}))
	unres := func(f postage.UnreserveIteratorFn) error {
		mtx.Lock()
		defer mtx.Unlock()
		i := 0
		defer func() { batchIDs = batchIDs[i:] }()
		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, swarm.MaxPO+1)
			if err != nil {
				return err
			}
			if stop {
				return 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
		}
	}
	t.Run("postage radius index count", newItemsCountTest(db.postageRadiusIndex, 0))
	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, 0))
	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_EvictMaxPO tests that when unreserving a batch at
swarm.MaxPO+1 results in the correct behaviour.
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")
	}
}
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)
			}
		}
	})
}