kunchenguid--no-mistakes
26f897c1ec
release / release-please (push) Failing after 1m49s
docs / build (push) Failing after 6m34s
release / build-and-upload (arm64, linux) (push) Has been cancelled
release / build-and-upload (arm64, windows) (push) Has been cancelled
release / build-darwin (amd64, darwin) (push) Has been cancelled
release / checksums (push) Has been cancelled
release / finalize (push) Has been cancelled
release / build-darwin (arm64, darwin) (push) Has been cancelled
release / build-and-upload (amd64, linux) (push) Has been cancelled
release / build-and-upload (amd64, windows) (push) Has been cancelled
docs / deploy (push) Has been cancelled
272 行
6.1 KiB
Go
272 行
6.1 KiB
Go
package ipc_test
|
|
|
|
import (
|
|
"bytes"
|
|
"context"
|
|
"encoding/json"
|
|
"fmt"
|
|
"log/slog"
|
|
"strings"
|
|
"sync"
|
|
"testing"
|
|
|
|
"github.com/kunchenguid/no-mistakes/internal/ipc"
|
|
)
|
|
|
|
func TestServerClientRoundTrip(t *testing.T) {
|
|
sock := socketPath(t)
|
|
srv := startServer(t, sock)
|
|
|
|
type echoParams struct {
|
|
Message string `json:"message"`
|
|
}
|
|
type echoResult struct {
|
|
Echo string `json:"echo"`
|
|
}
|
|
|
|
srv.Handle("echo", func(_ context.Context, raw json.RawMessage) (interface{}, error) {
|
|
var p echoParams
|
|
if err := json.Unmarshal(raw, &p); err != nil {
|
|
return nil, err
|
|
}
|
|
return echoResult{Echo: p.Message}, nil
|
|
})
|
|
|
|
c, err := ipc.Dial(sock)
|
|
if err != nil {
|
|
t.Fatalf("dial: %v", err)
|
|
}
|
|
defer c.Close()
|
|
|
|
var result echoResult
|
|
if err := c.Call("echo", echoParams{Message: "hello"}, &result); err != nil {
|
|
t.Fatalf("call: %v", err)
|
|
}
|
|
if result.Echo != "hello" {
|
|
t.Errorf("got echo=%q, want %q", result.Echo, "hello")
|
|
}
|
|
}
|
|
|
|
func TestMethodNotFound(t *testing.T) {
|
|
sock := socketPath(t)
|
|
startServer(t, sock)
|
|
|
|
c, err := ipc.Dial(sock)
|
|
if err != nil {
|
|
t.Fatalf("dial: %v", err)
|
|
}
|
|
defer c.Close()
|
|
|
|
var result json.RawMessage
|
|
err = c.Call("nonexistent", nil, &result)
|
|
if err == nil {
|
|
t.Fatal("expected error for unknown method")
|
|
}
|
|
rpcErr, ok := err.(*ipc.RPCError)
|
|
if !ok {
|
|
t.Fatalf("expected RPCError, got %T: %v", err, err)
|
|
}
|
|
if rpcErr.Code != ipc.ErrMethodNotFound {
|
|
t.Errorf("got code %d, want %d", rpcErr.Code, ipc.ErrMethodNotFound)
|
|
}
|
|
}
|
|
|
|
func TestHandlerError(t *testing.T) {
|
|
sock := socketPath(t)
|
|
srv := startServer(t, sock)
|
|
|
|
srv.Handle("fail", func(_ context.Context, _ json.RawMessage) (interface{}, error) {
|
|
return nil, fmt.Errorf("something broke")
|
|
})
|
|
|
|
c, err := ipc.Dial(sock)
|
|
if err != nil {
|
|
t.Fatalf("dial: %v", err)
|
|
}
|
|
defer c.Close()
|
|
|
|
var result json.RawMessage
|
|
err = c.Call("fail", nil, &result)
|
|
if err == nil {
|
|
t.Fatal("expected error")
|
|
}
|
|
rpcErr, ok := err.(*ipc.RPCError)
|
|
if !ok {
|
|
t.Fatalf("expected RPCError, got %T: %v", err, err)
|
|
}
|
|
if rpcErr.Code != ipc.ErrInternal {
|
|
t.Errorf("got code %d, want %d", rpcErr.Code, ipc.ErrInternal)
|
|
}
|
|
if rpcErr.Message != "something broke" {
|
|
t.Errorf("got message=%q, want %q", rpcErr.Message, "something broke")
|
|
}
|
|
}
|
|
|
|
func TestMultipleClients(t *testing.T) {
|
|
sock := socketPath(t)
|
|
srv := startServer(t, sock)
|
|
|
|
type addParams struct {
|
|
A int `json:"a"`
|
|
B int `json:"b"`
|
|
}
|
|
type addResult struct {
|
|
Sum int `json:"sum"`
|
|
}
|
|
|
|
srv.Handle("add", func(_ context.Context, raw json.RawMessage) (interface{}, error) {
|
|
var p addParams
|
|
if err := json.Unmarshal(raw, &p); err != nil {
|
|
return nil, err
|
|
}
|
|
return addResult{Sum: p.A + p.B}, nil
|
|
})
|
|
|
|
var wg sync.WaitGroup
|
|
for i := 0; i < 5; i++ {
|
|
wg.Add(1)
|
|
go func(n int) {
|
|
defer wg.Done()
|
|
c, err := ipc.Dial(sock)
|
|
if err != nil {
|
|
t.Errorf("client %d dial: %v", n, err)
|
|
return
|
|
}
|
|
defer c.Close()
|
|
|
|
var result addResult
|
|
if err := c.Call("add", addParams{A: n, B: 10}, &result); err != nil {
|
|
t.Errorf("client %d call: %v", n, err)
|
|
return
|
|
}
|
|
if result.Sum != n+10 {
|
|
t.Errorf("client %d: got sum=%d, want %d", n, result.Sum, n+10)
|
|
}
|
|
}(i)
|
|
}
|
|
wg.Wait()
|
|
}
|
|
|
|
func TestMultipleCallsOnSameConnection(t *testing.T) {
|
|
sock := socketPath(t)
|
|
srv := startServer(t, sock)
|
|
|
|
type echoParams struct {
|
|
Message string `json:"message"`
|
|
}
|
|
type echoResult struct {
|
|
Echo string `json:"echo"`
|
|
}
|
|
|
|
srv.Handle("echo", func(_ context.Context, raw json.RawMessage) (interface{}, error) {
|
|
var p echoParams
|
|
if err := json.Unmarshal(raw, &p); err != nil {
|
|
return nil, err
|
|
}
|
|
return echoResult{Echo: p.Message}, nil
|
|
})
|
|
|
|
c, err := ipc.Dial(sock)
|
|
if err != nil {
|
|
t.Fatalf("dial: %v", err)
|
|
}
|
|
defer c.Close()
|
|
|
|
for i := 0; i < 5; i++ {
|
|
msg := fmt.Sprintf("msg-%d", i)
|
|
var result echoResult
|
|
if err := c.Call("echo", echoParams{Message: msg}, &result); err != nil {
|
|
t.Fatalf("call %d: %v", i, err)
|
|
}
|
|
if result.Echo != msg {
|
|
t.Errorf("call %d: got echo=%q, want %q", i, result.Echo, msg)
|
|
}
|
|
}
|
|
}
|
|
|
|
func TestNilParams(t *testing.T) {
|
|
sock := socketPath(t)
|
|
srv := startServer(t, sock)
|
|
|
|
srv.Handle("health", func(_ context.Context, _ json.RawMessage) (interface{}, error) {
|
|
return ipc.HealthResult{Status: "ok"}, nil
|
|
})
|
|
|
|
c, err := ipc.Dial(sock)
|
|
if err != nil {
|
|
t.Fatalf("dial: %v", err)
|
|
}
|
|
defer c.Close()
|
|
|
|
var result ipc.HealthResult
|
|
if err := c.Call("health", nil, &result); err != nil {
|
|
t.Fatalf("call: %v", err)
|
|
}
|
|
if result.Status != "ok" {
|
|
t.Errorf("got status=%q, want %q", result.Status, "ok")
|
|
}
|
|
}
|
|
|
|
func TestHealthRequestsDoNotLogAtInfo(t *testing.T) {
|
|
sock := socketPath(t)
|
|
srv := startServer(t, sock)
|
|
|
|
srv.Handle(ipc.MethodHealth, func(_ context.Context, _ json.RawMessage) (interface{}, error) {
|
|
return ipc.HealthResult{Status: "ok"}, nil
|
|
})
|
|
srv.Handle("fail", func(_ context.Context, _ json.RawMessage) (interface{}, error) {
|
|
return nil, fmt.Errorf("something broke")
|
|
})
|
|
|
|
var logs bytes.Buffer
|
|
logger := slog.New(slog.NewTextHandler(&logs, &slog.HandlerOptions{Level: slog.LevelInfo}))
|
|
prev := slog.Default()
|
|
slog.SetDefault(logger)
|
|
defer slog.SetDefault(prev)
|
|
|
|
c, err := ipc.Dial(sock)
|
|
if err != nil {
|
|
t.Fatalf("dial: %v", err)
|
|
}
|
|
defer c.Close()
|
|
|
|
var health ipc.HealthResult
|
|
if err := c.Call(ipc.MethodHealth, nil, &health); err != nil {
|
|
t.Fatalf("health call: %v", err)
|
|
}
|
|
|
|
var raw json.RawMessage
|
|
err = c.Call("fail", nil, &raw)
|
|
if err == nil {
|
|
t.Fatal("expected fail call error")
|
|
}
|
|
|
|
logOutput := logs.String()
|
|
if strings.Contains(logOutput, "method=health") {
|
|
t.Fatalf("health request should not log at info: %s", logOutput)
|
|
}
|
|
if !strings.Contains(logOutput, "msg=\"ipc request failed\" method=fail") {
|
|
t.Fatalf("failed request log missing: %s", logOutput)
|
|
}
|
|
}
|
|
|
|
func TestCallWithNilResult(t *testing.T) {
|
|
sock := socketPath(t)
|
|
srv := startServer(t, sock)
|
|
|
|
srv.Handle("noop", func(_ context.Context, _ json.RawMessage) (interface{}, error) {
|
|
return map[string]bool{"ok": true}, nil
|
|
})
|
|
|
|
c, err := ipc.Dial(sock)
|
|
if err != nil {
|
|
t.Fatalf("dial: %v", err)
|
|
}
|
|
defer c.Close()
|
|
|
|
// Call with nil result pointer — should succeed without unmarshaling result.
|
|
if err := c.Call("noop", nil, nil); err != nil {
|
|
t.Fatalf("call with nil result: %v", err)
|
|
}
|
|
}
|