bring in khatru and eventstore.
This commit is contained in:
191
eventstore/mmm/fuzz_test.go
Normal file
191
eventstore/mmm/fuzz_test.go
Normal file
@@ -0,0 +1,191 @@
|
||||
package mmm
|
||||
|
||||
import (
|
||||
"context"
|
||||
"fmt"
|
||||
"math/rand/v2"
|
||||
"os"
|
||||
"slices"
|
||||
"testing"
|
||||
|
||||
"github.com/nbd-wtf/go-nostr"
|
||||
"github.com/rs/zerolog"
|
||||
"github.com/stretchr/testify/require"
|
||||
)
|
||||
|
||||
func FuzzTest(f *testing.F) {
|
||||
f.Add(0, uint(84), uint(10), uint(5))
|
||||
f.Fuzz(func(t *testing.T, seed int, nlayers, nevents, ndeletes uint) {
|
||||
nlayers = nlayers%23 + 1
|
||||
nevents = nevents%10000 + 1
|
||||
ndeletes = ndeletes % nevents
|
||||
|
||||
// create a temporary directory for the test
|
||||
tmpDir, err := os.MkdirTemp("", "mmm-test-*")
|
||||
require.NoError(t, err)
|
||||
defer os.RemoveAll(tmpDir)
|
||||
|
||||
logger := zerolog.Nop()
|
||||
rnd := rand.New(rand.NewPCG(uint64(seed), 0))
|
||||
|
||||
// initialize MMM
|
||||
mmm := &MultiMmapManager{
|
||||
Dir: tmpDir,
|
||||
Logger: &logger,
|
||||
}
|
||||
|
||||
err = mmm.Init()
|
||||
require.NoError(t, err)
|
||||
defer mmm.Close()
|
||||
|
||||
for i := range nlayers {
|
||||
name := string([]byte{97 + byte(i)})
|
||||
err = mmm.EnsureLayer(name, &IndexingLayer{
|
||||
MaxLimit: 1000,
|
||||
})
|
||||
require.NoError(t, err, "layer %s/%d", name, i)
|
||||
}
|
||||
|
||||
// create test events
|
||||
ctx := context.Background()
|
||||
sk := "945e01e37662430162121b804d3645a86d97df9d256917d86735d0eb219393eb"
|
||||
storedIds := make([]string, nevents)
|
||||
nTags := make(map[string]int)
|
||||
storedByLayer := make(map[string][]string)
|
||||
|
||||
// create n events with random combinations of tags
|
||||
for i := 0; i < int(nevents); i++ {
|
||||
tags := nostr.Tags{}
|
||||
// randomly add 1-nlayers tags
|
||||
numTags := 1 + (i % int(nlayers))
|
||||
usedTags := make(map[string]bool)
|
||||
|
||||
for j := 0; j < numTags; j++ {
|
||||
tag := string([]byte{97 + byte(i%int(nlayers))})
|
||||
if !usedTags[tag] {
|
||||
tags = append(tags, nostr.Tag{"t", tag})
|
||||
usedTags[tag] = true
|
||||
}
|
||||
}
|
||||
|
||||
evt := &nostr.Event{
|
||||
CreatedAt: nostr.Timestamp(i),
|
||||
Kind: i, // hack to query by serial id
|
||||
Tags: tags,
|
||||
Content: fmt.Sprintf("test content %d", i),
|
||||
}
|
||||
evt.Sign(sk)
|
||||
|
||||
for _, layer := range mmm.layers {
|
||||
if evt.Tags.FindWithValue("t", layer.name) != nil {
|
||||
err := layer.SaveEvent(ctx, evt)
|
||||
require.NoError(t, err)
|
||||
storedByLayer[layer.name] = append(storedByLayer[layer.name], evt.ID)
|
||||
}
|
||||
}
|
||||
|
||||
storedIds = append(storedIds, evt.ID)
|
||||
nTags[evt.ID] = len(evt.Tags)
|
||||
}
|
||||
|
||||
// verify each layer has the correct events
|
||||
for _, layer := range mmm.layers {
|
||||
results, err := layer.QueryEvents(ctx, nostr.Filter{})
|
||||
require.NoError(t, err)
|
||||
|
||||
count := 0
|
||||
for evt := range results {
|
||||
require.True(t, evt.Tags.ContainsAny("t", []string{layer.name}))
|
||||
count++
|
||||
}
|
||||
require.Equal(t, count, len(storedByLayer[layer.name]))
|
||||
}
|
||||
|
||||
// randomly select n events to delete from random layers
|
||||
deleted := make(map[string][]*IndexingLayer)
|
||||
|
||||
for range ndeletes {
|
||||
id := storedIds[rnd.Int()%len(storedIds)]
|
||||
layer := mmm.layers[rnd.Int()%len(mmm.layers)]
|
||||
|
||||
evt, layers := mmm.GetByID(id)
|
||||
|
||||
if slices.Contains(deleted[id], layer) {
|
||||
// already deleted from this layer
|
||||
require.NotContains(t, layers, layer)
|
||||
} else if evt != nil && evt.Tags.FindWithValue("t", layer.name) != nil {
|
||||
require.Contains(t, layers, layer)
|
||||
|
||||
// delete now
|
||||
layer.DeleteEvent(ctx, evt)
|
||||
deleted[id] = append(deleted[id], layer)
|
||||
} else {
|
||||
// was never saved to this in the first place
|
||||
require.NotContains(t, layers, layer)
|
||||
}
|
||||
}
|
||||
|
||||
for id, deletedlayers := range deleted {
|
||||
evt, foundlayers := mmm.GetByID(id)
|
||||
|
||||
for _, layer := range deletedlayers {
|
||||
require.NotContains(t, foundlayers, layer)
|
||||
}
|
||||
for _, layer := range foundlayers {
|
||||
require.NotNil(t, evt.Tags.FindWithValue("t", layer.name))
|
||||
}
|
||||
|
||||
if nTags[id] == len(deletedlayers) && evt != nil {
|
||||
deletedlayersnames := make([]string, len(deletedlayers))
|
||||
for i, layer := range deletedlayers {
|
||||
deletedlayersnames[i] = layer.name
|
||||
}
|
||||
|
||||
t.Fatalf("id %s has %d tags %v, should have been deleted from %v, but wasn't: %s",
|
||||
id, nTags[id], evt.Tags, deletedlayersnames, evt)
|
||||
} else if nTags[id] > len(deletedlayers) {
|
||||
t.Fatalf("id %s should still be available as it had %d tags and was only deleted from %v, but isn't",
|
||||
id, nTags[id], deletedlayers)
|
||||
}
|
||||
|
||||
if evt != nil {
|
||||
for _, layer := range mmm.layers {
|
||||
// verify event still accessible from other layers
|
||||
if slices.Contains(foundlayers, layer) {
|
||||
ch, err := layer.QueryEvents(ctx, nostr.Filter{Kinds: []int{evt.Kind}}) // hack
|
||||
require.NoError(t, err)
|
||||
fetched := <-ch
|
||||
require.NotNil(t, fetched)
|
||||
} else {
|
||||
// and not accessible from this layer we just deleted
|
||||
ch, err := layer.QueryEvents(ctx, nostr.Filter{Kinds: []int{evt.Kind}}) // hack
|
||||
require.NoError(t, err)
|
||||
fetched := <-ch
|
||||
require.Nil(t, fetched)
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// now delete a layer and events that only exist in that layer should vanish
|
||||
layer := mmm.layers[rnd.Int()%len(mmm.layers)]
|
||||
ch, err := layer.QueryEvents(ctx, nostr.Filter{})
|
||||
require.NoError(t, err)
|
||||
|
||||
eventsThatShouldVanish := make([]string, 0, nevents/2)
|
||||
for evt := range ch {
|
||||
if len(evt.Tags) == 1+len(deleted[evt.ID]) {
|
||||
eventsThatShouldVanish = append(eventsThatShouldVanish, evt.ID)
|
||||
}
|
||||
}
|
||||
|
||||
err = mmm.DropLayer(layer.name)
|
||||
require.NoError(t, err)
|
||||
|
||||
for _, id := range eventsThatShouldVanish {
|
||||
v, ils := mmm.GetByID(id)
|
||||
require.Nil(t, v)
|
||||
require.Empty(t, ils)
|
||||
}
|
||||
})
|
||||
}
|
||||
Reference in New Issue
Block a user