unobtainium 0.2.1 → 0.3.0
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.
- checksums.yaml +4 -4
- data/.gitignore +4 -0
- data/.rubocop.yml +6 -0
- data/Gemfile.lock +44 -0
- data/README.md +17 -14
- data/Rakefile +20 -4
- data/config/config.yml +14 -0
- data/docs/CONFIGURATION.md +250 -0
- data/{cuke/features → features}/step_definitions/steps.rb +0 -0
- data/{cuke/features → features}/support/env.rb +0 -0
- data/{cuke/features → features}/world.feature +0 -0
- data/lib/unobtainium/config.rb +31 -10
- data/lib/unobtainium/driver.rb +55 -36
- data/lib/unobtainium/drivers/appium.rb +16 -13
- data/lib/unobtainium/drivers/phantom.rb +138 -0
- data/lib/unobtainium/drivers/selenium.rb +6 -5
- data/lib/unobtainium/pathed_hash.rb +29 -7
- data/lib/unobtainium/recursive_merge.rb +15 -0
- data/lib/unobtainium/runtime.rb +33 -8
- data/lib/unobtainium/support/port_scanner.rb +160 -0
- data/lib/unobtainium/support/runner.rb +180 -0
- data/lib/unobtainium/{drivers/support → support}/util.rb +10 -3
- data/lib/unobtainium/version.rb +2 -1
- data/lib/unobtainium/world.rb +6 -3
- data/spec/port_scanner_spec.rb +131 -0
- data/spec/runner_spec.rb +66 -0
- data/spec/utility_spec.rb +31 -0
- data/unobtainium.gemspec +9 -1
- metadata +105 -12
- data/cuke/.gitignore +0 -2
- data/cuke/Gemfile +0 -13
- data/cuke/Gemfile.lock +0 -59
- data/cuke/README.md +0 -2
- data/cuke/config/config.yml +0 -7
@@ -7,9 +7,11 @@
|
|
7
7
|
# All rights reserved.
|
8
8
|
#
|
9
9
|
|
10
|
-
require_relative '
|
10
|
+
require_relative '../support/util'
|
11
11
|
|
12
12
|
module Unobtainium
|
13
|
+
# @api private
|
14
|
+
# Contains driver implementations
|
13
15
|
module Drivers
|
14
16
|
|
15
17
|
##
|
@@ -20,13 +22,12 @@ module Unobtainium
|
|
20
22
|
firefox: [:ff,],
|
21
23
|
internet_explorer: [:internetexplorer, :explorer, :ie,],
|
22
24
|
safari: [],
|
23
|
-
chrome: [],
|
24
|
-
chromium: [],
|
25
|
+
chrome: [:chromium],
|
25
26
|
remote: [],
|
26
27
|
}.freeze
|
27
28
|
|
28
29
|
class << self
|
29
|
-
include ::Unobtainium::
|
30
|
+
include ::Unobtainium::Support::Utility
|
30
31
|
|
31
32
|
##
|
32
33
|
# Return true if the given label matches this driver implementation,
|
@@ -47,7 +48,7 @@ module Unobtainium
|
|
47
48
|
|
48
49
|
##
|
49
50
|
# Selenium really wants symbol keys for the options
|
50
|
-
def
|
51
|
+
def resolve_options(label, options)
|
51
52
|
new_opts = {}
|
52
53
|
|
53
54
|
if not options.nil?
|
@@ -13,14 +13,25 @@ module Unobtainium
|
|
13
13
|
|
14
14
|
##
|
15
15
|
# The PathedHash class wraps Hash by offering pathed access on top of
|
16
|
-
# regular access, i.e. instead of h["first"]["second"] you can write
|
17
|
-
# h["first.second"]
|
16
|
+
# regular access, i.e. instead of `h["first"]["second"]` you can write
|
17
|
+
# `h["first.second"]`.
|
18
|
+
#
|
19
|
+
# The main benefit is much simpler code for accessing nested structured.
|
20
|
+
# For any given path, PathedHash will return nil from `[]` if *any* of
|
21
|
+
# the path components do not exist.
|
22
|
+
#
|
23
|
+
# Similarly, intermediate nodes will be created when you write a value
|
24
|
+
# for a path.
|
25
|
+
#
|
26
|
+
# PathedHash also includes RecursiveMerge.
|
18
27
|
class PathedHash
|
19
28
|
include RecursiveMerge
|
20
29
|
|
21
30
|
##
|
22
|
-
# Initializer
|
23
|
-
|
31
|
+
# Initializer. Accepts `nil`, hashes or pathed hashes.
|
32
|
+
#
|
33
|
+
# @param init [NilClass, Hash] initial values.
|
34
|
+
def initialize(init = nil)
|
24
35
|
if init.nil?
|
25
36
|
@data = {}
|
26
37
|
else
|
@@ -29,18 +40,23 @@ module Unobtainium
|
|
29
40
|
@separator = '.'
|
30
41
|
end
|
31
42
|
|
32
|
-
#
|
43
|
+
# @return [String] the separator is the character or pattern splitting paths.
|
33
44
|
attr_accessor :separator
|
34
45
|
|
46
|
+
# @api private
|
47
|
+
# Methods redefined to support pathed read access.
|
35
48
|
READ_METHODS = [
|
36
49
|
:[], :default, :delete, :fetch, :has_key?, :include?, :key?, :member?,
|
37
50
|
].freeze
|
51
|
+
|
52
|
+
# @api private
|
53
|
+
# Methods redefined to support pathed write access.
|
38
54
|
WRITE_METHODS = [
|
39
55
|
:[]=, :store,
|
40
56
|
].freeze
|
41
57
|
|
42
58
|
##
|
43
|
-
#
|
59
|
+
# @return [RegExp] the pattern to split paths at; based on `separator`
|
44
60
|
def split_pattern
|
45
61
|
/(?<!\\)#{Regexp.escape(@separator)}/
|
46
62
|
end
|
@@ -99,14 +115,18 @@ module Unobtainium
|
|
99
115
|
end
|
100
116
|
end
|
101
117
|
|
118
|
+
# @return [String] string representation
|
102
119
|
def to_s
|
103
120
|
@data.to_s
|
104
121
|
end
|
105
122
|
|
123
|
+
# @return [PathedHash] duplicate, as `.dup` usually works
|
106
124
|
def dup
|
107
125
|
PathedHash.new(@data.dup)
|
108
126
|
end
|
109
127
|
|
128
|
+
# In place merge, as it usually works for hashes.
|
129
|
+
# @return [PathedHash] self
|
110
130
|
def merge!(*args, &block)
|
111
131
|
# FIXME: we may need other methods like this. This is used by
|
112
132
|
# RecursiveMerge, so we know it's required.
|
@@ -114,7 +134,7 @@ module Unobtainium
|
|
114
134
|
end
|
115
135
|
|
116
136
|
##
|
117
|
-
# Map any missing method to the
|
137
|
+
# Map any missing method to the Hash implementation
|
118
138
|
def respond_to?(meth)
|
119
139
|
if not @data.nil? and @data.respond_to?(meth)
|
120
140
|
return true
|
@@ -122,6 +142,8 @@ module Unobtainium
|
|
122
142
|
return super
|
123
143
|
end
|
124
144
|
|
145
|
+
##
|
146
|
+
# Map any missing method to the Hash implementation
|
125
147
|
def method_missing(meth, *args, &block)
|
126
148
|
if not @data.nil? and @data.respond_to?(meth)
|
127
149
|
return @data.send(meth.to_s, *args, &block)
|
@@ -11,6 +11,18 @@ module Unobtainium
|
|
11
11
|
##
|
12
12
|
# Provides recursive merge functions for hashes. Used in PathedHash.
|
13
13
|
module RecursiveMerge
|
14
|
+
##
|
15
|
+
# Recursively merge `:other` into this Hash.
|
16
|
+
#
|
17
|
+
# This starts by merging the leaf-most Hash entries. Arrays are merged
|
18
|
+
# by addition.
|
19
|
+
#
|
20
|
+
# For everything that's neither Hash or Array, if the `:overwrite`
|
21
|
+
# parameter is true, the entry from `:other` is used. Otherwise the entry
|
22
|
+
# from `:self` is used.
|
23
|
+
#
|
24
|
+
# @param other [Hash] the hash to merge into `:self`
|
25
|
+
# @param overwrite [Boolean] see method description.
|
14
26
|
def recursive_merge!(other, overwrite = true)
|
15
27
|
if other.nil?
|
16
28
|
return self
|
@@ -33,6 +45,9 @@ module Unobtainium
|
|
33
45
|
merge!(other, &merger)
|
34
46
|
end
|
35
47
|
|
48
|
+
##
|
49
|
+
# Same as `dup.recursive_merge!`
|
50
|
+
# @param (see #recursive_merge!)
|
36
51
|
def recursive_merge(other, overwrite = true)
|
37
52
|
dup.recursive_merge!(other, overwrite)
|
38
53
|
end
|
data/lib/unobtainium/runtime.rb
CHANGED
@@ -34,13 +34,14 @@ module Unobtainium
|
|
34
34
|
end
|
35
35
|
|
36
36
|
##
|
37
|
-
#
|
37
|
+
# @return [Integer] number of objects stored in the object map
|
38
38
|
def length
|
39
39
|
return @objects.length
|
40
40
|
end
|
41
41
|
|
42
42
|
##
|
43
|
-
#
|
43
|
+
# @param name [String, Symbol] name or label for an object
|
44
|
+
# @return [Boolean] does an object with the given name exist?
|
44
45
|
def has?(name)
|
45
46
|
return @objects.key?(name)
|
46
47
|
end
|
@@ -51,8 +52,14 @@ module Unobtainium
|
|
51
52
|
# is stored.
|
52
53
|
#
|
53
54
|
# If a destructor is passed, it is used to destroy the *new* object only.
|
54
|
-
# If no destructor is passed and the object responds to a
|
55
|
-
# method is called.
|
55
|
+
# If no destructor is passed and the object responds to a `#destroy` method,
|
56
|
+
# that method is called.
|
57
|
+
#
|
58
|
+
# @param name [String, Symbol] name or label for the object to store
|
59
|
+
# @param object [Object] the object to store
|
60
|
+
# @param destructor [Func] a custom destructor accepting the object as its
|
61
|
+
# parameter.
|
62
|
+
# @return [Object] the stored object
|
56
63
|
def store(name, object, destructor = nil)
|
57
64
|
delete(name)
|
58
65
|
|
@@ -65,7 +72,13 @@ module Unobtainium
|
|
65
72
|
# Store the object returned by the block, if any. If no object is returned
|
66
73
|
# or no block is given, this function does nothing.
|
67
74
|
#
|
68
|
-
# Otherwise it works much like
|
75
|
+
# Otherwise it works much like `#store`.
|
76
|
+
#
|
77
|
+
# @param name [String, Symbol] name or label for the object to store
|
78
|
+
# @param destructor [Func] a custom destructor accepting the object as its
|
79
|
+
# parameter.
|
80
|
+
# @param block [Func] a block returning the created object.
|
81
|
+
# @return [Object] the stored object
|
69
82
|
def store_with(name, destructor = nil, &block)
|
70
83
|
object = nil
|
71
84
|
if not block.nil?
|
@@ -80,7 +93,8 @@ module Unobtainium
|
|
80
93
|
end
|
81
94
|
|
82
95
|
##
|
83
|
-
#
|
96
|
+
# (see #store)
|
97
|
+
# Like `#store`, but only stores the object if none exists for that key yet.
|
84
98
|
def store_if(name, object, destructor = nil)
|
85
99
|
if has?(name)
|
86
100
|
return self[name]
|
@@ -89,7 +103,8 @@ module Unobtainium
|
|
89
103
|
end
|
90
104
|
|
91
105
|
##
|
92
|
-
#
|
106
|
+
# (see #store_with)
|
107
|
+
# Like `#store_if`, but as a block version similar to `#store_with`.
|
93
108
|
def store_with_if(name, destructor = nil, &block)
|
94
109
|
if has?(name)
|
95
110
|
return self[name]
|
@@ -99,6 +114,8 @@ module Unobtainium
|
|
99
114
|
|
100
115
|
##
|
101
116
|
# Deletes (and destroys) any object found under the given name.
|
117
|
+
#
|
118
|
+
# @param name [String, Symbol] name or label for the object to store
|
102
119
|
def delete(name)
|
103
120
|
if not @objects.key?(name)
|
104
121
|
return
|
@@ -112,6 +129,11 @@ module Unobtainium
|
|
112
129
|
##
|
113
130
|
# Returns the object with the given name, or the default value if no such
|
114
131
|
# object exists.
|
132
|
+
#
|
133
|
+
# @param name [String, Symbol] name or label for the object to retrieve
|
134
|
+
# @param default [Object] default value to return if no object is found for
|
135
|
+
# name or label.
|
136
|
+
# @return [Object] the object matching the name/label, or the default value.
|
115
137
|
def fetch(name, default = nil)
|
116
138
|
return @objects.fetch(name)[0]
|
117
139
|
rescue KeyError
|
@@ -122,8 +144,11 @@ module Unobtainium
|
|
122
144
|
end
|
123
145
|
|
124
146
|
##
|
125
|
-
# Similar to
|
147
|
+
# Similar to `#fetch`, but always returns nil for an object that could not
|
126
148
|
# be found.
|
149
|
+
#
|
150
|
+
# @param name [String, Symbol] name or label for the object to retrieve
|
151
|
+
# @return [Object] the object matching the name/label, or the default value.
|
127
152
|
def [](name)
|
128
153
|
val = @objects[name]
|
129
154
|
if val.nil?
|
@@ -0,0 +1,160 @@
|
|
1
|
+
# coding: utf-8
|
2
|
+
#
|
3
|
+
# unobtainium
|
4
|
+
# https://github.com/jfinkhaeuser/unobtainium
|
5
|
+
#
|
6
|
+
# Copyright (c) 2016 Jens Finkhaeuser and other unobtainium contributors.
|
7
|
+
# All rights reserved.
|
8
|
+
#
|
9
|
+
|
10
|
+
require 'socket'
|
11
|
+
|
12
|
+
module Unobtainium
|
13
|
+
# @api private
|
14
|
+
# Contains support code
|
15
|
+
module Support
|
16
|
+
##
|
17
|
+
# A bit of metaprogramming hackery to make a constant with possible domains
|
18
|
+
# from Socket::Constants.
|
19
|
+
if not constants.include?('DOMAINS')
|
20
|
+
domains = []
|
21
|
+
Socket::Constants.constants.each do |name|
|
22
|
+
if name.to_s.start_with?("AF_")
|
23
|
+
domains << name.to_s.gsub(/^AF_/, '').to_sym
|
24
|
+
end
|
25
|
+
end
|
26
|
+
const_set('DOMAINS', domains.freeze)
|
27
|
+
end
|
28
|
+
|
29
|
+
##
|
30
|
+
# A port scanner for finding a free port for running e.g. a selenium
|
31
|
+
# or appium server.
|
32
|
+
module PortScanner
|
33
|
+
##
|
34
|
+
# Returns true if the port is open on the host, false otherwise.
|
35
|
+
# @param host [String] host name or IP address
|
36
|
+
# @param port [Integer] port number (1..65535)
|
37
|
+
# @param domains [Array/Symbol] :INET, :INET6, etc. or an Array of
|
38
|
+
# these. Any from Socket::Constants::AF_* work. Defaults to
|
39
|
+
# [:INET, :INET6].
|
40
|
+
def port_open?(host, port, domains = [:INET, :INET6])
|
41
|
+
if port < 1 or port > 65535
|
42
|
+
raise ArgumentError, "Port must be in range 1..65535!"
|
43
|
+
end
|
44
|
+
|
45
|
+
test_domains = nil
|
46
|
+
if domains.is_a? Array
|
47
|
+
test_domains = domains.dup
|
48
|
+
else
|
49
|
+
test_domains = [domains]
|
50
|
+
end
|
51
|
+
|
52
|
+
test_domains.each do |domain|
|
53
|
+
if not DOMAINS.include?(domain)
|
54
|
+
raise ArgumentError, "Domains must be one of #{DOMAINS}, or an Array "\
|
55
|
+
"of them, but #{domain} isn't!"
|
56
|
+
end
|
57
|
+
end
|
58
|
+
|
59
|
+
# Test a socket for each domain
|
60
|
+
addr = Socket.sockaddr_in(port, host)
|
61
|
+
|
62
|
+
test_domains.each do |domain|
|
63
|
+
begin
|
64
|
+
sock = Socket.new(domain, :STREAM)
|
65
|
+
return 0 == sock.connect(addr)
|
66
|
+
rescue Errno::EAFNOSUPPORT
|
67
|
+
next # try next domain
|
68
|
+
rescue Errno::ECONNREFUSED, Errno::ETIMEDOUT
|
69
|
+
return false
|
70
|
+
ensure
|
71
|
+
if not sock.nil?
|
72
|
+
sock.close
|
73
|
+
end
|
74
|
+
end
|
75
|
+
end
|
76
|
+
end
|
77
|
+
|
78
|
+
##
|
79
|
+
# Scan a mixture of ranges and arrays of ports for a given host.
|
80
|
+
# Return those that are open or closed, depending on the options
|
81
|
+
# given.
|
82
|
+
def scan(host, *args)
|
83
|
+
# Argument checks
|
84
|
+
if args.empty?
|
85
|
+
raise ArgumentError, "Need at least one port to scan!"
|
86
|
+
end
|
87
|
+
|
88
|
+
args.each do |item|
|
89
|
+
if not item.respond_to?(:each) and not item.respond_to?(:to_i)
|
90
|
+
raise ArgumentError, "The argument '#{item}' to #scan is not a "\
|
91
|
+
"Range, Array or convertible to Integer, aborting!"
|
92
|
+
end
|
93
|
+
end
|
94
|
+
|
95
|
+
# If the last argument is a Hash, treat it as options.
|
96
|
+
opts = {}
|
97
|
+
if args.last.is_a? Hash
|
98
|
+
opts = args.pop
|
99
|
+
end
|
100
|
+
opts = { for: :open, amount: :all }.merge(opts)
|
101
|
+
|
102
|
+
if not [:all, :first].include?(opts[:amount])
|
103
|
+
raise ArgumentError, ":amount must be one of :all, :first!"
|
104
|
+
end
|
105
|
+
if not [:open, :closed, :available].include?(opts[:for])
|
106
|
+
raise ArgumentError, ":for must beone of :open, :closed, :available!"
|
107
|
+
end
|
108
|
+
|
109
|
+
return run_scan(host, opts, *args)
|
110
|
+
end
|
111
|
+
|
112
|
+
private
|
113
|
+
|
114
|
+
def run_scan(host, opts, *args)
|
115
|
+
results = []
|
116
|
+
|
117
|
+
# Iteratively scan all arguments
|
118
|
+
args.each do |item|
|
119
|
+
if item.respond_to?(:to_i)
|
120
|
+
item_i = item.to_i
|
121
|
+
if test_port(host, item_i, opts[:for])
|
122
|
+
results << item_i
|
123
|
+
if opts[:amount] == :first
|
124
|
+
return results
|
125
|
+
end
|
126
|
+
end
|
127
|
+
next
|
128
|
+
end
|
129
|
+
|
130
|
+
item.each do |port|
|
131
|
+
if not test_port(host, port, opts[:for])
|
132
|
+
next
|
133
|
+
end
|
134
|
+
|
135
|
+
results << port
|
136
|
+
if opts[:amount] == :first
|
137
|
+
return results
|
138
|
+
end
|
139
|
+
end
|
140
|
+
end
|
141
|
+
|
142
|
+
return results
|
143
|
+
end
|
144
|
+
|
145
|
+
def test_port(host, port, test_for)
|
146
|
+
open = port_open?(host, port)
|
147
|
+
|
148
|
+
if open and :open == test_for
|
149
|
+
return true
|
150
|
+
end
|
151
|
+
|
152
|
+
if not open and [:closed, :available].include?(test_for)
|
153
|
+
return true
|
154
|
+
end
|
155
|
+
|
156
|
+
return false
|
157
|
+
end
|
158
|
+
end # module PortScanner
|
159
|
+
end # module Support
|
160
|
+
end # module Unobtainium
|
@@ -0,0 +1,180 @@
|
|
1
|
+
# coding: utf-8
|
2
|
+
#
|
3
|
+
# unobtainium
|
4
|
+
# https://github.com/jfinkhaeuser/unobtainium
|
5
|
+
#
|
6
|
+
# Copyright (c) 2016 Jens Finkhaeuser and other unobtainium contributors.
|
7
|
+
# All rights reserved.
|
8
|
+
#
|
9
|
+
require 'sys-proctable'
|
10
|
+
|
11
|
+
module Unobtainium
|
12
|
+
# @api private
|
13
|
+
# Contains support code
|
14
|
+
module Support
|
15
|
+
##
|
16
|
+
# Runs a shell command and detaches. Offers methods for managing the process
|
17
|
+
# lifetime. Implements #destroy, so you can use it with the Runtime class to
|
18
|
+
# kill the shell command upon script end.
|
19
|
+
class Runner
|
20
|
+
# @return [String] the ID passed to the constructor
|
21
|
+
attr_reader :id
|
22
|
+
|
23
|
+
# @return [Array] the command passed to the constructor
|
24
|
+
attr_reader :command
|
25
|
+
|
26
|
+
# @return [Integer] if the command is started, the pid of the process,
|
27
|
+
# or nil otherwise.
|
28
|
+
attr_reader :pid
|
29
|
+
|
30
|
+
# @return [IO] if the command is started, an IO object to read the
|
31
|
+
# commands output from, or nil otherwise.
|
32
|
+
attr_reader :stdout
|
33
|
+
|
34
|
+
# @return [IO] if the command is started, an IO object to read the
|
35
|
+
# commands error output from, or nil otherwise.
|
36
|
+
attr_reader :stderr
|
37
|
+
|
38
|
+
##
|
39
|
+
# Initialize with a shell command, but do not run yet.
|
40
|
+
#
|
41
|
+
# @param id [String] a unique ID for the command. This is to differentiate
|
42
|
+
# multiple similar commands from each other.
|
43
|
+
# @param command [Array] the remaining parameters are the command and its
|
44
|
+
# arguments.
|
45
|
+
def initialize(id, *command)
|
46
|
+
if command.empty?
|
47
|
+
raise ArgumentError, "Command may not be empty!"
|
48
|
+
end
|
49
|
+
|
50
|
+
@id = id
|
51
|
+
@command = command
|
52
|
+
@pid = nil
|
53
|
+
@stdout = nil
|
54
|
+
@stderr = nil
|
55
|
+
@wout = nil
|
56
|
+
@werr = nil
|
57
|
+
end
|
58
|
+
|
59
|
+
##
|
60
|
+
# Start the command. Afterwards, #pid, #stdout, and #stderr should be
|
61
|
+
# non-nil.
|
62
|
+
# @return [Integer] the pid of the command process
|
63
|
+
def start
|
64
|
+
if not @pid.nil?
|
65
|
+
raise "Command already running!"
|
66
|
+
end
|
67
|
+
|
68
|
+
# Capture options; pipes for stdout and stderr
|
69
|
+
@stdout, @wout = IO.pipe
|
70
|
+
@stderr, @werr = IO.pipe
|
71
|
+
opts = {
|
72
|
+
out: @wout,
|
73
|
+
err: @werr,
|
74
|
+
}
|
75
|
+
|
76
|
+
@pid = spawn({}, *@command, opts)
|
77
|
+
return @pid
|
78
|
+
end
|
79
|
+
|
80
|
+
##
|
81
|
+
# Wait for the command to exit.
|
82
|
+
# @return [Process::Status] exit status of the command.
|
83
|
+
def wait
|
84
|
+
_, status = Process.wait2(@pid)
|
85
|
+
cleanup
|
86
|
+
return status
|
87
|
+
end
|
88
|
+
|
89
|
+
##
|
90
|
+
# Send the "KILL" signal to the command process and all its children
|
91
|
+
def kill
|
92
|
+
signal("KILL", scope: :all)
|
93
|
+
cleanup
|
94
|
+
end
|
95
|
+
|
96
|
+
##
|
97
|
+
# Send the given signal to the process, and/or it's children.
|
98
|
+
# @param signal [String] the signal to send
|
99
|
+
# @param scope [Symbol] one of :self (the command process),
|
100
|
+
# :children (it's children *only) or :all (the process and its
|
101
|
+
# children.
|
102
|
+
def signal(signal, scope: :self)
|
103
|
+
if @pid.nil?
|
104
|
+
raise "No command is running!"
|
105
|
+
end
|
106
|
+
|
107
|
+
if not [:self, :children, :all].include?(scope)
|
108
|
+
raise ArgumentError, "The :scope argument must be one of :self, "\
|
109
|
+
":children or :all!"
|
110
|
+
end
|
111
|
+
|
112
|
+
# Figure out which pids to send the signal to. That is usually @pid,
|
113
|
+
# but possibly its children.
|
114
|
+
to_send = []
|
115
|
+
if [:self, :all].include?(scope)
|
116
|
+
to_send << @pid
|
117
|
+
end
|
118
|
+
|
119
|
+
if [:children, :all].include?(scope)
|
120
|
+
children = ::Sys::ProcTable.ps.select { |p| p.ppid == @pid }
|
121
|
+
to_send += children.collect(&:pid)
|
122
|
+
end
|
123
|
+
|
124
|
+
if to_send.empty?
|
125
|
+
raise "This should not happen. I have no pids to send a signal to!"
|
126
|
+
end
|
127
|
+
|
128
|
+
# Alright, send the signal!
|
129
|
+
to_send.each do |pid|
|
130
|
+
# rubocop:disable Lint/HandleExceptions
|
131
|
+
begin
|
132
|
+
Process.kill(signal, pid)
|
133
|
+
rescue
|
134
|
+
# If the kill didn't work, we don't really care.
|
135
|
+
end
|
136
|
+
# rubocop:enable Lint/HandleExceptions
|
137
|
+
end
|
138
|
+
|
139
|
+
# Clean up everything
|
140
|
+
cleanup(true)
|
141
|
+
end
|
142
|
+
|
143
|
+
##
|
144
|
+
# (see #kill)
|
145
|
+
# Use together with Runtime class to clean up any commands at exit.
|
146
|
+
def destroy
|
147
|
+
kill
|
148
|
+
end
|
149
|
+
|
150
|
+
private
|
151
|
+
|
152
|
+
def cleanup(all = false)
|
153
|
+
@pid = nil
|
154
|
+
# rubocop:disable Style/GuardClause
|
155
|
+
if not @wout.nil?
|
156
|
+
@wout.close
|
157
|
+
@wout = nil
|
158
|
+
end
|
159
|
+
if not @werr.nil?
|
160
|
+
@werr.close
|
161
|
+
@werr = nil
|
162
|
+
end
|
163
|
+
|
164
|
+
if not all
|
165
|
+
return
|
166
|
+
end
|
167
|
+
|
168
|
+
if not @stdout.nil?
|
169
|
+
@stdout.close
|
170
|
+
@stdout = nil
|
171
|
+
end
|
172
|
+
if not @stderr.nil?
|
173
|
+
@stderr.close
|
174
|
+
@stderr = nil
|
175
|
+
end
|
176
|
+
# rubocop:enable Style/GuardClause
|
177
|
+
end
|
178
|
+
end # class Runner
|
179
|
+
end # module Support
|
180
|
+
end # module Unobtainium
|