Files
OmniSocketGo/cmd/internal/transport/udp_linux_test.go

333 lines
9.0 KiB
Go

//go:build linux
package transport
import (
"net"
"reflect"
"testing"
"omnisocketgo/cmd/internal/latencylog"
"omnisocketgo/cmd/internal/protocol"
)
func TestUDPLinuxTimestampingRecordsKernelEvents(t *testing.T) {
tests := []struct {
name string
msg protocol.Message
}{
{
name: "text",
msg: protocol.Message{
Type: protocol.MessageTypeText,
ID: 41,
From: "peer-a",
To: "peer-b",
Body: []byte("hello over udp"),
},
},
{
name: "file",
msg: protocol.Message{
Type: protocol.MessageTypeFile,
ID: 42,
From: "peer-a",
To: "peer-b",
FileName: "payload.bin",
Body: []byte{0x00, 0x01, 0x02, 0xff},
},
},
}
for _, tt := range tests {
t.Run(tt.name, func(t *testing.T) {
senderLogger := &recordingLogger{}
receiverLogger := &recordingLogger{}
serverAddr, err := net.ResolveUDPAddr("udp", "127.0.0.1:0")
if err != nil {
t.Fatalf("ResolveUDPAddr() error = %v", err)
}
serverRaw, err := net.ListenUDP("udp", serverAddr)
if err != nil {
t.Fatalf("ListenUDP() error = %v", err)
}
receiver, err := NewUDPConn(
serverRaw,
nil,
WithUDPLogger(receiverLogger, latencylog.NodeRolePeer, "peer-b"),
)
if err != nil {
_ = serverRaw.Close()
t.Fatalf("NewUDPConn(receiver) error = %v", err)
}
t.Cleanup(func() { _ = receiver.Close() })
peerRaw, err := net.DialUDP("udp", nil, serverRaw.LocalAddr().(*net.UDPAddr))
if err != nil {
t.Fatalf("DialUDP() error = %v", err)
}
sender, err := NewUDPConn(
peerRaw,
nil,
WithUDPLogger(senderLogger, latencylog.NodeRolePeer, "peer-a"),
)
if err != nil {
_ = peerRaw.Close()
t.Fatalf("NewUDPConn(sender) error = %v", err)
}
t.Cleanup(func() { _ = sender.Close() })
sendErr := make(chan error, 1)
go func() {
sendErr <- sender.Send(tt.msg)
}()
got, _, err := receiver.Receive()
if err != nil {
t.Fatalf("Receive() error = %v", err)
}
if err := <-sendErr; err != nil {
t.Fatalf("Send() error = %v", err)
}
if !reflect.DeepEqual(got, tt.msg) {
t.Fatalf("message mismatch: got %+v want %+v", got, tt.msg)
}
assertHasEvent(t, senderLogger.Events(), latencylog.EventATXSched, tt.msg.ID)
assertHasEvent(t, senderLogger.Events(), latencylog.EventATXSoftware, tt.msg.ID)
assertHasEvent(t, receiverLogger.Events(), latencylog.EventBRXSoftware, tt.msg.ID)
})
}
}
func TestUDPLinuxTimestampingDebugLoggerCapturesDatagramAndErrqueueEvents(t *testing.T) {
debugLogger := &recordingTXTimestampDebugLogger{}
senderLogger := &recordingLogger{}
receiverLogger := &recordingLogger{}
serverAddr, err := net.ResolveUDPAddr("udp", "127.0.0.1:0")
if err != nil {
t.Fatalf("ResolveUDPAddr() error = %v", err)
}
serverRaw, err := net.ListenUDP("udp", serverAddr)
if err != nil {
t.Fatalf("ListenUDP() error = %v", err)
}
receiver, err := NewUDPConn(
serverRaw,
nil,
WithUDPLogger(receiverLogger, latencylog.NodeRolePeer, "peer-b"),
)
if err != nil {
_ = serverRaw.Close()
t.Fatalf("NewUDPConn(receiver) error = %v", err)
}
t.Cleanup(func() { _ = receiver.Close() })
peerRaw, err := net.DialUDP("udp", nil, serverRaw.LocalAddr().(*net.UDPAddr))
if err != nil {
t.Fatalf("DialUDP() error = %v", err)
}
sender, err := NewUDPConn(
peerRaw,
nil,
WithUDPLogger(senderLogger, latencylog.NodeRolePeer, "peer-a"),
WithUDPTXTimestampDebugLogger(debugLogger),
)
if err != nil {
_ = peerRaw.Close()
t.Fatalf("NewUDPConn(sender) error = %v", err)
}
t.Cleanup(func() { _ = sender.Close() })
msg := protocol.Message{
Type: protocol.MessageTypeText,
ID: 99,
From: "peer-a",
To: "peer-b",
Body: []byte("hello udp debug"),
}
sendErr := make(chan error, 1)
go func() {
sendErr <- sender.Send(msg)
}()
got, _, err := receiver.Receive()
if err != nil {
t.Fatalf("Receive() error = %v", err)
}
if err := <-sendErr; err != nil {
t.Fatalf("Send() error = %v", err)
}
if !reflect.DeepEqual(got, msg) {
t.Fatalf("message mismatch: got %+v want %+v", got, msg)
}
assertHasEvent(t, senderLogger.Events(), latencylog.EventATXSched, msg.ID)
assertHasEvent(t, senderLogger.Events(), latencylog.EventATXSoftware, msg.ID)
assertHasEvent(t, receiverLogger.Events(), latencylog.EventBRXSoftware, msg.ID)
sendChunkRecords := debugRecordsByType(debugLogger.Records(), txTimestampDebugRecordTypeSendChunk)
errqueueRecords := debugRecordsByType(debugLogger.Records(), txTimestampDebugRecordTypeErrqueueEvent)
if len(sendChunkRecords) == 0 {
t.Fatal("send_chunk debug records = 0, want at least 1")
}
if len(errqueueRecords) == 0 {
t.Fatal("errqueue_event debug records = 0, want at least 1")
}
finalChunkRecord := sendChunkRecords[len(sendChunkRecords)-1]
if finalChunkRecord.ExpectedTXID == nil {
t.Fatal("final send_chunk expected_tx_id = nil, want non-nil")
}
finalExpectedTXID := *finalChunkRecord.ExpectedTXID
selectedRecords := selectedErrqueueRecords(errqueueRecords)
if len(selectedRecords) == 0 {
t.Fatal("selected errqueue debug records = 0, want at least 1")
}
highestObservedID := uint32(0)
haveHighestObservedID := false
haveExactFinalID := false
for _, record := range errqueueRecords {
if record.EEData == nil {
continue
}
if !haveHighestObservedID || *record.EEData > highestObservedID {
highestObservedID = *record.EEData
haveHighestObservedID = true
}
if *record.EEData == finalExpectedTXID && isBusinessTXTimestampRecord(record) {
haveExactFinalID = true
}
}
if !haveHighestObservedID {
t.Fatal("highestObservedID missing, want at least one ee_data")
}
wantSelectedID := highestObservedID
if haveExactFinalID {
wantSelectedID = finalExpectedTXID
}
for _, record := range selectedRecords {
if record.EEData == nil {
t.Fatalf("selected record missing ee_data: %+v", record)
}
if *record.EEData != wantSelectedID {
t.Fatalf("selected ee_data = %d, want %d", *record.EEData, wantSelectedID)
}
}
selectedByEventName := make(map[string]int64, len(selectedRecords))
for _, record := range selectedRecords {
if record.TSUnixNano == nil {
t.Fatalf("selected record missing timestamp: %+v", record)
}
selectedByEventName[record.EventName] = *record.TSUnixNano
}
senderEventsByName := make(map[string]int64)
for _, event := range senderLogger.Events() {
if event.MessageID != msg.ID {
continue
}
if !isBusinessTXTimestampEventName(event.Event) {
continue
}
senderEventsByName[event.Event] = event.TsUnixNano
}
for eventName, selectedTS := range selectedByEventName {
if senderEventsByName[eventName] != selectedTS {
t.Fatalf("sender latency event %s = %d, want %d from selected debug record", eventName, senderEventsByName[eventName], selectedTS)
}
}
}
func TestUDPLinuxTimestampingCanBeDisabled(t *testing.T) {
senderLogger := &recordingLogger{}
receiverLogger := &recordingLogger{}
serverAddr, err := net.ResolveUDPAddr("udp", "127.0.0.1:0")
if err != nil {
t.Fatalf("ResolveUDPAddr() error = %v", err)
}
serverRaw, err := net.ListenUDP("udp", serverAddr)
if err != nil {
t.Fatalf("ListenUDP() error = %v", err)
}
receiver, err := NewUDPConn(
serverRaw,
nil,
WithUDPLogger(receiverLogger, latencylog.NodeRolePeer, "peer-b"),
WithUDPLinuxTimestamping(false),
)
if err != nil {
_ = serverRaw.Close()
t.Fatalf("NewUDPConn(receiver) error = %v", err)
}
t.Cleanup(func() { _ = receiver.Close() })
peerRaw, err := net.DialUDP("udp", nil, serverRaw.LocalAddr().(*net.UDPAddr))
if err != nil {
t.Fatalf("DialUDP() error = %v", err)
}
sender, err := NewUDPConn(
peerRaw,
nil,
WithUDPLogger(senderLogger, latencylog.NodeRolePeer, "peer-a"),
WithUDPLinuxTimestamping(false),
)
if err != nil {
_ = peerRaw.Close()
t.Fatalf("NewUDPConn(sender) error = %v", err)
}
t.Cleanup(func() { _ = sender.Close() })
msg := protocol.Message{
Type: protocol.MessageTypeText,
ID: 123,
From: "peer-a",
To: "peer-b",
Body: []byte("hello without udp timestamping"),
}
sendErr := make(chan error, 1)
go func() {
sendErr <- sender.Send(msg)
}()
got, _, err := receiver.Receive()
if err != nil {
t.Fatalf("Receive() error = %v", err)
}
if err := <-sendErr; err != nil {
t.Fatalf("Send() error = %v", err)
}
if !reflect.DeepEqual(got, msg) {
t.Fatalf("message mismatch: got %+v want %+v", got, msg)
}
if sender.raw != nil {
t.Fatal("sender.raw != nil, want nil when linux timestamping is disabled")
}
if receiver.raw != nil {
t.Fatal("receiver.raw != nil, want nil when linux timestamping is disabled")
}
assertMissingEvent(t, senderLogger.Events(), latencylog.EventATXSched, msg.ID)
assertMissingEvent(t, senderLogger.Events(), latencylog.EventATXSoftware, msg.ID)
assertMissingEvent(t, receiverLogger.Events(), latencylog.EventBRXSoftware, msg.ID)
}
func assertMissingEvent(t *testing.T, events []latencylog.Event, wantEvent string, wantMessageID uint64) {
t.Helper()
for _, event := range events {
if event.Event == wantEvent && event.MessageID == wantMessageID {
t.Fatalf("unexpected event %s for message %d: %+v", wantEvent, wantMessageID, event)
}
}
}