|
- // Copyright 2017 Google LLC
- //
- // Licensed under the Apache License, Version 2.0 (the "License");
- // you may not use this file except in compliance with the License.
- // You may obtain a copy of the License at
- //
- // http://www.apache.org/licenses/LICENSE-2.0
- //
- // Unless required by applicable law or agreed to in writing, software
- // distributed under the License is distributed on an "AS IS" BASIS,
- // WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
- // See the License for the specific language governing permissions and
- // limitations under the License.
-
- /*
- Package rpcreplay supports the capture and replay of gRPC calls. Its main goal is
- to improve testing. Once you capture the calls of a test that runs against a real
- service, 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 its
- DialOptions 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 arbitrary
- io.Writer.
-
-
- Replaying
-
- Replaying a captured file looks almost identical: create a Replayer and use
- its DialOptions. (Since we're reading the file and not writing it, we don't
- have 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()...)
-
-
- Initial State
-
- A test might use random or time-sensitive values, for instance to create unique
- resources for isolation from other tests. The test therefore has initial values, such
- as the current time, or a random seed, that differ from run to run. You must record
- this initial state and re-establish it on replay.
-
- To record the initial state, serialize it into a []byte and pass it as the second
- argument 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 that modify what is saved and matched from the replay file
-
- Recorders and replayers have support for running callbacks before messages are written/read
- from the replay file. A Recorder has a BeforeFunc that can modify a request or response
- before it is written to the replay file. The actual RPCs sent to the service during recording
- remain unaltered; only what is saved in the replay file can be changed.A Replayer has a
- BeforeFunc that can modify a request before it is sent for matching.
-
- Example uses for these callbacks include customized logging, or scrubbing data
- before RPCs are written to the replay file. If requests are modified by the callbacks during recording,
- it is important to perform the same modifications to the requests when replaying, 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 nil
- case Farewell:
- msg.line = "Bye bye!"
- return nil
- default:
- return fmt.Errorf("unknown message type")
- }
- }
-
- Nondeterminism
-
- A nondeterministic program may invoke RPCs in a different order each time
- it is run. The order in which RPCs are called during recording may differ
- from the order during replay.
-
- The replayer matches incoming to recorded requests by method name and request
- contents, so nondeterminism is only a concern for identical requests that result
- in different responses. A nondeterministic program whose behavior differs
- depending on the order of such RPCs probably has a race condition: since both the
- recorded sequence of RPCs and the sequence during replay are valid orderings, the
- program should behave the same under both.
-
-
- Other Replayer Differences
-
- Besides the differences in replay mentioned above, other differences may cause issues
- for some programs. We list them here.
-
- The Replayer delivers a response to an RPC immediately, without waiting for other
- incoming RPCs. This can violate causality. For example, in a Pub/Sub program where
- one goroutine publishes and another subscribes, during replay the Subscribe call may
- finish before the Publish call begins.
-
- For streaming RPCs, the Replayer delivers the result of Send and Recv calls in
- the 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, or
- the result of the CloseSend method.
- */
- package rpcreplay // import "cloud.google.com/go/rpcreplay"
|