first pass of merging in warp (doesn't build)
This commit is contained in:
File diff suppressed because it is too large
Load Diff
+149
-27
@@ -3,32 +3,32 @@ use std::sync::Arc;
|
||||
use async_channel::Sender;
|
||||
use futures_util::stream::AbortHandle;
|
||||
use instant::Instant;
|
||||
|
||||
use galaxyui::{App, ModelHandle};
|
||||
use parking_lot::FairMutex;
|
||||
use session_sharing_protocol::{
|
||||
common::{
|
||||
ActivePrompt, OrderedTerminalEvent, OrderedTerminalEventType, ParticipantId, Selection,
|
||||
SessionId,
|
||||
},
|
||||
sharer::{DownstreamMessage, ReconnectToken, UpstreamMessage},
|
||||
use session_sharing_protocol::common::{
|
||||
ActivePrompt, OrderedTerminalEvent, OrderedTerminalEventType, ParticipantId, Selection,
|
||||
SessionId,
|
||||
};
|
||||
use session_sharing_protocol::sharer::{
|
||||
DownstreamMessage, FailedToInitializeSessionReason, QuotaType, ReconnectToken, UpstreamMessage,
|
||||
};
|
||||
use galaxy_server_client::iap::IapManager;
|
||||
use galaxyui::{App, ModelHandle};
|
||||
use websocket::{Message, WebsocketMessage as _};
|
||||
|
||||
use crate::{
|
||||
auth::{auth_manager::AuthManager, AuthStateProvider},
|
||||
editor::ReplicaId,
|
||||
server::{
|
||||
server_api::ServerApiProvider, telemetry::context_provider::AppTelemetryContextProvider,
|
||||
},
|
||||
terminal::{
|
||||
shared_session::{SharedSessionScrollbackType, MAX_BYTES_SHAREABLE},
|
||||
TerminalModel,
|
||||
},
|
||||
test_util::assert_eventually,
|
||||
use super::{
|
||||
startup_max_attempts, Network, PtyBytesBatchStatus, Stage, StartupFailure, StartupRetryState,
|
||||
AMBIENT_CREATE_SESSION_MAX_ATTEMPTS,
|
||||
};
|
||||
|
||||
use super::{Network, PtyBytesBatchStatus, Stage};
|
||||
use crate::auth::auth_manager::AuthManager;
|
||||
use crate::auth::AuthStateProvider;
|
||||
use crate::editor::ReplicaId;
|
||||
use crate::server::server_api::ServerApiProvider;
|
||||
use crate::server::telemetry::context_provider::AppTelemetryContextProvider;
|
||||
use crate::terminal::shared_session::{
|
||||
SharedSessionScrollbackType, SharedSessionSource, MAX_BYTES_SHAREABLE,
|
||||
};
|
||||
use crate::terminal::TerminalModel;
|
||||
use crate::test_util::assert_eventually;
|
||||
|
||||
fn is_upstream_message_pty_bytes_read(
|
||||
message: UpstreamMessage,
|
||||
@@ -42,6 +42,117 @@ fn is_upstream_message_pty_bytes_read(
|
||||
}) if event_no == expected_event_no && bytes == compressed_bytes)
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn test_startup_max_attempts_only_retries_ambient_agent_sources() {
|
||||
assert_eq!(
|
||||
startup_max_attempts(&SharedSessionSource::ambient_agent(Some(
|
||||
"task-id".to_string()
|
||||
))),
|
||||
AMBIENT_CREATE_SESSION_MAX_ATTEMPTS
|
||||
);
|
||||
assert_eq!(startup_max_attempts(&SharedSessionSource::user(None)), 1);
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn test_startup_failure_retryability() {
|
||||
assert!(StartupFailure::Transport.is_retryable());
|
||||
assert!(StartupFailure::InitializeSend.is_retryable());
|
||||
assert!(StartupFailure::WebsocketClosedBeforeStarted.is_retryable());
|
||||
assert!(StartupFailure::WebsocketError.is_retryable());
|
||||
assert!(StartupFailure::Timeout.is_retryable());
|
||||
assert!(
|
||||
StartupFailure::ServerRejected(FailedToInitializeSessionReason::InternalServerError {
|
||||
details: "transient".to_string(),
|
||||
})
|
||||
.is_retryable()
|
||||
);
|
||||
|
||||
assert!(!StartupFailure::ServerRejected(
|
||||
FailedToInitializeSessionReason::ScrollbackTooLarge {}
|
||||
)
|
||||
.is_retryable());
|
||||
assert!(!StartupFailure::ServerRejected(
|
||||
FailedToInitializeSessionReason::NoUserQuotaRemaining {
|
||||
quota_type: QuotaType::SessionsCreated,
|
||||
}
|
||||
)
|
||||
.is_retryable());
|
||||
assert!(
|
||||
!StartupFailure::ServerRejected(FailedToInitializeSessionReason::UserNotFound)
|
||||
.is_retryable()
|
||||
);
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn test_should_retry_startup_failure_respects_attempt_budget() {
|
||||
App::test((), |mut app| async move {
|
||||
let network = create_network(&mut app, false).0;
|
||||
|
||||
network.update(&mut app, |network, _| {
|
||||
network.stage = Stage::BeforeStarted {
|
||||
startup_retry: StartupRetryState {
|
||||
current_attempt: 1,
|
||||
max_attempts: AMBIENT_CREATE_SESSION_MAX_ATTEMPTS,
|
||||
timeout_abort_handle: None,
|
||||
transport_abort_handle: None,
|
||||
},
|
||||
};
|
||||
assert!(network.should_retry_startup_failure(&StartupFailure::Timeout));
|
||||
|
||||
network.stage = Stage::BeforeStarted {
|
||||
startup_retry: StartupRetryState {
|
||||
current_attempt: AMBIENT_CREATE_SESSION_MAX_ATTEMPTS,
|
||||
max_attempts: AMBIENT_CREATE_SESSION_MAX_ATTEMPTS,
|
||||
timeout_abort_handle: None,
|
||||
transport_abort_handle: None,
|
||||
},
|
||||
};
|
||||
assert!(!network.should_retry_startup_failure(&StartupFailure::Timeout));
|
||||
|
||||
let mut startup_retry = StartupRetryState::new(1);
|
||||
startup_retry.current_attempt = 1;
|
||||
network.stage = Stage::BeforeStarted { startup_retry };
|
||||
assert!(
|
||||
!network.should_retry_startup_failure(&StartupFailure::ServerRejected(
|
||||
FailedToInitializeSessionReason::InternalServerError {
|
||||
details: "transient".to_string(),
|
||||
}
|
||||
))
|
||||
);
|
||||
});
|
||||
});
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn test_startup_attempt_stale_filtering() {
|
||||
App::test((), |mut app| async move {
|
||||
let network = create_network(&mut app, false).0;
|
||||
|
||||
network.update(&mut app, |network, _| {
|
||||
network.stage = Stage::BeforeStarted {
|
||||
startup_retry: StartupRetryState {
|
||||
current_attempt: 1,
|
||||
max_attempts: AMBIENT_CREATE_SESSION_MAX_ATTEMPTS,
|
||||
timeout_abort_handle: None,
|
||||
transport_abort_handle: None,
|
||||
},
|
||||
};
|
||||
assert!(!network.should_ignore_startup_attempt_websocket_callback(1));
|
||||
assert!(network.should_ignore_startup_attempt_websocket_callback(0));
|
||||
network.stage = Stage::StartedSuccessfully {
|
||||
startup_attempt: Some(1),
|
||||
};
|
||||
assert!(!network.should_ignore_startup_attempt_websocket_callback(1));
|
||||
assert!(network.should_ignore_startup_attempt_websocket_callback(0));
|
||||
|
||||
network.stage = Stage::StartedSuccessfully {
|
||||
startup_attempt: None,
|
||||
};
|
||||
assert!(!network.should_ignore_startup_attempt_websocket_callback(0));
|
||||
});
|
||||
});
|
||||
}
|
||||
|
||||
fn is_upstream_message_command_executed(
|
||||
message: &UpstreamMessage,
|
||||
expected_event_no: usize,
|
||||
@@ -75,7 +186,9 @@ fn create_network(
|
||||
|
||||
if session_initialized {
|
||||
network.update(app, |network, _| {
|
||||
network.stage = Stage::StartedSuccessfully;
|
||||
network.stage = Stage::StartedSuccessfully {
|
||||
startup_attempt: None,
|
||||
};
|
||||
});
|
||||
}
|
||||
|
||||
@@ -473,7 +586,7 @@ fn test_messages_are_buffered_before_session_initialized() {
|
||||
// The network should start in the BeforeStarted state with no events.
|
||||
assert_eq!(ws_proxy_rx.len(), 0);
|
||||
network.read(&app, |network, _| {
|
||||
assert!(matches!(&network.stage, Stage::BeforeStarted));
|
||||
assert!(matches!(&network.stage, Stage::BeforeStarted { .. }));
|
||||
assert_eq!(network.unacked_terminal_events.len(), 0);
|
||||
});
|
||||
|
||||
@@ -494,7 +607,7 @@ fn test_messages_are_buffered_before_session_initialized() {
|
||||
// The message should not be sent to the server but should instead be buffered.
|
||||
assert_eq!(ws_proxy_rx.len(), 0);
|
||||
network.read(&app, |network, _| {
|
||||
assert!(matches!(&network.stage, Stage::BeforeStarted));
|
||||
assert!(matches!(&network.stage, Stage::BeforeStarted { .. }));
|
||||
assert!(is_upstream_message_command_executed(
|
||||
&UpstreamMessage::OrderedTerminalEvent(
|
||||
network.unacked_terminal_events.get(&0).unwrap().clone()
|
||||
@@ -525,7 +638,7 @@ fn test_messages_are_buffered_before_session_initialized() {
|
||||
matches!(item.unwrap(), UpstreamMessage::UpdateActivePrompt(_));
|
||||
|
||||
network.read(&app, |network, _| {
|
||||
assert!(matches!(&network.stage, Stage::StartedSuccessfully));
|
||||
assert!(matches!(&network.stage, Stage::StartedSuccessfully { .. }));
|
||||
});
|
||||
});
|
||||
}
|
||||
@@ -534,6 +647,15 @@ fn test_messages_are_buffered_before_session_initialized() {
|
||||
fn test_messages_are_buffered_while_reconnecting() {
|
||||
App::test((), |mut app| async move {
|
||||
app.add_singleton_model(|_| ServerApiProvider::new_for_test());
|
||||
// Disabled (`None`) IapManager so the reconnect path, which reads the
|
||||
// singleton, doesn't panic; inert no-op in tests.
|
||||
app.add_singleton_model(|ctx| {
|
||||
IapManager::new(
|
||||
None,
|
||||
Box::new(|_| futures::FutureExt::boxed(futures::future::ready(None::<String>))),
|
||||
ctx,
|
||||
)
|
||||
});
|
||||
app.add_singleton_model(|_| AuthStateProvider::new_for_test());
|
||||
app.add_singleton_model(AppTelemetryContextProvider::new_context_provider);
|
||||
app.add_singleton_model(AuthManager::new_for_test);
|
||||
@@ -543,7 +665,7 @@ fn test_messages_are_buffered_while_reconnecting() {
|
||||
// The network should start in the BeforeStarted state with no events.
|
||||
assert_eq!(ws_proxy_rx.len(), 0);
|
||||
network.read(&app, |network, _| {
|
||||
assert!(matches!(&network.stage, Stage::BeforeStarted));
|
||||
assert!(matches!(&network.stage, Stage::BeforeStarted { .. }));
|
||||
assert_eq!(network.unacked_terminal_events.len(), 0);
|
||||
});
|
||||
|
||||
@@ -620,7 +742,7 @@ fn test_messages_are_buffered_while_reconnecting() {
|
||||
matches!(item.unwrap(), UpstreamMessage::UpdateActivePrompt(_));
|
||||
|
||||
network.read(&app, |network, _| {
|
||||
assert!(matches!(&network.stage, Stage::StartedSuccessfully));
|
||||
assert!(matches!(&network.stage, Stage::StartedSuccessfully { .. }));
|
||||
});
|
||||
});
|
||||
}
|
||||
Reference in New Issue
Block a user