speed up tests and update dependencies

- Override wsHandshakeTimeout in 3 websocket tests that waited 30s each
- Make initialRetryDelay a package variable for faster retry tests
- Reduce time.Sleep calls across agent_test.go (1.5s->150ms, 3s->1s, etc.)
- Update Makefile with test-fast and test-short targets, add -count=1
- Update golang.org/x/crypto, x/net, x/sys to latest minors
- Fix TestWSDialDefaultPorts to use mocked dials instead of real TCP
This commit is contained in:
Graham McIntire 2026-06-06 13:50:54 -05:00
parent b308c3dc2e
commit 217f85fbdd
No known key found for this signature in database
GPG key ID: F4ABF488E6029E59
8 changed files with 135 additions and 57 deletions

View file

@ -19,7 +19,7 @@ jobs:
go-version-file: go.mod go-version-file: go.mod
- name: Run tests - name: Run tests
run: go test -race -v ./... run: go test -race -v -count=1 -timeout 60s ./...
- name: Vet - name: Vet
run: go vet ./... run: go vet ./...

View file

@ -1,4 +1,4 @@
.PHONY: proto test build vet lint .PHONY: proto test test-fast test-race build vet lint
proto: proto:
protoc --go_out=. --go_opt=paths=source_relative proto/agent.proto protoc --go_out=. --go_opt=paths=source_relative proto/agent.proto
@ -6,7 +6,13 @@ proto:
mv proto/agent.pb.go pb/agent.pb.go mv proto/agent.pb.go pb/agent.pb.go
test: test:
go test -race -v ./... go test -race -v -count=1 -timeout 60s ./...
test-fast:
go test -count=1 -timeout 60s ./...
test-short:
go test -count=1 -timeout 30s -short ./...
build: build:
go build -o towerops-agent . go build -o towerops-agent .

View file

