106 lines
2.7 KiB
Go
106 lines
2.7 KiB
Go
// Package testutil provides an in-memory WebSocket connection so the engine's
|
|
// concurrency tests (#26) can run without real sockets. FakeConn satisfies the
|
|
// ws.Conn interface structurally.
|
|
package testutil
|
|
|
|
import (
|
|
"io"
|
|
"sync"
|
|
"time"
|
|
)
|
|
|
|
// textMessage mirrors gorilla/websocket.TextMessage (1) without importing it, so
|
|
// this helper stays dependency-light.
|
|
const textMessage = 1
|
|
|
|
// FakeConn is a thread-safe, in-memory connection. Outbound frames are captured
|
|
// in Writes(); inbound frames can be injected with Push() and are returned by
|
|
// ReadMessage in order until the connection is closed.
|
|
type FakeConn struct {
|
|
mu sync.Mutex
|
|
writes [][]byte
|
|
incoming chan []byte
|
|
closed bool
|
|
pong func(string) error
|
|
}
|
|
|
|
// NewFakeConn returns a ready connection.
|
|
func NewFakeConn() *FakeConn {
|
|
return &FakeConn{incoming: make(chan []byte, 1024)}
|
|
}
|
|
|
|
// ReadMessage returns the next injected frame, blocking until one is available or
|
|
// the connection is closed (then io.EOF).
|
|
func (f *FakeConn) ReadMessage() (int, []byte, error) {
|
|
b, ok := <-f.incoming
|
|
if !ok {
|
|
return 0, nil, io.EOF
|
|
}
|
|
return textMessage, b, nil
|
|
}
|
|
|
|
// Push injects an inbound frame for ReadMessage to return.
|
|
func (f *FakeConn) Push(frame []byte) {
|
|
f.mu.Lock()
|
|
defer f.mu.Unlock()
|
|
if f.closed {
|
|
return
|
|
}
|
|
f.incoming <- frame
|
|
}
|
|
|
|
// WriteMessage captures an outbound frame.
|
|
func (f *FakeConn) WriteMessage(_ int, data []byte) error {
|
|
f.mu.Lock()
|
|
defer f.mu.Unlock()
|
|
if f.closed {
|
|
return io.ErrClosedPipe
|
|
}
|
|
cp := make([]byte, len(data))
|
|
copy(cp, data)
|
|
f.writes = append(f.writes, cp)
|
|
return nil
|
|
}
|
|
|
|
// Writes returns a copy of all captured outbound frames.
|
|
func (f *FakeConn) Writes() [][]byte {
|
|
f.mu.Lock()
|
|
defer f.mu.Unlock()
|
|
out := make([][]byte, len(f.writes))
|
|
copy(out, f.writes)
|
|
return out
|
|
}
|
|
|
|
// WriteCount returns how many frames have been written.
|
|
func (f *FakeConn) WriteCount() int {
|
|
f.mu.Lock()
|
|
defer f.mu.Unlock()
|
|
return len(f.writes)
|
|
}
|
|
|
|
func (f *FakeConn) WriteControl(int, []byte, time.Time) error { return nil }
|
|
func (f *FakeConn) SetReadLimit(int64) {}
|
|
func (f *FakeConn) SetReadDeadline(time.Time) error { return nil }
|
|
func (f *FakeConn) SetWriteDeadline(time.Time) error { return nil }
|
|
func (f *FakeConn) SetPongHandler(h func(string) error) { f.pong = h }
|
|
|
|
// Pong invokes the registered pong handler (used by heartbeat tests).
|
|
func (f *FakeConn) Pong(appData string) error {
|
|
if f.pong != nil {
|
|
return f.pong(appData)
|
|
}
|
|
return nil
|
|
}
|
|
|
|
// Close makes ReadMessage return EOF and rejects further writes. Idempotent.
|
|
func (f *FakeConn) Close() error {
|
|
f.mu.Lock()
|
|
defer f.mu.Unlock()
|
|
if f.closed {
|
|
return nil
|
|
}
|
|
f.closed = true
|
|
close(f.incoming)
|
|
return nil
|
|
}
|