diff --git a/Cargo.lock b/Cargo.lock index b050f0ed51453954c7b2a2047f4075b8d98bab8c..71c34c68c46b645a085031b0b10d739ed4d55ab1 100644 --- a/Cargo.lock +++ b/Cargo.lock @@ -7322,6 +7322,7 @@ dependencies = [ "pretty_assertions", "profiling", "rand 0.9.2", + "rand_chacha 0.9.0", "raw-window-handle", "refineable", "reqwest_client", diff --git a/Cargo.toml b/Cargo.toml index ac6e310fe7d899486c5b5287f4ac07762751d9a1..6aef721d274d6a7fc218c0fd4548fd9b2ea31da0 100644 --- a/Cargo.toml +++ b/Cargo.toml @@ -602,6 +602,7 @@ prost-types = "0.9" pulldown-cmark = { version = "0.12.0", default-features = false } quote = "1.0.9" rand = "0.9" +rand_chacha = "0.9" rayon = "1.8" ref-cast = "1.0.24" regex = "1.5" diff --git a/crates/agent/src/edit_agent/evals.rs b/crates/agent/src/edit_agent/evals.rs index 2cc6a6b4242a07b688d1232cd39d13797c70b02b..f93436ebdaa3eaefb33491226c1b6abce38fd10a 100644 --- a/crates/agent/src/edit_agent/evals.rs +++ b/crates/agent/src/edit_agent/evals.rs @@ -7,7 +7,7 @@ use client::{Client, UserStore}; use collections::HashMap; use fs::FakeFs; use futures::{FutureExt, future::LocalBoxFuture}; -use gpui::{AppContext, TestAppContext, Timer}; +use gpui::{AppContext, TestAppContext, TestRng, Timer}; use http_client::StatusCode; use indoc::{formatdoc, indoc}; use language_model::{ @@ -1402,7 +1402,7 @@ fn eval( } fn run_eval(eval: EvalInput, tx: mpsc::Sender>) { - let dispatcher = gpui::TestDispatcher::new(StdRng::from_os_rng()); + let dispatcher = gpui::TestDispatcher::new(TestRng::from_os_rng()); let mut cx = TestAppContext::build(dispatcher, None); let output = cx.executor().block_test(async { let test = EditAgentTest::new(&mut cx).await; diff --git a/crates/editor/benches/display_map.rs b/crates/editor/benches/display_map.rs index 919249ad01b87fe5fbabe1b5fe6e563179b41d10..1efd70695b3d6ea6c11bfeed2b6d98dc4a529285 100644 --- a/crates/editor/benches/display_map.rs +++ b/crates/editor/benches/display_map.rs @@ -1,19 +1,19 @@ use criterion::{BenchmarkId, Criterion, criterion_group, criterion_main}; use editor::MultiBuffer; -use gpui::TestDispatcher; +use gpui::{TestDispatcher, TestRng}; use itertools::Itertools; -use rand::{Rng, SeedableRng, rngs::StdRng}; +use rand::{Rng, SeedableRng}; use std::num::NonZeroU32; use text::Bias; use util::RandomCharIter; fn to_tab_point_benchmark(c: &mut Criterion) { - let rng = StdRng::seed_from_u64(1); + let rng = TestRng::seed_from_u64(1); let dispatcher = TestDispatcher::new(rng); let cx = gpui::TestAppContext::build(dispatcher, None); let create_tab_map = |length: usize| { - let mut rng = StdRng::seed_from_u64(1); + let mut rng = TestRng::seed_from_u64(1); let text = RandomCharIter::new(&mut rng) .take(length) .collect::(); @@ -52,12 +52,12 @@ fn to_tab_point_benchmark(c: &mut Criterion) { } fn to_fold_point_benchmark(c: &mut Criterion) { - let rng = StdRng::seed_from_u64(1); + let rng = TestRng::seed_from_u64(1); let dispatcher = TestDispatcher::new(rng); let cx = gpui::TestAppContext::build(dispatcher, None); let create_tab_map = |length: usize| { - let mut rng = StdRng::seed_from_u64(1); + let mut rng = TestRng::seed_from_u64(1); let text = RandomCharIter::new(&mut rng) .take(length) .collect::(); diff --git a/crates/editor/benches/editor_render.rs b/crates/editor/benches/editor_render.rs index 0ae1af5537fb62a7658ccd306545503b818c28ae..bb6d572ae51cead70d137a5103010cbf8cc56e0a 100644 --- a/crates/editor/benches/editor_render.rs +++ b/crates/editor/benches/editor_render.rs @@ -3,7 +3,7 @@ use editor::{ Editor, EditorMode, MultiBuffer, actions::{DeleteToPreviousWordStart, SelectAll, SplitSelectionIntoLines}, }; -use gpui::{AppContext, Focusable as _, TestAppContext, TestDispatcher}; +use gpui::{AppContext, Focusable as _, TestAppContext, TestDispatcher, TestRng}; use project::Project; use rand::{Rng as _, SeedableRng as _, rngs::StdRng}; use settings::SettingsStore; @@ -117,7 +117,7 @@ fn editor_render(bencher: &mut Bencher<'_>, cx: &TestAppContext) { } pub fn benches() { - let dispatcher = TestDispatcher::new(StdRng::seed_from_u64(1)); + let dispatcher = TestDispatcher::new(TestRng::seed_from_u64(1)); let cx = gpui::TestAppContext::build(dispatcher, None); cx.update(|cx| { let store = SettingsStore::test(cx); diff --git a/crates/extension_host/benches/extension_compilation_benchmark.rs b/crates/extension_host/benches/extension_compilation_benchmark.rs index 9cb57fc1fb800df3f20d277cff5c85ecddadf5ad..102fe5713573e3ca2613fdd58c0ae059ac173a26 100644 --- a/crates/extension_host/benches/extension_compilation_benchmark.rs +++ b/crates/extension_host/benches/extension_compilation_benchmark.rs @@ -8,10 +8,10 @@ use extension::{ }; use extension_host::wasm_host::WasmHost; use fs::RealFs; -use gpui::{SemanticVersion, TestAppContext, TestDispatcher}; +use gpui::{SemanticVersion, TestAppContext, TestDispatcher, TestRng}; use http_client::{FakeHttpClient, Response}; use node_runtime::NodeRuntime; -use rand::{SeedableRng, rngs::StdRng}; +use rand::SeedableRng; use reqwest_client::ReqwestClient; use serde_json::json; use settings::SettingsStore; @@ -27,9 +27,9 @@ fn extension_benchmarks(c: &mut Criterion) { let wasm_bytes = wasm_bytes(&cx, &mut manifest); let manifest = Arc::new(manifest); let extensions_dir = TempTree::new(json!({ - "installed": {}, - "work": {} - })); + "installed": {}, + "work": {} +})); let wasm_host = wasm_host(&cx, &extensions_dir); group.bench_function(BenchmarkId::from_parameter(1), |b| { @@ -48,7 +48,7 @@ fn extension_benchmarks(c: &mut Criterion) { fn init() -> TestAppContext { const SEED: u64 = 9999; - let dispatcher = TestDispatcher::new(StdRng::seed_from_u64(SEED)); + let dispatcher = TestDispatcher::new(TestRng::seed_from_u64(SEED)); let cx = TestAppContext::build(dispatcher, None); cx.executor().allow_parking(); cx.update(|cx| { diff --git a/crates/gpui/Cargo.toml b/crates/gpui/Cargo.toml index 6523bbe526848c15053a4bad45dce208a5ecd7e0..73cce3e821730238968f4528b85db1deec46400a 100644 --- a/crates/gpui/Cargo.toml +++ b/crates/gpui/Cargo.toml @@ -22,6 +22,7 @@ test-support = [ "leak-detection", "collections/test-support", "rand", + "rand_chacha", "util/test-support", "http_client/test-support", "wayland", @@ -110,6 +111,7 @@ parking_lot.workspace = true postage.workspace = true profiling.workspace = true rand = { optional = true, workspace = true } +rand_chacha = { optional = true, workspace = true } raw-window-handle = "0.6" refineable.workspace = true resvg = { version = "0.45.0", default-features = false, features = [ @@ -247,6 +249,7 @@ http_client = { workspace = true, features = ["test-support"] } lyon = { version = "1.0", features = ["extra"] } pretty_assertions.workspace = true rand.workspace = true +rand_chacha.workspace = true reqwest_client = { workspace = true, features = ["test-support"] } unicode-segmentation.workspace = true util = { workspace = true, features = ["test-support"] } diff --git a/crates/gpui/src/app/test_context.rs b/crates/gpui/src/app/test_context.rs index d974823396d9f0d546a6b035f47b569145eb021b..f47da39f3834d868e0e9c4578bc901576aeed5d5 100644 --- a/crates/gpui/src/app/test_context.rs +++ b/crates/gpui/src/app/test_context.rs @@ -3,13 +3,13 @@ use crate::{ BackgroundExecutor, BorrowAppContext, Bounds, Capslock, ClipboardItem, DrawPhase, Drawable, Element, Empty, EventEmitter, ForegroundExecutor, Global, InputEvent, Keystroke, Modifiers, ModifiersChangedEvent, MouseButton, MouseDownEvent, MouseMoveEvent, MouseUpEvent, Pixels, - Platform, Point, Render, Result, Size, Task, TestDispatcher, TestPlatform, + Platform, Point, Render, Result, Size, Task, TestDispatcher, TestPlatform, TestRng, TestScreenCaptureSource, TestWindow, TextSystem, VisualContext, Window, WindowBounds, WindowHandle, WindowOptions, }; use anyhow::{anyhow, bail}; use futures::{Stream, StreamExt, channel::oneshot}; -use rand::{SeedableRng, rngs::StdRng}; +use rand::SeedableRng; use std::{cell::RefCell, future::Future, ops::Deref, rc::Rc, sync::Arc, time::Duration}; /// A TestAppContext is provided to tests created with `#[gpui::test]`, it provides @@ -144,7 +144,7 @@ impl TestAppContext { /// Create a single TestAppContext, for non-multi-client tests pub fn single() -> Self { - let dispatcher = TestDispatcher::new(StdRng::seed_from_u64(0)); + let dispatcher = TestDispatcher::new(TestRng::seed_from_u64(0)); Self::build(dispatcher, None) } diff --git a/crates/gpui/src/executor.rs b/crates/gpui/src/executor.rs index b6d3a407f5dbbab07e0273e668e9b5710824edda..90c28497eb1b5abb18203b311847bd8cce458267 100644 --- a/crates/gpui/src/executor.rs +++ b/crates/gpui/src/executor.rs @@ -22,7 +22,7 @@ use util::TryFutureExt; use waker_fn::waker_fn; #[cfg(any(test, feature = "test-support"))] -use rand::rngs::StdRng; +use rand::Rng; /// A pointer to the executor that is currently running, /// for spawning background tasks. @@ -443,7 +443,7 @@ impl BackgroundExecutor { /// in tests, returns the rng used by the dispatcher and seeded by the `SEED` environment variable #[cfg(any(test, feature = "test-support"))] - pub fn rng(&self) -> StdRng { + pub fn rng(&self) -> impl Rng { self.dispatcher.as_test().unwrap().rng() } diff --git a/crates/gpui/src/platform/test/dispatcher.rs b/crates/gpui/src/platform/test/dispatcher.rs index 017c29bfb558f77874a9729a52b518d9d41fb256..0b734e618977ae73da8c88b9188260aa7f5a8ed2 100644 --- a/crates/gpui/src/platform/test/dispatcher.rs +++ b/crates/gpui/src/platform/test/dispatcher.rs @@ -1,4 +1,4 @@ -use crate::{PlatformDispatcher, TaskLabel}; +use crate::{PlatformDispatcher, TaskLabel, TestRng}; use async_task::Runnable; use backtrace::Backtrace; use collections::{HashMap, HashSet, VecDeque}; @@ -25,7 +25,7 @@ pub struct TestDispatcher { } struct TestDispatcherState { - random: StdRng, + random: TestRng, foreground: HashMap>, background: Vec, deprioritized_background: Vec, @@ -43,7 +43,7 @@ struct TestDispatcherState { } impl TestDispatcher { - pub fn new(random: StdRng) -> Self { + pub fn new(random: TestRng) -> Self { let state = TestDispatcherState { random, foreground: HashMap::default(), @@ -227,7 +227,7 @@ impl TestDispatcher { }) } - pub fn rng(&self) -> StdRng { + pub fn rng(&self) -> impl Rng { self.state.lock().random.clone() } diff --git a/crates/gpui/src/test.rs b/crates/gpui/src/test.rs index 5ae72d2be1688893374e16a55445558b5bc33040..630d660ace27384e7421420991d8820037531d6b 100644 --- a/crates/gpui/src/test.rs +++ b/crates/gpui/src/test.rs @@ -54,7 +54,7 @@ pub fn run_test( eprintln!("seed = {seed}"); } let result = panic::catch_unwind(|| { - let dispatcher = TestDispatcher::new(StdRng::seed_from_u64(seed)); + let dispatcher = TestDispatcher::new(TestRng::seed_from_u64(seed)); test_fn(dispatcher, seed); }); @@ -159,3 +159,53 @@ pub fn observe(entity: &Entity, cx: &mut TestAppContext) -> Obser Observation { rx, _subscription } } + +pub use test_rng::TestRng; +mod test_rng { + type Inner = rand_chacha::ChaCha20Rng; + + /// A [portable][0] RNG, suitable for use in tests. + /// + /// Given the same seed, it is guaranteed to produce the same output on all platforms. The + /// values may change in minor version bumps. + /// + /// [0]: https://rust-random.github.io/book/crate-reprod.html#portable-items + #[derive(Debug, Clone)] + pub struct TestRng(Inner); + + impl rand::RngCore for TestRng { + fn next_u32(&mut self) -> u32 { + self.0.next_u32() + } + + fn next_u64(&mut self) -> u64 { + self.0.next_u64() + } + + fn fill_bytes(&mut self, dst: &mut [u8]) { + self.0.fill_bytes(dst); + } + } + + impl rand::SeedableRng for TestRng { + type Seed = ::Seed; + fn from_seed(seed: Self::Seed) -> Self { + Self(Inner::from_seed(seed)) + } + } + + #[cfg(test)] + mod tests { + use super::TestRng; + use rand::{RngCore, SeedableRng}; + + #[test] + fn test_rng_produces_reproducible_values_for_known_seeds() { + let mut rng = TestRng::seed_from_u64(0); + let mut buf = [0; 10]; + rng.fill_bytes(&mut buf); + + assert_eq!(buf, [178, 247, 245, 129, 214, 222, 60, 6, 168, 34]); + } + } +} diff --git a/crates/gpui/src/text_system/line_wrapper.rs b/crates/gpui/src/text_system/line_wrapper.rs index 0192a03a3238e8fad1f5f20e2b824755c0ecee4d..a72cf814d57d15b71d2b94fe324b64ccb14d2261 100644 --- a/crates/gpui/src/text_system/line_wrapper.rs +++ b/crates/gpui/src/text_system/line_wrapper.rs @@ -316,14 +316,14 @@ impl Boundary { mod tests { use super::*; use crate::{ - Font, FontFeatures, FontStyle, FontWeight, Hsla, TestAppContext, TestDispatcher, font, + font, Font, FontFeatures, FontStyle, FontWeight, Hsla, TestAppContext, TestDispatcher, TestRng }; #[cfg(target_os = "macos")] use crate::{TextRun, WindowTextSystem, WrapBoundary}; use rand::prelude::*; fn build_wrapper() -> LineWrapper { - let dispatcher = TestDispatcher::new(StdRng::seed_from_u64(0)); + let dispatcher = TestDispatcher::new(TestRng::seed_from_u64(0)); let cx = TestAppContext::build(dispatcher, None); let id = cx.text_system().resolve_font(&font(".ZedMono")); LineWrapper::new(id, px(16.), cx.text_system().platform_text_system.clone()) diff --git a/crates/gpui_macros/src/test.rs b/crates/gpui_macros/src/test.rs index 42ce304b97a2708bac8dc081b22a561162bdbb1a..dff2567bbd38daf0a354e723be7d59dcabbc71bd 100644 --- a/crates/gpui_macros/src/test.rs +++ b/crates/gpui_macros/src/test.rs @@ -140,7 +140,7 @@ fn generate_test_function( if let Type::Path(ty) = &*arg.ty { let last_segment = ty.path.segments.last(); match last_segment.map(|s| s.ident.to_string()).as_deref() { - Some("StdRng") => { + Some("TestRng") => { inner_fn_args.extend(quote!(rand::SeedableRng::seed_from_u64(_seed),)); continue; }