pub struct BannerTransport { /* private fields */ }Expand description
A GitTransport that writes one line describing the request it was given,
then reads the client out. A test double.
It exists to prove a specific thing that a mock recording a call cannot: that the byte stream reached the seam, in both directions, after a real SSH handshake. The line it writes names the service and the repository, so a test asserting on it is asserting on applied output rather than on a flag someone set (measured, not asserted).
Implementations§
Source§impl BannerTransport
impl BannerTransport
Sourcepub fn settling_after_it_releases_the_stream() -> Self
pub fn settling_after_it_releases_the_stream() -> Self
The same transport, but it drops the stream and then waits before it returns.
This is the red half of the exit-status ordering guard, and it is worth spelling out why it is not a contrivance.
russh’s Channel::into_stream() wraps the channel in
ChannelCloseOnDrop, whose Drop spawns a task that sends
ChannelMsg::Close. A server that hands the transport such a stream and
sends exit-status afterwards is in a race with that task: the ordinary
path wins it on a current-thread runtime (the send takes an available
permit without yielding) and loses it often enough on a multi-threaded
one to make a successful push report failure (measured 2026-08-03,
ssh exit 255 with a complete 244-byte advertisement).
A test on the ordinary path therefore proves nothing: it is green with the bug present. This mode gives the spawned task the scheduling it needs to run, which turns “usually” into “always” in the failing direction only. A server that closes nothing on drop is unaffected by any amount of waiting here, so a green under this transport is not a timing accident; it is the absence of the close.
Sourcepub fn holding() -> (Self, TransportHold)
pub fn holding() -> (Self, TransportHold)
A transport whose transfers stay in flight until the returned
TransportHold is released.
This is what makes the admission gate testable at all. A limit on concurrent transfers cannot be reached by a transport that finishes instantly: a hundred sequential clones never put two in flight together, and a test written against the ordinary banner would be asserting on scheduling luck. Here the first N transfers are genuinely simultaneous and stay that way until the test says otherwise.
The banner is written before the wait, so a client knows its transfer was admitted rather than merely accepted.
Sourcepub fn refusing(message: impl Into<String>) -> Self
pub fn refusing(message: impl Into<String>) -> Self
A transport that refuses without writing a byte, the way a transfer whose repository will not open does.
Distinct from BannerTransport::that_then_fails on the one axis that
matters: that one writes its banner first, so the client has already had
output and the framing is no longer the session’s to add to. This one has
written nothing, which is the only condition under which the session may
still hand the client a reason.
Sourcepub fn that_then_fails(self) -> Self
pub fn that_then_fails(self) -> Self
…and then reports that the transfer failed.
The other direction of the same guard: a non-zero status has to survive the stream being released too. Without this, the exit-status assertions could all be satisfied by a server that hard-coded zero.
Sourcepub fn served(&self) -> Vec<String>
pub fn served(&self) -> Vec<String>
Everything it has served, as "<verb> <ns>/<repo> <principal-id>".
Sourcepub fn git_protocols(&self) -> Vec<Option<String>>
pub fn git_protocols(&self) -> Vec<Option<String>>
The GIT_PROTOCOL value that arrived with each served request, in the
same order as BannerTransport::served.
Recorded separately from the banner so that a test can assert what the wire would negotiate with without changing the byte the existing end-to-end assertions read back over SSH.
The line this transport writes for request.