189 lines
5.2 KiB
Go
189 lines
5.2 KiB
Go
//go:build linux
|
|
|
|
package peer
|
|
|
|
import (
|
|
"net"
|
|
"strings"
|
|
"sync"
|
|
"testing"
|
|
|
|
"omnisocketgo/cmd/internal/latencylog"
|
|
"omnisocketgo/cmd/internal/protocol"
|
|
"omnisocketgo/cmd/internal/server"
|
|
)
|
|
|
|
func TestClientsExchangeMessagesWithLinuxTimestamps(t *testing.T) {
|
|
hub := server.NewHub()
|
|
serverAddr, cleanup := startRealHubServer(t, hub)
|
|
defer cleanup()
|
|
|
|
peerALogger := &recordingLogger{}
|
|
peerA, err := Dial(serverAddr, "peer-a", WithLogger(peerALogger))
|
|
if err != nil {
|
|
t.Fatalf("Dial(peer-a) error = %v", err)
|
|
}
|
|
defer func() { _ = peerA.Close() }()
|
|
|
|
peerBLogger := &recordingLogger{}
|
|
peerB, err := Dial(serverAddr, "peer-b", WithLogger(peerBLogger))
|
|
if err != nil {
|
|
t.Fatalf("Dial(peer-b) error = %v", err)
|
|
}
|
|
defer func() { _ = peerB.Close() }()
|
|
|
|
inboxDir := t.TempDir()
|
|
|
|
waitFor(t, func() bool { return hub.HasPeer("peer-a") && hub.HasPeer("peer-b") }, "both peers to be registered")
|
|
|
|
if err := peerA.SendText("peer-b", "hello"); err != nil {
|
|
t.Fatalf("SendText() error = %v", err)
|
|
}
|
|
textMsg, err := peerB.Receive()
|
|
if err != nil {
|
|
t.Fatalf("peerB.Receive(text) error = %v", err)
|
|
}
|
|
if _, err := peerB.PersistMessage(textMsg, inboxDir); err != nil {
|
|
t.Fatalf("peerB.PersistMessage(text) error = %v", err)
|
|
}
|
|
|
|
if err := peerA.SendFile("peer-b", "payload.bin", []byte{0x01, 0x02, 0x03}); err != nil {
|
|
t.Fatalf("SendFile() error = %v", err)
|
|
}
|
|
fileMsg, err := peerB.Receive()
|
|
if err != nil {
|
|
t.Fatalf("peerB.Receive(file) error = %v", err)
|
|
}
|
|
if _, err := peerB.PersistMessage(fileMsg, inboxDir); err != nil {
|
|
t.Fatalf("peerB.PersistMessage(file) error = %v", err)
|
|
}
|
|
|
|
waitFor(t, func() bool { return hasMessageEvents(peerALogger.Events(), 1, latencylog.EventAAppPrepBegin, latencylog.EventATXSched, latencylog.EventATXSoftware) }, "peer-a text kernel timestamps")
|
|
waitFor(t, func() bool { return hasMessageEvents(peerALogger.Events(), 2, latencylog.EventAAppPrepBegin, latencylog.EventATXSched, latencylog.EventATXSoftware) }, "peer-a file kernel timestamps")
|
|
waitFor(t, func() bool { return hasMessageEvents(peerBLogger.Events(), 1, latencylog.EventBRXSoftware, latencylog.EventBAppRecv, latencylog.EventBPersistBegin, latencylog.EventBPersistEnd) }, "peer-b text receive timestamps")
|
|
waitFor(t, func() bool { return hasMessageEvents(peerBLogger.Events(), 2, latencylog.EventBRXSoftware, latencylog.EventBAppRecv, latencylog.EventBPersistBegin, latencylog.EventBPersistEnd) }, "peer-b file receive timestamps")
|
|
}
|
|
|
|
func startRealHubServer(t *testing.T, hub *server.Hub) (string, func()) {
|
|
t.Helper()
|
|
|
|
listener, err := net.Listen("tcp", "127.0.0.1:0")
|
|
if err != nil {
|
|
t.Fatalf("net.Listen() error = %v", err)
|
|
}
|
|
|
|
var (
|
|
wg sync.WaitGroup
|
|
stop = make(chan struct{})
|
|
errOnce sync.Once
|
|
)
|
|
|
|
wg.Add(1)
|
|
go func() {
|
|
defer wg.Done()
|
|
for {
|
|
conn, acceptErr := listener.Accept()
|
|
if acceptErr != nil {
|
|
select {
|
|
case <-stop:
|
|
return
|
|
default:
|
|
}
|
|
if strings.Contains(acceptErr.Error(), "closed") {
|
|
return
|
|
}
|
|
t.Errorf("listener.Accept() error = %v", acceptErr)
|
|
return
|
|
}
|
|
|
|
wg.Add(1)
|
|
go func(rawConn net.Conn) {
|
|
defer wg.Done()
|
|
if serveErr := hub.ServeConn(rawConn); serveErr != nil && !isExpectedHubServeExit(serveErr) {
|
|
errOnce.Do(func() {
|
|
t.Logf("hub.ServeConn() ended with %v", serveErr)
|
|
})
|
|
}
|
|
}(conn)
|
|
}
|
|
}()
|
|
|
|
cleanup := func() {
|
|
close(stop)
|
|
_ = listener.Close()
|
|
wg.Wait()
|
|
}
|
|
|
|
return listener.Addr().String(), cleanup
|
|
}
|
|
|
|
func hasMessageEvents(events []latencylog.Event, messageID uint64, wantEvents ...string) bool {
|
|
seen := make(map[string]bool, len(wantEvents))
|
|
for _, event := range events {
|
|
if event.MessageID != messageID {
|
|
continue
|
|
}
|
|
if event.TsUnixNano <= 0 {
|
|
return false
|
|
}
|
|
seen[event.Event] = true
|
|
}
|
|
|
|
for _, wantEvent := range wantEvents {
|
|
if !seen[wantEvent] {
|
|
return false
|
|
}
|
|
}
|
|
|
|
return true
|
|
}
|
|
|
|
func isExpectedHubServeExit(err error) bool {
|
|
if err == nil {
|
|
return true
|
|
}
|
|
|
|
message := err.Error()
|
|
return strings.Contains(message, "closed") || strings.Contains(message, "protocol: read frame: EOF")
|
|
}
|
|
|
|
func TestLinuxTimestampedReceivePreservesBusinessMessageShape(t *testing.T) {
|
|
hub := server.NewHub()
|
|
serverAddr, cleanup := startRealHubServer(t, hub)
|
|
defer cleanup()
|
|
|
|
peerA, err := Dial(serverAddr, "peer-a")
|
|
if err != nil {
|
|
t.Fatalf("Dial(peer-a) error = %v", err)
|
|
}
|
|
defer func() { _ = peerA.Close() }()
|
|
|
|
peerB, err := Dial(serverAddr, "peer-b")
|
|
if err != nil {
|
|
t.Fatalf("Dial(peer-b) error = %v", err)
|
|
}
|
|
defer func() { _ = peerB.Close() }()
|
|
|
|
waitFor(t, func() bool { return hub.HasPeer("peer-a") && hub.HasPeer("peer-b") }, "both peers to be registered")
|
|
|
|
want := protocol.Message{
|
|
Type: protocol.MessageTypeFile,
|
|
ID: 1,
|
|
From: "peer-a",
|
|
To: "peer-b",
|
|
FileName: "payload.bin",
|
|
Body: []byte{0xde, 0xad, 0xbe, 0xef},
|
|
}
|
|
if err := peerA.SendFile(want.To, want.FileName, want.Body); err != nil {
|
|
t.Fatalf("SendFile() error = %v", err)
|
|
}
|
|
|
|
got, err := peerB.Receive()
|
|
if err != nil {
|
|
t.Fatalf("peerB.Receive() error = %v", err)
|
|
}
|
|
if got.Type != want.Type || got.ID != want.ID || got.From != want.From || got.To != want.To || got.FileName != want.FileName || string(got.Body) != string(want.Body) {
|
|
t.Fatalf("received message mismatch: got %+v want %+v", got, want)
|
|
}
|
|
}
|