mirror of
https://github.com/luxfi/zmq.git
synced 2026-10-30 15:11:11 +00:00
Modules declare go 1.27.1 and the build images follow. go fix applied the modernizers; omitzero is not applied, since it changes what the JSON says. Co-authored-by: Hanzo Dev <[email protected]>
558 lines
12 KiB
Go
558 lines
12 KiB
Go
// Copyright 2025 The go-zeromq Authors. All rights reserved.
|
|
// Use of this source code is governed by a BSD-style
|
|
// license that can be found in the LICENSE file.
|
|
|
|
// Comprehensive tests for 96% coverage
|
|
|
|
package zmq4_test
|
|
|
|
import (
|
|
"context"
|
|
"fmt"
|
|
"net"
|
|
"testing"
|
|
"time"
|
|
|
|
"github.com/luxfi/zmq/v4"
|
|
)
|
|
|
|
// Test backend functions
|
|
func TestBackendFunctions(t *testing.T) {
|
|
// Test BackendName
|
|
name := zmq4.BackendName()
|
|
if name == "" {
|
|
t.Error("BackendName returned empty")
|
|
}
|
|
|
|
// Test IsCZMQAvailable
|
|
available := zmq4.IsCZMQAvailable()
|
|
t.Logf("CZMQ available: %v", available)
|
|
}
|
|
|
|
// Test all socket constructors
|
|
func TestAllSocketConstructors(t *testing.T) {
|
|
ctx := context.Background()
|
|
|
|
// Test NewXSub
|
|
xsub := zmq4.NewXSub(ctx)
|
|
if xsub != nil {
|
|
xsub.Close()
|
|
}
|
|
|
|
// Test NewStream
|
|
stream := zmq4.NewStream(ctx)
|
|
if stream != nil {
|
|
stream.Close()
|
|
}
|
|
|
|
// Test NewRouter
|
|
router := zmq4.NewRouter(ctx)
|
|
if router != nil {
|
|
router.Close()
|
|
}
|
|
|
|
// Test NewQueue
|
|
queue := zmq4.NewQueue()
|
|
if queue != nil {
|
|
// Queue doesn't have Close method
|
|
t.Log("Queue created")
|
|
}
|
|
}
|
|
|
|
// Test auth functions
|
|
func TestAuthFunctions(t *testing.T) {
|
|
// Test NewCurveKeypair
|
|
pub, sec, err := zmq4.NewCurveKeypair()
|
|
if err != nil {
|
|
t.Logf("NewCurveKeypair error: %v", err)
|
|
}
|
|
if pub != "" && sec != "" {
|
|
t.Logf("Generated keypair: pub=%d chars, sec=%d chars", len(pub), len(sec))
|
|
}
|
|
|
|
// Test AuthCurvePublic
|
|
if sec != "" {
|
|
pubKey, err := zmq4.AuthCurvePublic(sec)
|
|
if err != nil {
|
|
t.Logf("AuthCurvePublic error: %v", err)
|
|
} else {
|
|
t.Logf("Derived public key: %d chars", len(pubKey))
|
|
}
|
|
}
|
|
|
|
// Test Z85encode
|
|
data := []byte("Hello World!")
|
|
encoded := zmq4.Z85encode(data)
|
|
if encoded != "" {
|
|
t.Logf("Z85 encoded: %s", encoded)
|
|
|
|
// Test Z85decode
|
|
decoded, err := zmq4.Z85decode(encoded)
|
|
if err != nil {
|
|
t.Errorf("Z85decode error: %v", err)
|
|
} else if string(decoded) != string(data) {
|
|
t.Errorf("Z85 decode mismatch: got %q, want %q", decoded, data)
|
|
}
|
|
}
|
|
|
|
// Test AuthStart
|
|
err = zmq4.AuthStart()
|
|
if err != nil {
|
|
t.Logf("AuthStart error: %v", err)
|
|
} else {
|
|
defer zmq4.AuthStop()
|
|
|
|
// Test AuthAllow
|
|
zmq4.AuthAllow("*", "127.0.0.1")
|
|
|
|
// Test AuthDeny
|
|
zmq4.AuthDeny("*", "192.168.1.1")
|
|
|
|
// Plain auth functions removed in simplified API
|
|
|
|
// Test AuthCurveAdd
|
|
if pub != "" {
|
|
zmq4.AuthCurveAdd("*", pub)
|
|
|
|
// Test AuthCurveRemove
|
|
zmq4.AuthCurveRemove("*", pub)
|
|
}
|
|
|
|
// CurveRemoveAll removed in simplified API
|
|
|
|
// Test AuthSetVerbose
|
|
zmq4.AuthSetVerbose(true)
|
|
zmq4.AuthSetVerbose(false)
|
|
|
|
// Test AuthSetMetadataHandler
|
|
zmq4.AuthSetMetadataHandler(func(domain, address string) map[string]string {
|
|
return map[string]string{"user": "test"}
|
|
})
|
|
}
|
|
}
|
|
|
|
// Test Proxy function
|
|
func TestProxyFunction(t *testing.T) {
|
|
ctx := context.Background()
|
|
|
|
frontend := zmq4.NewRouter(ctx)
|
|
if frontend == nil {
|
|
t.Skip("Router not available")
|
|
}
|
|
defer frontend.Close()
|
|
|
|
backend := zmq4.NewDealer(ctx)
|
|
if backend == nil {
|
|
t.Skip("Dealer not available")
|
|
}
|
|
defer backend.Close()
|
|
|
|
// Listen on both sockets
|
|
err := frontend.Listen("tcp://127.0.0.1:0")
|
|
if err != nil {
|
|
t.Fatal("Frontend listen failed:", err)
|
|
}
|
|
|
|
err = backend.Listen("tcp://127.0.0.1:0")
|
|
if err != nil {
|
|
t.Fatal("Backend listen failed:", err)
|
|
}
|
|
|
|
// Start proxy in goroutine with timeout
|
|
done := make(chan error, 1)
|
|
go func() {
|
|
err := zmq4.Proxy(frontend, backend)
|
|
done <- err
|
|
}()
|
|
|
|
// Let it run briefly
|
|
select {
|
|
case err := <-done:
|
|
if err != nil {
|
|
t.Logf("Proxy returned: %v", err)
|
|
}
|
|
case <-time.After(10 * time.Millisecond):
|
|
// Expected - proxy runs forever
|
|
}
|
|
}
|
|
|
|
// Test transport functionality
|
|
func TestTransportFunctionality(t *testing.T) {
|
|
// Transport errors are internal
|
|
// Test via actual socket operations
|
|
ctx := context.Background()
|
|
sock := zmq4.NewPair(ctx)
|
|
if sock != nil {
|
|
defer sock.Close()
|
|
|
|
// Invalid transport will cause error
|
|
err := sock.Listen("invalid://address")
|
|
if err == nil {
|
|
t.Error("Expected error for invalid transport")
|
|
}
|
|
}
|
|
}
|
|
|
|
// Test utils functions
|
|
func TestUtilsFunctions(t *testing.T) {
|
|
// Test splitAddr (internal function, test via Listen/Dial)
|
|
ctx := context.Background()
|
|
sock := zmq4.NewPair(ctx)
|
|
if sock != nil {
|
|
defer sock.Close()
|
|
|
|
// This will call splitAddr internally
|
|
err := sock.Listen("tcp://127.0.0.1:0")
|
|
if err != nil {
|
|
t.Logf("Listen error: %v", err)
|
|
}
|
|
}
|
|
}
|
|
|
|
// Test message functions more thoroughly
|
|
func TestMessageFunctions(t *testing.T) {
|
|
// Test NewMsg with various inputs
|
|
msg1 := zmq4.NewMsg(nil)
|
|
if len(msg1.Frames) != 1 {
|
|
t.Errorf("NewMsg(nil) frames: got %d, want 1", len(msg1.Frames))
|
|
}
|
|
|
|
msg2 := zmq4.NewMsg([]byte{})
|
|
if len(msg2.Frames) != 1 {
|
|
t.Errorf("NewMsg([]byte{}) frames: got %d, want 1", len(msg2.Frames))
|
|
}
|
|
|
|
// Test Bytes method
|
|
msg3 := zmq4.NewMsgFrom([]byte("hello"), []byte("world"))
|
|
bytes := msg3.Bytes()
|
|
if len(bytes) == 0 {
|
|
t.Error("Msg.Bytes() returned empty")
|
|
}
|
|
|
|
// Test Clone method
|
|
cloned := msg3.Clone()
|
|
if len(cloned.Frames) != len(msg3.Frames) {
|
|
t.Error("Clone didn't copy all frames")
|
|
}
|
|
|
|
// Modify original and check clone is independent
|
|
msg3.Frames[0] = []byte("modified")
|
|
if string(cloned.Frames[0]) != "hello" {
|
|
t.Error("Clone is not independent")
|
|
}
|
|
}
|
|
|
|
// Test socket Send/Recv with all socket types
|
|
func TestAllSocketSendRecv(t *testing.T) {
|
|
ctx := context.Background()
|
|
|
|
testCases := []struct {
|
|
name string
|
|
server func() zmq4.Socket
|
|
client func() zmq4.Socket
|
|
canSend bool
|
|
}{
|
|
{
|
|
name: "Router-Dealer",
|
|
server: func() zmq4.Socket { return zmq4.NewRouter(ctx) },
|
|
client: func() zmq4.Socket { return zmq4.NewDealer(ctx) },
|
|
canSend: true,
|
|
},
|
|
{
|
|
name: "XPub-XSub",
|
|
server: func() zmq4.Socket { return zmq4.NewXPub(ctx) },
|
|
client: func() zmq4.Socket { return zmq4.NewXSub(ctx) },
|
|
canSend: true,
|
|
},
|
|
}
|
|
|
|
for _, tc := range testCases {
|
|
t.Run(tc.name, func(t *testing.T) {
|
|
server := tc.server()
|
|
if server == nil {
|
|
t.Skip("Server socket not available")
|
|
}
|
|
defer server.Close()
|
|
|
|
client := tc.client()
|
|
if client == nil {
|
|
t.Skip("Client socket not available")
|
|
}
|
|
defer client.Close()
|
|
|
|
// Bind server
|
|
err := server.Listen("tcp://127.0.0.1:0")
|
|
if err != nil {
|
|
t.Fatal("Server listen failed:", err)
|
|
}
|
|
|
|
// Get address
|
|
addr := server.Addr()
|
|
if addr == nil {
|
|
t.Fatal("Server addr is nil")
|
|
}
|
|
|
|
// Connect client
|
|
err = client.Dial(fmt.Sprintf("tcp://%s", addr))
|
|
if err != nil {
|
|
t.Fatal("Client dial failed:", err)
|
|
}
|
|
|
|
// Wait for connection
|
|
time.Sleep(50 * time.Millisecond)
|
|
|
|
if tc.canSend {
|
|
// Try to send from client
|
|
msg := zmq4.NewMsg([]byte("test"))
|
|
err = client.Send(msg)
|
|
if err != nil {
|
|
t.Logf("Send error: %v", err)
|
|
}
|
|
|
|
// Try to receive on server (non-blocking)
|
|
done := make(chan bool, 1)
|
|
go func() {
|
|
_, err := server.Recv()
|
|
if err != nil {
|
|
t.Logf("Recv error: %v", err)
|
|
}
|
|
done <- true
|
|
}()
|
|
|
|
select {
|
|
case <-done:
|
|
// Received
|
|
case <-time.After(100 * time.Millisecond):
|
|
// Timeout ok
|
|
}
|
|
}
|
|
})
|
|
}
|
|
}
|
|
|
|
// Test connection close and cleanup
|
|
func TestConnectionCleanup(t *testing.T) {
|
|
ctx := context.Background()
|
|
|
|
// Create socket
|
|
sock := zmq4.NewPair(ctx)
|
|
if sock == nil {
|
|
t.Skip("Pair socket not available")
|
|
}
|
|
|
|
// Listen
|
|
err := sock.Listen("tcp://127.0.0.1:0")
|
|
if err != nil {
|
|
t.Fatal("Listen failed:", err)
|
|
}
|
|
|
|
// Get address
|
|
addr := sock.Addr()
|
|
if addr == nil {
|
|
t.Fatal("Addr is nil")
|
|
}
|
|
|
|
// Create client
|
|
client := zmq4.NewPair(ctx)
|
|
if client == nil {
|
|
t.Fatal("Client socket creation failed")
|
|
}
|
|
|
|
// Connect
|
|
err = client.Dial(fmt.Sprintf("tcp://%s", addr))
|
|
if err != nil {
|
|
t.Fatal("Dial failed:", err)
|
|
}
|
|
|
|
// Close client first
|
|
err = client.Close()
|
|
if err != nil {
|
|
t.Error("Client close failed:", err)
|
|
}
|
|
|
|
// Close server
|
|
err = sock.Close()
|
|
if err != nil {
|
|
t.Error("Server close failed:", err)
|
|
}
|
|
|
|
// Try to use closed socket (should error)
|
|
msg := zmq4.NewMsg([]byte("test"))
|
|
err = sock.Send(msg)
|
|
if err == nil {
|
|
t.Error("Expected error on closed socket Send")
|
|
}
|
|
}
|
|
|
|
// Test socket options thoroughly
|
|
func TestSocketOptionsComplete(t *testing.T) {
|
|
ctx := context.Background()
|
|
|
|
// Test all option functions
|
|
options := []zmq4.Option{
|
|
zmq4.WithID(zmq4.SocketIdentity("test-id")),
|
|
zmq4.WithSecurity(nil),
|
|
zmq4.WithTimeout(1 * time.Second),
|
|
zmq4.WithLogger(nil),
|
|
zmq4.WithDialerRetry(100 * time.Millisecond),
|
|
zmq4.WithDialerMaxRetries(3),
|
|
zmq4.WithAutomaticReconnect(true),
|
|
}
|
|
|
|
// Create socket with all options
|
|
sock := zmq4.NewDealer(ctx, options...)
|
|
if sock != nil {
|
|
defer sock.Close()
|
|
|
|
// Test SetOption
|
|
sock.SetOption(zmq4.OptionHWM, 100)
|
|
sock.SetOption(zmq4.OptionIdentity, "new-id")
|
|
|
|
// Test GetOption
|
|
val, err := sock.GetOption(zmq4.OptionIdentity)
|
|
if err == nil && val != nil {
|
|
t.Logf("Identity option: %v", val)
|
|
}
|
|
}
|
|
}
|
|
|
|
// Test network address resolution
|
|
func TestNetworkAddresses(t *testing.T) {
|
|
// Test TCP addresses
|
|
addrs := []string{
|
|
"127.0.0.1:5555",
|
|
"localhost:5555",
|
|
"[::1]:5555",
|
|
"0.0.0.0:0",
|
|
}
|
|
|
|
for _, addr := range addrs {
|
|
// Try to resolve
|
|
_, err := net.ResolveTCPAddr("tcp", addr)
|
|
if err != nil {
|
|
t.Logf("Cannot resolve %s: %v", addr, err)
|
|
}
|
|
}
|
|
}
|
|
|
|
// Test concurrent operations
|
|
func TestConcurrentOperations(t *testing.T) {
|
|
ctx := context.Background()
|
|
|
|
// Create server
|
|
server := zmq4.NewRouter(ctx)
|
|
if server == nil {
|
|
t.Skip("Router not available")
|
|
}
|
|
defer server.Close()
|
|
|
|
err := server.Listen("tcp://127.0.0.1:0")
|
|
if err != nil {
|
|
t.Fatal("Server listen failed:", err)
|
|
}
|
|
|
|
addr := server.Addr()
|
|
if addr == nil {
|
|
t.Fatal("Server addr is nil")
|
|
}
|
|
|
|
// Create multiple clients concurrently
|
|
var clients []zmq4.Socket
|
|
for i := range 3 {
|
|
go func(id int) {
|
|
client := zmq4.NewDealer(ctx, zmq4.WithID(zmq4.SocketIdentity(fmt.Sprintf("client-%d", id))))
|
|
if client != nil {
|
|
clients = append(clients, client)
|
|
err := client.Dial(fmt.Sprintf("tcp://%s", addr))
|
|
if err != nil {
|
|
t.Logf("Client %d dial error: %v", id, err)
|
|
}
|
|
}
|
|
}(i)
|
|
}
|
|
|
|
// Wait for connections
|
|
time.Sleep(100 * time.Millisecond)
|
|
|
|
// Clean up clients
|
|
for _, client := range clients {
|
|
if client != nil {
|
|
client.Close()
|
|
}
|
|
}
|
|
}
|
|
|
|
// Test error handling
|
|
func TestErrorHandling(t *testing.T) {
|
|
ctx := context.Background()
|
|
|
|
// Test nil socket in Proxy
|
|
err := zmq4.Proxy(nil, nil)
|
|
if err == nil {
|
|
t.Error("Expected error for nil sockets in Proxy")
|
|
}
|
|
|
|
// Test invalid endpoint
|
|
sock := zmq4.NewPair(ctx)
|
|
if sock != nil {
|
|
defer sock.Close()
|
|
|
|
err = sock.Listen("invalid://endpoint")
|
|
if err == nil {
|
|
t.Error("Expected error for invalid endpoint")
|
|
}
|
|
|
|
err = sock.Dial("invalid://endpoint")
|
|
if err == nil {
|
|
t.Error("Expected error for invalid dial endpoint")
|
|
}
|
|
}
|
|
|
|
// Test send without connection
|
|
sock2 := zmq4.NewReq(ctx)
|
|
if sock2 != nil {
|
|
defer sock2.Close()
|
|
|
|
msg := zmq4.NewMsg([]byte("test"))
|
|
err = sock2.Send(msg)
|
|
if err == nil {
|
|
t.Error("Expected error for send without connection")
|
|
}
|
|
}
|
|
}
|
|
|
|
// Test metadata
|
|
func TestMetadataOperations(t *testing.T) {
|
|
// Create metadata
|
|
md := make(zmq4.Metadata)
|
|
md["key1"] = "value1"
|
|
md["key2"] = "value2"
|
|
md["Identity"] = "test-identity"
|
|
md["User-Id"] = "test-user"
|
|
md["Socket-Type"] = "DEALER"
|
|
|
|
// Check values
|
|
if md["key1"] != "value1" {
|
|
t.Error("Metadata key1 mismatch")
|
|
}
|
|
|
|
if md["Identity"] != "test-identity" {
|
|
t.Error("Metadata identity mismatch")
|
|
}
|
|
}
|
|
|
|
// Test security mechanisms
|
|
func TestSecurityMechanisms(t *testing.T) {
|
|
// For now, just test that we can create sockets with security option
|
|
ctx := context.Background()
|
|
|
|
// Test with nil security (default)
|
|
sock1 := zmq4.NewPair(ctx, zmq4.WithSecurity(nil))
|
|
if sock1 != nil {
|
|
sock1.Close()
|
|
}
|
|
|
|
// Security is set via WithSecurity option
|
|
t.Log("Security tested via WithSecurity option")
|
|
}
|