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:
parent
cc8e3c2286
commit
0de8672044
16 changed files with 213 additions and 55 deletions
5
Cargo.lock
generated
5
Cargo.lock
generated
|
@ -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",
|
||||||
|
|
|
@ -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);
|
||||||
|
|
|
@ -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"] }
|
||||||
|
|
|
@ -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());
|
||||||
|
|
|
@ -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 {
|
||||||
|
|
|
@ -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
|
||||||
|
|
|
@ -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]>);
|
||||||
|
|
||||||
|
|
59
crates/clock/src/system_clock.rs
Normal file
59
crates/clock/src/system_clock.rs
Normal 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
|
||||||
|
}
|
||||||
|
}
|
|
@ -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();
|
||||||
|
|
|
@ -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);
|
||||||
|
|
|
@ -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(
|
||||||
|
|
|
@ -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]
|
||||||
|
|
|
@ -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
|
||||||
|
|
|
@ -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));
|
||||||
|
|
||||||
|
|
|
@ -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
|
||||||
|
|
|
@ -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());
|
||||||
|
|
Loading…
Add table
Add a link
Reference in a new issue