fix: back QMP test mocks with a unix socket instead of net.Pipe

Go 1.27's encoding/json v2 decoder stops at the closing brace and leaves
Encoder.Encode's trailing newline unread. net.Pipe is unbuffered and
synchronous, so the writer blocks on that undelivered byte while the peer
blocks writing its reply, deadlocking on the first QMP command.

TestTryBlockdevDel hung until the 180s package timeout. A real unix socket
is kernel-buffered and matches production QMP transport, which is already
the pattern newMockQMPClientWithEvents uses.
This commit is contained in:
Julian Sommer
2026-08-20 11:43:58 +10:00
parent 74117e5b26
commit adb60963c3
2 changed files with 49 additions and 17 deletions
+25 -9
View File
@@ -2213,24 +2213,31 @@ func TestHandleEC2Events_DetachVolume(t *testing.T) {
})
}
// newMockQMPClient creates a QMPClient backed by an in-memory pipe.
// newMockQMPClient creates a QMPClient backed by a unix socket.
// The returned cancel function stops the mock server goroutine.
// responseFunc is called for each received QMP command and should return the
// JSON object to send back (e.g. `{"return": {}}`). If nil, all commands
// get a success response.
//
// A real socket rather than net.Pipe, which is unbuffered and synchronous:
// the decoder stops at the closing brace and leaves the encoder's trailing
// newline unread, wedging the writer while the peer replies.
func newMockQMPClient(t *testing.T, responseFunc func(cmd qmp.QMPCommand) map[string]any) (*qmp.QMPClient, func()) {
t.Helper()
clientConn, serverConn := net.Pipe()
client := &qmp.QMPClient{
Conn: clientConn,
Decoder: json.NewDecoder(clientConn),
Encoder: json.NewEncoder(clientConn),
}
sockPath := filepath.Join(t.TempDir(), "qmp.sock")
ln, err := net.Listen("unix", sockPath)
require.NoError(t, err)
done := make(chan struct{})
go func() {
defer close(done)
serverConn, err := ln.Accept()
if err != nil {
return
}
defer serverConn.Close()
dec := json.NewDecoder(serverConn)
enc := json.NewEncoder(serverConn)
for {
@@ -2250,9 +2257,18 @@ func newMockQMPClient(t *testing.T, responseFunc func(cmd qmp.QMPCommand) map[st
}
}()
clientConn, err := net.Dial("unix", sockPath)
require.NoError(t, err)
client := &qmp.QMPClient{
Conn: clientConn,
Decoder: json.NewDecoder(clientConn),
Encoder: json.NewEncoder(clientConn),
}
cancel := func() {
clientConn.Close()
serverConn.Close()
_ = ln.Close()
_ = clientConn.Close()
<-done
}
return client, cancel
+24 -8
View File
@@ -24,23 +24,30 @@ import (
"github.com/stretchr/testify/require"
)
// newMockQMPClient creates a QMPClient backed by an in-memory pipe. The
// newMockQMPClient creates a QMPClient backed by a unix socket. The
// responder is invoked for every command and returns the JSON object the
// server should reply with (e.g. {"return": {}} or {"error": {...}}). A
// nil responder reply defaults to an empty success.
//
// A real socket rather than net.Pipe, which is unbuffered and synchronous:
// the decoder stops at the closing brace and leaves the encoder's trailing
// newline unread, wedging the writer while the peer replies.
func newMockQMPClient(t *testing.T, responder func(qmp.QMPCommand) map[string]any) (*qmp.QMPClient, func()) {
t.Helper()
clientConn, serverConn := net.Pipe()
client := &qmp.QMPClient{
Conn: clientConn,
Decoder: json.NewDecoder(clientConn),
Encoder: json.NewEncoder(clientConn),
}
sockPath := filepath.Join(t.TempDir(), "qmp.sock")
ln, err := net.Listen("unix", sockPath)
require.NoError(t, err)
done := make(chan struct{})
go func() {
defer close(done)
serverConn, err := ln.Accept()
if err != nil {
return
}
defer serverConn.Close()
dec := json.NewDecoder(serverConn)
enc := json.NewEncoder(serverConn)
for {
@@ -61,9 +68,18 @@ func newMockQMPClient(t *testing.T, responder func(qmp.QMPCommand) map[string]an
}
}()
clientConn, err := net.Dial("unix", sockPath)
require.NoError(t, err)
client := &qmp.QMPClient{
Conn: clientConn,
Decoder: json.NewDecoder(clientConn),
Encoder: json.NewEncoder(clientConn),
}
cancel := func() {
_ = ln.Close()
_ = clientConn.Close()
_ = serverConn.Close()
<-done
}
return client, cancel