xdstest

package module
v0.0.0-...-aaf25c8 Latest Latest
Warning

This package is not in the latest version of its module.

Go to latest
Published: Oct 31, 2026 License: Apache-2.0 Imports: 31 Imported by: 0

README

Envoy xDS testing for Go

Test an xDS control plane against a real Envoy, run in Docker with testcontainers-go. It works for any Go control plane, whichever xDS library it uses: write your own scenarios ("service X loses an instance"), and check that Envoy ends up where it should, safely.

To check that an xDS library serves any given config correctly, use the conformance suite in envoy-xds-spec instead. It runs against a small conformance server for any language, so every port of the library runs the same one.

These helpers are a Go port of the conformance harness in envoy-xds-spec. examples/ has them working against upstream go-control-plane.

Setup

go get github.com/JonathanO/envoy-xds-testing-go

You also need Docker, or another container runtime. The protos are go-control-plane's (github.com/envoyproxy/go-control-plane/envoy).

What's in it:

  • Recorder: a gRPC stream interceptor that records each xDS stream to and from your server, as a Conversation.
  • Sotw(c) / Delta(c): checks on a recorded conversation.
  • StartEnvoy: starts an Envoy container that keeps its log. It can fetch Stats, per-host Clusters, fetch a ConfigDump, and send a request through a listener with HTTPStatus.
  • Envoy.Healthy / Envoy.WarningsSince: checks on Envoy itself. It is connected, rejected nothing, has nothing warming, timed out no initial fetches, and logged no warnings or errors.
  • Settled, SettledIdle, SettledAfter: conditions to wait on until your control plane and Envoy have stopped talking.
  • WaitFor(timeout, check): polls a check until it passes.
  • Checkpoint: marks a conversation, Envoy's log and its stats, and checks everything since in one call.
  • Envoy.RoutedVia: a request answers 200 and went to the cluster you expect.
  • Package match: composable matchers for resources, messages, the order things happened in, and what Envoy holds.
Container runtimes

Anything testcontainers-go supports should do:

  • Docker Engine on Linux, or Docker Desktop on macOS or Windows: works as is.
  • Rancher Desktop: set its engine to dockerd (moby), not containerd, and on macOS the other settings on that page.
  • Podman: set DOCKER_HOST to its Docker-compatible socket, e.g. unix://$XDG_RUNTIME_DIR/podman/podman.sock, and start it with systemctl --user start podman.socket. Where Ryuk can't start, as on macOS, run it privileged (TESTCONTAINERS_RYUK_CONTAINER_PRIVILEGED=true, or ryuk.container.privileged=true in ~/.testcontainers.properties, with a rootful Podman machine), or disable it (TESTCONTAINERS_RYUK_DISABLED=true).

Envoy reaches your control plane at host.testcontainers.internal, through testcontainers-go's SSH tunnel to the host, not Docker's own route to the host, which differs by platform. So it needs nothing more of the runtime than the rest of testcontainers-go does, whether the runtime runs in a VM or not.

Every check returns an error rather than taking a *testing.T, so it works with WaitFor and with whatever assertions you use.

Testing your control plane

Envoy's config

Write a bootstrap config for the Envoy under test. The example's envoy-sotw.yaml and envoy-delta.yaml make a good start. It must:

  • put the admin interface on 0.0.0.0:9901;
  • point ADS at host.testcontainers.internal (xdstest.HostInternal), on port ${ADS_PORT}, which StartEnvoy fills in with ADSPort;
  • bind any listener you send traffic through to 0.0.0.0, or it can't be reached from the test;
  • use the node id and cluster that your control plane expects.

To give Envoy an upstream to send traffic to, use its own admin port. Every 127.0.0.x:9901 address inside the container reaches it, and GET / answers 200.

A test

examples/controlplane is this test, runnable, with go-control-plane standing in for your control plane, for SotW and delta.

