1/*
2Copyright 2016 Google LLC
3
4Licensed under the Apache License, Version 2.0 (the "License");
5you may not use this file except in compliance with the License.
6You may obtain a copy of the License at
7
8    http://www.apache.org/licenses/LICENSE-2.0
9
10Unless required by applicable law or agreed to in writing, software
11distributed under the License is distributed on an "AS IS" BASIS,
12WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
13See the License for the specific language governing permissions and
14limitations under the License.
15*/
16
17package testutil
18
19import (
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//	...
48type 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.
57func 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.
63func 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.
79func (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.
88func (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.
104func 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
122var portParser = regexp.MustCompile(`:[0-9]+`)
123
124func 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