1require 'test/unit'
2require 'timeout'
3require 'tempfile'
4require_relative 'envutil'
5
6class TestSignal < Test::Unit::TestCase
7  def have_fork?
8    begin
9      Process.fork {}
10      return true
11    rescue NotImplementedError
12      return false
13    end
14  end
15
16  def test_signal
17    return unless Process.respond_to?(:kill)
18    begin
19      x = 0
20      oldtrap = Signal.trap(:INT) {|sig| x = 2 }
21      Process.kill :INT, Process.pid
22      10.times do
23        break if 2 == x
24        sleep 0.1
25      end
26      assert_equal 2, x
27
28      Signal.trap(:INT) { raise "Interrupt" }
29      ex = assert_raise(RuntimeError) {
30        Process.kill :INT, Process.pid
31        sleep 0.1
32      }
33      assert_kind_of Exception, ex
34      assert_match(/Interrupt/, ex.message)
35    ensure
36      Signal.trap :INT, oldtrap if oldtrap
37    end
38  end
39
40  def test_signal_process_group
41    return unless Process.respond_to?(:kill)
42    return unless Process.respond_to?(:pgroup) # for mswin32
43
44    bug4362 = '[ruby-dev:43169]'
45    assert_nothing_raised(bug4362) do
46      pid = Process.spawn(EnvUtil.rubybin, '-e', 'sleep 10', :pgroup => true)
47      Process.kill(:"-TERM", pid)
48      Process.waitpid(pid)
49      assert_equal(true, $?.signaled?)
50      assert_equal(Signal.list["TERM"], $?.termsig)
51    end
52  end
53
54  def test_exit_action
55    return unless have_fork?	# skip this test
56    begin
57      r, w = IO.pipe
58      r0, w0 = IO.pipe
59      pid = Process.spawn(EnvUtil.rubybin, '-e', <<-'End', 3=>w, 4=>r0)
60        w = IO.new(3, "w")
61        r0 = IO.new(4, "r")
62        Signal.trap(:USR1, "EXIT")
63        w.syswrite("a")
64        Thread.start { sleep(2) }
65        r0.sysread(4096)
66      End
67      r.sysread(1)
68      sleep 0.1
69      assert_nothing_raised("[ruby-dev:26128]") {
70        Process.kill(:USR1, pid)
71        begin
72          Timeout.timeout(3) {
73            Process.waitpid pid
74          }
75        rescue Timeout::Error
76          Process.kill(:TERM, pid)
77          raise
78        end
79      }
80    ensure
81      r.close
82      w.close
83      r0.close
84      w0.close
85    end
86  end
87
88  def test_invalid_signal_name
89    return unless Process.respond_to?(:kill)
90
91    assert_raise(ArgumentError) { Process.kill(:XXXXXXXXXX, $$) }
92  end
93
94  def test_signal_exception
95    assert_raise(ArgumentError) { SignalException.new }
96    assert_raise(ArgumentError) { SignalException.new(-1) }
97    assert_raise(ArgumentError) { SignalException.new(:XXXXXXXXXX) }
98    Signal.list.each do |signm, signo|
99      next if signm == "EXIT"
100      assert_equal(SignalException.new(signm).signo, signo)
101      assert_equal(SignalException.new(signm.to_sym).signo, signo)
102      assert_equal(SignalException.new(signo).signo, signo)
103    end
104  end
105
106  def test_interrupt
107    assert_raise(Interrupt) { raise Interrupt.new }
108  end
109
110  def test_signal2
111    return unless Process.respond_to?(:kill)
112    begin
113      x = false
114      oldtrap = Signal.trap(:INT) {|sig| x = true }
115      GC.start
116
117      assert_raise(ArgumentError) { Process.kill }
118
119      Timeout.timeout(10) do
120        x = false
121        Process.kill(SignalException.new(:INT).signo, $$)
122        sleep(0.01) until x
123
124        x = false
125        Process.kill("INT", $$)
126        sleep(0.01) until x
127
128        x = false
129        Process.kill("SIGINT", $$)
130        sleep(0.01) until x
131
132        x = false
133        o = Object.new
134        def o.to_str; "SIGINT"; end
135        Process.kill(o, $$)
136        sleep(0.01) until x
137      end
138
139      assert_raise(ArgumentError) { Process.kill(Object.new, $$) }
140
141    ensure
142      Signal.trap(:INT, oldtrap) if oldtrap
143    end
144  end
145
146  def test_trap
147    return unless Process.respond_to?(:kill)
148    begin
149      oldtrap = Signal.trap(:INT) {|sig| }
150
151      assert_raise(ArgumentError) { Signal.trap }
152
153      assert_raise(SecurityError) do
154        s = proc {}.taint
155        Signal.trap(:INT, s)
156      end
157
158      # FIXME!
159      Signal.trap(:INT, nil)
160      Signal.trap(:INT, "")
161      Signal.trap(:INT, "SIG_IGN")
162      Signal.trap(:INT, "IGNORE")
163
164      Signal.trap(:INT, "SIG_DFL")
165      Signal.trap(:INT, "SYSTEM_DEFAULT")
166
167      Signal.trap(:INT, "EXIT")
168
169      Signal.trap(:INT, "xxxxxx")
170      Signal.trap(:INT, "xxxx")
171
172      Signal.trap(SignalException.new(:INT).signo, "SIG_DFL")
173
174      assert_raise(ArgumentError) { Signal.trap(-1, "xxxx") }
175
176      o = Object.new
177      def o.to_str; "SIGINT"; end
178      Signal.trap(o, "SIG_DFL")
179
180      assert_raise(ArgumentError) { Signal.trap("XXXXXXXXXX", "SIG_DFL") }
181
182    ensure
183      Signal.trap(:INT, oldtrap) if oldtrap
184    end
185  end
186
187  def test_kill_immediately_before_termination
188    return unless have_fork?	# skip this test
189
190    r, w = IO.pipe
191    pid = Process.fork do
192      r.close
193      Signal.trap(:USR1) { w.syswrite("foo") }
194      Process.kill :USR1, $$
195    end
196    w.close
197    assert_equal(r.read, "foo")
198  end
199
200  def test_signal_requiring
201    t = Tempfile.new(%w"require_ensure_test .rb")
202    t.puts "sleep"
203    t.close
204    error = IO.popen([EnvUtil.rubybin, "-e", <<EOS, t.path, :err => File::NULL]) do |child|
205trap(:INT, "DEFAULT")
206th = Thread.new do
207  begin
208    require ARGV[0]
209  ensure
210    err = $! ? [$!, $!.backtrace] : $!
211    Marshal.dump(err, STDOUT)
212    STDOUT.flush
213  end
214end
215Thread.pass while th.running?
216Process.kill(:INT, $$)
217th.join
218EOS
219      Marshal.load(child)
220    end
221    t.close!
222    assert_nil(error)
223  end
224
225  def test_reserved_signal
226    assert_raise(ArgumentError) {
227      Signal.trap(:SEGV) {}
228    }
229    assert_raise(ArgumentError) {
230      Signal.trap(:BUS) {}
231    }
232    assert_raise(ArgumentError) {
233      Signal.trap(:ILL) {}
234    }
235    assert_raise(ArgumentError) {
236      Signal.trap(:FPE) {}
237    }
238    assert_raise(ArgumentError) {
239      Signal.trap(:VTALRM) {}
240    }
241  end
242
243  def test_signame
244    return unless Process.respond_to?(:kill)
245
246    10.times do
247      IO.popen([EnvUtil.rubybin, "-e", <<EOS, :err => File::NULL]) do |child|
248        Signal.trap("INT") do |signo|
249          signame = Signal.signame(signo)
250          Marshal.dump(signame, STDOUT)
251          STDOUT.flush
252          exit 0
253        end
254        Process.kill("INT", $$)
255        sleep 1  # wait signal deliver
256EOS
257
258        signame = Marshal.load(child)
259        assert_equal(signame, "INT")
260      end
261    end
262  end
263
264  def test_trap_puts
265    assert_in_out_err([], <<-INPUT, ["a"*10000], [])
266      Signal.trap(:INT) {
267          # for enable internal io mutex
268          STDOUT.sync = false
269          # larger than internal io buffer
270          print "a"*10000
271      }
272      Process.kill :INT, $$
273      sleep 0.1
274    INPUT
275  end
276end
277