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
@@ -3013,7 +3013,7 @@ fn test_random_collaboration(cx: &mut App, mut rng: StdRng) {
|
||||
.map(|i| i.parse().expect("invalid `OPERATIONS` variable"))
|
||||
.unwrap_or(10);
|
||||
|
||||
let base_text_len = rng.gen_range(0..10);
|
||||
let base_text_len = rng.random_range(0..10);
|
||||
let base_text = RandomCharIter::new(&mut rng)
|
||||
.take(base_text_len)
|
||||
.collect::<String>();
|
||||
@@ -3022,7 +3022,7 @@ fn test_random_collaboration(cx: &mut App, mut rng: StdRng) {
|
||||
let network = Arc::new(Mutex::new(Network::new(rng.clone())));
|
||||
let base_buffer = cx.new(|cx| Buffer::local(base_text.as_str(), cx));
|
||||
|
||||
for i in 0..rng.gen_range(min_peers..=max_peers) {
|
||||
for i in 0..rng.random_range(min_peers..=max_peers) {
|
||||
let buffer = cx.new(|cx| {
|
||||
let state = base_buffer.read(cx).to_proto(cx);
|
||||
let ops = cx
|
||||
@@ -3035,7 +3035,7 @@ fn test_random_collaboration(cx: &mut App, mut rng: StdRng) {
|
||||
.map(|op| proto::deserialize_operation(op).unwrap()),
|
||||
cx,
|
||||
);
|
||||
buffer.set_group_interval(Duration::from_millis(rng.gen_range(0..=200)));
|
||||
buffer.set_group_interval(Duration::from_millis(rng.random_range(0..=200)));
|
||||
let network = network.clone();
|
||||
cx.subscribe(&cx.entity(), move |buffer, _, event, _| {
|
||||
if let BufferEvent::Operation {
|
||||
@@ -3066,11 +3066,11 @@ fn test_random_collaboration(cx: &mut App, mut rng: StdRng) {
|
||||
let mut next_diagnostic_id = 0;
|
||||
let mut active_selections = BTreeMap::default();
|
||||
loop {
|
||||
let replica_index = rng.gen_range(0..replica_ids.len());
|
||||
let replica_index = rng.random_range(0..replica_ids.len());
|
||||
let replica_id = replica_ids[replica_index];
|
||||
let buffer = &mut buffers[replica_index];
|
||||
let mut new_buffer = None;
|
||||
match rng.gen_range(0..100) {
|
||||
match rng.random_range(0..100) {
|
||||
0..=29 if mutation_count != 0 => {
|
||||
buffer.update(cx, |buffer, cx| {
|
||||
buffer.start_transaction_at(now);
|
||||
@@ -3082,13 +3082,13 @@ fn test_random_collaboration(cx: &mut App, mut rng: StdRng) {
|
||||
}
|
||||
30..=39 if mutation_count != 0 => {
|
||||
buffer.update(cx, |buffer, cx| {
|
||||
if rng.gen_bool(0.2) {
|
||||
if rng.random_bool(0.2) {
|
||||
log::info!("peer {} clearing active selections", replica_id);
|
||||
active_selections.remove(&replica_id);
|
||||
buffer.remove_active_selections(cx);
|
||||
} else {
|
||||
let mut selections = Vec::new();
|
||||
for id in 0..rng.gen_range(1..=5) {
|
||||
for id in 0..rng.random_range(1..=5) {
|
||||
let range = buffer.random_byte_range(0, &mut rng);
|
||||
selections.push(Selection {
|
||||
id,
|
||||
@@ -3111,7 +3111,7 @@ fn test_random_collaboration(cx: &mut App, mut rng: StdRng) {
|
||||
mutation_count -= 1;
|
||||
}
|
||||
40..=49 if mutation_count != 0 && replica_id == 0 => {
|
||||
let entry_count = rng.gen_range(1..=5);
|
||||
let entry_count = rng.random_range(1..=5);
|
||||
buffer.update(cx, |buffer, cx| {
|
||||
let diagnostics = DiagnosticSet::new(
|
||||
(0..entry_count).map(|_| {
|
||||
@@ -3166,7 +3166,7 @@ fn test_random_collaboration(cx: &mut App, mut rng: StdRng) {
|
||||
new_buffer.replica_id(),
|
||||
new_buffer.text()
|
||||
);
|
||||
new_buffer.set_group_interval(Duration::from_millis(rng.gen_range(0..=200)));
|
||||
new_buffer.set_group_interval(Duration::from_millis(rng.random_range(0..=200)));
|
||||
let network = network.clone();
|
||||
cx.subscribe(&cx.entity(), move |buffer, _, event, _| {
|
||||
if let BufferEvent::Operation {
|
||||
@@ -3238,7 +3238,7 @@ fn test_random_collaboration(cx: &mut App, mut rng: StdRng) {
|
||||
_ => {}
|
||||
}
|
||||
|
||||
now += Duration::from_millis(rng.gen_range(0..=200));
|
||||
now += Duration::from_millis(rng.random_range(0..=200));
|
||||
buffers.extend(new_buffer);
|
||||
|
||||
for buffer in &buffers {
|
||||
@@ -3320,23 +3320,23 @@ fn test_trailing_whitespace_ranges(mut rng: StdRng) {
|
||||
// Generate a random multi-line string containing
|
||||
// some lines with trailing whitespace.
|
||||
let mut text = String::new();
|
||||
for _ in 0..rng.gen_range(0..16) {
|
||||
for _ in 0..rng.gen_range(0..36) {
|
||||
text.push(match rng.gen_range(0..10) {
|
||||
for _ in 0..rng.random_range(0..16) {
|
||||
for _ in 0..rng.random_range(0..36) {
|
||||
text.push(match rng.random_range(0..10) {
|
||||
0..=1 => ' ',
|
||||
3 => '\t',
|
||||
_ => rng.gen_range('a'..='z'),
|
||||
_ => rng.random_range('a'..='z'),
|
||||
});
|
||||
}
|
||||
text.push('\n');
|
||||
}
|
||||
|
||||
match rng.gen_range(0..10) {
|
||||
match rng.random_range(0..10) {
|
||||
// sometimes remove the last newline
|
||||
0..=1 => drop(text.pop()), //
|
||||
|
||||
// sometimes add extra newlines
|
||||
2..=3 => text.push_str(&"\n".repeat(rng.gen_range(1..5))),
|
||||
2..=3 => text.push_str(&"\n".repeat(rng.random_range(1..5))),
|
||||
_ => {}
|
||||
}
|
||||
|
||||
|
||||
Reference in New Issue
Block a user