1// Copyright 2017 Google LLC 2// 3// Licensed under the Apache License, Version 2.0 (the "License"); 4// you may not use this file except in compliance with the License. 5// You may obtain a copy of the License at 6// 7// http://www.apache.org/licenses/LICENSE-2.0 8// 9// Unless required by applicable law or agreed to in writing, software 10// distributed under the License is distributed on an "AS IS" BASIS, 11// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 12// See the License for the specific language governing permissions and 13// limitations under the License. 14 15/* 16Package rpcreplay supports the capture and replay of gRPC calls. Its main goal is 17to improve testing. Once you capture the calls of a test that runs against a real 18service, you have an "automatic mock" that can be replayed against the same test, 19yielding a unit test that is fast and flake-free. 20 21This package is EXPERIMENTAL and subject to change without notice. 22 23 24Recording 25 26To record a sequence of gRPC calls to a file, create a Recorder and pass its 27DialOptions to grpc.Dial: 28 29 rec, err := rpcreplay.NewRecorder("service.replay", nil) 30 if err != nil { ... } 31 defer func() { 32 if err := rec.Close(); err != nil { ... } 33 }() 34 conn, err := grpc.Dial(serverAddress, rec.DialOptions()...) 35 36It is essential to close the Recorder when the interaction is finished. 37 38There is also a NewRecorderWriter function for capturing to an arbitrary 39io.Writer. 40 41 42Replaying 43 44Replaying a captured file looks almost identical: create a Replayer and use 45its DialOptions. (Since we're reading the file and not writing it, we don't 46have to be as careful about the error returned from Close). 47 48 rep, err := rpcreplay.NewReplayer("service.replay") 49 if err != nil { ... } 50 defer rep.Close() 51 conn, err := grpc.Dial(serverAddress, rep.DialOptions()...) 52 53Since a real connection isn't necessary for replay, you can get a fake 54one from the replayer instead of calling grpc.Dial: 55 56 rep, err := rpcreplay.NewReplayer("service.replay") 57 if err != nil { ... } 58 defer rep.Close() 59 conn, err := rep.Connection() 60 61Initial State 62 63A test might use random or time-sensitive values, for instance to create unique 64resources for isolation from other tests. The test therefore has initial values, such 65as the current time, or a random seed, that differ from run to run. You must record 66this initial state and re-establish it on replay. 67 68To record the initial state, serialize it into a []byte and pass it as the second 69argument to NewRecorder: 70 71 timeNow := time.Now() 72 b, err := timeNow.MarshalBinary() 73 if err != nil { ... } 74 rec, err := rpcreplay.NewRecorder("service.replay", b) 75 76On replay, get the bytes from Replayer.Initial: 77 78 rep, err := rpcreplay.NewReplayer("service.replay") 79 if err != nil { ... } 80 defer rep.Close() 81 err = timeNow.UnmarshalBinary(rep.Initial()) 82 if err != nil { ... } 83 84 85Callbacks 86 87Recorders and replayers have support for running callbacks before messages are 88written to or read from the replay file. A Recorder has a BeforeFunc that can modify 89a request or response before it is written to the replay file. The actual RPCs sent 90to the service during recording remain unaltered; only what is saved in the replay 91file can be changed. A Replayer has a BeforeFunc that can modify a request before it 92is sent for matching. 93 94Example uses for these callbacks include customized logging, or scrubbing data before 95RPCs are written to the replay file. If requests are modified by the callbacks during 96recording, it is important to perform the same modifications to the requests when 97replaying, or RPC matching on replay will fail. 98 99A common way to analyze and modify the various messages is to use a type switch. 100 101 // Assume these types implement proto.Message. 102 type Greeting struct { 103 line string 104 } 105 106 type Farewell struct { 107 line string 108 } 109 110 func sayings(method string, msg proto.Message) error { 111 switch m := msg.(type) { 112 case Greeting: 113 msg.line = "Hi!" 114 return nil 115 case Farewell: 116 msg.line = "Bye bye!" 117 return nil 118 default: 119 return fmt.Errorf("unknown message type") 120 } 121 } 122 123Nondeterminism 124 125A nondeterministic program may invoke RPCs in a different order each time 126it is run. The order in which RPCs are called during recording may differ 127from the order during replay. 128 129The replayer matches incoming to recorded requests by method name and request 130contents, so nondeterminism is only a concern for identical requests that result 131in different responses. A nondeterministic program whose behavior differs 132depending on the order of such RPCs probably has a race condition: since both the 133recorded sequence of RPCs and the sequence during replay are valid orderings, the 134program should behave the same under both. 135 136The same is not true of streaming RPCs. The replayer matches streams only by method 137name, since it has no other information at the time the stream is opened. Two streams 138with the same method name that are started concurrently may replay in the wrong 139order. 140 141Other Replayer Differences 142 143Besides the differences in replay mentioned above, other differences may cause issues 144for some programs. We list them here. 145 146The Replayer delivers a response to an RPC immediately, without waiting for other 147incoming RPCs. This can violate causality. For example, in a Pub/Sub program where 148one goroutine publishes and another subscribes, during replay the Subscribe call may 149finish before the Publish call begins. 150 151For streaming RPCs, the Replayer delivers the result of Send and Recv calls in 152the order they were recorded. No attempt is made to match message contents. 153 154At present, this package does not record or replay stream headers and trailers, or 155the result of the CloseSend method. 156*/ 157package rpcreplay // import "cloud.google.com/go/rpcreplay" 158