feat(go): upgrade from go 1.21 to 1.23

BREAKING CHANGE: will not work properly with Go 1.21.
This commit is contained in:
Houmgaor
2025-10-19 22:24:48 +02:00
parent cde7995132
commit f79e05c0c9
6 changed files with 724 additions and 7 deletions

View File

@@ -31,7 +31,7 @@ jobs:
- name: Set up Go
uses: actions/setup-go@v5
with:
go-version: '1.21'
go-version: '1.23'
- name: Download dependencies
run: go mod download
@@ -63,7 +63,7 @@ jobs:
- name: Set up Go
uses: actions/setup-go@v5
with:
go-version: '1.21'
go-version: '1.23'
- name: Download dependencies
run: go mod download
@@ -110,7 +110,7 @@ jobs:
- name: Set up Go
uses: actions/setup-go@v5
with:
go-version: '1.21'
go-version: '1.23'
- name: Run golangci-lint
uses: golangci/golangci-lint-action@v3

View File

@@ -22,7 +22,7 @@ jobs:
- name: Set up Go
uses: actions/setup-go@v5
with:
go-version: '1.21'
go-version: '1.23'
- name: Build Linux-amd64
run: env GOOS=linux GOARCH=amd64 go build -v

View File

@@ -25,6 +25,10 @@ and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0
- Bumped golang.org/x/net from 0.33.0 to 0.38.0
- Bumped golang.org/x/crypto from 0.31.0 to 0.35.0
## Removed
- Compatibility with Go 1.21 removed.
## [9.2.0] - 2023-04-01
### Added in 9.2.0

View File

