Skip to content

Add the shuttle_enabler crate - #325

Open
sarsko wants to merge 3 commits into
bump-shuttle-tokio-1from
add-shuttle-enabler
Open

Add the shuttle_enabler crate#325
sarsko wants to merge 3 commits into
bump-shuttle-tokio-1from
add-shuttle-enabler

Conversation

@sarsko

@sarsko sarsko commented Aug 22, 2026

Copy link
Copy Markdown
Contributor

Stacked on #327. Based on bump-shuttle-tokio-1 because the enabler's dependency on shuttle-tokio uses the 1 requirement that #327 introduces. GitHub will retarget this to main automatically once #327 merges.

wrappers/README.md has documented a shuttle_enabler crate since #240, but the crate was never added. The instructions there could not be followed, and the [shuttle_enabler](shuttle_enabler) link was dead. This adds it.

Why

A crate depending on several wrappers has to name every one of them in its own shuttle feature:

[features]
shuttle = [
   "tokio/shuttle",
   "parking_lot/shuttle",
   # ... one line per wrapped dependency
]

That list is easy to get wrong, and getting it wrong is quiet. A missing entry does not fail the build; it leaves that dependency running its real implementation inside a Shuttle test, where the scheduler has no control over it. With shuttle_enabler the list is one entry, and adding a wrapped dependency later needs no change to it.

What

The crate contains no code. It owns a shuttle feature that enables the shuttle feature of the ten wrappers, plus determinizable_collections' deterministic feature. Cargo compiles each crate once with the union of the features requested of it, so a downstream crate's own shuttle-tokio dependency picks up the feature without naming it.

Three choices worth a look during review:

  • Every dependency is optional, activated only by the shuttle feature, so depending on this crate costs nothing when the feature is off. cargo check -p shuttle_enabler compiles no wrapper crates at all.
  • default-features = false everywhere, so the crate contributes only the shuttle feature and does not turn on other features on a downstream crate's behalf.
  • Version requirements are as permissive as semver allows, deliberately. The mechanism depends on Cargo unifying this crate's dependency on a wrapper with the downstream crate's own dependency on it. If those resolve to semver-incompatible versions, Cargo builds two copies and only this crate's copy gets the feature, so the downstream crate keeps using the real implementation and its Shuttle test passes without having tested anything. That failure mode is documented in both READMEs rather than left as a trap.

Verification

The property that matters is that the feature actually reaches a downstream crate's own dependency, which building this crate alone does not prove. Tested with a scratch crate that depends on shuttle_enabler and on tokio = { package = "shuttle-tokio" }, never naming shuttle-tokio/shuttle itself, calling tokio::time::clear_triggers (present only in Shuttle's tokio::time):

Build Result
cargo check fails: cannot find function clear_triggers in module tokio::time, ie. it resolved to real tokio
cargo check --features shuttle compiles, ie. the feature reached it through unification

Also confirmed:

  • cargo check -p shuttle_enabler and cargo check -p shuttle_enabler --features shuttle both clean, the latter building all 11 wrappers and resolving shuttle-tokio v1.0.0.
  • cargo check --workspace, cargo fmt --all -- --check, cargo clippy -p shuttle_enabler --all-targets --features shuttle -- -D clippy::all, and cargo doc --no-deps all clean with RUSTFLAGS=-Dwarnings.
  • cargo tree -f "{p} [{f}]" on an isolated consumer shows 10 of the 11 wrappers receiving exactly [shuttle] (or [deterministic]), confirming the default-features = false neutrality. The one exception is shuttle-rand, which also gets default because wrappers/tokio/impls/tokio-retry depends on it without default-features = false. That predates this PR and I left it alone; happy to fix it here or separately if you'd like.

Also

Adds a short explanation of the unification mechanism to wrappers/README.md, and documents the version-skew failure mode there and in the new crate's README.

Open questions

  • Package name is shuttle_enabler with an underscore, matching what wrappers/README.md already documents, though most wrappers use hyphens. Say the word if you'd rather rename it and update the docs instead.
  • wrappers/README.md also documents a tokio-version-importer-do-not-use-directly crate for pinning, which likewise does not exist in the tree. Not touched here.

By submitting this pull request, I confirm that my contribution is made under the terms of the Apache 2.0 license.

wrappers/README.md has documented a `shuttle_enabler` crate since #240, but
the crate was never added, so the instructions could not be followed and the
`[shuttle_enabler](shuttle_enabler)` link was dead. This adds it.

The problem it solves: a crate depending on several wrappers has to list every
one of them in its own `shuttle` feature. That list is easy to get wrong, and
getting it wrong is quiet -- a missing entry does not fail the build, it leaves
that dependency running its real implementation inside a Shuttle test, where
the scheduler has no control over it. Depending on shuttle_enabler reduces the
list to one entry, and adding a wrapped dependency later needs no change to it.

The crate contains no code. It owns a `shuttle` feature that enables the
`shuttle` feature of all ten wrappers plus `determinizable_collections`'
`deterministic` feature. Cargo compiles each crate once with the union of the
features requested of it, so a downstream crate's own `shuttle-tokio`
dependency picks up the feature without naming it.

Details worth noting for review:

* Every dependency is optional and activated only by the `shuttle` feature, so
  depending on this crate costs nothing when the feature is off. Verified:
  `cargo check -p shuttle_enabler` compiles no wrapper crates at all.
* Dependencies use `default-features = false` so the crate contributes only the
  `shuttle` feature and does not silently turn on other features on a
  downstream crate's behalf.
* Version requirements are as permissive as semver allows on purpose. The whole
  mechanism depends on Cargo unifying this crate's dependency on a wrapper with
  the downstream crate's own dependency on it. If they resolve to
  semver-incompatible versions, Cargo builds two copies and only this crate's
  copy gets the feature, so the downstream crate keeps using the real
  implementation and its Shuttle test passes without testing anything. This
  failure mode is documented in both READMEs rather than left as a trap.

Verified the mechanism end to end with a scratch crate that depends on
shuttle_enabler and on `tokio = { package = "shuttle-tokio" }`, never naming
`shuttle-tokio/shuttle` itself, and which calls `tokio::time::clear_triggers`
(present only in Shuttle's tokio::time):

* without `--features shuttle`: fails with `cannot find function
  clear_triggers in module tokio::time`, ie. it resolved to real tokio
* with `--features shuttle`: compiles, ie. the feature reached it

Also fixes the shuttle-tokio version in the wrappers/README.md example, which
said `1` where the crate is at 0.1.0.

No CHANGELOG entry, matching the convention that release-prep PRs aggregate them.
sarsko added 2 commits August 22, 2026 01:58
Revert the wrappers/README.md example back to `version = "1"`. The example was
right and the crate version was wrong; shuttle-tokio is being republished as
1.0.0 to match the scheme the other wrappers follow, so the docs need no change.

Point the enabler's own dependency and examples at `1` accordingly.
@sarsko
sarsko changed the base branch from main to bump-shuttle-tokio-1 August 22, 2026 08:58
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

1 participant