rpcreplay
packageThis package is not in the latest version of its module.
Details
Validgo.mod file
The Go module system was introduced in Go 1.11 and is the official dependency management solution for Go.
Redistributable license
Redistributable licenses place minimal restrictions on how software can be used, modified, and redistributed.
Tagged version
Modules with tagged versions give importers more predictable builds.
Stable version
When a project reaches major version v1 it is considered stable.
- Learn more about best practices
Repository
Links
Documentation¶
Overview¶
Package rpcreplay supports the capture and replay of gRPC calls. Its main goal isto improve testing. Once you capture the calls of a test that runs against a realservice, you have an "automatic mock" that can be replayed against the same test,yielding a unit test that is fast and flake-free.
This package is EXPERIMENTAL and subject to change without notice.
Recording¶
To record a sequence of gRPC calls to a file, create a Recorder and pass itsDialOptions to grpc.Dial:
rec, err := rpcreplay.NewRecorder("service.replay", nil)if err != nil { ... }defer func() { if err := rec.Close(); err != nil { ... }}()conn, err := grpc.Dial(serverAddress, rec.DialOptions()...)It is essential to close the Recorder when the interaction is finished.
There is also a NewRecorderWriter function for capturing to an arbitraryio.Writer.
Replaying¶
Replaying a captured file looks almost identical: create a Replayer and useits DialOptions. (Since we're reading the file and not writing it, we don'thave to be as careful about the error returned from Close).
rep, err := rpcreplay.NewReplayer("service.replay")if err != nil { ... }defer rep.Close()conn, err := grpc.Dial(serverAddress, rep.DialOptions()...)Since a real connection isn't necessary for replay, you can get a fakeone from the replayer instead of calling grpc.Dial:
rep, err := rpcreplay.NewReplayer("service.replay")if err != nil { ... }defer rep.Close()conn, err := rep.Connection()Initial State¶
A test might use random or time-sensitive values, for instance to create uniqueresources for isolation from other tests. The test therefore has initial values, suchas the current time, or a random seed, that differ from run to run. You must recordthis initial state and re-establish it on replay.
To record the initial state, serialize it into a []byte and pass it as the secondargument to NewRecorder:
timeNow := time.Now()b, err := timeNow.MarshalBinary()if err != nil { ... }rec, err := rpcreplay.NewRecorder("service.replay", b)On replay, get the bytes from Replayer.Initial:
rep, err := rpcreplay.NewReplayer("service.replay")if err != nil { ... }defer rep.Close()err = timeNow.UnmarshalBinary(rep.Initial())if err != nil { ... }Callbacks¶
Recorders and replayers have support for running callbacks before messages arewritten to or read from the replay file. A Recorder has a BeforeFunc that can modifya request or response before it is written to the replay file. The actual RPCs sentto the service during recording remain unaltered; only what is saved in the replayfile can be changed. A Replayer has a BeforeFunc that can modify a request before itis sent for matching.
Example uses for these callbacks include customized logging, or scrubbing data beforeRPCs are written to the replay file. If requests are modified by the callbacks duringrecording, it is important to perform the same modifications to the requests whenreplaying, or RPC matching on replay will fail.
A common way to analyze and modify the various messages is to use a type switch.
// Assume these types implement proto.Message.type Greeting struct {line string}type Farewell struct {line string}func sayings(method string, msg proto.Message) error {switch m := msg.(type) {case Greeting:msg.line = "Hi!"return nilcase Farewell:msg.line = "Bye bye!"return nildefault:return fmt.Errorf("unknown message type")}}Nondeterminism¶
A nondeterministic program may invoke RPCs in a different order each timeit is run. The order in which RPCs are called during recording may differfrom the order during replay.
The replayer matches incoming to recorded requests by method name and requestcontents, so nondeterminism is only a concern for identical requests that resultin different responses. A nondeterministic program whose behavior differsdepending on the order of such RPCs probably has a race condition: since both therecorded sequence of RPCs and the sequence during replay are valid orderings, theprogram should behave the same under both.
The same is not true of streaming RPCs. The replayer matches streams only by methodname, since it has no other information at the time the stream is opened. Two streamswith the same method name that are started concurrently may replay in the wrongorder.
Other Replayer Differences¶
Besides the differences in replay mentioned above, other differences may cause issuesfor some programs. We list them here.
The Replayer delivers a response to an RPC immediately, without waiting for otherincoming RPCs. This can violate causality. For example, in a Pub/Sub program whereone goroutine publishes and another subscribes, during replay the Subscribe call mayfinish before the Publish call begins.
For streaming RPCs, the Replayer delivers the result of Send and Recv calls inthe order they were recorded. No attempt is made to match message contents.
At present, this package does not record or replay stream headers and trailers, orthe result of the CloseSend method.
Index¶
Examples¶
Constants¶
This section is empty.
Variables¶
This section is empty.
Functions¶
Types¶
typeRecorder¶
type Recorder struct {// BeforeFunc defines a function that can inspect and modify requests and responses// written to the replay file. It does not modify messages sent to the service.// It is run once before a request is written to the replay file, and once before a response// is written to the replay file.// The function is called with the method name and the message that triggered the callback.// If the function returns an error, the error will be returned to the client.// This is only executed for unary RPCs; streaming RPCs are not supported.BeforeFunc func(string,proto.Message)error// contains filtered or unexported fields}A Recorder records RPCs for later playback.
funcNewRecorder¶
NewRecorder creates a recorder that writes to filename. The file willalso store the initial bytes for retrieval during replay.
You must call Close on the Recorder to ensure that all data is written.
Example¶
package mainimport ("cloud.google.com/go/rpcreplay""google.golang.org/grpc")var serverAddress stringfunc main() {rec, err := rpcreplay.NewRecorder("service.replay", nil)if err != nil {// TODO: Handle error.}defer func() {if err := rec.Close(); err != nil {// TODO: Handle error.}}()conn, err := grpc.Dial(serverAddress, rec.DialOptions()...)if err != nil {// TODO: Handle error.}_ = conn // TODO: use connection}funcNewRecorderWriter¶
NewRecorderWriter creates a recorder that writes to w. The initialbytes will also be written to w for retrieval during replay.
You must call Close on the Recorder to ensure that all data is written.
func (*Recorder)DialOptions¶
func (r *Recorder) DialOptions() []grpc.DialOption
DialOptions returns the options that must be passed to grpc.Dialto enable recording.
typeReplayer¶
type Replayer struct {// BeforeFunc defines a function that can inspect and modify requests before they// are matched for responses from the replay file.// The function is called with the method name and the message that triggered the callback.// If the function returns an error, the error will be returned to the client.// This is only executed for unary RPCs; streaming RPCs are not supported.BeforeFunc func(string,proto.Message)error// contains filtered or unexported fields}A Replayer replays a set of RPCs saved by a Recorder.
funcNewReplayer¶
NewReplayer creates a Replayer that reads from filename.
Example¶
package mainimport ("cloud.google.com/go/rpcreplay""google.golang.org/grpc")var serverAddress stringfunc main() {rep, err := rpcreplay.NewReplayer("service.replay")if err != nil {// TODO: Handle error.}defer rep.Close()conn, err := grpc.Dial(serverAddress, rep.DialOptions()...)if err != nil {// TODO: Handle error.}_ = conn // TODO: use connection}funcNewReplayerReader¶
NewReplayerReader creates a Replayer that reads from r.
func (*Replayer)Connection¶added inv0.35.0
func (rep *Replayer) Connection() (*grpc.ClientConn,error)
Connection returns a fake gRPC connection suitable for replaying.
func (*Replayer)DialOptions¶
func (rep *Replayer) DialOptions() []grpc.DialOption
DialOptions returns the options that must be passed to grpc.Dialto enable replaying.
func (*Replayer)SetLogFunc¶
SetLogFunc sets a function to be used for debug logging. The functionshould be safe to be called from multiple goroutines.