This commit is contained in:
Conrad Irwin 2023-10-26 18:19:46 +02:00
parent 452006560e
commit adc426b668
15 changed files with 4231 additions and 4182 deletions

6
Cargo.lock generated
View File

@ -1135,7 +1135,7 @@ dependencies = [
"audio2", "audio2",
"client2", "client2",
"collections", "collections",
"fs", "fs2",
"futures 0.3.28", "futures 0.3.28",
"gpui2", "gpui2",
"language2", "language2",
@ -5899,7 +5899,7 @@ dependencies = [
"anyhow", "anyhow",
"client2", "client2",
"collections", "collections",
"fs", "fs2",
"futures 0.3.28", "futures 0.3.28",
"gpui2", "gpui2",
"language2", "language2",
@ -6066,7 +6066,7 @@ dependencies = [
"ctor", "ctor",
"db2", "db2",
"env_logger 0.9.3", "env_logger 0.9.3",
"fs", "fs2",
"fsevent", "fsevent",
"futures 0.3.28", "futures 0.3.28",
"fuzzy2", "fuzzy2",

View File

@ -25,7 +25,7 @@ collections = { path = "../collections" }
gpui2 = { path = "../gpui2" } gpui2 = { path = "../gpui2" }
log.workspace = true log.workspace = true
live_kit_client = { path = "../live_kit_client" } live_kit_client = { path = "../live_kit_client" }
fs = { path = "../fs" } fs2 = { path = "../fs2" }
language2 = { path = "../language2" } language2 = { path = "../language2" }
media = { path = "../media" } media = { path = "../media" }
project2 = { path = "../project2" } project2 = { path = "../project2" }
@ -43,7 +43,7 @@ serde_derive.workspace = true
[dev-dependencies] [dev-dependencies]
client2 = { path = "../client2", features = ["test-support"] } client2 = { path = "../client2", features = ["test-support"] }
fs = { path = "../fs", features = ["test-support"] } fs2 = { path = "../fs2", features = ["test-support"] }
language2 = { path = "../language2", features = ["test-support"] } language2 = { path = "../language2", features = ["test-support"] }
collections = { path = "../collections", features = ["test-support"] } collections = { path = "../collections", features = ["test-support"] }
gpui2 = { path = "../gpui2", features = ["test-support"] } gpui2 = { path = "../gpui2", features = ["test-support"] }

View File

@ -10,7 +10,7 @@ use client2::{
Client, ParticipantIndex, TypedEnvelope, User, UserStore, Client, ParticipantIndex, TypedEnvelope, User, UserStore,
}; };
use collections::{BTreeMap, HashMap, HashSet}; use collections::{BTreeMap, HashMap, HashSet};
use fs::Fs; use fs2::Fs;
use futures::{FutureExt, StreamExt}; use futures::{FutureExt, StreamExt};
use gpui2::{ use gpui2::{
AppContext, AsyncAppContext, Context, EventEmitter, Handle, ModelContext, Task, WeakHandle, AppContext, AsyncAppContext, Context, EventEmitter, Handle, ModelContext, Task, WeakHandle,

View File

@ -84,7 +84,7 @@ struct DeterministicState {
#[derive(Copy, Clone, Debug, PartialEq, Eq)] #[derive(Copy, Clone, Debug, PartialEq, Eq)]
pub enum ExecutorEvent { pub enum ExecutorEvent {
PollRunnable { id: usize }, PollRunnable { id: usize },
EnqueuRunnable { id: usize }, EnqueueRunnable { id: usize },
} }
#[cfg(any(test, feature = "test-support"))] #[cfg(any(test, feature = "test-support"))]
@ -199,7 +199,7 @@ impl Deterministic {
let unparker = self.parker.lock().unparker(); let unparker = self.parker.lock().unparker();
let (runnable, task) = async_task::spawn_local(future, move |runnable| { let (runnable, task) = async_task::spawn_local(future, move |runnable| {
let mut state = state.lock(); let mut state = state.lock();
state.push_to_history(ExecutorEvent::EnqueuRunnable { id }); state.push_to_history(ExecutorEvent::EnqueueRunnable { id });
state state
.scheduled_from_foreground .scheduled_from_foreground
.entry(cx_id) .entry(cx_id)
@ -229,7 +229,7 @@ impl Deterministic {
let mut state = state.lock(); let mut state = state.lock();
state state
.poll_history .poll_history
.push(ExecutorEvent::EnqueuRunnable { id }); .push(ExecutorEvent::EnqueueRunnable { id });
state state
.scheduled_from_background .scheduled_from_background
.push(BackgroundRunnable { id, runnable }); .push(BackgroundRunnable { id, runnable });
@ -616,7 +616,7 @@ impl ExecutorEvent {
pub fn id(&self) -> usize { pub fn id(&self) -> usize {
match self { match self {
ExecutorEvent::PollRunnable { id } => *id, ExecutorEvent::PollRunnable { id } => *id,
ExecutorEvent::EnqueuRunnable { id } => *id, ExecutorEvent::EnqueueRunnable { id } => *id,
} }
} }
} }

View File

@ -362,7 +362,7 @@ impl AppContext {
self.observers.remove(&entity_id); self.observers.remove(&entity_id);
self.event_listeners.remove(&entity_id); self.event_listeners.remove(&entity_id);
for mut release_callback in self.release_listeners.remove(&entity_id) { for mut release_callback in self.release_listeners.remove(&entity_id) {
release_callback(&mut entity, self); release_callback(entity.as_mut(), self);
} }
} }
} }

View File

@ -211,7 +211,7 @@ impl Drop for AnyHandle {
let count = entity_map let count = entity_map
.counts .counts
.get(self.entity_id) .get(self.entity_id)
.expect("Detected over-release of a handle."); .expect("detected over-release of a handle.");
let prev_count = count.fetch_sub(1, SeqCst); let prev_count = count.fetch_sub(1, SeqCst);
assert_ne!(prev_count, 0, "Detected over-release of a handle."); assert_ne!(prev_count, 0, "Detected over-release of a handle.");
if prev_count == 1 { if prev_count == 1 {

View File

@ -1,7 +1,8 @@
use crate::{ use crate::{
AnyWindowHandle, AppContext, AsyncAppContext, Context, Executor, Handle, MainThread, AnyWindowHandle, AppContext, AsyncAppContext, Context, EventEmitter, Executor, Handle,
ModelContext, Result, Task, TestDispatcher, TestPlatform, WindowContext, MainThread, ModelContext, Result, Task, TestDispatcher, TestPlatform, WindowContext,
}; };
use futures::SinkExt;
use parking_lot::Mutex; use parking_lot::Mutex;
use std::{any::Any, future::Future, sync::Arc}; use std::{any::Any, future::Future, sync::Arc};
@ -149,4 +150,39 @@ impl TestAppContext {
executor: self.executor.clone(), executor: self.executor.clone(),
} }
} }
pub fn subscribe<T: 'static + EventEmitter + Send + Sync>(
&mut self,
entity: &Handle<T>,
) -> futures::channel::mpsc::UnboundedReceiver<T::Event>
where
T::Event: 'static + Send + Clone,
{
let (mut tx, rx) = futures::channel::mpsc::unbounded();
entity
.update(self, |_, cx: &mut ModelContext<T>| {
cx.subscribe(&entity, move |_, _, event, _| {
let _ = tx.send(event.clone());
})
})
.detach();
rx
}
} }
// pub fn subscribe<T: Entity>(
// entity: &impl Handle<T>,
// cx: &mut TestAppContext,
// ) -> Observation<T::Event>
// where
// T::Event: Clone,
// {
// let (tx, rx) = smol::channel::unbounded();
// let _subscription = cx.update(|cx| {
// cx.subscribe(entity, move |_, event, _| {
// let _ = smol::block_on(tx.send(event.clone()));
// })
// });
// Observation { rx, _subscription }
// }

View File

@ -147,8 +147,10 @@ impl Executor {
Poll::Pending => { Poll::Pending => {
if !self.dispatcher.poll() { if !self.dispatcher.poll() {
#[cfg(any(test, feature = "test-support"))] #[cfg(any(test, feature = "test-support"))]
if let Some(_) = self.dispatcher.as_test() { if let Some(test) = self.dispatcher.as_test() {
panic!("blocked with nothing left to run") if !test.parking_allowed() {
panic!("blocked with nothing left to run")
}
} }
parker.park(); parker.park();
} }
@ -216,7 +218,7 @@ impl Executor {
#[cfg(any(test, feature = "test-support"))] #[cfg(any(test, feature = "test-support"))]
pub fn simulate_random_delay(&self) -> impl Future<Output = ()> { pub fn simulate_random_delay(&self) -> impl Future<Output = ()> {
self.dispatcher.as_test().unwrap().simulate_random_delay() self.spawn(self.dispatcher.as_test().unwrap().simulate_random_delay())
} }
#[cfg(any(test, feature = "test-support"))] #[cfg(any(test, feature = "test-support"))]
@ -229,6 +231,11 @@ impl Executor {
self.dispatcher.as_test().unwrap().run_until_parked() self.dispatcher.as_test().unwrap().run_until_parked()
} }
#[cfg(any(test, feature = "test-support"))]
pub fn allow_parking(&self) {
self.dispatcher.as_test().unwrap().allow_parking();
}
pub fn num_cpus(&self) -> usize { pub fn num_cpus(&self) -> usize {
num_cpus::get() num_cpus::get()
} }

View File

@ -28,6 +28,7 @@ struct TestDispatcherState {
time: Duration, time: Duration,
is_main_thread: bool, is_main_thread: bool,
next_id: TestDispatcherId, next_id: TestDispatcherId,
allow_parking: bool,
} }
impl TestDispatcher { impl TestDispatcher {
@ -40,6 +41,7 @@ impl TestDispatcher {
time: Duration::ZERO, time: Duration::ZERO,
is_main_thread: true, is_main_thread: true,
next_id: TestDispatcherId(1), next_id: TestDispatcherId(1),
allow_parking: false,
}; };
TestDispatcher { TestDispatcher {
@ -66,7 +68,7 @@ impl TestDispatcher {
self.state.lock().time = new_now; self.state.lock().time = new_now;
} }
pub fn simulate_random_delay(&self) -> impl Future<Output = ()> { pub fn simulate_random_delay(&self) -> impl 'static + Send + Future<Output = ()> {
pub struct YieldNow { pub struct YieldNow {
count: usize, count: usize,
} }
@ -75,6 +77,7 @@ impl TestDispatcher {
type Output = (); type Output = ();
fn poll(mut self: Pin<&mut Self>, cx: &mut Context) -> Poll<Self::Output> { fn poll(mut self: Pin<&mut Self>, cx: &mut Context) -> Poll<Self::Output> {
eprintln!("self.count: {}", self.count);
if self.count > 0 { if self.count > 0 {
self.count -= 1; self.count -= 1;
cx.waker().wake_by_ref(); cx.waker().wake_by_ref();
@ -93,6 +96,14 @@ impl TestDispatcher {
pub fn run_until_parked(&self) { pub fn run_until_parked(&self) {
while self.poll() {} while self.poll() {}
} }
pub fn parking_allowed(&self) -> bool {
self.state.lock().allow_parking
}
pub fn allow_parking(&self) {
self.state.lock().allow_parking = true
}
} }
impl Clone for TestDispatcher { impl Clone for TestDispatcher {

View File

@ -16,7 +16,7 @@ client2 = { path = "../client2" }
collections = { path = "../collections"} collections = { path = "../collections"}
language2 = { path = "../language2" } language2 = { path = "../language2" }
gpui2 = { path = "../gpui2" } gpui2 = { path = "../gpui2" }
fs = { path = "../fs" } fs2 = { path = "../fs2" }
lsp2 = { path = "../lsp2" } lsp2 = { path = "../lsp2" }
node_runtime = { path = "../node_runtime"} node_runtime = { path = "../node_runtime"}
util = { path = "../util" } util = { path = "../util" }
@ -32,4 +32,4 @@ parking_lot.workspace = true
[dev-dependencies] [dev-dependencies]
language2 = { path = "../language2", features = ["test-support"] } language2 = { path = "../language2", features = ["test-support"] }
gpui2 = { path = "../gpui2", features = ["test-support"] } gpui2 = { path = "../gpui2", features = ["test-support"] }
fs = { path = "../fs", features = ["test-support"] } fs2 = { path = "../fs2", features = ["test-support"] }

View File

@ -1,6 +1,6 @@
use anyhow::Context; use anyhow::Context;
use collections::{HashMap, HashSet}; use collections::{HashMap, HashSet};
use fs::Fs; use fs2::Fs;
use gpui2::{AsyncAppContext, Handle}; use gpui2::{AsyncAppContext, Handle};
use language2::{language_settings::language_settings, Buffer, BundledFormatter, Diff}; use language2::{language_settings::language_settings, Buffer, BundledFormatter, Diff};
use lsp2::{LanguageServer, LanguageServerId}; use lsp2::{LanguageServer, LanguageServerId};

View File

@ -25,7 +25,7 @@ client2 = { path = "../client2" }
clock = { path = "../clock" } clock = { path = "../clock" }
collections = { path = "../collections" } collections = { path = "../collections" }
db2 = { path = "../db2" } db2 = { path = "../db2" }
fs = { path = "../fs" } fs2 = { path = "../fs2" }
fsevent = { path = "../fsevent" } fsevent = { path = "../fsevent" }
fuzzy2 = { path = "../fuzzy2" } fuzzy2 = { path = "../fuzzy2" }
git = { path = "../git" } git = { path = "../git" }
@ -71,7 +71,7 @@ pretty_assertions.workspace = true
client2 = { path = "../client2", features = ["test-support"] } client2 = { path = "../client2", features = ["test-support"] }
collections = { path = "../collections", features = ["test-support"] } collections = { path = "../collections", features = ["test-support"] }
db2 = { path = "../db2", features = ["test-support"] } db2 = { path = "../db2", features = ["test-support"] }
fs = { path = "../fs", features = ["test-support"] } fs2 = { path = "../fs2", features = ["test-support"] }
gpui2 = { path = "../gpui2", features = ["test-support"] } gpui2 = { path = "../gpui2", features = ["test-support"] }
language2 = { path = "../language2", features = ["test-support"] } language2 = { path = "../language2", features = ["test-support"] }
lsp2 = { path = "../lsp2", features = ["test-support"] } lsp2 = { path = "../lsp2", features = ["test-support"] }

View File

@ -89,7 +89,7 @@ use util::{
post_inc, ResultExt, TryFutureExt as _, post_inc, ResultExt, TryFutureExt as _,
}; };
pub use fs::*; pub use fs2::*;
pub use worktree::*; pub use worktree::*;
pub trait Item { pub trait Item {
@ -842,39 +842,39 @@ impl Project {
} }
} }
// #[cfg(any(test, feature = "test-support"))] #[cfg(any(test, feature = "test-support"))]
// pub async fn test( pub async fn test(
// fs: Arc<dyn Fs>, fs: Arc<dyn Fs>,
// root_paths: impl IntoIterator<Item = &Path>, root_paths: impl IntoIterator<Item = &Path>,
// cx: &mut gpui::TestAppContext, cx: &mut gpui2::TestAppContext,
// ) -> Handle<Project> { ) -> Handle<Project> {
// let mut languages = LanguageRegistry::test(); let mut languages = LanguageRegistry::test();
// languages.set_executor(cx.background()); languages.set_executor(cx.executor().clone());
// let http_client = util::http::FakeHttpClient::with_404_response(); let http_client = util::http::FakeHttpClient::with_404_response();
// let client = cx.update(|cx| client2::Client::new(http_client.clone(), cx)); let client = cx.update(|cx| client2::Client::new(http_client.clone(), cx));
// let user_store = cx.add_model(|cx| UserStore::new(client.clone(), http_client, cx)); let user_store = cx.entity(|cx| UserStore::new(client.clone(), http_client, cx));
// let project = cx.update(|cx| { let project = cx.update(|cx| {
// Project::local( Project::local(
// client, client,
// node_runtime::FakeNodeRuntime::new(), node_runtime::FakeNodeRuntime::new(),
// user_store, user_store,
// Arc::new(languages), Arc::new(languages),
// fs, fs,
// cx, cx,
// ) )
// }); });
// for path in root_paths { for path in root_paths {
// let (tree, _) = project let (tree, _) = project
// .update(cx, |project, cx| { .update(cx, |project, cx| {
// project.find_or_create_local_worktree(path, true, cx) project.find_or_create_local_worktree(path, true, cx)
// }) })
// .await .await
// .unwrap(); .unwrap();
// tree.read_with(cx, |tree, _| tree.as_local().unwrap().scan_complete()) tree.update(cx, |tree, _| tree.as_local().unwrap().scan_complete())
// .await; .await;
// } }
// project project
// } }
/// Enables a prettier mock that avoids interacting with node runtime, prettier LSP wrapper, or any real file changes. /// Enables a prettier mock that avoids interacting with node runtime, prettier LSP wrapper, or any real file changes.
/// Instead, if appends the suffix to every input, this suffix is returned by this method. /// Instead, if appends the suffix to every input, this suffix is returned by this method.
@ -5199,7 +5199,7 @@ impl Project {
fs.create_file( fs.create_file(
&abs_path, &abs_path,
op.options op.options
.map(|options| fs::CreateOptions { .map(|options| fs2::CreateOptions {
overwrite: options.overwrite.unwrap_or(false), overwrite: options.overwrite.unwrap_or(false),
ignore_if_exists: options.ignore_if_exists.unwrap_or(false), ignore_if_exists: options.ignore_if_exists.unwrap_or(false),
}) })
@ -5222,7 +5222,7 @@ impl Project {
&source_abs_path, &source_abs_path,
&target_abs_path, &target_abs_path,
op.options op.options
.map(|options| fs::RenameOptions { .map(|options| fs2::RenameOptions {
overwrite: options.overwrite.unwrap_or(false), overwrite: options.overwrite.unwrap_or(false),
ignore_if_exists: options.ignore_if_exists.unwrap_or(false), ignore_if_exists: options.ignore_if_exists.unwrap_or(false),
}) })
@ -5238,7 +5238,7 @@ impl Project {
.map_err(|_| anyhow!("can't convert URI to path"))?; .map_err(|_| anyhow!("can't convert URI to path"))?;
let options = op let options = op
.options .options
.map(|options| fs::RemoveOptions { .map(|options| fs2::RemoveOptions {
recursive: options.recursive.unwrap_or(false), recursive: options.recursive.unwrap_or(false),
ignore_if_not_exists: options.ignore_if_not_exists.unwrap_or(false), ignore_if_not_exists: options.ignore_if_not_exists.unwrap_or(false),
}) })

File diff suppressed because it is too large Load Diff

View File

@ -6,7 +6,7 @@ use anyhow::{anyhow, Context as _, Result};
use client2::{proto, Client}; use client2::{proto, Client};
use clock::ReplicaId; use clock::ReplicaId;
use collections::{HashMap, HashSet, VecDeque}; use collections::{HashMap, HashSet, VecDeque};
use fs::{ use fs2::{
repository::{GitFileStatus, GitRepository, RepoPath}, repository::{GitFileStatus, GitRepository, RepoPath},
Fs, Fs,
}; };
@ -2815,7 +2815,7 @@ pub type UpdatedGitRepositoriesSet = Arc<[(Arc<Path>, GitRepositoryChange)]>;
impl Entry { impl Entry {
fn new( fn new(
path: Arc<Path>, path: Arc<Path>,
metadata: &fs::Metadata, metadata: &fs2::Metadata,
next_entry_id: &AtomicUsize, next_entry_id: &AtomicUsize,
root_char_bag: CharBag, root_char_bag: CharBag,
) -> Self { ) -> Self {
@ -4030,53 +4030,52 @@ struct UpdateIgnoreStatusJob {
scan_queue: Sender<ScanJob>, scan_queue: Sender<ScanJob>,
} }
// todo!("re-enable when we have tests") pub trait WorktreeModelHandle {
// pub trait WorktreeModelHandle { #[cfg(any(test, feature = "test-support"))]
// #[cfg(any(test, feature = "test-support"))] fn flush_fs_events<'a>(
// fn flush_fs_events<'a>( &self,
// &self, cx: &'a mut gpui2::TestAppContext,
// cx: &'a gpui::TestAppContext, ) -> futures::future::LocalBoxFuture<'a, ()>;
// ) -> futures::future::LocalBoxFuture<'a, ()>; }
// }
// impl WorktreeModelHandle for Handle<Worktree> { impl WorktreeModelHandle for Handle<Worktree> {
// // When the worktree's FS event stream sometimes delivers "redundant" events for FS changes that // When the worktree's FS event stream sometimes delivers "redundant" events for FS changes that
// // occurred before the worktree was constructed. These events can cause the worktree to perform // occurred before the worktree was constructed. These events can cause the worktree to perform
// // extra directory scans, and emit extra scan-state notifications. // extra directory scans, and emit extra scan-state notifications.
// // //
// // This function mutates the worktree's directory and waits for those mutations to be picked up, // This function mutates the worktree's directory and waits for those mutations to be picked up,
// // to ensure that all redundant FS events have already been processed. // to ensure that all redundant FS events have already been processed.
// #[cfg(any(test, feature = "test-support"))] #[cfg(any(test, feature = "test-support"))]
// fn flush_fs_events<'a>( fn flush_fs_events<'a>(
// &self, &self,
// cx: &'a gpui::TestAppContext, cx: &'a mut gpui2::TestAppContext,
// ) -> futures::future::LocalBoxFuture<'a, ()> { ) -> futures::future::LocalBoxFuture<'a, ()> {
// let filename = "fs-event-sentinel"; let filename = "fs-event-sentinel";
// let tree = self.clone(); let tree = self.clone();
// let (fs, root_path) = self.read_with(cx, |tree, _| { let (fs, root_path) = self.update(cx, |tree, _| {
// let tree = tree.as_local().unwrap(); let tree = tree.as_local().unwrap();
// (tree.fs.clone(), tree.abs_path().clone()) (tree.fs.clone(), tree.abs_path().clone())
// }); });
// async move { async move {
// fs.create_file(&root_path.join(filename), Default::default()) fs.create_file(&root_path.join(filename), Default::default())
// .await .await
// .unwrap(); .unwrap();
// tree.condition(cx, |tree, _| tree.entry_for_path(filename).is_some()) cx.executor().run_until_parked();
// .await; assert!(tree.update(cx, |tree, _| tree.entry_for_path(filename).is_some()));
// fs.remove_file(&root_path.join(filename), Default::default()) fs.remove_file(&root_path.join(filename), Default::default())
// .await .await
// .unwrap(); .unwrap();
// tree.condition(cx, |tree, _| tree.entry_for_path(filename).is_none()) cx.executor().run_until_parked();
// .await; assert!(tree.update(cx, |tree, _| tree.entry_for_path(filename).is_none()));
// cx.read(|cx| tree.read(cx).as_local().unwrap().scan_complete()) cx.update(|cx| tree.read(cx).as_local().unwrap().scan_complete())
// .await; .await;
// } }
// .boxed_local() .boxed_local()
// } }
// } }
#[derive(Clone, Debug)] #[derive(Clone, Debug)]
struct TraversalProgress<'a> { struct TraversalProgress<'a> {