Extract a scheduler crate from GPUI to enable unified integration testing of client and server code (#37326)

Extracts and cleans up GPUI's scheduler code into a new `scheduler`
crate, making it pluggable by external runtimes. This will enable
deterministic integration testing with cloud components by providing a
unified test scheduler across Zed and backend code. In Zed, it will
replace the existing GPUI scheduler for consistent async task management
across platforms.

## Changes

- **Core Implementation**: `TestScheduler` with seed-based
randomization, session tracking (`SessionId`), and foreground/background
task separation for reproducible testing.
- **Executors**: `ForegroundExecutor` (!Send, thread-local) and
`BackgroundExecutor` (Send, with blocking/timeout support) as
GPUI-compatible wrappers.
- **Clock and Timer**: Controllable `TestClock` and future-based `Timer`
for time-sensitive tests.
- **Testing APIs**: `once()`, `with_seed()`, and `many()` methods for
configurable test runs.
- **Dependencies**: Added `async-task`, `chrono`, `futures`, etc., with
updates to `Cargo.toml` and lock file.

## Benefits

- **Integration Testing**: Facilitates reliable async tests involving
cloud sessions, reducing flakiness via deterministic execution.
- **Pluggability**: Trait-based design (`Scheduler`) allows easy
integration into non-GPUI runtimes while maintaining GPUI compatibility.
- **Cleanup**: Refactors GPUI scheduler logic for clarity, correctness
(no `unwrap()`, proper error handling), and extensibility.

Follows Rust guidelines; run `./script/clippy` for verification.

- [x] Define and test a core scheduler that we think can power our cloud
code and GPUI
- [ ] Replace GPUI's scheduler


Release Notes:

- N/A

---------

Co-authored-by: Antonio Scandurra <me@as-cii.com>
This commit is contained in:
Nathan Sobo
2025-09-04 17:14:53 +02:00
committed by GitHub
co-authored by Antonio Scandurra
parent a05f86f97b
commit 1ae326432e
64 changed files with 1569 additions and 473 deletions
+7 -6
View File
@@ -815,7 +815,8 @@ pub fn defer<F: FnOnce()>(f: F) -> Deferred<F> {
#[cfg(any(test, feature = "test-support"))]
mod rng {
use rand::{Rng, seq::SliceRandom};
use rand::prelude::*;
pub struct RandomCharIter<T: Rng> {
rng: T,
simple_text: bool,
@@ -840,18 +841,18 @@ mod rng {
fn next(&mut self) -> Option<Self::Item> {
if self.simple_text {
return if self.rng.gen_range(0..100) < 5 {
return if self.rng.random_range(0..100) < 5 {
Some('\n')
} else {
Some(self.rng.gen_range(b'a'..b'z' + 1).into())
Some(self.rng.random_range(b'a'..b'z' + 1).into())
};
}
match self.rng.gen_range(0..100) {
match self.rng.random_range(0..100) {
// whitespace
0..=19 => [' ', '\n', '\r', '\t'].choose(&mut self.rng).copied(),
// two-byte greek letters
20..=32 => char::from_u32(self.rng.gen_range(('α' as u32)..('ω' as u32 + 1))),
20..=32 => char::from_u32(self.rng.random_range(('α' as u32)..('ω' as u32 + 1))),
// // three-byte characters
33..=45 => ['✋', '✅', '❌', '❎', '⭐']
.choose(&mut self.rng)
@@ -859,7 +860,7 @@ mod rng {
// // four-byte characters
46..=58 => ['🍐', '🏀', '🍗', '🎉'].choose(&mut self.rng).copied(),
// ascii letters
_ => Some(self.rng.gen_range(b'a'..b'z' + 1).into()),
_ => Some(self.rng.random_range(b'a'..b'z' + 1).into()),
}
}
}