func TestAddsAService(t *testing.T) {
	ctx := context.Background()
	controlPlane := newMyControlPlane()

	// Your server first, then Envoy. The other way round, Envoy backs off and can time out its first fetches.
	rec := &xdstest.Recorder{}
	server := grpc.NewServer(grpc.StreamInterceptor(rec.Intercept))
	controlPlane.Register(server)
	// On a free port, so tests can run in parallel.
	lis, err := net.Listen("tcp", ":0")
	must(t, err)
	go server.Serve(lis)
	t.Cleanup(server.Stop)

	bootstrap, err := os.ReadFile("testdata/envoy.yaml")
	must(t, err)
	envoy, err := xdstest.StartEnvoy(ctx, xdstest.EnvoyConfig{
		Image: "envoyproxy/envoy:v1.40-latest", Bootstrap: bootstrap, ADSPort: lis.Addr().(*net.TCPAddr).Port,
		ListenerPorts: []int{10000},
	})
	must(t, err)
	t.Cleanup(func() { envoy.Terminate(context.Background()) })

	// Wait for the initial sync. Everything Envoy asked for must be answered well inside its 15s initial fetch timeout.
	must(t, xdstest.WaitFor(30*time.Second, func() error {
		if len(rec.Conversations()) == 0 {
			return errors.New("envoy hasn't connected")
		}
		return nil
	}))
	c := rec.Conversations()[0]
	must(t, xdstest.WaitFor(10*time.Second, func() error { return xdstest.Sotw(c).HadConversationsWith(xdstest.CDS) }))
	must(t, xdstest.WaitFor(10*time.Second, xdstest.Sotw(c).AnsweredEveryType))
	must(t, xdstest.WaitFor(30*time.Second, xdstest.Settled(c, xdstest.DefaultQuietPeriod)))
	checkpoint, err := xdstest.TakeCheckpoint(ctx, envoy, c)
	must(t, err)

	// Make a change the way your users would, and wait for it to settle.
	controlPlane.AddService("foo", "127.0.0.1:9901")
	must(t, xdstest.WaitFor(30*time.Second, xdstest.SettledAfter(c, checkpoint.Message, xdstest.Settled(c, xdstest.DefaultQuietPeriod))))

	must(t, checkpoint.Sane(ctx)) // the conversation, and Envoy's health and log, since the checkpoint
	must(t, envoy.RoutedVia(ctx, 10000, "foo", "foo"))
}

func must(t *testing.T, err error) {
	t.Helper()
	if err != nil {
		t.Fatal(err)
	}
}

For delta xDS, use xdstest.Delta(c) and set api_type: DELTA_GRPC in Envoy's config. Checkpoint and xdstest.ChecksFor(c) work out which protocol a conversation uses.

Waiting for things to settle

If your control plane can tell when a stream is idle, wait with SettledIdle(c, idle). Otherwise Settled(c, quietPeriod) judges from the recorded conversation alone: every response answered, and nothing sent either way for the quiet period. After a change, wrap either one in SettledAfter(c, mark, ...). When you start waiting, the control plane may not have begun sending the change yet, and look idle.

What the checks mean
  • TakeCheckpoint(ctx, envoy, c): marks the conversation, Envoy's log and its stats, so later checks only look at what happened since. Take one once Envoy has synced, and again after anything you did on purpose that Envoy warns about, like a disconnect or a NACK.

    • Sane(ctx): all of the conversation checks below from the checkpoint on, then EnvoyHealthy.
    • SaneAfterReconnect(ctx, before): the same for a conversation Envoy started by reconnecting. It allows Envoy before 1.19 resending wildcard names. Given before, the conversation before it, it checks make before break from where Envoy left off; with nil, it skips that.
    • EnvoyHealthy(ctx): Envoy.Healthy and Envoy.WarningsSince, both since the checkpoint.
  • NothingSentUnasked: every response is for a type, and resource names, that Envoy subscribed to.

  • AllWereAcked: Envoy ACKed every response, and NACKed none.

  • AnsweredEveryType: every type Envoy asked for got a response. Without one, Envoy waits out its initial fetch timeout, or forever if that is disabled. A request for a name that doesn't exist needs a response too.

  • SentNoEmptyResponses(mark): no empty response after the mark. An empty first response for a wildcard type is fine, and is why this takes a mark.

  • WasMakeBeforeBreak(mark): no route was sent before Envoy ACKed the clusters and endpoints it uses, and no cluster was removed while an ACKed route still used it. Start after the initial sync: Envoy orders its own startup.

  • UnsubscribedFrom(type, name): Envoy dropped a subscription, e.g. EDS for a cluster you removed. Wait for this before checking a removal has settled. Envoy unsubscribes after it ACKs the cluster removal.

  • Envoy.Healthy(ctx, baseline): Envoy is connected, has nothing warming, and rejected nothing and timed out no initial fetches since the baseline. Pass nil, or after a deliberate NACK, a Stats taken after it.

  • Envoy.WarningsSince(mark): no warnings or errors in Envoy's log since the mark. Take the mark after startup, and again after anything you did on purpose that Envoy warns about, like a disconnect.

  • Envoy.RoutedVia(ctx, listenerPort, virtualHost, cluster): a request to the virtual host answers 200 and went to that cluster, going by its upstream_rq_total. A 200 alone doesn't say which cluster answered.

  • WasMakeBeforeBreakAfter(before, from): make before break on a conversation Envoy started by reconnecting, starting from what it last ACKed on before. Envoy keeps its config across a disconnect.