@ -26,6 +26,7 @@ var errChannelReloaded = fmt.Errorf("channel reloaded")
var joinTimeout = 10 * time.Second var joinTimeout = 10 * time.Second
var heartbeatInterval = 60 * time.Second var heartbeatInterval = 60 * time.Second
var channelHeartbeatInterval = 25 * time.Second var channelHeartbeatInterval = 25 * time.Second
var initialRetryDelay = time.Second
const maxJobPayloadBytes = 4 << 20 // 4 MB — well above any legitimate job list const maxJobPayloadBytes = 4 << 20 // 4 MB — well above any legitimate job list
@ -40,7 +41,7 @@ type channelMsg struct {
// runAgent connects to the server and runs the event loop with reconnect. // runAgent connects to the server and runs the event loop with reconnect.
func runAgent(ctx context.Context, wsURL, token string) { func runAgent(ctx context.Context, wsURL, token string) {
baseURL := strings.TrimRight(wsURL, "/") baseURL := strings.TrimRight(wsURL, "/")
retryDelay := time.Second retryDelay := initialRetryDelay
maxRetry := 10 * time.Second maxRetry := 10 * time.Second
const successfulConnectionThreshold = 30 * time.Second const successfulConnectionThreshold = 30 * time.Second
@ -60,7 +61,7 @@ func runAgent(ctx context.Context, wsURL, token string) {
slog.Debug("resetting reconnect backoff after successful session", slog.Debug("resetting reconnect backoff after successful session",
"duration", sessionDuration, "duration", sessionDuration,
"previous_delay", retryDelay) "previous_delay", retryDelay)
retryDelay = time.Second retryDelay = initialRetryDelay
} }
if ctx.Err() != nil { if ctx.Err() != nil {
@ -68,7 +69,7 @@ func runAgent(ctx context.Context, wsURL, token string) {
} }
if errors.Is(err, errRestartRequested) { if errors.Is(err, errRestartRequested) {
slog.Info("restart requested, reconnecting immediately") slog.Info("restart requested, reconnecting immediately")
retryDelay = time.Second retryDelay = initialRetryDelay
continue continue
} }
if err != nil { if err != nil {

View file

@ -141,7 +141,7 @@ func TestHandleMessage(t *testing.T) {
// Wait for goroutine to finish // Wait for goroutine to finish
select { select {
case <-snmpCh: case <-snmpCh:
case <-time.After(2 * time.Second): case <-time.After(500 * time.Millisecond):
t.Error("timed out waiting for snmp result") t.Error("timed out waiting for snmp result")
} }
}) })
@ -337,7 +337,7 @@ func TestHandleMessage(t *testing.T) {
_, _ = handleMessage(context.Background(), channelMsg{Event: "check_jobs", Payload: payload}, testPools(t), snmpCh, mtCh, credCh, monCh, checkCh, make(chan *pb.LldpTopologyResult, 1)) _, _ = handleMessage(context.Background(), channelMsg{Event: "check_jobs", Payload: payload}, testPools(t), snmpCh, mtCh, credCh, monCh, checkCh, make(chan *pb.LldpTopologyResult, 1))
select { select {
case <-checkCh: case <-checkCh:
case <-time.After(5 * time.Second): case <-time.After(time.Second):
t.Error("timed out waiting for check result") t.Error("timed out waiting for check result")
} }
}) })
@ -400,7 +400,7 @@ func TestHandleMessage(t *testing.T) {
_, _ = handleMessage(context.Background(), channelMsg{Event: "discovery_job", Payload: payload}, testPools(t), snmpCh, mtCh, credCh, monCh, checkCh, make(chan *pb.LldpTopologyResult, 1)) _, _ = handleMessage(context.Background(), channelMsg{Event: "discovery_job", Payload: payload}, testPools(t), snmpCh, mtCh, credCh, monCh, checkCh, make(chan *pb.LldpTopologyResult, 1))
select { select {
case <-snmpCh: case <-snmpCh:
case <-time.After(2 * time.Second): case <-time.After(500 * time.Millisecond):
t.Error("timed out waiting for discovery result") t.Error("timed out waiting for discovery result")
} }
}) })
@ -431,7 +431,7 @@ func TestHandleMessage(t *testing.T) {
if result.Error != "" { if result.Error != "" {
t.Errorf("unexpected error: %s", result.Error) t.Errorf("unexpected error: %s", result.Error)
} }
case <-time.After(2 * time.Second): case <-time.After(500 * time.Millisecond):
t.Error("timed out waiting for backup result") t.Error("timed out waiting for backup result")
} }
}) })
@ -560,7 +560,7 @@ func TestDispatchJob(t *testing.T) {
if result.Error == "" { if result.Error == "" {
t.Error("expected error from unreachable device") t.Error("expected error from unreachable device")
} }
case <-time.After(2 * time.Second): case <-time.After(500 * time.Millisecond):
t.Error("timed out") t.Error("timed out")
} }
}) })
@ -589,7 +589,7 @@ func TestDispatchJob(t *testing.T) {
if result.Success { if result.Success {
t.Error("expected failure") t.Error("expected failure")
} }
case <-time.After(2 * time.Second): case <-time.After(500 * time.Millisecond):
t.Error("timed out") t.Error("timed out")
} }
}) })
@ -618,7 +618,7 @@ func TestDispatchJob(t *testing.T) {
if result.Status != "success" { if result.Status != "success" {
t.Errorf("expected success, got %q", result.Status) t.Errorf("expected success, got %q", result.Status)
} }
case <-time.After(2 * time.Second): case <-time.After(500 * time.Millisecond):
t.Error("timed out") t.Error("timed out")
} }
}) })
@ -648,7 +648,7 @@ func TestDispatchJob(t *testing.T) {
select { select {
case <-snmpCh: case <-snmpCh:
case <-time.After(2 * time.Second): case <-time.After(500 * time.Millisecond):
t.Error("timed out") t.Error("timed out")
} }
}) })
@ -701,7 +701,7 @@ func TestRunSessionRejectsFailedJoin(t *testing.T) {
_, _ = conn.Write(frame) _, _ = conn.Write(frame)
// Keep connection open for a bit // Keep connection open for a bit
time.Sleep(time.Second) time.Sleep(200 * time.Millisecond)
}() }()
addr := ln.Addr().String() addr := ln.Addr().String()
@ -745,7 +745,7 @@ func TestRunSessionJoinTimeout(t *testing.T) {
frameBuf := make([]byte, 4096) frameBuf := make([]byte, 4096)
_, _ = conn.Read(frameBuf) _, _ = conn.Read(frameBuf)
time.Sleep(5 * time.Second) time.Sleep(2 * time.Second)
}() }()
addr := ln.Addr().String() addr := ln.Addr().String()
@ -932,8 +932,8 @@ func TestRunSessionCtxCancel(t *testing.T) {
if err != nil { if err != nil {
t.Errorf("expected nil error on ctx cancel, got: %v", err) t.Errorf("expected nil error on ctx cancel, got: %v", err)
} }
case <-time.After(5 * time.Second): case <-time.After(2 * time.Second):
t.Error("runSession did not exit after ctx cancel") t.Error("runSession did not exit after ctx cancel")
} }
srv.close() srv.close()
} }
@ -1088,6 +1088,10 @@ func TestRunSessionReadErrorDuringJoin(t *testing.T) {
} }
func TestRunAgentReconnectOnError(t *testing.T) { func TestRunAgentReconnectOnError(t *testing.T) {
origRetry := initialRetryDelay
defer func() { initialRetryDelay = origRetry }()
initialRetryDelay = 50 * time.Millisecond
// Server that fails first connection then succeeds, then sends restart. // Server that fails first connection then succeeds, then sends restart.
// Agent should reconnect (not exit) after both error and restart. // Agent should reconnect (not exit) after both error and restart.
ln, err := net.Listen("tcp", "127.0.0.1:0") ln, err := net.Listen("tcp", "127.0.0.1:0")
@ -1130,14 +1134,14 @@ func TestRunAgentReconnectOnError(t *testing.T) {
Ref: strPtr("1"), Ref: strPtr("1"),
}) })
_, _ = conn.Write(makeTextFrame(reply)) _, _ = conn.Write(makeTextFrame(reply))
time.Sleep(50 * time.Millisecond) time.Sleep(20 * time.Millisecond)
restart, _ := json.Marshal(channelMsg{ restart, _ := json.Marshal(channelMsg{
Topic: "agent:agent-0", Topic: "agent:agent-0",
Event: "restart", Event: "restart",
Payload: json.RawMessage(`{}`), Payload: json.RawMessage(`{}`),
}) })
_, _ = conn.Write(makeTextFrame(restart)) _, _ = conn.Write(makeTextFrame(restart))
time.Sleep(time.Second) time.Sleep(50 * time.Millisecond)
_ = conn.Close() _ = conn.Close()
default: default:
// Third connection: agent successfully reconnected after restart // Third connection: agent successfully reconnected after restart
@ -1146,7 +1150,7 @@ func TestRunAgentReconnectOnError(t *testing.T) {
} }
}() }()
ctx, cancel := context.WithTimeout(context.Background(), 10*time.Second) ctx, cancel := context.WithTimeout(context.Background(), 5*time.Second)
defer cancel() defer cancel()
done := make(chan struct{}) done := make(chan struct{})
@ -1156,7 +1160,7 @@ func TestRunAgentReconnectOnError(t *testing.T) {
}() }()
// Wait for 3rd connection attempt (proves agent reconnected after both error and restart) // Wait for 3rd connection attempt (proves agent reconnected after both error and restart)
deadline := time.After(10 * time.Second) deadline := time.After(5 * time.Second)
for { for {
if connCount.Load() >= 3 { if connCount.Load() >= 3 {
cancel() cancel()
@ -1166,7 +1170,7 @@ func TestRunAgentReconnectOnError(t *testing.T) {
case <-deadline: case <-deadline:
t.Fatalf("expected at least 3 connections, got %d", connCount.Load()) t.Fatalf("expected at least 3 connections, got %d", connCount.Load())
default: default:
time.Sleep(50 * time.Millisecond) time.Sleep(10 * time.Millisecond)
} }
} }
@ -1192,6 +1196,10 @@ func TestRunAgentContextCancellation(t *testing.T) {
} }
func TestRunAgentRestart(t *testing.T) { func TestRunAgentRestart(t *testing.T) {
origRetry := initialRetryDelay
defer func() { initialRetryDelay = origRetry }()
initialRetryDelay = 50 * time.Millisecond
// After receiving a restart event, the agent should reconnect (not exit). // After receiving a restart event, the agent should reconnect (not exit).
ln, err := net.Listen("tcp", "127.0.0.1:0") ln, err := net.Listen("tcp", "127.0.0.1:0")
if err != nil { if err != nil {
@ -1228,20 +1236,20 @@ func TestRunAgentRestart(t *testing.T) {
if count == 1 { if count == 1 {
// First connection: send restart event // First connection: send restart event
time.Sleep(50 * time.Millisecond) time.Sleep(20 * time.Millisecond)
restart, _ := json.Marshal(channelMsg{ restart, _ := json.Marshal(channelMsg{
Topic: "agent:agent-0", Topic: "agent:agent-0",
Event: "restart", Event: "restart",
Payload: json.RawMessage(`{}`), Payload: json.RawMessage(`{}`),
}) })
_, _ = conn.Write(makeTextFrame(restart)) _, _ = conn.Write(makeTextFrame(restart))
time.Sleep(time.Second) time.Sleep(200 * time.Millisecond)
} }
_ = conn.Close() _ = conn.Close()
} }
}() }()
ctx, cancel := context.WithTimeout(context.Background(), 10*time.Second) ctx, cancel := context.WithTimeout(context.Background(), 5*time.Second)
defer cancel() defer cancel()
done := make(chan struct{}) done := make(chan struct{})
@ -1261,7 +1269,7 @@ func TestRunAgentRestart(t *testing.T) {
case <-deadline: case <-deadline:
t.Fatalf("expected at least 2 connections, got %d", connCount.Load()) t.Fatalf("expected at least 2 connections, got %d", connCount.Load())
default: default:
time.Sleep(50 * time.Millisecond) time.Sleep(10 * time.Millisecond)
} }
} }
@ -1401,7 +1409,7 @@ func TestRunSessionProcessesJobResults(t *testing.T) {
srv.sendEvent("check_jobs", checkPayload) srv.sendEvent("check_jobs", checkPayload)
// Wait for results to flow through all channels // Wait for results to flow through all channels
time.Sleep(2 * time.Second) time.Sleep(500 * time.Millisecond)
close(stopDrain) close(stopDrain)
srv.close() srv.close()
}() }()
@ -1447,7 +1455,7 @@ func TestRunSessionSnmpBatchThreshold(t *testing.T) {
} }
srv.sendEvent("jobs", makeJobPayload(jobs...)) srv.sendEvent("jobs", makeJobPayload(jobs...))
time.Sleep(3 * time.Second) time.Sleep(time.Second)
close(stopDrain) close(stopDrain)
srv.close() srv.close()
}() }()
@ -1509,10 +1517,10 @@ func TestExecuteCheckCtxDoneInClosure(t *testing.T) {
executeCheck(ctx, check, p, checkCh) executeCheck(ctx, check, p, checkCh)
// Wait for the check to complete (TCP to port 1 fails fast) // Wait for the check to complete (TCP to port 1 fails fast)
time.Sleep(500 * time.Millisecond) time.Sleep(20 * time.Millisecond)
// Cancel ctx so the closure's select picks ctx.Done instead of blocked channel send // Cancel ctx so the closure's select picks ctx.Done instead of blocked channel send
cancel() cancel()
time.Sleep(100 * time.Millisecond) time.Sleep(10 * time.Millisecond)
} }
func TestRunSessionRestartInMainLoop(t *testing.T) { func TestRunSessionRestartInMainLoop(t *testing.T) {
@ -1523,10 +1531,10 @@ func TestRunSessionRestartInMainLoop(t *testing.T) {
stopDrain := make(chan struct{}) stopDrain := make(chan struct{})
go drainFrames(srv.conn, stopDrain) go drainFrames(srv.conn, stopDrain)
time.Sleep(100 * time.Millisecond) time.Sleep(50 * time.Millisecond)
// Send restart event — exercised in the main loop select // Send restart event — exercised in the main loop select
srv.sendEvent("restart", json.RawMessage(`{}`)) srv.sendEvent("restart", json.RawMessage(`{}`))
time.Sleep(time.Second) time.Sleep(200 * time.Millisecond)
close(stopDrain) close(stopDrain)
srv.close() srv.close()
}() }()
@ -1554,7 +1562,7 @@ func TestRunSessionHeartbeats(t *testing.T) {
stopDrain := make(chan struct{}) stopDrain := make(chan struct{})
go drainFrames(srv.conn, stopDrain) go drainFrames(srv.conn, stopDrain)
// Let heartbeats fire a few times // Let heartbeats fire a few times
time.Sleep(500 * time.Millisecond) time.Sleep(200 * time.Millisecond)
close(stopDrain) close(stopDrain)
srv.close() srv.close()
}() }()
@ -1566,6 +1574,10 @@ func TestRunSessionHeartbeats(t *testing.T) {
} }
func TestRunAgentCancelDuringRetry(t *testing.T) { func TestRunAgentCancelDuringRetry(t *testing.T) {
origRetry := initialRetryDelay
defer func() { initialRetryDelay = origRetry }()
initialRetryDelay = 100 * time.Millisecond
// Connects to a port nothing listens on, then cancel during retry delay // Connects to a port nothing listens on, then cancel during retry delay
ctx, cancel := context.WithCancel(context.Background()) ctx, cancel := context.WithCancel(context.Background())
@ -1576,13 +1588,13 @@ func TestRunAgentCancelDuringRetry(t *testing.T) {
}() }()
// Wait for first connection attempt to fail and retry delay to start // Wait for first connection attempt to fail and retry delay to start
time.Sleep(1500 * time.Millisecond) time.Sleep(150 * time.Millisecond)
cancel() cancel()
select { select {
case <-done: case <-done:
// runAgent returned after cancel during retry // runAgent returned after cancel during retry
case <-time.After(5 * time.Second): case <-time.After(time.Second):
t.Error("runAgent did not return after cancel during retry") t.Error("runAgent did not return after cancel during retry")
} }
} }

