Repository navigation
Surface replayed errors faithfully and key cassettes by a text method - #11
Conversation
Two defects, pinned before fixing. A failed server-streaming or bidi RPC raises `grpc.RpcError` on a plain gRPC channel. Through the sync interceptors it returns an empty stream instead, while recording the correct status code, so a call that should fail a test passes it. The async interceptors raise correctly, so the two paths disagree with each other as well as with gRPC. Replayed unary errors raise a bare `grpc.RpcError` carrying no status, which the documented example already assumes it can read. Separately, `client_call_details.method` arrives as `str` on the sync path and `bytes` on the async one. The recorded key differs between them, so cassettes do not replay across channel kinds, and an async-recorded cassette stores the method as a `!!binary` blob instead of readable text.
`_FakeStreamingCall.__next__` never consulted the recorded status, so a failed stream ended as if it had succeeded. It now delivers the messages that arrived before the failure and then raises, matching what gRPC does and what the async path already did. The error raised is a `ReplayedRpcError`, which is both a `grpc.RpcError` and a `grpc.Call` the way gRPC's own is, so `code()` and `details()` are readable off the exception. The unary fake raises the same object in place of the bare `grpc.RpcError` it used to raise, which carried no status at all despite the documented example reading one from it. `normalize_method` decodes the method the async path receives as bytes, so both channel kinds record the same key. Cassettes now replay across them, and an async-recorded file holds a readable method instead of a `!!binary` blob. The two tests that asserted a failed stream ends quietly are updated to the corrected contract, and the error-handling guide gains the streaming case.
|
Codecov Report✅ All modified and coverable lines are covered by tests. 📢 Thoughts on this report? Let us know! |
It surfaces while recording a failure as well as while replaying one, so `_RecordedRpcError` describes it where `ReplayedRpcError` did not. The underscore matches the other fakes in this module and marks it as not a public type: the sync path raises this and the async path raises `aio.AioRpcError`, so callers catch `grpc.RpcError`, which covers both. The docstring records why the stand-in exists and where the asymmetry comes from.
|
Follow-up on the two review points. The tests that pinned the bug are updated to the corrected contract in On exporting the error type, I went to make it public and found it should not be. The two paths raise different types: That is correct rather than a defect: gRPC ships So Happy to export it instead if you would rather have it public; it is a one-line change plus a docs entry. |
Both bugs surfaced by #10, fixed test-first. Commit 1 adds the tests and is red; commit 2 makes them green.
1. Failed streams were reported as successful ones
A failing server-streaming or bidi RPC raises
grpc.RpcErroron a plain gRPC channel. Through the sync interceptors it returned an empty stream while recording the correct status code, so a call that should fail a test passed it. The async interceptors raised correctly, so the two paths disagreed with each other as well as with gRPC._FakeStreamingCall.__next__never consultedself._code. It now delivers the messages that arrived before the failure, then raises, which is what gRPC does.Replayed unary errors had a related problem: they raised a bare
grpc.RpcErrorcarrying no status, even thoughdocs/examples/advanced.mdalready showedassert e.code() == grpc.StatusCode.NOT_FOUNDon a replayed error. The docs were right and the code was wrong. Both fakes now raiseReplayedRpcError, which is agrpc.RpcErrorand agrpc.Callthe way gRPC's own error object is, so the status reads off the exception.2. Cassettes were not portable between channel kinds
client_call_details.methodarrives asstron the sync path andbyteson the async one, so the recorded key differed andMethodMatchernever matched across them. Async-recorded cassettes also stored the method as a binary blob, which defeats reading the file:normalize_methoddecodes it at the four aio entry points. Cassettes now round-trip in both directions and the method is written as text.Red, then green
Commit 1 (
7b499d5), tests only:The four that pass are the controls: the async path already raised, the sync path already wrote a text method, and successful streams still complete.
Commit 2 (
262598b), with the fix:Notes
ReplayedRpcErrorimplements exactly whatgrpc.Calldeclares abstract, plus__str__. It is not exported: callers catchgrpc.RpcError.make allgreen,mkdocs build --strictclean.