func TestServer_disableBuiltinHook(t *testing.T) {
	s := NewServer(hmap{
		"rpc.nonesuch": methodFunc(func(context.Context, *Request) (interface{}, error) {
			return "OK", nil
		}),
	}, &ServerOptions{DisableBuiltin: true})
	ctx := context.Background()
	for _, name := range []string{rpcServerInfo} {
		if got := s.assign(ctx, name); got != nil {
			t.Errorf("s.assign(%s): got %+v, wanted nil", name, got)
		}
	}
	if got := s.assign(ctx, "rpc.nonesuch"); got == nil {
		t.Error("s.assign(rpc.nonesuch): missing assignment")
	}
}
Verify that the option to remove the special behaviour of rpc.* methods can
be correctly disabled by the server options.
package jrpc2
import (
	"context"
	"encoding/json"
	"reflect"
	"testing"
	"time"
	"github.com/creachadair/jrpc2/channel"
	"github.com/creachadair/jrpc2/code"
	"github.com/google/go-cmp/cmp"
	"github.com/google/go-cmp/cmp/cmpopts"
)
func TestParseRequests(t *testing.T) {
	tests := []struct {
		input string
		want  []*Request
		err   error
	}{
		{`[]`, nil, nil},
		{`{}`, []*Request{{}}, ErrInvalidVersion},
		{`{"jsonrpc":"2.0", "method": "foo", "params":[1, 2, 3]}`, []*Request{{
			method: "foo",
			params: json.RawMessage(`[1, 2, 3]`),
		}}, nil},
		{`{"jsonrpc":"2.0", "method": "foo", "id":10332, "params":null}`, []*Request{{
			id: json.RawMessage("10332"), method: "foo",
		}}, nil},
		{`[ {"jsonrpc": "2.0", "id": 1, "method": "A", "params": {}},
          {"jsonrpc": "2.0", "params": [5], "method": "B"} ]`, []*Request{
			{method: "A", id: json.RawMessage(`1`), params: json.RawMessage(`{}`)},
			{method: "B", params: json.RawMessage(`[5]`)},
		}, nil},
		{`[{"id": 37, "method": "complain", "params":[]}]`, []*Request{
			{method: "complain", id: json.RawMessage(`37`), params: json.RawMessage(`[]`)},
		}, ErrInvalidVersion},
		{`{`, nil, Errorf(code.ParseError, "invalid request value")},
		{`["bad"{]`, nil, Errorf(code.ParseError, "invalid request value")},
	}
	for _, test := range tests {
		got, err := ParseRequests([]byte(test.input))
		if !errEQ(err, test.err) {
			t.Errorf("ParseRequests(%#q): got error %v, want %v", test.input, err, test.err)
			continue
		}
		diff := cmp.Diff(test.want, got, cmp.AllowUnexported(Request{}), cmpopts.EquateEmpty())
		if diff != "" {
			t.Errorf("ParseRequests(%#q): wrong result (-want, +got):\n%s", test.input, diff)
		}
	}
}
func errEQ(x, y error) bool {
	if x == nil {
		return y == nil
	} else if y == nil {
		return false
	}
	return code.FromError(x) == code.FromError(y) && x.Error() == y.Error()
}
func TestRequest_UnmarshalParams(t *testing.T) {
	type xy struct {
		X int  `json:"x"`
		Y bool `json:"y"`
	}
	tests := []struct {
		input   string
		want    interface{}
		pstring string
		code    code.Code
	}{
		{`{"jsonrpc":"2.0", "id":1, "method":"X", "params":[1,2]}`, []int{1, 2}, "[1,2]", code.NoError},
		{`{"jsonrpc":"2.0", "id":2, "method":"Y", "params":null}`, "", "", code.NoError},
		{`{"jsonrpc":"2.0", "id":2, "method":"Y"}`, 0, "", code.NoError},
		{`{"jsonrpc":"2.0", "id":3, "method":"Z", "params":{}}`, xy{}, "{}", code.NoError},
		{`{"jsonrpc":"2.0", "id":4, "method":"Z", "params":{"x":17}}`, xy{X: 17}, `{"x":17}`, code.NoError},
		{`{"jsonrpc":"2.0", "id":5, "method":"Z", "params":{"x":23, "y":true}}`,
			xy{X: 23, Y: true}, `{"x":23, "y":true}`, code.NoError},
		{`{"jsonrpc":"2.0", "id":6, "method":"Z", "params":{"x":23, "z":"wat"}}`,
			xy{X: 23}, `{"x":23, "z":"wat"}`, code.NoError},
	}
	for _, test := range tests {
		req, err := ParseRequests([]byte(test.input))
		if err != nil {
			t.Errorf("Parsing request %#q failed: %v", test.input, err)
		} else if len(req) != 1 {
			t.Fatalf("Wrong number of requests: got %d, want 1", len(req))
		}
		target := reflect.New(reflect.TypeOf(test.want)).Interface()
		{
			err := req[0].UnmarshalParams(target)
			if got := code.FromError(err); got != test.code {
				t.Errorf("UnmarshalParams error: got code %d, want %d [%v]", got, test.code, err)
			}
			if err != nil {
				continue
			}
		}
		got := reflect.ValueOf(target).Elem().Interface()
		if diff := cmp.Diff(test.want, got); diff != "" {
			t.Errorf("Parameters(%#q): wrong result (-want, +got):\n%s", test.input, diff)
		}
		if got := req[0].ParamString(); got != test.pstring {
			t.Errorf("ParamString(%#q): got %q, want %q", test.input, got, test.pstring)
		}
	}
}
type hmap map[string]Handler
func (h hmap) Assign(_ context.Context, method string) Handler { return h[method] }
func (h hmap) Names() []string                                 { return nil }
func TestClient_contextCancellation(t *testing.T) {
	started := make(chan struct{})
	stopped := make(chan struct{})
	cpipe, spipe := channel.Direct()
	srv := NewServer(hmap{
		"Hang": methodFunc(func(ctx context.Context, _ *Request) (interface{}, error) {
			close(started)  
			select {
			case <-stopped:
				t.Log("Server unblocked by client completing")
				return true, nil
			case <-time.After(10 * time.Second):
				t.Error("Server timed out before completion")
				return false, nil
			}
		}),
	}, nil).Start(spipe)
	c := NewClient(cpipe, nil)
	defer func() {
		c.Close()
		srv.Wait()
	}()
	ctx, cancel := context.WithCancel(context.Background())
	req, err := c.req(ctx, "Hang", nil)
	if err != nil {
		t.Fatalf("c.req(Hang) failed: %v", err)
	}
	rsps, err := c.send(ctx, jmessages{req})
	if err != nil {
		t.Fatalf("c.send(Hang) failed: %v", err)
	}
	<-started
	cancel()
	rsp := rsps[0]
	rsp.wait()
	close(stopped)
	if err := rsp.Error(); err != nil {
		if err.Code != code.Cancelled {
			t.Errorf("Response error for %q: got %v, want %v", rsp.ID(), err, code.Cancelled)
		}
	} else {
		t.Errorf("Response for %q: unexpectedly succeeded", rsp.ID())
	}
}
func TestServer_specialMethods(t *testing.T) {
	s := NewServer(hmap{
		"rpc.nonesuch": methodFunc(func(context.Context, *Request) (interface{}, error) {
			return "OK", nil
		}),
		"donkeybait": methodFunc(func(context.Context, *Request) (interface{}, error) {
			return true, nil
		}),
	}, nil)
	ctx := context.Background()
	for _, name := range []string{rpcServerInfo, "donkeybait"} {
		if got := s.assign(ctx, name); got == nil {
			t.Errorf("s.assign(%s): no method assigned", name)
		}
	}
	if got := s.assign(ctx, "rpc.nonesuch"); got != nil {
		t.Errorf("s.assign(rpc.nonesuch): got %v, want nil", got)
	}
}