6
go.mod
View file

@ -4,9 +4,9 @@ go 1.25.6
require ( require (
github.com/gosnmp/gosnmp v1.43.2 github.com/gosnmp/gosnmp v1.43.2
golang.org/x/crypto v0.49.0 golang.org/x/crypto v0.52.0
golang.org/x/net v0.52.0 golang.org/x/net v0.55.0
google.golang.org/protobuf v1.36.11 google.golang.org/protobuf v1.36.11
) )
require golang.org/x/sys v0.42.0 // indirect require golang.org/x/sys v0.45.0 // indirect

16
go.sum
View file

@ -8,14 +8,14 @@ github.com/pmezard/go-difflib v1.0.0 h1:4DBwDE0NGyQoBHbLQYPwSUPoCMWR5BEzIk/f1lZb
github.com/pmezard/go-difflib v1.0.0/go.mod h1:iKH77koFhYxTK1pcRnkKkqfTogsbg7gZNVY4sRDYZ/4= github.com/pmezard/go-difflib v1.0.0/go.mod h1:iKH77koFhYxTK1pcRnkKkqfTogsbg7gZNVY4sRDYZ/4=
github.com/stretchr/testify v1.11.1 h1:7s2iGBzp5EwR7/aIZr8ao5+dra3wiQyKjjFuvgVKu7U= github.com/stretchr/testify v1.11.1 h1:7s2iGBzp5EwR7/aIZr8ao5+dra3wiQyKjjFuvgVKu7U=
github.com/stretchr/testify v1.11.1/go.mod h1:wZwfW3scLgRK+23gO65QZefKpKQRnfz6sD981Nm4B6U= github.com/stretchr/testify v1.11.1/go.mod h1:wZwfW3scLgRK+23gO65QZefKpKQRnfz6sD981Nm4B6U=
golang.org/x/crypto v0.49.0 h1:+Ng2ULVvLHnJ/ZFEq4KdcDd/cfjrrjjNSXNzxg0Y4U4= golang.org/x/crypto v0.52.0 h1:RMs7fP2rXdep0CftQlK8Uf+kibLm7qkCcradZWYz988=
golang.org/x/crypto v0.49.0/go.mod h1:ErX4dUh2UM+CFYiXZRTcMpEcN8b/1gxEuv3nODoYtCA= golang.org/x/crypto v0.52.0/go.mod h1:1QgfPxDqh0T2M/elOJtp9RvuR95kVjir0e6/BvEmGbc=
golang.org/x/net v0.52.0 h1:He/TN1l0e4mmR3QqHMT2Xab3Aj3L9qjbhRm78/6jrW0= golang.org/x/net v0.55.0 h1:bcvxaJn3e1U6InsFWt1JUq1aSjnRxLzT2rtD2KfkDF8=
golang.org/x/net v0.52.0/go.mod h1:R1MAz7uMZxVMualyPXb+VaqGSa3LIaUqk0eEt3w36Sw= golang.org/x/net v0.55.0/go.mod h1:L5U2KuzuOe1lY7Z+aWVIKK6qEeJXnXV9yzGA+WCHJww=
golang.org/x/sys v0.42.0 h1:omrd2nAlyT5ESRdCLYdm3+fMfNFE/+Rf4bDIQImRJeo= golang.org/x/sys v0.45.0 h1:dO4czNzziLiiXplLQgBCEpCvXQ3dnkn0SdaZSYdQ+FY=
golang.org/x/sys v0.42.0/go.mod h1:4GL1E5IUh+htKOUEOaiffhrAeqysfVGipDYzABqnCmw= golang.org/x/sys v0.45.0/go.mod h1:4GL1E5IUh+htKOUEOaiffhrAeqysfVGipDYzABqnCmw=
golang.org/x/term v0.41.0 h1:QCgPso/Q3RTJx2Th4bDLqML4W6iJiaXFq2/ftQF13YU= golang.org/x/term v0.43.0 h1:S4RLU2sB31O/NCl+zFN9Aru9A/Cq2aqKpTZJ6B+DwT4=
golang.org/x/term v0.41.0/go.mod h1:3pfBgksrReYfZ5lvYM0kSO0LIkAl4Yl2bXOkKP7Ec2A= golang.org/x/term v0.43.0/go.mod h1:lrhlHNdQJHO+1qVYiHfFKVuVioJIheAc3fBSMFYEIsk=
google.golang.org/protobuf v1.36.11 h1:fV6ZwhNocDyBLK0dj+fg8ektcVegBBuEolpbTQyBNVE= google.golang.org/protobuf v1.36.11 h1:fV6ZwhNocDyBLK0dj+fg8ektcVegBBuEolpbTQyBNVE=
google.golang.org/protobuf v1.36.11/go.mod h1:HTf+CrKn2C3g5S8VImy6tdcUvCska2kB7j23XfzDpco= google.golang.org/protobuf v1.36.11/go.mod h1:HTf+CrKn2C3g5S8VImy6tdcUvCska2kB7j23XfzDpco=
gopkg.in/yaml.v3 v3.0.1 h1:fxVm/GzAzEWqLHuvctI91KS9hhNmmWOoWu0XTYJS7CA= gopkg.in/yaml.v3 v3.0.1 h1:fxVm/GzAzEWqLHuvctI91KS9hhNmmWOoWu0XTYJS7CA=

