From 4a528fe85aaf30110821fce0f4b5bb966850be4c Mon Sep 17 00:00:00 2001 From: noe Date: Mon, 28 Oct 2024 19:53:57 -0700 Subject: [PATCH] add more tests --- cmd/pruner/pruner.go | 15 ++- cmd/pruner/pruner_test.go | 22 ++++ cmd/ws/eventhandler/ess_bench_test.go | 128 ++++++++++++++++++++++ cmd/ws/eventhandler/event_handler.go | 9 +- cmd/ws/eventhandler/event_handler_test.go | 73 ++++++------ cmd/ws/ingest/ingest.go | 7 +- cmd/ws/ingest/ingest_test.go | 14 ++- store/storemock/vehiclestore.go | 45 ++++++++ store/vehicle.go | 8 ++ 9 files changed, 266 insertions(+), 55 deletions(-) create mode 100644 cmd/pruner/pruner_test.go create mode 100644 cmd/ws/eventhandler/ess_bench_test.go create mode 100644 store/storemock/vehiclestore.go diff --git a/cmd/pruner/pruner.go b/cmd/pruner/pruner.go index c0bf291..abb540e 100644 --- a/cmd/pruner/pruner.go +++ b/cmd/pruner/pruner.go @@ -17,17 +17,22 @@ func main() { log.Fatalln(err) } + playerStore := store.NewPlayerStore(db) + vehicleStore := store.NewVehicleStore(db) + ctx, cancel := context.WithTimeout(context.Background(), time.Second*30) defer cancel() + run(ctx, playerStore, vehicleStore) +} + +func run(ctx context.Context, ps store.IPlayerStore, vs store.IVehicleStore) { var wg sync.WaitGroup wg.Add(1) go func() { defer wg.Done() - - playerStore := store.NewPlayerStore(db) - i, err := playerStore.Prune(ctx) + i, err := ps.Prune(ctx) if err != nil { log.Println("pruner: playerStore.Prune failed") } @@ -38,9 +43,7 @@ func main() { wg.Add(1) go func() { defer wg.Done() - - vehicleStore := store.NewVehicleStore(db) - i, err := vehicleStore.Prune(ctx) + i, err := vs.Prune(ctx) if err != nil { log.Println("pruner: vehicleStore.Prune failed") } diff --git a/cmd/pruner/pruner_test.go b/cmd/pruner/pruner_test.go new file mode 100644 index 0000000..370c781 --- /dev/null +++ b/cmd/pruner/pruner_test.go @@ -0,0 +1,22 @@ +package main + +import ( + "context" + "testing" + "time" + + "github.com/genudine/saerro-go/store/storemock" +) + +func TestRun(t *testing.T) { + ps := new(storemock.MockPlayerStore) + vs := new(storemock.MockVehicleStore) + + ctx, cancel := context.WithTimeout(context.Background(), time.Second*30) + defer cancel() + + ps.On("Prune", ctx).Return(30, nil) + vs.On("Prune", ctx).Return(30, nil) + + run(ctx, ps, vs) +} diff --git a/cmd/ws/eventhandler/ess_bench_test.go b/cmd/ws/eventhandler/ess_bench_test.go new file mode 100644 index 0000000..51cd4bc --- /dev/null +++ b/cmd/ws/eventhandler/ess_bench_test.go @@ -0,0 +1,128 @@ +package eventhandler_test + +import ( + "context" + "database/sql" + "math/rand" + "os" + "strconv" + "testing" + + "github.com/genudine/saerro-go/cmd/ws/eventhandler" + "github.com/genudine/saerro-go/translators" + "github.com/genudine/saerro-go/types" + "github.com/genudine/saerro-go/util" +) + +const PreloadedCharacterCount = 45 + +var ( + characterStore = make([]string, PreloadedCharacterCount) + db *sql.DB +) + +func BenchmarkESS(b *testing.B) { + events, eh := prebench(b) + + for i := 0; i < b.N; i++ { + event := events[i] + + eh.HandleEvent(context.Background(), event) + } +} + +func prebench(b *testing.B) ([]types.ESSEvent, eventhandler.EventHandler) { + b.Helper() + b.StopTimer() + + // Create our character pool + for i := 0; i < PreloadedCharacterCount; i++ { + characterStore[i] = mkRandomCharacterID() + } + + // Create events + events := []types.ESSEvent{} + for i := 0; i < b.N; i++ { + events = append(events, mkRandomEvent()) + } + + if db == nil { + var err error + db, err = util.GetDBConnection(os.Getenv("DB_ADDR")) + if err != nil { + b.Fatal(err) + } + } + + eh := eventhandler.NewEventHandler(db) + + b.ResetTimer() + b.StartTimer() + + return events, eh +} + +func mkRandomEvent() types.ESSEvent { + w := rand.Intn(4) + z := rand.Intn(7) + + switch rand.Intn(2) { + case 0: + return mkRandomDeathEvent(w, z) + default: + return mkRandomExpEvent(w, z) + } +} + +func mkRandomDeathEvent(world, zone int) types.ESSEvent { + return types.ESSEvent{ + EventName: "Death", + WorldID: uint16(world), + ZoneID: uint32(zone), + + CharacterID: getRandomCharacterID(), + CharacterLoadoutID: mkRandomLoadout(), + TeamID: mkRandomFaction(), + + AttackerCharacterID: getRandomCharacterID(), + AttackerLoadoutID: mkRandomLoadout(), + AttackerTeamID: mkRandomFaction(), + } +} + +func mkRandomExpEvent(world, zone int) types.ESSEvent { + return types.ESSEvent{ + EventName: "GainExperience", + WorldID: uint16(world), + ZoneID: uint32(zone), + + CharacterID: getRandomCharacterID(), + LoadoutID: mkRandomLoadout(), + TeamID: mkRandomFaction(), + + ExperienceID: rand.Uint32() % 256, + } +} + +func getRandomCharacterID() string { + i := rand.Intn(PreloadedCharacterCount) + return characterStore[i] +} + +func mkRandomCharacterID() string { + return strconv.Itoa(rand.Int()) +} + +func mkRandomFaction() types.Faction { + return types.Faction(rand.Intn(4)) +} + +func mkRandomLoadout() uint16 { + for { + i := rand.Intn(46) + _, ok := translators.LoadoutMap[uint16(i)] + if ok { + return uint16(i) + } + } +} diff --git a/cmd/ws/eventhandler/event_handler.go b/cmd/ws/eventhandler/event_handler.go index 4218201..4d635f7 100644 --- a/cmd/ws/eventhandler/event_handler.go +++ b/cmd/ws/eventhandler/event_handler.go @@ -9,8 +9,15 @@ import ( "github.com/genudine/saerro-go/types" ) +type IEventHandler interface { + HandleEvent(ctx context.Context, event types.ESSEvent) + HandleDeath(ctx context.Context, event types.ESSEvent) + HandleExperience(ctx context.Context, event types.ESSEvent) + HandleAnalytics(ctx context.Context, event types.ESSEvent) +} + type EventHandler struct { - Ingest *ingest.Ingest + Ingest ingest.IIngest } func NewEventHandler(db *sql.DB) EventHandler { diff --git a/cmd/ws/eventhandler/event_handler_test.go b/cmd/ws/eventhandler/event_handler_test.go index 54d8c78..a9f21be 100644 --- a/cmd/ws/eventhandler/event_handler_test.go +++ b/cmd/ws/eventhandler/event_handler_test.go @@ -5,32 +5,28 @@ import ( "testing" "time" - "github.com/avast/retry-go" - "github.com/stretchr/testify/assert" - "github.com/genudine/saerro-go/cmd/ws/ingest" - "github.com/genudine/saerro-go/store" - "github.com/genudine/saerro-go/translators" + "github.com/genudine/saerro-go/store/storemock" "github.com/genudine/saerro-go/types" - "github.com/genudine/saerro-go/util/testutil" ) -func getEventHandlerTestShim(t *testing.T) (EventHandler, context.Context) { +func getEventHandlerTestShim(t *testing.T) (EventHandler, context.Context, *storemock.MockPlayerStore) { t.Helper() - db := testutil.GetTestDB(t) ctx, cancel := context.WithTimeout(context.Background(), time.Second*15) t.Cleanup(cancel) + ps := new(storemock.MockPlayerStore) + return EventHandler{ Ingest: &ingest.Ingest{ - PlayerStore: store.NewPlayerStore(db), + PlayerStore: ps, }, - }, ctx + }, ctx, ps } func TestHandleDeath(t *testing.T) { - eh, ctx := getEventHandlerTestShim(t) + eh, ctx, ps := getEventHandlerTestShim(t) event := types.ESSEvent{ EventName: "Death", @@ -46,21 +42,17 @@ func TestHandleDeath(t *testing.T) { AttackerTeamID: types.TR, } + p1 := types.PopEventFromESSEvent(event, false).ToPlayer() + p2 := types.PopEventFromESSEvent(event, true).ToPlayer() + + ps.On("Insert", ctx, p1).Return(nil) + ps.On("Insert", ctx, p2).Return(nil) + eh.HandleDeath(ctx, event) - - player1, err := eh.Ingest.PlayerStore.GetOne(ctx, event.CharacterID) - assert.NoError(t, err, "player1 fetch failed") - assert.Equal(t, event.CharacterID, player1.CharacterID) - assert.Equal(t, string(translators.ClassFromLoadout(event.LoadoutID)), player1.ClassName) - - player2, err := eh.Ingest.PlayerStore.GetOne(ctx, event.AttackerCharacterID) - assert.NoError(t, err, "player2 fetch failed") - assert.Equal(t, event.AttackerCharacterID, player2.CharacterID) - assert.Equal(t, string(translators.ClassFromLoadout(event.AttackerLoadoutID)), player2.ClassName) } func TestHandleExperience(t *testing.T) { - eh, ctx := getEventHandlerTestShim(t) + eh, ctx, ps := getEventHandlerTestShim(t) event := types.ESSEvent{ EventName: "GainExperience", @@ -74,15 +66,14 @@ func TestHandleExperience(t *testing.T) { ExperienceID: 674, } + p := types.PopEventFromESSEvent(event, false).ToPlayer() + ps.On("Insert", ctx, p).Return(nil) + eh.HandleExperience(ctx, event) - player, err := eh.Ingest.PlayerStore.GetOne(ctx, event.CharacterID) - assert.NoError(t, err, "player fetch check failed") - assert.Equal(t, event.CharacterID, player.CharacterID) - assert.Equal(t, string(translators.ClassFromLoadout(event.LoadoutID)), player.ClassName) } func TestHandleAnalytics(t *testing.T) { - eh, ctx := getEventHandlerTestShim(t) + eh, ctx, _ := getEventHandlerTestShim(t) event := types.ESSEvent{ EventName: "GainExperience", WorldID: 17, @@ -99,7 +90,7 @@ func TestHandleAnalytics(t *testing.T) { } func TestHandleEvent(t *testing.T) { - eh, ctx := getEventHandlerTestShim(t) + eh, ctx, ps := getEventHandlerTestShim(t) events := []types.ESSEvent{ { @@ -107,9 +98,9 @@ func TestHandleEvent(t *testing.T) { WorldID: 17, ZoneID: 2, - CharacterID: "LyytisDoll", - LoadoutID: 3, - TeamID: types.NC, + CharacterID: "LyytisDoll", + CharacterLoadoutID: 3, + TeamID: types.NC, AttackerCharacterID: "Lyyti", AttackerLoadoutID: 3, @@ -126,19 +117,19 @@ func TestHandleEvent(t *testing.T) { ExperienceID: 201, }, + { + EventName: "", + }, } + p1 := types.PopEventFromESSEvent(events[0], false).ToPlayer() + ps.On("Insert", ctx, p1).Return(nil).Once() + p2 := types.PopEventFromESSEvent(events[0], true).ToPlayer() + ps.On("Insert", ctx, p2).Return(nil).Once() + p3 := types.PopEventFromESSEvent(events[1], false).ToPlayer() + ps.On("Insert", ctx, p3).Return(nil).Once() + for _, event := range events { eh.HandleEvent(ctx, event) } - - checkPlayers := []string{"LyytisDoll", "Lyyti", "DollNC"} - for _, id := range checkPlayers { - // eventual consistency <333 - err := retry.Do(func() error { - _, err := eh.Ingest.PlayerStore.GetOne(ctx, id) - return err - }) - assert.NoError(t, err) - } } diff --git a/cmd/ws/ingest/ingest.go b/cmd/ws/ingest/ingest.go index 50b88e8..4ed52ad 100644 --- a/cmd/ws/ingest/ingest.go +++ b/cmd/ws/ingest/ingest.go @@ -9,8 +9,13 @@ import ( "github.com/genudine/saerro-go/types" ) +type IIngest interface { + TrackPop(context.Context, types.PopEvent) +} + type Ingest struct { - PlayerStore store.IPlayerStore + PlayerStore store.IPlayerStore + VehicleStore store.IVehicleStore } func (i *Ingest) TrackPop(ctx context.Context, event types.PopEvent) { diff --git a/cmd/ws/ingest/ingest_test.go b/cmd/ws/ingest/ingest_test.go index 325b416..e21e105 100644 --- a/cmd/ws/ingest/ingest_test.go +++ b/cmd/ws/ingest/ingest_test.go @@ -12,23 +12,25 @@ import ( "github.com/genudine/saerro-go/types" ) -func mkIngest(t *testing.T) (context.Context, *ingest.Ingest, *storemock.MockPlayerStore) { +func mkIngest(t *testing.T) (context.Context, *ingest.Ingest, *storemock.MockPlayerStore, *storemock.MockVehicleStore) { t.Helper() ps := new(storemock.MockPlayerStore) + vs := new(storemock.MockVehicleStore) ctx, cancel := context.WithTimeout(context.Background(), time.Second*15) t.Cleanup(cancel) i := &ingest.Ingest{ - PlayerStore: ps, + PlayerStore: ps, + VehicleStore: vs, } - return ctx, i, ps + return ctx, i, ps, vs } func TestTrackPopHappyPath(t *testing.T) { - ctx, i, ps := mkIngest(t) + ctx, i, ps, _ := mkIngest(t) // Combat Medic on Emerald event := types.PopEvent{ @@ -48,7 +50,7 @@ func TestTrackPopHappyPath(t *testing.T) { } func TestTrackPopFixup(t *testing.T) { - ctx, i, ps := mkIngest(t) + ctx, i, ps, _ := mkIngest(t) event := types.PopEvent{ WorldID: 17, @@ -68,7 +70,7 @@ func TestTrackPopFixup(t *testing.T) { } func TestTrackPopFixupFailed(t *testing.T) { - ctx, i, ps := mkIngest(t) + ctx, i, ps, _ := mkIngest(t) event := types.PopEvent{ WorldID: 17, diff --git a/store/storemock/vehiclestore.go b/store/storemock/vehiclestore.go new file mode 100644 index 0000000..3fc3a98 --- /dev/null +++ b/store/storemock/vehiclestore.go @@ -0,0 +1,45 @@ +package storemock + +import ( + "context" + "database/sql" + + "github.com/genudine/saerro-go/types" + "github.com/stretchr/testify/mock" +) + +type MockVehicleStore struct { + mock.Mock + + DB *sql.DB + RanMigration bool +} + +func (m *MockVehicleStore) IsMigrated(ctx context.Context) bool { + args := m.Called(ctx) + return args.Bool(0) +} + +func (m *MockVehicleStore) RunMigration(ctx context.Context, force bool) { + m.Called(ctx, force) +} + +func (m *MockVehicleStore) Insert(ctx context.Context, vehicle *types.Vehicle) error { + args := m.Called(ctx, vehicle) + return args.Error(0) +} + +func (m *MockVehicleStore) GetOne(ctx context.Context, id string) (*types.Vehicle, error) { + args := m.Called(ctx, id) + + if args.Get(0) == nil { + return nil, args.Error(1) + } + + return args.Get(0).(*types.Vehicle), args.Error(1) +} + +func (m *MockVehicleStore) Prune(ctx context.Context) (int64, error) { + args := m.Called(ctx) + return int64(args.Int(0)), args.Error(1) +} diff --git a/store/vehicle.go b/store/vehicle.go index 0f26205..a313276 100644 --- a/store/vehicle.go +++ b/store/vehicle.go @@ -11,6 +11,14 @@ import ( "github.com/avast/retry-go" ) +type IVehicleStore interface { + IsMigrated(context.Context) bool + RunMigration(context.Context, bool) + Insert(context.Context, *types.Vehicle) error + GetOne(context.Context, string) (*types.Vehicle, error) + Prune(context.Context) (int64, error) +} + type VehicleStore struct { DB *sql.DB