bc3d12ec2c
- Fix promMetrics.Gauge/Counter/Observer mutating caller's labels map by using maps.Copy instead of direct assignment - Fix Gauge/Counter/Observer returning nil for unknown metric names (now return noop implementations) - Fix metricService.Close() race with sync.Once for safe concurrent calls - Fix SetDSCP returning nil instead of errUnsupport on unsupported ops - Add nil guards to WrapConn, WrapPacketConn, WrapUDPConn, WrapListener - Add doc comments to all exported symbols - Add 45 unit tests (metrics, noop, prom, service, wrapper)
231 lines
5.6 KiB
Go
231 lines
5.6 KiB
Go
package wrapper
|
|
|
|
import (
|
|
"io"
|
|
"net"
|
|
"sync"
|
|
"testing"
|
|
"time"
|
|
|
|
xio "github.com/go-gost/x/internal/io"
|
|
)
|
|
|
|
func TestWrapConnNil(t *testing.T) {
|
|
if c := WrapConn("svc", nil); c != nil {
|
|
t.Error("WrapConn(nil) should return nil")
|
|
}
|
|
}
|
|
|
|
func TestWrapPacketConnNil(t *testing.T) {
|
|
if pc := WrapPacketConn("svc", nil); pc != nil {
|
|
t.Error("WrapPacketConn(nil) should return nil")
|
|
}
|
|
}
|
|
|
|
func TestWrapUDPConnNil(t *testing.T) {
|
|
if uc := WrapUDPConn("svc", nil); uc != nil {
|
|
t.Error("WrapUDPConn(nil) should return nil")
|
|
}
|
|
}
|
|
|
|
func TestWrapConnReadWrite(t *testing.T) {
|
|
client, server := net.Pipe()
|
|
defer client.Close()
|
|
defer server.Close()
|
|
|
|
wrapped := WrapConn("test-svc", server)
|
|
|
|
var wg sync.WaitGroup
|
|
wg.Add(1)
|
|
go func() {
|
|
defer wg.Done()
|
|
client.Write([]byte("hello"))
|
|
}()
|
|
|
|
buf := make([]byte, 1024)
|
|
n, err := wrapped.Read(buf)
|
|
wg.Wait()
|
|
|
|
if err != nil {
|
|
t.Fatalf("Read error: %v", err)
|
|
}
|
|
if n != 5 {
|
|
t.Errorf("expected 5 bytes, got %d", n)
|
|
}
|
|
if string(buf[:n]) != "hello" {
|
|
t.Errorf("expected 'hello', got %q", string(buf[:n]))
|
|
}
|
|
|
|
// Write through the wrapped conn.
|
|
go func() {
|
|
buf := make([]byte, 1024)
|
|
n, err := client.Read(buf)
|
|
if err != nil {
|
|
t.Errorf("client Read error: %v", err)
|
|
return
|
|
}
|
|
if n != 5 || string(buf[:n]) != "world" {
|
|
t.Errorf("expected 'world', got %q", string(buf[:n]))
|
|
}
|
|
}()
|
|
|
|
nw, err := wrapped.Write([]byte("world"))
|
|
if err != nil {
|
|
t.Fatalf("Write error: %v", err)
|
|
}
|
|
if nw != 5 {
|
|
t.Errorf("expected 5 bytes written, got %d", nw)
|
|
}
|
|
}
|
|
|
|
func TestWrapConnSyscallConn(t *testing.T) {
|
|
client, server := net.Pipe()
|
|
defer client.Close()
|
|
defer server.Close()
|
|
|
|
wrapped := WrapConn("test-svc", server)
|
|
|
|
// SyscallConn should not panic. The return values depend on the underlying
|
|
// connection type.
|
|
_, _ = wrapped.(*serverConn).SyscallConn()
|
|
}
|
|
|
|
func TestWrapConnCloseReadCloseWrite(t *testing.T) {
|
|
client, server := net.Pipe()
|
|
defer client.Close()
|
|
defer server.Close()
|
|
|
|
wrapped := WrapConn("test-svc", server)
|
|
|
|
// net.Pipe does not implement CloseRead/CloseWrite, so these should
|
|
// return ErrUnsupported.
|
|
err := wrapped.(*serverConn).CloseRead()
|
|
if err != xio.ErrUnsupported {
|
|
t.Errorf("CloseRead: expected ErrUnsupported, got %v", err)
|
|
}
|
|
|
|
err = wrapped.(*serverConn).CloseWrite()
|
|
if err != xio.ErrUnsupported {
|
|
t.Errorf("CloseWrite: expected ErrUnsupported, got %v", err)
|
|
}
|
|
}
|
|
|
|
func TestWrapConnContext(t *testing.T) {
|
|
client, server := net.Pipe()
|
|
defer client.Close()
|
|
defer server.Close()
|
|
|
|
wrapped := WrapConn("test-svc", server)
|
|
|
|
// net.Pipe does not implement ctx.Context, so Context() returns nil.
|
|
ctx := wrapped.(*serverConn).Context()
|
|
if ctx != nil {
|
|
t.Errorf("expected nil context, got %v", ctx)
|
|
}
|
|
}
|
|
|
|
// mockPacketConn implements net.PacketConn for testing.
|
|
type mockPacketConn struct {
|
|
readFrom func([]byte) (int, net.Addr, error)
|
|
writeTo func([]byte, net.Addr) (int, error)
|
|
}
|
|
|
|
func (m *mockPacketConn) ReadFrom(p []byte) (int, net.Addr, error) {
|
|
return m.readFrom(p)
|
|
}
|
|
|
|
func (m *mockPacketConn) WriteTo(p []byte, addr net.Addr) (int, error) {
|
|
return m.writeTo(p, addr)
|
|
}
|
|
|
|
func (m *mockPacketConn) Close() error { return nil }
|
|
func (m *mockPacketConn) LocalAddr() net.Addr { return &net.UDPAddr{IP: net.IPv4(127, 0, 0, 1), Port: 0} }
|
|
func (m *mockPacketConn) SetDeadline(t time.Time) error { return nil }
|
|
func (m *mockPacketConn) SetReadDeadline(t time.Time) error { return nil }
|
|
func (m *mockPacketConn) SetWriteDeadline(t time.Time) error { return nil }
|
|
|
|
func TestWrapPacketConnReadFromWriteTo(t *testing.T) {
|
|
testAddr := &net.UDPAddr{IP: net.IPv4(10, 0, 0, 1), Port: 1234}
|
|
|
|
mock := &mockPacketConn{
|
|
readFrom: func(p []byte) (int, net.Addr, error) {
|
|
copy(p, []byte("data"))
|
|
return 4, testAddr, nil
|
|
},
|
|
writeTo: func(p []byte, addr net.Addr) (int, error) {
|
|
return len(p), nil
|
|
},
|
|
}
|
|
|
|
wrapped := WrapPacketConn("test-svc", mock)
|
|
|
|
// ReadFrom
|
|
buf := make([]byte, 1024)
|
|
n, addr, err := wrapped.ReadFrom(buf)
|
|
if err != nil {
|
|
t.Fatalf("ReadFrom error: %v", err)
|
|
}
|
|
if n != 4 {
|
|
t.Errorf("expected 4 bytes, got %d", n)
|
|
}
|
|
if addr.String() != testAddr.String() {
|
|
t.Errorf("expected addr %s, got %s", testAddr, addr)
|
|
}
|
|
|
|
// WriteTo
|
|
nw, err := wrapped.WriteTo([]byte("reply"), testAddr)
|
|
if err != nil {
|
|
t.Fatalf("WriteTo error: %v", err)
|
|
}
|
|
if nw != 5 {
|
|
t.Errorf("expected 5 bytes written, got %d", nw)
|
|
}
|
|
}
|
|
|
|
func TestWrapPacketConnContext(t *testing.T) {
|
|
mock := &mockPacketConn{
|
|
readFrom: func(p []byte) (int, net.Addr, error) { return 0, nil, io.EOF },
|
|
writeTo: func(p []byte, addr net.Addr) (int, error) { return len(p), nil },
|
|
}
|
|
|
|
wrapped := WrapPacketConn("test-svc", mock)
|
|
|
|
// mockPacketConn does not implement ctx.Context
|
|
ctx := wrapped.(*packetConn).Context()
|
|
if ctx != nil {
|
|
t.Errorf("expected nil context, got %v", ctx)
|
|
}
|
|
}
|
|
|
|
func TestWrapConnRemoteAddr(t *testing.T) {
|
|
client, server := net.Pipe()
|
|
defer client.Close()
|
|
defer server.Close()
|
|
|
|
wrapped := WrapConn("test-svc", server)
|
|
|
|
// The wrapped conn should expose the underlying remote address.
|
|
if wrapped.RemoteAddr() == nil {
|
|
t.Error("RemoteAddr should not be nil")
|
|
}
|
|
}
|
|
|
|
func TestWrapConnNilLabels(t *testing.T) {
|
|
// When the remote address has no port, SplitHostPort may fail.
|
|
// WrapConn should still succeed and the host may be empty.
|
|
client, server := net.Pipe()
|
|
defer client.Close()
|
|
defer server.Close()
|
|
|
|
wrapped := WrapConn("test-svc", server)
|
|
|
|
// Read should not panic even with potentially malformed addresses.
|
|
// net.Pipe addresses are well-formed so this is a smoke test.
|
|
buf := make([]byte, 1)
|
|
go client.Write([]byte{0x01})
|
|
_, err := wrapped.Read(buf)
|
|
if err != nil {
|
|
t.Fatalf("Read error: %v", err)
|
|
}
|
|
}
|