Ai
2 Star 3 Fork 0

YashanDB Community/yasrpc

加入 Gitee
与超过 1200万 开发者一起发现、参与优秀开源项目,私有仓库也完全免费 :)
免费加入
文件
克隆/下载
server_test.go 14.51 KB
一键复制 编辑 原始数据 按行查看 历史
huangsiyuan 提交于 2024-08-08 17:17 +08:00 . feat: support carry map in context
123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393394395396397398399400401402403404405406407408409410411412413414415416417418419420421422423424425426427428429430431432433434435436437438439440441442443444445446447448449450451452453454455456457458459460461462463464465466467468469470471472473474475476477478479480481482483484485486487488489490491492493494495496497498499500501502503504505506507508509510511512513514515516517518519520521522523524525526527528529530531532533534535536537538539540541542543544545546547548549550551552553554555556557558559560561562563564565566567568569570571572573574575576577578579580581582583584585586587588589590591592593594595596597598599600601602603604605606607608609610611612613614615616617618619620621622623624625626627628629
package yasrpc
import (
"context"
"errors"
"fmt"
"log"
"net"
"net/http/httptest"
"reflect"
"runtime"
"strings"
"sync"
"sync/atomic"
"testing"
"time"
)
var (
newServer *Server
serverAddr, newServerAddr string
httpServerAddr string
once, newOnce, httpOnce sync.Once
)
const (
newHttpPath = "/foo"
)
type Args struct {
A, B int
}
type Reply struct {
C int
}
type Arith int
// Some of Arith's methods have value args, some have pointer args. That's deliberate.
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) String(args *Args, reply *string) error {
*reply = fmt.Sprintf("%d+%d=%d", args.A, args.B, args.A+args.B)
return nil
}
func (t *Arith) Scan(args string, reply *Reply) (err error) {
_, err = fmt.Sscan(args, &reply.C)
return
}
func (t *Arith) Error(args *Args, reply *Reply) error {
panic("ERROR")
}
func (t *Arith) SleepMilli(args *Args, reply *Reply) error {
time.Sleep(time.Duration(args.A) * time.Millisecond)
return nil
}
type hidden int
func (t *hidden) Exported(args Args, reply *Reply) error {
reply.C = args.A + args.B
return nil
}
type Embed struct {
hidden
}
type BuiltinTypes struct{}
func (BuiltinTypes) Map(args *Args, reply *map[int]int) error {
(*reply)[args.A] = args.B
return nil
}
func (BuiltinTypes) Slice(args *Args, reply *[]int) error {
*reply = append(*reply, args.A, args.B)
return nil
}
func (BuiltinTypes) Array(args *Args, reply *[2]int) error {
(*reply)[0] = args.A
(*reply)[1] = args.B
return nil
}
func listenTCP() (net.Listener, string) {
l, e := net.Listen("tcp", "127.0.0.1:0")
if e != nil {
log.Fatalf("net.Listen tcp :0: %v", e)
}
return l, l.Addr().String()
}
func startDefaultServer() {
Register(new(Arith))
Register(new(Embed))
Register(BuiltinTypes{})
var l net.Listener
l, serverAddr = listenTCP()
log.Println("Test RPC server listening on", serverAddr)
go Accept(l)
HandleHTTP()
httpOnce.Do(startHttpServer)
}
func startNewServer() {
newServer = NewServer(nil)
newServer.Register(new(Arith))
newServer.Register(new(Embed))
var l net.Listener
l, newServerAddr = listenTCP()
log.Println("NewServer test RPC server listening on", newServerAddr)
go newServer.Accept(l)
newServer.HandleHTTP(newHttpPath, "/bar")
httpOnce.Do(startDefaultServer)
}
func startHttpServer() {
server := httptest.NewServer(nil)
httpServerAddr = server.Listener.Addr().String()
log.Println("Test HTTP RPC server listening on ", httpServerAddr)
}
func TestRPC(t *testing.T) {
once.Do(startDefaultServer)
testRPC(t, serverAddr)
newOnce.Do(startNewServer)
testRPC(t, newServerAddr)
}
func testRPC(t *testing.T, addr string) {
client, err := Dial("tcp", addr, nil, nil)
if err != nil {
t.Fatal("dialing: ", err)
}
defer client.Close()
// Synchronous calls
args := &Args{7, 8}
reply := new(Reply)
err = client.Call(context.Background(), "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: expected %d got %d", reply.C, args.A+args.B)
}
// Methods exported from unexported embedded structs
args = &Args{7, 0}
reply = new(Reply)
err = client.Call(context.Background(), "Embed.Exported", 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: expected %d got %d", reply.C, args.A+args.B)
}
// Nonexistent method
args = &Args{7, 0}
reply = new(Reply)
err = client.Call(context.Background(), "Arith.BadOperation", args, reply)
// expect an error
if err == nil {
t.Error("BadOperation: expected error")
} else if !strings.HasPrefix(err.Error(), "rpc server: can't find method") {
t.Errorf("BadOperation: expected can't find method error; got %q", err)
}
// Unknown service
args = &Args{7, 8}
reply = new(Reply)
err = client.Call(context.Background(), "Arith.Unknown", args, reply)
if err == nil {
t.Error("expected error calling unknown service")
} else if !strings.Contains(err.Error(), "method") {
t.Error("expected error about method; got", err)
}
// Out of order.
args = &Args{7, 8}
mulReply := new(Reply)
mulCall := client.Go("Arith.Mul", args, mulReply, nil, nil)
addReply := new(Reply)
addCall := client.Go("Arith.Add", args, addReply, nil, 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: expected %d got %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: expected %d got %d", mulReply.C, args.A*args.B)
}
// Error test
args = &Args{7, 0}
reply = new(Reply)
err = client.Call(context.Background(), "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)
}
// Bad type.
reply = new(Reply)
err = client.Call(context.Background(), "Arith.Add", reply, reply) // args, reply would be the correct thing to use
if err == nil {
t.Error("expected error calling Arith.Add with wrong arg type")
} else if !strings.Contains(err.Error(), "type") {
t.Error("expected error about type; got", err)
}
// Non-struct argument
const Val = 12345
str := fmt.Sprint(Val)
reply = new(Reply)
err = client.Call(context.Background(), "Arith.Scan", &str, reply)
if err != nil {
t.Errorf("Scan: expected no error but got string %q", err.Error())
} else if reply.C != Val {
t.Errorf("Scan: expected %d got %d", Val, reply.C)
}
// Non-struct reply
args = &Args{27, 35}
str = ""
err = client.Call(context.Background(), "Arith.String", args, &str)
if err != nil {
t.Errorf("String: expected no error but got string %q", err.Error())
}
expect := fmt.Sprintf("%d+%d=%d", args.A, args.B, args.A+args.B)
if str != expect {
t.Errorf("String: expected %s got %s", expect, str)
}
args = &Args{7, 8}
reply = new(Reply)
err = client.Call(context.Background(), "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: expected %d got %d", reply.C, args.A*args.B)
}
}
func TestHTTP(t *testing.T) {
once.Do(startDefaultServer)
testHTTPRPC(t)
}
func testHTTPRPC(t *testing.T) {
client, err := DialHTTP("tcp", httpServerAddr, nil, nil)
if err != nil {
t.Fatal("dialing", err)
}
defer client.Close()
// Synchronous calls
args := &Args{7, 8}
reply := new(Reply)
err = client.Call(context.Background(), "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: expected %d got %d", reply.C, args.A+args.B)
}
}
func TestBuiltinTypes(t *testing.T) {
once.Do(startDefaultServer)
client, err := DialHTTP("tcp", httpServerAddr, nil, nil)
if err != nil {
t.Fatal("dialing", err)
}
defer client.Close()
// Map
args := &Args{7, 8}
replyMap := map[int]int{}
err = client.Call(context.Background(), "BuiltinTypes.Map", args, &replyMap)
if err != nil {
t.Errorf("Map: expected no error but got string %q", err.Error())
}
if replyMap[args.A] != args.B {
t.Errorf("Map: expected %d got %d", args.B, replyMap[args.A])
}
// Slice
args = &Args{7, 8}
replySlice := new([]int)
err = client.Call(context.Background(), "BuiltinTypes.Slice", args, replySlice)
if err != nil {
t.Errorf("Slice: expected no error but got string %q", err.Error())
}
if e := []int{args.A, args.B}; !reflect.DeepEqual(*replySlice, e) {
t.Errorf("Slice: expected %v got %v", e, replySlice)
}
// Array
args = &Args{7, 8}
replyArray := [2]int{}
err = client.Call(context.Background(), "BuiltinTypes.Array", args, &replyArray)
if err != nil {
t.Errorf("Array: expected no error but got string %q", err.Error())
}
if e := [2]int{args.A, args.B}; !reflect.DeepEqual(replyArray, e) {
t.Errorf("Array: expected %v got %v", e, replyArray)
}
}
type ReplyNotPointer int
type ArgNotPublic int
type ReplyNotPublic int
type NeedsPtrType int
type local struct{}
func (t *ReplyNotPointer) ReplyNotPointer(args *Args, reply Reply) error {
return nil
}
func (t *ArgNotPublic) ArgNotPublic(args *local, reply *Reply) error {
return nil
}
func (t *ReplyNotPublic) ReplyNotPublic(args *Args, reply *local) error {
return nil
}
func (t *NeedsPtrType) NeedsPtrType(args *Args, reply *Reply) error {
return nil
}
// Check that registration handles lots of bad methods and a type with no suitable methods.
func TestRegistrationError(t *testing.T) {
err := Register(new(ReplyNotPointer))
if err == nil {
t.Error("expected error registering ReplyNotPointer")
}
err = Register(new(ArgNotPublic))
if err == nil {
t.Error("expected error registering ArgNotPublic")
}
err = Register(new(ReplyNotPublic))
if err == nil {
t.Error("expected error registering ReplyNotPublic")
}
err = Register(NeedsPtrType(0))
if err == nil {
t.Error("expected error registering NeedsPtrType")
} else if !strings.Contains(err.Error(), "suitable type") {
t.Error("expected hint when registering NeedsPtrType")
}
}
type WriteFailCodec int
func (WriteFailCodec) Write(*Header, interface{}) error {
// the panic caused by this error used to not unlock a lock.
return errors.New("fail")
}
func (WriteFailCodec) ReadHeader(*Header) error {
select {}
}
func (WriteFailCodec) ReadBody(interface{}) error {
select {}
}
func (WriteFailCodec) Close() error {
return nil
}
func (WriteFailCodec) ClientIp() string {
return ""
}
func TestSendDeadlock(t *testing.T) {
client := newClientCodec(WriteFailCodec(0), nil, nil)
defer client.Close()
done := make(chan bool)
go func() {
testSendDeadlock(client)
testSendDeadlock(client)
done <- true
}()
select {
case <-done:
return
case <-time.After(5 * time.Second):
t.Fatal("deadlock")
}
}
func testSendDeadlock(client *Client) {
defer func() {
recover()
}()
args := &Args{7, 8}
reply := new(Reply)
client.Call(context.Background(), "Arith.Add", args, reply)
}
func dialDirect() (*Client, error) {
return Dial("tcp", serverAddr, nil, nil)
}
func dialHTTP() (*Client, error) {
return DialHTTP("tcp", httpServerAddr, nil, nil)
}
func countMallocs(dial func() (*Client, error), t *testing.T) float64 {
once.Do(startDefaultServer)
client, err := dial()
if err != nil {
t.Fatal("error dialing", err)
}
defer client.Close()
args := &Args{7, 8}
reply := new(Reply)
return testing.AllocsPerRun(100, func() {
err := client.Call(context.Background(), "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: expected %d got %d", reply.C, args.A+args.B)
}
})
}
func TestCountMallocs(t *testing.T) {
if testing.Short() {
t.Skip("skipping malloc count in short mode")
}
runtime.GOMAXPROCS(1)
fmt.Printf("mallocs per rpc round trip: %v\n", countMallocs(dialDirect, t))
}
func TestCountMallocsOverHTTP(t *testing.T) {
if testing.Short() {
t.Skip("skipping malloc count in short mode")
}
runtime.GOMAXPROCS(1)
fmt.Printf("mallocs per HTTP rpc round trip: %v\n", countMallocs(dialHTTP, t))
}
func TestTCPClose(t *testing.T) {
once.Do(startDefaultServer)
client, err := dialHTTP()
if err != nil {
t.Fatalf("dialing: %v", err)
}
defer client.Close()
args := Args{17, 8}
var reply Reply
err = client.Call(context.Background(), "Arith.Mul", args, &reply)
if err != nil {
t.Fatal("arith error:", err)
}
t.Logf("Arith: %d*%d=%d\n", args.A, args.B, reply)
if reply.C != args.A*args.B {
t.Errorf("Add: expected %d got %d", reply.C, args.A*args.B)
}
}
func TestErrorAfterClientClose(t *testing.T) {
once.Do(startDefaultServer)
client, err := dialHTTP()
if err != nil {
t.Fatalf("dialing: %v", err)
}
err = client.Close()
if err != nil {
t.Fatal("close error:", err)
}
err = client.Call(context.Background(), "Arith.Add", &Args{7, 9}, new(Reply))
if err != ErrShutdown {
t.Errorf("Forever: expected ErrShutdown got %v", err)
}
}
func TestAcceptExitAfterListenerClose(t *testing.T) {
newServer := NewServer(nil)
newServer.Register(new(Arith))
var l net.Listener
l, _ = listenTCP()
l.Close()
newServer.Accept(l)
}
func benchmarkEndToEnd(dial func() (*Client, error), b *testing.B) {
once.Do(startDefaultServer)
client, err := dial()
if err != nil {
b.Fatal("error dialing:", err)
}
defer client.Close()
// Synchronous calls
args := &Args{7, 8}
b.ResetTimer()
b.RunParallel(func(pb *testing.PB) {
reply := new(Reply)
for pb.Next() {
err := client.Call(context.Background(), "Arith.Add", args, reply)
if err != nil {
b.Fatalf("rpc error: Add: expected no error but got string %q", err.Error())
}
if reply.C != args.A+args.B {
b.Fatalf("rpc error: Add: expected %d got %d", reply.C, args.A+args.B)
}
}
})
}
func benchmarkEndToEndAsync(dial func() (*Client, error), b *testing.B) {
if b.N == 0 {
return
}
const MaxConcurrentCalls = 100
once.Do(startDefaultServer)
client, err := dial()
if err != nil {
b.Fatal("error dialing:", err)
}
defer client.Close()
// Asynchronous calls
args := &Args{7, 8}
procs := 4 * runtime.GOMAXPROCS(-1)
send := int32(b.N)
recv := int32(b.N)
var wg sync.WaitGroup
wg.Add(procs)
gate := make(chan bool, MaxConcurrentCalls)
res := make(chan *Call, MaxConcurrentCalls)
b.ResetTimer()
for p := 0; p < procs; p++ {
go func() {
for atomic.AddInt32(&send, -1) >= 0 {
gate <- true
reply := new(Reply)
client.Go("Arith.Add", args, reply, res, nil)
}
}()
go func() {
for call := range res {
A := call.Args.(*Args).A
B := call.Args.(*Args).B
C := call.Reply.(*Reply).C
if A+B != C {
b.Errorf("incorrect reply: Add: expected %d got %d", A+B, C)
return
}
<-gate
if atomic.AddInt32(&recv, -1) == 0 {
close(res)
}
}
wg.Done()
}()
}
wg.Wait()
}
func BenchmarkEndToEnd(b *testing.B) {
benchmarkEndToEnd(dialDirect, b)
}
func BenchmarkEndToEndHTTP(b *testing.B) {
benchmarkEndToEnd(dialHTTP, b)
}
func BenchmarkEndToEndAsync(b *testing.B) {
benchmarkEndToEndAsync(dialDirect, b)
}
func BenchmarkEndToEndAsyncHTTP(b *testing.B) {
benchmarkEndToEndAsync(dialHTTP, b)
}
Loading...
马建仓 AI 助手
尝试更多
代码解读
代码找茬
代码优化
1
https://gitee.com/yashan_tech/yasrpc.git
git@gitee.com:yashan_tech/yasrpc.git
yashan_tech
yasrpc
yasrpc
master

搜索帮助