mirror of https://github.com/grpc/grpc-go.git
				
				
				
			
		
			
				
	
	
		
			180 lines
		
	
	
		
			5.3 KiB
		
	
	
	
		
			Go
		
	
	
	
			
		
		
	
	
			180 lines
		
	
	
		
			5.3 KiB
		
	
	
	
		
			Go
		
	
	
	
| /*
 | |
|  *
 | |
|  * Copyright 2020 gRPC authors.
 | |
|  *
 | |
|  * 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 stubserver is a stubbable implementation of
 | |
| // google.golang.org/grpc/test/grpc_testing for testing purposes.
 | |
| package stubserver
 | |
| 
 | |
| import (
 | |
| 	"context"
 | |
| 	"fmt"
 | |
| 	"net"
 | |
| 	"time"
 | |
| 
 | |
| 	"google.golang.org/grpc"
 | |
| 	"google.golang.org/grpc/connectivity"
 | |
| 	"google.golang.org/grpc/credentials/insecure"
 | |
| 	"google.golang.org/grpc/resolver"
 | |
| 	"google.golang.org/grpc/resolver/manual"
 | |
| 	"google.golang.org/grpc/serviceconfig"
 | |
| 
 | |
| 	testpb "google.golang.org/grpc/test/grpc_testing"
 | |
| )
 | |
| 
 | |
| // StubServer is a server that is easy to customize within individual test
 | |
| // cases.
 | |
| type StubServer struct {
 | |
| 	// Guarantees we satisfy this interface; panics if unimplemented methods are called.
 | |
| 	testpb.TestServiceServer
 | |
| 
 | |
| 	// Customizable implementations of server handlers.
 | |
| 	EmptyCallF      func(ctx context.Context, in *testpb.Empty) (*testpb.Empty, error)
 | |
| 	UnaryCallF      func(ctx context.Context, in *testpb.SimpleRequest) (*testpb.SimpleResponse, error)
 | |
| 	FullDuplexCallF func(stream testpb.TestService_FullDuplexCallServer) error
 | |
| 
 | |
| 	// A client connected to this service the test may use.  Created in Start().
 | |
| 	Client testpb.TestServiceClient
 | |
| 	CC     *grpc.ClientConn
 | |
| 	S      *grpc.Server
 | |
| 
 | |
| 	// Parameters for Listen and Dial. Defaults will be used if these are empty
 | |
| 	// before Start.
 | |
| 	Network string
 | |
| 	Address string
 | |
| 	Target  string
 | |
| 
 | |
| 	cleanups []func() // Lambdas executed in Stop(); populated by Start().
 | |
| 
 | |
| 	// Set automatically if Target == ""
 | |
| 	R *manual.Resolver
 | |
| }
 | |
| 
 | |
| // EmptyCall is the handler for testpb.EmptyCall
 | |
| func (ss *StubServer) EmptyCall(ctx context.Context, in *testpb.Empty) (*testpb.Empty, error) {
 | |
| 	return ss.EmptyCallF(ctx, in)
 | |
| }
 | |
| 
 | |
| // UnaryCall is the handler for testpb.UnaryCall
 | |
| func (ss *StubServer) UnaryCall(ctx context.Context, in *testpb.SimpleRequest) (*testpb.SimpleResponse, error) {
 | |
| 	return ss.UnaryCallF(ctx, in)
 | |
| }
 | |
| 
 | |
| // FullDuplexCall is the handler for testpb.FullDuplexCall
 | |
| func (ss *StubServer) FullDuplexCall(stream testpb.TestService_FullDuplexCallServer) error {
 | |
| 	return ss.FullDuplexCallF(stream)
 | |
| }
 | |
| 
 | |
| // Start starts the server and creates a client connected to it.
 | |
| func (ss *StubServer) Start(sopts []grpc.ServerOption, dopts ...grpc.DialOption) error {
 | |
| 	if err := ss.StartServer(sopts...); err != nil {
 | |
| 		return err
 | |
| 	}
 | |
| 	return ss.StartClient(dopts...)
 | |
| }
 | |
| 
 | |
| // StartServer only starts the server. It does not create a client to it.
 | |
| func (ss *StubServer) StartServer(sopts ...grpc.ServerOption) error {
 | |
| 	if ss.Network == "" {
 | |
| 		ss.Network = "tcp"
 | |
| 	}
 | |
| 	if ss.Address == "" {
 | |
| 		ss.Address = "localhost:0"
 | |
| 	}
 | |
| 	if ss.Target == "" {
 | |
| 		ss.R = manual.NewBuilderWithScheme("whatever")
 | |
| 	}
 | |
| 
 | |
| 	lis, err := net.Listen(ss.Network, ss.Address)
 | |
| 	if err != nil {
 | |
| 		return fmt.Errorf("net.Listen(%q, %q) = %v", ss.Network, ss.Address, err)
 | |
| 	}
 | |
| 	ss.Address = lis.Addr().String()
 | |
| 	ss.cleanups = append(ss.cleanups, func() { lis.Close() })
 | |
| 
 | |
| 	s := grpc.NewServer(sopts...)
 | |
| 	testpb.RegisterTestServiceServer(s, ss)
 | |
| 	go s.Serve(lis)
 | |
| 	ss.cleanups = append(ss.cleanups, s.Stop)
 | |
| 	ss.S = s
 | |
| 	return nil
 | |
| }
 | |
| 
 | |
| // StartClient creates a client connected to this service that the test may use.
 | |
| // The newly created client will be available in the Client field of StubServer.
 | |
| func (ss *StubServer) StartClient(dopts ...grpc.DialOption) error {
 | |
| 	opts := append([]grpc.DialOption{grpc.WithTransportCredentials(insecure.NewCredentials())}, dopts...)
 | |
| 	if ss.R != nil {
 | |
| 		ss.Target = ss.R.Scheme() + ":///" + ss.Address
 | |
| 		opts = append(opts, grpc.WithResolvers(ss.R))
 | |
| 	}
 | |
| 
 | |
| 	cc, err := grpc.Dial(ss.Target, opts...)
 | |
| 	if err != nil {
 | |
| 		return fmt.Errorf("grpc.Dial(%q) = %v", ss.Target, err)
 | |
| 	}
 | |
| 	ss.CC = cc
 | |
| 	if ss.R != nil {
 | |
| 		ss.R.UpdateState(resolver.State{Addresses: []resolver.Address{{Addr: ss.Address}}})
 | |
| 	}
 | |
| 	if err := waitForReady(cc); err != nil {
 | |
| 		return err
 | |
| 	}
 | |
| 
 | |
| 	ss.cleanups = append(ss.cleanups, func() { cc.Close() })
 | |
| 
 | |
| 	ss.Client = testpb.NewTestServiceClient(cc)
 | |
| 	return nil
 | |
| }
 | |
| 
 | |
| // NewServiceConfig applies sc to ss.Client using the resolver (if present).
 | |
| func (ss *StubServer) NewServiceConfig(sc string) {
 | |
| 	if ss.R != nil {
 | |
| 		ss.R.UpdateState(resolver.State{Addresses: []resolver.Address{{Addr: ss.Address}}, ServiceConfig: parseCfg(ss.R, sc)})
 | |
| 	}
 | |
| }
 | |
| 
 | |
| func waitForReady(cc *grpc.ClientConn) error {
 | |
| 	ctx, cancel := context.WithTimeout(context.Background(), 10*time.Second)
 | |
| 	defer cancel()
 | |
| 	for {
 | |
| 		s := cc.GetState()
 | |
| 		if s == connectivity.Ready {
 | |
| 			return nil
 | |
| 		}
 | |
| 		if !cc.WaitForStateChange(ctx, s) {
 | |
| 			// ctx got timeout or canceled.
 | |
| 			return ctx.Err()
 | |
| 		}
 | |
| 	}
 | |
| }
 | |
| 
 | |
| // Stop stops ss and cleans up all resources it consumed.
 | |
| func (ss *StubServer) Stop() {
 | |
| 	for i := len(ss.cleanups) - 1; i >= 0; i-- {
 | |
| 		ss.cleanups[i]()
 | |
| 	}
 | |
| }
 | |
| 
 | |
| func parseCfg(r *manual.Resolver, s string) *serviceconfig.ParseResult {
 | |
| 	g := r.CC.ParseServiceConfig(s)
 | |
| 	if g.Err != nil {
 | |
| 		panic(fmt.Sprintf("Error parsing config %q: %v", s, g.Err))
 | |
| 	}
 | |
| 	return g
 | |
| }
 |