fix: 压测客户端改为 MQTT 5 并完成 hello 与收发统计
This commit is contained in:
+147
-37
@@ -4,72 +4,182 @@ import (
|
||||
"bytes"
|
||||
"io"
|
||||
"net"
|
||||
"os"
|
||||
"strings"
|
||||
"testing"
|
||||
"time"
|
||||
|
||||
"git.asio.asia/nixevol/NixMsg/test/harness"
|
||||
"github.com/mochi-mqtt/server/v2/packets"
|
||||
)
|
||||
|
||||
// 极简 MQTT broker:读 CONNECT,回 CONNACK accepted,保持连接。
|
||||
func startFakeMQTTBroker(t *testing.T) (addr string, closeFn func()) {
|
||||
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 err := mc.Send(pkt); err != nil {
|
||||
t.Fatal(err)
|
||||
}
|
||||
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)
|
||||
}
|
||||
done := make(chan struct{})
|
||||
go func() {
|
||||
for {
|
||||
conn, err := ln.Accept()
|
||||
if err != nil {
|
||||
select {
|
||||
case <-done:
|
||||
return
|
||||
default:
|
||||
return
|
||||
}
|
||||
return
|
||||
}
|
||||
go func(c net.Conn) {
|
||||
defer func() { _ = c.Close() }()
|
||||
_ = c.SetDeadline(time.Now().Add(5 * time.Second))
|
||||
buf := make([]byte, 256)
|
||||
buf := make([]byte, 2048)
|
||||
n, err := c.Read(buf)
|
||||
if err != nil || n < 2 || buf[0] != 0x10 {
|
||||
if err != nil || n < 10 || buf[0] != 0x10 {
|
||||
return
|
||||
}
|
||||
// CONNACK: type 0x20, remaining 2, flags 0, rc 0
|
||||
_, _ = c.Write([]byte{0x20, 0x02, 0x00, 0x00})
|
||||
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() {
|
||||
close(done)
|
||||
_ = ln.Close()
|
||||
}
|
||||
return ln.Addr().String(), func() { _ = ln.Close() }
|
||||
}
|
||||
|
||||
func TestMQTTBenchConnectN(t *testing.T) {
|
||||
addr, stop := startFakeMQTTBroker(t)
|
||||
defer stop()
|
||||
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() }()
|
||||
|
||||
var out bytes.Buffer
|
||||
b := &MQTTBench{Addr: addr, ClientIDPrefix: "q-t-", Timeout: 2 * time.Second}
|
||||
defer b.Close()
|
||||
if err := b.ConnectN(3, &out); err != nil {
|
||||
t.Fatalf("ConnectN: %v", err)
|
||||
cfg := Config{
|
||||
HTTPBase: srv.HTTPBase,
|
||||
AdminPass: srv.AdminPassword,
|
||||
Password: "password1234",
|
||||
Prefix: "vr",
|
||||
N: 50,
|
||||
Rate: 20,
|
||||
Duration: time.Minute,
|
||||
GroupSize: 50,
|
||||
Scenario: "all",
|
||||
}
|
||||
if b.Alive() != 3 {
|
||||
t.Fatalf("alive=%d", b.Alive())
|
||||
rep, err := Run(cfg)
|
||||
if err != nil {
|
||||
t.Fatal(err)
|
||||
}
|
||||
got := out.String()
|
||||
if !bytes.Contains(out.Bytes(), []byte("mqtt connections: 3")) {
|
||||
t.Fatalf("output missing count: %q", got)
|
||||
}
|
||||
}
|
||||
|
||||
func TestMQTTBenchRequiresAddr(t *testing.T) {
|
||||
b := &MQTTBench{}
|
||||
if err := b.ConnectN(1, nil); err == nil {
|
||||
t.Fatal("expected error")
|
||||
if rep.SubmitMs.N == 0 {
|
||||
t.Fatalf("empty report:\n%s", rep.Format())
|
||||
}
|
||||
t.Log(rep.Format())
|
||||
}
|
||||
|
||||
Reference in New Issue
Block a user