1use std::collections::{BTreeMap, HashMap, HashSet};
24use std::path::PathBuf;
25use std::time::Duration;
26
27use crate::defaults::{config_paths, Defaults};
28use crate::msg_tree::{MsgTree, MsgTreeMode};
29use crate::node_set::NodeSet;
30use crate::propagation::PropagationTreeRouter;
31use crate::topology::TopologyParser;
32use crate::worker::exec::ExecWorker;
33use crate::worker::{EventHandler, Worker, WorkerError, WorkerState};
34
35#[derive(Debug, thiserror::Error)]
37pub enum TaskError {
38 #[error("worker error: {0}")]
40 Worker(#[from] WorkerError),
41 #[error("task timeout")]
43 Timeout,
44 #[error("task is already running")]
46 AlreadyRunning,
47 #[error("{0}_msgtree not set")]
49 MsgTreeDisabled(String),
50 #[error("topology error: {0}")]
52 Topology(String),
53 #[error("task error: {0}")]
55 General(String),
56}
57
58pub type Result<T> = std::result::Result<T, TaskError>;
60
61pub type Source = (usize, String);
66
67#[derive(Debug, Clone, Copy, PartialEq, Eq)]
69pub enum TaskState {
70 Idle,
72 Running,
74 Done,
76 Aborted,
78}
79
80#[derive(Debug, Clone)]
84pub struct TaskDefaults {
85 pub stderr: bool,
87 pub stdin: bool,
89 pub stdout_msgtree: bool,
91 pub stderr_msgtree: bool,
93 pub auto_tree: bool,
95}
96
97impl Default for TaskDefaults {
98 fn default() -> Self {
99 Self {
100 stderr: false,
101 stdin: true,
102 stdout_msgtree: true,
103 stderr_msgtree: true,
104 auto_tree: false,
105 }
106 }
107}
108
109impl TaskDefaults {
110 pub fn from_defaults(defaults: &Defaults) -> Self {
112 Self {
113 stderr: defaults.stderr(),
114 stdin: defaults.stdin(),
115 stdout_msgtree: true,
116 stderr_msgtree: true,
117 auto_tree: false,
118 }
119 }
120}
121
122#[derive(Debug, Clone)]
126pub struct TaskInfo {
127 pub debug: bool,
129 pub fanout: usize,
131 pub grooming_delay: f64,
133 pub connect_timeout: f64,
135 pub command_timeout: f64,
137}
138
139impl Default for TaskInfo {
140 fn default() -> Self {
141 Self {
142 debug: false,
143 fanout: 64,
144 grooming_delay: 0.5,
145 connect_timeout: 10.0,
146 command_timeout: 0.0,
147 }
148 }
149}
150
151impl TaskInfo {
152 pub fn from_defaults(defaults: &Defaults) -> Self {
154 Self {
155 debug: false,
156 fanout: defaults.fanout() as usize,
157 grooming_delay: 0.5,
158 connect_timeout: defaults.connect_timeout(),
159 command_timeout: defaults.command_timeout(),
160 ..Default::default()
161 }
162 }
163}
164
165fn topology_configs() -> Vec<PathBuf> {
167 config_paths("topology.conf")
168}
169
170fn get_short_hostname() -> String {
172 hostname::get()
173 .map(|h: std::ffi::OsString| {
174 h.to_string_lossy()
175 .to_string()
176 .split('.')
177 .next()
178 .unwrap_or("localhost")
179 .to_string()
180 })
181 .unwrap_or_else(|_| "localhost".to_string())
182}
183
184struct ManagedWorker {
186 worker: Box<dyn Worker>,
188 update_task_rc: bool,
190 autoclose: bool,
192}
193
194struct TaskGatheringHandler {
200 #[allow(dead_code)]
202 worker_id: usize,
203 stdout: HashMap<String, Vec<Vec<u8>>>,
205 stderr: HashMap<String, Vec<Vec<u8>>>,
207 retcodes: HashMap<String, i32>,
209 timeouts: HashSet<String>,
211 user_handler: Option<Box<dyn EventHandler>>,
213}
214
215impl TaskGatheringHandler {
216 fn new(worker_id: usize, user_handler: Option<Box<dyn EventHandler>>) -> Self {
217 Self {
218 worker_id,
219 stdout: HashMap::new(),
220 stderr: HashMap::new(),
221 retcodes: HashMap::new(),
222 timeouts: HashSet::new(),
223 user_handler,
224 }
225 }
226}
227
228impl EventHandler for TaskGatheringHandler {
229 fn on_start(&mut self, worker: &dyn Worker) {
230 if let Some(ref mut h) = self.user_handler {
231 h.on_start(worker);
232 }
233 }
234
235 fn on_read(&mut self, node: &str, fd: std::os::unix::io::RawFd, msg: &[u8]) {
236 let map = if fd == 2 {
238 &mut self.stderr
239 } else {
240 &mut self.stdout
241 };
242 map.entry(node.to_string()).or_default().push(msg.to_vec());
243
244 if let Some(ref mut h) = self.user_handler {
245 h.on_read(node, fd, msg);
246 }
247 }
248
249 fn on_close(&mut self, node: &str, rc: i32) {
250 self.retcodes.insert(node.to_string(), rc);
251
252 if let Some(ref mut h) = self.user_handler {
253 h.on_close(node, rc);
254 }
255 }
256
257 fn on_timeout(&mut self, node: &str) {
258 self.timeouts.insert(node.to_string());
259
260 if let Some(ref mut h) = self.user_handler {
261 h.on_timeout(node);
262 }
263 }
264
265 fn on_error(&mut self, node: &str, error: &WorkerError) {
266 if let Some(ref mut h) = self.user_handler {
267 h.on_error(node, error);
268 }
269 }
270
271 fn take_buffers(
272 &mut self,
273 ) -> (
274 HashMap<String, Vec<Vec<u8>>>,
275 HashMap<String, Vec<Vec<u8>>>,
276 HashSet<String>,
277 ) {
278 (
279 std::mem::take(&mut self.stdout),
280 std::mem::take(&mut self.stderr),
281 std::mem::take(&mut self.timeouts),
282 )
283 }
284}
285
286pub struct Task {
299 state: TaskState,
301 defaults: TaskDefaults,
303 info: TaskInfo,
305 timeout: Option<Duration>,
307
308 next_worker_id: usize,
311 workers: Vec<(usize, ManagedWorker)>,
313
314 stdout_tree: Option<MsgTree<Source>>,
317 stderr_tree: Option<MsgTree<Source>>,
319 d_source_rc: HashMap<Source, i32>,
321 d_rc_sources: BTreeMap<i32, HashSet<Source>>,
323 max_rc: Option<i32>,
325 timeout_sources: HashSet<Source>,
327
328 topology_parser: Option<TopologyParser>,
331 router: Option<PropagationTreeRouter>,
333 #[allow(dead_code)]
335 gateways: HashMap<String, usize>,
336}
337
338impl Task {
339 pub fn new() -> Self {
341 let defaults = TaskDefaults::default();
342 let info = TaskInfo::default();
343 Self::with_config(defaults, info)
344 }
345
346 pub fn from_defaults(defaults: &Defaults) -> Self {
348 let task_defaults = TaskDefaults::from_defaults(defaults);
349 let task_info = TaskInfo::from_defaults(defaults);
350 Self::with_config(task_defaults, task_info)
351 }
352
353 pub fn with_config(defaults: TaskDefaults, info: TaskInfo) -> Self {
355 let stdout_tree = if defaults.stdout_msgtree {
356 Some(MsgTree::new(MsgTreeMode::Shift))
357 } else {
358 None
359 };
360 let stderr_tree = if defaults.stderr_msgtree {
361 Some(MsgTree::new(MsgTreeMode::Shift))
362 } else {
363 None
364 };
365
366 Self {
367 state: TaskState::Idle,
368 defaults,
369 info,
370 timeout: None,
371 next_worker_id: 0,
372 workers: Vec::new(),
373 stdout_tree,
374 stderr_tree,
375 d_source_rc: HashMap::new(),
376 d_rc_sources: BTreeMap::new(),
377 max_rc: None,
378 timeout_sources: HashSet::new(),
379 topology_parser: None,
380 router: None,
381 gateways: HashMap::new(),
382 }
383 }
384
385 pub fn defaults(&self) -> &TaskDefaults {
391 &self.defaults
392 }
393
394 pub fn defaults_mut(&mut self) -> &mut TaskDefaults {
396 &mut self.defaults
397 }
398
399 pub fn info(&self) -> &TaskInfo {
401 &self.info
402 }
403
404 pub fn info_mut(&mut self) -> &mut TaskInfo {
406 &mut self.info
407 }
408
409 pub fn set_fanout(&mut self, fanout: usize) {
411 self.info.fanout = fanout;
412 }
413
414 pub fn fanout(&self) -> usize {
416 self.info.fanout
417 }
418
419 pub fn load_topology(&mut self, path: &str) -> Result<()> {
427 let hostname = get_short_hostname();
428 let mut parser = TopologyParser::new();
429 parser
430 .load(path)
431 .map_err(|e| TaskError::Topology(format!("failed to load topology: {}", e)))?;
432 parser
434 .tree(&hostname)
435 .map_err(|e| TaskError::Topology(format!("failed to build tree: {}", e)))?;
436 self.topology_parser = Some(parser);
437 Ok(())
438 }
439
440 fn try_load_default_topology(&mut self) -> bool {
442 if self.topology_parser.is_some() {
443 return true;
444 }
445 let configs = topology_configs();
446 for path in configs.iter().rev() {
447 if path.exists() {
448 if let Ok(()) = self.load_topology(&path.to_string_lossy()) {
449 return true;
450 }
451 }
452 }
453 false
454 }
455
456 pub fn default_tree_is_enabled(&mut self) -> bool {
458 if self.topology_parser.is_none() {
459 self.try_load_default_topology();
460 }
461 self.topology_parser.is_some() && self.defaults.auto_tree
462 }
463
464 pub fn default_router(&mut self) -> Result<&PropagationTreeRouter> {
466 if self.router.is_none() {
467 let hostname = get_short_hostname();
468 let parser = self
469 .topology_parser
470 .as_mut()
471 .ok_or_else(|| TaskError::Topology("no topology loaded".to_string()))?;
472 let topology = parser
473 .tree(&hostname)
474 .map_err(|e| TaskError::Topology(format!("failed to build tree: {}", e)))?;
475 let root_ns = topology
476 .root()
477 .ok_or_else(|| TaskError::Topology("topology has no root".to_string()))?;
478 let root_str = root_ns.nodeset.to_string();
479 let fanout = self.info.fanout;
480 let router = PropagationTreeRouter::new(&root_str, topology, fanout)
481 .map_err(|e| TaskError::Topology(format!("router creation failed: {}", e)))?;
482 self.router = Some(router);
483 }
484 Ok(self.router.as_ref().unwrap())
485 }
486
487 pub fn has_topology(&self) -> bool {
489 self.topology_parser.is_some()
490 }
491
492 pub fn shell(
503 &mut self,
504 command: &str,
505 nodes: Option<&str>,
506 handler: Option<Box<dyn EventHandler>>,
507 ) -> usize {
508 self.shell_with_opts(command, nodes, handler, None, false)
509 }
510
511 pub fn shell_with_opts(
515 &mut self,
516 command: &str,
517 nodes: Option<&str>,
518 handler: Option<Box<dyn EventHandler>>,
519 timeout: Option<Duration>,
520 autoclose: bool,
521 ) -> usize {
522 let node_list: Vec<String> = match nodes {
523 Some(ns) => {
524 match NodeSet::parse(ns) {
526 Ok(nodeset) => nodeset.iter().collect::<Vec<String>>(),
527 Err(_) => {
528 vec![ns.to_string()]
530 }
531 }
532 }
533 None => {
534 vec!["localhost".to_string()]
536 }
537 };
538
539 let fanout = self.info.fanout;
540 let worker_timeout = timeout.or_else(|| {
541 if self.info.command_timeout > 0.0 {
542 Some(Duration::from_secs_f64(self.info.command_timeout))
543 } else {
544 None
545 }
546 });
547
548 let mut worker = ExecWorker::new(node_list, command.to_string(), fanout, worker_timeout);
549 worker = worker.with_stderr(self.defaults.stderr);
550
551 self.schedule(Box::new(worker), handler, autoclose)
552 }
553
554 pub fn schedule(
559 &mut self,
560 worker: Box<dyn Worker>,
561 handler: Option<Box<dyn EventHandler>>,
562 autoclose: bool,
563 ) -> usize {
564 let worker_id = self.next_worker_id;
565 self.next_worker_id += 1;
566
567 let gathering = TaskGatheringHandler::new(worker_id, handler);
569 let mut managed_worker = ManagedWorker {
570 worker,
571 update_task_rc: true,
572 autoclose,
573 };
574 managed_worker.worker.set_handler(Box::new(gathering));
575
576 self.workers.push((worker_id, managed_worker));
577 worker_id
578 }
579
580 pub fn run(&mut self, timeout: Option<Duration>) -> Result<()> {
590 self.timeout = timeout;
591 self.resume()
592 }
593
594 pub fn run_command(
596 &mut self,
597 command: &str,
598 nodes: Option<&str>,
599 handler: Option<Box<dyn EventHandler>>,
600 timeout: Option<Duration>,
601 ) -> Result<usize> {
602 let wid = self.shell(command, nodes, handler);
603 self.timeout = timeout;
604 self.resume()?;
605 Ok(wid)
606 }
607
608 pub fn resume(&mut self) -> Result<()> {
610 if self.state == TaskState::Running {
611 return Err(TaskError::AlreadyRunning);
612 }
613
614 self.reset();
615 self.state = TaskState::Running;
616
617 let mut _running_count = 0;
619 let mut errors: Vec<TaskError> = Vec::new();
620
621 for (_wid, mw) in &mut self.workers {
623 if mw.worker.state() == WorkerState::Pending {
624 match mw.worker.start() {
625 Ok(()) => _running_count += 1,
626 Err(e) => errors.push(TaskError::Worker(e)),
627 }
628 }
629 }
630
631 let start = std::time::Instant::now();
633 loop {
634 let mut any_running = false;
635 for (_wid, mw) in &mut self.workers {
636 if !mw.autoclose && !mw.worker.is_done() {
637 let fds = mw.worker.read_fds();
639 for fd in &fds {
640 let _ = mw.worker.handle_read(*fd);
641 }
642 if !mw.worker.is_done() {
643 any_running = true;
644 }
645 }
646 }
647 if !any_running {
648 break;
649 }
650
651 if let Some(timeout) = self.timeout {
653 if start.elapsed() >= timeout {
654 for (_wid, mw) in &mut self.workers {
656 if !mw.worker.is_done() {
657 mw.worker.abort(true);
658 }
659 }
660 self.state = TaskState::Done;
661 return Err(TaskError::Timeout);
662 }
663 }
664
665 std::thread::sleep(Duration::from_millis(10));
667 }
668
669 self.collect_results();
671
672 self.state = TaskState::Done;
673
674 if let Some(err) = errors.into_iter().next() {
675 Err(err)
676 } else {
677 Ok(())
678 }
679 }
680
681 pub fn abort(&mut self, kill: bool) {
683 for (_wid, mw) in &mut self.workers {
684 if !mw.worker.is_done() {
685 mw.worker.abort(kill);
686 }
687 }
688 self.state = TaskState::Aborted;
689 }
690
691 pub fn running(&self) -> bool {
693 self.state == TaskState::Running
694 }
695
696 pub fn state(&self) -> TaskState {
698 self.state
699 }
700
701 fn reset(&mut self) {
707 if let Some(ref mut tree) = self.stdout_tree {
708 tree.clear();
709 }
710 if let Some(ref mut tree) = self.stderr_tree {
711 tree.clear();
712 }
713 self.d_source_rc.clear();
714 self.d_rc_sources.clear();
715 self.max_rc = None;
716 self.timeout_sources.clear();
717 }
718
719 fn collect_results(&mut self) {
721 for (wid, mw) in &mut self.workers {
722 let worker_id = *wid;
723
724 if mw.update_task_rc {
726 for (node, &rc) in mw.worker.retcodes() {
727 let source: Source = (worker_id, node.clone());
728 self.d_source_rc.insert(source.clone(), rc);
729 self.d_rc_sources.entry(rc).or_default().insert(source);
730
731 if self.max_rc.map_or(true, |max| rc > max) {
732 self.max_rc = Some(rc);
733 }
734 }
735 }
736
737 if let Some(mut handler) = mw.worker.take_handler() {
741 let (stdout, stderr, timeouts) = handler.take_buffers();
742
743 if let Some(ref mut tree) = self.stdout_tree {
745 for (node, chunks) in stdout {
746 let source: Source = (worker_id, node);
747 for chunk in chunks {
748 tree.add(source.clone(), chunk);
749 }
750 }
751 }
752
753 if let Some(ref mut tree) = self.stderr_tree {
755 for (node, chunks) in stderr {
756 let source: Source = (worker_id, node);
757 for chunk in chunks {
758 tree.add(source.clone(), chunk);
759 }
760 }
761 }
762
763 for node in timeouts {
765 self.timeout_sources.insert((worker_id, node));
766 }
767 }
768 }
769 }
770
771 pub fn max_retcode(&self) -> Option<i32> {
779 self.max_rc
780 }
781
782 pub fn node_retcode(&self, node: &str) -> Option<i32> {
787 let mut max_rc: Option<i32> = None;
788 for ((_, n), &rc) in &self.d_source_rc {
789 if n == node {
790 max_rc = Some(max_rc.map_or(rc, |m: i32| m.max(rc)));
791 }
792 }
793 max_rc
794 }
795
796 pub fn iter_retcodes(&self, match_keys: Option<&HashSet<String>>) -> Vec<(i32, Vec<String>)> {
801 let mut result = Vec::new();
802 for (&rc, sources) in &self.d_rc_sources {
803 let nodes: Vec<String> = sources
804 .iter()
805 .filter(|(_, n)| match_keys.map_or(true, |keys| keys.contains(n)))
806 .map(|(_, n)| n.clone())
807 .collect();
808 if !nodes.is_empty() {
809 result.push((rc, nodes));
810 }
811 }
812 result
813 }
814
815 pub fn iter_retcodes_by_worker(
817 &self,
818 worker_id: usize,
819 match_keys: Option<&HashSet<String>>,
820 ) -> Vec<(i32, Vec<String>)> {
821 let mut result = Vec::new();
822 for (&rc, sources) in &self.d_rc_sources {
823 let nodes: Vec<String> = sources
824 .iter()
825 .filter(|(wid, n)| {
826 *wid == worker_id && match_keys.map_or(true, |keys| keys.contains(n))
827 })
828 .map(|(_, n)| n.clone())
829 .collect();
830 if !nodes.is_empty() {
831 result.push((rc, nodes));
832 }
833 }
834 result
835 }
836
837 pub fn num_timeout(&self) -> usize {
839 self.timeout_sources.len()
840 }
841
842 pub fn iter_keys_timeout(&self) -> impl Iterator<Item = &str> {
844 self.timeout_sources.iter().map(|(_, n)| n.as_str())
845 }
846
847 pub fn num_timeout_by_worker(&self, worker_id: usize) -> usize {
849 self.timeout_sources
850 .iter()
851 .filter(|(wid, _)| *wid == worker_id)
852 .count()
853 }
854
855 pub fn iter_keys_timeout_by_worker(&self, worker_id: usize) -> impl Iterator<Item = &str> {
857 self.timeout_sources
858 .iter()
859 .filter(move |(wid, _)| *wid == worker_id)
860 .map(|(_, n)| n.as_str())
861 }
862
863 pub fn node_buffer(&self, node: &str) -> Result<Vec<u8>> {
871 let tree = self
872 .stdout_tree
873 .as_ref()
874 .ok_or_else(|| TaskError::MsgTreeDisabled("stdout".to_string()))?;
875 let mut buf = Vec::new();
876 for key in tree.keys() {
877 if key.1 == node {
878 if let Some(elem) = tree.get(key) {
879 let msg = elem.message();
880 if !buf.is_empty() {
881 buf.push(b'\n');
882 }
883 buf.extend_from_slice(&msg);
884 }
885 }
886 }
887 Ok(buf)
888 }
889
890 pub fn node_error(&self, node: &str) -> Result<Vec<u8>> {
892 let tree = self
893 .stderr_tree
894 .as_ref()
895 .ok_or_else(|| TaskError::MsgTreeDisabled("stderr".to_string()))?;
896 let mut buf = Vec::new();
897 for key in tree.keys() {
898 if key.1 == node {
899 if let Some(elem) = tree.get(key) {
900 let msg = elem.message();
901 if !buf.is_empty() {
902 buf.push(b'\n');
903 }
904 buf.extend_from_slice(&msg);
905 }
906 }
907 }
908 Ok(buf)
909 }
910
911 pub fn flush_buffers(&mut self) {
913 if let Some(ref mut tree) = self.stdout_tree {
914 tree.clear();
915 }
916 }
917
918 pub fn flush_errors(&mut self) {
920 if let Some(ref mut tree) = self.stderr_tree {
921 tree.clear();
922 }
923 }
924
925 pub fn num_workers(&self) -> usize {
931 self.workers.len()
932 }
933
934 pub fn worker(&self, worker_id: usize) -> Option<&dyn Worker> {
936 self.workers
937 .iter()
938 .find(|(wid, _)| *wid == worker_id)
939 .map(|(_, mw)| mw.worker.as_ref())
940 }
941
942 pub fn worker_retcodes(&self, worker_id: usize) -> Option<&HashMap<String, i32>> {
944 self.workers
945 .iter()
946 .find(|(wid, _)| *wid == worker_id)
947 .map(|(_, mw)| mw.worker.retcodes())
948 }
949}
950
951impl Default for Task {
952 fn default() -> Self {
953 Self::new()
954 }
955}
956
957#[cfg(test)]
962mod tests {
963 use super::*;
964 use std::sync::atomic::{AtomicBool, AtomicI32, AtomicUsize, Ordering};
965 use std::sync::Arc;
966
967 #[test]
970 fn test_task_state_variants() {
971 assert_eq!(TaskState::Idle, TaskState::Idle);
972 assert_eq!(TaskState::Running, TaskState::Running);
973 assert_eq!(TaskState::Done, TaskState::Done);
974 assert_eq!(TaskState::Aborted, TaskState::Aborted);
975 assert_ne!(TaskState::Idle, TaskState::Running);
976 }
977
978 #[test]
979 fn test_task_state_debug() {
980 assert_eq!(format!("{:?}", TaskState::Idle), "Idle");
981 assert_eq!(format!("{:?}", TaskState::Running), "Running");
982 assert_eq!(format!("{:?}", TaskState::Done), "Done");
983 assert_eq!(format!("{:?}", TaskState::Aborted), "Aborted");
984 }
985
986 #[test]
987 fn test_task_state_clone_copy() {
988 let s = TaskState::Running;
989 let s2 = s;
990 assert_eq!(s, s2);
991 }
992
993 #[test]
996 fn test_task_error_display() {
997 let e = TaskError::Timeout;
998 assert_eq!(e.to_string(), "task timeout");
999
1000 let e = TaskError::AlreadyRunning;
1001 assert_eq!(e.to_string(), "task is already running");
1002
1003 let e = TaskError::MsgTreeDisabled("stdout".to_string());
1004 assert_eq!(e.to_string(), "stdout_msgtree not set");
1005
1006 let e = TaskError::General("test error".to_string());
1007 assert_eq!(e.to_string(), "task error: test error");
1008
1009 let e = TaskError::Topology("no topology".to_string());
1010 assert_eq!(e.to_string(), "topology error: no topology");
1011 }
1012
1013 #[test]
1014 fn test_task_error_from_worker_error() {
1015 let we = WorkerError::Timeout;
1016 let te: TaskError = we.into();
1017 assert!(matches!(te, TaskError::Worker(WorkerError::Timeout)));
1018 }
1019
1020 #[test]
1023 fn test_task_defaults_default() {
1024 let d = TaskDefaults::default();
1025 assert!(!d.stderr);
1026 assert!(d.stdin);
1027 assert!(d.stdout_msgtree);
1028 assert!(d.stderr_msgtree);
1029 assert!(!d.auto_tree);
1030 }
1031
1032 #[test]
1033 fn test_task_defaults_from_defaults() {
1034 let defaults = Defaults::new();
1035 let d = TaskDefaults::from_defaults(&defaults);
1036 assert!(!d.stderr);
1037 assert!(d.stdin);
1038 }
1039
1040 #[test]
1043 fn test_task_info_default() {
1044 let info = TaskInfo::default();
1045 assert!(!info.debug);
1046 assert_eq!(info.fanout, 64);
1047 assert_eq!(info.grooming_delay, 0.5);
1048 assert_eq!(info.connect_timeout, 10.0);
1049 assert_eq!(info.command_timeout, 0.0);
1050 }
1051
1052 #[test]
1053 fn test_task_info_from_defaults() {
1054 let defaults = Defaults::new();
1055 let info = TaskInfo::from_defaults(&defaults);
1056 assert_eq!(info.fanout, defaults.fanout() as usize);
1057 assert_eq!(info.connect_timeout, defaults.connect_timeout());
1058 assert_eq!(info.command_timeout, defaults.command_timeout());
1059 }
1060
1061 #[test]
1064 fn test_task_new() {
1065 let task = Task::new();
1066 assert_eq!(task.state(), TaskState::Idle);
1067 assert_eq!(task.num_workers(), 0);
1068 assert_eq!(task.max_retcode(), None);
1069 assert_eq!(task.num_timeout(), 0);
1070 assert!(!task.running());
1071 }
1072
1073 #[test]
1074 fn test_task_default() {
1075 let task = Task::default();
1076 assert_eq!(task.state(), TaskState::Idle);
1077 }
1078
1079 #[test]
1080 fn test_task_from_defaults() {
1081 let defaults = Defaults::new();
1082 let task = Task::from_defaults(&defaults);
1083 assert_eq!(task.state(), TaskState::Idle);
1084 assert_eq!(task.fanout(), defaults.fanout() as usize);
1085 }
1086
1087 #[test]
1088 fn test_task_with_config() {
1089 let mut td = TaskDefaults::default();
1090 td.stderr = true;
1091 td.stdout_msgtree = false;
1092
1093 let mut ti = TaskInfo::default();
1094 ti.fanout = 32;
1095
1096 let task = Task::with_config(td, ti);
1097 assert!(task.defaults().stderr);
1098 assert!(!task.defaults().stdout_msgtree);
1099 assert_eq!(task.fanout(), 32);
1100 assert!(task.stdout_tree.is_none());
1102 assert!(task.stderr_tree.is_some());
1103 }
1104
1105 #[test]
1108 fn test_task_set_fanout() {
1109 let mut task = Task::new();
1110 assert_eq!(task.fanout(), 64);
1111 task.set_fanout(128);
1112 assert_eq!(task.fanout(), 128);
1113 }
1114
1115 #[test]
1118 fn test_task_shell_local() {
1119 let mut task = Task::new();
1120 let wid = task.shell("echo hello", None, None);
1121 assert_eq!(wid, 0);
1122 assert_eq!(task.num_workers(), 1);
1123 }
1124
1125 #[test]
1126 fn test_task_shell_remote() {
1127 let mut task = Task::new();
1128 let wid = task.shell("hostname", Some("node[1-3]"), None);
1129 assert_eq!(wid, 0);
1130 assert_eq!(task.num_workers(), 1);
1131 }
1132
1133 #[test]
1134 fn test_task_shell_multiple() {
1135 let mut task = Task::new();
1136 let wid1 = task.shell("echo 1", None, None);
1137 let wid2 = task.shell("echo 2", None, None);
1138 assert_eq!(wid1, 0);
1139 assert_eq!(wid2, 1);
1140 assert_eq!(task.num_workers(), 2);
1141 }
1142
1143 #[test]
1144 fn test_task_shell_with_opts() {
1145 let mut task = Task::new();
1146 let wid = task.shell_with_opts(
1147 "echo test",
1148 Some("node[1-5]"),
1149 None,
1150 Some(Duration::from_secs(30)),
1151 true,
1152 );
1153 assert_eq!(wid, 0);
1154 assert_eq!(task.num_workers(), 1);
1155 }
1156
1157 #[test]
1160 fn test_task_run_local_command() {
1161 let mut task = Task::new();
1162 task.shell("echo hello", None, None);
1163 let result = task.run(None);
1164 assert!(result.is_ok());
1165 assert_eq!(task.state(), TaskState::Done);
1166 }
1167
1168 #[test]
1169 fn test_task_run_returns_retcode() {
1170 let mut task = Task::new();
1171 task.shell("true", None, None);
1172 task.run(None).unwrap();
1173 assert_eq!(task.max_retcode(), Some(0));
1174 }
1175
1176 #[test]
1177 fn test_task_run_failing_command() {
1178 let mut task = Task::new();
1179 task.shell("false", None, None);
1180 task.run(None).unwrap();
1181 assert_eq!(task.max_retcode(), Some(1));
1182 }
1183
1184 #[test]
1185 fn test_task_run_mixed_retcodes() {
1186 let mut task = Task::new();
1187 task.shell("true", None, None);
1188 task.shell("exit 42", None, None);
1189 task.run(None).unwrap();
1190 assert_eq!(task.max_retcode(), Some(42));
1191 }
1192
1193 #[test]
1194 fn test_task_run_command_convenience() {
1195 let mut task = Task::new();
1196 let wid = task.run_command("echo test", None, None, None).unwrap();
1197 assert_eq!(wid, 0);
1198 assert_eq!(task.max_retcode(), Some(0));
1199 }
1200
1201 #[test]
1202 fn test_task_run_multiple_nodes() {
1203 let mut task = Task::new();
1204 task.shell("echo hi", Some("node[1-3]"), None);
1205 task.run(None).unwrap();
1206 assert_eq!(task.max_retcode(), Some(0));
1208
1209 let retcodes = task.iter_retcodes(None);
1210 for (rc, nodes) in &retcodes {
1212 assert_eq!(*rc, 0);
1213 }
1214 }
1215
1216 #[test]
1219 fn test_task_node_retcode() {
1220 let mut task = Task::new();
1221 task.shell("true", None, None);
1222 task.run(None).unwrap();
1223 assert_eq!(task.node_retcode("localhost"), Some(0));
1225 assert_eq!(task.node_retcode("nonexistent"), None);
1226 }
1227
1228 #[test]
1229 fn test_task_iter_retcodes_with_filter() {
1230 let mut task = Task::new();
1231 task.shell("echo hi", Some("node[1-3]"), None);
1232 task.run(None).unwrap();
1233
1234 let mut filter = HashSet::new();
1235 filter.insert("node1".to_string());
1236 let retcodes = task.iter_retcodes(Some(&filter));
1237 for (_rc, nodes) in &retcodes {
1238 for n in nodes {
1239 assert_eq!(n, "node1");
1240 }
1241 }
1242 }
1243
1244 #[test]
1245 fn test_task_iter_retcodes_by_worker() {
1246 let mut task = Task::new();
1247 let wid1 = task.shell("true", None, None);
1248 let wid2 = task.shell("exit 7", None, None);
1249 task.run(None).unwrap();
1250
1251 let rc1 = task.iter_retcodes_by_worker(wid1, None);
1252 assert_eq!(rc1.len(), 1);
1253 assert_eq!(rc1[0].0, 0);
1254
1255 let rc2 = task.iter_retcodes_by_worker(wid2, None);
1256 assert_eq!(rc2.len(), 1);
1257 assert_eq!(rc2[0].0, 7);
1258 }
1259
1260 #[test]
1263 fn test_task_abort() {
1264 let mut task = Task::new();
1265 task.shell("sleep 100", None, None);
1266 task.abort(true);
1268 assert_eq!(task.state(), TaskState::Aborted);
1269 }
1270
1271 #[test]
1274 fn test_task_run_with_timeout() {
1275 let mut task = Task::new();
1276 task.shell("sleep 100", None, None);
1277 let result = task.run(Some(Duration::from_millis(100)));
1278 assert!(result.is_err());
1279 match result.unwrap_err() {
1280 TaskError::Timeout => {}
1281 other => panic!("expected Timeout, got: {:?}", other),
1282 }
1283 }
1284
1285 #[test]
1288 fn test_task_with_custom_handler() {
1289 let started = Arc::new(AtomicBool::new(false));
1290 let closed = Arc::new(AtomicBool::new(false));
1291 let started_clone = started.clone();
1292 let closed_clone = closed.clone();
1293
1294 struct TestHandler {
1295 started: Arc<AtomicBool>,
1296 closed: Arc<AtomicBool>,
1297 }
1298
1299 impl EventHandler for TestHandler {
1300 fn on_start(&mut self, _worker: &dyn Worker) {
1301 self.started.store(true, Ordering::SeqCst);
1302 }
1303 fn on_close(&mut self, _node: &str, _rc: i32) {
1304 self.closed.store(true, Ordering::SeqCst);
1305 }
1306 }
1307
1308 let handler = TestHandler {
1309 started: started_clone,
1310 closed: closed_clone,
1311 };
1312
1313 let mut task = Task::new();
1314 task.shell("echo hello", None, Some(Box::new(handler)));
1315 task.run(None).unwrap();
1316
1317 assert!(started.load(Ordering::SeqCst), "on_start should be called");
1318 assert!(closed.load(Ordering::SeqCst), "on_close should be called");
1319 }
1320
1321 #[test]
1324 fn test_task_reset_between_runs() {
1325 let mut task = Task::new();
1326 task.shell("exit 5", None, None);
1327 task.run(None).unwrap();
1328 assert_eq!(task.max_retcode(), Some(5));
1329
1330 task.shell("exit 3", None, None);
1332 task.state = TaskState::Idle;
1334 task.run(None).unwrap();
1335 assert_eq!(task.max_retcode(), Some(5));
1339 }
1340
1341 #[test]
1344 fn test_task_topology_none_by_default() {
1345 let task = Task::new();
1346 assert!(!task.has_topology());
1347 }
1348
1349 #[test]
1350 fn test_task_default_tree_disabled() {
1351 let mut task = Task::new();
1352 assert!(!task.default_tree_is_enabled());
1353 }
1354
1355 #[test]
1358 fn test_task_node_buffer_empty() {
1359 let task = Task::new();
1360 let buf = task.node_buffer("somenode").unwrap();
1361 assert!(buf.is_empty());
1362 }
1363
1364 #[test]
1365 fn test_task_node_error_empty() {
1366 let task = Task::new();
1367 let buf = task.node_error("somenode").unwrap();
1368 assert!(buf.is_empty());
1369 }
1370
1371 #[test]
1372 fn test_task_node_buffer_disabled() {
1373 let mut td = TaskDefaults::default();
1374 td.stdout_msgtree = false;
1375 let task = Task::with_config(td, TaskInfo::default());
1376 let result = task.node_buffer("somenode");
1377 assert!(result.is_err());
1378 }
1379
1380 #[test]
1381 fn test_task_flush_buffers() {
1382 let mut task = Task::new();
1383 task.flush_buffers();
1384 task.flush_errors();
1385 }
1387
1388 #[test]
1391 fn test_task_worker_accessor() {
1392 let mut task = Task::new();
1393 let wid = task.shell("echo test", None, None);
1394 assert!(task.worker(wid).is_some());
1395 assert!(task.worker(999).is_none());
1396 }
1397
1398 #[test]
1399 fn test_task_worker_retcodes_accessor() {
1400 let mut task = Task::new();
1401 let wid = task.shell("echo test", None, None);
1402 task.run(None).unwrap();
1403 let retcodes = task.worker_retcodes(wid);
1404 assert!(retcodes.is_some());
1405 assert_eq!(*retcodes.unwrap().get("localhost").unwrap(), 0);
1406 }
1407
1408 #[test]
1411 fn test_task_defaults_mut() {
1412 let mut task = Task::new();
1413 assert!(!task.defaults().stderr);
1414 task.defaults_mut().stderr = true;
1415 assert!(task.defaults().stderr);
1416 }
1417
1418 #[test]
1419 fn test_task_info_mut() {
1420 let mut task = Task::new();
1421 assert_eq!(task.info().fanout, 64);
1422 task.info_mut().fanout = 256;
1423 assert_eq!(task.info().fanout, 256);
1424 }
1425
1426 #[test]
1429 fn test_get_short_hostname() {
1430 let hostname = get_short_hostname();
1431 assert!(!hostname.is_empty());
1432 assert!(!hostname.contains('.'));
1433 }
1434
1435 #[test]
1438 fn test_gathering_handler_collects_retcodes() {
1439 let mut handler = TaskGatheringHandler::new(0, None);
1440 handler.on_close("node1", 0);
1441 handler.on_close("node2", 1);
1442 assert_eq!(handler.retcodes.len(), 2);
1443 assert_eq!(handler.retcodes["node1"], 0);
1444 assert_eq!(handler.retcodes["node2"], 1);
1445 }
1446
1447 #[test]
1448 fn test_gathering_handler_collects_stdout() {
1449 let mut handler = TaskGatheringHandler::new(0, None);
1450 handler.on_read("node1", 1, b"hello");
1451 handler.on_read("node1", 1, b"world");
1452 handler.on_read("node2", 1, b"hi");
1453 assert_eq!(handler.stdout["node1"].len(), 2);
1454 assert_eq!(handler.stdout["node2"].len(), 1);
1455 }
1456
1457 #[test]
1458 fn test_gathering_handler_collects_stderr() {
1459 let mut handler = TaskGatheringHandler::new(0, None);
1460 handler.on_read("node1", 2, b"error msg");
1461 assert_eq!(handler.stderr["node1"].len(), 1);
1462 assert!(handler.stdout.is_empty());
1463 }
1464
1465 #[test]
1466 fn test_gathering_handler_collects_timeouts() {
1467 let mut handler = TaskGatheringHandler::new(0, None);
1468 handler.on_timeout("node1");
1469 handler.on_timeout("node2");
1470 assert_eq!(handler.timeouts.len(), 2);
1471 assert!(handler.timeouts.contains("node1"));
1472 }
1473
1474 #[test]
1475 fn test_gathering_handler_chains_user_handler() {
1476 let called = Arc::new(AtomicUsize::new(0));
1477 let called_clone = called.clone();
1478
1479 struct CountHandler(Arc<AtomicUsize>);
1480 impl EventHandler for CountHandler {
1481 fn on_close(&mut self, _node: &str, _rc: i32) {
1482 self.0.fetch_add(1, Ordering::SeqCst);
1483 }
1484 }
1485
1486 let user_handler = Box::new(CountHandler(called_clone));
1487 let mut handler = TaskGatheringHandler::new(0, Some(user_handler));
1488 handler.on_close("node1", 0);
1489 handler.on_close("node2", 0);
1490
1491 assert_eq!(called.load(Ordering::SeqCst), 2);
1492 assert_eq!(handler.retcodes.len(), 2);
1493 }
1494}