Matchers

Package match has matchers for your own expectations. Message matchers work the same for SotW and delta.

// Envoy ACKed routes that no longer use bar before bar's cluster was removed.
err := match.InOrder(c,
	match.AckOf(match.Response(xdstest.RDS, match.Containing(match.RouteConfig(match.Not(match.RoutingTo("bar")))))),
	match.Response(xdstest.CDS, match.Removing("bar")))

// Wait for Envoy to reject something.
err = xdstest.WaitFor(10*time.Second, func() error {
	return match.HasMessage(c, match.Nack(xdstest.CDS, match.WithMessage("connect_timeout")))
})

// What Envoy holds.
dump, err := envoy.ConfigDump(ctx, "")
names, err := match.ActiveClusterNames(dump)
err = match.HasRouteConfig(dump, match.Routing("foo", "foo-v2"))
  • Resources: Cluster(...), RouteConfig(...), EndpointAssignment(...), with Named, ClusterType, RoutingTo, Routing(virtualHost, cluster) and WithEndpoint(address, port).
  • Messages: Response(typeURL, ...), Request(typeURL, ...) and Nack(typeURL, ...) ("" for any type), with Containing, Removing, SubscribingTo, UnsubscribingFrom (delta only) and WithMessage. For SotW, Removing matches a wildcard (LDS, CDS, SRDS, VHDS) response without the resource.
  • Conversations: HasMessage, and InOrder with AckOf, Envoy's ACK of a matching response.
  • Envoy: ActiveClusters, ActiveClusterNames, HasActiveCluster and HasRouteConfig on a config dump.
  • Not, AllOf and AnyOf combine matchers, and New makes your own.

For anything else, look at what Envoy has: envoy.ConfigDump(ctx, "?resource=dynamic_active_clusters"), or envoy.Stats(ctx). Decoding a config dump needs every type in it linked in, so blank import the go-control-plane packages for any extensions your config uses beyond the HTTP connection manager and router.

Reconnects

To test a reconnect, stop your gRPC server and start a new one on the same port, with the same control plane and Recorder. Envoy then opens a second conversation. Check it with SaneAfterReconnect(ctx, before), passing the conversation before it: Envoy keeps its config across the disconnect, so make before break carries on from there.

Things to know
  • Each Envoy start costs a few seconds, and the first, which pulls the image, much more. Add checks to the scenarios you have before adding new scenarios.
  • Envoy connects to whichever ADSPort you give it, so tests on free ports can run in parallel.
  • Take a Checkpoint after Envoy has synced. Then checks only look at what happened since.

Examples

Each example in examples is its own module, with everything it needs, so you can copy one directory and start from it; just drop the replace line from its go.mod. They need Docker. Run one from its directory:

cd examples/controlplane && go test ./...

Their tests run in parallel, each with its own Envoy; go test -parallel 4 ./... runs at most four at once.

  • controlplane: testing your own control plane, as in "A test" above.

Documentation

Overview

Package xdstest tests an xDS control plane against a real Envoy, run in Docker with testcontainers-go.

Record the xDS conversation with a Recorder, check it with Sotw or Delta, wait for it to go quiet with Settled, and check Envoy itself with Envoy.Healthy and Envoy.WarningsSince.

Every check returns an error rather than taking a *testing.T, so use them with WaitFor or with your own assertions.

Index

Constants

View Source
const (
	LDS  = "type.googleapis.com/envoy.config.listener.v3.Listener"
	RDS  = "type.googleapis.com/envoy.config.route.v3.RouteConfiguration"
	SRDS = "type.googleapis.com/envoy.config.route.v3.ScopedRouteConfiguration"
	VHDS = "type.googleapis.com/envoy.config.route.v3.VirtualHost"
	CDS  = "type.googleapis.com/envoy.config.cluster.v3.Cluster"
	EDS  = "type.googleapis.com/envoy.config.endpoint.v3.ClusterLoadAssignment"
	SDS  = "type.googleapis.com/envoy.extensions.transport_sockets.tls.v3.Secret"
	RTDS = "type.googleapis.com/envoy.service.runtime.v3.Runtime"
)

