1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62 63 64 65 66 67 68 69 70 71 72 73 74 75 76 77 78 79 80 81 82 83 84 85 86 87 88 89 90 91 92 93 94 95 96 97 98 99 100 101 102 103 104 105 106 107 108 109 110 111 112 113 114 115 116 117 118 119 120 121 122 123 124 125 126 127 128 129 130 131 132 133 134 135 136 137 138 139 140 141 142 143 144 145 146 147 148 149 150 151 152 153 154 155 156 157 158 159 160 161 162 163 164 165 166 167 168 169 170 171 172 173 174 175 176 177 178 179 180 181 182 183 184 185 186 187 188 189 190 191 192 193 194 195 196 197 198 199 200 201 202 203 204 205 206 207 208 209 210 211 212 213 214 215 216 217 218 219 220 221 222 223 224 225 226 227 228 229 230 231 232 233 234 235 236
|
# coding: UTF-8
require 'test_helper'
class ChildTest < Minitest::Test
include POSIX::Spawn
# Become a new process group.
def setup
Process.setpgrp
end
# Kill any orphaned processes in our process group before continuing but
# ignore the TERM signal we receive.
def teardown
trap("TERM") { trap("TERM", "DEFAULT") }
begin
Process.kill("-TERM", Process.pid)
Process.wait
rescue Errno::ECHILD
end
end
# verify the process is no longer running and has been reaped.
def assert_process_reaped(pid)
Process.kill(0, pid)
assert false, "Process #{pid} still running"
rescue Errno::ESRCH
end
# verifies that all processes in the given process group are no longer running
# and have been reaped. The current ruby test process is excluded.
# XXX It's weird to use the SUT here but the :pgroup option is useful. Could
# be a IO.popen under Ruby >= 1.9 since it also supports :pgroup.
def assert_process_group_reaped(pgid)
command = "ps axo pgid,pid,args | grep '^#{pgid} ' | grep -v '^#{pgid} #$$'"
procs = POSIX::Spawn::Child.new(command, :pgroup => true).out
assert procs.empty?, "Processes in group #{pgid} still running:\n#{procs}"
end
def test_sanity
assert_same POSIX::Spawn::Child, Child
end
def test_argv_array_execs
p = Child.new('printf', '%s %s %s', '1', '2', '3 4')
assert p.success?
assert_equal "1 2 3 4", p.out
end
def test_argv_string_uses_sh
p = Child.new("echo via /bin/sh")
assert p.success?
assert_equal "via /bin/sh\n", p.out
end
def test_stdout
p = Child.new('echo', 'boom')
assert_equal "boom\n", p.out
assert_equal "", p.err
end
def test_stderr
p = Child.new('echo boom 1>&2')
assert_equal "", p.out
assert_equal "boom\n", p.err
end
def test_status
p = Child.new('exit 3')
assert !p.status.success?
assert_equal 3, p.status.exitstatus
end
def test_env
p = Child.new({ 'FOO' => 'BOOYAH' }, 'echo $FOO')
assert_equal "BOOYAH\n", p.out
end
def test_chdir
p = Child.new("pwd", :chdir => File.dirname(Dir.pwd))
assert_equal File.dirname(Dir.pwd) + "\n", p.out
end
def test_input
input = "HEY NOW\n" * 100_000 # 800K
p = Child.new('wc', '-l', :input => input)
assert_equal 100_000, p.out.strip.to_i
end
def test_max
child = Child.build('yes', :max => 100_000)
assert_raises(MaximumOutputExceeded) { child.exec! }
assert_process_reaped child.pid
assert_process_group_reaped Process.pid
end
def test_max_pgroup_kill
child = Child.build('yes', :max => 100_000, :pgroup_kill => true)
assert_raises(MaximumOutputExceeded) { child.exec! }
assert_process_reaped child.pid
assert_process_group_reaped child.pid
end
def test_max_with_child_hierarchy
child = Child.build('/bin/sh', '-c', 'true && yes', :max => 100_000)
assert_raises(MaximumOutputExceeded) { child.exec! }
assert_process_reaped child.pid
assert_process_group_reaped Process.pid
end
def test_max_with_child_hierarchy_pgroup_kill
child = Child.build('/bin/sh', '-c', 'true && yes', :max => 100_000, :pgroup_kill => true)
assert_raises(MaximumOutputExceeded) { child.exec! }
assert_process_reaped child.pid
assert_process_group_reaped child.pid
end
def test_max_with_stubborn_child
child = Child.build("trap '' TERM; yes", :max => 100_000)
assert_raises(MaximumOutputExceeded) { child.exec! }
assert_process_reaped child.pid
assert_process_group_reaped Process.pid
end
def test_max_with_stubborn_child_pgroup_kill
child = Child.build("trap '' TERM; yes", :max => 100_000, :pgroup_kill => true)
assert_raises(MaximumOutputExceeded) { child.exec! }
assert_process_reaped child.pid
assert_process_group_reaped child.pid
end
def test_max_with_partial_output
p = Child.build('yes', :max => 100_000)
assert_nil p.out
assert_raises MaximumOutputExceeded do
p.exec!
end
assert_output_exceeds_repeated_string("y\n", 100_000, p.out)
assert_process_reaped p.pid
assert_process_group_reaped Process.pid
end
def test_max_with_partial_output_long_lines
p = Child.build('yes', "nice to meet you", :max => 10_000)
assert_raises MaximumOutputExceeded do
p.exec!
end
assert_output_exceeds_repeated_string("nice to meet you\n", 10_000, p.out)
assert_process_reaped p.pid
assert_process_group_reaped Process.pid
end
def test_timeout
start = Time.now
child = Child.build('sleep', '1', :timeout => 0.05)
assert_raises(TimeoutExceeded) { child.exec! }
assert_process_reaped child.pid
assert_process_group_reaped Process.pid
assert (Time.now-start) <= 0.2
end
def test_timeout_pgroup_kill
start = Time.now
child = Child.build('sleep', '1', :timeout => 0.05, :pgroup_kill => true)
assert_raises(TimeoutExceeded) { child.exec! }
assert_process_reaped child.pid
assert_process_group_reaped child.pid
assert (Time.now-start) <= 0.2
end
def test_timeout_with_child_hierarchy
child = Child.build('/bin/sh', '-c', 'true && sleep 1', :timeout => 0.05)
assert_raises(TimeoutExceeded) { child.exec! }
assert_process_reaped child.pid
end
def test_timeout_with_child_hierarchy_pgroup_kill
child = Child.build('/bin/sh', '-c', 'true && sleep 1', :timeout => 0.05, :pgroup_kill => true)
assert_raises(TimeoutExceeded) { child.exec! }
assert_process_reaped child.pid
assert_process_group_reaped child.pid
end
def test_timeout_with_partial_output
start = Time.now
p = Child.build('echo Hello; sleep 1', :timeout => 0.05, :pgroup_kill => true)
assert_raises(TimeoutExceeded) { p.exec! }
assert_process_reaped p.pid
assert_process_group_reaped Process.pid
assert (Time.now-start) <= 0.2
assert_equal "Hello\n", p.out
end
def test_lots_of_input_and_lots_of_output_at_the_same_time
input = "stuff on stdin \n" * 1_000
command = "
while read line
do
echo stuff on stdout;
echo stuff on stderr 1>&2;
done
"
p = Child.new(command, :input => input)
assert_equal input.size, p.out.size
assert_equal input.size, p.err.size
assert p.success?
end
def test_input_cannot_be_written_due_to_broken_pipe
input = "1" * 100_000
p = Child.new('false', :input => input)
assert !p.success?
end
def test_utf8_input
input = "hålø"
p = Child.new('cat', :input => input)
assert p.success?
end
def test_utf8_input_long
input = "hålø" * 10_000
p = Child.new('cat', :input => input)
assert p.success?
end
##
# Assertion Helpers
def assert_output_exceeds_repeated_string(str, len, actual)
assert_operator actual.length, :>=, len
expected = (str * (len / str.length + 1)).slice(0, len)
assert_equal expected, actual.slice(0, len)
end
end
|