2022-02-06 07:06:32 +00:00
|
|
|
// Copyright (C) 2017 Michael J. Fromberger. All Rights Reserved.
|
|
|
|
|
|
|
|
package server_test
|
2021-12-04 16:42:11 +00:00
|
|
|
|
|
|
|
import (
|
|
|
|
"context"
|
|
|
|
"math/rand"
|
|
|
|
"net"
|
|
|
|
"sync"
|
|
|
|
"testing"
|
|
|
|
"time"
|
|
|
|
|
|
|
|
"github.com/creachadair/jrpc2"
|
|
|
|
"github.com/creachadair/jrpc2/channel"
|
|
|
|
"github.com/creachadair/jrpc2/handler"
|
2022-02-06 07:06:32 +00:00
|
|
|
"github.com/creachadair/jrpc2/server"
|
|
|
|
"github.com/fortytw2/leaktest"
|
2021-12-04 16:42:11 +00:00
|
|
|
)
|
|
|
|
|
|
|
|
var newChan = channel.Line
|
|
|
|
|
|
|
|
// A static test service that returns the same thing each time.
|
2022-02-06 07:06:32 +00:00
|
|
|
var testStatic = server.Static(handler.Map{
|
2021-12-04 16:42:11 +00:00
|
|
|
"Test": handler.New(func(context.Context) (string, error) {
|
|
|
|
return "OK", nil
|
|
|
|
}),
|
|
|
|
})
|
|
|
|
|
|
|
|
// A service with session state, to exercise start/finish plumbing.
|
|
|
|
type testSession struct {
|
|
|
|
t *testing.T
|
|
|
|
init bool
|
|
|
|
nCall int
|
|
|
|
}
|
|
|
|
|
2022-02-06 07:06:32 +00:00
|
|
|
func newTestSession(t *testing.T) func() server.Service {
|
|
|
|
return func() server.Service { t.Helper(); return &testSession{t: t} }
|
2021-12-04 16:42:11 +00:00
|
|
|
}
|
|
|
|
|
|
|
|
func (t *testSession) Assigner() (jrpc2.Assigner, error) {
|
|
|
|
if t.init {
|
|
|
|
t.t.Error("Service has already been initialized")
|
|
|
|
}
|
|
|
|
t.init = true
|
|
|
|
return handler.Map{
|
|
|
|
"Test": handler.New(func(context.Context) (string, error) {
|
|
|
|
if !t.init {
|
|
|
|
t.t.Error("Handler called before service initialized")
|
|
|
|
}
|
|
|
|
t.nCall++
|
|
|
|
return "OK", nil
|
|
|
|
}),
|
|
|
|
}, nil
|
|
|
|
}
|
|
|
|
|
|
|
|
func (t *testSession) Finish(assigner jrpc2.Assigner, stat jrpc2.ServerStatus) {
|
|
|
|
if _, ok := assigner.(handler.Map); !ok {
|
|
|
|
t.t.Errorf("Finished assigner: got %+v, want handler.Map", assigner)
|
|
|
|
}
|
|
|
|
if !t.init {
|
|
|
|
t.t.Error("Service finished without being initialized")
|
|
|
|
}
|
|
|
|
if !stat.Success() {
|
|
|
|
t.t.Errorf("Finish unsuccessful: %v", stat.Err)
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
|
|
|
func mustListen(t *testing.T) net.Listener {
|
|
|
|
t.Helper()
|
|
|
|
|
|
|
|
lst, err := net.Listen("tcp", "127.0.0.1:0")
|
|
|
|
if err != nil {
|
|
|
|
t.Fatalf("Listen: %v", err)
|
|
|
|
}
|
|
|
|
return lst
|
|
|
|
}
|
|
|
|
|
|
|
|
func mustDial(t *testing.T, addr string) *jrpc2.Client {
|
|
|
|
t.Helper()
|
|
|
|
|
|
|
|
conn, err := net.Dial("tcp", addr)
|
|
|
|
if err != nil {
|
|
|
|
t.Fatalf("Dial %q: %v", addr, err)
|
|
|
|
}
|
|
|
|
return jrpc2.NewClient(newChan(conn, conn), nil)
|
|
|
|
}
|
|
|
|
|
2022-02-06 07:06:32 +00:00
|
|
|
func mustServe(t *testing.T, ctx context.Context, lst net.Listener, newService func() server.Service) <-chan error {
|
2021-12-04 16:42:11 +00:00
|
|
|
t.Helper()
|
|
|
|
|
2022-02-06 07:06:32 +00:00
|
|
|
acc := server.NetAccepter(lst, newChan)
|
|
|
|
errc := make(chan error, 1)
|
2021-12-04 16:42:11 +00:00
|
|
|
go func() {
|
2022-02-06 07:06:32 +00:00
|
|
|
defer close(errc)
|
2021-12-04 16:42:11 +00:00
|
|
|
// Start a server loop to accept connections from the clients. This should
|
|
|
|
// exit cleanly once all the clients have finished and the listener closes.
|
2022-02-06 07:06:32 +00:00
|
|
|
errc <- server.Loop(ctx, acc, newService, nil)
|
2021-12-04 16:42:11 +00:00
|
|
|
}()
|
2022-02-06 07:06:32 +00:00
|
|
|
return errc
|
2021-12-04 16:42:11 +00:00
|
|
|
}
|
|
|
|
|
|
|
|
// Test that sequential clients against the same server work sanely.
|
|
|
|
func TestSeq(t *testing.T) {
|
2022-02-06 07:06:32 +00:00
|
|
|
defer leaktest.Check(t)()
|
|
|
|
|
2021-12-04 16:42:11 +00:00
|
|
|
lst := mustListen(t)
|
|
|
|
addr := lst.Addr().String()
|
2022-02-06 07:06:32 +00:00
|
|
|
errc := mustServe(t, context.Background(), lst, testStatic)
|
2021-12-04 16:42:11 +00:00
|
|
|
|
|
|
|
for i := 0; i < 5; i++ {
|
|
|
|
cli := mustDial(t, addr)
|
|
|
|
var rsp string
|
|
|
|
if err := cli.CallResult(context.Background(), "Test", nil, &rsp); err != nil {
|
|
|
|
t.Errorf("[client %d] Test call: unexpected error: %v", i, err)
|
|
|
|
} else if rsp != "OK" {
|
|
|
|
t.Errorf("[client %d]: Test call: got %q, want OK", i, rsp)
|
|
|
|
}
|
|
|
|
cli.Close()
|
|
|
|
}
|
|
|
|
lst.Close()
|
2022-02-06 07:06:32 +00:00
|
|
|
if err := <-errc; err != nil {
|
|
|
|
t.Errorf("Server exit failed: %v", err)
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
|
|
|
// Test that context plumbing works properly.
|
|
|
|
func TestLoop_cancelContext(t *testing.T) {
|
|
|
|
defer leaktest.Check(t)()
|
|
|
|
|
|
|
|
ctx, cancel := context.WithCancel(context.Background())
|
|
|
|
defer cancel()
|
|
|
|
|
|
|
|
lst := mustListen(t)
|
|
|
|
defer lst.Close()
|
|
|
|
errc := mustServe(t, ctx, lst, testStatic)
|
|
|
|
|
|
|
|
time.AfterFunc(50*time.Millisecond, cancel)
|
|
|
|
select {
|
|
|
|
case err := <-errc:
|
|
|
|
if err != nil {
|
|
|
|
t.Errorf("Loop exit reported error: %v", err)
|
|
|
|
}
|
|
|
|
case <-time.After(1 * time.Second):
|
|
|
|
t.Fatalf("Loop did not exit in a timely manner after cancellation")
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
|
|
|
// Test that cancelling a loop stops its servers.
|
|
|
|
func TestLoop_cancelServers(t *testing.T) {
|
|
|
|
defer leaktest.Check(t)()
|
|
|
|
|
|
|
|
ctx, cancel := context.WithCancel(context.Background())
|
|
|
|
defer cancel()
|
|
|
|
|
|
|
|
ready := make(chan struct{})
|
|
|
|
|
|
|
|
lst := mustListen(t)
|
|
|
|
errc := mustServe(t, ctx, lst, server.Static(handler.Map{
|
|
|
|
"Test": handler.New(func(ctx context.Context) error {
|
|
|
|
// Signal readiness then block until cancelled.
|
|
|
|
// The server will cancel this method when stopped.
|
|
|
|
close(ready)
|
|
|
|
<-ctx.Done()
|
|
|
|
return ctx.Err()
|
|
|
|
}),
|
|
|
|
}))
|
|
|
|
cli := mustDial(t, lst.Addr().String())
|
|
|
|
defer cli.Close()
|
|
|
|
|
|
|
|
// Issue a call to the server that will block until the server cancels the
|
|
|
|
// handler at shutdown. If the server blocks after cancellation, it means it
|
|
|
|
// is not correctly stopping its active servers.
|
|
|
|
go cli.Call(context.Background(), "Test", nil)
|
|
|
|
|
|
|
|
<-ready
|
|
|
|
cancel() // this should stop the loop and the server
|
|
|
|
|
|
|
|
select {
|
|
|
|
case err := <-errc:
|
|
|
|
if err != nil {
|
|
|
|
t.Errorf("Loop result: %v", err)
|
|
|
|
}
|
|
|
|
case <-time.After(1 * time.Second):
|
|
|
|
t.Error("Loop did not exit in a timely manner after cancellation")
|
|
|
|
}
|
2021-12-04 16:42:11 +00:00
|
|
|
}
|
|
|
|
|
|
|
|
// Test that concurrent clients against the same server work sanely.
|
|
|
|
func TestLoop(t *testing.T) {
|
2022-02-06 07:06:32 +00:00
|
|
|
defer leaktest.Check(t)()
|
|
|
|
|
2021-12-04 16:42:11 +00:00
|
|
|
tests := []struct {
|
|
|
|
desc string
|
2022-02-06 07:06:32 +00:00
|
|
|
cons func() server.Service
|
2021-12-04 16:42:11 +00:00
|
|
|
}{
|
2022-02-06 07:06:32 +00:00
|
|
|
{"StaticService", testStatic},
|
2021-12-04 16:42:11 +00:00
|
|
|
{"SessionStateService", newTestSession(t)},
|
|
|
|
}
|
|
|
|
const numClients = 5
|
|
|
|
const numCalls = 5
|
|
|
|
|
|
|
|
for _, test := range tests {
|
|
|
|
t.Run(test.desc, func(t *testing.T) {
|
|
|
|
lst := mustListen(t)
|
|
|
|
addr := lst.Addr().String()
|
2022-02-06 07:06:32 +00:00
|
|
|
errc := mustServe(t, context.Background(), lst, test.cons)
|
2021-12-04 16:42:11 +00:00
|
|
|
|
|
|
|
// Start a bunch of clients, each of which will dial the server and make
|
|
|
|
// some calls at random intervals to tickle the race detector.
|
|
|
|
var wg sync.WaitGroup
|
|
|
|
for i := 0; i < numClients; i++ {
|
|
|
|
wg.Add(1)
|
|
|
|
i := i
|
|
|
|
go func() {
|
|
|
|
defer wg.Done()
|
|
|
|
cli := mustDial(t, addr)
|
|
|
|
defer cli.Close()
|
|
|
|
|
|
|
|
for j := 0; j < numCalls; j++ {
|
|
|
|
time.Sleep(time.Duration(rand.Intn(10)) * time.Millisecond)
|
|
|
|
var rsp string
|
|
|
|
if err := cli.CallResult(context.Background(), "Test", nil, &rsp); err != nil {
|
|
|
|
t.Errorf("[client %d]: Test call %d: unexpected error: %v", i, j+1, err)
|
|
|
|
} else if rsp != "OK" {
|
|
|
|
t.Errorf("[client %d]: Test call %d: got %q, want OK", i, j+1, rsp)
|
|
|
|
}
|
|
|
|
}
|
|
|
|
}()
|
|
|
|
}
|
|
|
|
|
|
|
|
// Wait for the clients to be finished and then close the listener so that
|
|
|
|
// the service loop will stop.
|
|
|
|
wg.Wait()
|
|
|
|
lst.Close()
|
2022-02-06 07:06:32 +00:00
|
|
|
if err := <-errc; err != nil {
|
|
|
|
t.Errorf("Server exit failed: %v", err)
|
|
|
|
}
|
2021-12-04 16:42:11 +00:00
|
|
|
})
|
|
|
|
}
|
|
|
|
}
|