xDS type URLs.

View Source
const ADSPortPlaceholder = "${ADS_PORT}"

ADSPortPlaceholder is replaced in Envoy's bootstrap with EnvoyConfig.ADSPort, e.g. "port_value: ${ADS_PORT}".

View Source
const AdminPort = 9901

AdminPort is where Envoy's config must put its admin interface, on 0.0.0.0.

View Source
const DefaultQuietPeriod = 500 * time.Millisecond

DefaultQuietPeriod is how long a conversation must be silent to count as settled, if the control plane can't say.

View Source
const HostInternal = testcontainers.HostInternal

HostInternal is the name Envoy's config uses to reach your control plane on the host, e.g. in the ADS cluster.

Variables

This section is empty.

Functions

func IsWildcard

func IsWildcard(typeURL string) bool

IsWildcard reports whether Envoy subscribes to a type as a wildcard (all resources) by default: LDS, CDS, SRDS and VHDS.

func NameOf

func NameOf(resource proto.Message) string

NameOf is the name Envoy knows a resource by: its name field, or cluster_name for a ClusterLoadAssignment. It's "" if the resource has neither.

func Settled

func Settled(c *Conversation, quietPeriod time.Duration) func() error

Settled returns a condition, for WaitFor, that every response was answered and nothing has been sent either way for quietPeriod.

func SettledAfter

func SettledAfter(c *Conversation, fromMessage int, settled func() error) func() error

SettledAfter returns a condition that something was sent after fromMessage, then settled. Use it to wait for a change: when you start waiting, the control plane may not have begun sending it yet, and look idle.

func SettledIdle

func SettledIdle(c *Conversation, idle func() bool) func() error

SettledIdle returns a condition that the control plane says it is idle and every response was answered. Use it if your control plane can tell whether it has anything in flight or waiting to be sent on the stream: it knows whether more is coming, and there's no quiet period to wait out.

func WaitFor

func WaitFor(timeout time.Duration, check func() error) error

WaitFor polls check until it returns nil, or returns its last error once timeout has passed.

Types

type Checkpoint

type Checkpoint struct {

	// Message is the conversation's size when taken, e.g. for SettledAfter.
	Message int
	// LogLine is Envoy's log length when taken.
	LogLine int
	// Stats are Envoy's stats when taken.
	Stats map[string]uint64
	// contains filtered or unexported fields
}

Checkpoint is where a conversation and Envoy were at one moment: the conversation's size, Envoy's log length and Envoy's stats. Take one once Envoy has synced, make changes, then check that everything since was sane:

cp, err := xdstest.TakeCheckpoint(ctx, envoy, c)
controlPlane.AddService("foo")
err = xdstest.WaitFor(30*time.Second, xdstest.SettledAfter(c, cp.Message, xdstest.Settled(c, xdstest.DefaultQuietPeriod)))
err = cp.Sane(ctx)

func TakeCheckpoint

func TakeCheckpoint(ctx context.Context, envoy *Envoy, c *Conversation) (*Checkpoint, error)

TakeCheckpoint marks where things are now.

func (*Checkpoint) EnvoyHealthy

func (cp *Checkpoint) EnvoyHealthy(ctx context.Context) error

EnvoyHealthy checks that Envoy is connected, has nothing warming, and since the checkpoint rejected nothing, timed out no initial fetches and logged no warnings or errors.

func (*Checkpoint) Sane

func (cp *Checkpoint) Sane(ctx context.Context) error

Sane checks everything that should hold once things have settled after changes since the checkpoint. Envoy ACKed every response and was sent nothing it didn't ask for. Since the checkpoint: no empty responses, make before break, and EnvoyHealthy.

func (*Checkpoint) SaneAfterReconnect

func (cp *Checkpoint) SaneAfterReconnect(ctx context.Context, before *Conversation) error

SaneAfterReconnect is Sane for a conversation Envoy started by reconnecting. It allows Envoy before 1.19 resending resource names for wildcard types. Given before, the previous conversation with this Envoy, it also checks make before break from the start of this one, starting from what Envoy last ACKed there: Envoy keeps its config across a disconnect. With before nil, make before break isn't checked.

type Checks

