package load import ( "bytes" "io" "net" "os" "strings" "testing" "time" "git.asio.asia/nixevol/NixMsg/test/harness" "github.com/mochi-mqtt/server/v2/packets" ) func TestEncodeConnectMQTT5(t *testing.T) { raw, err := encodeConnect("ep-1", "password1234") if err != nil { t.Fatal(err) } if raw[0]>>4 != packets.Connect { t.Fatalf("type=0x%02x", raw[0]) } rem, n, err := decodeRemainingLength(raw[1:]) if err != nil { t.Fatal(err) } body := raw[1+n:] pk := packets.Packet{ ProtocolVersion: mqttProtocolLevel5, FixedHeader: packets.FixedHeader{Type: packets.Connect, Remaining: rem}, } if err := pk.ConnectDecode(body); err != nil { t.Fatal(err) } if pk.ProtocolVersion != mqttProtocolLevel5 { t.Fatalf("protocol version %d", pk.ProtocolVersion) } if !pk.Connect.Clean { t.Fatal("CleanStart=false") } if pk.Connect.Keepalive != mqttKeepaliveSec { t.Fatalf("keepalive=%d", pk.Connect.Keepalive) } if pk.Connect.ClientIdentifier != "ep-1" { t.Fatalf("client id %q", pk.Connect.ClientIdentifier) } if string(pk.Connect.Username) != "ep-1" { t.Fatalf("username %q", pk.Connect.Username) } if string(pk.Connect.Password) != "password1234" { t.Fatalf("password mismatch") } if !pk.Connect.UsernameFlag || !pk.Connect.PasswordFlag { t.Fatal("username/password flag") } if !bytes.Contains(raw, []byte{0, 4, 'M', 'Q', 'T', 'T', mqttProtocolLevel5}) { t.Fatal("CONNECT 不是 MQTT 5") } } func TestConnackReasonMQTT5(t *testing.T) { reason, err := connackReason([]byte{0x20, 0x03, 0x00, 0x00, 0x00}) if err != nil || reason != 0 { t.Fatalf("mqtt5 connack: %d %v", reason, err) } reason, err = connackReason([]byte{0x20, 0x02, 0x00, 0x87}) if err != nil || reason != 0x87 { t.Fatalf("mqtt311-style: %d %v", reason, err) } } func TestPercentiles(t *testing.T) { empty := calcPercentiles(nil) if empty.N != 0 { t.Fatalf("empty n=%d", empty.N) } one := calcPercentiles([]float64{7}) if one.P50 != 7 || one.P95 != 7 || one.P99 != 7 { t.Fatalf("%+v", one) } vals := []float64{1, 2, 3, 4, 5, 6, 7, 8, 9, 10} p := calcPercentiles(vals) if p.P50 != 5 || p.P95 != 10 || p.P99 != 10 { t.Fatalf("%+v", p) } } func TestEndpointIDs(t *testing.T) { ids, err := EndpointIDs("qb", 3) if err != nil { t.Fatal(err) } if strings.Join(ids, ",") != "qb0000,qb0001,qb0002" { t.Fatalf("%v", ids) } } func TestMQTT5ConnectFakeBroker(t *testing.T) { addr, stop := startFakeMQTT5(t) defer stop() mc, err := harness.DialMQTTTCP(addr, 2*time.Second) if err != nil { t.Fatal(err) } defer func() { _ = mc.Close() }() pkt, err := encodeConnect("ep1", "password1234") if err != nil { t.Fatal(err) } if sendErr := mc.Send(pkt); sendErr != nil { t.Fatal(sendErr) } ack, err := mc.Recv() if err != nil { t.Fatal(err) } reason, err := connackReason(ack) if err != nil || reason != 0 { t.Fatalf("reason=%d err=%v ack=%x", reason, err, ack) } } func startFakeMQTT5(t *testing.T) (addr string, closeFn func()) { t.Helper() ln, err := net.Listen("tcp", "127.0.0.1:0") if err != nil { t.Fatalf("listen: %v", err) } go func() { for { conn, err := ln.Accept() if err != nil { return } go func(c net.Conn) { defer func() { _ = c.Close() }() _ = c.SetDeadline(time.Now().Add(5 * time.Second)) buf := make([]byte, 2048) n, err := c.Read(buf) if err != nil || n < 10 || buf[0] != 0x10 { return } if !bytes.Contains(buf[:n], []byte{0, 4, 'M', 'Q', 'T', 'T', mqttProtocolLevel5}) { return } _, _ = c.Write([]byte{0x20, 0x03, 0x00, 0x00, 0x00}) _ = c.SetDeadline(time.Time{}) _, _ = io.Copy(io.Discard, c) }(conn) } }() return ln.Addr().String(), func() { _ = ln.Close() } } func TestLocalLoadReport(t *testing.T) { if os.Getenv("NIXMSG_LOAD_VERIFY") == "" { t.Skip("本地 50×20/s×1m:设置 NIXMSG_LOAD_VERIFY=1") } srv, err := harness.Start(harness.Options{}) if err != nil { t.Fatal(err) } defer func() { _ = srv.Stop() }() cfg := Config{ HTTPBase: srv.HTTPBase, AdminPass: srv.AdminPassword, Password: "password1234", Prefix: "vr", N: 50, Rate: 20, Duration: time.Minute, GroupSize: 50, Scenario: "all", } rep, err := Run(cfg) if err != nil { t.Fatal(err) } if rep.SubmitMs.N == 0 { t.Fatalf("empty report:\n%s", rep.Format()) } t.Log(rep.Format()) }