|  | // Copyright 2010 The Go Authors. All rights reserved. | 
|  | // Use of this source code is governed by a BSD-style | 
|  | // license that can be found in the LICENSE file. | 
|  |  | 
|  | package jsonrpc | 
|  |  | 
|  | import ( | 
|  | "encoding/json" | 
|  | "errors" | 
|  | "fmt" | 
|  | "io" | 
|  | "net" | 
|  | "net/rpc" | 
|  | "reflect" | 
|  | "strings" | 
|  | "testing" | 
|  | ) | 
|  |  | 
|  | type Args struct { | 
|  | A, B int | 
|  | } | 
|  |  | 
|  | type Reply struct { | 
|  | C int | 
|  | } | 
|  |  | 
|  | type Arith int | 
|  |  | 
|  | type ArithAddResp struct { | 
|  | Id     any   `json:"id"` | 
|  | Result Reply `json:"result"` | 
|  | Error  any   `json:"error"` | 
|  | } | 
|  |  | 
|  | func (t *Arith) Add(args *Args, reply *Reply) error { | 
|  | reply.C = args.A + args.B | 
|  | return nil | 
|  | } | 
|  |  | 
|  | func (t *Arith) Mul(args *Args, reply *Reply) error { | 
|  | reply.C = args.A * args.B | 
|  | return nil | 
|  | } | 
|  |  | 
|  | func (t *Arith) Div(args *Args, reply *Reply) error { | 
|  | if args.B == 0 { | 
|  | return errors.New("divide by zero") | 
|  | } | 
|  | reply.C = args.A / args.B | 
|  | return nil | 
|  | } | 
|  |  | 
|  | func (t *Arith) Error(args *Args, reply *Reply) error { | 
|  | panic("ERROR") | 
|  | } | 
|  |  | 
|  | type BuiltinTypes struct{} | 
|  |  | 
|  | func (BuiltinTypes) Map(i int, reply *map[int]int) error { | 
|  | (*reply)[i] = i | 
|  | return nil | 
|  | } | 
|  |  | 
|  | func (BuiltinTypes) Slice(i int, reply *[]int) error { | 
|  | *reply = append(*reply, i) | 
|  | return nil | 
|  | } | 
|  |  | 
|  | func (BuiltinTypes) Array(i int, reply *[1]int) error { | 
|  | (*reply)[0] = i | 
|  | return nil | 
|  | } | 
|  |  | 
|  | func init() { | 
|  | rpc.Register(new(Arith)) | 
|  | rpc.Register(BuiltinTypes{}) | 
|  | } | 
|  |  | 
|  | func TestServerNoParams(t *testing.T) { | 
|  | cli, srv := net.Pipe() | 
|  | defer cli.Close() | 
|  | go ServeConn(srv) | 
|  | dec := json.NewDecoder(cli) | 
|  |  | 
|  | fmt.Fprintf(cli, `{"method": "Arith.Add", "id": "123"}`) | 
|  | var resp ArithAddResp | 
|  | if err := dec.Decode(&resp); err != nil { | 
|  | t.Fatalf("Decode after no params: %s", err) | 
|  | } | 
|  | if resp.Error == nil { | 
|  | t.Fatalf("Expected error, got nil") | 
|  | } | 
|  | } | 
|  |  | 
|  | func TestServerEmptyMessage(t *testing.T) { | 
|  | cli, srv := net.Pipe() | 
|  | defer cli.Close() | 
|  | go ServeConn(srv) | 
|  | dec := json.NewDecoder(cli) | 
|  |  | 
|  | fmt.Fprintf(cli, "{}") | 
|  | var resp ArithAddResp | 
|  | if err := dec.Decode(&resp); err != nil { | 
|  | t.Fatalf("Decode after empty: %s", err) | 
|  | } | 
|  | if resp.Error == nil { | 
|  | t.Fatalf("Expected error, got nil") | 
|  | } | 
|  | } | 
|  |  | 
|  | func TestServer(t *testing.T) { | 
|  | cli, srv := net.Pipe() | 
|  | defer cli.Close() | 
|  | go ServeConn(srv) | 
|  | dec := json.NewDecoder(cli) | 
|  |  | 
|  | // Send hand-coded requests to server, parse responses. | 
|  | for i := 0; i < 10; i++ { | 
|  | fmt.Fprintf(cli, `{"method": "Arith.Add", "id": "\u%04d", "params": [{"A": %d, "B": %d}]}`, i, i, i+1) | 
|  | var resp ArithAddResp | 
|  | err := dec.Decode(&resp) | 
|  | if err != nil { | 
|  | t.Fatalf("Decode: %s", err) | 
|  | } | 
|  | if resp.Error != nil { | 
|  | t.Fatalf("resp.Error: %s", resp.Error) | 
|  | } | 
|  | if resp.Id.(string) != string(rune(i)) { | 
|  | t.Fatalf("resp: bad id %q want %q", resp.Id.(string), string(rune(i))) | 
|  | } | 
|  | if resp.Result.C != 2*i+1 { | 
|  | t.Fatalf("resp: bad result: %d+%d=%d", i, i+1, resp.Result.C) | 
|  | } | 
|  | } | 
|  | } | 
|  |  | 
|  | func TestClient(t *testing.T) { | 
|  | // Assume server is okay (TestServer is above). | 
|  | // Test client against server. | 
|  | cli, srv := net.Pipe() | 
|  | go ServeConn(srv) | 
|  |  | 
|  | client := NewClient(cli) | 
|  | defer client.Close() | 
|  |  | 
|  | // Synchronous calls | 
|  | args := &Args{7, 8} | 
|  | reply := new(Reply) | 
|  | err := client.Call("Arith.Add", args, reply) | 
|  | if err != nil { | 
|  | t.Errorf("Add: expected no error but got string %q", err.Error()) | 
|  | } | 
|  | if reply.C != args.A+args.B { | 
|  | t.Errorf("Add: got %d expected %d", reply.C, args.A+args.B) | 
|  | } | 
|  |  | 
|  | args = &Args{7, 8} | 
|  | reply = new(Reply) | 
|  | err = client.Call("Arith.Mul", args, reply) | 
|  | if err != nil { | 
|  | t.Errorf("Mul: expected no error but got string %q", err.Error()) | 
|  | } | 
|  | if reply.C != args.A*args.B { | 
|  | t.Errorf("Mul: got %d expected %d", reply.C, args.A*args.B) | 
|  | } | 
|  |  | 
|  | // Out of order. | 
|  | args = &Args{7, 8} | 
|  | mulReply := new(Reply) | 
|  | mulCall := client.Go("Arith.Mul", args, mulReply, nil) | 
|  | addReply := new(Reply) | 
|  | addCall := client.Go("Arith.Add", args, addReply, nil) | 
|  |  | 
|  | addCall = <-addCall.Done | 
|  | if addCall.Error != nil { | 
|  | t.Errorf("Add: expected no error but got string %q", addCall.Error.Error()) | 
|  | } | 
|  | if addReply.C != args.A+args.B { | 
|  | t.Errorf("Add: got %d expected %d", addReply.C, args.A+args.B) | 
|  | } | 
|  |  | 
|  | mulCall = <-mulCall.Done | 
|  | if mulCall.Error != nil { | 
|  | t.Errorf("Mul: expected no error but got string %q", mulCall.Error.Error()) | 
|  | } | 
|  | if mulReply.C != args.A*args.B { | 
|  | t.Errorf("Mul: got %d expected %d", mulReply.C, args.A*args.B) | 
|  | } | 
|  |  | 
|  | // Error test | 
|  | args = &Args{7, 0} | 
|  | reply = new(Reply) | 
|  | err = client.Call("Arith.Div", args, reply) | 
|  | // expect an error: zero divide | 
|  | if err == nil { | 
|  | t.Error("Div: expected error") | 
|  | } else if err.Error() != "divide by zero" { | 
|  | t.Error("Div: expected divide by zero error; got", err) | 
|  | } | 
|  | } | 
|  |  | 
|  | func TestBuiltinTypes(t *testing.T) { | 
|  | cli, srv := net.Pipe() | 
|  | go ServeConn(srv) | 
|  |  | 
|  | client := NewClient(cli) | 
|  | defer client.Close() | 
|  |  | 
|  | // Map | 
|  | arg := 7 | 
|  | replyMap := map[int]int{} | 
|  | err := client.Call("BuiltinTypes.Map", arg, &replyMap) | 
|  | if err != nil { | 
|  | t.Errorf("Map: expected no error but got string %q", err.Error()) | 
|  | } | 
|  | if replyMap[arg] != arg { | 
|  | t.Errorf("Map: expected %d got %d", arg, replyMap[arg]) | 
|  | } | 
|  |  | 
|  | // Slice | 
|  | replySlice := []int{} | 
|  | err = client.Call("BuiltinTypes.Slice", arg, &replySlice) | 
|  | if err != nil { | 
|  | t.Errorf("Slice: expected no error but got string %q", err.Error()) | 
|  | } | 
|  | if e := []int{arg}; !reflect.DeepEqual(replySlice, e) { | 
|  | t.Errorf("Slice: expected %v got %v", e, replySlice) | 
|  | } | 
|  |  | 
|  | // Array | 
|  | replyArray := [1]int{} | 
|  | err = client.Call("BuiltinTypes.Array", arg, &replyArray) | 
|  | if err != nil { | 
|  | t.Errorf("Array: expected no error but got string %q", err.Error()) | 
|  | } | 
|  | if e := [1]int{arg}; !reflect.DeepEqual(replyArray, e) { | 
|  | t.Errorf("Array: expected %v got %v", e, replyArray) | 
|  | } | 
|  | } | 
|  |  | 
|  | func TestMalformedInput(t *testing.T) { | 
|  | cli, srv := net.Pipe() | 
|  | go cli.Write([]byte(`{id:1}`)) // invalid json | 
|  | ServeConn(srv)                 // must return, not loop | 
|  | } | 
|  |  | 
|  | func TestMalformedOutput(t *testing.T) { | 
|  | cli, srv := net.Pipe() | 
|  | go srv.Write([]byte(`{"id":0,"result":null,"error":null}`)) | 
|  | go io.ReadAll(srv) | 
|  |  | 
|  | client := NewClient(cli) | 
|  | defer client.Close() | 
|  |  | 
|  | args := &Args{7, 8} | 
|  | reply := new(Reply) | 
|  | err := client.Call("Arith.Add", args, reply) | 
|  | if err == nil { | 
|  | t.Error("expected error") | 
|  | } | 
|  | } | 
|  |  | 
|  | func TestServerErrorHasNullResult(t *testing.T) { | 
|  | var out strings.Builder | 
|  | sc := NewServerCodec(struct { | 
|  | io.Reader | 
|  | io.Writer | 
|  | io.Closer | 
|  | }{ | 
|  | Reader: strings.NewReader(`{"method": "Arith.Add", "id": "123", "params": []}`), | 
|  | Writer: &out, | 
|  | Closer: io.NopCloser(nil), | 
|  | }) | 
|  | r := new(rpc.Request) | 
|  | if err := sc.ReadRequestHeader(r); err != nil { | 
|  | t.Fatal(err) | 
|  | } | 
|  | const valueText = "the value we don't want to see" | 
|  | const errorText = "some error" | 
|  | err := sc.WriteResponse(&rpc.Response{ | 
|  | ServiceMethod: "Method", | 
|  | Seq:           1, | 
|  | Error:         errorText, | 
|  | }, valueText) | 
|  | if err != nil { | 
|  | t.Fatal(err) | 
|  | } | 
|  | if !strings.Contains(out.String(), errorText) { | 
|  | t.Fatalf("Response didn't contain expected error %q: %s", errorText, &out) | 
|  | } | 
|  | if strings.Contains(out.String(), valueText) { | 
|  | t.Errorf("Response contains both an error and value: %s", &out) | 
|  | } | 
|  | } | 
|  |  | 
|  | func TestUnexpectedError(t *testing.T) { | 
|  | cli, srv := myPipe() | 
|  | go cli.PipeWriter.CloseWithError(errors.New("unexpected error!")) // reader will get this error | 
|  | ServeConn(srv)                                                    // must return, not loop | 
|  | } | 
|  |  | 
|  | // Copied from package net. | 
|  | func myPipe() (*pipe, *pipe) { | 
|  | r1, w1 := io.Pipe() | 
|  | r2, w2 := io.Pipe() | 
|  |  | 
|  | return &pipe{r1, w2}, &pipe{r2, w1} | 
|  | } | 
|  |  | 
|  | type pipe struct { | 
|  | *io.PipeReader | 
|  | *io.PipeWriter | 
|  | } | 
|  |  | 
|  | type pipeAddr int | 
|  |  | 
|  | func (pipeAddr) Network() string { | 
|  | return "pipe" | 
|  | } | 
|  |  | 
|  | func (pipeAddr) String() string { | 
|  | return "pipe" | 
|  | } | 
|  |  | 
|  | func (p *pipe) Close() error { | 
|  | err := p.PipeReader.Close() | 
|  | err1 := p.PipeWriter.Close() | 
|  | if err == nil { | 
|  | err = err1 | 
|  | } | 
|  | return err | 
|  | } | 
|  |  | 
|  | func (p *pipe) LocalAddr() net.Addr { | 
|  | return pipeAddr(0) | 
|  | } | 
|  |  | 
|  | func (p *pipe) RemoteAddr() net.Addr { | 
|  | return pipeAddr(0) | 
|  | } | 
|  |  | 
|  | func (p *pipe) SetTimeout(nsec int64) error { | 
|  | return errors.New("net.Pipe does not support timeouts") | 
|  | } | 
|  |  | 
|  | func (p *pipe) SetReadTimeout(nsec int64) error { | 
|  | return errors.New("net.Pipe does not support timeouts") | 
|  | } | 
|  |  | 
|  | func (p *pipe) SetWriteTimeout(nsec int64) error { | 
|  | return errors.New("net.Pipe does not support timeouts") | 
|  | } |