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