actionqueue_executor_local/
children.rs1use actionqueue_core::ids::{RunId, TaskId};
12use actionqueue_core::run::state::RunState;
13
14#[derive(Debug, Clone)]
16pub struct ChildState {
17 task_id: TaskId,
18 run_states: Vec<(RunId, RunState)>,
19 all_terminal: bool,
20}
21
22impl ChildState {
23 pub fn new(task_id: TaskId, run_states: Vec<(RunId, RunState)>) -> Self {
31 let all_terminal =
32 !run_states.is_empty() && run_states.iter().all(|(_, s)| s.is_terminal());
33 Self { task_id, run_states, all_terminal }
34 }
35
36 pub fn task_id(&self) -> TaskId {
38 self.task_id
39 }
40
41 pub fn run_states(&self) -> &[(RunId, RunState)] {
43 &self.run_states
44 }
45
46 pub fn all_terminal(&self) -> bool {
48 self.all_terminal
49 }
50
51 pub fn has_runs(&self) -> bool {
53 !self.run_states.is_empty()
54 }
55}
56
57#[derive(Debug, Clone, Default)]
62pub struct ChildrenSnapshot {
63 children: Vec<ChildState>,
64}
65
66impl ChildrenSnapshot {
67 pub fn new(children: Vec<ChildState>) -> Self {
69 Self { children }
70 }
71
72 pub fn children(&self) -> &[ChildState] {
74 &self.children
75 }
76
77 pub fn is_empty(&self) -> bool {
79 self.children.is_empty()
80 }
81
82 pub fn len(&self) -> usize {
84 self.children.len()
85 }
86
87 pub fn all_children_terminal(&self) -> bool {
89 !self.children.is_empty() && self.children.iter().all(|c| c.all_terminal())
90 }
91
92 pub fn get(&self, task_id: TaskId) -> Option<&ChildState> {
94 self.children.iter().find(|c| c.task_id() == task_id)
95 }
96}
97
98#[cfg(test)]
99mod tests {
100 use super::*;
101
102 fn tid() -> TaskId {
103 TaskId::new()
104 }
105
106 fn rid() -> RunId {
107 RunId::new()
108 }
109
110 #[test]
111 fn empty_snapshot() {
112 let snap = ChildrenSnapshot::default();
113 assert!(snap.is_empty());
114 assert!(!snap.all_children_terminal());
115 assert!(snap.children().is_empty());
116 }
117
118 #[test]
119 fn single_child_all_terminal() {
120 let snap =
121 ChildrenSnapshot::new(vec![ChildState::new(tid(), vec![(rid(), RunState::Completed)])]);
122 assert!(!snap.is_empty());
123 assert!(snap.all_children_terminal());
124 }
125
126 #[test]
127 fn single_child_not_terminal() {
128 let snap =
129 ChildrenSnapshot::new(vec![ChildState::new(tid(), vec![(rid(), RunState::Running)])]);
130 assert!(!snap.all_children_terminal());
131 }
132
133 #[test]
134 fn multiple_children_mixed() {
135 let snap = ChildrenSnapshot::new(vec![
136 ChildState::new(tid(), vec![(rid(), RunState::Completed)]),
137 ChildState::new(tid(), vec![(rid(), RunState::Running)]),
138 ]);
139 assert!(!snap.all_children_terminal());
140 }
141
142 #[test]
143 fn all_children_terminal_mixed_terminal_states() {
144 let snap = ChildrenSnapshot::new(vec![
145 ChildState::new(tid(), vec![(rid(), RunState::Completed)]),
146 ChildState::new(tid(), vec![(rid(), RunState::Failed)]),
147 ]);
148 assert!(snap.all_children_terminal());
149 }
150
151 #[test]
152 fn get_found_and_not_found() {
153 let known = tid();
154 let snap =
155 ChildrenSnapshot::new(vec![ChildState::new(known, vec![(rid(), RunState::Completed)])]);
156 assert!(snap.get(known).is_some());
157 assert!(snap.get(tid()).is_none());
158 }
159
160 #[test]
161 fn child_with_no_runs_not_terminal() {
162 let snap = ChildrenSnapshot::new(vec![ChildState::new(tid(), vec![])]);
163 assert!(!snap.all_children_terminal());
164 assert!(!snap.children()[0].all_terminal());
165 }
166
167 #[test]
168 fn child_with_no_runs_reports_has_runs_false() {
169 let child = ChildState::new(tid(), vec![]);
170 assert!(!child.has_runs());
171
172 let child_with_run = ChildState::new(tid(), vec![(rid(), RunState::Completed)]);
173 assert!(child_with_run.has_runs());
174 }
175}