feat: 基于KCP的Ping程序,利用目前的传输栈
This commit is contained in:
196
cmd/kcpping/main_test.go
Normal file
196
cmd/kcpping/main_test.go
Normal file
@@ -0,0 +1,196 @@
|
||||
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
|
||||
}
|
||||
Reference in New Issue
Block a user