204 lines
5.9 KiB
Go
204 lines
5.9 KiB
Go
package main
|
|
|
|
import (
|
|
"bytes"
|
|
"strings"
|
|
"testing"
|
|
"time"
|
|
)
|
|
|
|
func TestParseConfigDefaults(t *testing.T) {
|
|
cfg, err := parseConfig([]string{"-to", "peer-b"}, ioDiscard{})
|
|
if err != nil {
|
|
t.Fatalf("parseConfig() error = %v", err)
|
|
}
|
|
|
|
if cfg.id != defaultPeerID {
|
|
t.Fatalf("id = %q, want %q", cfg.id, defaultPeerID)
|
|
}
|
|
if cfg.server != defaultServer {
|
|
t.Fatalf("server = %q, want %q", cfg.server, defaultServer)
|
|
}
|
|
if cfg.count != defaultCount {
|
|
t.Fatalf("count = %d, want %d", cfg.count, defaultCount)
|
|
}
|
|
if cfg.interval != defaultInterval {
|
|
t.Fatalf("interval = %s, want %s", cfg.interval, defaultInterval)
|
|
}
|
|
if cfg.size != defaultSize {
|
|
t.Fatalf("size = %d, want %d", cfg.size, defaultSize)
|
|
}
|
|
if cfg.timeout != defaultTimeout {
|
|
t.Fatalf("timeout = %s, want %s", cfg.timeout, defaultTimeout)
|
|
}
|
|
}
|
|
|
|
func TestParseConfigRequiresTargetInPingMode(t *testing.T) {
|
|
_, err := parseConfig([]string{"-echo=false"}, ioDiscard{})
|
|
if err == nil || !strings.Contains(err.Error(), "flag -to is required") {
|
|
t.Fatalf("parseConfig() error = %v, want missing -to error", err)
|
|
}
|
|
}
|
|
|
|
func TestParseConfigAllowsEchoWithoutTarget(t *testing.T) {
|
|
cfg, err := parseConfig([]string{"-echo"}, ioDiscard{})
|
|
if err != nil {
|
|
t.Fatalf("parseConfig() error = %v", err)
|
|
}
|
|
if !cfg.echo {
|
|
t.Fatal("echo = false, want true")
|
|
}
|
|
}
|
|
|
|
func TestParseConfigRejectsBindDeviceFlag(t *testing.T) {
|
|
_, err := parseConfig([]string{"-to", "peer-b", "-bind-device", "wwan0"}, ioDiscard{})
|
|
if err == nil || !strings.Contains(err.Error(), "flag provided but not defined") {
|
|
t.Fatalf("parseConfig() error = %v, want unknown flag error", err)
|
|
}
|
|
}
|
|
|
|
func TestBuildPingPayloadUsesExactSize(t *testing.T) {
|
|
body, err := buildPingPayload(7, 123456789, 96)
|
|
if err != nil {
|
|
t.Fatalf("buildPingPayload() error = %v", err)
|
|
}
|
|
if len(body) != 96 {
|
|
t.Fatalf("len(body) = %d, want 96", len(body))
|
|
}
|
|
|
|
payload, err := parsePingPayload(body)
|
|
if err != nil {
|
|
t.Fatalf("parsePingPayload() error = %v", err)
|
|
}
|
|
if payload.Seq != 7 {
|
|
t.Fatalf("seq = %d, want 7", payload.Seq)
|
|
}
|
|
if payload.TSUnixNano != 123456789 {
|
|
t.Fatalf("ts_ns = %d, want 123456789", payload.TSUnixNano)
|
|
}
|
|
}
|
|
|
|
func TestBuildPingPayloadRejectsTooSmallSize(t *testing.T) {
|
|
_, err := buildPingPayload(1, 123456789, 8)
|
|
if err == nil || !strings.Contains(err.Error(), "too small") {
|
|
t.Fatalf("buildPingPayload() error = %v, want size too small error", err)
|
|
}
|
|
}
|
|
|
|
func TestParsePingPayloadRejectsInvalidJSON(t *testing.T) {
|
|
_, err := parsePingPayload([]byte("not-json"))
|
|
if err == nil || !strings.Contains(err.Error(), "decode ping payload") {
|
|
t.Fatalf("parsePingPayload() error = %v, want decode error", err)
|
|
}
|
|
}
|
|
|
|
func TestPingTrackerHandlesMatchedDuplicateAndTimeout(t *testing.T) {
|
|
tracker := newPingTracker(50 * time.Millisecond)
|
|
sentAt := time.Unix(0, 100)
|
|
tracker.markSent(1, sentAt)
|
|
|
|
match := tracker.observeReply(pingPayload{Seq: 1, TSUnixNano: sentAt.UnixNano()}, sentAt.Add(12*time.Millisecond))
|
|
if match.disposition != replyMatched {
|
|
t.Fatalf("first disposition = %v, want matched", match.disposition)
|
|
}
|
|
if match.rtt != 12*time.Millisecond {
|
|
t.Fatalf("first rtt = %s, want 12ms", match.rtt)
|
|
}
|
|
|
|
duplicate := tracker.observeReply(pingPayload{Seq: 1, TSUnixNano: sentAt.UnixNano()}, sentAt.Add(20*time.Millisecond))
|
|
if duplicate.disposition != replyDuplicate {
|
|
t.Fatalf("second disposition = %v, want duplicate", duplicate.disposition)
|
|
}
|
|
|
|
tracker.markSent(2, sentAt)
|
|
expired := tracker.expire(sentAt.Add(60 * time.Millisecond))
|
|
if len(expired) != 1 || expired[0] != 2 {
|
|
t.Fatalf("expired = %v, want [2]", expired)
|
|
}
|
|
|
|
late := tracker.observeReply(pingPayload{Seq: 2, TSUnixNano: sentAt.UnixNano()}, sentAt.Add(70*time.Millisecond))
|
|
if late.disposition != replyDuplicate {
|
|
t.Fatalf("late disposition = %v, want duplicate", late.disposition)
|
|
}
|
|
|
|
unexpected := tracker.observeReply(pingPayload{Seq: 99, TSUnixNano: sentAt.UnixNano()}, sentAt.Add(80*time.Millisecond))
|
|
if unexpected.disposition != replyUnexpected {
|
|
t.Fatalf("unexpected disposition = %v, want unexpected", unexpected.disposition)
|
|
}
|
|
}
|
|
|
|
func TestCalculateRTTSummary(t *testing.T) {
|
|
summary := calculateRTTSummary(
|
|
[]time.Duration{
|
|
10 * time.Millisecond,
|
|
20 * time.Millisecond,
|
|
30 * time.Millisecond,
|
|
40 * time.Millisecond,
|
|
50 * time.Millisecond,
|
|
},
|
|
6,
|
|
2,
|
|
)
|
|
|
|
if summary.Sent != 6 {
|
|
t.Fatalf("Sent = %d, want 6", summary.Sent)
|
|
}
|
|
if summary.Received != 5 {
|
|
t.Fatalf("Received = %d, want 5", summary.Received)
|
|
}
|
|
if summary.Duplicates != 2 {
|
|
t.Fatalf("Duplicates = %d, want 2", summary.Duplicates)
|
|
}
|
|
if summary.LossPct != (float64(1) * 100 / 6) {
|
|
t.Fatalf("LossPct = %f, want %f", summary.LossPct, float64(1)*100/6)
|
|
}
|
|
if summary.Min != 10*time.Millisecond {
|
|
t.Fatalf("Min = %s, want 10ms", summary.Min)
|
|
}
|
|
if summary.Avg != 30*time.Millisecond {
|
|
t.Fatalf("Avg = %s, want 30ms", summary.Avg)
|
|
}
|
|
if summary.Max != 50*time.Millisecond {
|
|
t.Fatalf("Max = %s, want 50ms", summary.Max)
|
|
}
|
|
if summary.P50 != 30*time.Millisecond {
|
|
t.Fatalf("P50 = %s, want 30ms", summary.P50)
|
|
}
|
|
if summary.P95 != 50*time.Millisecond {
|
|
t.Fatalf("P95 = %s, want 50ms", summary.P95)
|
|
}
|
|
if summary.P99 != 50*time.Millisecond {
|
|
t.Fatalf("P99 = %s, want 50ms", summary.P99)
|
|
}
|
|
if summary.StdDev == 0 {
|
|
t.Fatal("StdDev = 0, want non-zero")
|
|
}
|
|
}
|
|
|
|
func TestWriteSummaryUsesNAWithoutSamples(t *testing.T) {
|
|
var buf bytes.Buffer
|
|
err := writeSummary(&buf, "host", rttSummary{
|
|
Sent: 3,
|
|
Received: 0,
|
|
Duplicates: 1,
|
|
LossPct: 100,
|
|
})
|
|
if err != nil {
|
|
t.Fatalf("writeSummary() error = %v", err)
|
|
}
|
|
|
|
out := buf.String()
|
|
if !strings.Contains(out, "3 packets transmitted, 0 received, 1 duplicates, 100.00% packet loss") {
|
|
t.Fatalf("summary output missing counters: %q", out)
|
|
}
|
|
if !strings.Contains(out, "n/a/n/a/n/a/n/a/n/a/n/a") {
|
|
t.Fatalf("summary output missing n/a metrics: %q", out)
|
|
}
|
|
}
|
|
|
|
type ioDiscard struct{}
|
|
|
|
func (ioDiscard) Write(p []byte) (int, error) {
|
|
return len(p), nil
|
|
}
|