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.
@@ -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