@@ -4,7 +4,7 @@ import (
"bytes"
"encoding/hex"
"fmt"
"io/ioutil"
"os"
"testing"
"erupe-ce/server/channelserver/compression/nullcomp"
@@ -68,7 +68,7 @@ var tests = []struct {
}
func readTestDataFile(filename string) []byte {
data, err := ioutil.ReadFile(fmt.Sprintf("./test_data/%s", filename))
data, err := os.ReadFile(fmt.Sprintf("./test_data/%s", filename))
if err != nil {
panic(err)
}

View File

@@ -12,8 +12,8 @@ import (
"erupe-ce/network/binpacket"
"erupe-ce/network/mhfpacket"
"fmt"
"golang.org/x/exp/slices"
"math"
"slices"
"strconv"
"strings"
"time"

View File

@@ -0,0 +1,713 @@
package channelserver
import (
"net"
"slices"
"strings"
"testing"
"erupe-ce/common/byteframe"
"erupe-ce/common/mhfcourse"
_config "erupe-ce/config"
"erupe-ce/network/binpacket"
"erupe-ce/network/mhfpacket"
)
// TestSendServerChatMessage verifies that server chat messages are correctly formatted and queued
func TestSendServerChatMessage(t *testing.T) {
tests := []struct {
name string
message string
wantErr bool
}{
{
name: "simple_message",
message: "Hello, World!",
wantErr: false,
},
{
name: "empty_message",
message: "",
wantErr: false,
},
{
name: "special_characters",
message: "Test @#$%^&*()",
wantErr: false,
},
{
name: "unicode_message",
message: "テスト メッセージ",
wantErr: false,
},
{
name: "long_message",
message: strings.Repeat("A", 1000),
wantErr: false,
},
}
for _, tt := range tests {
t.Run(tt.name, func(t *testing.T) {
mock := &MockCryptConn{sentPackets: make([][]byte, 0)}
s := createTestSession(mock)
// Send the chat message
sendServerChatMessage(s, tt.message)
// Verify the message was queued
if len(s.sendPackets) == 0 {
t.Error("no packets were queued")
return
}
// Read from the channel with timeout to avoid hanging
select {
case pkt := <-s.sendPackets:
if pkt.data == nil {
t.Error("packet data is nil")
}
// Verify it's an MHFPacket (contains opcode)
if len(pkt.data) < 2 {
t.Error("packet too short to contain opcode")
}
default:
t.Error("no packet available in channel")
}
})
}
}
// TestHandleMsgSysCastBinary_SimpleData verifies basic data message handling
func TestHandleMsgSysCastBinary_SimpleData(t *testing.T) {
mock := &MockCryptConn{sentPackets: make([][]byte, 0)}
s := createTestSession(mock)
s.charID = 54321
s.stage = NewStage("test_stage")
s.stage.clients[s] = s.charID
s.server.sessions = make(map[net.Conn]*Session)
// Create a data message payload
bf := byteframe.NewByteFrame()
bf.SetLE()
bf.WriteUint32(0xDEADBEEF)
pkt := &mhfpacket.MsgSysCastBinary{
Unk: 0,
BroadcastType: BroadcastTypeStage,
MessageType: BinaryMessageTypeData,
RawDataPayload: bf.Data(),
}
// Should not panic
handleMsgSysCastBinary(s, pkt)
}
// TestHandleMsgSysCastBinary_DiceCommand verifies the @dice command
func TestHandleMsgSysCastBinary_DiceCommand(t *testing.T) {
mock := &MockCryptConn{sentPackets: make([][]byte, 0)}
s := createTestSession(mock)
s.charID = 99999
s.stage = NewStage("test_stage")
s.stage.clients[s] = s.charID
s.server.sessions = make(map[net.Conn]*Session)
// Build a chat message with @dice command
bf := byteframe.NewByteFrame()
bf.SetLE()
msg := &binpacket.MsgBinChat{
Unk0: 0,
Type: 5,
Flags: 0x80,
Message: "@dice",
SenderName: "TestPlayer",
}
msg.Build(bf)
pkt := &mhfpacket.MsgSysCastBinary{
Unk: 0,
BroadcastType: BroadcastTypeStage,
MessageType: BinaryMessageTypeChat,
RawDataPayload: bf.Data(),
}
// Should execute dice command and return
handleMsgSysCastBinary(s, pkt)
// Verify a response was queued (dice result)
if len(s.sendPackets) == 0 {
t.Error("dice command did not queue a response")
}
}
// TestBroadcastTypes verifies different broadcast types are handled
func TestBroadcastTypes(t *testing.T) {
tests := []struct {
name string
broadcastType uint8
buildPayload func() []byte
}{
{
name: "broadcast_targeted",
broadcastType: BroadcastTypeTargeted,
buildPayload: func() []byte {
bf := byteframe.NewByteFrame()
bf.SetBE() // Targeted uses BE
msg := &binpacket.MsgBinTargeted{
TargetCharIDs: []uint32{1, 2, 3},
RawDataPayload: []byte{0xDE, 0xAD, 0xBE, 0xEF},
}
msg.Build(bf)
return bf.Data()
},
},
{
name: "broadcast_stage",
broadcastType: BroadcastTypeStage,
buildPayload: func() []byte {
bf := byteframe.NewByteFrame()
bf.SetLE()
bf.WriteUint32(0x12345678)
return bf.Data()
},
},
{
name: "broadcast_server",
broadcastType: BroadcastTypeServer,
buildPayload: func() []byte {
bf := byteframe.NewByteFrame()
bf.SetLE()
bf.WriteUint32(0x12345678)
return bf.Data()
},
},
{
name: "broadcast_world",
broadcastType: BroadcastTypeWorld,
buildPayload: func() []byte {
bf := byteframe.NewByteFrame()
bf.SetLE()
bf.WriteUint32(0x12345678)
return bf.Data()
},
},
}
for _, tt := range tests {
t.Run(tt.name, func(t *testing.T) {
mock := &MockCryptConn{sentPackets: make([][]byte, 0)}
s := createTestSession(mock)
s.charID = 22222
s.stage = NewStage("test_stage")
s.stage.clients[s] = s.charID
s.server.sessions = make(map[net.Conn]*Session)
pkt := &mhfpacket.MsgSysCastBinary{
Unk: 0,
BroadcastType: tt.broadcastType,
MessageType: BinaryMessageTypeState,
RawDataPayload: tt.buildPayload(),
}
// Should handle without panic
handleMsgSysCastBinary(s, pkt)
})
}
}
// TestBinaryMessageTypes verifies different message types are handled
func TestBinaryMessageTypes(t *testing.T) {
tests := []struct {
name string
messageType uint8
buildPayload func() []byte
}{
{
name: "msg_type_state",
messageType: BinaryMessageTypeState,
buildPayload: func() []byte {
bf := byteframe.NewByteFrame()
bf.SetLE()
bf.WriteUint32(0xDEADBEEF)
return bf.Data()
},
},
{
name: "msg_type_chat",
messageType: BinaryMessageTypeChat,
buildPayload: func() []byte {
bf := byteframe.NewByteFrame()
bf.SetLE()
msg := &binpacket.MsgBinChat{
Unk0: 0,
Type: 5,
Flags: 0x80,
Message: "test",
SenderName: "Player",
}
msg.Build(bf)
return bf.Data()
},
},
{
name: "msg_type_quest",
messageType: BinaryMessageTypeQuest,
buildPayload: func() []byte {
bf := byteframe.NewByteFrame()
bf.SetLE()
bf.WriteUint32(0xDEADBEEF)
return bf.Data()
},
},
{
name: "msg_type_data",
messageType: BinaryMessageTypeData,
buildPayload: func() []byte {
bf := byteframe.NewByteFrame()
bf.SetLE()
bf.WriteUint32(0xDEADBEEF)
return bf.Data()
},
},
{
name: "msg_type_mail_notify",
messageType: BinaryMessageTypeMailNotify,
buildPayload: func() []byte {
bf := byteframe.NewByteFrame()
bf.SetLE()
bf.WriteUint32(0xDEADBEEF)
return bf.Data()
},
},
{
name: "msg_type_emote",
messageType: BinaryMessageTypeEmote,
buildPayload: func() []byte {
bf := byteframe.NewByteFrame()
bf.SetLE()
bf.WriteUint32(0xDEADBEEF)
return bf.Data()
},
},
}
for _, tt := range tests {
t.Run(tt.name, func(t *testing.T) {
mock := &MockCryptConn{sentPackets: make([][]byte, 0)}
s := createTestSession(mock)
s.charID = 33333
s.stage = NewStage("test_stage")
s.stage.clients[s] = s.charID
s.server.sessions = make(map[net.Conn]*Session)
pkt := &mhfpacket.MsgSysCastBinary{
Unk: 0,
BroadcastType: BroadcastTypeStage,
MessageType: tt.messageType,
RawDataPayload: tt.buildPayload(),
}
// Should handle without panic
handleMsgSysCastBinary(s, pkt)
})
}
}
// TestSlicesContainsUsage verifies the slices.Contains function works correctly
func TestSlicesContainsUsage(t *testing.T) {
tests := []struct {
name string
items []_config.Course
target _config.Course
expected bool
}{
{
name: "item_exists",
items: []_config.Course{
{Name: "Course1", Enabled: true},
{Name: "Course2", Enabled: false},
},
target: _config.Course{Name: "Course1", Enabled: true},
expected: true,
},
{
name: "item_not_found",
items: []_config.Course{
{Name: "Course1", Enabled: true},
{Name: "Course2", Enabled: false},
},
target: _config.Course{Name: "Course3", Enabled: true},
expected: false,
},
{
name: "empty_slice",
items: []_config.Course{},
target: _config.Course{Name: "Course1", Enabled: true},
expected: false,
},
{
name: "enabled_mismatch",
items: []_config.Course{
{Name: "Course1", Enabled: true},
},
target: _config.Course{Name: "Course1", Enabled: false},
expected: false,
},
}
for _, tt := range tests {
t.Run(tt.name, func(t *testing.T) {
result := slices.Contains(tt.items, tt.target)
if result != tt.expected {
t.Errorf("slices.Contains() = %v, want %v", result, tt.expected)
}
})
}
}
// TestSlicesIndexFuncUsage verifies the slices.IndexFunc function works correctly
func TestSlicesIndexFuncUsage(t *testing.T) {
tests := []struct {
name string
courses []mhfcourse.Course
predicate func(mhfcourse.Course) bool
expected int
}{
{
name: "empty_slice",
courses: []mhfcourse.Course{},
predicate: func(c mhfcourse.Course) bool {
return true
},
expected: -1,
},
}
for _, tt := range tests {
t.Run(tt.name, func(t *testing.T) {
result := slices.IndexFunc(tt.courses, tt.predicate)
if result != tt.expected {
t.Errorf("slices.IndexFunc() = %d, want %d", result, tt.expected)
}
})
}
}
// TestChatMessageParsing verifies chat message extraction from binary payload
func TestChatMessageParsing(t *testing.T) {
tests := []struct {
name string
messageContent string
authorName string
}{
{
name: "standard_message",
messageContent: "Hello World",
authorName: "Player123",
},
{
name: "special_chars_message",
messageContent: "Test@#$%^&*()",
authorName: "SpecialUser",
},
{
name: "empty_message",
messageContent: "",
authorName: "Silent",
},
}
for _, tt := range tests {
t.Run(tt.name, func(t *testing.T) {
// Build a binary chat message
bf := byteframe.NewByteFrame()
bf.SetLE()
msg := &binpacket.MsgBinChat{
Unk0: 0,
Type: 5,
Flags: 0x80,
Message: tt.messageContent,
SenderName: tt.authorName,
}
msg.Build(bf)
// Parse it back
parseBf := byteframe.NewByteFrameFromBytes(bf.Data())
parseBf.SetLE()
parseBf.Seek(8, 0) // Skip initial bytes
message := string(parseBf.ReadNullTerminatedBytes())
author := string(parseBf.ReadNullTerminatedBytes())
if message != tt.messageContent {
t.Errorf("message mismatch: got %q, want %q", message, tt.messageContent)
}
if author != tt.authorName {
t.Errorf("author mismatch: got %q, want %q", author, tt.authorName)
}
})
}
}
// TestBinaryMessageTypeEnums verifies message type constants
func TestBinaryMessageTypeEnums(t *testing.T) {
tests := []struct {
name string
typeVal uint8
typeID uint8
}{
{
name: "state_type",
typeVal: BinaryMessageTypeState,
typeID: 0,
},
{
name: "chat_type",
typeVal: BinaryMessageTypeChat,
typeID: 1,
},
{
name: "quest_type",
typeVal: BinaryMessageTypeQuest,
typeID: 2,
},
{
name: "data_type",
typeVal: BinaryMessageTypeData,
typeID: 3,
},
{
name: "mail_notify_type",
typeVal: BinaryMessageTypeMailNotify,
typeID: 4,
},
{
name: "emote_type",
typeVal: BinaryMessageTypeEmote,
typeID: 6,
},
}
for _, tt := range tests {
t.Run(tt.name, func(t *testing.T) {
if tt.typeVal != tt.typeID {
t.Errorf("type mismatch: got %d, want %d", tt.typeVal, tt.typeID)
}
})
}
}
// TestBroadcastTypeEnums verifies broadcast type constants
func TestBroadcastTypeEnums(t *testing.T) {
tests := []struct {
name string
typeVal uint8
typeID uint8
}{
{
name: "targeted_type",
typeVal: BroadcastTypeTargeted,
typeID: 0x01,
},
{
name: "stage_type",
typeVal: BroadcastTypeStage,
typeID: 0x03,
},
{
name: "server_type",
typeVal: BroadcastTypeServer,
typeID: 0x06,
},
{
name: "world_type",
typeVal: BroadcastTypeWorld,
typeID: 0x0a,
},
}
for _, tt := range tests {
t.Run(tt.name, func(t *testing.T) {
if tt.typeVal != tt.typeID {
t.Errorf("type mismatch: got %d, want %d", tt.typeVal, tt.typeID)
}
})
}
}
// TestPayloadHandling verifies raw payload handling in different scenarios
func TestPayloadHandling(t *testing.T) {
tests := []struct {
name string
payloadSize int
broadcastType uint8
messageType uint8
}{
{
name: "empty_payload",
payloadSize: 0,
broadcastType: BroadcastTypeStage,
messageType: BinaryMessageTypeData,
},
{
name: "small_payload",
payloadSize: 4,
broadcastType: BroadcastTypeStage,
messageType: BinaryMessageTypeData,
},
{
name: "large_payload",
payloadSize: 10000,
broadcastType: BroadcastTypeStage,
messageType: BinaryMessageTypeData,
},
}
for _, tt := range tests {
t.Run(tt.name, func(t *testing.T) {
mock := &MockCryptConn{sentPackets: make([][]byte, 0)}
s := createTestSession(mock)
s.charID = 44444
s.stage = NewStage("test_stage")
s.stage.clients[s] = s.charID
s.server.sessions = make(map[net.Conn]*Session)
// Create payload of specified size
payload := make([]byte, tt.payloadSize)
for i := 0; i < len(payload); i++ {
payload[i] = byte(i % 256)
}
pkt := &mhfpacket.MsgSysCastBinary{
Unk: 0,
BroadcastType: tt.broadcastType,
MessageType: tt.messageType,
RawDataPayload: payload,
}
// Should handle without panic
handleMsgSysCastBinary(s, pkt)
})
}
}
// TestCastedBinaryPacketConstruction verifies correct packet construction
func TestCastedBinaryPacketConstruction(t *testing.T) {
mock := &MockCryptConn{sentPackets: make([][]byte, 0)}
s := createTestSession(mock)
s.charID = 77777
message := "Test message"
sendServerChatMessage(s, message)
// Verify a packet was queued
if len(s.sendPackets) == 0 {
t.Fatal("no packets queued")
}
// Extract packet from channel
pkt := <-s.sendPackets
if pkt.data == nil {
t.Error("packet data is nil")
}
// The packet should be at least a valid MHF packet with opcode
if len(pkt.data) < 2 {
t.Error("packet too short")
}
}
// TestNilPayloadHandling verifies safe handling of nil payloads
func TestNilPayloadHandling(t *testing.T) {
mock := &MockCryptConn{sentPackets: make([][]byte, 0)}
s := createTestSession(mock)
s.charID = 55555
s.stage = NewStage("test_stage")
s.stage.clients[s] = s.charID
s.server.sessions = make(map[net.Conn]*Session)
pkt := &mhfpacket.MsgSysCastBinary{
Unk: 0,
BroadcastType: BroadcastTypeStage,
MessageType: BinaryMessageTypeData,
RawDataPayload: nil,
}
// Should handle nil payload without panic
handleMsgSysCastBinary(s, pkt)
}
// BenchmarkSendServerChatMessage benchmarks the chat message sending
func BenchmarkSendServerChatMessage(b *testing.B) {
mock := &MockCryptConn{sentPackets: make([][]byte, 0)}
s := createTestSession(mock)
message := "This is a benchmark message"
b.ResetTimer()
for i := 0; i < b.N; i++ {
sendServerChatMessage(s, message)
}
}
// BenchmarkHandleMsgSysCastBinary benchmarks the packet handling
func BenchmarkHandleMsgSysCastBinary(b *testing.B) {
mock := &MockCryptConn{sentPackets: make([][]byte, 0)}
s := createTestSession(mock)
s.charID = 99999
s.stage = NewStage("test_stage")
s.stage.clients[s] = s.charID
s.server.sessions = make(map[net.Conn]*Session)
// Prepare packet
bf := byteframe.NewByteFrame()
bf.SetLE()
bf.WriteUint32(0x12345678)
pkt := &mhfpacket.MsgSysCastBinary{
Unk: 0,
BroadcastType: BroadcastTypeStage,
MessageType: BinaryMessageTypeData,
RawDataPayload: bf.Data(),
}
b.ResetTimer()
for i := 0; i < b.N; i++ {
handleMsgSysCastBinary(s, pkt)
}
}
// BenchmarkSlicesContains benchmarks the slices.Contains function
func BenchmarkSlicesContains(b *testing.B) {
courses := []_config.Course{
{Name: "Course1", Enabled: true},
{Name: "Course2", Enabled: false},
{Name: "Course3", Enabled: true},
{Name: "Course4", Enabled: false},
{Name: "Course5", Enabled: true},
}
target := _config.Course{Name: "Course3", Enabled: true}
b.ResetTimer()
for i := 0; i < b.N; i++ {
slices.Contains(courses, target)
}
}
// BenchmarkSlicesIndexFunc benchmarks the slices.IndexFunc function
func BenchmarkSlicesIndexFunc(b *testing.B) {
// Create mock courses (empty as real data not needed for benchmark)
courses := make([]mhfcourse.Course, 100)
predicate := func(c mhfcourse.Course) bool {
return false // Worst case - always iterate to end
}
b.ResetTimer()
for i := 0; i < b.N; i++ {
slices.IndexFunc(courses, predicate)
}
}