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",
cameron created
Cargo.lock | 1
Cargo.toml | 1
crates/agent/src/edit_agent/evals.rs | 4
crates/editor/benches/display_map.rs | 12
crates/editor/benches/editor_render.rs | 4
crates/extension_host/benches/extension_compilation_benchmark.rs | 12
crates/gpui/Cargo.toml | 3
crates/gpui/src/app/test_context.rs | 6
crates/gpui/src/executor.rs | 4
crates/gpui/src/platform/test/dispatcher.rs | 8
crates/gpui/src/test.rs | 52 +
crates/gpui/src/text_system/line_wrapper.rs | 4
crates/gpui_macros/src/test.rs | 2
13 files changed, 84 insertions(+), 29 deletions(-)
@@ -7322,6 +7322,7 @@ dependencies = [
"pretty_assertions",
"profiling",
"rand 0.9.2",
+ "rand_chacha 0.9.0",
"raw-window-handle",
"refineable",
"reqwest_client",
@@ -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"
@@ -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<Result<EvalOutput>>) {
- 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;
@@ -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::<String>();
@@ -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::<String>();
@@ -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);
@@ -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| {
@@ -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"] }
@@ -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)
}
@@ -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()
}
@@ -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<TestDispatcherId, VecDeque<Runnable>>,
background: Vec<Runnable>,
deprioritized_background: Vec<Runnable>,
@@ -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()
}
@@ -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<T: 'static>(entity: &Entity<T>, 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 = <Inner as rand::SeedableRng>::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]);
+ }
+ }
+}
@@ -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())
@@ -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;
}