1use crate::{
4 ChildStopped, ObserveChild, ObservePeer, PeerStopped, ReportTerminalOutcome, TerminalOutcome,
5};
6use behavior::{
7 Actions, Address, Behavior, BehaviorActed, BirthMode, CreationId, EventLayer, Here,
8 InterpreterRequest, InterpreterRequests, Protocol, ReturnsToEmitter, SendEffects, SendLayer,
9};
10
11pub trait TerminationTarget<A: Address>: Copy {
17 type Report;
18 type Request: InterpreterRequest<ReturnToEmitter = ReturnsToEmitter<Self::Report, Here>>;
19
20 fn request(self) -> Self::Request;
21 fn matches(self, report: &Self::Report) -> bool;
22 fn outcome(report: Self::Report) -> TerminalOutcome<A>;
23}
24
25pub struct ChildTermination<P: Protocol, Occurrence> {
27 pub child: CreationId,
28 protocol: core::marker::PhantomData<fn() -> P>,
29 occurrence: core::marker::PhantomData<fn() -> Occurrence>,
30}
31
32impl<P: Protocol, Occurrence> Copy for ChildTermination<P, Occurrence> {}
33
34impl<P: Protocol, Occurrence> Clone for ChildTermination<P, Occurrence> {
35 fn clone(&self) -> Self {
36 *self
37 }
38}
39
40impl<P: Protocol, Occurrence> PartialEq for ChildTermination<P, Occurrence> {
41 fn eq(&self, other: &Self) -> bool {
42 self.child == other.child
43 }
44}
45
46impl<P: Protocol, Occurrence> Eq for ChildTermination<P, Occurrence> {}
47
48impl<P: Protocol, Occurrence> core::fmt::Debug for ChildTermination<P, Occurrence> {
49 fn fmt(&self, formatter: &mut core::fmt::Formatter<'_>) -> core::fmt::Result {
50 formatter
51 .debug_struct("ChildTermination")
52 .field("child", &self.child)
53 .finish()
54 }
55}
56
57impl<P: Protocol, Occurrence> ChildTermination<P, Occurrence> {
58 #[must_use]
59 pub const fn new(child: CreationId) -> Self {
60 Self {
61 child,
62 protocol: core::marker::PhantomData,
63 occurrence: core::marker::PhantomData,
64 }
65 }
66}
67
68impl<P, Occurrence> TerminationTarget<P::Addr> for ChildTermination<P, Occurrence>
69where
70 P: Protocol,
71{
72 type Report = ChildStopped<P::Addr>;
73 type Request = ObserveChild<P, Occurrence>;
74
75 fn request(self) -> Self::Request {
76 ObserveChild::new(self.child)
77 }
78
79 fn matches(self, report: &Self::Report) -> bool {
80 report.child == self.child
81 }
82
83 fn outcome(report: Self::Report) -> TerminalOutcome<P::Addr> {
84 report.outcome
85 }
86}
87
88#[derive(Debug, Clone, Copy, PartialEq, Eq)]
90pub struct PeerTermination<A: Address> {
91 pub peer: A,
92}
93
94impl<A: Address> PeerTermination<A> {
95 #[must_use]
96 pub const fn new(peer: A) -> Self {
97 Self { peer }
98 }
99}
100
101impl<A> TerminationTarget<A> for PeerTermination<A>
102where
103 A: Address + Copy + Eq,
104{
105 type Report = PeerStopped<A>;
106 type Request = ObservePeer<A>;
107
108 fn request(self) -> Self::Request {
109 ObservePeer::new(self.peer)
110 }
111
112 fn matches(self, report: &Self::Report) -> bool {
113 report.peer == self.peer
114 }
115
116 fn outcome(report: Self::Report) -> TerminalOutcome<A> {
117 report.outcome
118 }
119}
120
121#[derive(Debug, Clone, Copy, PartialEq, Eq)]
123pub enum TerminalDisposition {
124 Discharge,
126 Propagate,
128}
129
130pub type TerminalPropagationPolicy<A> = fn(&TerminalOutcome<A>) -> TerminalDisposition;
133
134#[must_use]
136pub const fn propagate_all<A: Address>(_: &TerminalOutcome<A>) -> TerminalDisposition {
137 TerminalDisposition::Propagate
138}
139
140#[must_use]
143pub const fn propagate_abnormal<A: Address>(outcome: &TerminalOutcome<A>) -> TerminalDisposition {
144 match outcome {
145 Ok(crate::Exit::Normal | crate::Exit::Collected) => TerminalDisposition::Discharge,
146 Ok(crate::Exit::LinkDied(_) | crate::Exit::SupervisionFailed(_)) | Err(_) => {
147 TerminalDisposition::Propagate
148 }
149 }
150}
151
152#[derive(Debug, Clone, Copy, PartialEq, Eq)]
154pub enum TerminalPropagationState {
155 Observing,
156 Discharged,
157 Propagated,
158}
159
160#[derive(thiserror::Error)]
162pub enum TerminationPropagationError<E, Report> {
163 #[error("wrapped behavior rejected its event")]
165 Inner(#[source] E),
166 #[error("terminal report does not match the active propagation source")]
169 UnexpectedReport {
170 state: TerminalPropagationState,
171 report: Report,
172 },
173}
174
175impl<E: core::fmt::Debug, Report> core::fmt::Debug for TerminationPropagationError<E, Report> {
176 fn fmt(&self, formatter: &mut core::fmt::Formatter<'_>) -> core::fmt::Result {
177 match self {
178 Self::Inner(error) => formatter.debug_tuple("Inner").field(error).finish(),
179 Self::UnexpectedReport { state, .. } => formatter
180 .debug_struct("UnexpectedReport")
181 .field("state", state)
182 .field("report", &"<retained>")
183 .finish(),
184 }
185 }
186}
187
188#[derive(behavior_macros::SendProduct)]
190pub struct TerminalPropagationSends<Observations, Reports> {
191 pub observations: Observations,
192 pub reports: Reports,
193}
194
195type PropagationSends<A, Request> = TerminalPropagationSends<
196 InterpreterRequests<Request>,
197 InterpreterRequests<ReportTerminalOutcome<A>>,
198>;
199
200pub struct PropagateTermination<B: Behavior, Target> {
222 inner: B,
223 target: Target,
224 policy: TerminalPropagationPolicy<behavior::BehaviorAddr<B>>,
225 state: TerminalPropagationState,
226}
227
228type PropagationActions<B, Target> = Actions<
229 behavior::BehaviorAddr<B>,
230 <B as Behavior>::Ph,
231 SendLayer<
232 PropagationSends<
233 behavior::BehaviorAddr<B>,
234 <Target as TerminationTarget<behavior::BehaviorAddr<B>>>::Request,
235 >,
236 <B as Behavior>::Sends,
237 >,
238 <B as Behavior>::Birth,
239>;
240
241impl<B, Target> PropagateTermination<B, Target>
242where
243 B: Behavior,
244 Target: TerminationTarget<behavior::BehaviorAddr<B>>,
245{
246 #[must_use]
247 pub const fn new(
248 inner: B,
249 target: Target,
250 policy: TerminalPropagationPolicy<behavior::BehaviorAddr<B>>,
251 ) -> Self {
252 Self {
253 inner,
254 target,
255 policy,
256 state: TerminalPropagationState::Observing,
257 }
258 }
259
260 #[must_use]
261 pub const fn state(&self) -> TerminalPropagationState {
262 self.state
263 }
264
265 fn wrap(
266 actions: Actions<behavior::BehaviorAddr<B>, B::Ph, B::Sends, B::Birth>,
267 owned: PropagationSends<behavior::BehaviorAddr<B>, Target::Request>,
268 ) -> PropagationActions<B, Target> {
269 actions.map_sends(|inner| SendLayer::new(owned, inner))
270 }
271}
272
273impl<B, Target> behavior::BehaviorBase for PropagateTermination<B, Target>
274where
275 B: Behavior + behavior::BehaviorBase,
276 Target: TerminationTarget<behavior::BehaviorAddr<B>>,
277{
278 type Base = B::Base;
279
280 fn base(&self) -> &Self::Base {
281 self.inner.base()
282 }
283}
284
285impl<B, Target> crate::StashStatus for PropagateTermination<B, Target>
286where
287 B: Behavior + crate::StashStatus,
288 Target: TerminationTarget<behavior::BehaviorAddr<B>>,
289{
290 fn stashed_messages(&self) -> usize {
291 self.inner.stashed_messages()
292 }
293}
294
295impl<B, Target, A, Ph, Sends, Br> Behavior for PropagateTermination<B, Target>
296where
297 A: Address,
298 Sends: SendEffects + behavior::SendsFor<B::Event>,
299 Br: BirthMode,
300 B: Behavior<Ph = Ph, Sends = Sends, Birth = Br>,
301 B::Protocol: behavior::Protocol<Addr = A>,
302 Target: TerminationTarget<A>,
303 Target::Report: Send,
304 Target::Request: Send,
305{
306 type Protocol = B::Protocol;
307 type Event = EventLayer<Target::Report, B::Event>;
308 type Sends = SendLayer<PropagationSends<A, Target::Request>, Sends>;
309 type Ph = Ph;
310 type Error = TerminationPropagationError<B::Error, Target::Report>;
311 type Birth = Br;
312
313 fn init(&mut self, _: behavior::InitializationTurn) -> BehaviorActed<Self> {
314 let actions =
315 behavior::initialize(&mut self.inner).map_err(TerminationPropagationError::Inner)?;
316 let mut owned: PropagationSends<A, Target::Request> = TerminalPropagationSends::empty();
317 owned.observations.send(self.target.request());
318 Ok(Self::wrap(actions, owned))
319 }
320
321 fn transition(&mut self, _: behavior::ActiveTurn, event: Self::Event) -> BehaviorActed<Self> {
322 match event {
323 EventLayer::Owned(report)
324 if self.state == TerminalPropagationState::Observing
325 && self.target.matches(&report) =>
326 {
327 let outcome = Target::outcome(report);
328 match (self.policy)(&outcome) {
329 TerminalDisposition::Discharge => {
330 self.state = TerminalPropagationState::Discharged;
331 Ok(Actions::cont())
332 }
333 TerminalDisposition::Propagate => {
334 self.state = TerminalPropagationState::Propagated;
335 let mut owned: PropagationSends<A, Target::Request> =
336 TerminalPropagationSends::empty();
337 owned.reports.send(ReportTerminalOutcome::new(outcome));
338 Ok(Actions::new(
339 SendLayer::new(owned, Sends::empty()),
340 behavior::Creations::empty(),
341 behavior::Step::Stop(behavior::Stopped),
342 ))
343 }
344 }
345 }
346 EventLayer::Owned(report) => Err(TerminationPropagationError::UnexpectedReport {
347 state: self.state,
348 report,
349 }),
350 EventLayer::Inner(event) => behavior::delegate_transition(&mut self.inner, event)
351 .map(|actions| Self::wrap(actions, TerminalPropagationSends::empty()))
352 .map_err(TerminationPropagationError::Inner),
353 }
354 }
355}
356
357#[cfg(test)]
358mod tests {
359 use std::time::Instant;
360
361 use super::*;
362 use crate::{
363 Activate as _, Crash, Exit, RestartDenial, RestartReleaseFailure, SupervisionFailureReason,
364 };
365 use behavior::{AllocationRejection, CreationRejection};
366 use behavior::{
367 Births, CreateChild, CreationSequence, Creations, MailAddr, Never, NoBirths, NoSends, Step,
368 User,
369 };
370 use proptest::prelude::*;
371
372 struct Probe {
373 worker: CreationId,
374 }
375
376 #[derive(Debug, Eq, PartialEq)]
377 struct Worker;
378
379 impl behavior::Protocol for Worker {
380 type Addr = MailAddr;
381 type Msg = Never;
382 }
383
384 impl behavior::BehaviorBase for Worker {
385 type Base = Self;
386
387 fn base(&self) -> &Self::Base {
388 self
389 }
390 }
391
392 impl Behavior for Worker {
393 type Protocol = Self;
394 type Event = User<MailAddr, Never>;
395 type Sends = NoSends;
396 type Ph = Never;
397 type Error = Never;
398 type Birth = NoBirths;
399
400 fn transition(
401 &mut self,
402 _: behavior::ActiveTurn,
403 event: Self::Event,
404 ) -> BehaviorActed<Self> {
405 match event.message {}
406 }
407 }
408
409 impl behavior::Protocol for Probe {
410 type Addr = MailAddr;
411 type Msg = u8;
412 }
413
414 impl behavior::BehaviorBase for Probe {
415 type Base = Self;
416
417 fn base(&self) -> &Self::Base {
418 self
419 }
420 }
421
422 impl Behavior for Probe {
423 type Protocol = Self;
424 type Event = User<MailAddr, u8>;
425 type Sends = Vec<u8>;
426 type Ph = Never;
427 type Error = Never;
428 type Birth = Births<Worker>;
429
430 fn init(&mut self, _: behavior::InitializationTurn) -> BehaviorActed<Self> {
431 Ok(Actions::new(
432 vec![1],
433 Creations::one(CreateChild::birth(self.worker, Worker)),
434 Step::Continue,
435 ))
436 }
437
438 fn transition(
439 &mut self,
440 _: behavior::ActiveTurn,
441 event: Self::Event,
442 ) -> BehaviorActed<Self> {
443 Ok(Actions::send(vec![event.message]))
444 }
445 }
446
447 fn worker_creation() -> CreationId {
448 CreationSequence::new()
449 .issue()
450 .expect("the first worker creation ID exists")
451 }
452
453 fn child(
454 worker: CreationId,
455 policy: TerminalPropagationPolicy<MailAddr>,
456 ) -> PropagateTermination<Probe, ChildTermination<Worker, behavior::ChildHead>> {
457 PropagateTermination::new(Probe { worker }, ChildTermination::new(worker), policy)
458 }
459
460 #[test]
461 fn initialization_preserves_inner_effects_and_observes_the_exact_child() {
462 let worker = worker_creation();
463 let initialized = child(worker, propagate_all).initialize().unwrap();
464
465 assert_eq!(initialized.actions.sends.inner, [1]);
466 assert_eq!(
467 initialized.actions.creates,
468 Creations::one(CreateChild::birth(worker, Worker))
469 );
470 assert!(matches!(initialized.actions.become_, Step::Continue));
471 assert_eq!(
472 initialized.actions.sends.owned.observations.as_slice(),
473 [ObserveChild::new(worker)]
474 );
475 assert!(initialized.actions.sends.owned.reports.is_empty());
476 }
477
478 #[test]
479 fn every_terminal_variant_is_propagated_without_reclassification() {
480 let outcomes = [
481 Ok(Exit::Normal),
482 Ok(Exit::Collected),
483 Ok(Exit::LinkDied(MailAddr(4))),
484 Ok(Exit::SupervisionFailed(
485 SupervisionFailureReason::StableChildStopped,
486 )),
487 Ok(Exit::SupervisionFailed(
488 SupervisionFailureReason::RestartDenied(RestartDenial::BudgetExceeded {
489 restarts_in_window: 2,
490 replacements_requested: 3,
491 maximum_restarts: 4,
492 }),
493 )),
494 Ok(Exit::SupervisionFailed(
495 SupervisionFailureReason::RestartDenied(RestartDenial::ReleaseRejected(
496 RestartReleaseFailure::DurationOverflow,
497 )),
498 )),
499 Ok(Exit::SupervisionFailed(
500 SupervisionFailureReason::RestartDenied(RestartDenial::AttemptSequenceExhausted),
501 )),
502 Ok(Exit::SupervisionFailed(
503 SupervisionFailureReason::RestartDenied(RestartDenial::TimerGenerationExhausted),
504 )),
505 Ok(Exit::SupervisionFailed(
506 SupervisionFailureReason::RestartDenied(RestartDenial::TimerIdentityExhausted),
507 )),
508 Ok(Exit::SupervisionFailed(
509 SupervisionFailureReason::StableChildCreationRejected(
510 CreationRejection::Allocation(AllocationRejection::Exhausted),
511 ),
512 )),
513 Ok(Exit::SupervisionFailed(
514 SupervisionFailureReason::StableChildCreationRejected(
515 CreationRejection::Allocation(AllocationRejection::AddressAlreadyClaimed),
516 ),
517 )),
518 Ok(Exit::SupervisionFailed(
519 SupervisionFailureReason::StableChildCreationRejected(
520 CreationRejection::InitializationFailed,
521 ),
522 )),
523 Ok(Exit::SupervisionFailed(
524 SupervisionFailureReason::StableChildCreationRejected(
525 CreationRejection::EnvironmentFailed,
526 ),
527 )),
528 Ok(Exit::SupervisionFailed(
529 SupervisionFailureReason::WorkerCreationRejected(CreationRejection::Allocation(
530 AllocationRejection::Exhausted,
531 )),
532 )),
533 Ok(Exit::SupervisionFailed(
534 SupervisionFailureReason::WorkerCreationRejected(CreationRejection::Allocation(
535 AllocationRejection::AddressAlreadyClaimed,
536 )),
537 )),
538 Ok(Exit::SupervisionFailed(
539 SupervisionFailureReason::WorkerCreationRejected(
540 CreationRejection::InitializationFailed,
541 ),
542 )),
543 Ok(Exit::SupervisionFailed(
544 SupervisionFailureReason::WorkerCreationRejected(
545 CreationRejection::EnvironmentFailed,
546 ),
547 )),
548 Ok(Exit::SupervisionFailed(
549 SupervisionFailureReason::WorkerFactoryRejected,
550 )),
551 Err(Crash::Failed),
552 Err(Crash::EnvironmentFailed),
553 Err(Crash::CapabilityFailed),
554 Err(Crash::Panicked),
555 Err(Crash::Cancelled),
556 ];
557
558 for outcome in outcomes {
559 let worker = worker_creation();
560 let mut active = child(worker, propagate_all).initialize().unwrap().behavior;
561 let actions = active
562 .transition(EventLayer::Owned(ChildStopped::new(
563 worker,
564 outcome,
565 Instant::now(),
566 )))
567 .unwrap();
568
569 assert_eq!(
570 actions.sends.owned.reports.as_slice(),
571 [ReportTerminalOutcome::new(outcome)]
572 );
573 assert!(actions.sends.owned.observations.is_empty());
574 assert!(actions.sends.inner.is_empty());
575 assert!(actions.creates.is_empty());
576 assert!(matches!(actions.become_, Step::Stop(_)));
577 assert_eq!(active.state(), TerminalPropagationState::Propagated);
578
579 let duplicate = ChildStopped::new(worker, outcome, Instant::now());
580 let rejected = active.transition(EventLayer::Owned(duplicate));
581 assert!(matches!(
582 rejected,
583 Err(TerminationPropagationError::UnexpectedReport {
584 state: TerminalPropagationState::Propagated,
585 report,
586 }) if report == duplicate
587 ));
588 }
589 }
590
591 #[test]
592 fn abnormal_policy_explicitly_discharges_normal_outcomes() {
593 for outcome in [Ok(Exit::Normal), Ok(Exit::Collected)] {
594 let worker = worker_creation();
595 let mut active = child(worker, propagate_abnormal)
596 .initialize()
597 .unwrap()
598 .behavior;
599 let actions = active
600 .transition(EventLayer::Owned(ChildStopped::new(
601 worker,
602 outcome,
603 Instant::now(),
604 )))
605 .unwrap();
606
607 assert!(actions.sends.owned.reports.is_empty());
608 assert!(matches!(actions.become_, Step::Continue));
609 assert_eq!(active.state(), TerminalPropagationState::Discharged);
610 }
611 }
612
613 #[test]
614 fn unmatched_reports_are_returned_and_inner_events_preserve_inner_actions() {
615 let mut creations = CreationSequence::new();
616 let worker = creations.issue().expect("the worker creation ID exists");
617 let unrelated_worker = creations
618 .issue()
619 .expect("the unrelated worker creation ID exists");
620 let mut active = child(worker, propagate_all).initialize().unwrap().behavior;
621 let unrelated = ChildStopped::new(unrelated_worker, Err(Crash::Failed), Instant::now());
622 let rejected = active.transition(EventLayer::Owned(unrelated));
623 assert!(matches!(
624 rejected,
625 Err(TerminationPropagationError::UnexpectedReport {
626 state: TerminalPropagationState::Observing,
627 report,
628 }) if report == unrelated
629 ));
630 assert_eq!(active.state(), TerminalPropagationState::Observing);
631
632 let delegated = active
633 .transition(EventLayer::Inner(User::new(MailAddr(3), 6)))
634 .unwrap();
635 assert_eq!(delegated.sends.inner, [6]);
636 assert!(delegated.sends.owned.observations.is_empty());
637 assert!(delegated.sends.owned.reports.is_empty());
638 }
639
640 #[test]
641 fn peer_target_returns_foreign_report_before_propagating_selected_peer() {
642 let mut initialized = PropagateTermination::new(
643 Probe {
644 worker: worker_creation(),
645 },
646 PeerTermination::new(MailAddr(4)),
647 propagate_all,
648 )
649 .initialize()
650 .unwrap();
651 assert_eq!(
652 initialized.actions.sends.owned.observations.as_slice(),
653 [ObservePeer::new(MailAddr(4))]
654 );
655
656 let foreign = PeerStopped::new(MailAddr(5), Err(Crash::Failed));
657 let rejected = initialized
658 .behavior
659 .transition(EventLayer::Owned(foreign.clone()));
660 assert!(matches!(
661 rejected,
662 Err(TerminationPropagationError::UnexpectedReport {
663 state: TerminalPropagationState::Observing,
664 report,
665 }) if report == foreign
666 ));
667 assert_eq!(
668 initialized.behavior.state(),
669 TerminalPropagationState::Observing
670 );
671
672 let outcome = Err(Crash::Panicked);
673 let actions = initialized
674 .behavior
675 .transition(EventLayer::Owned(PeerStopped::new(MailAddr(4), outcome)))
676 .unwrap();
677 assert_eq!(
678 actions.sends.owned.reports.as_slice(),
679 [ReportTerminalOutcome::new(outcome)]
680 );
681 assert!(actions.sends.owned.observations.is_empty());
682 assert!(actions.sends.inner.is_empty());
683 assert!(actions.creates.is_empty());
684 assert!(matches!(actions.become_, behavior::Step::Stop(_)));
685 assert_eq!(
686 initialized.behavior.state(),
687 TerminalPropagationState::Propagated
688 );
689 }
690
691 proptest! {
692 #[test]
693 fn arbitrary_terminal_payload_is_conserved_once(
694 tag in 0_u8..23,
695 peer in any::<u64>(),
696 admitted in any::<usize>(),
697 requested in any::<usize>(),
698 maximum in any::<u32>(),
699 ) {
700 let outcome = match tag {
701 0 => Ok(Exit::Normal),
702 1 => Ok(Exit::Collected),
703 2 => Ok(Exit::LinkDied(MailAddr(peer))),
704 3 => Ok(Exit::SupervisionFailed(
705 SupervisionFailureReason::StableChildStopped,
706 )),
707 4 => Ok(Exit::SupervisionFailed(
708 SupervisionFailureReason::RestartDenied(RestartDenial::BudgetExceeded {
709 restarts_in_window: admitted,
710 replacements_requested: requested,
711 maximum_restarts: maximum,
712 }),
713 )),
714 5 => Ok(Exit::SupervisionFailed(SupervisionFailureReason::RestartDenied(RestartDenial::ReleaseRejected(RestartReleaseFailure::DurationOverflow)))),
715 6 => Ok(Exit::SupervisionFailed(SupervisionFailureReason::RestartDenied(RestartDenial::AttemptSequenceExhausted))),
716 7 => Ok(Exit::SupervisionFailed(SupervisionFailureReason::RestartDenied(RestartDenial::TimerGenerationExhausted))),
717 8 => Ok(Exit::SupervisionFailed(SupervisionFailureReason::RestartDenied(RestartDenial::TimerIdentityExhausted))),
718 9 => Ok(Exit::SupervisionFailed(SupervisionFailureReason::StableChildCreationRejected(CreationRejection::Allocation(AllocationRejection::Exhausted)))),
719 10 => Ok(Exit::SupervisionFailed(SupervisionFailureReason::StableChildCreationRejected(CreationRejection::Allocation(AllocationRejection::AddressAlreadyClaimed)))),
720 11 => Ok(Exit::SupervisionFailed(SupervisionFailureReason::StableChildCreationRejected(CreationRejection::InitializationFailed))),
721 12 => Ok(Exit::SupervisionFailed(SupervisionFailureReason::StableChildCreationRejected(CreationRejection::EnvironmentFailed))),
722 13 => Ok(Exit::SupervisionFailed(SupervisionFailureReason::WorkerCreationRejected(CreationRejection::Allocation(AllocationRejection::Exhausted)))),
723 14 => Ok(Exit::SupervisionFailed(SupervisionFailureReason::WorkerCreationRejected(CreationRejection::Allocation(AllocationRejection::AddressAlreadyClaimed)))),
724 15 => Ok(Exit::SupervisionFailed(SupervisionFailureReason::WorkerCreationRejected(CreationRejection::InitializationFailed))),
725 16 => Ok(Exit::SupervisionFailed(SupervisionFailureReason::WorkerCreationRejected(CreationRejection::EnvironmentFailed))),
726 17 => Ok(Exit::SupervisionFailed(SupervisionFailureReason::WorkerFactoryRejected)),
727 18 => Err(Crash::Failed),
728 19 => Err(Crash::EnvironmentFailed),
729 20 => Err(Crash::Panicked),
730 21 => Err(Crash::Cancelled),
731 _ => Err(Crash::CapabilityFailed),
732 };
733 let worker = worker_creation();
734 let mut active = child(worker, propagate_all).initialize().unwrap().behavior;
735 let first = active.transition(EventLayer::Owned(ChildStopped::new(
736 worker,
737 outcome,
738 Instant::now(),
739 ))).unwrap();
740 prop_assert_eq!(
741 first.sends.owned.reports.as_slice(),
742 [ReportTerminalOutcome::new(outcome)]
743 );
744 let duplicate = ChildStopped::new(
745 worker,
746 outcome,
747 Instant::now(),
748 );
749 let rejected = matches!(
750 active.transition(EventLayer::Owned(duplicate)),
751 Err(TerminationPropagationError::UnexpectedReport {
752 state: TerminalPropagationState::Propagated,
753 report,
754 }) if report == duplicate
755 );
756 prop_assert!(rejected);
757 }
758 }
759}