269 lines
6.9 KiB
Go
269 lines
6.9 KiB
Go
package parser
|
||
|
||
import (
|
||
"testing"
|
||
)
|
||
|
||
const sampleSource = `package messages
|
||
|
||
type Vector3 struct {
|
||
X float32 ` + "`" + `pack:"min=-500,max=500,bits=16"` + "`" + `
|
||
Y float32 ` + "`" + `pack:"min=-500,max=500,bits=16"` + "`" + `
|
||
Z float32 ` + "`" + `pack:"min=-500,max=500,bits=16"` + "`" + `
|
||
}
|
||
|
||
type MoveMessage struct {
|
||
Position Vector3
|
||
Velocity [3]float32
|
||
Waypoints []Vector3
|
||
PlayerID uint32
|
||
Name string
|
||
Active bool
|
||
}
|
||
|
||
type SpawnMessage struct {
|
||
ID uint64
|
||
Position Vector3
|
||
Tags [4]string
|
||
Data []uint8
|
||
}
|
||
`
|
||
|
||
const enumSource = `package messages
|
||
|
||
type Opcode uint16
|
||
|
||
const (
|
||
OpcodeUnknown Opcode = iota
|
||
OpcodeAuthorize
|
||
OpcodeJoinRoom
|
||
)
|
||
|
||
type EnvelopeMessage struct {
|
||
Code Opcode
|
||
Counter uint8
|
||
}
|
||
`
|
||
|
||
func TestParseSource_Primitives(t *testing.T) {
|
||
msgs, err := ParseSource(sampleSource)
|
||
if err != nil {
|
||
t.Fatalf("ParseSource: %v", err)
|
||
}
|
||
if len(msgs) != 3 {
|
||
t.Fatalf("expected 3 messages, got %d", len(msgs))
|
||
}
|
||
}
|
||
|
||
func TestParseSource_Vector3(t *testing.T) {
|
||
msgs, err := ParseSource(sampleSource)
|
||
if err != nil {
|
||
t.Fatalf("ParseSource: %v", err)
|
||
}
|
||
|
||
v3 := msgs[0]
|
||
if v3.Name != "Vector3" {
|
||
t.Fatalf("expected Vector3, got %s", v3.Name)
|
||
}
|
||
if len(v3.Fields) != 3 {
|
||
t.Fatalf("expected 3 fields, got %d", len(v3.Fields))
|
||
}
|
||
|
||
for _, f := range v3.Fields {
|
||
if f.Kind != KindPrimitive {
|
||
t.Errorf("field %s: expected KindPrimitive, got %d", f.Name, f.Kind)
|
||
}
|
||
if f.Primitive != KindFloat32 {
|
||
t.Errorf("field %s: expected KindFloat32, got %d", f.Name, f.Primitive)
|
||
}
|
||
if f.Quant == nil {
|
||
t.Errorf("field %s: expected quant info, got nil", f.Name)
|
||
continue
|
||
}
|
||
if f.Quant.Min != -500 || f.Quant.Max != 500 || f.Quant.Bits != 16 {
|
||
t.Errorf("field %s: wrong quant info %+v", f.Name, f.Quant)
|
||
}
|
||
}
|
||
}
|
||
|
||
func TestParseSource_MoveMessage(t *testing.T) {
|
||
msgs, err := ParseSource(sampleSource)
|
||
if err != nil {
|
||
t.Fatalf("ParseSource: %v", err)
|
||
}
|
||
|
||
msg := msgs[1]
|
||
if msg.Name != "MoveMessage" {
|
||
t.Fatalf("expected MoveMessage, got %s", msg.Name)
|
||
}
|
||
|
||
tests := []struct {
|
||
name string
|
||
kind FieldKind
|
||
typeName string
|
||
fixedLen int
|
||
elemKind FieldKind
|
||
}{
|
||
{"Position", KindNested, "Vector3", 0, 0},
|
||
{"Velocity", KindFixedArray, "", 3, KindPrimitive},
|
||
{"Waypoints", KindSlice, "", 0, KindNested},
|
||
{"PlayerID", KindPrimitive, "", 0, 0},
|
||
{"Name", KindPrimitive, "", 0, 0},
|
||
{"Active", KindPrimitive, "", 0, 0},
|
||
}
|
||
|
||
if len(msg.Fields) != len(tests) {
|
||
t.Fatalf("expected %d fields, got %d", len(tests), len(msg.Fields))
|
||
}
|
||
|
||
for i, tc := range tests {
|
||
f := msg.Fields[i]
|
||
if f.Name != tc.name {
|
||
t.Errorf("[%d] expected field %s, got %s", i, tc.name, f.Name)
|
||
}
|
||
if f.Kind != tc.kind {
|
||
t.Errorf("field %s: expected kind %d, got %d", tc.name, tc.kind, f.Kind)
|
||
}
|
||
if tc.typeName != "" && f.TypeName != tc.typeName {
|
||
t.Errorf("field %s: expected TypeName %s, got %s", tc.name, tc.typeName, f.TypeName)
|
||
}
|
||
if tc.fixedLen > 0 {
|
||
if f.FixedLen != tc.fixedLen {
|
||
t.Errorf("field %s: expected FixedLen %d, got %d", tc.name, tc.fixedLen, f.FixedLen)
|
||
}
|
||
if f.Elem == nil {
|
||
t.Errorf("field %s: Elem is nil", tc.name)
|
||
} else if f.Elem.Kind != tc.elemKind {
|
||
t.Errorf("field %s: Elem.Kind expected %d, got %d", tc.name, tc.elemKind, f.Elem.Kind)
|
||
}
|
||
}
|
||
if tc.kind == KindSlice {
|
||
if f.Elem == nil {
|
||
t.Errorf("field %s: Elem is nil for slice", tc.name)
|
||
} else if f.Elem.Kind != tc.elemKind {
|
||
t.Errorf("field %s: Elem.Kind expected %d, got %d", tc.name, tc.elemKind, f.Elem.Kind)
|
||
}
|
||
}
|
||
}
|
||
}
|
||
|
||
func TestParseSource_SpawnMessage(t *testing.T) {
|
||
msgs, err := ParseSource(sampleSource)
|
||
if err != nil {
|
||
t.Fatalf("ParseSource: %v", err)
|
||
}
|
||
|
||
msg := msgs[2]
|
||
if msg.Name != "SpawnMessage" {
|
||
t.Fatalf("expected SpawnMessage, got %s", msg.Name)
|
||
}
|
||
|
||
// Tags: [4]string
|
||
tags := msg.Fields[2]
|
||
if tags.Kind != KindFixedArray || tags.FixedLen != 4 {
|
||
t.Errorf("Tags: expected KindFixedArray[4], got kind=%d fixedLen=%d", tags.Kind, tags.FixedLen)
|
||
}
|
||
if tags.Elem == nil || tags.Elem.Primitive != KindString {
|
||
t.Errorf("Tags: expected string element")
|
||
}
|
||
|
||
// Data: []uint8
|
||
data := msg.Fields[3]
|
||
if data.Kind != KindSlice {
|
||
t.Errorf("Data: expected KindSlice, got %d", data.Kind)
|
||
}
|
||
if data.Elem == nil || data.Elem.Primitive != KindUint8 {
|
||
t.Errorf("Data: expected uint8 element")
|
||
}
|
||
}
|
||
|
||
func TestParseSchemaSource_Enums(t *testing.T) {
|
||
schema, err := ParseSchemaSource(enumSource)
|
||
if err != nil {
|
||
t.Fatalf("ParseSchemaSource: %v", err)
|
||
}
|
||
|
||
if len(schema.Messages) != 1 {
|
||
t.Fatalf("expected 1 message, got %d", len(schema.Messages))
|
||
}
|
||
if len(schema.Enums) != 1 {
|
||
t.Fatalf("expected 1 enum, got %d", len(schema.Enums))
|
||
}
|
||
|
||
enum := schema.Enums[0]
|
||
if enum.Name != "Opcode" {
|
||
t.Fatalf("expected enum Opcode, got %s", enum.Name)
|
||
}
|
||
if enum.Primitive != KindUint16 {
|
||
t.Fatalf("expected Opcode base kind uint16, got %d", enum.Primitive)
|
||
}
|
||
if len(enum.Values) != 3 {
|
||
t.Fatalf("expected 3 enum values, got %d", len(enum.Values))
|
||
}
|
||
if enum.Values[1].Name != "OpcodeAuthorize" || enum.Values[1].Value != "1" {
|
||
t.Fatalf("unexpected enum value %#v", enum.Values[1])
|
||
}
|
||
|
||
field := schema.Messages[0].Fields[0]
|
||
if field.Kind != KindPrimitive {
|
||
t.Fatalf("expected EnvelopeMessage.Code to be primitive, got %d", field.Kind)
|
||
}
|
||
if field.NamedType != "Opcode" {
|
||
t.Fatalf("expected named type Opcode, got %q", field.NamedType)
|
||
}
|
||
if field.Primitive != KindUint16 {
|
||
t.Fatalf("expected underlying uint16, got %d", field.Primitive)
|
||
}
|
||
}
|
||
|
||
func TestQuantTag_Errors(t *testing.T) {
|
||
cases := []struct {
|
||
src string
|
||
wantErr bool
|
||
}{
|
||
{`package p; type T struct { X float32 ` + "`" + `pack:"min=0,max=100"` + "`" + ` }`, false},
|
||
{`package p; type T struct { X float32 ` + "`" + `pack:"min=100,max=0"` + "`" + ` }`, true}, // max < min
|
||
{`package p; type T struct { X float32 ` + "`" + `pack:"min=0,max=100,bits=32"` + "`" + ` }`, true}, // bad bits
|
||
{`package p; type T struct { X int32 ` + "`" + `pack:"min=0,max=100"` + "`" + ` }`, true}, // quant на int
|
||
{`package p; type T struct { X float32 ` + "`" + `pack:"foo=1"` + "`" + ` }`, true}, // unknown key
|
||
}
|
||
|
||
for i, tc := range cases {
|
||
_, err := ParseSource(tc.src)
|
||
if tc.wantErr && err == nil {
|
||
t.Errorf("[%d] expected error, got nil", i)
|
||
}
|
||
if !tc.wantErr && err != nil {
|
||
t.Errorf("[%d] unexpected error: %v", i, err)
|
||
}
|
||
}
|
||
}
|
||
|
||
func TestWireSize(t *testing.T) {
|
||
msgs, err := ParseSource(sampleSource)
|
||
if err != nil {
|
||
t.Fatalf("ParseSource: %v", err)
|
||
}
|
||
|
||
v3 := msgs[0]
|
||
// Vector3: 3 × uint16 (квантизованные float32) = 6 байт
|
||
if v3.MinWireSize() != 6 {
|
||
t.Errorf("Vector3.MinWireSize: expected 6, got %d", v3.MinWireSize())
|
||
}
|
||
if v3.HasVariableFields() {
|
||
t.Errorf("Vector3 should not have variable fields")
|
||
}
|
||
}
|
||
|
||
func TestNestedUnknownType(t *testing.T) {
|
||
src := `package p
|
||
type Msg struct {
|
||
Pos UnknownType
|
||
}
|
||
`
|
||
_, err := ParseSource(src)
|
||
if err == nil {
|
||
t.Fatal("expected error for unknown nested type, got nil")
|
||
}
|
||
}
|