- 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
340 lines
8.7 KiB
Go
340 lines
8.7 KiB
Go
package main
|
|
|
|
import (
|
|
"fmt"
|
|
"net"
|
|
"runtime"
|
|
"strings"
|
|
"testing"
|
|
|
|
"golang.org/x/net/icmp"
|
|
)
|
|
|
|
func TestPingDeviceLocalhost(t *testing.T) {
|
|
if runtime.GOOS == "windows" {
|
|
t.Skip("skipping ping test on windows")
|
|
}
|
|
if testing.Short() {
|
|
t.Skip("skipping real ICMP ping in short mode")
|
|
}
|
|
ms, err := pingDevice("127.0.0.1", 2000)
|
|
if err != nil {
|
|
t.Skipf("ping not available: %v", err)
|
|
}
|
|
if ms <= 0 {
|
|
t.Errorf("expected positive response time, got %v", ms)
|
|
}
|
|
}
|
|
|
|
func TestPingDeviceInvalidIP(t *testing.T) {
|
|
_, err := pingDevice("not-an-ip", 5000)
|
|
if err == nil {
|
|
t.Error("expected error for invalid IP")
|
|
}
|
|
}
|
|
|
|
func TestPingDeviceIPv6(t *testing.T) {
|
|
if runtime.GOOS == "windows" {
|
|
t.Skip("skipping ping test on windows")
|
|
}
|
|
if testing.Short() {
|
|
t.Skip("skipping real ICMP ping in short mode")
|
|
}
|
|
ms, err := pingDevice("::1", 2000)
|
|
if err != nil {
|
|
t.Skipf("IPv6 not available: %v", err)
|
|
}
|
|
if ms <= 0 {
|
|
t.Errorf("expected positive response time, got %v", ms)
|
|
}
|
|
}
|
|
|
|
func TestIcmpPingLocalhost(t *testing.T) {
|
|
if testing.Short() {
|
|
t.Skip("skipping real ICMP ping in short mode")
|
|
}
|
|
ms, err := icmpPing("127.0.0.1", 2000)
|
|
if err != nil {
|
|
t.Skipf("ICMP not available: %v", err)
|
|
}
|
|
if ms <= 0 {
|
|
t.Errorf("expected positive response time, got %v", ms)
|
|
}
|
|
}
|
|
|
|
func TestIcmpPingIPv6(t *testing.T) {
|
|
if testing.Short() {
|
|
t.Skip("skipping real ICMP ping in short mode")
|
|
}
|
|
ms, err := icmpPing("::1", 2000)
|
|
if err != nil {
|
|
t.Skipf("IPv6 ICMP not available: %v", err)
|
|
}
|
|
if ms <= 0 {
|
|
t.Errorf("expected positive response time, got %v", ms)
|
|
}
|
|
}
|
|
|
|
func TestIcmpPingInvalidIP(t *testing.T) {
|
|
_, err := icmpPing("not-an-ip", 5000)
|
|
if err == nil {
|
|
t.Error("expected error for invalid IP")
|
|
}
|
|
}
|
|
|
|
func TestErrICMPUnavailableError(t *testing.T) {
|
|
err := &errICMPUnavailable{err: fmt.Errorf("permission denied")}
|
|
if err.Error() != "permission denied" {
|
|
t.Errorf("got %q, want %q", err.Error(), "permission denied")
|
|
}
|
|
}
|
|
|
|
func TestParsePingTime(t *testing.T) {
|
|
tests := []struct {
|
|
name string
|
|
output string
|
|
want float64
|
|
wantErr bool
|
|
}{
|
|
{
|
|
name: "standard linux",
|
|
output: "64 bytes from 8.8.8.8: icmp_seq=1 ttl=118 time=12.3 ms",
|
|
want: 12.3,
|
|
},
|
|
{
|
|
name: "localhost",
|
|
output: "64 bytes from localhost: icmp_seq=1 ttl=64 time=0.123 ms",
|
|
want: 0.123,
|
|
},
|
|
{
|
|
name: "multiline",
|
|
output: "PING 8.8.8.8 (8.8.8.8): 56 data bytes\n64 bytes from 8.8.8.8: icmp_seq=0 ttl=118 time=15.7 ms\n--- 8.8.8.8 ping statistics ---",
|
|
want: 15.7,
|
|
},
|
|
{
|
|
name: "no time field",
|
|
output: "Request timeout for icmp_seq 0",
|
|
wantErr: true,
|
|
},
|
|
{
|
|
name: "empty",
|
|
output: "",
|
|
wantErr: true,
|
|
},
|
|
{
|
|
name: "time= without ms suffix",
|
|
output: "64 bytes from 10.0.0.1: icmp_seq=1 ttl=64 time=1.234\n",
|
|
want: 1.234,
|
|
},
|
|
}
|
|
for _, tt := range tests {
|
|
t.Run(tt.name, func(t *testing.T) {
|
|
got, err := parsePingTime(tt.output)
|
|
if tt.wantErr {
|
|
if err == nil {
|
|
t.Errorf("expected error, got %v", got)
|
|
}
|
|
return
|
|
}
|
|
if err != nil {
|
|
t.Errorf("unexpected error: %v", err)
|
|
return
|
|
}
|
|
if got != tt.want {
|
|
t.Errorf("got %v, want %v", got, tt.want)
|
|
}
|
|
})
|
|
}
|
|
}
|
|
|
|
func TestExecPingLocalhost(t *testing.T) {
|
|
if runtime.GOOS == "windows" {
|
|
t.Skip("skipping on windows")
|
|
}
|
|
if testing.Short() {
|
|
t.Skip("skipping real exec ping in short mode")
|
|
}
|
|
ms, err := execPing("127.0.0.1", 2000)
|
|
if err != nil {
|
|
t.Skipf("ping command not available: %v", err)
|
|
}
|
|
if ms <= 0 {
|
|
t.Errorf("expected positive response time, got %v", ms)
|
|
}
|
|
}
|
|
|
|
func TestExecPingInvalidIP(t *testing.T) {
|
|
_, err := execPing("not-an-ip", 5000)
|
|
if err == nil {
|
|
t.Error("expected error for invalid IP")
|
|
}
|
|
}
|
|
|
|
func TestExecPingIPv6Localhost(t *testing.T) {
|
|
if runtime.GOOS == "windows" {
|
|
t.Skip("skipping on windows")
|
|
}
|
|
if testing.Short() {
|
|
t.Skip("skipping real exec ping in short mode")
|
|
}
|
|
ms, err := execPing("::1", 2000)
|
|
if err != nil {
|
|
t.Skipf("ping6 not available: %v", err)
|
|
}
|
|
if ms <= 0 {
|
|
t.Errorf("expected positive response time, got %v", ms)
|
|
}
|
|
}
|
|
|
|
func TestExecPingUnreachable(t *testing.T) {
|
|
if runtime.GOOS == "windows" {
|
|
t.Skip("skipping on windows")
|
|
}
|
|
_, err := execPing("192.0.2.1", 1000) // TEST-NET-1 — unreachable
|
|
if err == nil {
|
|
t.Error("expected error for unreachable host")
|
|
}
|
|
if err != nil && !strings.Contains(err.Error(), "ping failed") {
|
|
t.Errorf("expected 'ping failed' in error, got: %v", err)
|
|
}
|
|
}
|
|
|
|
func TestPingDeviceFallbackToExec(t *testing.T) {
|
|
if runtime.GOOS == "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
|
|
origListen := icmpListenPacket
|
|
defer func() { icmpListenPacket = origListen }()
|
|
|
|
icmpListenPacket = func(network, address string) (*icmp.PacketConn, error) {
|
|
return nil, fmt.Errorf("permission denied")
|
|
}
|
|
|
|
ms, err := pingDevice("127.0.0.1", 2000)
|
|
if err != nil {
|
|
t.Skipf("exec ping fallback not available: %v", err)
|
|
}
|
|
if ms <= 0 {
|
|
t.Errorf("expected positive response time via exec fallback, got %v", ms)
|
|
}
|
|
}
|
|
|
|
func TestPingDeviceNonICMPError(t *testing.T) {
|
|
// When icmpPing returns a non-errICMPUnavailable error, pingDevice should NOT
|
|
// fall back to exec — it should return the error directly.
|
|
origListen := icmpListenPacket
|
|
defer func() { icmpListenPacket = origListen }()
|
|
|
|
// First call (raw ICMP) returns errICMPUnavailable → triggers UDP fallback
|
|
// Second call (UDP ICMP) returns a real write error → not errICMPUnavailable
|
|
calls := 0
|
|
icmpListenPacket = func(network, address string) (*icmp.PacketConn, error) {
|
|
calls++
|
|
if calls == 1 {
|
|
// Raw ICMP fails with errICMPUnavailable
|
|
return nil, fmt.Errorf("permission denied")
|
|
}
|
|
// UDP ICMP also fails with errICMPUnavailable
|
|
return nil, fmt.Errorf("also denied")
|
|
}
|
|
|
|
_, err := pingDevice("127.0.0.1", 1000)
|
|
// Both ICMP attempts fail with errICMPUnavailable, so it falls back to exec
|
|
// which should succeed for localhost
|
|
if err != nil {
|
|
t.Skipf("ping fallback not available: %v", err)
|
|
}
|
|
}
|
|
|
|
func TestDoICMPPingIPv6Network(t *testing.T) {
|
|
// Test with the IPv6 raw network to cover the ipv6-icmp branches
|
|
ip := net.ParseIP("::1")
|
|
_, err := doICMPPing(ip, "ip6:ipv6-icmp", false, 1000)
|
|
if err != nil {
|
|
t.Skipf("IPv6 ICMP not available: %v", err)
|
|
}
|
|
}
|
|
|
|
func TestDoICMPPingUDPNetwork(t *testing.T) {
|
|
// Test with UDP network to cover the udp address branch
|
|
ip := net.ParseIP("127.0.0.1")
|
|
_, err := doICMPPing(ip, "udp4", true, 1000)
|
|
if err != nil {
|
|
t.Skipf("UDP ICMP not available: %v", err)
|
|
}
|
|
}
|
|
|
|
func TestDoICMPPingTimeout(t *testing.T) {
|
|
// Ping unreachable IP with short timeout → covers icmp read timeout error
|
|
ip := net.ParseIP("192.0.2.1") // TEST-NET-1 — unreachable
|
|
_, err := doICMPPing(ip, "udp4", true, 100)
|
|
if err == nil {
|
|
t.Error("expected timeout error for unreachable host")
|
|
}
|
|
if err != nil && !strings.Contains(err.Error(), "icmp read") {
|
|
t.Logf("got error (expected icmp read timeout): %v", err)
|
|
}
|
|
}
|
|
|
|
func TestDoICMPPingIPv6Timeout(t *testing.T) {
|
|
// IPv6 unreachable — covers the ipv6 branch in doICMPPing
|
|
ip := net.ParseIP("100::1") // Unreachable IPv6
|
|
_, err := doICMPPing(ip, "udp6", false, 100)
|
|
if err != nil {
|
|
// May fail with various errors depending on system IPv6 support
|
|
t.Logf("IPv6 ICMP error (expected): %v", err)
|
|
}
|
|
}
|
|
|
|
func TestIcmpPingNonICMPUnavailableError(t *testing.T) {
|
|
// When raw ICMP returns a non-errICMPUnavailable error, icmpPing should
|
|
// return that error without falling back to UDP.
|
|
origListen := icmpListenPacket
|
|
defer func() { icmpListenPacket = origListen }()
|
|
|
|
// Raw ICMP succeeds (opens a connection), but pinging unreachable IP will timeout.
|
|
// The timeout error is NOT errICMPUnavailable, so icmpPing returns it directly.
|
|
icmpListenPacket = func(network, address string) (*icmp.PacketConn, error) {
|
|
return icmp.ListenPacket("udp4", address)
|
|
}
|
|
|
|
_, err := icmpPing("192.0.2.1", 100) // TEST-NET-1, 100ms timeout
|
|
if err == nil {
|
|
t.Error("expected error for unreachable host")
|
|
}
|
|
}
|
|
|
|
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
|
|
origListen := icmpListenPacket
|
|
defer func() { icmpListenPacket = origListen }()
|
|
|
|
calls := 0
|
|
icmpListenPacket = func(network, address string) (*icmp.PacketConn, error) {
|
|
calls++
|
|
if calls == 1 {
|
|
// Raw ICMP fails
|
|
return nil, fmt.Errorf("permission denied")
|
|
}
|
|
// UDP ICMP uses the real implementation
|
|
return icmp.ListenPacket(network, address)
|
|
}
|
|
|
|
ms, err := icmpPing("127.0.0.1", 2000)
|
|
if err != nil {
|
|
t.Skipf("UDP ICMP not available: %v", err)
|
|
}
|
|
if ms <= 0 {
|
|
t.Errorf("expected positive response time, got %v", ms)
|
|
}
|
|
if calls < 2 {
|
|
t.Errorf("expected at least 2 ListenPacket calls (raw + udp), got %d", calls)
|
|
}
|
|
}
|