mirror of
https://github.com/Luzifer/nginx-sso.git
synced 2024-12-20 21:01:17 +00:00
153 lines
5.4 KiB
Go
153 lines
5.4 KiB
Go
// 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()...)
|
|
|
|
Since a real connection isn't necessary for replay, you can get a fake
|
|
one 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 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
|
|
|
|
Recorders and replayers have support for running callbacks before messages are
|
|
written to or 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"
|