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