sys-proctree 0.0.1
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- data/lib/sys/proctree/process.rb +21 -0
- data/lib/sys/proctree/process_status_list.rb +17 -0
- data/lib/sys/proctree/tree.rb +38 -0
- data/lib/sys/proctree/version.rb +5 -0
- data/lib/sys/proctree.rb +21 -0
- data/spec/lib/sys/proctree/process_spec.rb +69 -0
- data/spec/lib/sys/proctree/process_status_list_spec.rb +47 -0
- data/spec/lib/sys/proctree/tree_spec.rb +68 -0
- data/spec/lib/sys/proctree_spec.rb +13 -0
- data/spec/process_integration_spec.rb +64 -0
- data/spec/process_spec.rb +7 -0
- data/spec/resources/start_child.bat +3 -0
- data/spec/resources/start_child.sh +3 -0
- data/spec/resources/start_parent.bat +3 -0
- data/spec/resources/start_parent.sh +4 -0
- data/spec/spec_helper.rb +6 -0
- metadata +89 -0
@@ -0,0 +1,21 @@
|
|
1
|
+
module Sys
|
2
|
+
module ProcTree
|
3
|
+
|
4
|
+
module Process
|
5
|
+
|
6
|
+
def kill_tree(signal, pid)
|
7
|
+
pids = ::Sys::ProcTree::Tree.find(pid)
|
8
|
+
pids.collect do |pid|
|
9
|
+
begin
|
10
|
+
::Process.kill(signal, pid)
|
11
|
+
::Process.wait(pid)
|
12
|
+
rescue
|
13
|
+
[pid, nil]
|
14
|
+
end
|
15
|
+
end
|
16
|
+
end
|
17
|
+
|
18
|
+
end
|
19
|
+
|
20
|
+
end
|
21
|
+
end
|
@@ -0,0 +1,38 @@
|
|
1
|
+
module Sys
|
2
|
+
module ProcTree
|
3
|
+
|
4
|
+
class Tree
|
5
|
+
|
6
|
+
class << self
|
7
|
+
|
8
|
+
def find(pid)
|
9
|
+
::Sys::ProcTree::Tree.new(pid).pids
|
10
|
+
end
|
11
|
+
|
12
|
+
end
|
13
|
+
|
14
|
+
def initialize(pid)
|
15
|
+
@pid = pid
|
16
|
+
end
|
17
|
+
|
18
|
+
def pids
|
19
|
+
process_status_list.exists?(@pid) ? with_children([@pid]) : []
|
20
|
+
end
|
21
|
+
|
22
|
+
private
|
23
|
+
|
24
|
+
def with_children(pids)
|
25
|
+
child_pids = process_status_list.map do |proc|
|
26
|
+
pids.include?(proc.ppid) ? proc.pid : nil
|
27
|
+
end.compact
|
28
|
+
child_pids.empty? ? pids : with_children(child_pids) + pids
|
29
|
+
end
|
30
|
+
|
31
|
+
def process_status_list
|
32
|
+
@process_status_list ||= ::Sys::ProcTree::ProcessStatusList.new
|
33
|
+
end
|
34
|
+
|
35
|
+
end
|
36
|
+
|
37
|
+
end
|
38
|
+
end
|
data/lib/sys/proctree.rb
ADDED
@@ -0,0 +1,21 @@
|
|
1
|
+
require 'rubygems'
|
2
|
+
require 'bundler/setup'
|
3
|
+
Bundler.require(:default)
|
4
|
+
|
5
|
+
require_relative "proctree/process_status_list"
|
6
|
+
require_relative "proctree/tree"
|
7
|
+
require_relative "proctree/process"
|
8
|
+
|
9
|
+
::Process.extend(::Sys::ProcTree::Process)
|
10
|
+
|
11
|
+
module Sys
|
12
|
+
|
13
|
+
module ProcTree
|
14
|
+
|
15
|
+
def self.find(pid)
|
16
|
+
::Sys::ProcTree::Tree.find(pid)
|
17
|
+
end
|
18
|
+
|
19
|
+
end
|
20
|
+
|
21
|
+
end
|
@@ -0,0 +1,69 @@
|
|
1
|
+
describe ::Sys::ProcTree::Process do
|
2
|
+
|
3
|
+
class TestableProcess
|
4
|
+
extend Sys::ProcTree::Process
|
5
|
+
end
|
6
|
+
|
7
|
+
describe "#kill_tree" do
|
8
|
+
|
9
|
+
let(:kill_signal) { 7 }
|
10
|
+
let(:tree_pids) { [2, 6, 8] }
|
11
|
+
|
12
|
+
before(:each) do
|
13
|
+
::Sys::ProcTree::Tree.stub!(:find).and_return([2, 6, 8])
|
14
|
+
::Process.stub!(:kill)
|
15
|
+
::Process.stub!(:wait)
|
16
|
+
end
|
17
|
+
|
18
|
+
it "should discover the pids of the process tree" do
|
19
|
+
::Sys::ProcTree::Tree.should_receive(:find).with(8).and_return(tree_pids)
|
20
|
+
|
21
|
+
TestableProcess.kill_tree(kill_signal, 8)
|
22
|
+
end
|
23
|
+
|
24
|
+
it "should attempt to kill each process with the provided kill signal in tree order" do
|
25
|
+
tree_pids.each { |pid| ::Process.should_receive(:kill).with(kill_signal, pid) }
|
26
|
+
|
27
|
+
TestableProcess.kill_tree(kill_signal, 8)
|
28
|
+
end
|
29
|
+
|
30
|
+
|
31
|
+
it "should wait for all processes to complete" do
|
32
|
+
tree_pids.each { |pid| ::Process.should_receive(:wait).with(pid) }
|
33
|
+
|
34
|
+
TestableProcess.kill_tree(kill_signal, 8)
|
35
|
+
end
|
36
|
+
|
37
|
+
it "should return the exit status of all killed processes" do
|
38
|
+
wait_results = tree_pids.map do |pid|
|
39
|
+
[pid, double(Process::Status)].tap { |result| ::Process.stub!(:wait).with(pid).and_return(result) }
|
40
|
+
end
|
41
|
+
|
42
|
+
TestableProcess.kill_tree(kill_signal, 8).should eql(wait_results)
|
43
|
+
end
|
44
|
+
|
45
|
+
describe "when a process has already been killed" do
|
46
|
+
|
47
|
+
let(:kill_signal) { 9 }
|
48
|
+
|
49
|
+
before(:each) { ::Process.stub!(:kill).with(kill_signal, 6).and_raise("No such process") }
|
50
|
+
|
51
|
+
it "should continue to kill subsequent processes" do
|
52
|
+
[2, 8].each { |pid| ::Process.should_receive(:kill).with(kill_signal, pid) }
|
53
|
+
|
54
|
+
TestableProcess.kill_tree(kill_signal, 8)
|
55
|
+
end
|
56
|
+
|
57
|
+
it "should return an exit status of nil" do
|
58
|
+
wait_results = [[2, double(Process::Status)], [6, nil], [8, double(Process::Status)]]
|
59
|
+
::Process.stub!(:wait).with(2).and_return(wait_results[0])
|
60
|
+
::Process.stub!(:wait).with(8).and_return(wait_results[2])
|
61
|
+
|
62
|
+
TestableProcess.kill_tree(kill_signal, 8).should eql(wait_results)
|
63
|
+
end
|
64
|
+
|
65
|
+
end
|
66
|
+
|
67
|
+
end
|
68
|
+
|
69
|
+
end
|
@@ -0,0 +1,47 @@
|
|
1
|
+
describe ::Sys::ProcTree::ProcessStatusList do
|
2
|
+
|
3
|
+
before(:each) { ::Sys::ProcTable.stub!(:ps).and_return(proc_list) }
|
4
|
+
|
5
|
+
let(:proc_list) do
|
6
|
+
[double("ProcTableStruct", pid: 1, ppid: 2),
|
7
|
+
double("ProcTableStruct", pid: 2, ppid: 3),
|
8
|
+
double("ProcTableStruct", pid: 3, ppid: nil)]
|
9
|
+
end
|
10
|
+
|
11
|
+
let(:list) { ::Sys::ProcTree::ProcessStatusList.new }
|
12
|
+
|
13
|
+
it "should be an array" do
|
14
|
+
list.should be_an(Array)
|
15
|
+
end
|
16
|
+
|
17
|
+
it "should contain proc table process status results" do
|
18
|
+
::Sys::ProcTable.should_receive(:ps).and_return(proc_list)
|
19
|
+
|
20
|
+
list.should eql(proc_list)
|
21
|
+
end
|
22
|
+
|
23
|
+
describe "exists?" do
|
24
|
+
|
25
|
+
describe "when the process exists" do
|
26
|
+
|
27
|
+
let(:pid) { 2 }
|
28
|
+
|
29
|
+
it "should return true" do
|
30
|
+
list.exists?(pid).should be_true
|
31
|
+
end
|
32
|
+
|
33
|
+
end
|
34
|
+
|
35
|
+
describe "when the process does not exist" do
|
36
|
+
|
37
|
+
let(:pid) { -1 }
|
38
|
+
|
39
|
+
it "should return false" do
|
40
|
+
list.exists?(pid).should be_false
|
41
|
+
end
|
42
|
+
|
43
|
+
end
|
44
|
+
|
45
|
+
end
|
46
|
+
|
47
|
+
end
|
@@ -0,0 +1,68 @@
|
|
1
|
+
describe ::Sys::ProcTree::Tree do
|
2
|
+
|
3
|
+
let(:tree) { ::Sys::ProcTree::Tree }
|
4
|
+
|
5
|
+
describe "#find" do
|
6
|
+
|
7
|
+
before(:each) do
|
8
|
+
::Sys::ProcTree::ProcessStatusList.should_receive(:new).and_return(process_status_list)
|
9
|
+
end
|
10
|
+
|
11
|
+
let(:process_status_list) do
|
12
|
+
create_proc_table_entries([{ pid: 2, ppid: 1 },
|
13
|
+
{ pid: 1, ppid: nil },
|
14
|
+
{ pid: 3, ppid: 2 },
|
15
|
+
{ pid: 4, ppid: nil },
|
16
|
+
{ pid: 5, ppid: 6 },
|
17
|
+
{ pid: 6, ppid: nil },
|
18
|
+
{ pid: 7, ppid: 6 },
|
19
|
+
{ pid: 8, ppid: 6 }])
|
20
|
+
end
|
21
|
+
|
22
|
+
describe "when the provided process exists" do
|
23
|
+
|
24
|
+
before(:each) { process_status_list.stub!(:exists?).with(pid).and_return(true) }
|
25
|
+
|
26
|
+
describe "when the tree is more than one level deep" do
|
27
|
+
|
28
|
+
describe "with one process on each level" do
|
29
|
+
|
30
|
+
let(:pid) { 1 }
|
31
|
+
|
32
|
+
it "should return an array representing the tree in order from child first to parent last" do
|
33
|
+
tree.find(pid).should eql([3, 2, 1])
|
34
|
+
end
|
35
|
+
|
36
|
+
end
|
37
|
+
|
38
|
+
describe "with multiple processes on a level" do
|
39
|
+
|
40
|
+
let(:pid) { 6 }
|
41
|
+
|
42
|
+
it "should return an array containing processes order from child to parent, then as they appear in the underlying process table" do
|
43
|
+
tree.find(pid).should eql([5, 7, 8, 6])
|
44
|
+
end
|
45
|
+
|
46
|
+
end
|
47
|
+
|
48
|
+
end
|
49
|
+
|
50
|
+
end
|
51
|
+
|
52
|
+
describe "when the provided process does not exist" do
|
53
|
+
|
54
|
+
before(:each) { process_status_list.stub!(:exists?).and_return(false) }
|
55
|
+
|
56
|
+
it "should return an empty array" do
|
57
|
+
tree.find(0).should be_empty
|
58
|
+
end
|
59
|
+
|
60
|
+
end
|
61
|
+
|
62
|
+
def create_proc_table_entries(options_array)
|
63
|
+
options_array.map { |options| double("ProcTableStruct", options) }
|
64
|
+
end
|
65
|
+
|
66
|
+
end
|
67
|
+
|
68
|
+
end
|
@@ -0,0 +1,13 @@
|
|
1
|
+
describe ::Sys::ProcTree do
|
2
|
+
|
3
|
+
describe "#find" do
|
4
|
+
|
5
|
+
it "should find a tree via a Sys::ProcTree::Tree" do
|
6
|
+
::Sys::ProcTree::Tree.should_receive(:find).with(7).and_return([1, 3, 7])
|
7
|
+
|
8
|
+
::Sys::ProcTree.find(7).should eql([1, 3, 7])
|
9
|
+
end
|
10
|
+
|
11
|
+
end
|
12
|
+
|
13
|
+
end
|
@@ -0,0 +1,64 @@
|
|
1
|
+
describe ::Process, "integrating with real processes" do
|
2
|
+
|
3
|
+
RESOURCE_DIR = File.expand_path("../resources", __FILE__)
|
4
|
+
|
5
|
+
FIVE_SECONDS = 5
|
6
|
+
|
7
|
+
describe "#kill_tree" do
|
8
|
+
|
9
|
+
describe "when one process is running" do
|
10
|
+
|
11
|
+
let(:pid) { ::Process.spawn("irb") }
|
12
|
+
|
13
|
+
it "should kill the process" do
|
14
|
+
::Process.kill_tree(9, pid)
|
15
|
+
|
16
|
+
wait_until_killed([pid])
|
17
|
+
end
|
18
|
+
|
19
|
+
end
|
20
|
+
|
21
|
+
describe "when multiple processes are running" do
|
22
|
+
|
23
|
+
let(:ppid) { start_multiple_processes }
|
24
|
+
|
25
|
+
before(:each) do
|
26
|
+
wait_until_true("process tree has started") { ::Sys::ProcTree::Tree.find(ppid).length == 3 }
|
27
|
+
end
|
28
|
+
|
29
|
+
it "should kill all processes in the tree" do
|
30
|
+
tree_pids = ::Sys::ProcTree::Tree.find(ppid)
|
31
|
+
|
32
|
+
::Process.kill_tree(9, ppid)
|
33
|
+
|
34
|
+
wait_until_killed(tree_pids)
|
35
|
+
end
|
36
|
+
|
37
|
+
end
|
38
|
+
|
39
|
+
end
|
40
|
+
|
41
|
+
def start_multiple_processes
|
42
|
+
::Process.spawn("#{RESOURCE_DIR}/start_parent.#{::OS.windows? ? "bat" : "sh"}")
|
43
|
+
end
|
44
|
+
|
45
|
+
def wait_until_killed(pids)
|
46
|
+
wait_until_true("processes #{pids.join(", ")} have been killed") do
|
47
|
+
process_statuses = ::Sys::ProcTree::ProcessStatusList.new
|
48
|
+
pids.reduce(true) { |all_killed_flag, pid| all_killed_flag && !process_statuses.exists?(pid) }
|
49
|
+
end
|
50
|
+
end
|
51
|
+
|
52
|
+
def wait_until_true(description, &block)
|
53
|
+
start_time = Time.now
|
54
|
+
while true
|
55
|
+
begin
|
56
|
+
return if block.call
|
57
|
+
rescue
|
58
|
+
# Intentionally blank
|
59
|
+
end
|
60
|
+
raise "Timed-out waiting until '#{description}'" if Time.now - start_time > FIVE_SECONDS
|
61
|
+
end
|
62
|
+
end
|
63
|
+
|
64
|
+
end
|
data/spec/spec_helper.rb
ADDED
metadata
ADDED
@@ -0,0 +1,89 @@
|
|
1
|
+
--- !ruby/object:Gem::Specification
|
2
|
+
name: sys-proctree
|
3
|
+
version: !ruby/object:Gem::Version
|
4
|
+
version: 0.0.1
|
5
|
+
prerelease:
|
6
|
+
platform: ruby
|
7
|
+
authors:
|
8
|
+
- Matthew Ueckerman
|
9
|
+
autorequire:
|
10
|
+
bindir: bin
|
11
|
+
cert_chain: []
|
12
|
+
date: 2013-02-19 00:00:00.000000000 Z
|
13
|
+
dependencies:
|
14
|
+
- !ruby/object:Gem::Dependency
|
15
|
+
name: sys-proctable
|
16
|
+
requirement: !ruby/object:Gem::Requirement
|
17
|
+
none: false
|
18
|
+
requirements:
|
19
|
+
- - ~>
|
20
|
+
- !ruby/object:Gem::Version
|
21
|
+
version: 0.9.2
|
22
|
+
type: :runtime
|
23
|
+
prerelease: false
|
24
|
+
version_requirements: !ruby/object:Gem::Requirement
|
25
|
+
none: false
|
26
|
+
requirements:
|
27
|
+
- - ~>
|
28
|
+
- !ruby/object:Gem::Version
|
29
|
+
version: 0.9.2
|
30
|
+
description: Discovers and kills process trees via process lists provided by the sys-proctable
|
31
|
+
gem
|
32
|
+
email: matthew.ueckerman@myob.com
|
33
|
+
executables: []
|
34
|
+
extensions: []
|
35
|
+
extra_rdoc_files: []
|
36
|
+
files:
|
37
|
+
- ./lib/sys/proctree/process.rb
|
38
|
+
- ./lib/sys/proctree/process_status_list.rb
|
39
|
+
- ./lib/sys/proctree/tree.rb
|
40
|
+
- ./lib/sys/proctree/version.rb
|
41
|
+
- ./lib/sys/proctree.rb
|
42
|
+
- ./spec/lib/sys/proctree/process_spec.rb
|
43
|
+
- ./spec/lib/sys/proctree/process_status_list_spec.rb
|
44
|
+
- ./spec/lib/sys/proctree/tree_spec.rb
|
45
|
+
- ./spec/lib/sys/proctree_spec.rb
|
46
|
+
- ./spec/process_integration_spec.rb
|
47
|
+
- ./spec/process_spec.rb
|
48
|
+
- ./spec/resources/start_child.bat
|
49
|
+
- ./spec/resources/start_child.sh
|
50
|
+
- ./spec/resources/start_parent.bat
|
51
|
+
- ./spec/resources/start_parent.sh
|
52
|
+
- ./spec/spec_helper.rb
|
53
|
+
homepage: http://github.com/MYOB-Technology/sys-proctree
|
54
|
+
licenses:
|
55
|
+
- MIT
|
56
|
+
post_install_message:
|
57
|
+
rdoc_options: []
|
58
|
+
require_paths:
|
59
|
+
- lib
|
60
|
+
required_ruby_version: !ruby/object:Gem::Requirement
|
61
|
+
none: false
|
62
|
+
requirements:
|
63
|
+
- - ! '>='
|
64
|
+
- !ruby/object:Gem::Version
|
65
|
+
version: 1.9.3
|
66
|
+
required_rubygems_version: !ruby/object:Gem::Requirement
|
67
|
+
none: false
|
68
|
+
requirements:
|
69
|
+
- - ! '>='
|
70
|
+
- !ruby/object:Gem::Version
|
71
|
+
version: '0'
|
72
|
+
requirements: []
|
73
|
+
rubyforge_project: sys-proctree
|
74
|
+
rubygems_version: 1.8.25
|
75
|
+
signing_key:
|
76
|
+
specification_version: 3
|
77
|
+
summary: Discovers and can attempt to lay waste to a process tree
|
78
|
+
test_files:
|
79
|
+
- ./spec/lib/sys/proctree/process_spec.rb
|
80
|
+
- ./spec/lib/sys/proctree/process_status_list_spec.rb
|
81
|
+
- ./spec/lib/sys/proctree/tree_spec.rb
|
82
|
+
- ./spec/lib/sys/proctree_spec.rb
|
83
|
+
- ./spec/process_integration_spec.rb
|
84
|
+
- ./spec/process_spec.rb
|
85
|
+
- ./spec/resources/start_child.bat
|
86
|
+
- ./spec/resources/start_child.sh
|
87
|
+
- ./spec/resources/start_parent.bat
|
88
|
+
- ./spec/resources/start_parent.sh
|
89
|
+
- ./spec/spec_helper.rb
|