...

Source file src/cloud.google.com/go/internal/testutil/server.go

Documentation: cloud.google.com/go/internal/testutil

     1  /*
     2  Copyright 2016 Google LLC
     3  
     4  Licensed under the Apache License, Version 2.0 (the "License");
     5  you may not use this file except in compliance with the License.
     6  You may obtain a copy of the License at
     7  
     8      http://www.apache.org/licenses/LICENSE-2.0
     9  
    10  Unless required by applicable law or agreed to in writing, software
    11  distributed under the License is distributed on an "AS IS" BASIS,
    12  WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
    13  See the License for the specific language governing permissions and
    14  limitations under the License.
    15  */
    16  
    17  package testutil
    18  
    19  import (
    20  	"fmt"
    21  	"log"
    22  	"net"
    23  	"regexp"
    24  	"strconv"
    25  
    26  	"google.golang.org/grpc"
    27  	"google.golang.org/grpc/codes"
    28  	"google.golang.org/grpc/status"
    29  )
    30  
    31  // A Server is an in-process gRPC server, listening on a system-chosen port on
    32  // the local loopback interface. Servers are for testing only and are not
    33  // intended to be used in production code.
    34  //
    35  // To create a server, make a new Server, register your handlers, then call
    36  // Start:
    37  //
    38  //	srv, err := NewServer()
    39  //	...
    40  //	mypb.RegisterMyServiceServer(srv.Gsrv, &myHandler)
    41  //	....
    42  //	srv.Start()
    43  //
    44  // Clients should connect to the server with no security:
    45  //
    46  //	conn, err := grpc.Dial(srv.Addr, grpc.WithInsecure())
    47  //	...
    48  type Server struct {
    49  	Addr string
    50  	Port int
    51  	l    net.Listener
    52  	Gsrv *grpc.Server
    53  }
    54  
    55  // NewServer creates a new Server. The Server will be listening for gRPC connections
    56  // at the address named by the Addr field, without TLS.
    57  func NewServer(opts ...grpc.ServerOption) (*Server, error) {
    58  	return NewServerWithPort(0, opts...)
    59  }
    60  
    61  // NewServerWithPort creates a new Server at a specific port. The Server will be listening
    62  // for gRPC connections at the address named by the Addr field, without TLS.
    63  func NewServerWithPort(port int, opts ...grpc.ServerOption) (*Server, error) {
    64  	l, err := net.Listen("tcp", fmt.Sprintf("localhost:%d", port))
    65  	if err != nil {
    66  		return nil, err
    67  	}
    68  	s := &Server{
    69  		Addr: l.Addr().String(),
    70  		Port: parsePort(l.Addr().String()),
    71  		l:    l,
    72  		Gsrv: grpc.NewServer(opts...),
    73  	}
    74  	return s, nil
    75  }
    76  
    77  // Start causes the server to start accepting incoming connections.
    78  // Call Start after registering handlers.
    79  func (s *Server) Start() {
    80  	go func() {
    81  		if err := s.Gsrv.Serve(s.l); err != nil {
    82  			log.Printf("testutil.Server.Start: %v", err)
    83  		}
    84  	}()
    85  }
    86  
    87  // Close shuts down the server.
    88  func (s *Server) Close() {
    89  	s.Gsrv.Stop()
    90  	s.l.Close()
    91  }
    92  
    93  // PageBounds converts an incoming page size and token from an RPC request into
    94  // slice bounds and the outgoing next-page token.
    95  //
    96  // PageBounds assumes that the complete, unpaginated list of items exists as a
    97  // single slice. In addition to the page size and token, PageBounds needs the
    98  // length of that slice.
    99  //
   100  // PageBounds's first two return values should be used to construct a sub-slice of
   101  // the complete, unpaginated slice. E.g. if the complete slice is s, then
   102  // s[from:to] is the desired page. Its third return value should be set as the
   103  // NextPageToken field of the RPC response.
   104  func PageBounds(pageSize int, pageToken string, length int) (from, to int, nextPageToken string, err error) {
   105  	from, to = 0, length
   106  	if pageToken != "" {
   107  		from, err = strconv.Atoi(pageToken)
   108  		if err != nil {
   109  			return 0, 0, "", status.Errorf(codes.InvalidArgument, "bad page token: %v", err)
   110  		}
   111  		if from >= length {
   112  			return length, length, "", nil
   113  		}
   114  	}
   115  	if pageSize > 0 && from+pageSize < length {
   116  		to = from + pageSize
   117  		nextPageToken = strconv.Itoa(to)
   118  	}
   119  	return from, to, nextPageToken, nil
   120  }
   121  
   122  var portParser = regexp.MustCompile(`:[0-9]+`)
   123  
   124  func parsePort(addr string) int {
   125  	res := portParser.FindAllString(addr, -1)
   126  	if len(res) == 0 {
   127  		panic(fmt.Errorf("parsePort: found no numbers in %s", addr))
   128  	}
   129  	stringPort := res[0][1:] // strip the :
   130  	p, err := strconv.ParseInt(stringPort, 10, 32)
   131  	if err != nil {
   132  		panic(err)
   133  	}
   134  	return int(p)
   135  }
   136  

View as plain text