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:
co-authored by
Antonio Scandurra
parent
a05f86f97b
commit
1ae326432e
@@ -1552,15 +1552,15 @@ pub mod tests {
|
||||
.map(|i| i.parse().expect("invalid `OPERATIONS` variable"))
|
||||
.unwrap_or(10);
|
||||
|
||||
let mut tab_size = rng.gen_range(1..=4);
|
||||
let buffer_start_excerpt_header_height = rng.gen_range(1..=5);
|
||||
let excerpt_header_height = rng.gen_range(1..=5);
|
||||
let mut tab_size = rng.random_range(1..=4);
|
||||
let buffer_start_excerpt_header_height = rng.random_range(1..=5);
|
||||
let excerpt_header_height = rng.random_range(1..=5);
|
||||
let font_size = px(14.0);
|
||||
let max_wrap_width = 300.0;
|
||||
let mut wrap_width = if rng.gen_bool(0.1) {
|
||||
let mut wrap_width = if rng.random_bool(0.1) {
|
||||
None
|
||||
} else {
|
||||
Some(px(rng.gen_range(0.0..=max_wrap_width)))
|
||||
Some(px(rng.random_range(0.0..=max_wrap_width)))
|
||||
};
|
||||
|
||||
log::info!("tab size: {}", tab_size);
|
||||
@@ -1571,8 +1571,8 @@ pub mod tests {
|
||||
});
|
||||
|
||||
let buffer = cx.update(|cx| {
|
||||
if rng.r#gen() {
|
||||
let len = rng.gen_range(0..10);
|
||||
if rng.random() {
|
||||
let len = rng.random_range(0..10);
|
||||
let text = util::RandomCharIter::new(&mut rng)
|
||||
.take(len)
|
||||
.collect::<String>();
|
||||
@@ -1609,12 +1609,12 @@ pub mod tests {
|
||||
log::info!("display text: {:?}", snapshot.text());
|
||||
|
||||
for _i in 0..operations {
|
||||
match rng.gen_range(0..100) {
|
||||
match rng.random_range(0..100) {
|
||||
0..=19 => {
|
||||
wrap_width = if rng.gen_bool(0.2) {
|
||||
wrap_width = if rng.random_bool(0.2) {
|
||||
None
|
||||
} else {
|
||||
Some(px(rng.gen_range(0.0..=max_wrap_width)))
|
||||
Some(px(rng.random_range(0.0..=max_wrap_width)))
|
||||
};
|
||||
log::info!("setting wrap width to {:?}", wrap_width);
|
||||
map.update(cx, |map, cx| map.set_wrap_width(wrap_width, cx));
|
||||
@@ -1634,28 +1634,27 @@ pub mod tests {
|
||||
}
|
||||
30..=44 => {
|
||||
map.update(cx, |map, cx| {
|
||||
if rng.r#gen() || blocks.is_empty() {
|
||||
if rng.random() || blocks.is_empty() {
|
||||
let buffer = map.snapshot(cx).buffer_snapshot;
|
||||
let block_properties = (0..rng.gen_range(1..=1))
|
||||
let block_properties = (0..rng.random_range(1..=1))
|
||||
.map(|_| {
|
||||
let position =
|
||||
buffer.anchor_after(buffer.clip_offset(
|
||||
rng.gen_range(0..=buffer.len()),
|
||||
Bias::Left,
|
||||
));
|
||||
let position = buffer.anchor_after(buffer.clip_offset(
|
||||
rng.random_range(0..=buffer.len()),
|
||||
Bias::Left,
|
||||
));
|
||||
|
||||
let placement = if rng.r#gen() {
|
||||
let placement = if rng.random() {
|
||||
BlockPlacement::Above(position)
|
||||
} else {
|
||||
BlockPlacement::Below(position)
|
||||
};
|
||||
let height = rng.gen_range(1..5);
|
||||
let height = rng.random_range(1..5);
|
||||
log::info!(
|
||||
"inserting block {:?} with height {}",
|
||||
placement.as_ref().map(|p| p.to_point(&buffer)),
|
||||
height
|
||||
);
|
||||
let priority = rng.gen_range(1..100);
|
||||
let priority = rng.random_range(1..100);
|
||||
BlockProperties {
|
||||
placement,
|
||||
style: BlockStyle::Fixed,
|
||||
@@ -1668,9 +1667,9 @@ pub mod tests {
|
||||
blocks.extend(map.insert_blocks(block_properties, cx));
|
||||
} else {
|
||||
blocks.shuffle(&mut rng);
|
||||
let remove_count = rng.gen_range(1..=4.min(blocks.len()));
|
||||
let remove_count = rng.random_range(1..=4.min(blocks.len()));
|
||||
let block_ids_to_remove = (0..remove_count)
|
||||
.map(|_| blocks.remove(rng.gen_range(0..blocks.len())))
|
||||
.map(|_| blocks.remove(rng.random_range(0..blocks.len())))
|
||||
.collect();
|
||||
log::info!("removing block ids {:?}", block_ids_to_remove);
|
||||
map.remove_blocks(block_ids_to_remove, cx);
|
||||
@@ -1679,16 +1678,16 @@ pub mod tests {
|
||||
}
|
||||
45..=79 => {
|
||||
let mut ranges = Vec::new();
|
||||
for _ in 0..rng.gen_range(1..=3) {
|
||||
for _ in 0..rng.random_range(1..=3) {
|
||||
buffer.read_with(cx, |buffer, cx| {
|
||||
let buffer = buffer.read(cx);
|
||||
let end = buffer.clip_offset(rng.gen_range(0..=buffer.len()), Right);
|
||||
let start = buffer.clip_offset(rng.gen_range(0..=end), Left);
|
||||
let end = buffer.clip_offset(rng.random_range(0..=buffer.len()), Right);
|
||||
let start = buffer.clip_offset(rng.random_range(0..=end), Left);
|
||||
ranges.push(start..end);
|
||||
});
|
||||
}
|
||||
|
||||
if rng.r#gen() && fold_count > 0 {
|
||||
if rng.random() && fold_count > 0 {
|
||||
log::info!("unfolding ranges: {:?}", ranges);
|
||||
map.update(cx, |map, cx| {
|
||||
map.unfold_intersecting(ranges, true, cx);
|
||||
@@ -1727,8 +1726,8 @@ pub mod tests {
|
||||
// Line boundaries
|
||||
let buffer = &snapshot.buffer_snapshot;
|
||||
for _ in 0..5 {
|
||||
let row = rng.gen_range(0..=buffer.max_point().row);
|
||||
let column = rng.gen_range(0..=buffer.line_len(MultiBufferRow(row)));
|
||||
let row = rng.random_range(0..=buffer.max_point().row);
|
||||
let column = rng.random_range(0..=buffer.line_len(MultiBufferRow(row)));
|
||||
let point = buffer.clip_point(Point::new(row, column), Left);
|
||||
|
||||
let (prev_buffer_bound, prev_display_bound) = snapshot.prev_line_boundary(point);
|
||||
@@ -1776,8 +1775,8 @@ pub mod tests {
|
||||
let min_point = snapshot.clip_point(DisplayPoint::new(DisplayRow(0), 0), Left);
|
||||
let max_point = snapshot.clip_point(snapshot.max_point(), Right);
|
||||
for _ in 0..5 {
|
||||
let row = rng.gen_range(0..=snapshot.max_point().row().0);
|
||||
let column = rng.gen_range(0..=snapshot.line_len(DisplayRow(row)));
|
||||
let row = rng.random_range(0..=snapshot.max_point().row().0);
|
||||
let column = rng.random_range(0..=snapshot.line_len(DisplayRow(row)));
|
||||
let point = snapshot.clip_point(DisplayPoint::new(DisplayRow(row), column), Left);
|
||||
|
||||
log::info!("Moving from point {:?}", point);
|
||||
|
||||
Reference in New Issue
Block a user