Files
OmniSocketGo/cmd/kcpping/main_test.go

197 lines
5.6 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 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
}