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
@@ -3580,7 +3580,7 @@ impl MultiBuffer {
|
||||
pub fn build_random(rng: &mut impl rand::Rng, cx: &mut gpui::App) -> Entity<Self> {
|
||||
cx.new(|cx| {
|
||||
let mut multibuffer = MultiBuffer::new(Capability::ReadWrite);
|
||||
let mutation_count = rng.gen_range(1..=5);
|
||||
let mutation_count = rng.random_range(1..=5);
|
||||
multibuffer.randomly_edit_excerpts(rng, mutation_count, cx);
|
||||
multibuffer
|
||||
})
|
||||
@@ -3603,16 +3603,17 @@ impl MultiBuffer {
|
||||
}
|
||||
|
||||
let new_start = last_end.map_or(0, |last_end| last_end + 1);
|
||||
let end = snapshot.clip_offset(rng.gen_range(new_start..=snapshot.len()), Bias::Right);
|
||||
let start = snapshot.clip_offset(rng.gen_range(new_start..=end), Bias::Right);
|
||||
let end =
|
||||
snapshot.clip_offset(rng.random_range(new_start..=snapshot.len()), Bias::Right);
|
||||
let start = snapshot.clip_offset(rng.random_range(new_start..=end), Bias::Right);
|
||||
last_end = Some(end);
|
||||
|
||||
let mut range = start..end;
|
||||
if rng.gen_bool(0.2) {
|
||||
if rng.random_bool(0.2) {
|
||||
mem::swap(&mut range.start, &mut range.end);
|
||||
}
|
||||
|
||||
let new_text_len = rng.gen_range(0..10);
|
||||
let new_text_len = rng.random_range(0..10);
|
||||
let new_text: String = RandomCharIter::new(&mut *rng).take(new_text_len).collect();
|
||||
|
||||
edits.push((range, new_text.into()));
|
||||
@@ -3639,18 +3640,18 @@ impl MultiBuffer {
|
||||
|
||||
let mut buffers = Vec::new();
|
||||
for _ in 0..mutation_count {
|
||||
if rng.gen_bool(0.05) {
|
||||
if rng.random_bool(0.05) {
|
||||
log::info!("Clearing multi-buffer");
|
||||
self.clear(cx);
|
||||
continue;
|
||||
} else if rng.gen_bool(0.1) && !self.excerpt_ids().is_empty() {
|
||||
} else if rng.random_bool(0.1) && !self.excerpt_ids().is_empty() {
|
||||
let ids = self.excerpt_ids();
|
||||
let mut excerpts = HashSet::default();
|
||||
for _ in 0..rng.gen_range(0..ids.len()) {
|
||||
for _ in 0..rng.random_range(0..ids.len()) {
|
||||
excerpts.extend(ids.choose(rng).copied());
|
||||
}
|
||||
|
||||
let line_count = rng.gen_range(0..5);
|
||||
let line_count = rng.random_range(0..5);
|
||||
|
||||
log::info!("Expanding excerpts {excerpts:?} by {line_count} lines");
|
||||
|
||||
@@ -3664,8 +3665,8 @@ impl MultiBuffer {
|
||||
}
|
||||
|
||||
let excerpt_ids = self.excerpt_ids();
|
||||
if excerpt_ids.is_empty() || (rng.r#gen() && excerpt_ids.len() < max_excerpts) {
|
||||
let buffer_handle = if rng.r#gen() || self.buffers.borrow().is_empty() {
|
||||
if excerpt_ids.is_empty() || (rng.random() && excerpt_ids.len() < max_excerpts) {
|
||||
let buffer_handle = if rng.random() || self.buffers.borrow().is_empty() {
|
||||
let text = RandomCharIter::new(&mut *rng).take(10).collect::<String>();
|
||||
buffers.push(cx.new(|cx| Buffer::local(text, cx)));
|
||||
let buffer = buffers.last().unwrap().read(cx);
|
||||
@@ -3687,11 +3688,11 @@ impl MultiBuffer {
|
||||
|
||||
let buffer = buffer_handle.read(cx);
|
||||
let buffer_text = buffer.text();
|
||||
let ranges = (0..rng.gen_range(0..5))
|
||||
let ranges = (0..rng.random_range(0..5))
|
||||
.map(|_| {
|
||||
let end_ix =
|
||||
buffer.clip_offset(rng.gen_range(0..=buffer.len()), Bias::Right);
|
||||
let start_ix = buffer.clip_offset(rng.gen_range(0..=end_ix), Bias::Left);
|
||||
buffer.clip_offset(rng.random_range(0..=buffer.len()), Bias::Right);
|
||||
let start_ix = buffer.clip_offset(rng.random_range(0..=end_ix), Bias::Left);
|
||||
ExcerptRange::new(start_ix..end_ix)
|
||||
})
|
||||
.collect::<Vec<_>>();
|
||||
@@ -3708,7 +3709,7 @@ impl MultiBuffer {
|
||||
let excerpt_id = self.push_excerpts(buffer_handle.clone(), ranges, cx);
|
||||
log::info!("Inserted with ids: {:?}", excerpt_id);
|
||||
} else {
|
||||
let remove_count = rng.gen_range(1..=excerpt_ids.len());
|
||||
let remove_count = rng.random_range(1..=excerpt_ids.len());
|
||||
let mut excerpts_to_remove = excerpt_ids
|
||||
.choose_multiple(rng, remove_count)
|
||||
.cloned()
|
||||
@@ -3730,7 +3731,7 @@ impl MultiBuffer {
|
||||
) {
|
||||
use rand::prelude::*;
|
||||
|
||||
if rng.gen_bool(0.7) || self.singleton {
|
||||
if rng.random_bool(0.7) || self.singleton {
|
||||
let buffer = self
|
||||
.buffers
|
||||
.borrow()
|
||||
@@ -3740,7 +3741,7 @@ impl MultiBuffer {
|
||||
|
||||
if let Some(buffer) = buffer {
|
||||
buffer.update(cx, |buffer, cx| {
|
||||
if rng.r#gen() {
|
||||
if rng.random() {
|
||||
buffer.randomly_edit(rng, mutation_count, cx);
|
||||
} else {
|
||||
buffer.randomly_undo_redo(rng, cx);
|
||||
@@ -6388,8 +6389,8 @@ impl MultiBufferSnapshot {
|
||||
#[cfg(any(test, feature = "test-support"))]
|
||||
impl MultiBufferSnapshot {
|
||||
pub fn random_byte_range(&self, start_offset: usize, rng: &mut impl rand::Rng) -> Range<usize> {
|
||||
let end = self.clip_offset(rng.gen_range(start_offset..=self.len()), Bias::Right);
|
||||
let start = self.clip_offset(rng.gen_range(start_offset..=end), Bias::Right);
|
||||
let end = self.clip_offset(rng.random_range(start_offset..=self.len()), Bias::Right);
|
||||
let start = self.clip_offset(rng.random_range(start_offset..=end), Bias::Right);
|
||||
start..end
|
||||
}
|
||||
|
||||
|
||||
@@ -2491,12 +2491,12 @@ async fn test_random_set_ranges(cx: &mut TestAppContext, mut rng: StdRng) {
|
||||
|
||||
for _ in 0..operations {
|
||||
let snapshot = buf.update(cx, |buf, _| buf.snapshot());
|
||||
let num_ranges = rng.gen_range(0..=10);
|
||||
let num_ranges = rng.random_range(0..=10);
|
||||
let max_row = snapshot.max_point().row;
|
||||
let mut ranges = (0..num_ranges)
|
||||
.map(|_| {
|
||||
let start = rng.gen_range(0..max_row);
|
||||
let end = rng.gen_range(start + 1..max_row + 1);
|
||||
let start = rng.random_range(0..max_row);
|
||||
let end = rng.random_range(start + 1..max_row + 1);
|
||||
Point::row_range(start..end)
|
||||
})
|
||||
.collect::<Vec<_>>();
|
||||
@@ -2562,11 +2562,11 @@ async fn test_random_multibuffer(cx: &mut TestAppContext, mut rng: StdRng) {
|
||||
let mut needs_diff_calculation = false;
|
||||
|
||||
for _ in 0..operations {
|
||||
match rng.gen_range(0..100) {
|
||||
match rng.random_range(0..100) {
|
||||
0..=14 if !buffers.is_empty() => {
|
||||
let buffer = buffers.choose(&mut rng).unwrap();
|
||||
buffer.update(cx, |buf, cx| {
|
||||
let edit_count = rng.gen_range(1..5);
|
||||
let edit_count = rng.random_range(1..5);
|
||||
buf.randomly_edit(&mut rng, edit_count, cx);
|
||||
log::info!("buffer text:\n{}", buf.text());
|
||||
needs_diff_calculation = true;
|
||||
@@ -2577,11 +2577,11 @@ async fn test_random_multibuffer(cx: &mut TestAppContext, mut rng: StdRng) {
|
||||
multibuffer.update(cx, |multibuffer, cx| {
|
||||
let ids = multibuffer.excerpt_ids();
|
||||
let mut excerpts = HashSet::default();
|
||||
for _ in 0..rng.gen_range(0..ids.len()) {
|
||||
for _ in 0..rng.random_range(0..ids.len()) {
|
||||
excerpts.extend(ids.choose(&mut rng).copied());
|
||||
}
|
||||
|
||||
let line_count = rng.gen_range(0..5);
|
||||
let line_count = rng.random_range(0..5);
|
||||
|
||||
let excerpt_ixs = excerpts
|
||||
.iter()
|
||||
@@ -2600,7 +2600,7 @@ async fn test_random_multibuffer(cx: &mut TestAppContext, mut rng: StdRng) {
|
||||
}
|
||||
20..=29 if !reference.excerpts.is_empty() => {
|
||||
let mut ids_to_remove = vec![];
|
||||
for _ in 0..rng.gen_range(1..=3) {
|
||||
for _ in 0..rng.random_range(1..=3) {
|
||||
let Some(excerpt) = reference.excerpts.choose(&mut rng) else {
|
||||
break;
|
||||
};
|
||||
@@ -2620,8 +2620,12 @@ async fn test_random_multibuffer(cx: &mut TestAppContext, mut rng: StdRng) {
|
||||
let multibuffer =
|
||||
multibuffer.read_with(cx, |multibuffer, cx| multibuffer.snapshot(cx));
|
||||
let offset =
|
||||
multibuffer.clip_offset(rng.gen_range(0..=multibuffer.len()), Bias::Left);
|
||||
let bias = if rng.r#gen() { Bias::Left } else { Bias::Right };
|
||||
multibuffer.clip_offset(rng.random_range(0..=multibuffer.len()), Bias::Left);
|
||||
let bias = if rng.random() {
|
||||
Bias::Left
|
||||
} else {
|
||||
Bias::Right
|
||||
};
|
||||
log::info!("Creating anchor at {} with bias {:?}", offset, bias);
|
||||
anchors.push(multibuffer.anchor_at(offset, bias));
|
||||
anchors.sort_by(|a, b| a.cmp(b, &multibuffer));
|
||||
@@ -2654,7 +2658,7 @@ async fn test_random_multibuffer(cx: &mut TestAppContext, mut rng: StdRng) {
|
||||
45..=55 if !reference.excerpts.is_empty() => {
|
||||
multibuffer.update(cx, |multibuffer, cx| {
|
||||
let snapshot = multibuffer.snapshot(cx);
|
||||
let excerpt_ix = rng.gen_range(0..reference.excerpts.len());
|
||||
let excerpt_ix = rng.random_range(0..reference.excerpts.len());
|
||||
let excerpt = &reference.excerpts[excerpt_ix];
|
||||
let start = excerpt.range.start;
|
||||
let end = excerpt.range.end;
|
||||
@@ -2691,7 +2695,7 @@ async fn test_random_multibuffer(cx: &mut TestAppContext, mut rng: StdRng) {
|
||||
});
|
||||
}
|
||||
_ => {
|
||||
let buffer_handle = if buffers.is_empty() || rng.gen_bool(0.4) {
|
||||
let buffer_handle = if buffers.is_empty() || rng.random_bool(0.4) {
|
||||
let mut base_text = util::RandomCharIter::new(&mut rng)
|
||||
.take(256)
|
||||
.collect::<String>();
|
||||
@@ -2708,7 +2712,7 @@ async fn test_random_multibuffer(cx: &mut TestAppContext, mut rng: StdRng) {
|
||||
buffers.choose(&mut rng).unwrap()
|
||||
};
|
||||
|
||||
let prev_excerpt_ix = rng.gen_range(0..=reference.excerpts.len());
|
||||
let prev_excerpt_ix = rng.random_range(0..=reference.excerpts.len());
|
||||
let prev_excerpt_id = reference
|
||||
.excerpts
|
||||
.get(prev_excerpt_ix)
|
||||
@@ -2716,8 +2720,8 @@ async fn test_random_multibuffer(cx: &mut TestAppContext, mut rng: StdRng) {
|
||||
let excerpt_ix = (prev_excerpt_ix + 1).min(reference.excerpts.len());
|
||||
|
||||
let (range, anchor_range) = buffer_handle.read_with(cx, |buffer, _| {
|
||||
let end_row = rng.gen_range(0..=buffer.max_point().row);
|
||||
let start_row = rng.gen_range(0..=end_row);
|
||||
let end_row = rng.random_range(0..=buffer.max_point().row);
|
||||
let start_row = rng.random_range(0..=end_row);
|
||||
let end_ix = buffer.point_to_offset(Point::new(end_row, 0));
|
||||
let start_ix = buffer.point_to_offset(Point::new(start_row, 0));
|
||||
let anchor_range = buffer.anchor_before(start_ix)..buffer.anchor_after(end_ix);
|
||||
@@ -2766,7 +2770,7 @@ async fn test_random_multibuffer(cx: &mut TestAppContext, mut rng: StdRng) {
|
||||
}
|
||||
}
|
||||
|
||||
if rng.gen_bool(0.3) {
|
||||
if rng.random_bool(0.3) {
|
||||
multibuffer.update(cx, |multibuffer, cx| {
|
||||
old_versions.push((multibuffer.snapshot(cx), multibuffer.subscribe()));
|
||||
})
|
||||
@@ -2815,7 +2819,7 @@ async fn test_random_multibuffer(cx: &mut TestAppContext, mut rng: StdRng) {
|
||||
pretty_assertions::assert_eq!(actual_row_infos, expected_row_infos);
|
||||
|
||||
for _ in 0..5 {
|
||||
let start_row = rng.gen_range(0..=expected_row_infos.len());
|
||||
let start_row = rng.random_range(0..=expected_row_infos.len());
|
||||
assert_eq!(
|
||||
snapshot
|
||||
.row_infos(MultiBufferRow(start_row as u32))
|
||||
@@ -2872,8 +2876,8 @@ async fn test_random_multibuffer(cx: &mut TestAppContext, mut rng: StdRng) {
|
||||
|
||||
let text_rope = Rope::from(expected_text.as_str());
|
||||
for _ in 0..10 {
|
||||
let end_ix = text_rope.clip_offset(rng.gen_range(0..=text_rope.len()), Bias::Right);
|
||||
let start_ix = text_rope.clip_offset(rng.gen_range(0..=end_ix), Bias::Left);
|
||||
let end_ix = text_rope.clip_offset(rng.random_range(0..=text_rope.len()), Bias::Right);
|
||||
let start_ix = text_rope.clip_offset(rng.random_range(0..=end_ix), Bias::Left);
|
||||
|
||||
let text_for_range = snapshot
|
||||
.text_for_range(start_ix..end_ix)
|
||||
@@ -2908,7 +2912,7 @@ async fn test_random_multibuffer(cx: &mut TestAppContext, mut rng: StdRng) {
|
||||
}
|
||||
|
||||
for _ in 0..10 {
|
||||
let end_ix = text_rope.clip_offset(rng.gen_range(0..=text_rope.len()), Bias::Right);
|
||||
let end_ix = text_rope.clip_offset(rng.random_range(0..=text_rope.len()), Bias::Right);
|
||||
assert_eq!(
|
||||
snapshot.reversed_chars_at(end_ix).collect::<String>(),
|
||||
expected_text[..end_ix].chars().rev().collect::<String>(),
|
||||
@@ -2916,8 +2920,8 @@ async fn test_random_multibuffer(cx: &mut TestAppContext, mut rng: StdRng) {
|
||||
}
|
||||
|
||||
for _ in 0..10 {
|
||||
let end_ix = rng.gen_range(0..=text_rope.len());
|
||||
let start_ix = rng.gen_range(0..=end_ix);
|
||||
let end_ix = rng.random_range(0..=text_rope.len());
|
||||
let start_ix = rng.random_range(0..=end_ix);
|
||||
assert_eq!(
|
||||
snapshot
|
||||
.bytes_in_range(start_ix..end_ix)
|
||||
|
||||
Reference in New Issue
Block a user