flatbuffers & transcoders
This commit is contained in:
@@ -0,0 +1,275 @@
|
||||
package transcoder
|
||||
|
||||
import (
|
||||
"bytes"
|
||||
"reflect"
|
||||
"strconv"
|
||||
"strings"
|
||||
"testing"
|
||||
|
||||
model "galaxy/model/report"
|
||||
fbs "galaxy/schema/fbs/battle"
|
||||
|
||||
flatbuffers "github.com/google/flatbuffers/go"
|
||||
"github.com/google/uuid"
|
||||
)
|
||||
|
||||
func TestBattleReportToPayloadAndPayloadToBattleReportRoundTrip(t *testing.T) {
|
||||
t.Parallel()
|
||||
|
||||
source := sampleBattleReport()
|
||||
|
||||
payload, err := BattleReportToPayload(source)
|
||||
if err != nil {
|
||||
t.Fatalf("encode battle report payload: %v", err)
|
||||
}
|
||||
|
||||
decoded, err := PayloadToBattleReport(payload)
|
||||
if err != nil {
|
||||
t.Fatalf("decode battle report payload: %v", err)
|
||||
}
|
||||
|
||||
expected := battleReportWireClone(t, source)
|
||||
if !reflect.DeepEqual(expected, decoded) {
|
||||
t.Fatalf("round-trip mismatch\nexpected: %#v\ndecoded: %#v", expected, decoded)
|
||||
}
|
||||
}
|
||||
|
||||
func TestBattleReportToPayloadNilReport(t *testing.T) {
|
||||
t.Parallel()
|
||||
|
||||
_, err := BattleReportToPayload(nil)
|
||||
if err == nil {
|
||||
t.Fatal("expected error for nil battle report")
|
||||
}
|
||||
}
|
||||
|
||||
func TestPayloadToBattleReportEmptyData(t *testing.T) {
|
||||
t.Parallel()
|
||||
|
||||
_, err := PayloadToBattleReport(nil)
|
||||
if err == nil {
|
||||
t.Fatal("expected error for empty payload")
|
||||
}
|
||||
}
|
||||
|
||||
func TestPayloadToBattleReportGarbageDataDoesNotPanic(t *testing.T) {
|
||||
t.Parallel()
|
||||
|
||||
_, err := PayloadToBattleReport([]byte{0x01, 0x02, 0x03})
|
||||
if err == nil {
|
||||
t.Fatal("expected error for malformed payload")
|
||||
}
|
||||
}
|
||||
|
||||
func TestPayloadToBattleReportMissingID(t *testing.T) {
|
||||
t.Parallel()
|
||||
|
||||
payload := buildBattleReportPayload(func(builder *flatbuffers.Builder) flatbuffers.UOffsetT {
|
||||
fbs.BattleReportStart(builder)
|
||||
fbs.BattleReportAddPlanet(builder, 7)
|
||||
return fbs.BattleReportEnd(builder)
|
||||
})
|
||||
|
||||
_, err := PayloadToBattleReport(payload)
|
||||
if err == nil {
|
||||
t.Fatal("expected error for missing battle report id")
|
||||
}
|
||||
if !strings.Contains(err.Error(), "id is missing") {
|
||||
t.Fatalf("unexpected error: %v", err)
|
||||
}
|
||||
}
|
||||
|
||||
func TestPayloadToBattleReportMissingRaceValue(t *testing.T) {
|
||||
t.Parallel()
|
||||
|
||||
payload := buildBattleReportPayload(func(builder *flatbuffers.Builder) flatbuffers.UOffsetT {
|
||||
fbs.RaceEntryStart(builder)
|
||||
fbs.RaceEntryAddKey(builder, 1)
|
||||
race := fbs.RaceEntryEnd(builder)
|
||||
|
||||
fbs.BattleReportStartRacesVector(builder, 1)
|
||||
builder.PrependUOffsetT(race)
|
||||
races := builder.EndVector(1)
|
||||
|
||||
fbs.BattleReportStart(builder)
|
||||
fbs.BattleReportAddId(builder, fbs.CreateUUID(builder, 1, 2))
|
||||
fbs.BattleReportAddRaces(builder, races)
|
||||
return fbs.BattleReportEnd(builder)
|
||||
})
|
||||
|
||||
_, err := PayloadToBattleReport(payload)
|
||||
if err == nil {
|
||||
t.Fatal("expected error for missing race value")
|
||||
}
|
||||
if !strings.Contains(err.Error(), "race value is missing") {
|
||||
t.Fatalf("unexpected error: %v", err)
|
||||
}
|
||||
}
|
||||
|
||||
func TestPayloadToBattleReportMissingShipValue(t *testing.T) {
|
||||
t.Parallel()
|
||||
|
||||
payload := buildBattleReportPayload(func(builder *flatbuffers.Builder) flatbuffers.UOffsetT {
|
||||
fbs.ShipEntryStart(builder)
|
||||
fbs.ShipEntryAddKey(builder, 1)
|
||||
ship := fbs.ShipEntryEnd(builder)
|
||||
|
||||
fbs.BattleReportStartShipsVector(builder, 1)
|
||||
builder.PrependUOffsetT(ship)
|
||||
ships := builder.EndVector(1)
|
||||
|
||||
fbs.BattleReportStart(builder)
|
||||
fbs.BattleReportAddId(builder, fbs.CreateUUID(builder, 1, 2))
|
||||
fbs.BattleReportAddShips(builder, ships)
|
||||
return fbs.BattleReportEnd(builder)
|
||||
})
|
||||
|
||||
_, err := PayloadToBattleReport(payload)
|
||||
if err == nil {
|
||||
t.Fatal("expected error for missing ship value")
|
||||
}
|
||||
if !strings.Contains(err.Error(), "ship value is missing") {
|
||||
t.Fatalf("unexpected error: %v", err)
|
||||
}
|
||||
}
|
||||
|
||||
func TestPayloadToBattleReportOverflowInt(t *testing.T) {
|
||||
t.Parallel()
|
||||
|
||||
if strconv.IntSize == 64 {
|
||||
t.Skip("int overflow from int64 is not possible on 64-bit runtime")
|
||||
}
|
||||
|
||||
maxInt := int(^uint(0) >> 1)
|
||||
overflowValue := int64(maxInt) + 1
|
||||
payload := buildBattleReportPayload(func(builder *flatbuffers.Builder) flatbuffers.UOffsetT {
|
||||
fbs.BattleActionReportStart(builder)
|
||||
fbs.BattleActionReportAddAttacker(builder, overflowValue)
|
||||
protocol := fbs.BattleActionReportEnd(builder)
|
||||
|
||||
fbs.BattleReportStartProtocolVector(builder, 1)
|
||||
builder.PrependUOffsetT(protocol)
|
||||
protocolVector := builder.EndVector(1)
|
||||
|
||||
fbs.BattleReportStart(builder)
|
||||
fbs.BattleReportAddId(builder, fbs.CreateUUID(builder, 1, 2))
|
||||
fbs.BattleReportAddProtocol(builder, protocolVector)
|
||||
return fbs.BattleReportEnd(builder)
|
||||
})
|
||||
|
||||
_, err := PayloadToBattleReport(payload)
|
||||
if err == nil {
|
||||
t.Fatal("expected overflow error")
|
||||
}
|
||||
if !strings.Contains(err.Error(), "overflows int") {
|
||||
t.Fatalf("unexpected error: %v", err)
|
||||
}
|
||||
}
|
||||
|
||||
func TestPayloadToBattleReportOverflowUint(t *testing.T) {
|
||||
t.Parallel()
|
||||
|
||||
if strconv.IntSize == 64 {
|
||||
t.Skip("uint overflow from uint64 is not possible on 64-bit runtime")
|
||||
}
|
||||
|
||||
maxUint := uint64(^uint(0))
|
||||
overflowValue := maxUint + 1
|
||||
payload := buildBattleReportPayload(func(builder *flatbuffers.Builder) flatbuffers.UOffsetT {
|
||||
fbs.BattleReportStart(builder)
|
||||
fbs.BattleReportAddId(builder, fbs.CreateUUID(builder, 1, 2))
|
||||
fbs.BattleReportAddPlanet(builder, overflowValue)
|
||||
return fbs.BattleReportEnd(builder)
|
||||
})
|
||||
|
||||
_, err := PayloadToBattleReport(payload)
|
||||
if err == nil {
|
||||
t.Fatal("expected overflow error")
|
||||
}
|
||||
if !strings.Contains(err.Error(), "overflows uint") {
|
||||
t.Fatalf("unexpected error: %v", err)
|
||||
}
|
||||
}
|
||||
|
||||
func TestBattleReportToPayloadDeterministicMapEncoding(t *testing.T) {
|
||||
t.Parallel()
|
||||
|
||||
report := sampleBattleReport()
|
||||
|
||||
firstPayload, err := BattleReportToPayload(report)
|
||||
if err != nil {
|
||||
t.Fatalf("encode battle report payload: %v", err)
|
||||
}
|
||||
|
||||
for i := 0; i < 20; i++ {
|
||||
nextPayload, nextErr := BattleReportToPayload(report)
|
||||
if nextErr != nil {
|
||||
t.Fatalf("encode battle report payload #%d: %v", i+2, nextErr)
|
||||
}
|
||||
if !bytes.Equal(firstPayload, nextPayload) {
|
||||
t.Fatalf("payload differs between runs at iteration %d", i+2)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
func sampleBattleReport() *model.BattleReport {
|
||||
return &model.BattleReport{
|
||||
ID: uuid.MustParse("44444444-4444-4444-4444-444444444444"),
|
||||
Planet: 12,
|
||||
PlanetName: "Nexus",
|
||||
Races: map[int]uuid.UUID{
|
||||
2: uuid.MustParse("55555555-5555-5555-5555-555555555555"),
|
||||
1: uuid.MustParse("66666666-6666-6666-6666-666666666666"),
|
||||
},
|
||||
Ships: map[int]model.BattleReportGroup{
|
||||
3: {
|
||||
InBattle: true,
|
||||
Number: 20,
|
||||
NumberLeft: 7,
|
||||
LoadQuantity: model.Float(3.5),
|
||||
Tech: map[string]model.Float{"WEAPONS": model.Float(2.0), "DRIVE": model.Float(1.5)},
|
||||
Race: "Terrans",
|
||||
ClassName: "Frigate",
|
||||
LoadType: "MAT",
|
||||
},
|
||||
1: {
|
||||
InBattle: false,
|
||||
Number: 15,
|
||||
NumberLeft: 15,
|
||||
LoadQuantity: model.Float(0.0),
|
||||
Tech: map[string]model.Float{"CARGO": model.Float(1.25), "SHIELDS": model.Float(1.75)},
|
||||
Race: "Martians",
|
||||
ClassName: "Destroyer",
|
||||
LoadType: "CAP",
|
||||
},
|
||||
},
|
||||
Protocol: []model.BattleActionReport{
|
||||
{Attacker: 1, AttackerShipClass: 3, Defender: 2, DefenderShipClass: 1, Destroyed: true},
|
||||
{Attacker: 2, AttackerShipClass: 1, Defender: 1, DefenderShipClass: 3, Destroyed: false},
|
||||
},
|
||||
}
|
||||
}
|
||||
|
||||
func battleReportWireClone(t *testing.T, source *model.BattleReport) *model.BattleReport {
|
||||
t.Helper()
|
||||
|
||||
data, err := source.MarshalBinary()
|
||||
if err != nil {
|
||||
t.Fatalf("marshal source battle report: %v", err)
|
||||
}
|
||||
|
||||
result := new(model.BattleReport)
|
||||
if err := result.UnmarshalBinary(data); err != nil {
|
||||
t.Fatalf("unmarshal source battle report clone: %v", err)
|
||||
}
|
||||
|
||||
return result
|
||||
}
|
||||
|
||||
func buildBattleReportPayload(build func(*flatbuffers.Builder) flatbuffers.UOffsetT) []byte {
|
||||
builder := flatbuffers.NewBuilder(256)
|
||||
offset := build(builder)
|
||||
fbs.FinishBattleReportBuffer(builder, offset)
|
||||
return builder.FinishedBytes()
|
||||
}
|
||||
Reference in New Issue
Block a user