# 01-func.t # Test the public BSD::Process routines # # Copyright (C) 2006-2007 David Landgren use strict; use Test::More; use Config; my $Unchanged = 'The scalar remains the same'; $_ = $Unchanged; my $RUNNING_ON_FREEBSD_4 = $Config{osvers} =~ /^4/; my $RUNNING_ON_FREEBSD_5 = $Config{osvers} =~ /^5/; plan tests => 152 + scalar(BSD::Process::attr()); BEGIN { use_ok('BSD::Process'); } my $info = BSD::Process::info(); # remove all attributes from object, should be none left over ok( defined( delete $info->{pid} ), 'attribute pid'); ok( defined( delete $info->{ppid} ), 'attribute ppid'); ok( defined( delete $info->{pgid} ), 'attribute pgid'); ok( defined( delete $info->{tpgid} ), 'attribute tpgid'); ok( defined( delete $info->{sid} ), 'attribute sid'); ok( defined( delete $info->{jobc} ), 'attribute jobc'); ok( defined( delete $info->{rssize} ), 'attribute rssize'); ok( defined( delete $info->{swrss} ), 'attribute swrss'); ok( defined( delete $info->{tsize} ), 'attribute tsize'); ok( defined( delete $info->{xstat} ), 'attribute xstat'); ok( defined( delete $info->{acflag} ), 'attribute acflag'); ok( defined( delete $info->{pctcpu} ), 'attribute pctcpu'); ok( defined( delete $info->{estcpu} ), 'attribute estcpu'); ok( defined( delete $info->{slptime} ), 'attribute slptime'); ok( defined( delete $info->{swtime} ), 'attribute swtime'); ok( defined( delete $info->{runtime} ), 'attribute runtime'); ok( defined( delete $info->{flag} ), 'attribute flag'); ok( defined( delete $info->{nice} ), 'attribute nice'); ok( defined( delete $info->{lock} ), 'attribute lock'); ok( defined( delete $info->{rqindex} ), 'attribute rqindex'); ok( defined( delete $info->{oncpu} ), 'attribute oncpu'); ok( defined( delete $info->{lastcpu} ), 'attribute lastcpu'); ok( defined( delete $info->{wmesg} ), 'attribute wmesg'); ok( defined( delete $info->{login} ), 'attribute login'); ok( defined( delete $info->{comm} ), 'attribute comm'); ok( defined( delete $info->{args} ), 'attribute args'); ok( defined( delete $info->{tsid} ), 'attribute tsid'); ok( defined( delete $info->{uid} ), 'attribute uid'); ok( defined( delete $info->{ruid} ), 'attribute ruid'); ok( defined( delete $info->{svuid} ), 'attribute svuid'); ok( defined( delete $info->{rgid} ), 'attribute rgid'); ok( defined( delete $info->{svgid} ), 'attribute svgid'); ok( defined( delete $info->{size} ), 'attribute size'); ok( defined( delete $info->{dsize} ), 'attribute dsize'); ok( defined( delete $info->{ssize} ), 'attribute ssize'); ok( defined( delete $info->{start} ), 'attribute start'); ok( defined( delete $info->{childtime} ), 'attribute childtime'); ok( defined( delete $info->{advlock} ), 'attribute advlock'); ok( defined( delete $info->{controlt} ), 'attribute controlt'); ok( defined( delete $info->{kthread} ), 'attribute kthread'); ok( defined( delete $info->{noload} ), 'attribute noload'); ok( defined( delete $info->{ppwait} ), 'attribute ppwait'); ok( defined( delete $info->{profil} ), 'attribute profil'); ok( defined( delete $info->{stopprof} ), 'attribute stopprof'); ok( defined( delete $info->{sugid} ), 'attribute sugid'); ok( defined( delete $info->{system} ), 'attribute system'); ok( defined( delete $info->{single_exit} ), 'attribute single_exit'); ok( defined( delete $info->{traced} ), 'attribute traced'); ok( defined( delete $info->{waited} ), 'attribute waited'); ok( defined( delete $info->{wexit} ), 'attribute wexit'); ok( defined( delete $info->{exec} ), 'attribute exec'); ok( defined( delete $info->{kiflag} ), 'attribute kiflag'); ok( defined( delete $info->{locked} ), 'attribute locked'); ok( defined( delete $info->{isctty} ), 'attribute isctty'); ok( defined( delete $info->{issleader} ), 'attribute issleader'); ok( defined( delete $info->{stat} ), 'attribute stat'); ok( defined( delete $info->{stat_1} ), 'attribute stat_1'); ok( defined( delete $info->{stat_2} ), 'attribute stat_2'); ok( defined( delete $info->{stat_3} ), 'attribute stat_3'); ok( defined( delete $info->{stat_4} ), 'attribute stat_4'); ok( defined( delete $info->{stat_5} ), 'attribute stat_5'); ok( defined( delete $info->{stat_6} ), 'attribute stat_6'); ok( defined( delete $info->{stat_7} ), 'attribute stat_7'); ok( defined( delete $info->{ocomm} ), 'attribute ocomm'); ok( defined( delete $info->{lockname} ), 'attribute lockname'); ok( defined( delete $info->{pri_class} ), 'attribute pri_class'); ok( defined( delete $info->{pri_level} ), 'attribute pri_level'); ok( defined( delete $info->{pri_native} ), 'attribute pri_native'); ok( defined( delete $info->{pri_user} ), 'attribute pri_user'); ok( defined( delete $info->{utime} ), 'attribute utime'); ok( defined( delete $info->{stime} ), 'attribute stime'); ok( defined( delete $info->{time} ), 'attribute time (utime+stime)'); ok( defined( delete $info->{maxrss} ), 'attribute maxrss'); ok( defined( delete $info->{ixrss} ), 'attribute ixrss'); ok( defined( delete $info->{idrss} ), 'attribute idrss'); ok( defined( delete $info->{isrss} ), 'attribute isrss'); ok( defined( delete $info->{minflt} ), 'attribute minflt'); ok( defined( delete $info->{majflt} ), 'attribute majflt'); ok( defined( delete $info->{nswap} ), 'attribute nswap'); ok( defined( delete $info->{inblock} ), 'attribute inblock'); ok( defined( delete $info->{oublock} ), 'attribute oublock'); ok( defined( delete $info->{msgsnd} ), 'attribute msgsnd'); ok( defined( delete $info->{msgrcv} ), 'attribute msgrcv'); ok( defined( delete $info->{nsignals} ), 'attribute nsignals'); ok( defined( delete $info->{nvcsw} ), 'attribute nvcsw'); ok( defined( delete $info->{nivcsw} ), 'attribute nivcsw'); ok( defined( delete $info->{hadthreads} ), 'attribute hadthreads'); ok( defined( delete $info->{emul} ), 'attribute emul'); ok( defined( delete $info->{jid} ), 'attribute jid'); ok( defined( delete $info->{numthreads} ), 'attribute numthreads'); ok( defined( delete $info->{utime_ch} ), 'attribute utime_ch'); ok( defined( delete $info->{stime_ch} ), 'attribute stime_ch'); ok( defined( delete $info->{time_ch} ), 'attribute time_ch (utime_ch+stime_ch)'); ok( defined( delete $info->{maxrss_ch} ), 'attribute maxrss_ch'); ok( defined( delete $info->{ixrss_ch} ), 'attribute ixrss_ch'); ok( defined( delete $info->{idrss_ch} ), 'attribute idrss_ch'); ok( defined( delete $info->{isrss_ch} ), 'attribute isrss_ch'); ok( defined( delete $info->{minflt_ch} ), 'attribute minflt_ch'); ok( defined( delete $info->{majflt_ch} ), 'attribute majflt_ch'); ok( defined( delete $info->{nswap_ch} ), 'attribute nswap_ch'); ok( defined( delete $info->{inblock_ch} ), 'attribute inblock_ch'); ok( defined( delete $info->{oublock_ch} ), 'attribute oublock_ch'); ok( defined( delete $info->{msgsnd_ch} ), 'attribute msgsnd_ch'); ok( defined( delete $info->{msgrcv_ch} ), 'attribute msgrcv_ch'); ok( defined( delete $info->{nsignals_ch} ), 'attribute nsignals_ch'); ok( defined( delete $info->{nvcsw_ch} ), 'attribute nvcsw_ch'); ok( defined( delete $info->{nivcsw_ch} ), 'attribute nivcsw_ch'); my $ngroups; ok( defined( $ngroups = delete $info->{ngroups} ), 'attribute ngroups'); # attribute returning non-scalars my $grouplist = delete $info->{groups}; ok( defined($grouplist), 'attribute groups' ); is( ref($grouplist), 'ARRAY', q{... it's a list} ); if ($RUNNING_ON_FREEBSD_4) { pass("... of the expected size (unknowable on FreeBSD 4.x)"); } else { is( scalar(@$grouplist), $ngroups, "... of the expected size" ) or diag("grouplist = (@$grouplist)"); } # check for typos in hv_store calls in Process.xs is( scalar(keys %$info), 0, 'all attributes have been accounted for' ) or diag( 'leftover: ' . join( ',', keys %$info )); my @attribute = BSD::Process::attr; my $max_len = 0; my $proc = BSD::Process::info(); my $exists = 0; for my $attr (@attribute) { if ($max_len < length($attr)) { $max_len = length($attr); } if (exists $proc->{$attr}) { pass("lookup $attr"); delete $proc->{$attr}; ++$exists; } else { fail("lookup $attr"); } } is($max_len, BSD::Process::attr_len, 'length of longest attribute'); is($exists, scalar(@attribute), "all lookups exist"); is(scalar(grep {!/^_/} keys %$proc), 0, 'nothing left to look up'); is(scalar(@attribute), scalar(BSD::Process::attr_alias), 'attributes and aliases'); my @all = BSD::Process::list(); my $all_procs = @all; cmp_ok( scalar(@all), '>', 10, "list of all processes ($all_procs)" ) or diag("proclist: (@all)"); # processes owned by a uid SKIP: { skip( "not supported on FreeBSD 4.x", 4 ) if $RUNNING_ON_FREEBSD_4; # count the processes owned by each uid my %uid; for my $pid (@all) { my $proc = BSD::Process->new($pid); if ($proc) { diag( "proc $proc->{_pid} is a zombie" ) if exists $proc->{_pid} and not exists $proc->{pid}; $uid{$proc->{uid}}++; } else { diag( "new() failed for pid $pid" ); } } # now find the uids that own the most processes my ($biggest, $bigger) = (sort {$uid{$b} <=> $uid{$a} || $a <=> $b} keys %uid )[0,1]; my @proc = BSD::Process::list( uid => $biggest ); cmp_ok( scalar(@proc), '<', $all_procs, "uid $biggest smaller than count of all processes" ); my $biggest_uid = @proc; @proc = BSD::Process::list( effective_user_id => $bigger ); cmp_ok( scalar(@proc), '<', $all_procs, "uid $bigger smaller than count of all processes" ); cmp_ok( scalar(@proc), '<=', $biggest_uid, "uid $bigger smaller or equal to uid $biggest" ); my $all_uid = BSD::Process::all( uid => $biggest ); my $total = scalar(keys %$all_uid); my $same_uid = 0; for my $proc (keys %$all_uid) { ++$same_uid if $all_uid->{$proc}{uid} == $biggest; } is ($total, $same_uid, "same number of processes for uid $biggest" ) or do { diag( "pid: $_ uid: all_uid->{$_}{uid}" ) for keys %$all_uid; }; } # processes owned by a ruid SKIP: { skip( "not supported on FreeBSD 4.x", 5 ) if $RUNNING_ON_FREEBSD_4; # count the processes owned by each real uid my %ruid; for my $pid (@all) { my $proc = BSD::Process->new($pid); $ruid{$proc->{ruid}}++ if defined $proc->{ruid}; } # now find the uids that own the most processes my ($biggest, $bigger) = (sort {$ruid{$b} <=> $ruid{$a} || $a <=> $b} keys %ruid )[0,1]; my @proc = BSD::Process::list( ruid => $biggest ); cmp_ok( scalar(@proc), '<', $all_procs, "ruid $biggest smaller than count of all processes" ); my $biggest_ruid = @proc; @proc = BSD::Process::list( real_user_id => $bigger ); cmp_ok( scalar(@proc), '<', $all_procs, "ruid $bigger smaller than count of all processes" ); cmp_ok( scalar(@proc), '<=', $biggest_ruid, "ruid $bigger smaller or equal to ruid $biggest" ); my $all_ruid = BSD::Process::all( resolve => 1, ruid => $bigger ); my $total = keys %$all_ruid; my $same_uid = 0; my $blessed = 0; for my $proc (keys %$all_ruid) { ++$same_uid if scalar(getpwnam($all_ruid->{$proc}{ruid})) == $bigger; ++$blessed if ref($all_ruid->{$proc}) eq 'BSD::Process'; } is ($total, $same_uid, "same number of processes for ruid $bigger" ) or do { diag( "pid: $_ uid: $all_ruid->{$_}{uid}" ) for keys %$all_ruid; }; is ($total, $blessed, "... and all blessed BSD::Process objects" ); } SKIP: { # processes owned by an effective gid skip( "not supported on FreeBSD 4.x or 5.x", 6 ) if $RUNNING_ON_FREEBSD_4 or $RUNNING_ON_FREEBSD_5; # count the processes owned by each effective gid # kinfo_proc lacks a gid field, so we'll punt with a real gid my %gid; for my $pid (@all) { my $proc = BSD::Process->new($pid); $gid{$proc->{rgid}}++ if defined $proc->{rgid}; } # now find the gids that own the most processes my ($biggest, $bigger) = (sort {$gid{$b} <=> $gid{$a} || $a <=> $b} keys %gid )[0,1]; my @proc = BSD::Process::list( gid => $biggest ); cmp_ok( scalar(@proc), '<', $all_procs, "gid $biggest smaller than count of all processes" ); my $biggest_gid = @proc; @proc = BSD::Process::list( effective_group_id => $bigger ); cmp_ok( scalar(@proc), '<', $all_procs, "gid $bigger smaller than count of all processes" ); cmp_ok( scalar(@proc), '<=', $biggest_gid, "gid $bigger smaller or equal to gid $biggest" ); # processes owned by a rgid my %rgid; for my $pid (@all) { my $proc = BSD::Process->new($pid); $rgid{$proc->{rgid}}++ if defined $proc->{rgid}; } # now find the gids that own the most processes ($biggest, $bigger) = (sort {$rgid{$b} <=> $rgid{$a} || $a <=> $b} keys %rgid )[0,1]; @proc = BSD::Process::list( rgid => $biggest ); cmp_ok( scalar(@proc), '<', $all_procs, "rgid $biggest smaller than count of all processes" ); my $biggest_rgid = @proc; @proc = BSD::Process::list( real_group_id => $bigger ); cmp_ok( scalar(@proc), '<', $all_procs, "rgid $bigger smaller than count of all processes" ); cmp_ok( scalar(@proc), '<=', $biggest_rgid, "rgid $bigger smaller or equal to rgid $biggest" ); } # process groups SKIP: { skip( "not supported on FreeBSD 4.x", 6 ) if $RUNNING_ON_FREEBSD_4; # count the processes in each process group my %pgid; for my $pid (@all) { my $proc = BSD::Process->new($pid); $pgid{$proc->{pgid}}++ if defined $proc->{pgid}; } # now find the process groups with the most members my ($biggest, $bigger) = (sort {$pgid{$b} <=> $pgid{$a} || $a <=> $b} keys %pgid )[0,1]; my @proc = BSD::Process::list( pgid => $biggest ); cmp_ok( scalar(@proc), '<', $all_procs, "pgid $biggest smaller than count of all processes" ); my $biggest_pgid = @proc; @proc = BSD::Process::list( process_group_id => $bigger ); cmp_ok( scalar(@proc), '<', $all_procs, "pgid $bigger smaller than count of all processes" ); cmp_ok( scalar(@proc), '<=', $biggest_pgid, "pgid $bigger smaller or equal to pgid $biggest" ); # process sessions # count the processes in each process session my %sid; for my $pid (@all) { my $proc = BSD::Process->new($pid); $sid{$proc->{sid}}++ if defined $proc->{sid}; } # now find the process groups with the most members ($biggest, $bigger) = (sort {$sid{$b} <=> $sid{$a} || $a <=> $b} keys %sid )[0,1]; @proc = BSD::Process::list( sid => $biggest ); cmp_ok( scalar(@proc), '<', $all_procs, "sid $biggest smaller than count of all processes" ); my $biggest_sid = @proc; @proc = BSD::Process::list( process_session_id => $bigger ); cmp_ok( scalar(@proc), '<', $all_procs, "sid $bigger smaller than count of all processes" ); cmp_ok( scalar(@proc), '<=', $biggest_sid, "sid $bigger smaller or equal to sid $biggest" ); } $info = BSD::Process::info($$); is( $info->{pid}, $$, "system says my pid is the same ($$)" ); isnt( $info->{pid}, $info->{ppid}, 'I am not my parent' ); my $parent = BSD::Process::info($info->{ppid}); is( $parent->{pid}, $info->{ppid}, 'my parent is indeed my parent' ); isnt( $info->{pid}, $parent->{ppid}, 'I am not my grandparent' ); isnt( $parent->{pid}, $parent->{ppid}, 'and my parent is not my grandparent' ); SKIP: { skip( "not supported on FreeBSD 4.x", 6 ) if $RUNNING_ON_FREEBSD_4; my $resolved = BSD::Process::info({resolve => 1}); is( $resolved->{uid}, scalar(getpwuid($info->{uid})), 'resolve implicit pid' ); $resolved = BSD::Process::info($info->{pid}, {resolve => 1}); is( $resolved->{uid}, scalar(getpwuid($info->{uid})), 'resolve explicit pid' ); my $root = BSD::Process::all( uid => 'root' ); my $uid_root_count = 0; $root->{$_}->uid == 0 and ++$uid_root_count for keys %$root; is( $uid_root_count, scalar(keys %$root), q{counted all uid root's processes} ); $root = BSD::Process::all( effective_user_id => 'root' ); $uid_root_count = 0; $root->{$_}->uid == 0 and ++$uid_root_count for keys %$root; is( $uid_root_count, scalar(keys %$root), q{counted all effective uid root's processes} ); $root = BSD::Process::all( ruid => 'root' ); $uid_root_count = 0; for (keys %$root) { if ($root->{$_}->uid == 0) { ++$uid_root_count; } elsif ($root->{$_}->ruid == 0) { ++$uid_root_count; $ENV{PERL_AUTHOR_TESTING} and diag("root proc $_ has uid " . $root->{$_}->uid . "/" . $root->{$_}->ruid ); } } is( $uid_root_count, scalar(keys %$root), q{counted all ruid root's processes} ); $root = BSD::Process::all( real_user_id => 'root' ); $uid_root_count = 0; $root->{$_}->ruid == 0 and ++$uid_root_count for keys %$root; is( $uid_root_count, scalar(keys %$root), q{counted all real_user_id root's processes} ); } SKIP: { skip( "not supported on FreeBSD 4.x or 5.x", 2 ) if $RUNNING_ON_FREEBSD_4 or $RUNNING_ON_FREEBSD_5; my $wheel_gid = getgrnam('wheel'); { my $wheel = BSD::Process::all( gid => 'wheel' ); my $gid_wheel_count = 0; for my $pid (keys %$wheel) { my $proc = $wheel->{$pid}; if ($proc->rgid == $wheel_gid) { ++$gid_wheel_count; } else { my $msg = "$proc->{comm}($proc->{pid}) has rgid $proc->{rgid} not $wheel_gid"; if ($proc->{comm} eq 'sshd') { # sshd uses process separation, which throws this off ++$gid_wheel_count; $msg .= " (pass)"; } $ENV{PERL_AUTHOR_TESTING} and diag( $msg ); } } is( $gid_wheel_count, scalar(keys %$wheel), q{counted all gid wheel's processes} ); } { my $wheel = BSD::Process::all( effective_group_id => 'wheel' ); my $gid_wheel_count = 0; for my $pid (keys %$wheel) { my $proc = $wheel->{$pid}; if ($proc->rgid == $wheel_gid) { ++$gid_wheel_count; } else { my $msg = "$proc->{comm}($proc->{pid}) has rgid $proc->{rgid} not $wheel_gid"; if ($proc->{comm} eq 'sshd') { # sshd uses process separation, which throws this off ++$gid_wheel_count; $msg .= " (pass)"; } $ENV{PERL_AUTHOR_TESTING} and diag( $msg ); } } is( $gid_wheel_count, scalar(keys %$wheel), q{counted all effective_group_id wheel's processes} ); } } is($_, $Unchanged, $Unchanged);