View file

@ -14,7 +14,10 @@ func TestPingDeviceLocalhost(t *testing.T) {
if runtime.GOOS == "windows" { if runtime.GOOS == "windows" {
t.Skip("skipping ping test on windows") t.Skip("skipping ping test on windows")
} }
ms, err := pingDevice("127.0.0.1", 5000) if testing.Short() {
t.Skip("skipping real ICMP ping in short mode")
}
ms, err := pingDevice("127.0.0.1", 2000)
if err != nil { if err != nil {
t.Skipf("ping not available: %v", err) t.Skipf("ping not available: %v", err)
} }
@ -34,7 +37,10 @@ func TestPingDeviceIPv6(t *testing.T) {
if runtime.GOOS == "windows" { if runtime.GOOS == "windows" {
t.Skip("skipping ping test on windows") t.Skip("skipping ping test on windows")
} }
ms, err := pingDevice("::1", 5000) if testing.Short() {
t.Skip("skipping real ICMP ping in short mode")
}
ms, err := pingDevice("::1", 2000)
if err != nil { if err != nil {
t.Skipf("IPv6 not available: %v", err) t.Skipf("IPv6 not available: %v", err)
} }
@ -44,7 +50,10 @@ func TestPingDeviceIPv6(t *testing.T) {
} }
func TestIcmpPingLocalhost(t *testing.T) { func TestIcmpPingLocalhost(t *testing.T) {
ms, err := icmpPing("127.0.0.1", 5000) if testing.Short() {
t.Skip("skipping real ICMP ping in short mode")
}
ms, err := icmpPing("127.0.0.1", 2000)
if err != nil { if err != nil {
t.Skipf("ICMP not available: %v", err) t.Skipf("ICMP not available: %v", err)
} }
@ -54,7 +63,10 @@ func TestIcmpPingLocalhost(t *testing.T) {
} }
func TestIcmpPingIPv6(t *testing.T) { func TestIcmpPingIPv6(t *testing.T) {
ms, err := icmpPing("::1", 5000) if testing.Short() {
t.Skip("skipping real ICMP ping in short mode")
}
ms, err := icmpPing("::1", 2000)
if err != nil { if err != nil {
t.Skipf("IPv6 ICMP not available: %v", err) t.Skipf("IPv6 ICMP not available: %v", err)
} }
@ -139,7 +151,10 @@ func TestExecPingLocalhost(t *testing.T) {
if runtime.GOOS == "windows" { if runtime.GOOS == "windows" {
t.Skip("skipping on windows") t.Skip("skipping on windows")
} }
ms, err := execPing("127.0.0.1", 5000) if testing.Short() {
t.Skip("skipping real exec ping in short mode")
}
ms, err := execPing("127.0.0.1", 2000)
if err != nil { if err != nil {
t.Skipf("ping command not available: %v", err) t.Skipf("ping command not available: %v", err)
} }
@ -159,7 +174,10 @@ func TestExecPingIPv6Localhost(t *testing.T) {
if runtime.GOOS == "windows" { if runtime.GOOS == "windows" {
t.Skip("skipping on windows") t.Skip("skipping on windows")
} }
ms, err := execPing("::1", 5000) if testing.Short() {
t.Skip("skipping real exec ping in short mode")
}
ms, err := execPing("::1", 2000)
if err != nil { if err != nil {
t.Skipf("ping6 not available: %v", err) t.Skipf("ping6 not available: %v", err)
} }
@ -185,6 +203,9 @@ func TestPingDeviceFallbackToExec(t *testing.T) {
if runtime.GOOS == "windows" { if runtime.GOOS == "windows" {
t.Skip("skipping on windows") t.Skip("skipping on windows")
} }
if testing.Short() {
t.Skip("skipping real ping in short mode")
}
// Mock icmpListenPacket to always fail → forces fallback to execPing // Mock icmpListenPacket to always fail → forces fallback to execPing
origListen := icmpListenPacket origListen := icmpListenPacket
defer func() { icmpListenPacket = origListen }() defer func() { icmpListenPacket = origListen }()
@ -193,7 +214,7 @@ func TestPingDeviceFallbackToExec(t *testing.T) {
return nil, fmt.Errorf("permission denied") return nil, fmt.Errorf("permission denied")
} }
ms, err := pingDevice("127.0.0.1", 5000) ms, err := pingDevice("127.0.0.1", 2000)
if err != nil { if err != nil {
t.Skipf("exec ping fallback not available: %v", err) t.Skipf("exec ping fallback not available: %v", err)
} }
@ -288,6 +309,9 @@ func TestIcmpPingNonICMPUnavailableError(t *testing.T) {
} }
func TestIcmpPingUDPFallback(t *testing.T) { func TestIcmpPingUDPFallback(t *testing.T) {
if testing.Short() {
t.Skip("skipping real ICMP in short mode")
}
// Mock raw ICMP to fail, forcing UDP fallback path in icmpPing // Mock raw ICMP to fail, forcing UDP fallback path in icmpPing
origListen := icmpListenPacket origListen := icmpListenPacket
defer func() { icmpListenPacket = origListen }() defer func() { icmpListenPacket = origListen }()
@ -303,7 +327,7 @@ func TestIcmpPingUDPFallback(t *testing.T) {
return icmp.ListenPacket(network, address) return icmp.ListenPacket(network, address)
} }
ms, err := icmpPing("127.0.0.1", 5000) ms, err := icmpPing("127.0.0.1", 2000)
if err != nil { if err != nil {
t.Skipf("UDP ICMP not available: %v", err) t.Skipf("UDP ICMP not available: %v", err)
} }

View file

@ -712,6 +712,10 @@ func TestWSDialMissingAcceptHeader(t *testing.T) {
} }
func TestWSDialMissingUpgradeHeader(t *testing.T) { func TestWSDialMissingUpgradeHeader(t *testing.T) {
origTimeout := wsHandshakeTimeout
defer func() { wsHandshakeTimeout = origTimeout }()
wsHandshakeTimeout = time.Second
ln, err := net.Listen("tcp", "127.0.0.1:0") ln, err := net.Listen("tcp", "127.0.0.1:0")
if err != nil { if err != nil {
t.Fatal(err) t.Fatal(err)
@ -743,6 +747,10 @@ func TestWSDialMissingUpgradeHeader(t *testing.T) {
} }
func TestWSDialRejectsNon101Status(t *testing.T) { func TestWSDialRejectsNon101Status(t *testing.T) {
origTimeout := wsHandshakeTimeout
defer func() { wsHandshakeTimeout = origTimeout }()
wsHandshakeTimeout = time.Second
ln, err := net.Listen("tcp", "127.0.0.1:0") ln, err := net.Listen("tcp", "127.0.0.1:0")
if err != nil { if err != nil {
t.Fatal(err) t.Fatal(err)
@ -774,17 +782,44 @@ func TestWSDialRejectsNon101Status(t *testing.T) {
} }
func TestWSDialDefaultPorts(t *testing.T) { func TestWSDialDefaultPorts(t *testing.T) {
// Test that ws:// defaults to port 80 — will fail to connect but verifies URL parsing origTimeout := wsHandshakeTimeout
origDial := netDial
origTLS := tlsDial
defer func() {
wsHandshakeTimeout = origTimeout
netDial = origDial
tlsDial = origTLS
}()
wsHandshakeTimeout = time.Second
var wsCalls, wssCalls int
netDial = func(network, addr string) (net.Conn, error) {
wsCalls++
if addr == "127.0.0.1:80" {
return nil, fmt.Errorf("connection refused (mock)")
}
return nil, fmt.Errorf("mock dial: %s", addr)
}
tlsDial = func(network, addr string) (net.Conn, error) {
wssCalls++
return nil, fmt.Errorf("tls dial refused (mock)")
}
_, err := WSDial("ws://127.0.0.1/path") _, err := WSDial("ws://127.0.0.1/path")
if err == nil { if err == nil {
t.Error("expected connection error (nothing on port 80)") t.Error("expected connection error (nothing on port 80)")
} }
if wsCalls == 0 {
t.Error("expected netDial to be called for ws://")
}
// Test that wss:// defaults to port 443
_, err = WSDial("wss://127.0.0.1/path") _, err = WSDial("wss://127.0.0.1/path")
if err == nil { if err == nil {
t.Error("expected connection error (nothing on port 443)") t.Error("expected connection error (nothing on port 443)")
} }
if wssCalls == 0 {
t.Error("expected tlsDial to be called for wss://")
}
} }
func TestWSDialRealHTTPServer(t *testing.T) { func TestWSDialRealHTTPServer(t *testing.T) {