sys-proctree 0.0.1

Sign up to get free protection for your applications and to get access to all the features.
@@ -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,17 @@
1
+ module Sys
2
+ module ProcTree
3
+
4
+ class ProcessStatusList < Array
5
+
6
+ def initialize
7
+ super(::Sys::ProcTable.ps)
8
+ end
9
+
10
+ def exists?(pid)
11
+ !!find { |proc| proc.pid == pid }
12
+ end
13
+
14
+ end
15
+
16
+ end
17
+ 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
@@ -0,0 +1,5 @@
1
+ module Sys
2
+ module ProcTree
3
+ VERSION = "0.0.1"
4
+ end
5
+ end
@@ -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
@@ -0,0 +1,7 @@
1
+ describe ::Process do
2
+
3
+ it "should have the capability to kill a process tree" do
4
+ ::Process.should be_a(::Sys::ProcTree::Process)
5
+ end
6
+
7
+ end
@@ -0,0 +1,3 @@
1
+ @echo off
2
+
3
+ start /b irb.exe
@@ -0,0 +1,3 @@
1
+ #!/bin/sh
2
+
3
+ irb
@@ -0,0 +1,3 @@
1
+ @echo off
2
+
3
+ start /b %CD%/start_child.bat
@@ -0,0 +1,4 @@
1
+ #!/bin/sh
2
+
3
+ BASEDIR=$(dirname $0)
4
+ $BASEDIR/start_child.sh
@@ -0,0 +1,6 @@
1
+ require 'cover_me'
2
+
3
+ require_relative "../lib/sys/proctree"
4
+ Bundler.require(:test)
5
+
6
+ Dir[File.expand_path('../support/**/*.rb', __FILE__)].each { |file| require file }
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