func TestServer_CancelRequest(t *testing.T) {
	ready := make(chan struct{})
	loc := server.NewLocal(handler.Map{
		"Stall": handler.New(func(ctx context.Context) error {
			close(ready)
			t.Log("Stall handler: waiting for context cancellation")
			<-ctx.Done()
			return ctx.Err()
		}),
		"Test": handler.New(func(ctx context.Context, req *jrpc2.Request) error {
			var id string
			if err := req.UnmarshalParams(&handler.Args{&id}); err != nil {
				return err
			}
			t.Logf("Test handler: cancelling %q...", id)
			jrpc2.ServerFromContext(ctx).CancelRequest(id)
			return nil
		}),
	}, nil)
	defer loc.Close()
	ctx := context.Background()
	errc := make(chan error, 1)
	go func() {
		_, err := loc.Client.Call(ctx, "Stall", nil)
		errc <- err
		close(errc)
	}()
	<-ready
	if _, err := loc.Client.Call(ctx, "Test", []string{"1"}); err != nil {
		t.Errorf("Test call failed: %v", err)
	}
	err := <-errc
	got := code.FromError(err)
	if got != code.Cancelled {
		t.Errorf("Stall: got %v (%v), want %v", err, got, code.Cancelled)
	} else {
		t.Logf("Cancellation succeeded, got expected error: %v", err)
	}
}
Test that a handler can cancel an in-flight request.
package jrpc2_test
import (
	"context"
	"encoding/json"
	"errors"
	"fmt"
	"sort"
	"sync/atomic"
	"testing"
	"time"
	"github.com/creachadair/jrpc2"
	"github.com/creachadair/jrpc2/channel"
	"github.com/creachadair/jrpc2/code"
	"github.com/creachadair/jrpc2/handler"
	"github.com/creachadair/jrpc2/jctx"
	"github.com/creachadair/jrpc2/server"
	"github.com/google/go-cmp/cmp"
)
var (
	_ code.ErrCoder = (*jrpc2.Error)(nil)
)
var notAuthorized = code.Register(-32095, "request not authorized")
var testOK = handler.New(func(ctx context.Context) (string, error) {
	return "OK", nil
})
var testService = handler.Map{
	"Add": handler.New((dummy{}).Add),
	"Mul": handler.New((dummy{}).Mul),
	"Max": handler.New((dummy{}).Max),
	"Nil":  handler.New(methodNil),
	"Ctx":  handler.New(methodCtx),
	"Ping": handler.New(methodPing),
}
type dummy struct{}
func (dummy) Add(_ context.Context, req *jrpc2.Request) (interface{}, error) {
	if req.IsNotification() {
		return nil, errors.New("ignoring notification")
	}
	var vals []int
	if err := req.UnmarshalParams(&vals); err != nil {
		return nil, err
	}
	var sum int
	for _, v := range vals {
		sum += v
	}
	return sum, nil
}
func (dummy) Mul(_ context.Context, req struct{ X, Y int }) (int, error) {
	return req.X * req.Y, nil
}
func (dummy) Max(_ context.Context, vs []int) (int, error) {
	if len(vs) == 0 {
		return 0, jrpc2.Errorf(code.InvalidParams, "cannot compute max of no elements")
	}
	max := vs[0]
	for _, v := range vs[1:] {
		if v > max {
			max = v
		}
	}
	return max, nil
}
func methodNil(_ context.Context) (int, error) { return 42, nil }
func methodCtx(ctx context.Context, req *jrpc2.Request) (int, error) {
	if creq := jrpc2.InboundRequest(ctx); creq != req {
		return 0, fmt.Errorf("wrong req in context %p ≠ %p", creq, req)
	}
	return 1, nil
}
func methodPing(ctx context.Context, req *jrpc2.Request) error {
	if !req.IsNotification() {
		return errors.New("called Ping expecting a response")
	}
	return nil
}
var callTests = []struct {
	method string
	params interface{}
	want   int
}{
	{"Test.Add", []int{}, 0},
	{"Test.Add", []int{1, 2, 3}, 6},
	{"Test.Mul", struct{ X, Y int }{7, 9}, 63},
	{"Test.Mul", struct{ X, Y int }{}, 0},
	{"Test.Max", []int{3, 1, 8, 4, 2, 0, -5}, 8},
	{"Test.Ctx", nil, 1},
	{"Test.Nil", nil, 42},
	{"Test.Nil", json.RawMessage("null"), 42},
}
func TestServerInfo_methodNames(t *testing.T) {
	loc := server.NewLocal(handler.ServiceMap{
		"Test": testService,
	}, nil)
	defer loc.Close()
	s := loc.Server
	got, want := s.ServerInfo().Methods, []string{
		"Test.Add", "Test.Ctx", "Test.Max", "Test.Mul", "Test.Nil", "Test.Ping",
	}
	if diff := cmp.Diff(want, got); diff != "" {
		t.Errorf("Wrong method names: (-want, +got)\n%s", diff)
	}
}
func TestClient_Call(t *testing.T) {
	loc := server.NewLocal(handler.ServiceMap{
		"Test": testService,
	}, &server.LocalOptions{
		Server: &jrpc2.ServerOptions{
			AllowV1:     true,
			Concurrency: 16,
		},
	})
	defer loc.Close()
	c := loc.Client
	ctx := context.Background()
	for _, test := range callTests {
		rsp, err := c.Call(ctx, test.method, test.params)
		if err != nil {
			t.Errorf("Call %q %v: unexpected error: %v", test.method, test.params, err)
			continue
		}
		var got int
		if err := rsp.UnmarshalResult(&got); err != nil {
			t.Errorf("Unmarshaling result: %v", err)
			continue
		}
		if got != test.want {
			t.Errorf("Call %q %v: got %v, want %v", test.method, test.params, got, test.want)
		}
		if err := c.Notify(ctx, test.method, test.params); err != nil {
			t.Errorf("Notify %q %v: unexpected error: %v", test.method, test.params, err)
		}
	}
}
func TestClient_CallResult(t *testing.T) {
	loc := server.NewLocal(handler.ServiceMap{
		"Test": testService,
	}, &server.LocalOptions{
		Server: &jrpc2.ServerOptions{Concurrency: 16},
	})
	defer loc.Close()
	c := loc.Client
	ctx := context.Background()
	for _, test := range callTests {
		var got int
		if err := c.CallResult(ctx, test.method, test.params, &got); err != nil {
			t.Errorf("CallResult %q %v: unexpected error: %v", test.method, test.params, err)
			continue
		}
		if got != test.want {
			t.Errorf("CallResult %q %v: got %v, want %v", test.method, test.params, got, test.want)
		}
	}
}
func TestClient_Batch(t *testing.T) {
	loc := server.NewLocal(handler.ServiceMap{
		"Test": testService,
	}, &server.LocalOptions{
		Server: &jrpc2.ServerOptions{
			AllowV1:     true,
			Concurrency: 16,
		},
	})
	defer loc.Close()
	c := loc.Client
	ctx := context.Background()
	specs := make([]jrpc2.Spec, len(callTests)+1)
	specs[0] = jrpc2.Spec{
		Method: "Test.Ping",
		Params: []string{"hey"},
		Notify: true,
	}
	for i, test := range callTests {
		specs[i+1] = jrpc2.Spec{
			Method: test.method,
			Params: test.params,
			Notify: false,
		}
	}
	batch, err := c.Batch(ctx, specs)
	if err != nil {
		t.Fatalf("Batch failed: %v", err)
	}
	if len(batch) != len(callTests) {
		t.Errorf("Wrong number of responses: got %d, want %d", len(batch), len(callTests))
	}
	for i, rsp := range batch {
		if err := rsp.Error(); err != nil {
			t.Errorf("Response %d failed: %v", i+1, err)
			continue
		}
		var got int
		if err := rsp.UnmarshalResult(&got); err != nil {
			t.Errorf("Umarshaling result %d: %v", i+1, err)
			continue
		}
		if got != callTests[i].want {
			t.Errorf("Response %d (%q): got %v, want %v", i+1, rsp.ID(), got, callTests[i].want)
		}
	}
}
func TestServer_notificationOrder(t *testing.T) {
	var last int32
	loc := server.NewLocal(handler.Map{
		"Test": handler.New(func(_ context.Context, req *jrpc2.Request) error {
			var seq int32
			if err := req.UnmarshalParams(&handler.Args{&seq}); err != nil {
				t.Errorf("Invalid test parameters: %v", err)
				return err
			}
			if old := atomic.SwapInt32(&last, seq); old != seq-1 {
				t.Errorf("Request out of sequence at #%d: got %d, want %d", seq, old, seq-1)
			}
			return nil
		}),
	}, &server.LocalOptions{
		Server: &jrpc2.ServerOptions{Concurrency: 16},
	})
	for i := 1; i < 10; i++ {
		if err := loc.Client.Notify(context.Background(), "Test", []int{i}); err != nil {
			t.Errorf("Test notification failed: %v", err)
		}
	}
	if err := loc.Close(); err != nil {
		t.Logf("Warning: error at server exit: %v", err)
	}
}
func TestHandler_errorOnly(t *testing.T) {
	const errMessage = "not enough strings"
	loc := server.NewLocal(handler.Map{
		"ErrorOnly": handler.New(func(_ context.Context, ss []string) error {
			if len(ss) == 0 {
				return jrpc2.Errorf(1, errMessage)
			}
			t.Logf("ErrorOnly succeeds on input %q", ss)
			return nil
		}),
	}, nil)
	defer loc.Close()
	c := loc.Client
	ctx := context.Background()
	t.Run("CallExpectingError", func(t *testing.T) {
		rsp, err := c.Call(ctx, "ErrorOnly", []string{})
		if err == nil {
			t.Errorf("ErrorOnly: got %+v, want error", rsp)
		} else if e, ok := err.(*jrpc2.Error); !ok {
			t.Errorf("ErrorOnly: got %v, want *Error", err)
		} else if e.Code != 1 || e.Message != errMessage {
			t.Errorf("ErrorOnly: got (%s, %s), want (1, %s)", e.Code, e.Message, errMessage)
		}
	})
	t.Run("CallExpectingOK", func(t *testing.T) {
		rsp, err := c.Call(ctx, "ErrorOnly", []string{"aiutami!"})
		if err != nil {
			t.Errorf("ErrorOnly: unexpected error: %v", err)
		}
		var got json.RawMessage
		if err := rsp.UnmarshalResult(&got); err != nil {
			t.Fatalf("Failed to unmarshal result data: %v", err)
		} else if r := string(got); r != "null" {
			t.Errorf("ErrorOnly response: got %q, want null", r)
		}
	})
}
func TestServer_contextTimeout(t *testing.T) {
	loc := server.NewLocal(handler.Map{
		"Stall": handler.New(func(ctx context.Context) (bool, error) {
			t.Log("Stalling...")
			select {
			case <-ctx.Done():
				t.Logf("Stall context done: err=%v", ctx.Err())
				return true, nil
			case <-time.After(5 * time.Second):
				return false, errors.New("stall timed out")
			}
		}),
	}, nil)
	defer loc.Close()
	c := loc.Client
	ctx, cancel := context.WithTimeout(context.Background(), 100*time.Millisecond)
	defer cancel()
	start := time.Now()
	got, err := c.Call(ctx, "Stall", nil)
	if err == nil {
		t.Errorf("Stall: got %+v, wanted error", got)
	} else if err != context.DeadlineExceeded {
		t.Errorf("Stall: got error %v, want %v", err, context.DeadlineExceeded)
	} else {
		t.Logf("Successfully cancelled after %v", time.Since(start))
	}
}
func TestServer_stopCancelsHandlers(t *testing.T) {
	started := make(chan struct{})
	stopped := make(chan error, 1)
	loc := server.NewLocal(handler.Map{
		"Hang": handler.New(func(ctx context.Context) (bool, error) {
			close(started)  
			<-ctx.Done()
			return true, ctx.Err()
		}),
	}, nil)
	defer loc.Close()
	s, c := loc.Server, loc.Client
	go func() {
		defer close(stopped)
		_, err := c.Call(context.Background(), "Hang", nil)
		stopped <- err
	}()
	<-started
	s.Stop()
	select {
	case <-time.After(30 * time.Second):
		t.Error("Timed out waiting for service handler to fail")
	case err := <-stopped:
		if ec := code.FromError(err); ec != code.Cancelled {
			t.Errorf("Client error: got %v (%v), wanted code %v", err, ec, code.Cancelled)
		}
	}
}