Steps
The grpc step
A grpc step makes one call: unary or streaming, described by .proto files that vero compiles
when the plan loads. vero never asks a server for its schema, so a misspelled method or a message
field the request type lacks is a load error, before any socket opens. The call runs under the
step's deadline and uses the run's TLS settings, --insecure-skip-verify, --ca-cert and the
client certificate, unless plaintext is set. Plan format has the keys every
step shares; this page has the ones a grpc step adds.
| Key | Type | Meaning |
|---|---|---|
target |
string | host:port, templated |
plaintext |
bool | no TLS; off by default, so a call uses TLS with the run's settings |
method |
string | package.Service/Method, a literal |
protos |
list of strings | the .proto files that describe the method, relative to the plan |
importPaths |
list of strings | where imports are found, relative to the plan; the plan's directory is always searched |
message |
any | the request, written as JSON: strings templated, numbers as written; for a method that takes one message |
send |
list of objects | the requests, in order, each written like message; for a method that takes a stream |
expect |
object | optional, for a method that returns a stream: { count, timeout } as for ws, stop collecting early |
metadata |
map of strings | sent with the call, templated |
A plan
grpc.yaml calls the vero.test.Orders service that the two files in
examples/grpc/ describe: one unary call, then one server stream. orders.proto imports
types.proto, which sits beside it, so the step names grpc in importPaths.
- name: get-order
steps:
- grpc:
target: "{{ env.grpc }}"
plaintext: true
method: vero.test.Orders/GetOrder
protos: [grpc/orders.proto]
importPaths: [grpc]
message: { id: 7 }
assert:
- code == "OK"
- body.id == 7
- body.status == "pending"
- body.items[0].sku == "ABC-1"
- body.total.cents == 1010
- body.qty == 0
- headers["x-served-by"] == "fixture"
No shipped fixture speaks gRPC. The repository test runs this plan against a server it starts in process, and real ws and gRPC servers runs the step against grpcbin. Against the in-process server:
run 01M3ZS1HYJ3RV49T7FA519H2MH grpc.yaml --jobs 4 lifecycle isolated
2 passed 0 failed 0 errored 0 timed out 0 skipped 0 blocked 0 not run (2 tasks, 2 requests, 0.0s)
Each call counts as one request in the summary, and vero plan counts it among the requests the
plan sends.
The method's shape
The shape of the method decides which keys apply, and the wrong one is a load error:
| Method | Goes out | Comes back |
|---|---|---|
| unary | message |
body |
| server streaming | message |
messages |
| client streaming | send |
body |
| bidirectional | send |
messages |
send goes out in order and then the client half-closes. No send waits for a reply, and none
can be templated from one: the plan is data, not a conversation. Sends and reads run at the same
time, so a server that answers each message before it takes the next works. send holds at most
1000 messages, and each is a JSON object.
A server stream is collected until the server ends it, up to 1000 messages. A stream that sends
more ends the step errored with the stream sent more than 1000 messages; set expect: { count, timeout } to stop collecting earlier. With expect, collection stops after count messages or at
timeout from the start of the call, whichever comes first, and the step also asserts
messages count <count>, so fewer messages is a failure that shows what arrived. expect.count
is 1 to 1000, and expect.timeout has to be shorter than the step's deadline (30 s unless the
step sets timeout), or the load error says expect.timeout 2s does not fit in the step's 1s deadline; raise the step's timeout.
When vero stops a stream this way, the call is cancelled and the server has given no status:
code is nil, and there are no trailers. A plan cannot tell from that whether the server would
have failed later; it did not say. A stream the server ended on its own just as the window closed
keeps the server's status.
Messages, in and out
A request message is written as JSON. Keys match the proto's JSON names first and the field names second, through nested messages and lists, and a key the type lacks is refused at load:
grpc.yaml:22: tasks.get-order.steps[0].grpc.message: message.skuu: vero.test.GetOrderRequest has no field skuu
Strings in a message are templated and numbers are sent as written. A templated value is checked
when it renders, so a rendered message the type cannot hold ends the step errored with the message does not fit vero.test.GetOrderRequest: ..., or send[2] does not fit ... for a stream.
A response reaches assertions with the proto's JSON field names and every field present, so
body.qty == 0 holds on a message that never set qty. A message-typed field that was not set is
nil. Enums come as their names, bytes as base64, and every integer kind and float is an exact
number: body.id == 9007199254740993 holds and body.id == 9007199254740992 does not. NaN and
+Inf arrive as those strings, since they are not numbers an assertion can compare.
Names in scope
| Name | Meaning |
|---|---|
code |
the status as the gRPC spec writes it: OK, NOT_FOUND, UNAVAILABLE, DEADLINE_EXCEEDED; nil when vero stopped the stream. A code the spec does not name is CODE_<number> |
message |
the status message |
body |
the one response of a method that returns one message; nil unless the code is OK, and nil for a method that returns a stream |
messages |
a stream's responses in arrival order, each with body and at, the time since the call started |
headers, trailers |
the server's metadata; no trailers when vero stopped the stream |
duration.first |
when the first streamed message arrived; n/a for a unary call or an empty stream |
duration.total |
the whole call |
env, tasks, run |
as in every step |
Unless one of its own assertions reads code, the step also asserts code == "OK", or
code == nil or code == "OK" when it has expect. A non-OK status the plan does not mention
fails the step, the way an http step with no status assertion would not let a 500 through unnoticed.
A plan that expects an error says so: code == "NOT_FOUND" and message == "order 404 not found"
pass against a server that answers that way.
No server answering at all is errored, not a code. Assertions has the
expression syntax, and duration on its own is refused: name duration.first or
duration.total.
What a failure shows
The failure block prints the call, what went out, the headers the server sent, the response or each streamed message with its arrival time, and the trailers:
FAIL get-order › step 1 › assert #3 grpc.yaml:26
GRPC /vero.test.Orders/GetOrder 127.0.0.1:50051 OK 1.7ms
> {"id":7}
< content-type: application/grpc
< x-served-by: fixture
< {"id":7,"items":[{"sku":"ABC-1"}],"qty":0,"status":"pending","total":{"cents":1010,"currency":"EUR"}}
assert body.status == "shipped" failed
body.status = "pending"
grpc.yaml:26
A stream that sent fewer messages than expect.count asked for, by expect.timeout:
GRPC /vero.test.Orders/WatchOrder 127.0.0.1:50051 OK 1.1ms
> {"id":2}
< content-type: application/grpc
< {"id":1,"items":[],"qty":0,"status":"pending","total":null} at 0.9ms
< {"id":2,"items":[],"qty":0,"status":"packed","total":null} at 1.0ms
assert messages count 3 failed
messages = [{"body":{"id":1,"items":[],"qty":0,"status":"pending","total":null},"at":0.950},{"body":{"id":2,"items":[],"qty":0,"status":"packed","total":null},"at":0.961}]
2 of 3 messages arrived within 300ms
grpc.yaml:41
The status column reads stopped by vero, no status when expect ended the stream, and no answer when no server answered:
ERROR get-order › step 1 grpc.yaml
connecting to 127.0.0.1:50051: connection error: desc = "transport: Error while dialing: dial tcp 127.0.0.1:50051: connect: connection refused"
GRPC /vero.test.Orders/GetOrder 127.0.0.1:50051 no answer 2.6ms
> {"id":7}
A call the step's deadline cut off is timed_out with deadline exceeded calling /vero.test.Orders/GetOrder. The clock decides that, not the server: a server can answer
DEADLINE_EXCEEDED first, because grpc-go sends it the deadline, and that answer is vero's
deadline, not a status the server chose.
The event log carries the same call as a grpc event: the target, the method, what was sent, the
code and status message, the response or each received message with its time, headers and
trailers (vero.events/v1).
Load errors
| Plan | Load error |
|---|---|
no target |
a grpc step needs a target, host:port |
no method |
a grpc step needs a method, package.Service/Method |
no protos |
a grpc step needs protos, the .proto files that describe its method, relative to the plan |
a {{ in method, protos or importPaths |
method, protos and importPaths are literals, not templates |
method: Orders |
method is package.Service/Method, got "Orders" |
| a method the service lacks | service vero.test.Orders has no method GetOrders; it has GetOrder, WatchOrder, AddItems, Echo |
| a service no proto declares | no service vero.test.Order in grpc/orders.proto |
| a proto that does not compile | the compiler's message, at .protos |
send on a unary or server-streaming method |
vero.test.Orders/WatchOrder takes one message; write it as message, not send |
message on a client-streaming method |
vero.test.Orders/AddItems takes a stream; write its messages as send: [ ... ], not message |
expect on a method that returns one message |
vero.test.Orders/GetOrder returns one message; expect applies to a method that returns a stream |
| more than 1000 sends | send holds at most 1000 messages, got 1001 |
| a send that is not an object | a message is a JSON object |
This page is docs/content/steps/grpc.md in the repository.