type Checks interface {
	// AllWereAcked checks that Envoy ACKed every response, and NACKed none.
	AllWereAcked() error

	// NothingSentUnasked checks that every response is for a type, and resource names, that Envoy subscribed to.
	// Before 1.19, Envoy resent resource names for wildcard types when it reconnected; pass true to allow that.
	NothingSentUnasked(allowPre119WildcardReconnect bool) error

	// UnsubscribedFrom checks that Envoy dropped its subscription to a resource, e.g. EDS for a removed cluster.
	UnsubscribedFrom(typeURL, name string) error

	// SentNoEmptyResponses checks that no response from fromMessage on was empty. Earlier ones may be: an empty first
	// response for a wildcard type tells Envoy there is nothing yet.
	SentNoEmptyResponses(fromMessage int) error

	// WasMakeBeforeBreak checks that, from fromMessage on, no route was sent before Envoy ACKed the clusters and
	// endpoints it uses, and no cluster was removed while an ACKed route still used it. Start after the initial sync:
	// answers to Envoy's first subscriptions aren't ordered, Envoy's own startup sequencing covers those.
	WasMakeBeforeBreak(fromMessage int) error

	// WasMakeBeforeBreakAfter is WasMakeBeforeBreak for a conversation Envoy started by reconnecting. Envoy keeps its
	// config across a disconnect, so this starts from what it last ACKed in before, the previous conversation, and
	// checks this one from fromMessage on, usually 0.
	WasMakeBeforeBreakAfter(before *Conversation, fromMessage int) error

	// AskedForEveryRouteConfigInUse checks that Envoy subscribed to the route config of every listener it ACKed. Until
	// it has, it hasn't synced, though it may look done: a rate limited Envoy can ACK a listener well before it asks
	// for the listener's routes.
	AskedForEveryRouteConfigInUse() error

	// AnsweredEveryType checks that every type Envoy asked for got at least one response. Without one Envoy waits out
	// its initial fetch timeout, or forever if that is disabled.
	AnsweredEveryType() error

	// HadConversationsWith checks that Envoy asked for each of these types.
	HadConversationsWith(typeURLs ...string) error
}

Checks are checks on one recorded conversation. Each looks at the conversation as it is when called, and returns nil or what is wrong. Get them with Sotw or Delta, whichever protocol Envoy uses.

func ChecksFor

func ChecksFor(c *Conversation) Checks

ChecksFor returns Sotw or Delta checks, whichever protocol the conversation uses.

func Delta

func Delta(c *Conversation) Checks

Delta returns checks for an incremental (delta) conversation.

func Sotw

func Sotw(c *Conversation) Checks

Sotw returns checks for a state-of-the-world conversation.

type Conversation

type Conversation struct {
	// contains filtered or unexported fields
}

Conversation is one recorded stream: requests from Envoy and responses to it, in order.

func NewConversation

func NewConversation() *Conversation

NewConversation returns an empty conversation, e.g. to test your own checks against.

func (*Conversation) Add

func (c *Conversation) Add(m proto.Message)

Add records a message.

func (*Conversation) Finished

func (c *Conversation) Finished() bool

Finished reports whether the stream has ended.

func (*Conversation) Messages

func (c *Conversation) Messages() []proto.Message

Messages returns a copy of the messages so far.

func (*Conversation) QuietFor

func (c *Conversation) QuietFor(d time.Duration) bool

QuietFor reports whether nothing has been sent either way for at least d.

func (*Conversation) Size

func (c *Conversation) Size() int

Size is the number of messages so far. Take it as a mark before a change, so later checks only look at what came after.

type Envoy

type Envoy struct {
	// contains filtered or unexported fields
}

Envoy is a running Envoy container. It keeps Envoy's log.

func StartEnvoy

func StartEnvoy(ctx context.Context, cfg EnvoyConfig) (*Envoy, error)

StartEnvoy starts Envoy and waits for its admin interface. Old Envoy builds occasionally crash at startup, so it tries up to three times.

func (*Envoy) Accept

func (e *Envoy) Accept(l testcontainers.Log)

Accept implements testcontainers.LogConsumer.

func (*Envoy) Clusters

func (e *Envoy) Clusters(ctx context.Context) (map[string]uint64, error)

Clusters returns what Envoy has per host, from its /clusters admin page: the numeric values by everything before the last "::", e.g. "foo::127.0.0.2:9901::rq_total".

func (*Envoy) ConfigDump

func (e *Envoy) ConfigDump(ctx context.Context, query string) (*adminv3.ConfigDump, error)

