mirror of
https://github.com/Mezeporta/Erupe.git
synced 2026-03-24 16:43:37 +01:00
test: add unit tests for core packages
Add comprehensive test coverage for: - common/token: token generation and RNG tests - common/stringsupport: string encoding, CSV operations - common/byteframe: binary read/write operations - common/mhfcourse: course/subscription logic - network/crypt_packet: packet header parsing - network/binpacket: binary packet round-trips - network/mhfpacket: packet interface and opcode mapping - config: configuration struct and loading - server/entranceserver: response building - server/signserver: response ID constants - server/signv2server: HTTP endpoint validation - server/channelserver: session, semaphore, and handler tests All tests pass with race detector enabled.
This commit is contained in:
384
server/channelserver/sys_semaphore_test.go
Normal file
384
server/channelserver/sys_semaphore_test.go
Normal file
@@ -0,0 +1,384 @@
|
||||
package channelserver
|
||||
|
||||
import (
|
||||
"sync"
|
||||
"testing"
|
||||
)
|
||||
|
||||
func TestNewSemaphore(t *testing.T) {
|
||||
server := createMockServer()
|
||||
server.semaphoreIndex = 6 // Start index (IDs 0-6 are reserved)
|
||||
|
||||
sema := NewSemaphore(server, "test_semaphore", 16)
|
||||
|
||||
if sema == nil {
|
||||
t.Fatal("NewSemaphore() returned nil")
|
||||
}
|
||||
if sema.id_semaphore != "test_semaphore" {
|
||||
t.Errorf("id_semaphore = %s, want test_semaphore", sema.id_semaphore)
|
||||
}
|
||||
if sema.maxPlayers != 16 {
|
||||
t.Errorf("maxPlayers = %d, want 16", sema.maxPlayers)
|
||||
}
|
||||
if sema.clients == nil {
|
||||
t.Error("clients map should be initialized")
|
||||
}
|
||||
if sema.reservedClientSlots == nil {
|
||||
t.Error("reservedClientSlots map should be initialized")
|
||||
}
|
||||
}
|
||||
|
||||
func TestNewSemaphoreIDIncrement(t *testing.T) {
|
||||
server := createMockServer()
|
||||
server.semaphoreIndex = 6
|
||||
|
||||
sema1 := NewSemaphore(server, "sema1", 4)
|
||||
sema2 := NewSemaphore(server, "sema2", 4)
|
||||
sema3 := NewSemaphore(server, "sema3", 4)
|
||||
|
||||
// IDs should increment
|
||||
if sema1.id == sema2.id {
|
||||
t.Error("semaphore IDs should be unique")
|
||||
}
|
||||
if sema2.id == sema3.id {
|
||||
t.Error("semaphore IDs should be unique")
|
||||
}
|
||||
}
|
||||
|
||||
func TestSemaphoreClients(t *testing.T) {
|
||||
server := createMockServer()
|
||||
sema := NewSemaphore(server, "test", 4)
|
||||
|
||||
session1 := createMockSession(100, server)
|
||||
session2 := createMockSession(200, server)
|
||||
|
||||
// Add clients
|
||||
sema.clients[session1] = session1.charID
|
||||
sema.clients[session2] = session2.charID
|
||||
|
||||
if len(sema.clients) != 2 {
|
||||
t.Errorf("clients count = %d, want 2", len(sema.clients))
|
||||
}
|
||||
|
||||
// Verify client IDs
|
||||
if sema.clients[session1] != 100 {
|
||||
t.Errorf("clients[session1] = %d, want 100", sema.clients[session1])
|
||||
}
|
||||
if sema.clients[session2] != 200 {
|
||||
t.Errorf("clients[session2] = %d, want 200", sema.clients[session2])
|
||||
}
|
||||
}
|
||||
|
||||
func TestSemaphoreReservedSlots(t *testing.T) {
|
||||
server := createMockServer()
|
||||
sema := NewSemaphore(server, "test", 4)
|
||||
|
||||
// Reserve slots
|
||||
sema.reservedClientSlots[100] = nil
|
||||
sema.reservedClientSlots[200] = nil
|
||||
|
||||
if len(sema.reservedClientSlots) != 2 {
|
||||
t.Errorf("reservedClientSlots count = %d, want 2", len(sema.reservedClientSlots))
|
||||
}
|
||||
|
||||
// Check existence
|
||||
if _, ok := sema.reservedClientSlots[100]; !ok {
|
||||
t.Error("charID 100 should be reserved")
|
||||
}
|
||||
if _, ok := sema.reservedClientSlots[200]; !ok {
|
||||
t.Error("charID 200 should be reserved")
|
||||
}
|
||||
if _, ok := sema.reservedClientSlots[300]; ok {
|
||||
t.Error("charID 300 should not be reserved")
|
||||
}
|
||||
}
|
||||
|
||||
func TestSemaphoreRemoveClient(t *testing.T) {
|
||||
server := createMockServer()
|
||||
sema := NewSemaphore(server, "test", 4)
|
||||
|
||||
session := createMockSession(100, server)
|
||||
sema.clients[session] = session.charID
|
||||
|
||||
// Remove client
|
||||
delete(sema.clients, session)
|
||||
|
||||
if len(sema.clients) != 0 {
|
||||
t.Errorf("clients count = %d, want 0 after delete", len(sema.clients))
|
||||
}
|
||||
}
|
||||
|
||||
func TestSemaphoreMaxPlayers(t *testing.T) {
|
||||
tests := []struct {
|
||||
name string
|
||||
maxPlayers uint16
|
||||
}{
|
||||
{"quest party", 4},
|
||||
{"small event", 16},
|
||||
{"raviente", 32},
|
||||
{"large event", 64},
|
||||
}
|
||||
|
||||
for _, tt := range tests {
|
||||
t.Run(tt.name, func(t *testing.T) {
|
||||
server := createMockServer()
|
||||
sema := NewSemaphore(server, tt.name, tt.maxPlayers)
|
||||
|
||||
if sema.maxPlayers != tt.maxPlayers {
|
||||
t.Errorf("maxPlayers = %d, want %d", sema.maxPlayers, tt.maxPlayers)
|
||||
}
|
||||
})
|
||||
}
|
||||
}
|
||||
|
||||
func TestSemaphoreBroadcastMHF(t *testing.T) {
|
||||
server := createMockServer()
|
||||
sema := NewSemaphore(server, "test", 4)
|
||||
|
||||
session1 := createMockSession(100, server)
|
||||
session2 := createMockSession(200, server)
|
||||
session3 := createMockSession(300, server)
|
||||
|
||||
sema.clients[session1] = session1.charID
|
||||
sema.clients[session2] = session2.charID
|
||||
sema.clients[session3] = session3.charID
|
||||
|
||||
pkt := &mockPacket{opcode: 0x1234}
|
||||
|
||||
// Broadcast excluding session1
|
||||
sema.BroadcastMHF(pkt, session1)
|
||||
|
||||
// session2 and session3 should receive
|
||||
select {
|
||||
case data := <-session2.sendPackets:
|
||||
if len(data.data) == 0 {
|
||||
t.Error("session2 received empty data")
|
||||
}
|
||||
default:
|
||||
t.Error("session2 did not receive broadcast")
|
||||
}
|
||||
|
||||
select {
|
||||
case data := <-session3.sendPackets:
|
||||
if len(data.data) == 0 {
|
||||
t.Error("session3 received empty data")
|
||||
}
|
||||
default:
|
||||
t.Error("session3 did not receive broadcast")
|
||||
}
|
||||
|
||||
// session1 should NOT receive (it was ignored)
|
||||
select {
|
||||
case <-session1.sendPackets:
|
||||
t.Error("session1 should not receive broadcast (it was ignored)")
|
||||
default:
|
||||
// Expected - no data for session1
|
||||
}
|
||||
}
|
||||
|
||||
func TestSemaphoreBroadcastRavi(t *testing.T) {
|
||||
server := createMockServer()
|
||||
sema := NewSemaphore(server, "raviente", 32)
|
||||
|
||||
session1 := createMockSession(100, server)
|
||||
session2 := createMockSession(200, server)
|
||||
|
||||
sema.clients[session1] = session1.charID
|
||||
sema.clients[session2] = session2.charID
|
||||
|
||||
pkt := &mockPacket{opcode: 0x5678}
|
||||
|
||||
// Broadcast to all (no ignored session)
|
||||
sema.BroadcastRavi(pkt)
|
||||
|
||||
// Both should receive
|
||||
select {
|
||||
case data := <-session1.sendPackets:
|
||||
if len(data.data) == 0 {
|
||||
t.Error("session1 received empty data")
|
||||
}
|
||||
default:
|
||||
t.Error("session1 did not receive Ravi broadcast")
|
||||
}
|
||||
|
||||
select {
|
||||
case data := <-session2.sendPackets:
|
||||
if len(data.data) == 0 {
|
||||
t.Error("session2 received empty data")
|
||||
}
|
||||
default:
|
||||
t.Error("session2 did not receive Ravi broadcast")
|
||||
}
|
||||
}
|
||||
|
||||
func TestSemaphoreBroadcastToAll(t *testing.T) {
|
||||
server := createMockServer()
|
||||
sema := NewSemaphore(server, "test", 4)
|
||||
|
||||
session1 := createMockSession(100, server)
|
||||
session2 := createMockSession(200, server)
|
||||
|
||||
sema.clients[session1] = session1.charID
|
||||
sema.clients[session2] = session2.charID
|
||||
|
||||
pkt := &mockPacket{opcode: 0x1234}
|
||||
|
||||
// Broadcast to all (nil ignored session)
|
||||
sema.BroadcastMHF(pkt, nil)
|
||||
|
||||
// Both should receive
|
||||
count := 0
|
||||
select {
|
||||
case <-session1.sendPackets:
|
||||
count++
|
||||
default:
|
||||
}
|
||||
select {
|
||||
case <-session2.sendPackets:
|
||||
count++
|
||||
default:
|
||||
}
|
||||
|
||||
if count != 2 {
|
||||
t.Errorf("expected 2 broadcasts, got %d", count)
|
||||
}
|
||||
}
|
||||
|
||||
func TestSemaphoreRWMutex(t *testing.T) {
|
||||
server := createMockServer()
|
||||
sema := NewSemaphore(server, "test", 4)
|
||||
|
||||
// Test that RWMutex works
|
||||
sema.RLock()
|
||||
_ = len(sema.clients) // Read operation
|
||||
sema.RUnlock()
|
||||
|
||||
sema.Lock()
|
||||
sema.clients[createMockSession(100, server)] = 100 // Write operation
|
||||
sema.Unlock()
|
||||
}
|
||||
|
||||
func TestSemaphoreConcurrentAccess(t *testing.T) {
|
||||
server := createMockServer()
|
||||
sema := NewSemaphore(server, "test", 100)
|
||||
|
||||
var wg sync.WaitGroup
|
||||
|
||||
// Concurrent writers
|
||||
for i := 0; i < 10; i++ {
|
||||
wg.Add(1)
|
||||
go func(id int) {
|
||||
defer wg.Done()
|
||||
for j := 0; j < 100; j++ {
|
||||
session := createMockSession(uint32(id*100+j), server)
|
||||
sema.Lock()
|
||||
sema.clients[session] = session.charID
|
||||
sema.Unlock()
|
||||
|
||||
sema.Lock()
|
||||
delete(sema.clients, session)
|
||||
sema.Unlock()
|
||||
}
|
||||
}(i)
|
||||
}
|
||||
|
||||
// Concurrent readers
|
||||
for i := 0; i < 5; i++ {
|
||||
wg.Add(1)
|
||||
go func() {
|
||||
defer wg.Done()
|
||||
for j := 0; j < 100; j++ {
|
||||
sema.RLock()
|
||||
_ = len(sema.clients)
|
||||
sema.RUnlock()
|
||||
}
|
||||
}()
|
||||
}
|
||||
|
||||
wg.Wait()
|
||||
}
|
||||
|
||||
func TestSemaphoreEmptyBroadcast(t *testing.T) {
|
||||
server := createMockServer()
|
||||
sema := NewSemaphore(server, "test", 4)
|
||||
|
||||
pkt := &mockPacket{opcode: 0x1234}
|
||||
|
||||
// Should not panic with no clients
|
||||
sema.BroadcastMHF(pkt, nil)
|
||||
sema.BroadcastRavi(pkt)
|
||||
}
|
||||
|
||||
func TestSemaphoreIDString(t *testing.T) {
|
||||
server := createMockServer()
|
||||
|
||||
tests := []string{
|
||||
"quest_001",
|
||||
"raviente_phase1",
|
||||
"tournament_round3",
|
||||
"diva_defense",
|
||||
}
|
||||
|
||||
for _, id := range tests {
|
||||
sema := NewSemaphore(server, id, 4)
|
||||
if sema.id_semaphore != id {
|
||||
t.Errorf("id_semaphore = %s, want %s", sema.id_semaphore, id)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
func TestSemaphoreNumericID(t *testing.T) {
|
||||
server := createMockServer()
|
||||
server.semaphoreIndex = 6 // IDs 0-6 reserved
|
||||
|
||||
sema := NewSemaphore(server, "test", 4)
|
||||
|
||||
// First semaphore should get ID 7
|
||||
if sema.id < 7 {
|
||||
t.Errorf("semaphore id = %d, should be >= 7", sema.id)
|
||||
}
|
||||
}
|
||||
|
||||
func TestSemaphoreReserveAndRelease(t *testing.T) {
|
||||
server := createMockServer()
|
||||
sema := NewSemaphore(server, "test", 4)
|
||||
|
||||
// Reserve
|
||||
sema.reservedClientSlots[100] = nil
|
||||
if _, ok := sema.reservedClientSlots[100]; !ok {
|
||||
t.Error("slot 100 should be reserved")
|
||||
}
|
||||
|
||||
// Release
|
||||
delete(sema.reservedClientSlots, 100)
|
||||
if _, ok := sema.reservedClientSlots[100]; ok {
|
||||
t.Error("slot 100 should be released")
|
||||
}
|
||||
}
|
||||
|
||||
func TestSemaphoreClientAndReservedSeparate(t *testing.T) {
|
||||
server := createMockServer()
|
||||
sema := NewSemaphore(server, "test", 4)
|
||||
|
||||
session := createMockSession(100, server)
|
||||
|
||||
// Client in active clients
|
||||
sema.clients[session] = 100
|
||||
|
||||
// Same charID reserved
|
||||
sema.reservedClientSlots[100] = nil
|
||||
|
||||
// Both should exist independently
|
||||
if _, ok := sema.clients[session]; !ok {
|
||||
t.Error("session should be in active clients")
|
||||
}
|
||||
if _, ok := sema.reservedClientSlots[100]; !ok {
|
||||
t.Error("charID 100 should be reserved")
|
||||
}
|
||||
|
||||
// Remove from one doesn't affect other
|
||||
delete(sema.clients, session)
|
||||
if _, ok := sema.reservedClientSlots[100]; !ok {
|
||||
t.Error("charID 100 should still be reserved after removing from clients")
|
||||
}
|
||||
}
|
||||
Reference in New Issue
Block a user