mirror of
https://github.com/Mezeporta/Erupe.git
synced 2026-03-24 08:33:41 +01:00
test: expand channelserver coverage from 7.5% to 12%
Add comprehensive tests for channelserver package: - handlers_character_test.go: CharacterSaveData, pointer constants - handlers_data_test.go: grpToGR function with boundary tests - handlers_quest_test.go: findSubSliceIndices, equal functions - handlers_simple_test.go: simple handlers, ack responses - handlers_util_test.go: stub handlers, ack helpers - sys_channel_server_test.go: Server, Raviente, stages, semaphores - sys_object_test.go: Object, Stage, stageBinaryKey structs All tests pass with race detection enabled.
This commit is contained in:
397
server/channelserver/sys_object_test.go
Normal file
397
server/channelserver/sys_object_test.go
Normal file
@@ -0,0 +1,397 @@
|
||||
package channelserver
|
||||
|
||||
import (
|
||||
"sync"
|
||||
"testing"
|
||||
)
|
||||
|
||||
func TestObjectStruct(t *testing.T) {
|
||||
obj := &Object{
|
||||
id: 12345,
|
||||
ownerCharID: 67890,
|
||||
x: 100.5,
|
||||
y: 50.25,
|
||||
z: -10.0,
|
||||
}
|
||||
|
||||
if obj.id != 12345 {
|
||||
t.Errorf("Object id = %d, want 12345", obj.id)
|
||||
}
|
||||
if obj.ownerCharID != 67890 {
|
||||
t.Errorf("Object ownerCharID = %d, want 67890", obj.ownerCharID)
|
||||
}
|
||||
if obj.x != 100.5 {
|
||||
t.Errorf("Object x = %f, want 100.5", obj.x)
|
||||
}
|
||||
if obj.y != 50.25 {
|
||||
t.Errorf("Object y = %f, want 50.25", obj.y)
|
||||
}
|
||||
if obj.z != -10.0 {
|
||||
t.Errorf("Object z = %f, want -10.0", obj.z)
|
||||
}
|
||||
}
|
||||
|
||||
func TestObjectRWMutex(t *testing.T) {
|
||||
obj := &Object{
|
||||
id: 1,
|
||||
ownerCharID: 100,
|
||||
x: 0,
|
||||
y: 0,
|
||||
z: 0,
|
||||
}
|
||||
|
||||
// Test read lock
|
||||
obj.RLock()
|
||||
_ = obj.x
|
||||
obj.RUnlock()
|
||||
|
||||
// Test write lock
|
||||
obj.Lock()
|
||||
obj.x = 100.0
|
||||
obj.Unlock()
|
||||
|
||||
if obj.x != 100.0 {
|
||||
t.Errorf("Object x = %f, want 100.0 after write", obj.x)
|
||||
}
|
||||
}
|
||||
|
||||
func TestObjectConcurrentAccess(t *testing.T) {
|
||||
obj := &Object{
|
||||
id: 1,
|
||||
ownerCharID: 100,
|
||||
x: 0,
|
||||
y: 0,
|
||||
z: 0,
|
||||
}
|
||||
|
||||
var wg sync.WaitGroup
|
||||
|
||||
// Concurrent writers
|
||||
for i := 0; i < 10; i++ {
|
||||
wg.Add(1)
|
||||
go func(val float32) {
|
||||
defer wg.Done()
|
||||
for j := 0; j < 100; j++ {
|
||||
obj.Lock()
|
||||
obj.x = val
|
||||
obj.y = val
|
||||
obj.z = val
|
||||
obj.Unlock()
|
||||
}
|
||||
}(float32(i))
|
||||
}
|
||||
|
||||
// Concurrent readers
|
||||
for i := 0; i < 5; i++ {
|
||||
wg.Add(1)
|
||||
go func() {
|
||||
defer wg.Done()
|
||||
for j := 0; j < 100; j++ {
|
||||
obj.RLock()
|
||||
_ = obj.x
|
||||
_ = obj.y
|
||||
_ = obj.z
|
||||
obj.RUnlock()
|
||||
}
|
||||
}()
|
||||
}
|
||||
|
||||
wg.Wait()
|
||||
}
|
||||
|
||||
func TestStageBinaryKeyStruct(t *testing.T) {
|
||||
key1 := stageBinaryKey{id0: 1, id1: 2}
|
||||
key2 := stageBinaryKey{id0: 1, id1: 3}
|
||||
key3 := stageBinaryKey{id0: 1, id1: 2}
|
||||
|
||||
// Different keys
|
||||
if key1 == key2 {
|
||||
t.Error("key1 and key2 should be different")
|
||||
}
|
||||
|
||||
// Same keys
|
||||
if key1 != key3 {
|
||||
t.Error("key1 and key3 should be equal")
|
||||
}
|
||||
}
|
||||
|
||||
func TestStageBinaryKeyAsMapKey(t *testing.T) {
|
||||
data := make(map[stageBinaryKey][]byte)
|
||||
|
||||
key1 := stageBinaryKey{id0: 0, id1: 0}
|
||||
key2 := stageBinaryKey{id0: 0, id1: 1}
|
||||
key3 := stageBinaryKey{id0: 1, id1: 0}
|
||||
|
||||
data[key1] = []byte{0x01}
|
||||
data[key2] = []byte{0x02}
|
||||
data[key3] = []byte{0x03}
|
||||
|
||||
if len(data) != 3 {
|
||||
t.Errorf("Expected 3 entries, got %d", len(data))
|
||||
}
|
||||
|
||||
if data[key1][0] != 0x01 {
|
||||
t.Errorf("data[key1] = 0x%02X, want 0x01", data[key1][0])
|
||||
}
|
||||
if data[key2][0] != 0x02 {
|
||||
t.Errorf("data[key2] = 0x%02X, want 0x02", data[key2][0])
|
||||
}
|
||||
if data[key3][0] != 0x03 {
|
||||
t.Errorf("data[key3] = 0x%02X, want 0x03", data[key3][0])
|
||||
}
|
||||
}
|
||||
|
||||
func TestNewStageDefaults(t *testing.T) {
|
||||
stage := NewStage("test_stage_001")
|
||||
|
||||
if stage.id != "test_stage_001" {
|
||||
t.Errorf("stage.id = %s, want test_stage_001", stage.id)
|
||||
}
|
||||
if stage.maxPlayers != 4 {
|
||||
t.Errorf("stage.maxPlayers = %d, want 4 (default)", stage.maxPlayers)
|
||||
}
|
||||
if stage.objectIndex != 0 {
|
||||
t.Errorf("stage.objectIndex = %d, want 0", stage.objectIndex)
|
||||
}
|
||||
if stage.clients == nil {
|
||||
t.Error("stage.clients should be initialized")
|
||||
}
|
||||
if stage.reservedClientSlots == nil {
|
||||
t.Error("stage.reservedClientSlots should be initialized")
|
||||
}
|
||||
if stage.objects == nil {
|
||||
t.Error("stage.objects should be initialized")
|
||||
}
|
||||
if stage.rawBinaryData == nil {
|
||||
t.Error("stage.rawBinaryData should be initialized")
|
||||
}
|
||||
if stage.host != nil {
|
||||
t.Error("stage.host should be nil initially")
|
||||
}
|
||||
if stage.password != "" {
|
||||
t.Errorf("stage.password should be empty, got %s", stage.password)
|
||||
}
|
||||
}
|
||||
|
||||
func TestStageNextObjectID(t *testing.T) {
|
||||
stage := NewStage("test")
|
||||
|
||||
// First ID should be 1 (index 0 is skipped)
|
||||
id1 := stage.NextObjectID()
|
||||
if stage.objectIndex != 1 {
|
||||
t.Errorf("objectIndex after first call = %d, want 1", stage.objectIndex)
|
||||
}
|
||||
|
||||
// Get several IDs and ensure they increment
|
||||
id2 := stage.NextObjectID()
|
||||
id3 := stage.NextObjectID()
|
||||
|
||||
if id1 == id2 || id2 == id3 {
|
||||
t.Error("Object IDs should be unique")
|
||||
}
|
||||
}
|
||||
|
||||
func TestStageNextObjectID_WrapAround(t *testing.T) {
|
||||
stage := NewStage("test")
|
||||
stage.objectIndex = 125
|
||||
|
||||
// Get ID at 126
|
||||
stage.NextObjectID()
|
||||
if stage.objectIndex != 126 {
|
||||
t.Errorf("objectIndex = %d, want 126", stage.objectIndex)
|
||||
}
|
||||
|
||||
// Next should wrap to 1 (skipping 0 and 127)
|
||||
stage.NextObjectID()
|
||||
if stage.objectIndex != 1 {
|
||||
t.Errorf("objectIndex = %d, want 1 (wrapped around)", stage.objectIndex)
|
||||
}
|
||||
}
|
||||
|
||||
func TestStageIsQuest(t *testing.T) {
|
||||
stage := NewStage("test")
|
||||
|
||||
// Initially not a quest
|
||||
if stage.isQuest() {
|
||||
t.Error("New stage should not be a quest")
|
||||
}
|
||||
|
||||
// Add reserved slot
|
||||
stage.reservedClientSlots[100] = true
|
||||
|
||||
if !stage.isQuest() {
|
||||
t.Error("Stage with reserved slots should be a quest")
|
||||
}
|
||||
}
|
||||
|
||||
func TestStageReservedClientSlots(t *testing.T) {
|
||||
stage := NewStage("test")
|
||||
|
||||
// Reserve some slots
|
||||
stage.reservedClientSlots[100] = true
|
||||
stage.reservedClientSlots[200] = false // ready status doesn't matter for presence
|
||||
stage.reservedClientSlots[300] = true
|
||||
|
||||
if len(stage.reservedClientSlots) != 3 {
|
||||
t.Errorf("reservedClientSlots count = %d, want 3", len(stage.reservedClientSlots))
|
||||
}
|
||||
|
||||
// Check ready status
|
||||
if !stage.reservedClientSlots[100] {
|
||||
t.Error("charID 100 should be ready")
|
||||
}
|
||||
if stage.reservedClientSlots[200] {
|
||||
t.Error("charID 200 should not be ready")
|
||||
}
|
||||
}
|
||||
|
||||
func TestStageRawBinaryData(t *testing.T) {
|
||||
stage := NewStage("test")
|
||||
|
||||
key := stageBinaryKey{id0: 5, id1: 10}
|
||||
data := []byte{0xDE, 0xAD, 0xBE, 0xEF}
|
||||
|
||||
stage.rawBinaryData[key] = data
|
||||
|
||||
retrieved := stage.rawBinaryData[key]
|
||||
if len(retrieved) != 4 {
|
||||
t.Fatalf("retrieved data len = %d, want 4", len(retrieved))
|
||||
}
|
||||
if retrieved[0] != 0xDE || retrieved[3] != 0xEF {
|
||||
t.Error("retrieved data doesn't match stored data")
|
||||
}
|
||||
}
|
||||
|
||||
func TestStageObjects(t *testing.T) {
|
||||
stage := NewStage("test")
|
||||
|
||||
obj := &Object{
|
||||
id: stage.NextObjectID(),
|
||||
ownerCharID: 12345,
|
||||
x: 100.0,
|
||||
y: 200.0,
|
||||
z: 300.0,
|
||||
}
|
||||
|
||||
stage.objects[obj.id] = obj
|
||||
|
||||
if len(stage.objects) != 1 {
|
||||
t.Errorf("objects count = %d, want 1", len(stage.objects))
|
||||
}
|
||||
|
||||
retrieved := stage.objects[obj.id]
|
||||
if retrieved.ownerCharID != 12345 {
|
||||
t.Errorf("retrieved object ownerCharID = %d, want 12345", retrieved.ownerCharID)
|
||||
}
|
||||
}
|
||||
|
||||
func TestStageHost(t *testing.T) {
|
||||
server := createMockServer()
|
||||
stage := NewStage("test")
|
||||
|
||||
// Set host
|
||||
host := createMockSession(100, server)
|
||||
stage.host = host
|
||||
|
||||
if stage.host != host {
|
||||
t.Error("stage host not set correctly")
|
||||
}
|
||||
if stage.host.charID != 100 {
|
||||
t.Errorf("stage host charID = %d, want 100", stage.host.charID)
|
||||
}
|
||||
}
|
||||
|
||||
func TestStagePassword(t *testing.T) {
|
||||
stage := NewStage("test")
|
||||
|
||||
// Set password
|
||||
stage.password = "secret123"
|
||||
|
||||
if stage.password != "secret123" {
|
||||
t.Errorf("stage password = %s, want secret123", stage.password)
|
||||
}
|
||||
}
|
||||
|
||||
func TestStageMaxPlayers(t *testing.T) {
|
||||
stage := NewStage("test")
|
||||
|
||||
// Change max players
|
||||
stage.maxPlayers = 16
|
||||
|
||||
if stage.maxPlayers != 16 {
|
||||
t.Errorf("stage maxPlayers = %d, want 16", stage.maxPlayers)
|
||||
}
|
||||
}
|
||||
|
||||
func TestStageConcurrentClientAccess(t *testing.T) {
|
||||
server := createMockServer()
|
||||
stage := NewStage("test")
|
||||
|
||||
var wg sync.WaitGroup
|
||||
|
||||
// Concurrent client additions
|
||||
for i := 0; i < 10; i++ {
|
||||
wg.Add(1)
|
||||
go func(id int) {
|
||||
defer wg.Done()
|
||||
for j := 0; j < 10; j++ {
|
||||
session := createMockSession(uint32(id*100+j), server)
|
||||
stage.Lock()
|
||||
stage.clients[session] = session.charID
|
||||
stage.Unlock()
|
||||
|
||||
stage.Lock()
|
||||
delete(stage.clients, session)
|
||||
stage.Unlock()
|
||||
}
|
||||
}(i)
|
||||
}
|
||||
|
||||
// Concurrent reads
|
||||
for i := 0; i < 5; i++ {
|
||||
wg.Add(1)
|
||||
go func() {
|
||||
defer wg.Done()
|
||||
for j := 0; j < 50; j++ {
|
||||
stage.RLock()
|
||||
_ = len(stage.clients)
|
||||
stage.RUnlock()
|
||||
}
|
||||
}()
|
||||
}
|
||||
|
||||
wg.Wait()
|
||||
}
|
||||
|
||||
func TestStageBroadcastMHF_EmptyStage(t *testing.T) {
|
||||
stage := NewStage("test")
|
||||
pkt := &mockPacket{opcode: 0x1234}
|
||||
|
||||
// Should not panic with empty stage
|
||||
stage.BroadcastMHF(pkt, nil)
|
||||
}
|
||||
|
||||
func TestStageBroadcastMHF_SkipsNilClientContext(t *testing.T) {
|
||||
server := createMockServer()
|
||||
stage := NewStage("test")
|
||||
|
||||
session1 := createMockSession(1, server)
|
||||
session2 := createMockSession(2, server)
|
||||
session2.clientContext = nil // Nil context should be skipped
|
||||
|
||||
stage.clients[session1] = session1.charID
|
||||
stage.clients[session2] = session2.charID
|
||||
|
||||
pkt := &mockPacket{opcode: 0x1234}
|
||||
|
||||
// Should not panic
|
||||
stage.BroadcastMHF(pkt, nil)
|
||||
|
||||
// Only session1 should receive
|
||||
select {
|
||||
case <-session1.sendPackets:
|
||||
// Good
|
||||
default:
|
||||
t.Error("session1 should receive broadcast")
|
||||
}
|
||||
}
|
||||
Reference in New Issue
Block a user