ConfigDump returns Envoy's config dump. query is added to the admin URL, e.g. "?resource=dynamic_active_clusters", or "" for everything. Decoding needs every type in the dump to be linked in: blank import the go-control-plane packages for any extensions your config uses.

func (*Envoy) Container

func (e *Envoy) Container() testcontainers.Container

Container is the underlying container, for anything this doesn't cover.

func (*Envoy) HTTPStatus

func (e *Envoy) HTTPStatus(ctx context.Context, listenerPort int, virtualHost string) (int, error)

HTTPStatus sends GET / with the given Host header through one of Envoy's listeners and returns the status code.

func (*Envoy) Healthy

func (e *Envoy) Healthy(ctx context.Context, baseline map[string]uint64) error

Healthy checks that Envoy is connected to its control plane, has nothing warming, and rejected no updates and timed out no initial fetches (Envoy 1.24 on; older ones have no stat for it) since baseline, an earlier Envoy.Stats or nil for ever. Stats can trail an ACK a little, so it retries for up to 10 seconds.

func (*Envoy) LogSince

func (e *Envoy) LogSince(fromLine int) []string

LogSince returns a copy of the log lines from fromLine on.

func (*Envoy) LogSize

func (e *Envoy) LogSize() int

LogSize is the number of log lines so far. Take it as a mark once Envoy has synced, to skip startup noise.

func (*Envoy) RoutedVia

func (e *Envoy) RoutedVia(ctx context.Context, listenerPort int, virtualHost, cluster string) error

RoutedVia checks that GET / with this Host header through one of Envoy's listeners answers 200 and went to this cluster, going by its upstream_rq_total. A 200 alone doesn't say which cluster answered. It retries for up to 5 seconds, while Envoy's workers catch up with an ACKed change.

func (*Envoy) Stats

func (e *Envoy) Stats(ctx context.Context) (map[string]uint64, error)

Stats returns Envoy's counters and gauges by name.

func (*Envoy) Terminate

func (e *Envoy) Terminate(ctx context.Context) error

Terminate stops and removes the container.

func (*Envoy) URL

func (e *Envoy) URL(ctx context.Context, port int) (string, error)

URL is the base URL of a port in the container, as reached from the test.

func (*Envoy) WarningsSince

func (e *Envoy) WarningsSince(fromLine int) error

WarningsSince checks that Envoy logged no warnings or errors from fromLine on, e.g. a Envoy.LogSize taken once Envoy has synced.

type EnvoyConfig

type EnvoyConfig struct {
	// Image is the Envoy image, e.g. "envoyproxy/envoy:v1.40-latest".
	Image string
	// Bootstrap is Envoy's YAML config. It must put the admin interface on 0.0.0.0:9901 and point ADS at
	// HostInternal on ADSPortPlaceholder, which is replaced with ADSPort.
	Bootstrap []byte
	// ADSPort is the port your control plane listens on, on the host. Start it before Envoy, or Envoy backs off and
	// can time out its initial fetches. Listen on ":0" and pass the port it got, so tests can run in parallel.
	ADSPort int
	// ListenerPorts are listener ports to reach from the test, e.g. with HTTPStatus. Bind those listeners to 0.0.0.0.
	ListenerPorts []int
	// Args are extra Envoy arguments, e.g. "-l", "debug".
	Args []string
}

EnvoyConfig says how to run Envoy.

type Recorder

type Recorder struct {
	// contains filtered or unexported fields
}

Recorder is a gRPC stream interceptor that records every stream to and from your server:

rec := &xdstest.Recorder{}
server := grpc.NewServer(grpc.StreamInterceptor(rec.Intercept))

Use one Recorder for the whole test, so a reconnect shows up as a second conversation.

func (*Recorder) Conversations

func (r *Recorder) Conversations() []*Conversation

Conversations returns the streams recorded so far, oldest first.

func (*Recorder) Intercept

func (r *Recorder) Intercept(srv any, ss grpc.ServerStream, _ *grpc.StreamServerInfo, handler grpc.StreamHandler) error

Intercept is a grpc.StreamServerInterceptor.

Directories

Path Synopsis
Package match has composable matchers for xDS resources, recorded messages, the order things happened in, and what Envoy holds.
Package match has composable matchers for xDS resources, recorded messages, the order things happened in, and what Envoy holds.

Jump to

Keyboard shortcuts

? : This menu
/ : Search site
f or F : Jump to
y or Y : Canonical URL