RegionIO/internal/server/server_test.go

152 lines
4.1 KiB
Go

package server
import (
"context"
"errors"
"sync"
"sync/atomic"
"testing"
"regionio/internal/world"
)
func TestPlayerRegistrySupportsFourPlayersAndEnforcesLimit(t *testing.T) {
cfg := DefaultConfig()
cfg.MaxPlayers = 4
srv, err := NewWithCache(cfg, world.NewCache(-1, world.GenerateFlat))
if err != nil {
t.Fatal(err)
}
var sends atomic.Int32
var sessions []*PlayerSession
for _, name := range []string{"Alice", "Bob", "Carol", "Dave"} {
profile := Profile{Name: name, UUID: OfflineUUID(name)}
session, err := srv.RegisterPlayer(profile, func(int32, []byte) error {
sends.Add(1)
return nil
})
if err != nil {
t.Fatalf("register %s: %v", name, err)
}
sessions = append(sessions, session)
}
if got := srv.PlayerCount(); got != 4 {
t.Fatalf("player count = %d, want 4", got)
}
if _, err := srv.RegisterPlayer(Profile{Name: "Eve", UUID: OfflineUUID("Eve")}, nil); !errors.Is(err, ErrServerFull) {
t.Fatalf("fifth player error = %v, want ErrServerFull", err)
}
srv.Broadcast(1, []byte("packet"))
if got := sends.Load(); got != 4 {
t.Fatalf("broadcast sends = %d, want 4", got)
}
for _, session := range sessions {
srv.UnregisterPlayer(session)
}
if got := srv.PlayerCount(); got != 0 {
t.Fatalf("player count after disconnect = %d, want 0", got)
}
}
func TestConcurrentFourPlayerTransformsAndChunkBroadcast(t *testing.T) {
cfg := DefaultConfig()
cfg.WorldDir = ""
cfg.MaxPlayers = 4
srv, err := NewWithCache(cfg, world.NewCache(-1, world.GenerateFlat))
if err != nil {
t.Fatal(err)
}
var sends [4]atomic.Int32
sessions := make([]*PlayerSession, 4)
for i, name := range []string{"Alice", "Bob", "Carol", "Dave"} {
i := i
sessions[i], err = srv.RegisterPlayer(Profile{Name: name, UUID: OfflineUUID(name)}, func(int32, []byte) error {
sends[i].Add(1)
return nil
})
if err != nil {
t.Fatal(err)
}
srv.SetPlayerViewDistance(sessions[i], 2)
}
var wg sync.WaitGroup
for i, session := range sessions {
i, session := i, session
wg.Add(1)
go func() {
defer wg.Done()
for step := 0; step < 500; step++ {
srv.SetPlayerTransform(session, float64(i*16+step), 80, float64(-step), float32(step%360), 10, step%2 == 0)
_ = srv.PlayerSnapshots()
srv.BroadcastChunk(int32(step>>4), int32(-step>>4), 1, nil)
}
}()
}
wg.Wait()
if got := len(srv.PlayerSnapshots()); got != 4 {
t.Fatalf("snapshot count = %d, want 4", got)
}
for i, session := range sessions {
x := float64(i * 160)
srv.SetPlayerTransform(session, x, 80, 0, 0, 0, true)
before := sends[i].Load()
srv.BroadcastChunk(0, 0, 2, nil)
got := sends[i].Load() - before
if i == 0 && got != 1 {
t.Fatalf("near player received %d packets, want 1", got)
}
if i > 0 && got != 0 {
t.Fatalf("far player %d received %d packets, want 0", i, got)
}
}
}
func TestPlayerRegistryRejectsDuplicateName(t *testing.T) {
cfg := DefaultConfig()
srv, err := NewWithCache(cfg, world.NewCache(-1, world.GenerateFlat))
if err != nil {
t.Fatal(err)
}
first := Profile{Name: "Alice", UUID: OfflineUUID("Alice")}
if _, err := srv.RegisterPlayer(first, nil); err != nil {
t.Fatal(err)
}
duplicate := Profile{Name: "ALICE", UUID: OfflineUUID("ALICE")}
if _, err := srv.RegisterPlayer(duplicate, nil); !errors.Is(err, ErrDuplicatePlayer) {
t.Fatalf("duplicate error = %v, want ErrDuplicatePlayer", err)
}
}
func TestServerRejectsNegativeCacheLimit(t *testing.T) {
cfg := DefaultConfig()
cfg.MaxCachedChunks = -1
if _, err := NewWithCache(cfg, world.NewCache(-1, world.GenerateFlat)); err == nil {
t.Fatal("negative cache limit was accepted")
}
}
func TestNewUsesProductionBatchGenerator(t *testing.T) {
cfg := DefaultConfig()
cfg.WorldDir = ""
cfg.WorldSeed = 12345
cfg.MaxCachedChunks = 64
srv, err := New(cfg)
if err != nil {
t.Fatal(err)
}
if err := srv.Chunks().PreloadErrContext(context.Background(), 0, 0); err != nil {
t.Fatal(err)
}
for cx := int32(-1); cx <= 1; cx++ {
for cz := int32(-1); cz <= 1; cz++ {
if !srv.Chunks().IsChunkLoaded(cx, cz) {
t.Fatalf("production batch did not publish neighbor (%d,%d)", cx, cz)
}
}
}
}