Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
29 changes: 28 additions & 1 deletion rust/devlaunch-core/src/flows/provision.rs
Original file line number Diff line number Diff line change
Expand Up @@ -2345,18 +2345,45 @@ fi

impl Trip {
/// The payload of this trip's `ssh --command`.
///
/// Re-parses the argv [`setup_pass`] builds, `ssh <workspace> --command
/// <payload>`, and the two expects are the guard on that coupling: rename
/// the flag or move the payload and this fails at the read, saying which of
/// the two assumptions broke, rather than handing the test a neighbouring
/// element to assert against. The shape is not this fixture's invention —
/// `--command` is in `SSH_VALUE_FLAGS`
/// (`devlaunch-test-support/src/devpod.rs`), and
/// `test/fixtures/devpod/conformance.json` carries an `ssh ... --command`
/// row measured against real devpod v0.26.1.
fn script(&self) -> &str {
let at = self
.argv
.iter()
.position(|arg| arg == "--command")
.expect("a trip with a --command");
&self.argv[at + 1]
self.argv.get(at + 1).expect("a --command with a payload")
}
}

/// Stands in for the one devpod spawn point, recording what was asked of it.
///
/// A recorder, not a third fake devpod, and that is why nothing here belongs in
/// `test/fixtures/devpod/conformance.json`. A fake devpod decides an outcome
/// *from argv*; `record` never reads argv to decide anything — it returns the
/// `call`-th entry of a list the test handed the constructor, and stores the
/// argv only so the assertions can read it back afterwards. It holds no
/// workspaces and has no `Running`/`Stopped` to move between.
///
/// A corpus row is `given` state → argv → exit + `then` state, so a row written
/// against this has nothing to bind to at either end: there is no state to seed
/// and none to read back, and the exit is whatever the test wrote in the
/// constructor. Driving it over the corpus would assert a fixture against
/// itself, which is the failure the corpus was built to end.
///
/// The fake devpod the rest of the suite meets is
/// `devlaunch_test_support::devpod::DevpodMachine`, reached through
/// `FakeRunner`, and that one *is* corpus-covered — no third fake escaped it.
///
/// `answers` is consumed one per trip, the last repeating — the three-trip flow
/// (probe, transfer, install) needs different answers to different trips, and a
/// single number could only play one of them.
Expand Down
34 changes: 32 additions & 2 deletions rust/devlaunch-runner/src/lib.rs
Original file line number Diff line number Diff line change
Expand Up @@ -381,8 +381,38 @@ pub enum DetachOutcome {
NotStarted(OsFailure),
}

/// The one seam. Implemented once for real processes ([`ProcessRunner`]) and
/// once for tests (`devlaunch_test_support::FakeRunner`).
/// The one seam: every process devlaunch starts is started through here.
///
/// One implementation does real work, [`ProcessRunner`], and it is the only impl
/// outside test code — which is what makes this a seam rather than a habit, and
/// is asserted in `tests/one_seam.rs` rather than left to this sentence. One
/// implementation stands in for it across the suite:
/// `devlaunch_test_support::FakeRunner`, a call recorder, an argv-prefix response
/// table, and the fake devpod behind them (`DevpodMachine`, the one the
/// conformance corpus pins).
///
/// Tests wrap those two rather than implementing the trait afresh, and the
/// wrappers come and go, so the enumeration is the scan in `tests/one_seam.rs`,
/// which lists every impl in the workspace with its file and its verdict, rather
/// than a number written here. (`grep -rn "impl Runner for"` is the eyeball
/// version and over-reports: this sentence matches it, and so do that test's own
/// fixtures.) Two shapes are worth recognising before reading one:
///
/// - **Part real.** A wrapper holding a [`ProcessRunner`] beside a fake answers
/// for the programs a unit test must not really run — devpod above all, and
/// docker, whose real daemon would be the developer's own — and hands it
/// whatever they do not fake, git above all. That is how a test drives a real
/// repository against a workspace that never existed. Three do this today, and
/// they are worth naming because a reader otherwise finds them by grepping:
/// `flows::listing`'s `FakeDevpodRealGit`, which is named after the pattern,
/// `flows::lifecycle`'s `Devpod`, and `flows::workspace_clone`'s `StubbedLfs`.
/// The last is the one to read carefully rather than by analogy: it routes on
/// the `git lfs` subcommand rather than on the program, and only its `capture`
/// reaches `ProcessRunner` at all.
/// - **A recorder.** A wrapper that answers by call index rather than from argv
/// is not a fake devpod at all: it plays back a list the test handed it and
/// keeps the argv for the assertions to read. `flows::provision`'s `Trips` is
/// the example, and its doc says why a recorder cannot join the corpus.
pub trait Runner {
/// Run to completion, reading both streams as text.
fn capture(&self, spec: &SpawnSpec) -> Outcome<CapturedText>;
Expand Down
Loading
Loading