Add SystemClock (#8239)

This PR adds a `SystemClock` trait for abstracting away the system
clock.

This allows us to swap out the real system clock with a
`FakeSystemClock` in the tests, thus allowing the fake passage of time.

We're using this in `Telemetry` to better mock the clock for testing
purposes.

Release Notes:

- N/A
This commit is contained in:
Marshall Bowers 2024-02-22 22:28:08 -05:00 committed by GitHub
parent cc8e3c2286
commit 0de8672044
No known key found for this signature in database
GPG key ID: B5690EEEBB952194
16 changed files with 213 additions and 55 deletions

5
Cargo.lock generated
View file

@ -1911,6 +1911,7 @@ dependencies = [
"async-recursion 0.3.2", "async-recursion 0.3.2",
"async-tungstenite", "async-tungstenite",
"chrono", "chrono",
"clock",
"collections", "collections",
"db", "db",
"feature_flags", "feature_flags",
@ -1948,6 +1949,8 @@ dependencies = [
name = "clock" name = "clock"
version = "0.1.0" version = "0.1.0"
dependencies = [ dependencies = [
"chrono",
"parking_lot 0.11.2",
"smallvec", "smallvec",
] ]
@ -11732,6 +11735,7 @@ dependencies = [
"bincode", "bincode",
"call", "call",
"client", "client",
"clock",
"collections", "collections",
"db", "db",
"derive_more", "derive_more",
@ -11956,6 +11960,7 @@ dependencies = [
"chrono", "chrono",
"cli", "cli",
"client", "client",
"clock",
"collab_ui", "collab_ui",
"collections", "collections",
"command_palette", "command_palette",

View file

@ -2,6 +2,7 @@ use crate::channel_chat::ChannelChatEvent;
use super::*; use super::*;
use client::{test::FakeServer, Client, UserStore}; use client::{test::FakeServer, Client, UserStore};
use clock::FakeSystemClock;
use gpui::{AppContext, Context, Model, TestAppContext}; use gpui::{AppContext, Context, Model, TestAppContext};
use rpc::proto::{self}; use rpc::proto::{self};
use settings::SettingsStore; use settings::SettingsStore;
@ -337,8 +338,9 @@ fn init_test(cx: &mut AppContext) -> Model<ChannelStore> {
release_channel::init("0.0.0", cx); release_channel::init("0.0.0", cx);
client::init_settings(cx); client::init_settings(cx);
let clock = Arc::new(FakeSystemClock::default());
let http = FakeHttpClient::with_404_response(); let http = FakeHttpClient::with_404_response();
let client = Client::new(http.clone(), cx); let client = Client::new(clock, http.clone(), cx);
let user_store = cx.new_model(|cx| UserStore::new(client.clone(), cx)); let user_store = cx.new_model(|cx| UserStore::new(client.clone(), cx));
client::init(&client, cx); client::init(&client, cx);

View file

@ -10,10 +10,11 @@ path = "src/client.rs"
doctest = false doctest = false
[features] [features]
test-support = ["collections/test-support", "gpui/test-support", "rpc/test-support"] test-support = ["clock/test-support", "collections/test-support", "gpui/test-support", "rpc/test-support"]
[dependencies] [dependencies]
chrono = { version = "0.4", features = ["serde"] } chrono = { version = "0.4", features = ["serde"] }
clock.workspace = true
collections.workspace = true collections.workspace = true
db.workspace = true db.workspace = true
gpui.workspace = true gpui.workspace = true
@ -51,6 +52,7 @@ uuid.workspace = true
url.workspace = true url.workspace = true
[dev-dependencies] [dev-dependencies]
clock = { workspace = true, features = ["test-support"] }
collections = { workspace = true, features = ["test-support"] } collections = { workspace = true, features = ["test-support"] }
gpui = { workspace = true, features = ["test-support"] } gpui = { workspace = true, features = ["test-support"] }
rpc = { workspace = true, features = ["test-support"] } rpc = { workspace = true, features = ["test-support"] }

View file

@ -10,6 +10,7 @@ use async_tungstenite::tungstenite::{
error::Error as WebsocketError, error::Error as WebsocketError,
http::{Request, StatusCode}, http::{Request, StatusCode},
}; };
use clock::SystemClock;
use collections::HashMap; use collections::HashMap;
use futures::{ use futures::{
channel::oneshot, future::LocalBoxFuture, AsyncReadExt, FutureExt, SinkExt, StreamExt, channel::oneshot, future::LocalBoxFuture, AsyncReadExt, FutureExt, SinkExt, StreamExt,
@ -421,11 +422,15 @@ impl settings::Settings for TelemetrySettings {
} }
impl Client { impl Client {
pub fn new(http: Arc<ZedHttpClient>, cx: &mut AppContext) -> Arc<Self> { pub fn new(
clock: Arc<dyn SystemClock>,
http: Arc<ZedHttpClient>,
cx: &mut AppContext,
) -> Arc<Self> {
let client = Arc::new(Self { let client = Arc::new(Self {
id: AtomicU64::new(0), id: AtomicU64::new(0),
peer: Peer::new(0), peer: Peer::new(0),
telemetry: Telemetry::new(http.clone(), cx), telemetry: Telemetry::new(clock, http.clone(), cx),
http, http,
state: Default::default(), state: Default::default(),
@ -1455,6 +1460,7 @@ mod tests {
use super::*; use super::*;
use crate::test::FakeServer; use crate::test::FakeServer;
use clock::FakeSystemClock;
use gpui::{BackgroundExecutor, Context, TestAppContext}; use gpui::{BackgroundExecutor, Context, TestAppContext};
use parking_lot::Mutex; use parking_lot::Mutex;
use settings::SettingsStore; use settings::SettingsStore;
@ -1465,7 +1471,13 @@ mod tests {
async fn test_reconnection(cx: &mut TestAppContext) { async fn test_reconnection(cx: &mut TestAppContext) {
init_test(cx); init_test(cx);
let user_id = 5; let user_id = 5;
let client = cx.update(|cx| Client::new(FakeHttpClient::with_404_response(), cx)); let client = cx.update(|cx| {
Client::new(
Arc::new(FakeSystemClock::default()),
FakeHttpClient::with_404_response(),
cx,
)
});
let server = FakeServer::for_client(user_id, &client, cx).await; let server = FakeServer::for_client(user_id, &client, cx).await;
let mut status = client.status(); let mut status = client.status();
assert!(matches!( assert!(matches!(
@ -1500,7 +1512,13 @@ mod tests {
async fn test_connection_timeout(executor: BackgroundExecutor, cx: &mut TestAppContext) { async fn test_connection_timeout(executor: BackgroundExecutor, cx: &mut TestAppContext) {
init_test(cx); init_test(cx);
let user_id = 5; let user_id = 5;
let client = cx.update(|cx| Client::new(FakeHttpClient::with_404_response(), cx)); let client = cx.update(|cx| {
Client::new(
Arc::new(FakeSystemClock::default()),
FakeHttpClient::with_404_response(),
cx,
)
});
let mut status = client.status(); let mut status = client.status();
// Time out when client tries to connect. // Time out when client tries to connect.
@ -1573,7 +1591,13 @@ mod tests {
init_test(cx); init_test(cx);
let auth_count = Arc::new(Mutex::new(0)); let auth_count = Arc::new(Mutex::new(0));
let dropped_auth_count = Arc::new(Mutex::new(0)); let dropped_auth_count = Arc::new(Mutex::new(0));
let client = cx.update(|cx| Client::new(FakeHttpClient::with_404_response(), cx)); let client = cx.update(|cx| {
Client::new(
Arc::new(FakeSystemClock::default()),
FakeHttpClient::with_404_response(),
cx,
)
});
client.override_authenticate({ client.override_authenticate({
let auth_count = auth_count.clone(); let auth_count = auth_count.clone();
let dropped_auth_count = dropped_auth_count.clone(); let dropped_auth_count = dropped_auth_count.clone();
@ -1621,7 +1645,13 @@ mod tests {
async fn test_subscribing_to_entity(cx: &mut TestAppContext) { async fn test_subscribing_to_entity(cx: &mut TestAppContext) {
init_test(cx); init_test(cx);
let user_id = 5; let user_id = 5;
let client = cx.update(|cx| Client::new(FakeHttpClient::with_404_response(), cx)); let client = cx.update(|cx| {
Client::new(
Arc::new(FakeSystemClock::default()),
FakeHttpClient::with_404_response(),
cx,
)
});
let server = FakeServer::for_client(user_id, &client, cx).await; let server = FakeServer::for_client(user_id, &client, cx).await;
let (done_tx1, mut done_rx1) = smol::channel::unbounded(); let (done_tx1, mut done_rx1) = smol::channel::unbounded();
@ -1675,7 +1705,13 @@ mod tests {
async fn test_subscribing_after_dropping_subscription(cx: &mut TestAppContext) { async fn test_subscribing_after_dropping_subscription(cx: &mut TestAppContext) {
init_test(cx); init_test(cx);
let user_id = 5; let user_id = 5;
let client = cx.update(|cx| Client::new(FakeHttpClient::with_404_response(), cx)); let client = cx.update(|cx| {
Client::new(
Arc::new(FakeSystemClock::default()),
FakeHttpClient::with_404_response(),
cx,
)
});
let server = FakeServer::for_client(user_id, &client, cx).await; let server = FakeServer::for_client(user_id, &client, cx).await;
let model = cx.new_model(|_| TestModel::default()); let model = cx.new_model(|_| TestModel::default());
@ -1704,7 +1740,13 @@ mod tests {
async fn test_dropping_subscription_in_handler(cx: &mut TestAppContext) { async fn test_dropping_subscription_in_handler(cx: &mut TestAppContext) {
init_test(cx); init_test(cx);
let user_id = 5; let user_id = 5;
let client = cx.update(|cx| Client::new(FakeHttpClient::with_404_response(), cx)); let client = cx.update(|cx| {
Client::new(
Arc::new(FakeSystemClock::default()),
FakeHttpClient::with_404_response(),
cx,
)
});
let server = FakeServer::for_client(user_id, &client, cx).await; let server = FakeServer::for_client(user_id, &client, cx).await;
let model = cx.new_model(|_| TestModel::default()); let model = cx.new_model(|_| TestModel::default());

View file

@ -2,6 +2,7 @@ mod event_coalescer;
use crate::TelemetrySettings; use crate::TelemetrySettings;
use chrono::{DateTime, Utc}; use chrono::{DateTime, Utc};
use clock::SystemClock;
use futures::Future; use futures::Future;
use gpui::{AppContext, AppMetadata, BackgroundExecutor, Task}; use gpui::{AppContext, AppMetadata, BackgroundExecutor, Task};
use once_cell::sync::Lazy; use once_cell::sync::Lazy;
@ -24,6 +25,7 @@ use util::TryFutureExt;
use self::event_coalescer::EventCoalescer; use self::event_coalescer::EventCoalescer;
pub struct Telemetry { pub struct Telemetry {
clock: Arc<dyn SystemClock>,
http_client: Arc<ZedHttpClient>, http_client: Arc<ZedHttpClient>,
executor: BackgroundExecutor, executor: BackgroundExecutor,
state: Arc<Mutex<TelemetryState>>, state: Arc<Mutex<TelemetryState>>,
@ -156,7 +158,11 @@ static ZED_CLIENT_CHECKSUM_SEED: Lazy<Option<Vec<u8>>> = Lazy::new(|| {
}); });
impl Telemetry { impl Telemetry {
pub fn new(client: Arc<ZedHttpClient>, cx: &mut AppContext) -> Arc<Self> { pub fn new(
clock: Arc<dyn SystemClock>,
client: Arc<ZedHttpClient>,
cx: &mut AppContext,
) -> Arc<Self> {
let release_channel = let release_channel =
ReleaseChannel::try_global(cx).map(|release_channel| release_channel.display_name()); ReleaseChannel::try_global(cx).map(|release_channel| release_channel.display_name());
@ -205,6 +211,7 @@ impl Telemetry {
// TODO: Replace all hardware stuff with nested SystemSpecs json // TODO: Replace all hardware stuff with nested SystemSpecs json
let this = Arc::new(Self { let this = Arc::new(Self {
clock,
http_client: client, http_client: client,
executor: cx.background_executor().clone(), executor: cx.background_executor().clone(),
state, state,
@ -317,7 +324,8 @@ impl Telemetry {
operation, operation,
copilot_enabled, copilot_enabled,
copilot_enabled_for_language, copilot_enabled_for_language,
milliseconds_since_first_event: self.milliseconds_since_first_event(Utc::now()), milliseconds_since_first_event: self
.milliseconds_since_first_event(self.clock.utc_now()),
}; };
self.report_event(event) self.report_event(event)
@ -333,7 +341,8 @@ impl Telemetry {
suggestion_id, suggestion_id,
suggestion_accepted, suggestion_accepted,
file_extension, file_extension,
milliseconds_since_first_event: self.milliseconds_since_first_event(Utc::now()), milliseconds_since_first_event: self
.milliseconds_since_first_event(self.clock.utc_now()),
}; };
self.report_event(event) self.report_event(event)
@ -349,7 +358,8 @@ impl Telemetry {
conversation_id, conversation_id,
kind, kind,
model, model,
milliseconds_since_first_event: self.milliseconds_since_first_event(Utc::now()), milliseconds_since_first_event: self
.milliseconds_since_first_event(self.clock.utc_now()),
}; };
self.report_event(event) self.report_event(event)
@ -365,7 +375,8 @@ impl Telemetry {
operation, operation,
room_id, room_id,
channel_id, channel_id,
milliseconds_since_first_event: self.milliseconds_since_first_event(Utc::now()), milliseconds_since_first_event: self
.milliseconds_since_first_event(self.clock.utc_now()),
}; };
self.report_event(event) self.report_event(event)
@ -375,7 +386,8 @@ impl Telemetry {
let event = Event::Cpu { let event = Event::Cpu {
usage_as_percentage, usage_as_percentage,
core_count, core_count,
milliseconds_since_first_event: self.milliseconds_since_first_event(Utc::now()), milliseconds_since_first_event: self
.milliseconds_since_first_event(self.clock.utc_now()),
}; };
self.report_event(event) self.report_event(event)
@ -389,24 +401,18 @@ impl Telemetry {
let event = Event::Memory { let event = Event::Memory {
memory_in_bytes, memory_in_bytes,
virtual_memory_in_bytes, virtual_memory_in_bytes,
milliseconds_since_first_event: self.milliseconds_since_first_event(Utc::now()), milliseconds_since_first_event: self
.milliseconds_since_first_event(self.clock.utc_now()),
}; };
self.report_event(event) self.report_event(event)
} }
pub fn report_app_event(self: &Arc<Self>, operation: String) { pub fn report_app_event(self: &Arc<Self>, operation: String) -> Event {
self.report_app_event_with_date_time(operation, Utc::now());
}
fn report_app_event_with_date_time(
self: &Arc<Self>,
operation: String,
date_time: DateTime<Utc>,
) -> Event {
let event = Event::App { let event = Event::App {
operation, operation,
milliseconds_since_first_event: self.milliseconds_since_first_event(date_time), milliseconds_since_first_event: self
.milliseconds_since_first_event(self.clock.utc_now()),
}; };
self.report_event(event.clone()); self.report_event(event.clone());
@ -418,7 +424,8 @@ impl Telemetry {
let event = Event::Setting { let event = Event::Setting {
setting, setting,
value, value,
milliseconds_since_first_event: self.milliseconds_since_first_event(Utc::now()), milliseconds_since_first_event: self
.milliseconds_since_first_event(self.clock.utc_now()),
}; };
self.report_event(event) self.report_event(event)
@ -433,7 +440,8 @@ impl Telemetry {
let event = Event::Edit { let event = Event::Edit {
duration: end.timestamp_millis() - start.timestamp_millis(), duration: end.timestamp_millis() - start.timestamp_millis(),
environment, environment,
milliseconds_since_first_event: self.milliseconds_since_first_event(Utc::now()), milliseconds_since_first_event: self
.milliseconds_since_first_event(self.clock.utc_now()),
}; };
self.report_event(event); self.report_event(event);
@ -444,7 +452,8 @@ impl Telemetry {
let event = Event::Action { let event = Event::Action {
source, source,
action, action,
milliseconds_since_first_event: self.milliseconds_since_first_event(Utc::now()), milliseconds_since_first_event: self
.milliseconds_since_first_event(self.clock.utc_now()),
}; };
self.report_event(event) self.report_event(event)
@ -590,29 +599,32 @@ impl Telemetry {
mod tests { mod tests {
use super::*; use super::*;
use chrono::TimeZone; use chrono::TimeZone;
use clock::FakeSystemClock;
use gpui::TestAppContext; use gpui::TestAppContext;
use util::http::FakeHttpClient; use util::http::FakeHttpClient;
#[gpui::test] #[gpui::test]
fn test_telemetry_flush_on_max_queue_size(cx: &mut TestAppContext) { fn test_telemetry_flush_on_max_queue_size(cx: &mut TestAppContext) {
init_test(cx); init_test(cx);
let clock = Arc::new(FakeSystemClock::new(
Utc.with_ymd_and_hms(1990, 4, 12, 12, 0, 0).unwrap(),
));
let http = FakeHttpClient::with_200_response(); let http = FakeHttpClient::with_200_response();
let installation_id = Some("installation_id".to_string()); let installation_id = Some("installation_id".to_string());
let session_id = "session_id".to_string(); let session_id = "session_id".to_string();
cx.update(|cx| { cx.update(|cx| {
let telemetry = Telemetry::new(http, cx); let telemetry = Telemetry::new(clock.clone(), http, cx);
telemetry.state.lock().max_queue_size = 4; telemetry.state.lock().max_queue_size = 4;
telemetry.start(installation_id, session_id, cx); telemetry.start(installation_id, session_id, cx);
assert!(is_empty_state(&telemetry)); assert!(is_empty_state(&telemetry));
let first_date_time = Utc.with_ymd_and_hms(1990, 4, 12, 12, 0, 0).unwrap(); let first_date_time = clock.utc_now();
let operation = "test".to_string(); let operation = "test".to_string();
let event = let event = telemetry.report_app_event(operation.clone());
telemetry.report_app_event_with_date_time(operation.clone(), first_date_time);
assert_eq!( assert_eq!(
event, event,
Event::App { Event::App {
@ -627,9 +639,9 @@ mod tests {
Some(first_date_time) Some(first_date_time)
); );
let mut date_time = first_date_time + chrono::Duration::milliseconds(100); clock.advance(chrono::Duration::milliseconds(100));
let event = telemetry.report_app_event_with_date_time(operation.clone(), date_time); let event = telemetry.report_app_event(operation.clone());
assert_eq!( assert_eq!(
event, event,
Event::App { Event::App {
@ -644,9 +656,9 @@ mod tests {
Some(first_date_time) Some(first_date_time)
); );
date_time += chrono::Duration::milliseconds(100); clock.advance(chrono::Duration::milliseconds(100));
let event = telemetry.report_app_event_with_date_time(operation.clone(), date_time); let event = telemetry.report_app_event(operation.clone());
assert_eq!( assert_eq!(
event, event,
Event::App { Event::App {
@ -661,10 +673,10 @@ mod tests {
Some(first_date_time) Some(first_date_time)
); );
date_time += chrono::Duration::milliseconds(100); clock.advance(chrono::Duration::milliseconds(100));
// Adding a 4th event should cause a flush // Adding a 4th event should cause a flush
let event = telemetry.report_app_event_with_date_time(operation.clone(), date_time); let event = telemetry.report_app_event(operation.clone());
assert_eq!( assert_eq!(
event, event,
Event::App { Event::App {
@ -680,22 +692,24 @@ mod tests {
#[gpui::test] #[gpui::test]
async fn test_connection_timeout(executor: BackgroundExecutor, cx: &mut TestAppContext) { async fn test_connection_timeout(executor: BackgroundExecutor, cx: &mut TestAppContext) {
init_test(cx); init_test(cx);
let clock = Arc::new(FakeSystemClock::new(
Utc.with_ymd_and_hms(1990, 4, 12, 12, 0, 0).unwrap(),
));
let http = FakeHttpClient::with_200_response(); let http = FakeHttpClient::with_200_response();
let installation_id = Some("installation_id".to_string()); let installation_id = Some("installation_id".to_string());
let session_id = "session_id".to_string(); let session_id = "session_id".to_string();
cx.update(|cx| { cx.update(|cx| {
let telemetry = Telemetry::new(http, cx); let telemetry = Telemetry::new(clock.clone(), http, cx);
telemetry.state.lock().max_queue_size = 4; telemetry.state.lock().max_queue_size = 4;
telemetry.start(installation_id, session_id, cx); telemetry.start(installation_id, session_id, cx);
assert!(is_empty_state(&telemetry)); assert!(is_empty_state(&telemetry));
let first_date_time = Utc.with_ymd_and_hms(1990, 4, 12, 12, 0, 0).unwrap(); let first_date_time = clock.utc_now();
let operation = "test".to_string(); let operation = "test".to_string();
let event = let event = telemetry.report_app_event(operation.clone());
telemetry.report_app_event_with_date_time(operation.clone(), first_date_time);
assert_eq!( assert_eq!(
event, event,
Event::App { Event::App {

View file

@ -9,5 +9,10 @@ license = "GPL-3.0-or-later"
path = "src/clock.rs" path = "src/clock.rs"
doctest = false doctest = false
[features]
test-support = ["dep:parking_lot"]
[dependencies] [dependencies]
chrono.workspace = true
parking_lot = { workspace = true, optional = true }
smallvec.workspace = true smallvec.workspace = true

View file

@ -1,13 +1,17 @@
mod system_clock;
use smallvec::SmallVec; use smallvec::SmallVec;
use std::{ use std::{
cmp::{self, Ordering}, cmp::{self, Ordering},
fmt, iter, fmt, iter,
}; };
/// A unique identifier for each distributed node pub use system_clock::*;
/// A unique identifier for each distributed node.
pub type ReplicaId = u16; pub type ReplicaId = u16;
/// A [Lamport sequence number](https://en.wikipedia.org/wiki/Lamport_timestamp), /// A [Lamport sequence number](https://en.wikipedia.org/wiki/Lamport_timestamp).
pub type Seq = u32; pub type Seq = u32;
/// A [Lamport timestamp](https://en.wikipedia.org/wiki/Lamport_timestamp), /// A [Lamport timestamp](https://en.wikipedia.org/wiki/Lamport_timestamp),
@ -18,7 +22,7 @@ pub struct Lamport {
pub value: Seq, pub value: Seq,
} }
/// A [vector clock](https://en.wikipedia.org/wiki/Vector_clock) /// A [vector clock](https://en.wikipedia.org/wiki/Vector_clock).
#[derive(Clone, Default, Hash, Eq, PartialEq)] #[derive(Clone, Default, Hash, Eq, PartialEq)]
pub struct Global(SmallVec<[u32; 8]>); pub struct Global(SmallVec<[u32; 8]>);

View file

@ -0,0 +1,59 @@
use chrono::{DateTime, Utc};
pub trait SystemClock: Send + Sync {
/// Returns the current date and time in UTC.
fn utc_now(&self) -> DateTime<Utc>;
}
pub struct RealSystemClock;
impl SystemClock for RealSystemClock {
fn utc_now(&self) -> DateTime<Utc> {
Utc::now()
}
}
#[cfg(any(test, feature = "test-support"))]
pub struct FakeSystemClockState {
now: DateTime<Utc>,
}
#[cfg(any(test, feature = "test-support"))]
pub struct FakeSystemClock {
// Use an unfair lock to ensure tests are deterministic.
state: parking_lot::Mutex<FakeSystemClockState>,
}
#[cfg(any(test, feature = "test-support"))]
impl Default for FakeSystemClock {
fn default() -> Self {
Self::new(Utc::now())
}
}
#[cfg(any(test, feature = "test-support"))]
impl FakeSystemClock {
pub fn new(now: DateTime<Utc>) -> Self {
let state = FakeSystemClockState { now };
Self {
state: parking_lot::Mutex::new(state),
}
}
pub fn set_now(&self, now: DateTime<Utc>) {
self.state.lock().now = now;
}
/// Advances the [`FakeSystemClock`] by the specified [`Duration`](chrono::Duration).
pub fn advance(&self, duration: chrono::Duration) {
self.state.lock().now += duration;
}
}
#[cfg(any(test, feature = "test-support"))]
impl SystemClock for FakeSystemClock {
fn utc_now(&self) -> DateTime<Utc> {
self.state.lock().now
}
}

View file

@ -10,6 +10,7 @@ use channel::{ChannelBuffer, ChannelStore};
use client::{ use client::{
self, proto::PeerId, Client, Connection, Credentials, EstablishConnectionError, UserStore, self, proto::PeerId, Client, Connection, Credentials, EstablishConnectionError, UserStore,
}; };
use clock::FakeSystemClock;
use collab_ui::channel_view::ChannelView; use collab_ui::channel_view::ChannelView;
use collections::{HashMap, HashSet}; use collections::{HashMap, HashSet};
use fs::FakeFs; use fs::FakeFs;
@ -163,6 +164,7 @@ impl TestServer {
client::init_settings(cx); client::init_settings(cx);
}); });
let clock = Arc::new(FakeSystemClock::default());
let http = FakeHttpClient::with_404_response(); let http = FakeHttpClient::with_404_response();
let user_id = if let Ok(Some(user)) = self.app_state.db.get_user_by_github_login(name).await let user_id = if let Ok(Some(user)) = self.app_state.db.get_user_by_github_login(name).await
{ {
@ -185,7 +187,7 @@ impl TestServer {
.user_id .user_id
}; };
let client_name = name.to_string(); let client_name = name.to_string();
let mut client = cx.update(|cx| Client::new(http.clone(), cx)); let mut client = cx.update(|cx| Client::new(clock, http.clone(), cx));
let server = self.server.clone(); let server = self.server.clone();
let db = self.app_state.db.clone(); let db = self.app_state.db.clone();
let connection_killers = self.connection_killers.clone(); let connection_killers = self.connection_killers.clone();

View file

@ -385,6 +385,7 @@ impl Render for MessageEditor {
mod tests { mod tests {
use super::*; use super::*;
use client::{Client, User, UserStore}; use client::{Client, User, UserStore};
use clock::FakeSystemClock;
use gpui::TestAppContext; use gpui::TestAppContext;
use language::{Language, LanguageConfig}; use language::{Language, LanguageConfig};
use rpc::proto; use rpc::proto;
@ -455,8 +456,9 @@ mod tests {
let settings = SettingsStore::test(cx); let settings = SettingsStore::test(cx);
cx.set_global(settings); cx.set_global(settings);
let clock = Arc::new(FakeSystemClock::default());
let http = FakeHttpClient::with_404_response(); let http = FakeHttpClient::with_404_response();
let client = Client::new(http.clone(), cx); let client = Client::new(clock, http.clone(), cx);
let user_store = cx.new_model(|cx| UserStore::new(client.clone(), cx)); let user_store = cx.new_model(|cx| UserStore::new(client.clone(), cx));
theme::init(theme::LoadThemes::JustBase, cx); theme::init(theme::LoadThemes::JustBase, cx);
language::init(cx); language::init(cx);

View file

@ -853,10 +853,13 @@ impl Project {
root_paths: impl IntoIterator<Item = &Path>, root_paths: impl IntoIterator<Item = &Path>,
cx: &mut gpui::TestAppContext, cx: &mut gpui::TestAppContext,
) -> Model<Project> { ) -> Model<Project> {
use clock::FakeSystemClock;
let mut languages = LanguageRegistry::test(); let mut languages = LanguageRegistry::test();
languages.set_executor(cx.executor()); languages.set_executor(cx.executor());
let clock = Arc::new(FakeSystemClock::default());
let http_client = util::http::FakeHttpClient::with_404_response(); let http_client = util::http::FakeHttpClient::with_404_response();
let client = cx.update(|cx| client::Client::new(http_client.clone(), cx)); let client = cx.update(|cx| client::Client::new(clock, http_client.clone(), cx));
let user_store = cx.new_model(|cx| UserStore::new(client.clone(), cx)); let user_store = cx.new_model(|cx| UserStore::new(client.clone(), cx));
let project = cx.update(|cx| { let project = cx.update(|cx| {
Project::local( Project::local(

View file

@ -5,6 +5,7 @@ use crate::{
}; };
use anyhow::Result; use anyhow::Result;
use client::Client; use client::Client;
use clock::FakeSystemClock;
use fs::{repository::GitFileStatus, FakeFs, Fs, RealFs, RemoveOptions}; use fs::{repository::GitFileStatus, FakeFs, Fs, RealFs, RemoveOptions};
use git::GITIGNORE; use git::GITIGNORE;
use gpui::{ModelContext, Task, TestAppContext}; use gpui::{ModelContext, Task, TestAppContext};
@ -1263,7 +1264,13 @@ async fn test_create_directory_during_initial_scan(cx: &mut TestAppContext) {
async fn test_create_dir_all_on_create_entry(cx: &mut TestAppContext) { async fn test_create_dir_all_on_create_entry(cx: &mut TestAppContext) {
init_test(cx); init_test(cx);
cx.executor().allow_parking(); cx.executor().allow_parking();
let client_fake = cx.update(|cx| Client::new(FakeHttpClient::with_404_response(), cx)); let client_fake = cx.update(|cx| {
Client::new(
Arc::new(FakeSystemClock::default()),
FakeHttpClient::with_404_response(),
cx,
)
});
let fs_fake = FakeFs::new(cx.background_executor.clone()); let fs_fake = FakeFs::new(cx.background_executor.clone());
fs_fake fs_fake
@ -1304,7 +1311,13 @@ async fn test_create_dir_all_on_create_entry(cx: &mut TestAppContext) {
assert!(tree.entry_for_path("a/b/").unwrap().is_dir()); assert!(tree.entry_for_path("a/b/").unwrap().is_dir());
}); });
let client_real = cx.update(|cx| Client::new(FakeHttpClient::with_404_response(), cx)); let client_real = cx.update(|cx| {
Client::new(
Arc::new(FakeSystemClock::default()),
FakeHttpClient::with_404_response(),
cx,
)
});
let fs_real = Arc::new(RealFs); let fs_real = Arc::new(RealFs);
let temp_root = temp_tree(json!({ let temp_root = temp_tree(json!({
@ -2396,8 +2409,9 @@ async fn test_propagate_git_statuses(cx: &mut TestAppContext) {
} }
fn build_client(cx: &mut TestAppContext) -> Arc<Client> { fn build_client(cx: &mut TestAppContext) -> Arc<Client> {
let clock = Arc::new(FakeSystemClock::default());
let http_client = FakeHttpClient::with_404_response(); let http_client = FakeHttpClient::with_404_response();
cx.update(|cx| Client::new(http_client, cx)) cx.update(|cx| Client::new(clock, http_client, cx))
} }
#[track_caller] #[track_caller]

View file

@ -25,6 +25,7 @@ async-recursion = "1.0.0"
bincode = "1.2.1" bincode = "1.2.1"
call.workspace = true call.workspace = true
client.workspace = true client.workspace = true
clock.workspace = true
collections.workspace = true collections.workspace = true
db.workspace = true db.workspace = true
derive_more.workspace = true derive_more.workspace = true

View file

@ -397,8 +397,9 @@ impl AppState {
let fs = fs::FakeFs::new(cx.background_executor().clone()); let fs = fs::FakeFs::new(cx.background_executor().clone());
let languages = Arc::new(LanguageRegistry::test()); let languages = Arc::new(LanguageRegistry::test());
let clock = Arc::new(clock::FakeSystemClock::default());
let http_client = util::http::FakeHttpClient::with_404_response(); let http_client = util::http::FakeHttpClient::with_404_response();
let client = Client::new(http_client.clone(), cx); let client = Client::new(clock, http_client.clone(), cx);
let user_store = cx.new_model(|cx| UserStore::new(client.clone(), cx)); let user_store = cx.new_model(|cx| UserStore::new(client.clone(), cx));
let workspace_store = cx.new_model(|cx| WorkspaceStore::new(client.clone(), cx)); let workspace_store = cx.new_model(|cx| WorkspaceStore::new(client.clone(), cx));

View file

@ -34,6 +34,7 @@ channel.workspace = true
chrono = "0.4" chrono = "0.4"
cli.workspace = true cli.workspace = true
client.workspace = true client.workspace = true
clock.workspace = true
collab_ui.workspace = true collab_ui.workspace = true
collections.workspace = true collections.workspace = true
command_palette.workspace = true command_palette.workspace = true

View file

@ -140,9 +140,10 @@ fn main() {
handle_keymap_file_changes(user_keymap_file_rx, cx); handle_keymap_file_changes(user_keymap_file_rx, cx);
client::init_settings(cx); client::init_settings(cx);
let clock = Arc::new(clock::RealSystemClock);
let http = http::zed_client(&client::ClientSettings::get_global(cx).server_url); let http = http::zed_client(&client::ClientSettings::get_global(cx).server_url);
let client = client::Client::new(http.clone(), cx); let client = client::Client::new(clock, http.clone(), cx);
let mut languages = LanguageRegistry::new(login_shell_env_loaded); let mut languages = LanguageRegistry::new(login_shell_env_loaded);
let copilot_language_server_id = languages.next_language_server_id(); let copilot_language_server_id = languages.next_language_server_id();
languages.set_executor(cx.background_executor().clone()); languages.set_executor(cx.background_executor().clone());