qbash 0.4.3 → 0.4.5
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/lib/qbash.rb +26 -20
- data/qbash.gemspec +1 -1
- data/test/test_qbash.rb +57 -0
- metadata +1 -1
checksums.yaml
CHANGED
@@ -1,7 +1,7 @@
|
|
1
1
|
---
|
2
2
|
SHA256:
|
3
|
-
metadata.gz:
|
4
|
-
data.tar.gz:
|
3
|
+
metadata.gz: 1ca64f77c050b51b02164ffab0c532d6d915fe0ba76ed4d311894b150eac963f
|
4
|
+
data.tar.gz: 4082a2f3aadf655a8fe6a6b7df67a85212f0fe9b8221bf64a26329abdfb8218a
|
5
5
|
SHA512:
|
6
|
-
metadata.gz:
|
7
|
-
data.tar.gz:
|
6
|
+
metadata.gz: 80535d4feebc803f62b1da69da63f2412330cc8f21a8ca9a5cfc344535b2c15c6816df65fcaa16db40efff702b8628dcd7817a4b93003a423b8928ea3df82d25
|
7
|
+
data.tar.gz: 609155111977c724663a6ba5c6a90718252d0c520f608c3dcadf0abc80dc0bf5d135f27a1204cfc9a002371461c9ff2e8cff85b9862dc6404fc2baf0a9a70932
|
data/lib/qbash.rb
CHANGED
@@ -75,17 +75,19 @@ module Kernel
|
|
75
75
|
#
|
76
76
|
# @param [String, Array] cmd The command to run (String or Array of arguments)
|
77
77
|
# @param [String] stdin The +stdin+ to provide to the command
|
78
|
+
# @param [Array] opts List of bash options, like "--login" and "--noprofile"
|
78
79
|
# @param [Hash] env Hash of environment variables
|
79
80
|
# @param [Loog|IO] log Logging facility with +.debug()+ method (or +$stdout+, or nil if should go to +/dev/null+)
|
80
81
|
# @param [Array] accept List of accepted exit codes (accepts all if the list is +nil+)
|
81
82
|
# @param [Boolean] both If set to TRUE, the function returns an array +(stdout, code)+
|
82
83
|
# @param [Integer] level Logging level (use +Logger::DEBUG+, +Logger::INFO+, +Logger::WARN+, or +Logger::ERROR+)
|
83
84
|
# @return [String] Everything that was printed to the +stdout+ by the command
|
84
|
-
def qbash(cmd, stdin: '', env: {}, log: Loog::NULL, accept: [0], both: false, level: Logger::DEBUG)
|
85
|
+
def qbash(cmd, opts: [], stdin: '', env: {}, log: Loog::NULL, accept: [0], both: false, level: Logger::DEBUG)
|
85
86
|
env.each { |k, v| raise "env[#{k}] is nil" if v.nil? }
|
86
87
|
cmd = cmd.reject { |a| a.nil? || (a.is_a?(String) && a.empty?) }.join(' ') if cmd.is_a?(Array)
|
87
88
|
logit =
|
88
89
|
lambda do |msg|
|
90
|
+
msg = msg.gsub(/\n$/, '')
|
89
91
|
mtd =
|
90
92
|
case level
|
91
93
|
when Logger::DEBUG
|
@@ -107,16 +109,18 @@ module Kernel
|
|
107
109
|
log.print("#{msg}\n")
|
108
110
|
end
|
109
111
|
end
|
110
|
-
logit["+ #{cmd}"]
|
111
112
|
buf = ''
|
112
113
|
e = 1
|
113
114
|
start = Time.now
|
114
|
-
|
115
|
+
bash = ['exec', '/bin/bash'] + opts + ['-c', Shellwords.escape(cmd)]
|
116
|
+
Open3.popen2e(env, bash.join(' ')) do |sin, sout, ctrl|
|
117
|
+
pid = ctrl.pid
|
118
|
+
logit["+ #{cmd} /##{pid}"]
|
115
119
|
consume =
|
116
120
|
lambda do
|
117
121
|
loop do
|
118
122
|
break if sout.eof?
|
119
|
-
ln = sout.gets
|
123
|
+
ln = sout.gets # together with the \n at the end
|
120
124
|
next if ln.nil?
|
121
125
|
next if ln.empty?
|
122
126
|
buf += ln
|
@@ -132,22 +136,24 @@ module Kernel
|
|
132
136
|
if block_given?
|
133
137
|
watch = Thread.new { consume.call }
|
134
138
|
watch.abort_on_exception = true
|
135
|
-
|
136
|
-
|
137
|
-
|
138
|
-
|
139
|
-
|
140
|
-
|
141
|
-
|
142
|
-
|
143
|
-
|
144
|
-
|
145
|
-
|
146
|
-
|
147
|
-
|
148
|
-
|
149
|
-
|
150
|
-
|
139
|
+
begin
|
140
|
+
yield pid
|
141
|
+
ensure
|
142
|
+
sout.close
|
143
|
+
watch.join(0.01)
|
144
|
+
watch.kill if watch.alive?
|
145
|
+
attempt = 1
|
146
|
+
since = Time.now
|
147
|
+
loop do
|
148
|
+
Process.kill(0, pid) # should be dead already (raising Errno::ESRCH)
|
149
|
+
Process.kill('TERM', pid) # let's try to kill it
|
150
|
+
logit["Tried to stop ##{pid} with SIGTERM (attempt no.#{attempt}, #{since.ago}): #{cmd}"]
|
151
|
+
sleep(0.1)
|
152
|
+
attempt += 1
|
153
|
+
rescue Errno::ESRCH
|
154
|
+
logit["Process ##{pid} reacted to SIGTERM, after #{attempt} attempts and #{since.ago}"] if attempt > 1
|
155
|
+
break
|
156
|
+
end
|
151
157
|
end
|
152
158
|
else
|
153
159
|
consume.call
|
data/qbash.gemspec
CHANGED
@@ -9,7 +9,7 @@ Gem::Specification.new do |s|
|
|
9
9
|
s.required_rubygems_version = Gem::Requirement.new('>= 0') if s.respond_to? :required_rubygems_version=
|
10
10
|
s.required_ruby_version = '>=3.2'
|
11
11
|
s.name = 'qbash'
|
12
|
-
s.version = '0.4.
|
12
|
+
s.version = '0.4.5'
|
13
13
|
s.license = 'MIT'
|
14
14
|
s.summary = 'Quick Executor of a BASH Command'
|
15
15
|
s.description =
|
data/test/test_qbash.rb
CHANGED
@@ -79,6 +79,14 @@ class TestQbash < Minitest::Test
|
|
79
79
|
end
|
80
80
|
end
|
81
81
|
|
82
|
+
def test_lets_exception_float_up
|
83
|
+
assert_raises(StandardError) do
|
84
|
+
qbash('sleep 767676', accept: nil, log: $stdout) do
|
85
|
+
raise 'intentional'
|
86
|
+
end
|
87
|
+
end
|
88
|
+
end
|
89
|
+
|
82
90
|
def test_kills_in_thread
|
83
91
|
Thread.new do
|
84
92
|
qbash('sleep 9999', accept: nil) do |pid|
|
@@ -87,6 +95,21 @@ class TestQbash < Minitest::Test
|
|
87
95
|
end.join
|
88
96
|
end
|
89
97
|
|
98
|
+
def test_truly_kills
|
99
|
+
qbash('sleep 9876543', accept: nil) do
|
100
|
+
sleep(0.1)
|
101
|
+
end
|
102
|
+
refute_empty(qbash('ps ax | grep -v 9876543'))
|
103
|
+
end
|
104
|
+
|
105
|
+
def test_executes_without_sh_only_bash
|
106
|
+
qbash('sleep 89898989', accept: nil) do
|
107
|
+
refute_empty(qbash('ps ax | grep -v "sh -c exec /bin/bash -c sleep 89898989" | grep -v grep'))
|
108
|
+
refute_empty(qbash('ps ax | grep "sleep 89898989" | grep -v grep'))
|
109
|
+
sleep(0.1)
|
110
|
+
end
|
111
|
+
end
|
112
|
+
|
90
113
|
def test_logs_in_background
|
91
114
|
stdout = nil
|
92
115
|
buf = Loog::Buffer.new
|
@@ -107,6 +130,26 @@ class TestQbash < Minitest::Test
|
|
107
130
|
refute_includes(buf.to_s, "\n\n")
|
108
131
|
end
|
109
132
|
|
133
|
+
def test_logs_multi_line_print
|
134
|
+
buf = Loog::Buffer.new
|
135
|
+
pid = nil
|
136
|
+
qbash('echo one; echo two', log: buf, accept: nil) do |i|
|
137
|
+
sleep(0.1)
|
138
|
+
pid = i
|
139
|
+
end
|
140
|
+
assert_equal(buf.to_s, "+ echo one; echo two /##{pid}\n##{pid}: one\n##{pid}: two\n")
|
141
|
+
end
|
142
|
+
|
143
|
+
def test_logs_multi_line_to_console
|
144
|
+
console = FakeConsole.new
|
145
|
+
pid = nil
|
146
|
+
qbash('echo one; echo two', log: console, accept: nil) do |i|
|
147
|
+
sleep(0.1)
|
148
|
+
pid = i
|
149
|
+
end
|
150
|
+
assert_equal(console.to_s, "+ echo one; echo two /##{pid}\n##{pid}: one\n##{pid}: two\n")
|
151
|
+
end
|
152
|
+
|
110
153
|
def test_with_both
|
111
154
|
Dir.mktmpdir do |home|
|
112
155
|
stdout, code = qbash("cat #{Shellwords.escape(File.join(home, 'foo.txt'))}", accept: nil, both: true)
|
@@ -131,4 +174,18 @@ class TestQbash < Minitest::Test
|
|
131
174
|
refute(t.join(0.1))
|
132
175
|
t.kill
|
133
176
|
end
|
177
|
+
|
178
|
+
class FakeConsole
|
179
|
+
def initialize
|
180
|
+
@buf = ''
|
181
|
+
end
|
182
|
+
|
183
|
+
def to_s
|
184
|
+
@buf
|
185
|
+
end
|
186
|
+
|
187
|
+
def print(ln)
|
188
|
+
@buf += ln
|
189
|
+
end
|
190
|
+
end
|
134
191
|
end
|