eq2go/internal/packets/parser/parser_test.go

658 lines
16 KiB
Go

package parser
import (
"eq2emu/internal/common"
"testing"
)
func TestBasicParsing(t *testing.T) {
pml := `<packet name="Test">
<version number="1">
<i32 name="player_id">
<str16 name="player_name">
<color name="skin_color">
</version>
</packet>`
packets, err := Parse(pml)
if err != nil {
t.Fatalf("Parse failed: %v", err)
}
packet := packets["Test"]
if packet == nil {
t.Fatal("Test packet not found")
}
// Check fields
if len(packet.Fields) != 3 {
t.Errorf("Expected 3 fields, got %d", len(packet.Fields))
}
if packet.Fields["player_id"].Type != common.TypeInt32 {
t.Error("player_id should be TypeInt32")
}
if packet.Fields["player_name"].Type != common.TypeString16 {
t.Error("player_name should be TypeString16")
}
if packet.Fields["skin_color"].Type != common.TypeColor {
t.Error("skin_color should be TypeColor")
}
// Check order
order := packet.Orders[1]
expected := []string{"player_id", "player_name", "skin_color"}
if !equalSlices(order, expected) {
t.Errorf("Expected order %v, got %v", expected, order)
}
}
func TestFloat64Support(t *testing.T) {
pml := `<packet name="FloatTest">
<version number="1">
<f32 name="position_x">
<f64 name="precise_value">
<double name="legacy_double">
</version>
</packet>`
packets, err := Parse(pml)
if err != nil {
t.Fatalf("Parse failed: %v", err)
}
packet := packets["FloatTest"]
if packet.Fields["position_x"].Type != common.TypeFloat {
t.Error("position_x should be TypeFloat")
}
if packet.Fields["precise_value"].Type != common.TypeDouble {
t.Error("precise_value should be TypeDouble")
}
if packet.Fields["legacy_double"].Type != common.TypeDouble {
t.Error("legacy_double should be TypeDouble")
}
}
func TestOversizedFields(t *testing.T) {
pml := `<packet name="OversizedTest">
<version number="1">
<i8 name="small_count">
<i16 name="num_words" oversized="255">
<i32 name="large_value" oversized="65535">
</version>
</packet>`
packets, err := Parse(pml)
if err != nil {
t.Fatalf("Parse failed: %v", err)
}
packet := packets["OversizedTest"]
if packet.Fields["small_count"].Oversized != 0 {
t.Error("small_count should not be oversized")
}
if packet.Fields["num_words"].Oversized != 255 {
t.Errorf("num_words oversized should be 255, got %d", packet.Fields["num_words"].Oversized)
}
if packet.Fields["large_value"].Oversized != 65535 {
t.Errorf("large_value oversized should be 65535, got %d", packet.Fields["large_value"].Oversized)
}
}
func TestType2Support(t *testing.T) {
pml := `<packet name="Type2Test">
<version number="1">
<i8 name="stat_type">
<i32 name="stat_value" type2="f32" type2_if="stat_type!=6">
<i16 name="another_field" type2="i32">
</version>
</packet>`
packets, err := Parse(pml)
if err != nil {
t.Fatalf("Parse failed: %v", err)
}
packet := packets["Type2Test"]
statValue := packet.Fields["stat_value"]
if statValue.Type != common.TypeInt32 {
t.Error("stat_value primary type should be TypeInt32")
}
if statValue.Type2 != common.TypeFloat {
t.Error("stat_value type2 should be TypeFloat")
}
if statValue.Type2Cond != "stat_type!=6" {
t.Errorf("Expected type2_if 'stat_type!=6', got '%s'", statValue.Type2Cond)
}
anotherField := packet.Fields["another_field"]
if anotherField.Type2 != common.TypeInt32 {
t.Error("another_field type2 should be TypeInt32")
}
if anotherField.Type2Cond != "" {
t.Error("another_field should have empty type2_if")
}
}
func TestAdvancedFieldAttributes(t *testing.T) {
pml := `<packet name="AttributeTest">
<version number="1">
<i8 name="data_array" size="10" default="5">
<str16 name="optional_text" if="var:has_text" optional="true">
<i32 name="hidden_field" add_to_struct="false" add_type="i16">
</version>
</packet>`
packets, err := Parse(pml)
if err != nil {
t.Fatalf("Parse failed: %v", err)
}
packet := packets["AttributeTest"]
dataArray := packet.Fields["data_array"]
if dataArray.Length != 10 {
t.Errorf("Expected size 10, got %d", dataArray.Length)
}
if dataArray.DefaultValue != 5 {
t.Errorf("Expected default 5, got %d", dataArray.DefaultValue)
}
optionalText := packet.Fields["optional_text"]
if optionalText.Condition != "var:has_text" {
t.Errorf("Expected condition 'var:has_text', got '%s'", optionalText.Condition)
}
if !optionalText.Optional {
t.Error("optional_text should be optional")
}
hiddenField := packet.Fields["hidden_field"]
if hiddenField.AddToStruct {
t.Error("hidden_field should not be added to struct")
}
if hiddenField.AddType != common.TypeInt16 {
t.Error("hidden_field add_type should be TypeInt16")
}
}
func TestArrayMaxSize(t *testing.T) {
pml := `<packet name="ArrayMaxTest">
<version number="1">
<i8 name="item_count">
<array name="items" count="var:item_count" max_size="100">
<substruct>
<i32 name="item_id">
</substruct>
</array>
</version>
</packet>`
packets, err := Parse(pml)
if err != nil {
t.Fatalf("Parse failed: %v", err)
}
packet := packets["ArrayMaxTest"]
itemsField := packet.Fields["items"]
if itemsField.MaxArraySize != 100 {
t.Errorf("Expected max_size 100, got %d", itemsField.MaxArraySize)
}
}
func TestArrayOptionalAttributes(t *testing.T) {
pml := `<packet name="ArrayOptionalTest">
<version number="1">
<i8 name="count">
<array name="optional_items" count="var:count" optional="true" add_to_struct="false">
<substruct>
<i32 name="id">
</substruct>
</array>
</version>
</packet>`
packets, err := Parse(pml)
if err != nil {
t.Fatalf("Parse failed: %v", err)
}
packet := packets["ArrayOptionalTest"]
itemsField := packet.Fields["optional_items"]
if !itemsField.Optional {
t.Error("optional_items should be optional")
}
if itemsField.AddToStruct {
t.Error("optional_items should not be added to struct")
}
}
func TestMultipleVersions(t *testing.T) {
pml := `<packet name="MultiVersion">
<version number="1">
<i32 name="id">
<str16 name="name">
</version>
<version number="562">
<i32 name="id">
<str16 name="name">
<color name="color">
</version>
</packet>`
packets, err := Parse(pml)
if err != nil {
t.Fatalf("Parse failed: %v", err)
}
packet := packets["MultiVersion"]
if packet == nil {
t.Fatal("MultiVersion packet not found")
}
// Check both versions exist
if len(packet.Orders) != 2 {
t.Errorf("Expected 2 versions, got %d", len(packet.Orders))
}
v1Order := packet.Orders[1]
v562Order := packet.Orders[562]
if len(v1Order) != 2 {
t.Errorf("Version 1 should have 2 fields, got %d", len(v1Order))
}
if len(v562Order) != 3 {
t.Errorf("Version 562 should have 3 fields, got %d", len(v562Order))
}
}
func TestArrayParsing(t *testing.T) {
pml := `<packet name="ArrayTest">
<version number="1">
<i8 name="item_count">
<array name="items" count="var:item_count">
<substruct>
<i32 name="item_id">
<str16 name="item_name">
</substruct>
</array>
</version>
</packet>`
packets, err := Parse(pml)
if err != nil {
t.Fatalf("Parse failed: %v", err)
}
packet := packets["ArrayTest"]
itemsField := packet.Fields["items"]
if itemsField.Type != common.TypeArray {
t.Error("items should be TypeArray")
}
if itemsField.Condition != "var:item_count" {
t.Errorf("Expected condition 'var:item_count', got '%s'", itemsField.Condition)
}
if itemsField.SubDef == nil {
t.Fatal("SubDef should not be nil")
}
// Check substruct fields
if len(itemsField.SubDef.Fields) != 2 {
t.Errorf("Expected 2 substruct fields, got %d", len(itemsField.SubDef.Fields))
}
if itemsField.SubDef.Fields["item_id"].Type != common.TypeInt32 {
t.Error("item_id should be TypeInt32")
}
}
func TestConditionalParsing(t *testing.T) {
pml := `<packet name="ConditionalTest">
<version number="1">
<i32 name="player_id">
<str16 name="guild_name" if="flag:has_guild">
<i8 name="enhancement" if="item_type!=0">
<color name="aura" if="special_flags&0x01">
</version>
</packet>`
packets, err := Parse(pml)
if err != nil {
t.Fatalf("Parse failed: %v", err)
}
packet := packets["ConditionalTest"]
if packet.Fields["guild_name"].Condition != "flag:has_guild" {
t.Errorf("guild_name condition wrong: %s", packet.Fields["guild_name"].Condition)
}
if packet.Fields["enhancement"].Condition != "item_type!=0" {
t.Errorf("enhancement condition wrong: %s", packet.Fields["enhancement"].Condition)
}
if packet.Fields["aura"].Condition != "special_flags&0x01" {
t.Errorf("aura condition wrong: %s", packet.Fields["aura"].Condition)
}
}
func TestCommaFieldNames(t *testing.T) {
pml := `<packet name="CommaTest">
<version number="1">
<i32 name="player_id,account_id">
<f32 name="pos_x,pos_y,pos_z">
</version>
</packet>`
packets, err := Parse(pml)
if err != nil {
t.Fatalf("Parse failed: %v", err)
}
packet := packets["CommaTest"]
expectedFields := []string{"player_id", "account_id", "pos_x", "pos_y", "pos_z"}
if len(packet.Fields) != len(expectedFields) {
t.Errorf("Expected %d fields, got %d", len(expectedFields), len(packet.Fields))
}
for _, field := range expectedFields {
if _, exists := packet.Fields[field]; !exists {
t.Errorf("Field %s not found", field)
}
}
}
func TestSubstructReference(t *testing.T) {
pml := `<substruct name="ItemInfo">
<i32 name="item_id">
<str16 name="item_name">
</substruct>
<packet name="SubstructTest">
<version number="1">
<i8 name="count">
<array name="items" count="var:count" substruct="ItemInfo">
</version>
</packet>`
parser := NewParser(pml)
packets, err := parser.Parse()
if err != nil {
t.Fatalf("Parse failed: %v", err)
}
packet := packets["SubstructTest"]
itemsField := packet.Fields["items"]
if itemsField.SubDef == nil {
t.Fatal("SubDef should not be nil for referenced substruct")
}
if len(itemsField.SubDef.Fields) != 2 {
t.Errorf("Expected 2 substruct fields, got %d", len(itemsField.SubDef.Fields))
}
}
func TestFieldAttributes(t *testing.T) {
pml := `<packet name="AttributeTest">
<version number="1">
<i8 name="data_array" size="10">
<str16 name="optional_text" if="var:has_text">
</version>
</packet>`
packets, err := Parse(pml)
if err != nil {
t.Fatalf("Parse failed: %v", err)
}
packet := packets["AttributeTest"]
if packet.Fields["data_array"].Length != 10 {
t.Errorf("Expected size 10, got %d", packet.Fields["data_array"].Length)
}
if packet.Fields["optional_text"].Condition != "var:has_text" {
t.Errorf("Expected condition 'var:has_text', got '%s'", packet.Fields["optional_text"].Condition)
}
}
func TestComments(t *testing.T) {
pml := `<!-- This is a comment -->
<packet name="CommentTest">
<!-- Another comment -->
<version number="1">
<i32 name="id"> <!-- Inline comment -->
<str16 name="name">
</version>
</packet>`
packets, err := Parse(pml)
if err != nil {
t.Fatalf("Parse failed: %v", err)
}
packet := packets["CommentTest"]
if len(packet.Fields) != 2 {
t.Errorf("Comments should not affect parsing, expected 2 fields, got %d", len(packet.Fields))
}
}
func TestBinaryParsingFloat64(t *testing.T) {
// Test binary parsing with float64
pml := `<packet name="BinaryFloat64">
<version number="1">
<f64 name="precise_value">
</version>
</packet>`
packets, err := Parse(pml)
if err != nil {
t.Fatalf("Parse failed: %v", err)
}
// Create test data: 8 bytes representing float64 value 123.456
testData := []byte{0x77, 0xbe, 0x9f, 0x1a, 0x2f, 0xdd, 0x5e, 0x40} // 123.456 in little-endian
result, err := packets["BinaryFloat64"].Parse(testData, 1, 0)
if err != nil {
t.Fatalf("Binary parse failed: %v", err)
}
if val, ok := result["precise_value"].(float64); !ok {
t.Error("precise_value should be float64")
} else if val < 123.0 || val > 124.0 { // Rough check
t.Errorf("Expected value around 123.456, got %f", val)
}
}
func TestBinaryParsingOversized(t *testing.T) {
// Test oversized field parsing
pml := `<packet name="BinaryOversized">
<version number="1">
<i16 name="normal_value">
<i16 name="oversized_value" oversized="255">
</version>
</packet>`
packets, err := Parse(pml)
if err != nil {
t.Fatalf("Parse failed: %v", err)
}
// Test data: normal 16-bit value (100), then oversized marker (255) + 16-bit value (1000)
testData := []byte{0x64, 0x00, 0xff, 0xe8, 0x03} // 100, 255, 1000
result, err := packets["BinaryOversized"].Parse(testData, 1, 0)
if err != nil {
t.Fatalf("Binary parse failed: %v", err)
}
if val := result["normal_value"].(uint16); val != 100 {
t.Errorf("Expected normal_value 100, got %d", val)
}
if val := result["oversized_value"].(uint16); val != 1000 {
t.Errorf("Expected oversized_value 1000, got %d", val)
}
}
func TestBinaryParsingType2(t *testing.T) {
// Test type2 switching in binary parsing
pml := `<packet name="BinaryType2">
<version number="1">
<i8 name="stat_type">
<i32 name="stat_value" type2="f32" type2_if="stat_type==6">
</version>
</packet>`
packets, err := Parse(pml)
if err != nil {
t.Fatalf("Parse failed: %v", err)
}
// Test with stat_type = 6 (should use float)
testData1 := []byte{0x06, 0x00, 0x00, 0x20, 0x41} // stat_type=6, float 10.0
result1, err := packets["BinaryType2"].Parse(testData1, 1, 0)
if err != nil {
t.Fatalf("Binary parse failed: %v", err)
}
if statType := result1["stat_type"].(uint8); statType != 6 {
t.Errorf("Expected stat_type 6, got %d", statType)
}
// Note: The actual type switching logic depends on conditions.go implementation
// This test verifies the parsing structure is correct
}
func TestBinaryParsingArrayMaxSize(t *testing.T) {
// Test array max size limit
pml := `<packet name="BinaryArrayMax">
<version number="1">
<i8 name="item_count">
<array name="items" count="var:item_count" max_size="2">
<substruct>
<i16 name="item_id">
</substruct>
</array>
</version>
</packet>`
packets, err := Parse(pml)
if err != nil {
t.Fatalf("Parse failed: %v", err)
}
// Test data: count=5, but max_size=2 should limit to 2 items
testData := []byte{0x05, 0x01, 0x00, 0x02, 0x00} // count=5, item1=1, item2=2
result, err := packets["BinaryArrayMax"].Parse(testData, 1, 0)
if err != nil {
t.Fatalf("Binary parse failed: %v", err)
}
items := result["items"].([]map[string]any)
if len(items) != 2 {
t.Errorf("Expected 2 items due to max_size, got %d", len(items))
}
}
func TestErrorHandling(t *testing.T) {
testCases := []struct {
name string
pml string
}{
{"Unclosed tag", "<packet name=\"Test\"><version number=\"1\"><i32 name=\"id\">"},
{"Invalid XML", "<packet><version><i32></packet>"},
{"Missing quotes", "<packet name=Test><version number=1></version></packet>"},
{"Invalid oversized", "<packet name=\"Test\"><version number=\"1\"><i32 name=\"id\" oversized=\"abc\"></version></packet>"},
}
for _, tc := range testCases {
t.Run(tc.name, func(t *testing.T) {
_, err := Parse(tc.pml)
if err == nil {
t.Error("Expected error but got none")
}
})
}
}
func BenchmarkSimplePacket(b *testing.B) {
pml := `<packet name="Simple">
<version number="1">
<i32 name="id">
<str16 name="name">
<i8 name="level">
<f32 name="x,y,z">
</version>
</packet>`
for b.Loop() {
_, err := Parse(pml)
if err != nil {
b.Fatal(err)
}
}
}
func BenchmarkComplexPacketWithNewFeatures(b *testing.B) {
pml := `<packet name="Complex">
<version number="562">
<i32 name="player_id,account_id">
<str16 name="player_name">
<color name="skin_color,hair_color,eye_color">
<str16 name="guild_name" if="flag:has_guild">
<i32 name="guild_id" if="flag:has_guild">
<i8 name="equipment_count">
<array name="equipment" count="var:equipment_count" max_size="50">
<substruct>
<i16 name="slot_id,item_type">
<color name="primary_color,secondary_color">
<i8 name="enhancement_level" if="item_type!=0">
<i32 name="stat_value" type2="f32" type2_if="item_type==6" oversized="255">
</substruct>
</array>
<i8 name="stat_count">
<array name="stats" count="var:stat_count" optional="true">
<substruct>
<i8 name="stat_type">
<i32 name="base_value">
<i32 name="modified_value" if="stat_type>=1&stat_type<=5">
<f64 name="percentage" if="stat_type==6">
</substruct>
</array>
</version>
</packet>`
for b.Loop() {
_, err := Parse(pml)
if err != nil {
b.Fatal(err)
}
}
}
// Helper function to compare slices
func equalSlices(a, b []string) bool {
if len(a) != len(b) {
return false
}
for i, v := range a {
if v != b[i] {
return false
}
}
return true
}