/* * Minio Cloud Storage, (C) 2018 Minio, Inc. * * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. * You may obtain a copy of the License at * * http://www.apache.org/licenses/LICENSE-2.0 * * Unless required by applicable law or agreed to in writing, software * distributed under the License is distributed on an "AS IS" BASIS, * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. * See the License for the specific language governing permissions and * limitations under the License. */ package rpc import ( "bytes" "encoding/gob" "errors" "net/http" "net/http/httptest" "reflect" "testing" ) func gobEncode(e interface{}) ([]byte, error) { var buf bytes.Buffer err := gob.NewEncoder(&buf).Encode(e) return buf.Bytes(), err } type Args struct { A, B int } func (a *Args) Authenticate() (err error) { if a.A == 0 && a.B == 0 { err = errors.New("authenticated failed") } return } type Quotient struct { Quo, Rem int } type Arith struct{} func (t *Arith) Multiply(args *Args, reply *int) error { *reply = args.A * args.B return nil } func (t *Arith) Divide(args *Args, quo *Quotient) error { if args.B == 0 { return errors.New("divide by zero") } quo.Quo = args.A / args.B quo.Rem = args.A % args.B return nil } type mytype int type Auth struct{} func (a Auth) Authenticate() error { return nil } // exported method. func (t mytype) Foo(a *Auth, b *int) error { return nil } // incompatible method because of unexported method. func (t mytype) foo(a *Auth, b *int) error { return nil } // incompatible method because of first argument is not Authenticator. func (t *mytype) Bar(a, b *int) error { return nil } // incompatible method because of error is not returned. func (t mytype) IncompatFoo(a, b *int) { } // incompatible method because of second argument is not a pointer. func (t *mytype) IncompatBar(a *int, b int) error { return nil } func TestIsExportedOrBuiltinType(t *testing.T) { var i int case1Type := reflect.TypeOf(i) var iptr *int case2Type := reflect.TypeOf(iptr) var a Arith case3Type := reflect.TypeOf(a) var aptr *Arith case4Type := reflect.TypeOf(aptr) var m mytype case5Type := reflect.TypeOf(m) var mptr *mytype case6Type := reflect.TypeOf(mptr) testCases := []struct { t reflect.Type expectedResult bool }{ {case1Type, true}, {case2Type, true}, {case3Type, true}, {case4Type, true}, // Type.Name() starts with lower case and Type.PkgPath() is not empty. {case5Type, false}, // Type.Name() starts with lower case and Type.PkgPath() is not empty. {case6Type, false}, } for i, testCase := range testCases { result := isExportedOrBuiltinType(testCase.t) if result != testCase.expectedResult { t.Fatalf("case %v: expected: %v, got: %v\n", i+1, testCase.expectedResult, result) } } } func TestGetMethodMap(t *testing.T) { var a Arith case1Type := reflect.TypeOf(a) var aptr *Arith case2Type := reflect.TypeOf(aptr) var m mytype case3Type := reflect.TypeOf(m) var mptr *mytype case4Type := reflect.TypeOf(mptr) testCases := []struct { t reflect.Type expectedResult int }{ // No methods exported. {case1Type, 0}, // Multiply and Divide methods are exported. {case2Type, 2}, // Foo method is exported. {case3Type, 1}, // Foo method is exported. {case4Type, 1}, } for i, testCase := range testCases { m := getMethodMap(testCase.t) result := len(m) if result != testCase.expectedResult { t.Fatalf("case %v: expected: %v, got: %v\n", i+1, testCase.expectedResult, result) } } } func TestServerRegisterName(t *testing.T) { case1Receiver := &Arith{} var case2Receiver mytype var case3Receiver *Arith i := 0 var case4Receiver = &i var case5Receiver Arith testCases := []struct { name string receiver interface{} expectErr bool }{ {"Arith", case1Receiver, false}, {"arith", case1Receiver, false}, {"Arith", case2Receiver, false}, // nil receiver error. {"Arith", nil, true}, // nil receiver error. {"Arith", case3Receiver, true}, // rpc.Register: type Arith has no exported methods of suitable type error. {"Arith", case4Receiver, true}, // rpc.Register: type Arith has no exported methods of suitable type (hint: pass a pointer to value of that type) error. {"Arith", case5Receiver, true}, } for i, testCase := range testCases { err := NewServer().RegisterName(testCase.name, testCase.receiver) expectErr := (err != nil) if expectErr != testCase.expectErr { t.Fatalf("case %v: expected: %v, got: %v\n", i+1, testCase.expectErr, expectErr) } } } func TestServerCall(t *testing.T) { server1 := NewServer() if err := server1.RegisterName("Arith", &Arith{}); err != nil { t.Fatalf("unexpected error %v", err) } server2 := NewServer() if err := server2.RegisterName("arith", &Arith{}); err != nil { t.Fatalf("unexpected error %v", err) } case1ArgBytes, err := gobEncode(&Args{7, 8}) if err != nil { t.Fatalf("unexpected error %v", err) } reply := 7 * 8 case1ExpectedResult, err := gobEncode(&reply) if err != nil { t.Fatalf("unexpected error %v", err) } case2ArgBytes, err := gobEncode(&Args{}) if err != nil { t.Fatalf("unexpected error %v", err) } testCases := []struct { server *Server serviceMethod string argBytes []byte expectedResult []byte expectErr bool }{ {server1, "Arith.Multiply", case1ArgBytes, case1ExpectedResult, false}, {server2, "arith.Multiply", case1ArgBytes, case1ExpectedResult, false}, // invalid service/method request ill-formed error. {server1, "Multiply", nil, nil, true}, // can't find service error. {server1, "arith.Multiply", nil, nil, true}, // can't find method error. {server1, "Arith.Add", nil, nil, true}, // gob decode error. {server1, "Arith.Multiply", []byte{10}, nil, true}, // authentication error. {server1, "Arith.Multiply", case2ArgBytes, nil, true}, } for i, testCase := range testCases { buf := bufPool.Get() defer bufPool.Put(buf) err := testCase.server.call(testCase.serviceMethod, testCase.argBytes, buf) expectErr := (err != nil) if expectErr != testCase.expectErr { t.Fatalf("case %v: error: expected: %v, got: %v\n", i+1, testCase.expectErr, expectErr) } if !testCase.expectErr { if !reflect.DeepEqual(buf.Bytes(), testCase.expectedResult) { t.Fatalf("case %v: result: expected: %v, got: %v\n", i+1, testCase.expectedResult, buf.Bytes()) } } } } func TestServerServeHTTP(t *testing.T) { server1 := NewServer() if err := server1.RegisterName("Arith", &Arith{}); err != nil { t.Fatalf("unexpected error %v", err) } argBytes, err := gobEncode(&Args{7, 8}) if err != nil { t.Fatalf("unexpected error %v", err) } requestBodyData, err := gobEncode(CallRequest{Method: "Arith.Multiply", ArgBytes: argBytes}) if err != nil { t.Fatalf("unexpected error %v", err) } case1Request, err := http.NewRequest("POST", "http://localhost:12345/", bytes.NewReader(requestBodyData)) if err != nil { t.Fatalf("unexpected error %v", err) } reply := 7 * 8 replyBytes, err := gobEncode(&reply) if err != nil { t.Fatalf("unexpected error %v", err) } case1Result, err := gobEncode(CallResponse{ReplyBytes: replyBytes}) if err != nil { t.Fatalf("unexpected error %v", err) } case2Request, err := http.NewRequest("GET", "http://localhost:12345/", bytes.NewReader([]byte{})) if err != nil { t.Fatalf("unexpected error %v", err) } case3Request, err := http.NewRequest("POST", "http://localhost:12345/", bytes.NewReader([]byte{10, 20})) if err != nil { t.Fatalf("unexpected error %v", err) } requestBodyData, err = gobEncode(CallRequest{Method: "Arith.Add", ArgBytes: argBytes}) if err != nil { t.Fatalf("unexpected error %v", err) } case4Request, err := http.NewRequest("POST", "http://localhost:12345/", bytes.NewReader(requestBodyData)) if err != nil { t.Fatalf("unexpected error %v", err) } case4Result, err := gobEncode(CallResponse{Error: "can't find method Add"}) if err != nil { t.Fatalf("unexpected error %v", err) } testCases := []struct { server *Server httpRequest *http.Request expectedCode int expectedResult []byte }{ {server1, case1Request, http.StatusOK, case1Result}, {server1, case2Request, http.StatusMethodNotAllowed, nil}, {server1, case3Request, http.StatusBadRequest, nil}, {server1, case4Request, http.StatusOK, case4Result}, } for i, testCase := range testCases { writer := httptest.NewRecorder() testCase.server.ServeHTTP(writer, testCase.httpRequest) if writer.Code != testCase.expectedCode { t.Fatalf("case %v: code: expected: %v, got: %v\n", i+1, testCase.expectedCode, writer.Code) } if testCase.expectedCode == http.StatusOK { result := writer.Body.Bytes() if !reflect.DeepEqual(result, testCase.expectedResult) { t.Fatalf("case %v: result: expected: %v, got: %v\n", i+1, testCase.expectedResult, result) } } } }