1use crate::{
2 Execution, Outcome, PhaseSpec, PlanError, Report, Total,
3 sync::{Counter, MetadataCell},
4};
5use alloc::{boxed::Box, string::String, sync::Arc, vec::Vec};
6use core::{
7 fmt,
8 sync::atomic::{AtomicBool, AtomicU8, AtomicUsize, Ordering},
9};
10
11#[derive(Clone, Copy, Debug, PartialEq, Eq, PartialOrd, Ord, Hash)]
14pub struct NodeId(usize);
15
16impl NodeId {
17 pub const ROOT: Self = Self(0);
19
20 pub const fn get(self) -> usize {
22 self.0
23 }
24}
25
26impl fmt::Display for NodeId {
27 fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
28 self.0.fmt(f)
29 }
30}
31
32#[derive(Clone, Copy, Debug, Default, PartialEq, Eq)]
34#[non_exhaustive]
35pub enum Status {
36 #[default]
38 NotStarted,
39 Running,
41 Finished(Outcome),
43}
44
45#[derive(Clone, Copy, Debug, PartialEq)]
47#[non_exhaustive]
48pub struct Summary {
49 pub fraction: Option<f64>,
53 pub unresolved_fraction: f64,
55 pub status: Status,
57}
58fn credited(status: Status) -> bool {
60 matches!(
61 status,
62 Status::Finished(Outcome::Succeeded | Outcome::Skipped | Outcome::NotRun)
63 )
64}
65fn leaf_fraction(status: Status, total: Total, completed: u64, overflowed: bool) -> Option<f64> {
67 match (status, total) {
68 (Status::Finished(Outcome::Succeeded | Outcome::Skipped), _) => Some(1.0),
69 (Status::Finished(Outcome::NotRun), _) => Some(0.0),
70 _ if overflowed => None,
71 (_, Total::Exact(0) | Total::Estimated(0)) => Some(0.0),
72 (_, Total::Exact(n) | Total::Estimated(n)) => Some((completed as f64 / n as f64).min(1.0)),
73 _ => None,
74 }
75}
76
77#[derive(Clone)]
78struct Metadata {
79 units: Name,
80 total: Total,
81 revisions: Vec<Total>,
82 execution: Execution,
83 children: Vec<Arc<Node>>,
84 frozen: Option<Snapshot>,
87}
88
89const UNKNOWN: u8 = u8::MAX;
92fn encode(outcome: Outcome) -> u8 {
93 match outcome {
94 Outcome::Succeeded => 0,
95 Outcome::Skipped => 1,
96 Outcome::Cancelled => 2,
97 Outcome::Failed => 3,
98 Outcome::Abandoned => 4,
99 Outcome::NotRun => 5,
100 _ => UNKNOWN,
101 }
102}
103fn decode(code: u8) -> Option<Outcome> {
104 match code {
105 0 => Some(Outcome::Succeeded),
106 1 => Some(Outcome::Skipped),
107 2 => Some(Outcome::Cancelled),
108 3 => Some(Outcome::Failed),
109 4 => Some(Outcome::Abandoned),
110 5 => Some(Outcome::NotRun),
111 _ => None,
112 }
113}
114const MAX_REVISIONS: usize = 16;
116
117#[derive(Clone)]
120enum Name {
121 Inline { len: u8, bytes: [u8; INLINE_NAME] },
122 Heap(Box<str>),
123}
124const INLINE_NAME: usize = 22;
125impl Name {
126 fn new(name: &str) -> Self {
127 match name.len() {
128 len @ ..=INLINE_NAME => {
129 let mut bytes = [0; INLINE_NAME];
130 bytes[..len].copy_from_slice(name.as_bytes());
131 Self::Inline {
132 len: len as u8,
133 bytes,
134 }
135 }
136 _ => Self::Heap(name.into()),
137 }
138 }
139 fn as_str(&self) -> &str {
140 match self {
141 Self::Inline { len, bytes } => {
143 core::str::from_utf8(&bytes[..usize::from(*len)]).unwrap_or_default()
144 }
145 Self::Heap(name) => name,
146 }
147 }
148}
149
150struct Node {
151 id: NodeId,
152 parent: Option<NodeId>,
153 name: Name,
154 weight: u64,
155 initial_total: Total,
156 next_id: Arc<AtomicUsize>,
157 issued: AtomicBool,
158 branch: AtomicBool,
159 state: AtomicU8,
162 outcome: AtomicU8,
163 inferred: AtomicBool,
164 completed: Counter,
165 final_count: Counter,
167 meta: MetadataCell<Metadata>,
168}
169impl Node {
170 fn new(
171 id: NodeId,
172 parent: Option<NodeId>,
173 spec: &PhaseSpec<'_>,
174 next_id: Arc<AtomicUsize>,
175 ) -> Self {
176 Self {
177 id,
178 parent,
179 name: Name::new(spec.name),
180 weight: spec.weight,
181 initial_total: spec.total,
182 next_id,
183 issued: AtomicBool::new(false),
184 branch: AtomicBool::new(false),
185 state: AtomicU8::new(0),
186 outcome: AtomicU8::new(UNKNOWN),
187 inferred: AtomicBool::new(false),
188 completed: Counter::new(),
189 final_count: Counter::new(),
190 meta: MetadataCell::new(Metadata {
191 units: Name::new(spec.units),
192 total: spec.total,
193 revisions: Vec::new(),
194 execution: spec.execution,
195 children: Vec::new(),
196 frozen: None,
197 }),
198 }
199 }
200 fn snapshot(&self) -> Snapshot {
201 self.snapshot_with(false)
202 .expect("blocking metadata read succeeds")
203 }
204 fn read(&self, nonblocking: bool) -> Option<(Status, &Counter, Arc<Metadata>)> {
207 let state = self.state.load(Ordering::Acquire);
208 let meta = if nonblocking {
209 self.meta.try_get()?
210 } else {
211 self.meta.get()
212 };
213 Some(match state {
214 0 => (Status::NotStarted, &self.completed, meta),
215 1 => (Status::Running, &self.completed, meta),
216 _ => {
218 let outcome = decode(self.outcome.load(Ordering::Relaxed));
219 let status = Status::Finished(outcome.unwrap_or(Outcome::Abandoned));
220 (status, &self.final_count, meta)
221 }
222 })
223 }
224 fn snapshot_with(&self, nonblocking: bool) -> Option<Snapshot> {
225 let (status, counter, meta) = self.read(nonblocking)?;
226 if let Some(frozen) = &meta.frozen {
227 return Some(frozen.clone());
228 }
229 let mut result = Snapshot {
230 id: self.id,
231 parent: self.parent,
232 name: self.name.as_str().into(),
233 weight: self.weight,
234 units: meta.units.as_str().into(),
235 total: meta.total,
236 initial_total: self.initial_total,
237 total_revisions: meta.revisions.clone(),
238 execution: meta.execution,
239 status,
240 completed: counter.get(),
241 overflowed: counter.overflowed(),
242 completion_inferred: self.inferred.load(Ordering::Relaxed),
243 children: Vec::with_capacity(meta.children.len()),
244 };
245 for child in &meta.children {
246 let child = child.snapshot_with(nonblocking)?;
247 if result.status == Status::NotStarted && child.status != Status::NotStarted {
248 result.status = Status::Running;
250 }
251 result.children.push(child);
252 }
253 Some(result)
254 }
255 fn summary(&self, nonblocking: bool) -> Option<Summary> {
256 let (status, counter, meta) = self.read(nonblocking)?;
259 if let Some(frozen) = &meta.frozen {
260 return Some(Summary {
261 fraction: frozen.fraction(),
262 unresolved_fraction: frozen.unresolved_fraction(),
263 status: frozen.status,
264 });
265 }
266 let fraction = leaf_fraction(status, meta.total, counter.get(), counter.overflowed());
267 let mut result = Summary {
268 fraction,
269 unresolved_fraction: if fraction.is_some() { 0.0 } else { 1.0 },
270 status,
271 };
272 if meta.children.is_empty() {
273 return Some(result);
274 }
275 let mut sum = 0.0;
276 for child in &meta.children {
277 sum += child.weight as f64;
278 }
279 let mut fraction = 0.0;
280 let mut unresolved = 0.0;
281 for child in &meta.children {
282 let observed = child.summary(nonblocking)?;
283 if result.status == Status::NotStarted && observed.status != Status::NotStarted {
284 result.status = Status::Running;
285 }
286 let weight = child.weight as f64 / sum;
287 fraction += weight * observed.fraction.unwrap_or(0.0);
288 unresolved += weight * observed.unresolved_fraction;
289 }
290 if !credited(status) {
291 result.fraction = Some(fraction.clamp(0.0, 1.0));
292 result.unresolved_fraction = unresolved;
293 }
294 Some(result)
295 }
296 fn finished(&self) -> bool {
297 self.state.load(Ordering::Acquire) == 2
298 }
299}
300
301#[must_use = "finish the phase after its work joins; dropping it unfinished records Abandoned"]
312pub struct Phase {
313 node: Arc<Node>,
314}
315impl Phase {
316 pub fn new(name: impl Into<String>, total: Total) -> Self {
318 let name: String = name.into();
319 Self {
320 node: Arc::new(Node::new(
321 NodeId::ROOT,
322 None,
323 &PhaseSpec::new(&name, 1, total),
324 Arc::new(AtomicUsize::new(1)),
325 )),
326 }
327 }
328 pub fn reporter(&self) -> Reporter {
333 self.node.issued.store(true, Ordering::Relaxed);
334 self.deferred_reporter()
335 }
336 pub(crate) fn deferred_reporter(&self) -> Reporter {
338 Reporter {
339 node: Arc::clone(&self.node),
340 }
341 }
342 pub fn observer(&self) -> Observer {
344 Observer {
345 node: Arc::clone(&self.node),
346 }
347 }
348 pub fn id(&self) -> NodeId {
350 self.node.id
351 }
352 pub fn start(&mut self) -> Result<(), PlanError> {
355 self.ensure_live()?;
356 self.node.state.store(1, Ordering::Release);
357 Ok(())
358 }
359 pub fn set_units(&mut self, units: impl Into<String>) -> Result<(), PlanError> {
361 self.ensure_unused()?;
362 let units: String = units.into();
363 let mut meta = (*self.node.meta.get()).clone();
364 meta.units = Name::new(&units);
365 self.node.meta.publish(meta);
366 Ok(())
367 }
368 pub fn set_execution(&mut self, execution: Execution) -> Result<(), PlanError> {
370 self.ensure_unused()?;
371 let mut meta = (*self.node.meta.get()).clone();
372 meta.execution = execution;
373 self.node.meta.publish(meta);
374 Ok(())
375 }
376 pub fn set_total(&mut self, total: Total) -> Result<(), PlanError> {
381 self.ensure_live()?;
382 if self.node.branch.load(Ordering::Relaxed) {
383 return Err(PlanError::NotALeaf);
384 }
385 let mut meta = (*self.node.meta.get()).clone();
386 if meta.total != total {
387 meta.total = total;
388 if meta.revisions.len() == MAX_REVISIONS {
391 meta.revisions.remove(0);
392 }
393 meta.revisions.push(total);
394 self.node.meta.publish(meta);
395 }
396 Ok(())
397 }
398 pub fn split<const N: usize>(
411 &mut self,
412 execution: Execution,
413 parts: [PhaseSpec<'_>; N],
414 ) -> Result<[Phase; N], PlanError> {
415 match self.split_vec(execution, &parts)?.try_into() {
416 Ok(children) => Ok(children),
417 Err(_) => unreachable!("one child per part"),
418 }
419 }
420 pub fn split_vec(
425 &mut self,
426 execution: Execution,
427 parts: &[PhaseSpec<'_>],
428 ) -> Result<Vec<Phase>, PlanError> {
429 let mut children = Vec::with_capacity(parts.len());
430 self.split_each(execution, parts, &mut |child| children.push(child))?;
431 Ok(children)
432 }
433 #[allow(deprecated)] pub(crate) fn split_each(
437 &mut self,
438 execution: Execution,
439 parts: &[PhaseSpec<'_>],
440 each: &mut dyn FnMut(Phase),
441 ) -> Result<(), PlanError> {
442 self.ensure_unused()?;
443 PhaseSpec::validate_split(parts)?;
444 let first = self
445 .node
446 .next_id
447 .fetch_update(Ordering::Relaxed, Ordering::Relaxed, |n| {
448 n.checked_add(parts.len())
449 })
450 .map_err(|_| PlanError::Overflow)?;
451 let mut nodes = Vec::with_capacity(parts.len());
452 for (i, part) in parts.iter().enumerate() {
453 nodes.push(Arc::new(Node::new(
454 NodeId(first + i),
455 Some(self.node.id),
456 part,
457 Arc::clone(&self.node.next_id),
458 )));
459 }
460 let mut meta = (*self.node.meta.get()).clone();
461 meta.execution = execution;
462 meta.total = Total::Unknown;
464 meta.revisions.clear();
465 for node in &nodes {
466 each(Phase {
467 node: Arc::clone(node),
468 });
469 }
470 meta.children = nodes;
471 self.node.meta.publish(meta);
472 self.node.branch.store(true, Ordering::Release);
473 Ok(())
474 }
475 pub fn finish(&mut self) -> Result<(), PlanError> {
478 self.finish_with(Outcome::Succeeded)
479 }
480 pub fn finish_with(&mut self, outcome: Outcome) -> Result<(), PlanError> {
487 self.ensure_live()?;
488 for child in &self.node.meta.get().children {
489 if !child.finished() {
490 return Err(PlanError::UnfinishedChildren);
491 }
492 }
493 self.seal(outcome);
494 Ok(())
495 }
496
497 pub(crate) fn record_outcome(&mut self, outcome: Outcome) -> Result<(), PlanError> {
498 self.ensure_live()?;
499 self.seal(outcome);
500 Ok(())
501 }
502
503 pub(crate) fn complete_inferred(&mut self, outcome: Outcome) -> Result<(), PlanError> {
504 self.ensure_live()?;
505 self.node.inferred.store(true, Ordering::Relaxed);
506 let outcome = if self
507 .node
508 .summary(false)
509 .is_some_and(|s| s.status == Status::Running)
510 {
511 Outcome::Abandoned
512 } else {
513 outcome
514 };
515 self.record_outcome(outcome)
516 }
517 fn seal(&mut self, outcome: Outcome) {
523 let code = encode(outcome);
524 let mut running_child = false;
525 for child in &self.node.meta.get().children {
526 if !child.finished() {
527 running_child = true;
528 }
529 }
530 if code == UNKNOWN || running_child {
531 let mut snapshot = self.node.snapshot();
532 resolve_children(&mut snapshot, outcome);
533 snapshot.status = Status::Finished(outcome);
534 let mut meta = (*self.node.meta.get()).clone();
535 meta.frozen = Some(snapshot);
536 self.node.meta.publish(meta);
537 }
538 self.node.final_count.copy_from(&self.node.completed);
539 self.node.outcome.store(code, Ordering::Relaxed);
540 self.node.state.store(2, Ordering::Release);
541 }
542 fn ensure_live(&self) -> Result<(), PlanError> {
543 if self.node.finished() {
544 Err(PlanError::Finished)
545 } else {
546 Ok(())
547 }
548 }
549 fn ensure_unused(&self) -> Result<(), PlanError> {
550 self.ensure_live()?;
551 if self.node.issued.load(Ordering::Relaxed) || self.node.branch.load(Ordering::Relaxed) {
552 Err(PlanError::AlreadyInUse)
553 } else {
554 Ok(())
555 }
556 }
557}
558impl Drop for Phase {
559 fn drop(&mut self) {
560 if !self.node.finished() {
561 self.seal(Outcome::Abandoned);
562 }
563 }
564}
565
566impl how_far::Complete for Phase {
567 fn complete_as(mut self, outcome: Outcome) {
568 let _ = self.record_outcome(outcome);
569 }
570}
571
572fn resolve_children(snapshot: &mut Snapshot, parent: Outcome) {
575 for child in &mut snapshot.children {
576 let outcome = match child.status {
577 Status::Finished(_) => continue,
578 Status::NotStarted => match parent {
579 Outcome::Succeeded | Outcome::Skipped => Outcome::Skipped,
580 Outcome::Abandoned => Outcome::Abandoned,
581 _ => Outcome::NotRun,
582 },
583 Status::Running => Outcome::Abandoned,
584 };
585 resolve_children(child, outcome);
586 child.status = Status::Finished(outcome);
587 child.completion_inferred = true;
588 }
589}
590#[derive(Clone)]
596pub struct Reporter {
597 node: Arc<Node>,
598}
599impl fmt::Debug for Reporter {
600 fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
601 f.debug_struct("Reporter")
602 .field("phase", &self.node.id)
603 .finish_non_exhaustive()
604 }
605}
606impl Report for Reporter {
607 #[inline]
608 #[track_caller]
609 #[allow(clippy::collapsible_match)] fn advance(&self, completed: u64) {
611 if completed == 0 || self.node.branch.load(Ordering::Relaxed) {
612 return;
613 }
614 if !self.node.issued.load(Ordering::Relaxed) {
617 self.node.issued.store(true, Ordering::Relaxed);
618 }
619 match self.node.state.load(Ordering::Acquire) {
620 2 => return,
621 0 => {
622 if self
623 .node
624 .state
625 .compare_exchange(0, 1, Ordering::Relaxed, Ordering::Relaxed)
626 == Err(2)
627 {
628 return;
629 }
630 }
631 _ => {}
632 }
633 self.node.completed.add(completed);
634 }
635 fn may_report(&self) -> bool {
636 !self.node.branch.load(Ordering::Relaxed)
637 }
638}
639
640#[derive(Clone)]
643pub struct Observer {
644 node: Arc<Node>,
645}
646impl fmt::Debug for Observer {
647 fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
648 f.debug_struct("Observer")
649 .field("phase", &self.node.id)
650 .finish_non_exhaustive()
651 }
652}
653impl Observer {
654 pub fn id(&self) -> NodeId {
656 self.node.id
657 }
658 pub fn summary(&self) -> Summary {
661 self.node.summary(false).expect("blocking read")
662 }
663 pub fn try_summary(&self) -> Option<Summary> {
665 self.node.summary(true)
666 }
667 pub fn name(&self) -> &str {
669 self.node.name.as_str()
670 }
671
672 pub fn snapshot(&self) -> Snapshot {
679 self.node.snapshot()
680 }
681 pub fn try_snapshot(&self) -> Option<Snapshot> {
688 self.node.snapshot_with(true)
689 }
690 pub fn is_finished(&self) -> bool {
692 self.node.finished()
693 }
694 pub fn children(&self) -> Vec<Observer> {
697 let meta = self.node.meta.get();
698 let mut children = Vec::with_capacity(meta.children.len());
699 for node in &meta.children {
700 children.push(Observer {
701 node: Arc::clone(node),
702 });
703 }
704 children
705 }
706}
707
708#[derive(Clone, Debug, PartialEq)]
713#[non_exhaustive]
714pub struct Snapshot {
715 pub id: NodeId,
717 pub parent: Option<NodeId>,
719 pub name: String,
721 pub weight: u64,
723 pub units: String,
725 pub total: Total,
727 pub initial_total: Total,
729 pub total_revisions: Vec<Total>,
732 pub execution: Execution,
734 pub status: Status,
736 pub completed: u64,
738 pub overflowed: bool,
740 pub completion_inferred: bool,
743 pub children: Vec<Snapshot>,
745}
746impl Snapshot {
747 pub fn write_json(&self, out: &mut impl fmt::Write) -> fmt::Result {
754 out.write_str("{\"schema_version\":2,\"root\":")?;
755 self.write_node_json(out)?;
756 out.write_char('}')
757 }
758 fn write_node_json(&self, out: &mut impl fmt::Write) -> fmt::Result {
759 use crate::json::{outcome_name, quote};
760 write!(out, "{{\"id\":{},\"parent\":", self.id)?;
761 match self.parent {
762 Some(id) => write!(out, "{id}")?,
763 None => out.write_str("null")?,
764 }
765 out.write_str(",\"name\":")?;
766 quote(out, &self.name)?;
767 write!(out, ",\"weight\":\"{}\",\"units\":", self.weight)?;
768 quote(out, &self.units)?;
769 out.write_str(",\"total\":")?;
770 write_total(out, self.total)?;
771 out.write_str(",\"initial_total\":")?;
772 write_total(out, self.initial_total)?;
773 out.write_str(",\"total_revisions\":[")?;
774 for (i, total) in self.total_revisions.iter().enumerate() {
775 if i > 0 {
776 out.write_char(',')?;
777 }
778 write_total(out, *total)?;
779 }
780 let (execution, capacity) = match self.execution {
781 Execution::Unspecified => ("Unspecified", None),
782 Execution::Sequence => ("Sequence", None),
783 Execution::ForkJoin => ("ForkJoin", None),
784 Execution::WorkPool {
785 max_parallelism, ..
786 } => ("WorkPool", Some(max_parallelism)),
787 _ => ("Other", None),
788 };
789 write!(out, "],\"execution\":\"{execution}\",\"max_parallelism\":")?;
790 match capacity {
791 Some(n) => write!(out, "{n}")?,
792 None => out.write_str("null")?,
793 }
794 let (status, outcome) = match self.status {
795 Status::NotStarted => ("NotStarted", None),
796 Status::Running => ("Running", None),
797 Status::Finished(outcome) => ("Finished", Some(outcome)),
798 };
799 write!(out, ",\"status\":\"{status}\",\"outcome\":")?;
800 match outcome {
801 Some(outcome) => write!(out, "\"{}\"", outcome_name(outcome))?,
802 None => out.write_str("null")?,
803 }
804 write!(out, ",\"completion_inferred\":{}", self.completion_inferred)?;
805 write!(
806 out,
807 ",\"completed\":\"{}\",\"overflowed\":{},\"overrun\":{},\"fraction\":",
808 self.completed,
809 self.overflowed,
810 self.overrun()
811 )?;
812 match self.fraction() {
813 Some(fraction) => write!(out, "{fraction}")?,
814 None => out.write_str("null")?,
815 }
816 write!(
817 out,
818 ",\"unresolved_fraction\":{},\"children\":[",
819 self.unresolved_fraction()
820 )?;
821 for (i, child) in self.children.iter().enumerate() {
822 if i > 0 {
823 out.write_char(',')?;
824 }
825 child.write_node_json(out)?;
826 }
827 out.write_str("]}")
828 }
829 pub const fn counter_max() -> u64 {
835 crate::sync::COUNT_MAX
836 }
837 pub fn fraction(&self) -> Option<f64> {
845 if self.children.is_empty() || credited(self.status) {
846 return leaf_fraction(self.status, self.total, self.completed, self.overflowed);
847 }
848 let sum = self.child_weight_sum();
849 let mut result = 0.0;
850 for child in &self.children {
851 result += child.weight as f64 / sum * child.fraction().unwrap_or(0.0);
852 }
853 Some(result.clamp(0.0, 1.0))
854 }
855 pub fn unresolved_fraction(&self) -> f64 {
858 if self.children.is_empty() {
859 return if self.fraction().is_some() { 0.0 } else { 1.0 };
860 }
861 if credited(self.status) {
862 return 0.0;
863 }
864 let sum = self.child_weight_sum();
865 let mut unresolved = 0.0;
866 for child in &self.children {
867 unresolved += child.weight as f64 / sum * child.unresolved_fraction();
868 }
869 unresolved
870 }
871 fn child_weight_sum(&self) -> f64 {
872 let mut sum = 0.0;
873 for child in &self.children {
874 sum += child.weight as f64;
875 }
876 sum
877 }
878 pub fn overrun(&self) -> bool {
880 matches!(self.total, Total::Exact(n) if self.completed > n) || self.overflowed
881 }
882}
883fn write_total(out: &mut impl fmt::Write, total: Total) -> fmt::Result {
884 match total {
885 Total::Unknown => out.write_str("{\"kind\":\"Unknown\"}"),
886 Total::Exact(n) => write!(out, "{{\"kind\":\"Exact\",\"count\":\"{n}\"}}"),
887 Total::Estimated(n) => write!(out, "{{\"kind\":\"Estimated\",\"count\":\"{n}\"}}"),
888 _ => out.write_str("{\"kind\":\"Other\"}"),
889 }
890}
891
892#[cfg(all(test, feature = "std"))]
893mod tests {
894 use super::*;
895 use crate::Stop;
896 use crate::poll::LocalPoller;
897 use almost_enough::Stopper;
898
899 #[test]
900 fn a_report_racing_with_the_finish_is_not_shown() {
901 let mut leaf = Phase::new("leaf", Total::Exact(3));
902 leaf.reporter().advance(2);
903 leaf.finish().unwrap();
904 leaf.node.completed.add(5);
907 let snapshot = leaf.observer().snapshot();
908 assert_eq!(snapshot.completed, 2);
909 assert_eq!(snapshot.status, Status::Finished(Outcome::Succeeded));
910 }
911
912 #[test]
913 fn a_busy_child_skips_ui_snapshots_while_reports_callbacks_and_cancellation_continue() {
914 let mut root = Phase::new("root", Total::Unknown);
915 let [child] = root
916 .split(
917 Execution::Sequence,
918 [PhaseSpec::new("child", 1, Total::Exact(3))],
919 )
920 .unwrap();
921 let observer = root.observer();
922 let stop = Stopper::new();
923 let cancel = stop.clone();
924 let mut poller = LocalPoller::new(observer.clone());
925 poller.subscribe(move |event| {
926 assert!(event.try_snapshot().is_none());
927 assert!(!event.snapshot_materialized());
928 cancel.cancel();
929 });
930 child.node.meta.with_lock_for_test(|| {
931 child.reporter().advance(1);
932 assert!(observer.try_snapshot().is_none());
933 poller.poll();
934 assert!(stop.check().is_err());
935 });
936 assert_eq!(observer.try_snapshot().unwrap().children[0].completed, 1);
937 }
938}