1use crate::error::Error;
2use crate::runtime::manager::RuntimeManager;
3use crate::runtime::{Runtime, RuntimeError};
4use crate::utils::BoxEventStream;
5use autoagents_protocol::{Event, RuntimeID};
6use futures_util::FutureExt;
7use std::path::PathBuf;
8use std::sync::Arc;
9use tokio::task::JoinHandle;
10
11#[derive(Debug, thiserror::Error)]
13pub enum EnvironmentError {
14 #[error("Runtime not found: {0}")]
15 RuntimeNotFound(RuntimeID),
16
17 #[error("No default runtime registered")]
18 NoDefaultRuntime,
19
20 #[error("Environment is already running")]
21 AlreadyRunning,
22
23 #[error("Runtime error: {0}")]
24 RuntimeError(#[from] Box<RuntimeError>),
25
26 #[error("Error when consuming receiver")]
27 EventError,
28
29 #[error("Run task join error: {0}")]
30 JoinError(#[from] tokio::task::JoinError),
31
32 #[error("Finished run task result was not yet available")]
33 RunResultNotReady,
34}
35
36#[derive(Clone)]
38pub struct EnvironmentConfig {
39 pub working_dir: PathBuf,
40}
41
42impl Default for EnvironmentConfig {
43 fn default() -> Self {
44 Self {
45 working_dir: std::env::current_dir().unwrap_or_default(),
46 }
47 }
48}
49
50pub struct Environment {
73 config: EnvironmentConfig,
74 runtime_manager: Arc<RuntimeManager>,
75 default_runtime: Option<RuntimeID>,
76 handle: Option<JoinHandle<Result<(), RuntimeError>>>,
77 launch_state: RuntimeLaunchState,
78}
79
80#[derive(Debug, Clone, Copy, PartialEq, Eq, Default)]
81enum RuntimeLaunchState {
82 #[default]
84 Idle,
85 Managed,
87 Background,
89}
90
91impl Environment {
92 pub fn new(config: Option<EnvironmentConfig>) -> Self {
94 let config = config.unwrap_or_default();
95 let runtime_manager = Arc::new(RuntimeManager::new());
96
97 Self {
98 config,
99 runtime_manager,
100 default_runtime: None,
101 handle: None,
102 launch_state: RuntimeLaunchState::Idle,
103 }
104 }
105
106 pub async fn register_runtime(&mut self, runtime: Arc<dyn Runtime>) -> Result<(), Error> {
109 self.runtime_manager
110 .register_runtime(runtime.clone())
111 .await?;
112 if self.default_runtime.is_none() {
113 self.default_runtime = Some(runtime.id());
114 }
115 Ok(())
116 }
117
118 pub fn config(&self) -> &EnvironmentConfig {
120 &self.config
121 }
122
123 pub async fn get_runtime(&self, runtime_id: &RuntimeID) -> Option<Arc<dyn Runtime>> {
125 self.runtime_manager.get_runtime(runtime_id).await
126 }
127
128 pub async fn get_runtime_or_default(
130 &self,
131 runtime_id: Option<RuntimeID>,
132 ) -> Result<Arc<dyn Runtime>, Error> {
133 let rid = match runtime_id {
134 Some(id) => id,
135 None => self
136 .default_runtime
137 .ok_or(EnvironmentError::NoDefaultRuntime)?,
138 };
139 self.get_runtime(&rid)
140 .await
141 .ok_or_else(|| EnvironmentError::RuntimeNotFound(rid).into())
142 }
143
144 #[allow(clippy::result_large_err)] pub fn run(&mut self) -> Result<(), EnvironmentError> {
158 self.reconcile_finished_managed_launch()?;
159
160 if self.launch_state == RuntimeLaunchState::Background {
161 return Err(EnvironmentError::AlreadyRunning);
162 }
163
164 if self.is_running() {
165 return Err(EnvironmentError::AlreadyRunning);
166 }
167
168 let manager = self.runtime_manager.clone();
169 let handle = tokio::spawn(async move { manager.run().await });
170 self.handle = Some(handle);
171 self.launch_state = RuntimeLaunchState::Managed;
172 Ok(())
173 }
174
175 pub async fn wait(&mut self) -> Result<Result<(), RuntimeError>, tokio::task::JoinError> {
186 let handle = match self.handle.take() {
187 Some(handle) => handle,
188 None => return Ok(Ok(())),
189 };
190
191 let mut guard = RestoreRunHandleOnDrop {
192 environment: self,
193 handle: Some(handle),
194 };
195
196 let join_result = guard.handle.as_mut().expect("handle was just set").await;
197
198 guard.handle = None;
199 guard.environment.launch_state = RuntimeLaunchState::Idle;
200
201 join_result
202 }
203
204 pub async fn run_background(&mut self) -> Result<(), EnvironmentError> {
210 self.reconcile_finished_managed_launch()?;
211
212 if self.launch_state != RuntimeLaunchState::Idle || self.is_running() {
213 return Err(EnvironmentError::AlreadyRunning);
214 }
215
216 let manager = self.runtime_manager.clone();
217 manager
218 .run_background()
219 .await
220 .map_err(|e| EnvironmentError::RuntimeError(Box::new(e)))?;
221 self.launch_state = RuntimeLaunchState::Background;
222 Ok(())
223 }
224
225 pub async fn take_event_receiver(
228 &mut self,
229 runtime_id: Option<RuntimeID>,
230 ) -> Result<BoxEventStream<Event>, EnvironmentError> {
231 let runtime = self
232 .get_runtime_or_default(runtime_id)
233 .await
234 .map_err(|err| match err {
235 Error::EnvironmentError(env_err) => env_err,
236 _ => EnvironmentError::EventError,
237 })?;
238
239 runtime
240 .take_event_receiver()
241 .await
242 .ok_or(EnvironmentError::EventError)
243 }
244
245 pub async fn subscribe_events(
247 &self,
248 runtime_id: Option<RuntimeID>,
249 ) -> Result<BoxEventStream<Event>, EnvironmentError> {
250 let runtime = self
251 .get_runtime_or_default(runtime_id)
252 .await
253 .map_err(|err| match err {
254 Error::EnvironmentError(env_err) => env_err,
255 _ => EnvironmentError::EventError,
256 })?;
257 Ok(runtime.subscribe_events().await)
258 }
259
260 pub async fn shutdown(&mut self) -> Result<(), EnvironmentError> {
262 let stop_result = self.runtime_manager.stop().await;
263
264 let join_result = if let Some(handle) = self.handle.take() {
265 Some(handle.await)
266 } else {
267 None
268 };
269
270 self.launch_state = RuntimeLaunchState::Idle;
271
272 if let Err(e) = stop_result {
273 return Err(EnvironmentError::RuntimeError(Box::new(e)));
274 }
275
276 match join_result {
277 None | Some(Ok(Ok(()))) => Ok(()),
278 Some(Ok(Err(e))) => Err(EnvironmentError::RuntimeError(Box::new(e))),
279 Some(Err(e)) => Err(EnvironmentError::JoinError(e)),
280 }
281 }
282
283 pub fn is_running(&self) -> bool {
289 match self.launch_state {
290 RuntimeLaunchState::Background => true,
291 RuntimeLaunchState::Managed => self
292 .handle
293 .as_ref()
294 .is_some_and(|handle| !handle.is_finished()),
295 RuntimeLaunchState::Idle => false,
296 }
297 }
298
299 #[allow(clippy::result_large_err)]
300 fn reconcile_finished_managed_launch(&mut self) -> Result<(), EnvironmentError> {
307 if self.launch_state != RuntimeLaunchState::Managed {
308 return Ok(());
309 }
310
311 let Some(handle) = self.handle.take() else {
312 self.launch_state = RuntimeLaunchState::Idle;
313 return Ok(());
314 };
315
316 if !handle.is_finished() {
317 self.handle = Some(handle);
318 return Ok(());
319 }
320
321 self.launch_state = RuntimeLaunchState::Idle;
322 Self::join_finished_handle(handle)
323 }
324
325 #[allow(clippy::result_large_err)]
326 fn join_finished_handle(
327 handle: JoinHandle<Result<(), RuntimeError>>,
328 ) -> Result<(), EnvironmentError> {
329 debug_assert!(
330 handle.is_finished(),
331 "join_finished_handle requires a finished run task"
332 );
333
334 match handle.now_or_never() {
335 Some(Ok(Ok(()))) => Ok(()),
336 Some(Ok(Err(e))) => Err(EnvironmentError::RuntimeError(Box::new(e))),
337 Some(Err(e)) => Err(EnvironmentError::JoinError(e)),
338 None => Err(EnvironmentError::RunResultNotReady),
339 }
340 }
341}
342
343struct RestoreRunHandleOnDrop<'a> {
346 environment: &'a mut Environment,
347 handle: Option<JoinHandle<Result<(), RuntimeError>>>,
348}
349
350impl Drop for RestoreRunHandleOnDrop<'_> {
351 fn drop(&mut self) {
352 if let Some(handle) = self.handle.take() {
353 self.environment.handle = Some(handle);
354 }
355 }
356}
357
358#[cfg(test)]
359mod tests {
360 use super::*;
361 use crate::runtime::SingleThreadedRuntime;
362 use tempfile::tempdir;
363 use tokio::sync::mpsc;
364 use uuid::Uuid;
365
366 #[test]
367 fn test_environment_config_default() {
368 let config = EnvironmentConfig::default();
369 assert_eq!(
370 config.working_dir,
371 std::env::current_dir().unwrap_or_default()
372 );
373 }
374
375 #[test]
376 fn test_environment_config_custom() {
377 let dir = tempdir().expect("Unable to create temp dir");
378 let config = EnvironmentConfig {
379 working_dir: dir.path().to_path_buf(),
380 };
381 assert_eq!(config.working_dir, dir.path().to_path_buf());
382 }
383
384 #[tokio::test]
385 async fn test_environment_get_runtime() {
386 let mut env = Environment::new(None);
387 let runtime = SingleThreadedRuntime::new(None);
388 let runtime_id = runtime.id;
389 env.register_runtime(runtime).await.unwrap();
390
391 let runtime = env.get_runtime(&runtime_id).await;
393
394 assert!(runtime.is_some());
395
396 let non_existent_id = Uuid::new_v4();
398 let runtime = env.get_runtime(&non_existent_id).await;
399 assert!(runtime.is_none());
400 }
401
402 #[tokio::test]
403 async fn test_environment_take_event_receiver() {
404 let mut env = Environment::new(None);
405 let runtime = SingleThreadedRuntime::new(None);
406 let _ = runtime.id;
407 env.register_runtime(runtime).await.unwrap();
408 let receiver = env.take_event_receiver(None).await;
409 assert!(receiver.is_ok());
410
411 let receiver2 = env.take_event_receiver(None).await;
413 assert!(receiver2.is_err());
414 }
415
416 #[tokio::test]
417 async fn test_environment_shutdown() {
418 let mut env = Environment::new(None);
419 env.shutdown()
420 .await
421 .expect("shutdown should succeed when idle");
422 }
423
424 #[tokio::test]
425 async fn test_environment_error_runtime_not_found() {
426 let mut env = Environment::new(None);
427 let runtime = SingleThreadedRuntime::new(None);
428 let _ = runtime.id;
429 env.register_runtime(runtime).await.unwrap();
430 let non_existent_id = Uuid::new_v4();
431
432 let result = env.get_runtime_or_default(Some(non_existent_id)).await;
433 assert!(matches!(
434 result,
435 Err(Error::EnvironmentError(EnvironmentError::RuntimeNotFound(id)))
436 if id == non_existent_id
437 ));
438 }
439
440 #[test]
441 fn test_environment_error_already_running_display() {
442 let error = EnvironmentError::AlreadyRunning;
443 assert!(error.to_string().contains("already running"));
444 }
445
446 #[test]
447 fn test_environment_error_run_result_not_ready_display() {
448 let error = EnvironmentError::RunResultNotReady;
449 assert!(error.to_string().contains("not yet available"));
450 }
451
452 #[test]
453 fn test_environment_error_display() {
454 let runtime_id = Uuid::new_v4();
455 let error = EnvironmentError::RuntimeNotFound(runtime_id);
456 assert!(error.to_string().contains("Runtime not found"));
457 assert!(error.to_string().contains(&runtime_id.to_string()));
458 }
459
460 #[test]
461 fn test_environment_error_no_default_display() {
462 let error = EnvironmentError::NoDefaultRuntime;
463 assert!(error.to_string().contains("No default runtime registered"));
464 }
465
466 #[tokio::test]
467 async fn test_get_runtime_or_default_no_default_runtime() {
468 let env = Environment::new(None);
469
470 let result = env.get_runtime_or_default(None).await;
471 assert!(matches!(
472 result,
473 Err(Error::EnvironmentError(EnvironmentError::NoDefaultRuntime))
474 ));
475 }
476
477 #[tokio::test]
478 async fn test_take_event_receiver_no_default_runtime() {
479 let mut env = Environment::new(None);
480
481 let result = env.take_event_receiver(None).await;
482 assert!(matches!(result, Err(EnvironmentError::NoDefaultRuntime)));
483 }
484
485 #[tokio::test]
486 async fn test_subscribe_events_no_default_runtime() {
487 let env = Environment::new(None);
488
489 let result = env.subscribe_events(None).await;
490 assert!(matches!(result, Err(EnvironmentError::NoDefaultRuntime)));
491 }
492
493 #[tokio::test]
494 async fn test_environment_run_stores_handle() {
495 let mut env = Environment::new(None);
496 let runtime = SingleThreadedRuntime::new(None);
497 env.register_runtime(runtime).await.unwrap();
498
499 assert!(!env.is_running());
500 env.run().expect("run should succeed");
501 assert!(env.is_running());
502 assert!(matches!(env.run(), Err(EnvironmentError::AlreadyRunning)));
503
504 env.shutdown().await.expect("shutdown should succeed");
505 assert!(!env.is_running());
506 }
507
508 #[tokio::test]
509 async fn test_environment_run_can_restart_after_shutdown() {
510 let mut env = Environment::new(None);
511 let runtime = SingleThreadedRuntime::new(None);
512 env.register_runtime(runtime).await.unwrap();
513
514 env.run().expect("initial run should succeed");
515 env.shutdown().await.expect("shutdown should succeed");
516
517 env.run().expect("run after shutdown should succeed");
521 let run_result = env.wait().await.expect("wait should join run task");
522 assert!(run_result.is_err());
523
524 env.shutdown()
525 .await
526 .expect("shutdown should succeed when idle after failed run");
527 }
528
529 #[tokio::test]
530 async fn test_get_runtime_or_default_runtime_not_found_variant() {
531 let mut env = Environment::new(None);
532 let runtime = SingleThreadedRuntime::new(None);
533 env.register_runtime(runtime).await.unwrap();
534 let non_existent_id = Uuid::new_v4();
535
536 let result = env.get_runtime_or_default(Some(non_existent_id)).await;
537 assert!(matches!(
538 result,
539 Err(Error::EnvironmentError(EnvironmentError::RuntimeNotFound(id)))
540 if id == non_existent_id
541 ));
542 }
543
544 #[tokio::test]
545 async fn test_take_event_receiver_runtime_not_found() {
546 let mut env = Environment::new(None);
547 let runtime = SingleThreadedRuntime::new(None);
548 env.register_runtime(runtime).await.unwrap();
549 let non_existent_id = Uuid::new_v4();
550
551 let result = env.take_event_receiver(Some(non_existent_id)).await;
552 assert!(matches!(
553 result,
554 Err(EnvironmentError::RuntimeNotFound(id)) if id == non_existent_id
555 ));
556 }
557
558 #[tokio::test]
559 async fn test_environment_wait_restores_handle_when_cancelled() {
560 use tokio::time::{Duration, sleep};
561
562 let mut env = Environment::new(None);
563 let runtime = SingleThreadedRuntime::new(None);
564 env.register_runtime(runtime).await.unwrap();
565
566 env.run().expect("run should succeed");
567 assert!(env.is_running());
568
569 {
570 let wait_fut = env.wait();
571 tokio::pin!(wait_fut);
572
573 tokio::select! {
574 _ = &mut wait_fut => panic!("run task should not finish immediately"),
575 _ = sleep(Duration::from_millis(10)) => {}
576 }
577 }
578
579 assert!(
580 env.is_running(),
581 "cancelled wait should restore the join handle"
582 );
583
584 env.shutdown()
585 .await
586 .expect("shutdown should join the restored handle");
587 assert!(!env.is_running());
588 }
589
590 #[tokio::test]
591 async fn test_environment_wait_is_idempotent() {
592 use tokio::time::{Duration, timeout};
593
594 let mut env = Environment::new(None);
595 let runtime = SingleThreadedRuntime::new(None);
596 env.register_runtime(runtime).await.unwrap();
597
598 env.run().expect("run should succeed");
599 env.shutdown().await.expect("shutdown should succeed");
600
601 for _ in 0..2 {
602 let result = timeout(Duration::from_secs(1), env.wait())
603 .await
604 .expect("wait should not hang");
605 assert!(result.is_ok());
606 }
607 }
608
609 #[derive(Clone, Copy)]
610 enum ImmediateRuntimeBehavior {
611 Success,
612 Error,
613 }
614
615 struct ImmediateRuntime {
616 id: RuntimeID,
617 behavior: ImmediateRuntimeBehavior,
618 tx: mpsc::Sender<Event>,
619 }
620
621 #[async_trait::async_trait]
622 impl Runtime for ImmediateRuntime {
623 fn id(&self) -> RuntimeID {
624 self.id
625 }
626
627 async fn subscribe_any(
628 &self,
629 _topic_name: &str,
630 _topic_type: std::any::TypeId,
631 _actor: Arc<dyn crate::actor::AnyActor>,
632 ) -> Result<(), RuntimeError> {
633 Ok(())
634 }
635
636 async fn publish_any(
637 &self,
638 _topic_name: &str,
639 _topic_type: std::any::TypeId,
640 _message: Arc<dyn std::any::Any + Send + Sync>,
641 ) -> Result<(), RuntimeError> {
642 Ok(())
643 }
644
645 fn tx(&self) -> mpsc::Sender<Event> {
646 self.tx.clone()
647 }
648
649 async fn transport(&self) -> Arc<dyn crate::actor::Transport> {
650 Arc::new(crate::actor::LocalTransport)
651 }
652
653 async fn take_event_receiver(&self) -> Option<BoxEventStream<Event>> {
654 None
655 }
656
657 async fn subscribe_events(&self) -> BoxEventStream<Event> {
658 Box::pin(futures::stream::empty())
659 }
660
661 async fn run(&self) -> Result<(), Box<dyn std::error::Error + Send + Sync>> {
662 match self.behavior {
663 ImmediateRuntimeBehavior::Success => Ok(()),
664 ImmediateRuntimeBehavior::Error => {
665 Err(std::io::Error::other("immediate runtime run failed").into())
666 }
667 }
668 }
669
670 async fn stop(&self) -> Result<(), Box<dyn std::error::Error + Send + Sync>> {
671 Ok(())
672 }
673 }
674
675 #[tokio::test]
676 async fn test_environment_wait_awaits_completed_run_task() {
677 let mut env = Environment::new(None);
678 let (tx, _rx) = mpsc::channel(1);
679 let runtime = Arc::new(ImmediateRuntime {
680 id: RuntimeID::new_v4(),
681 behavior: ImmediateRuntimeBehavior::Success,
682 tx,
683 }) as Arc<dyn Runtime>;
684 env.register_runtime(runtime).await.unwrap();
685
686 env.run().expect("run should succeed");
687 let wait_result = env.wait().await.expect("wait join should succeed");
688 assert!(wait_result.is_ok());
689 }
690
691 #[tokio::test]
692 async fn test_environment_run_restarts_after_finished_handle() {
693 use tokio::time::{Duration, sleep};
694
695 let mut env = Environment::new(None);
696 let (tx, _rx) = mpsc::channel(1);
697 let runtime = Arc::new(ImmediateRuntime {
698 id: RuntimeID::new_v4(),
699 behavior: ImmediateRuntimeBehavior::Success,
700 tx,
701 }) as Arc<dyn Runtime>;
702 env.register_runtime(runtime).await.unwrap();
703
704 env.run().expect("initial run should succeed");
705 sleep(Duration::from_millis(20)).await;
706 assert!(!env.is_running());
707
708 env.run().expect("run after finished handle should succeed");
709 env.shutdown().await.expect("shutdown should succeed");
710 }
711
712 #[tokio::test]
713 async fn test_environment_run_background_starts_runtimes() {
714 let mut env = Environment::new(None);
715 let (tx, _rx) = mpsc::channel(1);
716 let runtime = Arc::new(ImmediateRuntime {
717 id: RuntimeID::new_v4(),
718 behavior: ImmediateRuntimeBehavior::Success,
719 tx,
720 }) as Arc<dyn Runtime>;
721 env.register_runtime(runtime).await.unwrap();
722
723 env.run_background()
724 .await
725 .expect("run_background should succeed");
726 assert!(
727 env.is_running(),
728 "run_background should mark the environment as running until shutdown"
729 );
730
731 env.shutdown().await.expect("shutdown should succeed");
732 }
733
734 #[tokio::test]
735 async fn test_environment_run_background_rejects_after_run() {
736 let mut env = Environment::new(None);
737 let runtime = SingleThreadedRuntime::new(None);
738 env.register_runtime(runtime).await.unwrap();
739
740 env.run().expect("run should succeed");
741 assert!(matches!(
742 env.run_background().await,
743 Err(EnvironmentError::AlreadyRunning)
744 ));
745
746 env.shutdown().await.expect("shutdown should succeed");
747 }
748
749 #[tokio::test]
750 async fn test_environment_run_rejects_after_run_background() {
751 let mut env = Environment::new(None);
752 let (tx, _rx) = mpsc::channel(1);
753 let runtime = Arc::new(ImmediateRuntime {
754 id: RuntimeID::new_v4(),
755 behavior: ImmediateRuntimeBehavior::Success,
756 tx,
757 }) as Arc<dyn Runtime>;
758 env.register_runtime(runtime).await.unwrap();
759
760 env.run_background()
761 .await
762 .expect("run_background should succeed");
763 assert!(matches!(env.run(), Err(EnvironmentError::AlreadyRunning)));
764
765 env.shutdown().await.expect("shutdown should succeed");
766 }
767
768 #[tokio::test]
769 async fn test_environment_wait_propagates_run_failure() {
770 let mut env = Environment::new(None);
771 let (tx, _rx) = mpsc::channel(1);
772 let runtime = Arc::new(ImmediateRuntime {
773 id: RuntimeID::new_v4(),
774 behavior: ImmediateRuntimeBehavior::Error,
775 tx,
776 }) as Arc<dyn Runtime>;
777 env.register_runtime(runtime).await.unwrap();
778
779 env.run().expect("run should succeed");
780 let wait_result = env.wait().await.expect("wait join should succeed");
781 assert!(wait_result.is_err());
782 }
783
784 #[tokio::test]
785 async fn test_environment_run_surfaces_prior_failure_without_wait() {
786 use tokio::time::{Duration, sleep};
787
788 let mut env = Environment::new(None);
789 let (tx, _rx) = mpsc::channel(1);
790 let runtime = Arc::new(ImmediateRuntime {
791 id: RuntimeID::new_v4(),
792 behavior: ImmediateRuntimeBehavior::Error,
793 tx,
794 }) as Arc<dyn Runtime>;
795 env.register_runtime(runtime).await.unwrap();
796
797 env.run().expect("initial run should succeed");
798 sleep(Duration::from_millis(20)).await;
799 assert!(!env.is_running());
800
801 let err = env
802 .run()
803 .expect_err("restart should surface prior run failure");
804 assert!(matches!(err, EnvironmentError::RuntimeError(_)));
805 }
806
807 #[tokio::test]
808 async fn test_environment_run_background_surfaces_prior_failure_without_wait() {
809 use tokio::time::{Duration, sleep};
810
811 let mut env = Environment::new(None);
812 let (tx, _rx) = mpsc::channel(1);
813 let runtime = Arc::new(ImmediateRuntime {
814 id: RuntimeID::new_v4(),
815 behavior: ImmediateRuntimeBehavior::Error,
816 tx,
817 }) as Arc<dyn Runtime>;
818 env.register_runtime(runtime).await.unwrap();
819
820 env.run().expect("initial run should succeed");
821 sleep(Duration::from_millis(20)).await;
822 assert!(!env.is_running());
823
824 let err = env
825 .run_background()
826 .await
827 .expect_err("run_background should surface prior run failure");
828 assert!(matches!(err, EnvironmentError::RuntimeError(_)));
829 }
830
831 #[test]
832 fn test_environment_config_accessor() {
833 let dir = tempdir().expect("Unable to create temp dir");
834 let config = EnvironmentConfig {
835 working_dir: dir.path().to_path_buf(),
836 };
837 let env = Environment::new(Some(config.clone()));
838 assert_eq!(env.config().working_dir, config.working_dir);
839 }
840
841 #[tokio::test]
842 async fn test_subscribe_events_with_default_runtime() {
843 let mut env = Environment::new(None);
844 let runtime = SingleThreadedRuntime::new(None);
845 env.register_runtime(runtime).await.unwrap();
846
847 let stream = env.subscribe_events(None).await;
848 assert!(stream.is_ok());
849 }
850
851 #[tokio::test]
852 async fn test_get_runtime_or_default_uses_default_runtime() {
853 let mut env = Environment::new(None);
854 let runtime = SingleThreadedRuntime::new(None);
855 let runtime_id = runtime.id;
856 env.register_runtime(runtime).await.unwrap();
857
858 let resolved = env
859 .get_runtime_or_default(None)
860 .await
861 .expect("default runtime should resolve");
862 assert_eq!(resolved.id(), runtime_id);
863 }
864}