1use anyhow::anyhow;
97
98#[cfg( unix )]
99use anyhow::Context;
100
101#[cfg( target_os = "freebsd" )]
102use freebsd::parse_id;
103
104#[cfg( target_os = "freebsd" )]
105use serde::{Deserialize, de};
106
107use std::{
108 fmt::{self, Debug, Display},
109 mem,
110 ops::Deref,
111 str,
112};
113
114#[cfg( not( target_os = "windows" ))]
115use std::process::Command;
116
117use trees::bfs::{BfsForest, Split, Splitted};
118
119#[derive( Copy, Clone, Debug, PartialEq, Eq, PartialOrd, Ord )]
121#[cfg_attr( target_os = "freebsd", derive( Deserialize ))]
122pub struct Pid( pub u32 );
123
124#[cfg( target_os = "freebsd" )]
125mod freebsd;
126#[cfg( target_os = "freebsd" )]
127use freebsd::{PsOutput, pals_from_procfs};
128
129#[cfg( target_os = "linux" )]
130mod linux;
131#[cfg( target_os = "linux" )]
132use linux::pals_from_procfs;
133
134#[cfg( windows )]
135mod windows;
136
137#[derive( Debug )]
140#[cfg_attr( target_os = "freebsd", derive( Deserialize ))]
141pub struct ProcList {
142 #[cfg_attr( target_os = "freebsd", serde( rename = "process" ))]
143 procs : Vec<ProcNode>,
144 #[cfg_attr( target_os = "freebsd", serde( skip ))]
145 root : Link,
146}
147
148impl Default for ProcList {
149 fn default() -> Self { ProcList{ procs: Vec::new(), root: Link::default() }}
150}
151
152impl ProcList {
153 pub fn parent_of( &self, pid: Pid ) -> Option<&Process> {
155 self.locate( pid ).and_then( |index| {
156 let process = &self.procs[ index ].process;
157 if process.pid != process.ppid {
158 Some( process )
159 } else {
160 None
161 }
162 })
163 }
164
165 fn locate( &self, pid: Pid ) -> Option<usize> {
166 self.procs.binary_search_by_key( &pid, ProcNode::id ).ok()
167 }
168
169 fn adopt( &mut self, parent: Option<usize>, child: usize ) -> anyhow::Result<()> {
170 let mut index = parent;
171 let child_pid = self.procs[ child ].pid;
172 let node_count = self.procs[ child ].link.size.descendants + 1;
173 while let Some( i ) = index {
174 self.procs[i].link.size.descendants += node_count;
175 let pid = self.procs[i].pid;
176 if pid == self.procs[i].ppid {
177 break;
178 } else if pid == child_pid {
179 return Err( anyhow!( "error: {:?} caused circuit in process tree.", pid ));
180 }
181 index = self.locate( self.procs[i].ppid );
182 }
183
184 match parent {
185 Some( parent ) => {
186 self.procs[ parent ].link.size.degree += 1;
187 self.procs[ child ].link.sib = self.procs[ parent ].link.child;
188 self.procs[ child ].link.parent = parent;
189 self.procs[ parent ].link.child = child;
190 },
191 None => {
192 self.root.size.degree += 1;
193 self.procs[ child ].link.sib = self.root.child;
194 self.root.child = child;
195 },
196 }
197
198 self.root.size.descendants += node_count;
199
200 Ok(())
201 }
202
203 pub fn procs( &self ) -> impl Iterator<Item=&ProcNode> {
219 self.procs.iter()
220 }
221
222 pub fn children<'a, 's:'a>( &'s self ) -> Proc<'a> {
240 Proc {
241 proc_list : self,
242 index : self.root.child,
243 degree : self.root.size.degree,
244 }
245 }
246
247 pub fn bfs<'a, 's:'a>( &'s self ) -> BfsForest<Splitted<Proc<'a>>> {
252 BfsForest::from( self.children(), self.root.size )
253 }
254}
255
256impl Display for ProcList {
257 fn fmt( &self, f: &mut fmt::Formatter ) -> fmt::Result {
258 f.write_str( "[" )?;
259 let mut children = self.children();
260 if let Some( first ) = children.next() {
261 first.display( f, 1, true, true )?;
262 }
263 for child in children {
264 child.display( f, 1, false, false )?;
265 }
266 f.write_str( "]" )
267 }
268}
269
270#[derive( Copy, Clone )]
273pub struct Proc<'a> {
274 proc_list : &'a ProcList,
275 index : usize,
276 degree : usize,
277}
278
279impl<'a> Proc<'a> {
280 pub fn parent( &self ) -> Option<Self> {
282 let parent = self.proc_list.procs[ self.index ].link.parent;
283 if parent == Link::null() {
284 None
285 } else {
286 let degree = self.proc_list.procs[ parent ].link.size.degree;
287 Some( Proc{ proc_list: self.proc_list, index: parent, degree })
288 }
289 }
290
291 pub fn children( &self ) -> Self {
293 let link = &self.proc_list.procs[ self.index ].link;
294 Proc {
295 proc_list : self.proc_list,
296 index : link.child,
297 degree : link.size.degree,
298 }
299 }
300
301 fn display( &self, f: &mut fmt::Formatter, ident: usize, is_first_child: bool, is_first_line: bool ) -> fmt::Result {
302 if !is_first_line {
303 if is_first_child {
304 f.write_str("\n")?;
305 } else {
306 f.write_str(",\n")?;
307 }
308 for _ in 0..ident {
309 f.write_str(" ")?;
310 }
311 }
312 let process = &self.proc_list.procs[ self.index ].process;
313 write!( f, "{{cmd: \"{}\", pid:{}"
314 , escape8259::escape( &process.command )
315 , process.pid.0
316 )?;
317
318 let mut ends_with_pid = true;
319
320 let argv = process.argv_to_string();
321 if !argv.is_empty() {
322 ends_with_pid = false;
323 write!( f, ", args:[{}]", argv )?;
324 }
325
326 let mut subprocs = self.children();
327 if let Some( first ) = subprocs.next() {
328 ends_with_pid = false;
329 f.write_str( ", subs:[" )?;
330 first.display( f, ident+1, true, false )?;
331 for proc in subprocs {
332 proc.display( f, ident+1, false, false )?;
333 }
334 f.write_str("]")?;
335 }
336 if ends_with_pid {
337 f.write_str(" ")?;
338 }
339 f.write_str("}")
340 }
341}
342
343impl<'a> Iterator for Proc<'a> {
344 type Item = Self;
345
346 fn next( &mut self ) -> Option<Self::Item> {
347 if self.index == Link::null() {
348 None
349 } else {
350 let index = self.index;
351 let link = &self.proc_list.procs[ index ].link;
352 self.index = link.sib;
353 self.degree -= 1;
354 Some( Proc{ proc_list: self.proc_list, index, degree: link.size.degree })
355 }
356 }
357
358 fn size_hint( &self) -> (usize, Option<usize>) {
359 (self.degree, Some(self.degree) )
360 }
361}
362
363impl<'a> ExactSizeIterator for Proc<'a> {}
364
365impl<'a> Deref for Proc<'a> {
366 type Target = Process;
367
368 fn deref( &self ) -> &Process {
369 if self.index == Link::null() {
370 panic!( "bad deref to `Proc`." );
371 }
372 &self.proc_list.procs[ self.index ]
373 }
374}
375
376impl<'a> Debug for Proc<'a> {
377 fn fmt( &self, f: &mut fmt::Formatter ) -> fmt::Result {
378 self.deref().fmt( f )
379 }
380}
381
382impl<'a> Split for Proc<'a> {
383 type Item = &'a Process;
384 type Iter = Self;
385
386 fn split( self ) -> (Self::Item, Self::Iter, usize) {
387 let process = &self.proc_list.procs[ self.index ];
388 let descendants = process.link.size.descendants;
389 (process, self.children(), descendants)
390 }
391}
392
393#[doc( hidden )]
394#[derive( Debug )]
395#[cfg_attr( target_os = "freebsd", derive( Deserialize ))]
396pub struct ProcNode {
397 #[cfg_attr( target_os = "freebsd", serde( flatten ))]
398 pub process : Process,
399 #[cfg_attr( target_os = "freebsd", serde( skip ))]
400 link : Link,
401}
402
403impl Deref for ProcNode {
404 type Target = Process;
405
406 fn deref( &self ) -> &Process { &self.process }
407}
408
409#[derive( Clone, Debug, PartialEq, Eq )]
412#[cfg_attr( target_os = "freebsd", derive( Deserialize ))]
413pub struct Process {
414 #[cfg_attr( target_os = "freebsd", serde( deserialize_with = "parse_id" ))]
415 pub pid : Pid,
416 #[cfg_attr( target_os = "freebsd", serde( deserialize_with = "parse_id" ))]
417 pub ppid : Pid,
418 pub command : String,
419 pub arguments : String,
420}
421
422impl ProcNode {
423 fn id( &self ) -> Pid { self.pid }
424
425 pub(crate) fn from( process: Process ) -> Self { ProcNode{ process, link: Link::default() }}
426}
427
428fn argv_iter( argv: &str ) -> impl Iterator<Item=&str> {
429 argv.trim_end_matches('\0').split( '\0' )
430}
431
432impl Process {
433 pub fn argv( &self ) -> impl Iterator<Item=&str> {
435 argv_iter( &self.arguments )
436 }
437
438 fn argv_to_string( &self ) -> String {
439 let mut s = String::new();
440 if !self.arguments.is_empty() {
441 let mut argv = self.argv();
442 if let Some( arg ) = argv.next() {
443 s.push_str( &format!( " \"{}\"", escape8259::escape( arg )));
444 for arg in argv {
445 s.push_str( &format!( ", \"{}\"", escape8259::escape( arg )));
446 }
447 s.push(' ');
448 }
449 }
450 s
451 }
452}
453
454#[derive( Debug )]
455struct Link {
456 size : trees::Size,
457 parent : usize,
458 child : usize,
459 sib : usize,
460}
461
462impl Link {
463 const fn null() -> usize { std::usize::MAX }
464}
465
466impl Default for Link {
467 fn default() -> Self {
468 Link {
469 parent : Link::null(),
470 child : Link::null(),
471 sib : Link::null(),
472 size : trees::Size::default(),
473 }
474 }
475}
476
477#[cfg( target_os = "freebsd" )]
479pub fn pals() -> anyhow::Result<ProcList> {
480 pals_from_procfs( "/proc" )
481 .or_else( |_| pals_from_ps_json() )
482 .or_else( |_| pals_from_ps_ww() )
483}
484
485#[cfg( target_os = "linux" )]
487pub fn pals() -> anyhow::Result<ProcList> {
488 pals_from_procfs( "/proc" )
489 .or_else( |_| pals_from_ps_ww() )
490}
491
492#[cfg( target_os = "windows" )]
494pub fn pals() -> anyhow::Result<ProcList> {
495 windows::pals_from_wmic()
496}
497
498#[cfg( not( any( target_os = "freebsd", target_os = "linux", target_os = "windows" )))]
500pub fn pals() -> anyhow::Result<ProcList> {
501 pals_from_ps_ww()
502}
503
504#[cfg( not( target_os = "windows" ))]
505pub(crate) fn pals_from_ps_ww() -> anyhow::Result<ProcList> {
506 let output = String::from_utf8(
507 Command::new("ps")
508 .args( &[ "awwo", "pid,ppid,comm,args" ])
509 .output()?
510 .stdout
511 )?;
512 from_table( &output )
513}
514
515#[cfg( target_os = "freebsd" )]
516fn pals_from_ps_json() -> anyhow::Result<ProcList> {
517 let output = String::from_utf8(
518 Command::new("ps")
519 .args( &[ "a", "--libxo", "json", "-o", "pid,ppid,comm,args" ])
520 .output()?
521 .stdout
522 )?;
523 from_json( &output )
524}
525
526#[cfg( target_os = "freebsd" )]
527fn from_json( json: &str ) -> anyhow::Result<ProcList> {
528 let ps_output: PsOutput = serde_json::from_str( json )?;
529
530 build_tree( ps_output.proc_list, ArgvStatus::Parsing )
531}
532
533#[cfg( unix )]
534fn from_table( table: &str ) -> anyhow::Result<ProcList> {
535 let mut lines = table.lines();
536 let header = lines.next().context("ps command should generate some output.")?;
537
538 let ppid_col = header.find(" PID")
539 .context( "PID column should be generated by ps command.")? + 4;
540 let comm_col = header.find(" PPID")
541 .context("PPID column should be generated by ps command.")? + 5;
542 let args_col = header.rfind("COMMAND")
543 .context("COMMAND column should be generated by ps command.")?;
544
545 let procs = lines.try_fold( Vec::new(), |mut procs, line| -> anyhow::Result<Vec<ProcNode>> {
546 let bytes = line.as_bytes();
547 let pid = Pid( u32::from_str_radix( str::from_utf8( &bytes[ ..ppid_col ])?.trim(), 10 )? );
548 let ppid = Pid( u32::from_str_radix( str::from_utf8( &bytes[ ppid_col..comm_col ])?.trim(), 10 )? );
549 let command = str::from_utf8( &bytes[ comm_col..args_col ])?.trim().to_owned();
550 let arguments = str::from_utf8( &bytes[ args_col.. ])?.trim().to_owned();
551
552 procs.push( ProcNode::from( Process{ pid, ppid, command, arguments }));
553 Ok( procs )
554 })?;
555
556 build_tree( ProcList{ procs, root: Link::default() }, ArgvStatus::Parsing )
557}
558
559enum ArgvStatus {
560 Parsing,
561 #[cfg( unix )]
562 Splitted,
563}
564
565impl ArgvStatus {
566 fn needs_parsing( &self ) -> bool {
567 match self {
568 ArgvStatus::Parsing => true,
569 #[cfg( unix )]
570 ArgvStatus::Splitted => false,
571 }
572 }
573}
574
575fn build_tree( mut proc_list: ProcList, argv_status: ArgvStatus ) -> anyhow::Result<ProcList> {
576 proc_list.procs.sort_unstable_by_key( ProcNode::id );
577
578 for i in 0..proc_list.procs.len() {
579 let parent = if proc_list.procs[i].pid != proc_list.procs[i].ppid {
580 proc_list.locate( proc_list.procs[i].ppid )
581 } else {
582 None
583 };
584 proc_list.adopt( parent, i )?;
585
586 if argv_status.needs_parsing() {
587 let arguments = &mut proc_list.procs[i].process.arguments;
588
589 #[cfg( unix )]
590 {
591 let mut args = String::with_capacity( arguments.len() + 8 );
592 mem::swap( &mut args, arguments );
593 let mut splitted_args = cmdline_words_parser::parse_posix( &mut args );
594 while let Some( arg ) = splitted_args.next() {
595 arguments.push_str( arg );
596 arguments.push( '\0' );
597 }
598 }
599
600 #[cfg( windows )]
601 {
602 let mut splitted_argv = win_argv( arguments );
603 mem::swap( arguments, &mut splitted_argv );
604 }
605 }
606 }
607 Ok( proc_list )
608}
609
610pub fn win_argv( cmdline: &str ) -> String {
615 let mut argv = String::new();
616
617 enum Status {
618 Backslashes( usize ),
619 Normal,
620 Spaces,
621 }
622
623 let mut arg = String::new();
624 let mut status = Status::Normal;
625 let mut quoting = false;
626
627 for ch in cmdline.chars() {
628 match status {
629 Status::Backslashes( ref mut count ) => match ch {
630 '\\' => *count += 1,
631 '"' => {
632 for _ in 0..(*count/2) {
633 arg.push('\\');
634 }
635 if *count%2 == 0 {
636 quoting = true;
637 } else {
638 arg.push('"');
639 }
640 status = Status::Normal;
641 },
642 _ => {
643 for _ in 0..*count {
644 arg.push('\\');
645 }
646 arg.push( ch );
647 status = Status::Normal;
648 },
649 },
650 Status::Normal => match ch {
651 '"' => quoting = !quoting,
652 ' '| '\t' => {
653 if quoting {
654 arg.push( ch );
655 } else {
656 status = Status::Spaces;
657 argv.push_str( &arg );
658 arg.clear();
659 argv.push('\0');
660 }
661 },
662 '\\' => status = Status::Backslashes( 1 ),
663 _ => arg.push( ch ),
664 },
665 Status::Spaces => match ch {
666 '"' => {
667 status = Status::Normal;
668 quoting = true;
669 },
670 '\\' => status = Status::Backslashes( 1 ),
671 ' ' | '\t' => (),
672 _ => {
673 status = Status::Normal;
674 arg.push( ch );
675 },
676 },
677 }
678 }
679
680 if !arg.is_empty() {
681 argv.push_str( &arg );
682 argv.push('\0');
683 }
684
685 argv
686}
687
688#[cfg( test )]
689mod tests {
690 #[cfg( target_os = "windows" )]
691 use crate::windows::from_utf16_file;
692
693 use std::{
694 env,
695 path::{Path, PathBuf},
696 };
697
698 use super::*;
699
700 #[cfg( target_os = "freebsd" )]
701 const JSON: &'static str = r#"{
702 "process-information": {
703 "process": [
704 {
705 "pid": "1004",
706 "ppid": "1002",
707 "command": "alphabet",
708 "arguments": "alphabet --alpha 0 --beta 1 --gamma 2"
709 },
710 {
711 "pid": "1005",
712 "ppid": "1001",
713 "command": "cargo",
714 "arguments": "cargo build --no-default-features --featurues \"nigthly no_std\""
715 },
716 {
717 "pid": "1002",
718 "ppid": "1001",
719 "command": "cargo",
720 "arguments": "cargo check"
721 },
722 {
723 "pid": "1003",
724 "ppid": "1002",
725 "command": "foo",
726 "arguments": "foo -bar -baz"
727 },
728
729 {
730 "pid": "1006",
731 "ppid": "1005",
732 "command": "ping",
733 "arguments": "ping 192.168.1.1"
734 },
735 {
736 "pid": "1007",
737 "ppid": "1005",
738 "command": "ps",
739 "arguments": "ps aux"
740 },
741 {
742 "pid": "1001",
743 "ppid": "999",
744 "command": "tcsh",
745 "arguments": "-tcsh (tcsh)"
746 }
747 ]
748 }
749}"#;
750
751 #[cfg( unix )]
752 const TABLE: &'static str = r#" PID PPID COMMAND COMMAND
7531004 1002 alphabet alphabet --alpha 0 --beta 1 --gamma 2
7541005 1001 cargo cargo build --no-default-features --featurues "nigthly no_std"
7551002 1001 cargo cargo check
7561003 1002 foo foo -bar -baz
7571006 1005 ping ping 192.168.1.1
7581007 1005 ps ps aux
7591001 999 tcsh -tcsh (tcsh)"#;
760
761 #[cfg( unix )]
762 fn fake_procfs() -> PathBuf {
763 let manifest_dir = env::var( "CARGO_MANIFEST_DIR" ).unwrap();
764
765 #[cfg( target_os = "freebsd" )]
766 let path = Path::new( &manifest_dir ).join( "test/freebsd/proc" );
767
768 #[cfg( target_os = "linux" )]
769 let path = Path::new( &manifest_dir ).join( "test/linux/proc" );
770
771 path
772 }
773
774 #[cfg( target_os = "windows" )]
775 fn fake_wmic_utf16_file_path() -> PathBuf {
776 let manifest_dir = env::var( "CARGO_MANIFEST_DIR" ).unwrap();
777 Path::new( &manifest_dir ).join( "test" ).join("windows").join( "pals.output" )
778 }
779
780 #[test]
781 fn ps() {
782 #[cfg( target_os = "freebsd" )]
783 assert!( pals_from_ps_json().is_ok() );
784
785 #[cfg( unix )]
786 assert!( pals_from_ps_ww().is_ok() );
787 }
788
789 #[test]
790 fn sort() {
791 #[cfg( target_os = "freebsd" )]
792 (|| {
793 let proc_list = from_json( JSON ).unwrap();
794 assert_eq!( proc_list.procs().map( |proc| proc.pid.0 ).collect::<Vec<_>>(),
795 vec![ 1001, 1002, 1003, 1004, 1005, 1006, 1007 ]);
796 })();
797
798 #[cfg( unix )]
799 (|| {
800 let proc_list = from_table( TABLE ).unwrap();
801 assert_eq!( proc_list.procs().map( |proc| proc.pid.0 ).collect::<Vec<_>>(),
802 vec![ 1001, 1002, 1003, 1004, 1005, 1006, 1007 ]);
803 })();
804
805 #[cfg( any( target_os = "freebsd", target_os = "linux" ))]
806 (|| {
807 let proc_list = pals_from_procfs( &fake_procfs() ).unwrap();
808 assert_eq!( proc_list.procs().map( |proc| proc.pid.0 ).collect::<Vec<_>>(),
809 vec![ 1001, 1002, 1003, 1004, 1005, 1006, 1007 ]);
810 })();
811
812 #[cfg( target_os = "windows" )]
813 (|| {
814 let path = fake_wmic_utf16_file_path();
815 let proc_list = from_utf16_file( &path ).unwrap();
816 assert_eq!( proc_list.procs().map( |proc| proc.pid.0 ).collect::<Vec<_>>(),
817 vec![ 1001, 1002, 1003, 1004, 1005, 1006, 1007 ]);
818 })();
819 }
820
821 #[test]
822 fn bfs() {
823 #[cfg( target_os = "freebsd" )]
824 (|| {
825 let proc_list = from_json( JSON ).unwrap();
826 let visits = proc_list.bfs().iter
827 .map( |visit| (visit.data.pid.0, visit.size.degree, visit.size.descendants) )
828 .collect::<Vec<_>>();
829 assert_eq!( visits, vec![
830 (1001, 2, 6),
831 (1005, 2, 2),
832 (1002, 2, 2),
833 (1007, 0, 0),
834 (1006, 0, 0),
835 (1004, 0, 0),
836 (1003, 0, 0),
837 ]);
838 })();
839
840 #[cfg( unix )]
841 (|| {
842 let proc_list = from_table( TABLE ).unwrap();
843 let visits = proc_list.bfs().iter
844 .map( |visit| (visit.data.pid.0, visit.size.degree, visit.size.descendants) )
845 .collect::<Vec<_>>();
846 assert_eq!( visits, vec![
847 (1001, 2, 6),
848 (1005, 2, 2),
849 (1002, 2, 2),
850 (1007, 0, 0),
851 (1006, 0, 0),
852 (1004, 0, 0),
853 (1003, 0, 0),
854 ]);
855 })();
856
857 #[cfg( any( target_os = "freebsd", target_os = "linux" ))]
858 (|| {
859 let proc_list = pals_from_procfs( &fake_procfs() ).unwrap();
860 let visits = proc_list.bfs().iter
861 .map( |visit| (visit.data.pid.0, visit.size.degree, visit.size.descendants) )
862 .collect::<Vec<_>>();
863 assert_eq!( visits, vec![
864 (1001, 2, 6),
865 (1005, 2, 2),
866 (1002, 2, 2),
867 (1007, 0, 0),
868 (1006, 0, 0),
869 (1004, 0, 0),
870 (1003, 0, 0),
871 ]);
872 })();
873
874 #[cfg( target_os = "windows" )]
875 (|| {
876 let path = fake_wmic_utf16_file_path();
877 let proc_list = from_utf16_file( &path ).unwrap();
878 let visits = proc_list.bfs().iter
879 .map( |visit| (visit.data.pid.0, visit.size.degree, visit.size.descendants) )
880 .collect::<Vec<_>>();
881 assert_eq!( visits, vec![
882 (1001, 2, 6),
883 (1005, 2, 2),
884 (1002, 2, 2),
885 (1007, 0, 0),
886 (1006, 0, 0),
887 (1004, 0, 0),
888 (1003, 0, 0),
889 ]);
890 })();
891 }
892
893 #[test]
894 fn forest() {
895 macro_rules! process {
896 ($pid:expr, $ppid:expr, $comm:expr, $args:expr) => {
897 Process{ pid: Pid($pid), ppid: Pid($ppid), command: $comm.to_owned(), arguments: $args.to_owned(), }
898 }
899 }
900
901 let p1 = &process!( 1001, 999, "tcsh" , "-tcsh\0(tcsh)\0" );
902 let p5 = &process!( 1005, 1001, "cargo" , "cargo\0build\0--no-default-features\0--featurues\0nigthly no_std\0" );
903 let p2 = &process!( 1002, 1001, "cargo" , "cargo\0check\0" );
904 let p7 = &process!( 1007, 1005, "ps" , "ps\0aux\0" );
905 let p6 = &process!( 1006, 1005, "ping" , "ping\0192.168.1.1\0" );
906 let p4 = &process!( 1004, 1002, "alphabet", "alphabet\0--alpha\00\0--beta\01\0--gamma\02\0" );
907 let p3 = &process!( 1003, 1002, "foo" , "foo\0-bar\0-baz\0" );
908
909 #[cfg( target_os = "freebsd" )]
910 (|| {
911 let proc_list = from_json( JSON ).unwrap();
912 let bfs = proc_list.bfs();
913 let forest = trees::Forest::<&Process>::from( bfs );
914 let expected = trees::Forest::<&Process>::from_tuple((
915 (p1, (p5, p7, p6), (p2, p4, p3), ),
916 ));
917 assert_eq!( forest, expected );
918 })();
919
920 #[cfg( unix )]
921 (|| {
922 let proc_list = from_table( TABLE ).unwrap();
923 let bfs = proc_list.bfs();
924 let forest = trees::Forest::<&Process>::from( bfs );
925 let expected = trees::Forest::<&Process>::from_tuple((
926 (p1, (p5, p7, p6), (p2, p4, p3), ),
927 ));
928 assert_eq!( forest, expected );
929 })();
930
931 #[cfg( any( target_os = "freebsd", target_os = "linux" ))]
932 (|| {
933 let proc_list = pals_from_procfs( &fake_procfs() ).unwrap();
934 let bfs = proc_list.bfs();
935 let forest = trees::Forest::<&Process>::from( bfs );
936 let expected = trees::Forest::<&Process>::from_tuple((
937 (p1, (p5, p7, p6), (p2, p4, p3), ),
938 ));
939 assert_eq!( forest, expected );
940 })();
941
942 #[cfg( target_os = "windows" )]
943 (|| {
944 let path = fake_wmic_utf16_file_path();
945 let proc_list = from_utf16_file( &path ).unwrap();
946 let bfs = proc_list.bfs();
947 let forest = trees::Forest::<&Process>::from( bfs );
948 let expected = trees::Forest::<&Process>::from_tuple((
949 (p1, (p5, p7, p6), (p2, p4, p3), ),
950 ));
951 assert_eq!( forest, expected );
952 })();
953 }
954
955 #[test]
956 fn win_argv_works() {
957 assert_eq!( argv_iter( &win_argv( r#""abc" d e"# )).collect::<Vec<_>>(), vec![ "abc" , "d" , "e" ]);
958 assert_eq!( argv_iter( &win_argv( r#"a\\\b d"e f"g h"# )).collect::<Vec<_>>(), vec![r#"a\\\b"# , "de fg", "h" ]);
959 assert_eq!( argv_iter( &win_argv( r#"a\\\"b c d"# )).collect::<Vec<_>>(), vec![r#"a\"b"# , "c" , "d" ]);
960 assert_eq!( argv_iter( &win_argv( r#"a\\\\"b c" d e"# )).collect::<Vec<_>>(), vec![r#"a\\b c"#, "d" , "e" ]);
961 }
962
963 #[test]
964 fn display() {
965
966 #[cfg( any( target_os = "freebsd", target_os = "linux" ))]
967 {
968 let proc_list = pals_from_procfs( &fake_procfs() ).unwrap();
969 assert_eq!( proc_list.to_string(),
970r#"[{cmd: "tcsh", pid:1001, args:[ "-tcsh", "(tcsh)" ], subs:[
971 {cmd: "cargo", pid:1005, args:[ "cargo", "build", "--no-default-features", "--featurues", "nigthly no_std" ], subs:[
972 {cmd: "ps", pid:1007, args:[ "ps", "aux" ]},
973 {cmd: "ping", pid:1006, args:[ "ping", "192.168.1.1" ]}]},
974 {cmd: "cargo", pid:1002, args:[ "cargo", "check" ], subs:[
975 {cmd: "alphabet", pid:1004, args:[ "alphabet", "--alpha", "0", "--beta", "1", "--gamma", "2" ]},
976 {cmd: "foo", pid:1003, args:[ "foo", "-bar", "-baz" ]}]}]}]"# );
977 }
978 }
979}