mirror of
https://github.com/mulgadc/spinifex.git
synced 2026-10-15 09:01:31 +00:00
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:
@@ -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,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
|
||||
|
||||
Reference in New Issue
Block a user