mirror of
https://github.com/hanzoai/dev.git
synced 2026-10-25 03:32:32 +00:00
Our history is just-every's with author emails rewritten, so the graphs are
disjoint and git finds no merge base. Supplied one by hand: d1f1f51a41, the
just-every commit matching our Oct-2025 fork point (22 files apart). 9618
upstream commits, 7004 files, +1.81M/-159k, 37 conflicts.
Resolution:
- Upstream code where our side was the Oct-2025 prototype of a feature they
have since reimplemented 3-5x larger (agent_runs, browser_sessions,
history_cell/{agent,browser}, the vt100 harness and its snapshots).
- Upstream's config.rs module split (builder/defaults/sources/validation)
kept; zen5-coder and the provider-follows-model rule re-applied on top.
- Ours where the file IS the identity: README, package.json, postinstall,
release notes pipeline, integration-zed.
- Hybrid where upstream improved a file we had branded: updates.rs (their
release-repo migration, our hanzoai/dev), release.yml (their fail-fast npm
auth check, our package name), chatwidget.rs (their turn handling, our
HANZO_USER_KEY hint), exec human output (their 237 lines, our banner).
Superseded and dropped deliberately: our resume_command_name() coder-alias
hack — upstream's derive_resume_command_name() reads the invocation basename
generically, which yields "dev" for us.
LLM.md now records the real topology and the sync point; AGENTS.md and
CLAUDE.md are symlinks to it again. Single parent on purpose: adopting
jecode/main as a second parent would re-import 13,742 commits carrying the
upstream author emails a prior rewrite deliberately removed.
Version 0.2.188 -> 0.2.189.
Co-authored-by: Hanzo Dev <[email protected]>
588 lines
22 KiB
Rust
588 lines
22 KiB
Rust
use std::sync::Arc;
|
|
use std::sync::atomic::AtomicUsize;
|
|
use std::sync::atomic::Ordering;
|
|
|
|
use codex_exec_server::EnvironmentReadyInfo;
|
|
use codex_exec_server::ExecServerError;
|
|
use codex_exec_server::NoiseChannelPublicKey;
|
|
use codex_exec_server::NoiseRendezvousConnectBundle;
|
|
use codex_exec_server::NoiseRendezvousConnectProvider;
|
|
use codex_exec_server_test_support::environment_manager_without_environments;
|
|
use codex_protocol::capabilities::CapabilityRootLocation;
|
|
use codex_protocol::capabilities::SelectedCapabilityRoot;
|
|
use codex_utils_path_uri::PathUri;
|
|
use futures::FutureExt;
|
|
use futures::future::BoxFuture;
|
|
use futures::poll;
|
|
use pretty_assertions::assert_eq;
|
|
|
|
#[derive(Default)]
|
|
struct FailingNoiseConnectProvider {
|
|
calls: AtomicUsize,
|
|
}
|
|
|
|
impl FailingNoiseConnectProvider {
|
|
fn calls(&self) -> usize {
|
|
self.calls.load(Ordering::Relaxed)
|
|
}
|
|
}
|
|
|
|
impl NoiseRendezvousConnectProvider for FailingNoiseConnectProvider {
|
|
fn connect_bundle(
|
|
&self,
|
|
_: NoiseChannelPublicKey,
|
|
) -> BoxFuture<'_, Result<NoiseRendezvousConnectBundle, ExecServerError>> {
|
|
self.calls.fetch_add(1, Ordering::Relaxed);
|
|
async {
|
|
Err(ExecServerError::Protocol(
|
|
"test Noise provider called".to_string(),
|
|
))
|
|
}
|
|
.boxed()
|
|
}
|
|
}
|
|
|
|
fn ready_info(root_id: &str, environment_id: &str) -> anyhow::Result<EnvironmentReadyInfo> {
|
|
Ok(EnvironmentReadyInfo {
|
|
selected_capability_roots: vec![SelectedCapabilityRoot {
|
|
id: root_id.to_string(),
|
|
location: CapabilityRootLocation::Environment {
|
|
environment_id: environment_id.to_string(),
|
|
path: PathUri::parse("file:///plugins/root")?,
|
|
},
|
|
}],
|
|
})
|
|
}
|
|
|
|
#[tokio::test]
|
|
async fn deferred_environment_waits_before_connecting() -> anyhow::Result<()> {
|
|
let manager = environment_manager_without_environments();
|
|
let provider = Arc::new(FailingNoiseConnectProvider::default());
|
|
let registration =
|
|
manager.register_deferred_noise_environment("tools".to_string(), provider.clone())?;
|
|
let environment = manager.get_environment("tools").expect("environment");
|
|
let connection_state = environment
|
|
.subscribe_connection_state()
|
|
.expect("remote environment connection state");
|
|
let mut readiness = Box::pin(environment.wait_until_ready());
|
|
|
|
assert!(poll!(&mut readiness).is_pending());
|
|
assert_eq!(provider.calls(), 0);
|
|
assert!(environment.selected_capability_roots().is_empty());
|
|
|
|
let ready_info = ready_info("selected-root", "tools")?;
|
|
registration.complete(Ok(ready_info.clone()))?;
|
|
assert_eq!(
|
|
environment.selected_capability_roots(),
|
|
ready_info.selected_capability_roots
|
|
);
|
|
let error = readiness.await.unwrap_err();
|
|
assert!(error.to_string().contains("test Noise provider called"));
|
|
assert_eq!(provider.calls(), 1);
|
|
assert!(!connection_state.has_changed()?);
|
|
Ok(())
|
|
}
|
|
|
|
#[tokio::test]
|
|
async fn deferred_registration_replaces_an_ordinary_noise_environment() -> anyhow::Result<()> {
|
|
let manager = environment_manager_without_environments();
|
|
manager.upsert_noise_environment(
|
|
"tools".to_string(),
|
|
Arc::new(FailingNoiseConnectProvider::default()),
|
|
)?;
|
|
let ordinary = manager
|
|
.get_environment("tools")
|
|
.expect("ordinary environment");
|
|
let deferred_provider = Arc::new(FailingNoiseConnectProvider::default());
|
|
|
|
let registration = manager
|
|
.register_deferred_noise_environment("tools".to_string(), deferred_provider.clone())?;
|
|
let deferred = manager
|
|
.get_environment("tools")
|
|
.expect("deferred environment");
|
|
assert!(!Arc::ptr_eq(&ordinary, &deferred));
|
|
let mut readiness = Box::pin(deferred.wait_until_ready());
|
|
assert!(poll!(&mut readiness).is_pending());
|
|
|
|
registration.complete(Ok(ready_info("selected-root", "tools")?))?;
|
|
let error = readiness.await.unwrap_err();
|
|
assert!(error.to_string().contains("test Noise provider called"));
|
|
assert_eq!(deferred_provider.calls(), 1);
|
|
Ok(())
|
|
}
|
|
|
|
#[tokio::test]
|
|
async fn ordinary_noise_environment_replaces_a_deferred_registration() -> anyhow::Result<()> {
|
|
let manager = environment_manager_without_environments();
|
|
let deferred_provider = Arc::new(FailingNoiseConnectProvider::default());
|
|
let registration = manager
|
|
.register_deferred_noise_environment("tools".to_string(), deferred_provider.clone())?;
|
|
let deferred = manager
|
|
.get_environment("tools")
|
|
.expect("deferred environment");
|
|
let ordinary_provider = Arc::new(FailingNoiseConnectProvider::default());
|
|
|
|
manager.upsert_noise_environment("tools".to_string(), ordinary_provider.clone())?;
|
|
let ordinary = manager
|
|
.get_environment("tools")
|
|
.expect("ordinary environment");
|
|
assert!(!Arc::ptr_eq(&deferred, &ordinary));
|
|
|
|
drop(registration);
|
|
let error = deferred.wait_until_ready().await.unwrap_err();
|
|
assert!(
|
|
error
|
|
.to_string()
|
|
.contains("registration ended before completion")
|
|
);
|
|
assert_eq!(deferred_provider.calls(), 0);
|
|
let error = ordinary.wait_until_ready().await.unwrap_err();
|
|
assert!(error.to_string().contains("test Noise provider called"));
|
|
assert_eq!(ordinary_provider.calls(), 1);
|
|
Ok(())
|
|
}
|
|
|
|
#[tokio::test]
|
|
async fn existing_environment_publishes_readiness_without_replacement() -> anyhow::Result<()> {
|
|
let manager = environment_manager_without_environments();
|
|
let existing_provider = Arc::new(FailingNoiseConnectProvider::default());
|
|
manager.upsert_noise_environment("tools".to_string(), existing_provider)?;
|
|
let existing_environment = manager
|
|
.get_environment("tools")
|
|
.expect("existing environment");
|
|
|
|
let ready_info = ready_info("selected-root", "tools")?;
|
|
manager.publish_ready_info("tools", ready_info.clone())?;
|
|
|
|
let current_environment = manager
|
|
.get_environment("tools")
|
|
.expect("current environment");
|
|
assert!(Arc::ptr_eq(&existing_environment, ¤t_environment));
|
|
assert_eq!(
|
|
existing_environment.selected_capability_roots(),
|
|
ready_info.selected_capability_roots
|
|
);
|
|
Ok(())
|
|
}
|
|
|
|
#[tokio::test]
|
|
async fn readiness_updates_the_current_environment_after_replacement() -> anyhow::Result<()> {
|
|
let manager = environment_manager_without_environments();
|
|
manager.upsert_noise_environment(
|
|
"tools".to_string(),
|
|
Arc::new(FailingNoiseConnectProvider::default()),
|
|
)?;
|
|
let captured_environment = manager
|
|
.get_environment("tools")
|
|
.expect("captured environment");
|
|
manager.upsert_noise_environment(
|
|
"tools".to_string(),
|
|
Arc::new(FailingNoiseConnectProvider::default()),
|
|
)?;
|
|
let current_environment = manager
|
|
.get_environment("tools")
|
|
.expect("replacement environment");
|
|
let ready_info = ready_info("selected-root", "tools")?;
|
|
|
|
manager.publish_ready_info("tools", ready_info.clone())?;
|
|
|
|
assert!(!Arc::ptr_eq(&captured_environment, ¤t_environment));
|
|
assert!(captured_environment.selected_capability_roots().is_empty());
|
|
assert_eq!(
|
|
current_environment.selected_capability_roots(),
|
|
ready_info.selected_capability_roots
|
|
);
|
|
Ok(())
|
|
}
|
|
|
|
#[tokio::test]
|
|
async fn publishing_readiness_requires_existing_environment() -> anyhow::Result<()> {
|
|
let manager = environment_manager_without_environments();
|
|
|
|
let error = manager
|
|
.publish_ready_info("tools", ready_info("selected-root", "tools")?)
|
|
.unwrap_err();
|
|
|
|
assert!(matches!(error, ExecServerError::Protocol(_)));
|
|
assert!(manager.get_environment("tools").is_none());
|
|
Ok(())
|
|
}
|
|
|
|
#[tokio::test]
|
|
async fn existing_environment_accepts_matching_readiness() -> anyhow::Result<()> {
|
|
let manager = environment_manager_without_environments();
|
|
manager.upsert_noise_environment(
|
|
"tools".to_string(),
|
|
Arc::new(FailingNoiseConnectProvider::default()),
|
|
)?;
|
|
let environment = manager.get_environment("tools").expect("environment");
|
|
let ready_info = ready_info("selected-root", "tools")?;
|
|
|
|
for _ in 0..2 {
|
|
manager.publish_ready_info("tools", ready_info.clone())?;
|
|
}
|
|
assert_eq!(
|
|
environment.selected_capability_roots(),
|
|
ready_info.selected_capability_roots
|
|
);
|
|
Ok(())
|
|
}
|
|
|
|
#[tokio::test]
|
|
async fn existing_environment_overwrites_published_readiness() -> anyhow::Result<()> {
|
|
let manager = environment_manager_without_environments();
|
|
manager.upsert_noise_environment(
|
|
"tools".to_string(),
|
|
Arc::new(FailingNoiseConnectProvider::default()),
|
|
)?;
|
|
let environment = manager.get_environment("tools").expect("environment");
|
|
let selected_ready_info = ready_info("selected-root", "tools")?;
|
|
manager.publish_ready_info("tools", selected_ready_info)?;
|
|
|
|
let updated_ready_info = ready_info("different-root", "tools")?;
|
|
manager.publish_ready_info("tools", updated_ready_info.clone())?;
|
|
assert_eq!(
|
|
environment.selected_capability_roots(),
|
|
updated_ready_info.selected_capability_roots
|
|
);
|
|
assert!(Arc::ptr_eq(
|
|
&environment,
|
|
&manager.get_environment("tools").expect("environment")
|
|
));
|
|
|
|
manager.publish_ready_info("tools", EnvironmentReadyInfo::default())?;
|
|
assert!(environment.selected_capability_roots().is_empty());
|
|
Ok(())
|
|
}
|
|
|
|
#[tokio::test]
|
|
async fn publishing_readiness_before_deferred_completion_preserves_the_gate() -> anyhow::Result<()>
|
|
{
|
|
let manager = environment_manager_without_environments();
|
|
let provider = Arc::new(FailingNoiseConnectProvider::default());
|
|
let registration =
|
|
manager.register_deferred_noise_environment("tools".to_string(), provider.clone())?;
|
|
let environment = manager.get_environment("tools").expect("environment");
|
|
let mut readiness = Box::pin(environment.wait_until_ready());
|
|
|
|
manager.publish_ready_info("tools", ready_info("published-root", "tools")?)?;
|
|
assert!(poll!(&mut readiness).is_pending());
|
|
assert_eq!(provider.calls(), 0);
|
|
|
|
let completed_ready_info = ready_info("completed-root", "tools")?;
|
|
registration.complete(Ok(completed_ready_info.clone()))?;
|
|
assert_eq!(
|
|
environment.selected_capability_roots(),
|
|
completed_ready_info.selected_capability_roots
|
|
);
|
|
let error = readiness.await.unwrap_err();
|
|
assert!(error.to_string().contains("test Noise provider called"));
|
|
assert_eq!(provider.calls(), 1);
|
|
Ok(())
|
|
}
|
|
|
|
#[tokio::test]
|
|
async fn existing_environment_rejects_invalid_readiness() -> anyhow::Result<()> {
|
|
let manager = environment_manager_without_environments();
|
|
manager.upsert_noise_environment(
|
|
"tools".to_string(),
|
|
Arc::new(FailingNoiseConnectProvider::default()),
|
|
)?;
|
|
let existing_environment = manager
|
|
.get_environment("tools")
|
|
.expect("existing environment");
|
|
let error = manager
|
|
.publish_ready_info("tools", ready_info("selected-root", "other")?)
|
|
.unwrap_err();
|
|
|
|
assert!(matches!(error, ExecServerError::Protocol(_)));
|
|
assert!(existing_environment.selected_capability_roots().is_empty());
|
|
let current_environment = manager
|
|
.get_environment("tools")
|
|
.expect("current environment");
|
|
assert!(Arc::ptr_eq(&existing_environment, ¤t_environment));
|
|
Ok(())
|
|
}
|
|
|
|
#[tokio::test]
|
|
async fn failure_and_dropped_registration_are_terminal() -> anyhow::Result<()> {
|
|
let manager = environment_manager_without_environments();
|
|
let failed_provider = Arc::new(FailingNoiseConnectProvider::default());
|
|
let failed = manager
|
|
.register_deferred_noise_environment("failed".to_string(), failed_provider.clone())?;
|
|
let failed_environment = manager.get_environment("failed").expect("environment");
|
|
failed.complete(Err("provisioning failed".to_string()))?;
|
|
let error = failed_environment.wait_until_ready().await.unwrap_err();
|
|
assert!(
|
|
error
|
|
.to_string()
|
|
.ends_with("environment unavailable: provisioning failed")
|
|
);
|
|
assert_eq!(failed_provider.calls(), 0);
|
|
|
|
let dropped_provider = Arc::new(FailingNoiseConnectProvider::default());
|
|
let dropped = manager
|
|
.register_deferred_noise_environment("dropped".to_string(), dropped_provider.clone())?;
|
|
let dropped_environment = manager.get_environment("dropped").expect("environment");
|
|
drop(dropped);
|
|
let error = dropped_environment.wait_until_ready().await.unwrap_err();
|
|
assert!(
|
|
error
|
|
.to_string()
|
|
.contains("registration ended before completion")
|
|
);
|
|
assert_eq!(dropped_provider.calls(), 0);
|
|
assert!(manager.get_environment("failed").is_some());
|
|
assert!(manager.get_environment("dropped").is_some());
|
|
Ok(())
|
|
}
|
|
|
|
#[tokio::test]
|
|
async fn invalid_ready_info_is_terminal() -> anyhow::Result<()> {
|
|
let manager = environment_manager_without_environments();
|
|
let provider = Arc::new(FailingNoiseConnectProvider::default());
|
|
let registration =
|
|
manager.register_deferred_noise_environment("tools".to_string(), provider.clone())?;
|
|
let environment = manager.get_environment("tools").expect("environment");
|
|
|
|
let error = registration
|
|
.complete(Ok(ready_info("selected-root", "other")?))
|
|
.unwrap_err();
|
|
assert!(matches!(error, ExecServerError::Protocol(_)));
|
|
let readiness_error = environment.wait_until_ready().await.unwrap_err();
|
|
assert!(
|
|
readiness_error
|
|
.to_string()
|
|
.contains("belong to environment")
|
|
);
|
|
assert!(environment.selected_capability_roots().is_empty());
|
|
assert_eq!(provider.calls(), 0);
|
|
Ok(())
|
|
}
|
|
|
|
#[tokio::test]
|
|
async fn late_completion_is_isolated_from_replacement() -> anyhow::Result<()> {
|
|
let manager = environment_manager_without_environments();
|
|
let old_provider = Arc::new(FailingNoiseConnectProvider::default());
|
|
let old_registration =
|
|
manager.register_deferred_noise_environment("tools".to_string(), old_provider.clone())?;
|
|
let old_environment = manager.get_environment("tools").expect("old environment");
|
|
let current_provider = Arc::new(FailingNoiseConnectProvider::default());
|
|
let current_registration = manager
|
|
.register_deferred_noise_environment("tools".to_string(), current_provider.clone())?;
|
|
let current = manager.get_environment("tools").expect("current");
|
|
|
|
let old_ready_info = ready_info("old-root", "tools")?;
|
|
old_registration.complete(Ok(old_ready_info.clone()))?;
|
|
assert_eq!(
|
|
old_environment.selected_capability_roots(),
|
|
old_ready_info.selected_capability_roots
|
|
);
|
|
assert!(current.selected_capability_roots().is_empty());
|
|
let old_error = old_environment.wait_until_ready().await.unwrap_err();
|
|
assert!(old_error.to_string().contains("test Noise provider called"));
|
|
assert_eq!(old_provider.calls(), 1);
|
|
let mut current_readiness = Box::pin(current.wait_until_ready());
|
|
assert!(poll!(&mut current_readiness).is_pending());
|
|
assert_eq!(current_provider.calls(), 0);
|
|
|
|
let current_ready_info = ready_info("current-root", "tools")?;
|
|
current_registration.complete(Ok(current_ready_info.clone()))?;
|
|
assert_eq!(
|
|
current.selected_capability_roots(),
|
|
current_ready_info.selected_capability_roots
|
|
);
|
|
let current_error = current_readiness.await.unwrap_err();
|
|
assert!(
|
|
current_error
|
|
.to_string()
|
|
.contains("test Noise provider called")
|
|
);
|
|
assert_eq!(current_provider.calls(), 1);
|
|
Ok(())
|
|
}
|
|
|
|
#[tokio::test]
|
|
async fn eager_noise_environment_connects_without_registration() -> anyhow::Result<()> {
|
|
let manager = environment_manager_without_environments();
|
|
let provider = Arc::new(FailingNoiseConnectProvider::default());
|
|
manager.upsert_noise_environment("tools".to_string(), provider.clone())?;
|
|
let environment = manager.get_environment("tools").expect("environment");
|
|
|
|
let error = environment.wait_until_ready().await.unwrap_err();
|
|
assert!(error.to_string().contains("test Noise provider called"));
|
|
assert_eq!(provider.calls(), 1);
|
|
Ok(())
|
|
}
|
|
|
|
#[tokio::test]
|
|
async fn readiness_before_materialization_creates_the_stable_environment() -> anyhow::Result<()> {
|
|
let manager = environment_manager_without_environments();
|
|
let readiness_provider = Arc::new(FailingNoiseConnectProvider::default());
|
|
let materialization_provider = Arc::new(FailingNoiseConnectProvider::default());
|
|
let selected = ready_info("selected-root", "tools")?;
|
|
|
|
let ready = manager
|
|
.report_environment_provisioning_status(
|
|
"tools".to_string(),
|
|
Ok(selected.clone()),
|
|
readiness_provider.clone(),
|
|
)?
|
|
.expect("readiness report should create the environment");
|
|
let materialized = manager.materialize_pending_noise_environment(
|
|
"tools".to_string(),
|
|
materialization_provider.clone(),
|
|
)?;
|
|
|
|
assert!(Arc::ptr_eq(&ready, &materialized));
|
|
assert_eq!(
|
|
ready.selected_capability_roots(),
|
|
selected.selected_capability_roots
|
|
);
|
|
let error = ready.wait_until_ready().await.unwrap_err();
|
|
assert!(error.to_string().contains("test Noise provider called"));
|
|
assert_eq!(readiness_provider.calls(), 1);
|
|
assert_eq!(materialization_provider.calls(), 0);
|
|
Ok(())
|
|
}
|
|
|
|
#[tokio::test]
|
|
async fn materialize_then_report_ready_reuses_the_pending_environment() -> anyhow::Result<()> {
|
|
let manager = environment_manager_without_environments();
|
|
let pending_provider = Arc::new(FailingNoiseConnectProvider::default());
|
|
let pending = manager
|
|
.materialize_pending_noise_environment("tools".to_string(), pending_provider.clone())?;
|
|
let mut pending_readiness = Box::pin(pending.wait_until_ready());
|
|
assert!(poll!(&mut pending_readiness).is_pending());
|
|
let ready = manager
|
|
.report_environment_provisioning_status(
|
|
"tools".to_string(),
|
|
Ok(ready_info("selected-root", "tools")?),
|
|
Arc::new(FailingNoiseConnectProvider::default()),
|
|
)?
|
|
.expect("provisioning report should apply to the pending environment");
|
|
|
|
assert!(Arc::ptr_eq(&pending, &ready));
|
|
let error = pending_readiness.await.unwrap_err();
|
|
assert!(error.to_string().contains("test Noise provider called"));
|
|
assert_eq!(pending_provider.calls(), 1);
|
|
Ok(())
|
|
}
|
|
|
|
#[tokio::test]
|
|
async fn failure_before_materialization_is_terminal_without_connecting() -> anyhow::Result<()> {
|
|
let manager = environment_manager_without_environments();
|
|
let provider = Arc::new(FailingNoiseConnectProvider::default());
|
|
|
|
let failed = manager
|
|
.report_environment_provisioning_status(
|
|
"tools".to_string(),
|
|
Err("provisioning failed".to_string()),
|
|
provider.clone(),
|
|
)?
|
|
.expect("failure report should create the environment");
|
|
let materialized = manager.materialize_pending_noise_environment(
|
|
"tools".to_string(),
|
|
Arc::new(FailingNoiseConnectProvider::default()),
|
|
)?;
|
|
|
|
assert!(Arc::ptr_eq(&failed, &materialized));
|
|
let error = failed.wait_until_ready().await.unwrap_err();
|
|
assert!(error.to_string().ends_with("provisioning failed"));
|
|
assert_eq!(provider.calls(), 0);
|
|
Ok(())
|
|
}
|
|
|
|
#[tokio::test]
|
|
async fn failure_releases_the_existing_pending_environment_without_connecting() -> anyhow::Result<()>
|
|
{
|
|
let manager = environment_manager_without_environments();
|
|
let provider = Arc::new(FailingNoiseConnectProvider::default());
|
|
let pending =
|
|
manager.materialize_pending_noise_environment("tools".to_string(), provider.clone())?;
|
|
|
|
let reported = manager
|
|
.report_environment_provisioning_status(
|
|
"tools".to_string(),
|
|
Err("provisioning failed".to_string()),
|
|
provider.clone(),
|
|
)?
|
|
.expect("failure report should apply to the pending environment");
|
|
|
|
assert!(Arc::ptr_eq(&pending, &reported));
|
|
let error = pending.wait_until_ready().await.unwrap_err();
|
|
assert!(error.to_string().ends_with("provisioning failed"));
|
|
assert_eq!(provider.calls(), 0);
|
|
Ok(())
|
|
}
|
|
|
|
#[tokio::test]
|
|
async fn repeated_failure_preserves_the_first_error_and_rejects_ready() -> anyhow::Result<()> {
|
|
let manager = environment_manager_without_environments();
|
|
let provider = Arc::new(FailingNoiseConnectProvider::default());
|
|
let failed = manager
|
|
.report_environment_provisioning_status(
|
|
"tools".to_string(),
|
|
Err("first failure".to_string()),
|
|
provider.clone(),
|
|
)?
|
|
.expect("failure report should create the environment");
|
|
|
|
let repeated = manager
|
|
.report_environment_provisioning_status(
|
|
"tools".to_string(),
|
|
Err("different failure".to_string()),
|
|
provider.clone(),
|
|
)?
|
|
.expect("repeated failure should be idempotent");
|
|
assert!(Arc::ptr_eq(&failed, &repeated));
|
|
|
|
let error = manager
|
|
.report_environment_provisioning_status(
|
|
"tools".to_string(),
|
|
Ok(ready_info("selected-root", "other")?),
|
|
provider.clone(),
|
|
)
|
|
.unwrap_err();
|
|
assert!(error.to_string().contains("first failure"));
|
|
|
|
let error = manager
|
|
.report_environment_provisioning_status(
|
|
"tools".to_string(),
|
|
Ok(ready_info("selected-root", "tools")?),
|
|
provider.clone(),
|
|
)
|
|
.unwrap_err();
|
|
assert!(error.to_string().contains("first failure"));
|
|
assert!(failed.selected_capability_roots().is_empty());
|
|
let error = failed.wait_until_ready().await.unwrap_err();
|
|
assert!(error.to_string().ends_with("first failure"));
|
|
assert_eq!(provider.calls(), 0);
|
|
Ok(())
|
|
}
|
|
|
|
#[tokio::test]
|
|
async fn ready_environment_rejects_a_later_failure() -> anyhow::Result<()> {
|
|
let manager = environment_manager_without_environments();
|
|
let provider = Arc::new(FailingNoiseConnectProvider::default());
|
|
let ready = manager
|
|
.report_environment_provisioning_status(
|
|
"tools".to_string(),
|
|
Ok(ready_info("selected-root", "tools")?),
|
|
provider.clone(),
|
|
)?
|
|
.expect("ready report should create the environment");
|
|
|
|
let error = manager
|
|
.report_environment_provisioning_status(
|
|
"tools".to_string(),
|
|
Err("late failure".to_string()),
|
|
provider,
|
|
)
|
|
.unwrap_err();
|
|
|
|
assert!(error.to_string().contains("already ready"));
|
|
assert_eq!(ready.selected_capability_roots().len(), 1);
|
|
Ok(())
|
|
}
|