397 lines
9.0 KiB
Go
397 lines
9.0 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 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 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>"},
|
|
}
|
|
|
|
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>`
|
|
|
|
b.ResetTimer()
|
|
for i := 0; i < b.N; i++ {
|
|
_, err := Parse(pml)
|
|
if err != nil {
|
|
b.Fatal(err)
|
|
}
|
|
}
|
|
}
|
|
|
|
func BenchmarkComplexPacket(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">
|
|
<substruct>
|
|
<i16 name="slot_id,item_type">
|
|
<color name="primary_color,secondary_color">
|
|
<i8 name="enhancement_level" if="item_type!=0">
|
|
</substruct>
|
|
</array>
|
|
<i8 name="stat_count">
|
|
<array name="stats" count="var:stat_count">
|
|
<substruct>
|
|
<i8 name="stat_type">
|
|
<i32 name="base_value">
|
|
<i32 name="modified_value" if="stat_type>=1&stat_type<=5">
|
|
<f32 name="percentage" if="stat_type==6">
|
|
</substruct>
|
|
</array>
|
|
</version>
|
|
</packet>`
|
|
|
|
b.ResetTimer()
|
|
for i := 0; i < b.N; i++ {
|
|
_, err := Parse(pml)
|
|
if err != nil {
|
|
b.Fatal(err)
|
|
}
|
|
}
|
|
}
|
|
|
|
func BenchmarkLargePacket(b *testing.B) {
|
|
// Generate a large packet definition
|
|
pmlBuilder := `<packet name="Large"><version number="1">`
|
|
for i := 0; i < 100; i++ {
|
|
pmlBuilder += `<i32 name="field` + string(rune('A'+i%26)) + `">`
|
|
}
|
|
pmlBuilder += `</version></packet>`
|
|
|
|
b.ResetTimer()
|
|
for i := 0; i < b.N; i++ {
|
|
_, err := Parse(pmlBuilder)
|
|
if err != nil {
|
|
b.Fatal(err)
|
|
}
|
|
}
|
|
}
|
|
|
|
func BenchmarkWithSubstructs(b *testing.B) {
|
|
pml := `<substruct name="Item">
|
|
<i32 name="id">
|
|
<str16 name="name">
|
|
<i16 name="quantity">
|
|
</substruct>
|
|
|
|
<packet name="WithSubstruct">
|
|
<version number="1">
|
|
<i8 name="count">
|
|
<array name="items" count="var:count" substruct="Item">
|
|
</version>
|
|
</packet>`
|
|
|
|
b.ResetTimer()
|
|
for i := 0; i < b.N; i++ {
|
|
parser := NewParser(pml)
|
|
_, err := parser.Parse()
|
|
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
|
|
}
|