mini-pure-gem 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.
Files changed (157) hide show
  1. checksums.yaml +7 -0
  2. data/mini-pure-gem.gemspec +11 -0
  3. data/unicorn-6.1.0/Application_Timeouts +77 -0
  4. data/unicorn-6.1.0/CONTRIBUTORS +39 -0
  5. data/unicorn-6.1.0/COPYING +674 -0
  6. data/unicorn-6.1.0/DESIGN +95 -0
  7. data/unicorn-6.1.0/Documentation/unicorn.1 +222 -0
  8. data/unicorn-6.1.0/Documentation/unicorn_rails.1 +207 -0
  9. data/unicorn-6.1.0/FAQ +70 -0
  10. data/unicorn-6.1.0/GIT-VERSION-FILE +1 -0
  11. data/unicorn-6.1.0/GIT-VERSION-GEN +39 -0
  12. data/unicorn-6.1.0/GNUmakefile +313 -0
  13. data/unicorn-6.1.0/HACKING +112 -0
  14. data/unicorn-6.1.0/ISSUES +97 -0
  15. data/unicorn-6.1.0/KNOWN_ISSUES +79 -0
  16. data/unicorn-6.1.0/LATEST +24 -0
  17. data/unicorn-6.1.0/LICENSE +67 -0
  18. data/unicorn-6.1.0/Links +58 -0
  19. data/unicorn-6.1.0/NEWS +2890 -0
  20. data/unicorn-6.1.0/PHILOSOPHY +139 -0
  21. data/unicorn-6.1.0/README +151 -0
  22. data/unicorn-6.1.0/Rakefile +16 -0
  23. data/unicorn-6.1.0/SIGNALS +123 -0
  24. data/unicorn-6.1.0/Sandbox +104 -0
  25. data/unicorn-6.1.0/TODO +3 -0
  26. data/unicorn-6.1.0/TUNING +119 -0
  27. data/unicorn-6.1.0/archive/slrnpull.conf +4 -0
  28. data/unicorn-6.1.0/bin/unicorn +128 -0
  29. data/unicorn-6.1.0/bin/unicorn_rails +209 -0
  30. data/unicorn-6.1.0/examples/big_app_gc.rb +2 -0
  31. data/unicorn-6.1.0/examples/echo.ru +27 -0
  32. data/unicorn-6.1.0/examples/init.sh +102 -0
  33. data/unicorn-6.1.0/examples/logger_mp_safe.rb +25 -0
  34. data/unicorn-6.1.0/examples/logrotate.conf +44 -0
  35. data/unicorn-6.1.0/examples/nginx.conf +156 -0
  36. data/unicorn-6.1.0/examples/unicorn.conf.minimal.rb +13 -0
  37. data/unicorn-6.1.0/examples/unicorn.conf.rb +110 -0
  38. data/unicorn-6.1.0/examples/unicorn.socket +11 -0
  39. data/unicorn-6.1.0/examples/unicorn@.service +40 -0
  40. data/unicorn-6.1.0/ext/unicorn_http/CFLAGS +13 -0
  41. data/unicorn-6.1.0/ext/unicorn_http/c_util.h +116 -0
  42. data/unicorn-6.1.0/ext/unicorn_http/common_field_optimization.h +128 -0
  43. data/unicorn-6.1.0/ext/unicorn_http/epollexclusive.h +124 -0
  44. data/unicorn-6.1.0/ext/unicorn_http/ext_help.h +38 -0
  45. data/unicorn-6.1.0/ext/unicorn_http/extconf.rb +37 -0
  46. data/unicorn-6.1.0/ext/unicorn_http/global_variables.h +97 -0
  47. data/unicorn-6.1.0/ext/unicorn_http/httpdate.c +79 -0
  48. data/unicorn-6.1.0/ext/unicorn_http/unicorn_http.c +4319 -0
  49. data/unicorn-6.1.0/ext/unicorn_http/unicorn_http.rl +1025 -0
  50. data/unicorn-6.1.0/ext/unicorn_http/unicorn_http_common.rl +76 -0
  51. data/unicorn-6.1.0/lib/unicorn/app/old_rails/static.rb +59 -0
  52. data/unicorn-6.1.0/lib/unicorn/app/old_rails.rb +35 -0
  53. data/unicorn-6.1.0/lib/unicorn/cgi_wrapper.rb +147 -0
  54. data/unicorn-6.1.0/lib/unicorn/configurator.rb +743 -0
  55. data/unicorn-6.1.0/lib/unicorn/const.rb +21 -0
  56. data/unicorn-6.1.0/lib/unicorn/http_request.rb +201 -0
  57. data/unicorn-6.1.0/lib/unicorn/http_response.rb +61 -0
  58. data/unicorn-6.1.0/lib/unicorn/http_server.rb +893 -0
  59. data/unicorn-6.1.0/lib/unicorn/launcher.rb +62 -0
  60. data/unicorn-6.1.0/lib/unicorn/oob_gc.rb +81 -0
  61. data/unicorn-6.1.0/lib/unicorn/preread_input.rb +33 -0
  62. data/unicorn-6.1.0/lib/unicorn/select_waiter.rb +6 -0
  63. data/unicorn-6.1.0/lib/unicorn/socket_helper.rb +211 -0
  64. data/unicorn-6.1.0/lib/unicorn/stream_input.rb +147 -0
  65. data/unicorn-6.1.0/lib/unicorn/tee_input.rb +131 -0
  66. data/unicorn-6.1.0/lib/unicorn/tmpio.rb +33 -0
  67. data/unicorn-6.1.0/lib/unicorn/util.rb +90 -0
  68. data/unicorn-6.1.0/lib/unicorn/version.rb +1 -0
  69. data/unicorn-6.1.0/lib/unicorn/worker.rb +165 -0
  70. data/unicorn-6.1.0/lib/unicorn.rb +138 -0
  71. data/unicorn-6.1.0/man/man1/unicorn.1 +222 -0
  72. data/unicorn-6.1.0/man/man1/unicorn_rails.1 +207 -0
  73. data/unicorn-6.1.0/setup.rb +1586 -0
  74. data/unicorn-6.1.0/t/GNUmakefile +5 -0
  75. data/unicorn-6.1.0/t/README +42 -0
  76. data/unicorn-6.1.0/t/bin/content-md5-put +36 -0
  77. data/unicorn-6.1.0/t/bin/sha1sum.rb +17 -0
  78. data/unicorn-6.1.0/t/bin/unused_listen +40 -0
  79. data/unicorn-6.1.0/t/broken-app.ru +12 -0
  80. data/unicorn-6.1.0/t/detach.ru +11 -0
  81. data/unicorn-6.1.0/t/env.ru +3 -0
  82. data/unicorn-6.1.0/t/fails-rack-lint.ru +5 -0
  83. data/unicorn-6.1.0/t/heartbeat-timeout.ru +12 -0
  84. data/unicorn-6.1.0/t/listener_names.ru +4 -0
  85. data/unicorn-6.1.0/t/my-tap-lib.sh +201 -0
  86. data/unicorn-6.1.0/t/oob_gc.ru +20 -0
  87. data/unicorn-6.1.0/t/oob_gc_path.ru +20 -0
  88. data/unicorn-6.1.0/t/pid.ru +3 -0
  89. data/unicorn-6.1.0/t/preread_input.ru +17 -0
  90. data/unicorn-6.1.0/t/rack-input-tests.ru +21 -0
  91. data/unicorn-6.1.0/t/t0000-http-basic.sh +50 -0
  92. data/unicorn-6.1.0/t/t0001-reload-bad-config.sh +53 -0
  93. data/unicorn-6.1.0/t/t0002-config-conflict.sh +49 -0
  94. data/unicorn-6.1.0/t/t0002-parser-error.sh +94 -0
  95. data/unicorn-6.1.0/t/t0003-working_directory.sh +51 -0
  96. data/unicorn-6.1.0/t/t0004-heartbeat-timeout.sh +69 -0
  97. data/unicorn-6.1.0/t/t0004-working_directory_broken.sh +24 -0
  98. data/unicorn-6.1.0/t/t0005-working_directory_app.rb.sh +40 -0
  99. data/unicorn-6.1.0/t/t0006-reopen-logs.sh +83 -0
  100. data/unicorn-6.1.0/t/t0006.ru +13 -0
  101. data/unicorn-6.1.0/t/t0007-working_directory_no_embed_cli.sh +44 -0
  102. data/unicorn-6.1.0/t/t0008-back_out_of_upgrade.sh +110 -0
  103. data/unicorn-6.1.0/t/t0009-broken-app.sh +56 -0
  104. data/unicorn-6.1.0/t/t0009-winch_ttin.sh +59 -0
  105. data/unicorn-6.1.0/t/t0010-reap-logging.sh +55 -0
  106. data/unicorn-6.1.0/t/t0011-active-unix-socket.sh +79 -0
  107. data/unicorn-6.1.0/t/t0012-reload-empty-config.sh +86 -0
  108. data/unicorn-6.1.0/t/t0013-rewindable-input-false.sh +24 -0
  109. data/unicorn-6.1.0/t/t0013.ru +12 -0
  110. data/unicorn-6.1.0/t/t0014-rewindable-input-true.sh +24 -0
  111. data/unicorn-6.1.0/t/t0014.ru +12 -0
  112. data/unicorn-6.1.0/t/t0015-configurator-internals.sh +25 -0
  113. data/unicorn-6.1.0/t/t0018-write-on-close.sh +23 -0
  114. data/unicorn-6.1.0/t/t0019-max_header_len.sh +49 -0
  115. data/unicorn-6.1.0/t/t0020-at_exit-handler.sh +49 -0
  116. data/unicorn-6.1.0/t/t0021-process_detach.sh +29 -0
  117. data/unicorn-6.1.0/t/t0022-listener_names-preload_app.sh +32 -0
  118. data/unicorn-6.1.0/t/t0100-rack-input-tests.sh +124 -0
  119. data/unicorn-6.1.0/t/t0116-client_body_buffer_size.sh +80 -0
  120. data/unicorn-6.1.0/t/t0116.ru +16 -0
  121. data/unicorn-6.1.0/t/t0300-no-default-middleware.sh +20 -0
  122. data/unicorn-6.1.0/t/t0301-no-default-middleware-ignored-in-config.sh +25 -0
  123. data/unicorn-6.1.0/t/t0301.ru +13 -0
  124. data/unicorn-6.1.0/t/t9000-preread-input.sh +48 -0
  125. data/unicorn-6.1.0/t/t9001-oob_gc.sh +47 -0
  126. data/unicorn-6.1.0/t/t9002-oob_gc-path.sh +75 -0
  127. data/unicorn-6.1.0/t/test-lib.sh +129 -0
  128. data/unicorn-6.1.0/t/write-on-close.ru +11 -0
  129. data/unicorn-6.1.0/test/aggregate.rb +15 -0
  130. data/unicorn-6.1.0/test/benchmark/README +60 -0
  131. data/unicorn-6.1.0/test/benchmark/dd.ru +18 -0
  132. data/unicorn-6.1.0/test/benchmark/ddstream.ru +50 -0
  133. data/unicorn-6.1.0/test/benchmark/readinput.ru +40 -0
  134. data/unicorn-6.1.0/test/benchmark/stack.ru +8 -0
  135. data/unicorn-6.1.0/test/benchmark/uconnect.perl +66 -0
  136. data/unicorn-6.1.0/test/exec/README +5 -0
  137. data/unicorn-6.1.0/test/exec/test_exec.rb +1101 -0
  138. data/unicorn-6.1.0/test/test_helper.rb +306 -0
  139. data/unicorn-6.1.0/test/unit/test_ccc.rb +91 -0
  140. data/unicorn-6.1.0/test/unit/test_configurator.rb +175 -0
  141. data/unicorn-6.1.0/test/unit/test_droplet.rb +28 -0
  142. data/unicorn-6.1.0/test/unit/test_http_parser.rb +884 -0
  143. data/unicorn-6.1.0/test/unit/test_http_parser_ng.rb +714 -0
  144. data/unicorn-6.1.0/test/unit/test_request.rb +182 -0
  145. data/unicorn-6.1.0/test/unit/test_response.rb +111 -0
  146. data/unicorn-6.1.0/test/unit/test_server.rb +342 -0
  147. data/unicorn-6.1.0/test/unit/test_signals.rb +188 -0
  148. data/unicorn-6.1.0/test/unit/test_socket_helper.rb +201 -0
  149. data/unicorn-6.1.0/test/unit/test_stream_input.rb +203 -0
  150. data/unicorn-6.1.0/test/unit/test_tee_input.rb +304 -0
  151. data/unicorn-6.1.0/test/unit/test_upload.rb +301 -0
  152. data/unicorn-6.1.0/test/unit/test_util.rb +131 -0
  153. data/unicorn-6.1.0/test/unit/test_waiter.rb +34 -0
  154. data/unicorn-6.1.0/unicorn.gemspec +49 -0
  155. data/unicorn-6.1.0/unicorn_1 +1 -0
  156. data/unicorn-6.1.0/unicorn_rails_1 +1 -0
  157. metadata +195 -0
@@ -0,0 +1,1101 @@
1
+ # -*- encoding: binary -*-
2
+
3
+ # Copyright (c) 2009 Eric Wong
4
+ FLOCK_PATH = File.expand_path(__FILE__)
5
+ require './test/test_helper'
6
+
7
+ do_test = true
8
+ $unicorn_bin = ENV['UNICORN_TEST_BIN'] || "unicorn"
9
+ redirect_test_io do
10
+ do_test = system($unicorn_bin, '-v')
11
+ end
12
+
13
+ unless do_test
14
+ warn "#{$unicorn_bin} not found in PATH=#{ENV['PATH']}, " \
15
+ "skipping this test"
16
+ end
17
+
18
+ unless try_require('rack')
19
+ warn "Unable to load Rack, skipping this test"
20
+ do_test = false
21
+ end
22
+
23
+ class ExecTest < Test::Unit::TestCase
24
+ trap(:QUIT, 'IGNORE')
25
+
26
+ HI = <<-EOS
27
+ use Rack::ContentLength
28
+ run proc { |env| [ 200, { 'Content-Type' => 'text/plain' }, [ "HI\\n" ] ] }
29
+ EOS
30
+
31
+ SHOW_RACK_ENV = <<-EOS
32
+ use Rack::ContentLength
33
+ run proc { |env|
34
+ [ 200, { 'Content-Type' => 'text/plain' }, [ ENV['RACK_ENV'] ] ]
35
+ }
36
+ EOS
37
+
38
+ HELLO = <<-EOS
39
+ class Hello
40
+ def call(env)
41
+ [ 200, { 'Content-Type' => 'text/plain' }, [ "HI\\n" ] ]
42
+ end
43
+ end
44
+ EOS
45
+
46
+ COMMON_TMP = Tempfile.new('unicorn_tmp') unless defined?(COMMON_TMP)
47
+
48
+ HEAVY_WORKERS = 2
49
+ HEAVY_CFG = <<-EOS
50
+ worker_processes #{HEAVY_WORKERS}
51
+ timeout 30
52
+ logger Logger.new('#{COMMON_TMP.path}')
53
+ before_fork do |server, worker|
54
+ server.logger.info "before_fork: worker=\#{worker.nr}"
55
+ end
56
+ EOS
57
+
58
+ WORKING_DIRECTORY_CHECK_RU = <<-EOS
59
+ use Rack::ContentLength
60
+ run lambda { |env|
61
+ pwd = ENV['PWD']
62
+ a = ::File.stat(pwd)
63
+ b = ::File.stat(Dir.pwd)
64
+ if (a.ino == b.ino && a.dev == b.dev)
65
+ [ 200, { 'Content-Type' => 'text/plain' }, [ pwd ] ]
66
+ else
67
+ [ 404, { 'Content-Type' => 'text/plain' }, [] ]
68
+ end
69
+ }
70
+ EOS
71
+
72
+ def setup
73
+ @pwd = Dir.pwd
74
+ @tmpfile = Tempfile.new('unicorn_exec_test')
75
+ @tmpdir = @tmpfile.path
76
+ @tmpfile.close!
77
+ Dir.mkdir(@tmpdir)
78
+ Dir.chdir(@tmpdir)
79
+ @addr = ENV['UNICORN_TEST_ADDR'] || '127.0.0.1'
80
+ @port = unused_port(@addr)
81
+ @sockets = []
82
+ @start_pid = $$
83
+ end
84
+
85
+ def teardown
86
+ return if @start_pid != $$
87
+ Dir.chdir(@pwd)
88
+ FileUtils.rmtree(@tmpdir)
89
+ @sockets.each { |path| File.unlink(path) rescue nil }
90
+ loop do
91
+ Process.kill('-QUIT', 0)
92
+ begin
93
+ Process.waitpid(-1, Process::WNOHANG) or break
94
+ rescue Errno::ECHILD
95
+ break
96
+ end
97
+ end
98
+ end
99
+
100
+ def test_sd_listen_fds_emulation
101
+ # [ruby-core:69895] [Bug #11336] fixed by r51576
102
+ return if RUBY_VERSION.to_f < 2.3
103
+
104
+ File.open("config.ru", "wb") { |fp| fp.write(HI) }
105
+ sock = TCPServer.new(@addr, @port)
106
+
107
+ [ %W(-l #@addr:#@port), nil ].each do |l|
108
+ sock.setsockopt(:SOL_SOCKET, :SO_KEEPALIVE, 0)
109
+
110
+ pid = xfork do
111
+ redirect_test_io do
112
+ # pretend to be systemd
113
+ ENV['LISTEN_PID'] = "#$$"
114
+ ENV['LISTEN_FDS'] = '1'
115
+
116
+ # 3 = SD_LISTEN_FDS_START
117
+ args = [ $unicorn_bin ]
118
+ args.concat(l) if l
119
+ args << { 3 => sock }
120
+ exec(*args)
121
+ end
122
+ end
123
+ res = hit(["http://#@addr:#@port/"])
124
+ assert_equal [ "HI\n" ], res
125
+ assert_shutdown(pid)
126
+ assert sock.getsockopt(:SOL_SOCKET, :SO_KEEPALIVE).bool,
127
+ 'unicorn should always set SO_KEEPALIVE on inherited sockets'
128
+ end
129
+ ensure
130
+ sock.close if sock
131
+ end
132
+
133
+ def test_inherit_listener_unspecified
134
+ File.open("config.ru", "wb") { |fp| fp.write(HI) }
135
+ sock = TCPServer.new(@addr, @port)
136
+ sock.setsockopt(:SOL_SOCKET, :SO_KEEPALIVE, 0)
137
+
138
+ pid = xfork do
139
+ redirect_test_io do
140
+ ENV['UNICORN_FD'] = sock.fileno.to_s
141
+ exec($unicorn_bin, sock.fileno => sock.fileno)
142
+ end
143
+ end
144
+ res = hit(["http://#@addr:#@port/"])
145
+ assert_equal [ "HI\n" ], res
146
+ assert_shutdown(pid)
147
+ assert sock.getsockopt(:SOL_SOCKET, :SO_KEEPALIVE).bool,
148
+ 'unicorn should always set SO_KEEPALIVE on inherited sockets'
149
+ ensure
150
+ sock.close if sock
151
+ end
152
+
153
+ def test_working_directory_rel_path_config_file
154
+ other = Tempfile.new('unicorn.wd')
155
+ File.unlink(other.path)
156
+ Dir.mkdir(other.path)
157
+ File.open("config.ru", "wb") do |fp|
158
+ fp.syswrite WORKING_DIRECTORY_CHECK_RU
159
+ end
160
+ FileUtils.cp("config.ru", other.path + "/config.ru")
161
+ Dir.chdir(@tmpdir)
162
+
163
+ tmp = File.open('unicorn.config', 'wb')
164
+ tmp.syswrite <<EOF
165
+ working_directory '#@tmpdir'
166
+ listen '#@addr:#@port'
167
+ EOF
168
+ pid = xfork { redirect_test_io { exec($unicorn_bin, "-c#{tmp.path}") } }
169
+ wait_workers_ready("test_stderr.#{pid}.log", 1)
170
+ results = hit(["http://#@addr:#@port/"])
171
+ assert_equal @tmpdir, results.first
172
+ File.truncate("test_stderr.#{pid}.log", 0)
173
+
174
+ tmp.sysseek(0)
175
+ tmp.truncate(0)
176
+ tmp.syswrite <<EOF
177
+ working_directory '#{other.path}'
178
+ listen '#@addr:#@port'
179
+ EOF
180
+
181
+ Process.kill(:HUP, pid)
182
+ lines = []
183
+ re = /config_file=(.+) would not be accessible in working_directory=(.+)/
184
+ until lines.grep(re)
185
+ sleep 0.1
186
+ lines = File.readlines("test_stderr.#{pid}.log")
187
+ end
188
+
189
+ File.truncate("test_stderr.#{pid}.log", 0)
190
+ FileUtils.cp('unicorn.config', other.path + "/unicorn.config")
191
+ Process.kill(:HUP, pid)
192
+ wait_workers_ready("test_stderr.#{pid}.log", 1)
193
+ results = hit(["http://#@addr:#@port/"])
194
+ assert_equal other.path, results.first
195
+
196
+ Process.kill(:QUIT, pid)
197
+ ensure
198
+ FileUtils.rmtree(other.path)
199
+ end
200
+
201
+ def test_working_directory
202
+ other = Tempfile.new('unicorn.wd')
203
+ File.unlink(other.path)
204
+ Dir.mkdir(other.path)
205
+ File.open("config.ru", "wb") do |fp|
206
+ fp.syswrite WORKING_DIRECTORY_CHECK_RU
207
+ end
208
+ FileUtils.cp("config.ru", other.path + "/config.ru")
209
+ tmp = Tempfile.new('unicorn.config')
210
+ tmp.syswrite <<EOF
211
+ working_directory '#@tmpdir'
212
+ listen '#@addr:#@port'
213
+ EOF
214
+ pid = xfork { redirect_test_io { exec($unicorn_bin, "-c#{tmp.path}") } }
215
+ wait_workers_ready("test_stderr.#{pid}.log", 1)
216
+ results = hit(["http://#@addr:#@port/"])
217
+ assert_equal @tmpdir, results.first
218
+ File.truncate("test_stderr.#{pid}.log", 0)
219
+
220
+ tmp.sysseek(0)
221
+ tmp.truncate(0)
222
+ tmp.syswrite <<EOF
223
+ working_directory '#{other.path}'
224
+ listen '#@addr:#@port'
225
+ EOF
226
+
227
+ Process.kill(:HUP, pid)
228
+ wait_workers_ready("test_stderr.#{pid}.log", 1)
229
+ results = hit(["http://#@addr:#@port/"])
230
+ assert_equal other.path, results.first
231
+
232
+ Process.kill(:QUIT, pid)
233
+ ensure
234
+ FileUtils.rmtree(other.path)
235
+ end
236
+
237
+ def test_working_directory_controls_relative_paths
238
+ other = Tempfile.new('unicorn.wd')
239
+ File.unlink(other.path)
240
+ Dir.mkdir(other.path)
241
+ File.open("config.ru", "wb") do |fp|
242
+ fp.syswrite WORKING_DIRECTORY_CHECK_RU
243
+ end
244
+ FileUtils.cp("config.ru", other.path + "/config.ru")
245
+ system('mkfifo', "#{other.path}/fifo")
246
+ tmp = Tempfile.new('unicorn.config')
247
+ tmp.syswrite <<EOF
248
+ pid "pid_file_here"
249
+ stderr_path "stderr_log_here"
250
+ stdout_path "stdout_log_here"
251
+ working_directory '#{other.path}'
252
+ listen '#@addr:#@port'
253
+ after_fork do |server, worker|
254
+ File.open("fifo", "wb").close
255
+ end
256
+ EOF
257
+ pid = xfork { redirect_test_io { exec($unicorn_bin, "-c#{tmp.path}") } }
258
+ File.open("#{other.path}/fifo", "rb").close
259
+
260
+ assert ! File.exist?("stderr_log_here")
261
+ assert ! File.exist?("stdout_log_here")
262
+ assert ! File.exist?("pid_file_here")
263
+
264
+ assert ! File.exist?("#@tmpdir/stderr_log_here")
265
+ assert ! File.exist?("#@tmpdir/stdout_log_here")
266
+ assert ! File.exist?("#@tmpdir/pid_file_here")
267
+
268
+ assert File.exist?("#{other.path}/pid_file_here")
269
+ assert_equal "#{pid}\n", File.read("#{other.path}/pid_file_here")
270
+ assert File.exist?("#{other.path}/stderr_log_here")
271
+ assert File.exist?("#{other.path}/stdout_log_here")
272
+ wait_master_ready("#{other.path}/stderr_log_here")
273
+
274
+ Process.kill(:QUIT, pid)
275
+ ensure
276
+ FileUtils.rmtree(other.path)
277
+ end
278
+
279
+ def test_exit_signals
280
+ %w(INT TERM QUIT).each do |sig|
281
+ File.open("config.ru", "wb") { |fp| fp.syswrite(HI) }
282
+ pid = xfork { redirect_test_io { exec($unicorn_bin, "-l#@addr:#@port") } }
283
+ wait_master_ready("test_stderr.#{pid}.log")
284
+ wait_workers_ready("test_stderr.#{pid}.log", 1)
285
+
286
+ Process.kill(sig, pid)
287
+ pid, status = Process.waitpid2(pid)
288
+
289
+ reaped = File.readlines("test_stderr.#{pid}.log").grep(/reaped/)
290
+ assert_equal 1, reaped.size
291
+ assert status.exited?
292
+ end
293
+ end
294
+
295
+ def test_basic
296
+ File.open("config.ru", "wb") { |fp| fp.syswrite(HI) }
297
+ pid = fork do
298
+ redirect_test_io { exec($unicorn_bin, "-l", "#{@addr}:#{@port}") }
299
+ end
300
+ results = retry_hit(["http://#{@addr}:#{@port}/"])
301
+ assert_equal String, results[0].class
302
+ assert_shutdown(pid)
303
+ end
304
+
305
+ def test_rack_env_unset
306
+ File.open("config.ru", "wb") { |fp| fp.syswrite(SHOW_RACK_ENV) }
307
+ pid = fork { redirect_test_io { exec($unicorn_bin, "-l#@addr:#@port") } }
308
+ results = retry_hit(["http://#{@addr}:#{@port}/"])
309
+ assert_equal "development", results.first
310
+ assert_shutdown(pid)
311
+ end
312
+
313
+ def test_rack_env_cli_set
314
+ File.open("config.ru", "wb") { |fp| fp.syswrite(SHOW_RACK_ENV) }
315
+ pid = fork {
316
+ redirect_test_io { exec($unicorn_bin, "-l#@addr:#@port", "-Easdf") }
317
+ }
318
+ results = retry_hit(["http://#{@addr}:#{@port}/"])
319
+ assert_equal "asdf", results.first
320
+ assert_shutdown(pid)
321
+ end
322
+
323
+ def test_rack_env_ENV_set
324
+ File.open("config.ru", "wb") { |fp| fp.syswrite(SHOW_RACK_ENV) }
325
+ pid = fork {
326
+ ENV["RACK_ENV"] = "foobar"
327
+ redirect_test_io { exec($unicorn_bin, "-l#@addr:#@port") }
328
+ }
329
+ results = retry_hit(["http://#{@addr}:#{@port}/"])
330
+ assert_equal "foobar", results.first
331
+ assert_shutdown(pid)
332
+ end
333
+
334
+ def test_rack_env_cli_override_ENV
335
+ File.open("config.ru", "wb") { |fp| fp.syswrite(SHOW_RACK_ENV) }
336
+ pid = fork {
337
+ ENV["RACK_ENV"] = "foobar"
338
+ redirect_test_io { exec($unicorn_bin, "-l#@addr:#@port", "-Easdf") }
339
+ }
340
+ results = retry_hit(["http://#{@addr}:#{@port}/"])
341
+ assert_equal "asdf", results.first
342
+ assert_shutdown(pid)
343
+ end
344
+
345
+ def test_ttin_ttou
346
+ File.open("config.ru", "wb") { |fp| fp.syswrite(HI) }
347
+ pid = fork { redirect_test_io { exec($unicorn_bin, "-l#@addr:#@port") } }
348
+ log = "test_stderr.#{pid}.log"
349
+ wait_master_ready(log)
350
+ [ 2, 3].each { |i|
351
+ Process.kill(:TTIN, pid)
352
+ wait_workers_ready(log, i)
353
+ }
354
+ File.truncate(log, 0)
355
+ reaped = nil
356
+ [ 2, 1, 0].each { |i|
357
+ Process.kill(:TTOU, pid)
358
+ DEFAULT_TRIES.times {
359
+ sleep DEFAULT_RES
360
+ reaped = File.readlines(log).grep(/reaped.*\s*worker=#{i}$/)
361
+ break if reaped.size == 1
362
+ }
363
+ assert_equal 1, reaped.size
364
+ }
365
+ end
366
+
367
+ def test_help
368
+ redirect_test_io do
369
+ assert(system($unicorn_bin, "-h"), "help text returns true")
370
+ end
371
+ assert_equal 0, File.stat("test_stderr.#$$.log").size
372
+ assert_not_equal 0, File.stat("test_stdout.#$$.log").size
373
+ lines = File.readlines("test_stdout.#$$.log")
374
+
375
+ # Be considerate of the on-call technician working from their
376
+ # mobile phone or netbook on a slow connection :)
377
+ assert lines.size <= 24, "help height fits in an ANSI terminal window"
378
+ lines.each do |line|
379
+ line.chomp!
380
+ assert line.size <= 80, "help width fits in an ANSI terminal window"
381
+ end
382
+ end
383
+
384
+ def test_broken_reexec_config
385
+ File.open("config.ru", "wb") { |fp| fp.syswrite(HI) }
386
+ pid_file = "#{@tmpdir}/test.pid"
387
+ old_file = "#{pid_file}.oldbin"
388
+ ucfg = Tempfile.new('unicorn_test_config')
389
+ ucfg.syswrite("listen %(#@addr:#@port)\n")
390
+ ucfg.syswrite("pid %(#{pid_file})\n")
391
+ ucfg.syswrite("logger Logger.new(%(#{@tmpdir}/log))\n")
392
+ pid = xfork do
393
+ redirect_test_io do
394
+ exec($unicorn_bin, "-D", "-l#{@addr}:#{@port}", "-c#{ucfg.path}")
395
+ end
396
+ end
397
+ results = retry_hit(["http://#{@addr}:#{@port}/"])
398
+ assert_equal String, results[0].class
399
+
400
+ wait_for_file(pid_file)
401
+ Process.waitpid(pid)
402
+ Process.kill(:USR2, File.read(pid_file).to_i)
403
+ wait_for_file(old_file)
404
+ wait_for_file(pid_file)
405
+ old_pid = File.read(old_file).to_i
406
+ Process.kill(:QUIT, old_pid)
407
+ wait_for_death(old_pid)
408
+
409
+ ucfg.syswrite("timeout %(#{pid_file})\n") # introduce a bug
410
+ current_pid = File.read(pid_file).to_i
411
+ Process.kill(:USR2, current_pid)
412
+
413
+ # wait for pid_file to restore itself
414
+ tries = DEFAULT_TRIES
415
+ begin
416
+ while current_pid != File.read(pid_file).to_i
417
+ sleep(DEFAULT_RES) and (tries -= 1) > 0
418
+ end
419
+ rescue Errno::ENOENT
420
+ (sleep(DEFAULT_RES) and (tries -= 1) > 0) and retry
421
+ end
422
+ assert_equal current_pid, File.read(pid_file).to_i
423
+
424
+ tries = DEFAULT_TRIES
425
+ while File.exist?(old_file)
426
+ (sleep(DEFAULT_RES) and (tries -= 1) > 0) or break
427
+ end
428
+ assert ! File.exist?(old_file), "oldbin=#{old_file} gone"
429
+ port2 = unused_port(@addr)
430
+
431
+ # fix the bug
432
+ ucfg.sysseek(0)
433
+ ucfg.truncate(0)
434
+ ucfg.syswrite("listen %(#@addr:#@port)\n")
435
+ ucfg.syswrite("listen %(#@addr:#{port2})\n")
436
+ ucfg.syswrite("pid %(#{pid_file})\n")
437
+ Process.kill(:USR2, current_pid)
438
+
439
+ wait_for_file(old_file)
440
+ wait_for_file(pid_file)
441
+ new_pid = File.read(pid_file).to_i
442
+ assert_not_equal current_pid, new_pid
443
+ assert_equal current_pid, File.read(old_file).to_i
444
+ results = retry_hit(["http://#{@addr}:#{@port}/",
445
+ "http://#{@addr}:#{port2}/"])
446
+ assert_equal String, results[0].class
447
+ assert_equal String, results[1].class
448
+
449
+ Process.kill(:QUIT, current_pid)
450
+ Process.kill(:QUIT, new_pid)
451
+ end
452
+
453
+ def test_broken_reexec_ru
454
+ File.open("config.ru", "wb") { |fp| fp.syswrite(HI) }
455
+ pid_file = "#{@tmpdir}/test.pid"
456
+ old_file = "#{pid_file}.oldbin"
457
+ ucfg = Tempfile.new('unicorn_test_config')
458
+ ucfg.syswrite("pid %(#{pid_file})\n")
459
+ ucfg.syswrite("logger Logger.new(%(#{@tmpdir}/log))\n")
460
+ pid = xfork do
461
+ redirect_test_io do
462
+ exec($unicorn_bin, "-D", "-l#{@addr}:#{@port}", "-c#{ucfg.path}")
463
+ end
464
+ end
465
+ results = retry_hit(["http://#{@addr}:#{@port}/"])
466
+ assert_equal String, results[0].class
467
+
468
+ wait_for_file(pid_file)
469
+ Process.waitpid(pid)
470
+ Process.kill(:USR2, File.read(pid_file).to_i)
471
+ wait_for_file(old_file)
472
+ wait_for_file(pid_file)
473
+ old_pid = File.read(old_file).to_i
474
+ Process.kill(:QUIT, old_pid)
475
+ wait_for_death(old_pid)
476
+
477
+ File.unlink("config.ru") # break reloading
478
+ current_pid = File.read(pid_file).to_i
479
+ Process.kill(:USR2, current_pid)
480
+
481
+ # wait for pid_file to restore itself
482
+ tries = DEFAULT_TRIES
483
+ begin
484
+ while current_pid != File.read(pid_file).to_i
485
+ sleep(DEFAULT_RES) and (tries -= 1) > 0
486
+ end
487
+ rescue Errno::ENOENT
488
+ (sleep(DEFAULT_RES) and (tries -= 1) > 0) and retry
489
+ end
490
+
491
+ tries = DEFAULT_TRIES
492
+ while File.exist?(old_file)
493
+ (sleep(DEFAULT_RES) and (tries -= 1) > 0) or break
494
+ end
495
+ assert ! File.exist?(old_file), "oldbin=#{old_file} gone"
496
+ assert_equal current_pid, File.read(pid_file).to_i
497
+
498
+ # fix the bug
499
+ File.open("config.ru", "wb") { |fp| fp.syswrite(HI) }
500
+ Process.kill(:USR2, current_pid)
501
+ wait_for_file(old_file)
502
+ wait_for_file(pid_file)
503
+ new_pid = File.read(pid_file).to_i
504
+ assert_not_equal current_pid, new_pid
505
+ assert_equal current_pid, File.read(old_file).to_i
506
+ results = retry_hit(["http://#{@addr}:#{@port}/"])
507
+ assert_equal String, results[0].class
508
+
509
+ Process.kill(:QUIT, current_pid)
510
+ Process.kill(:QUIT, new_pid)
511
+ end
512
+
513
+ def test_unicorn_config_listener_swap
514
+ port_cli = unused_port
515
+ File.open("config.ru", "wb") { |fp| fp.syswrite(HI) }
516
+ ucfg = Tempfile.new('unicorn_test_config')
517
+ ucfg.syswrite("listen '#@addr:#@port'\n")
518
+ pid = xfork do
519
+ redirect_test_io do
520
+ exec($unicorn_bin, "-c#{ucfg.path}", "-l#@addr:#{port_cli}")
521
+ end
522
+ end
523
+ results = retry_hit(["http://#@addr:#{port_cli}/"])
524
+ assert_equal String, results[0].class
525
+ results = retry_hit(["http://#@addr:#@port/"])
526
+ assert_equal String, results[0].class
527
+
528
+ port2 = unused_port(@addr)
529
+ ucfg.sysseek(0)
530
+ ucfg.truncate(0)
531
+ ucfg.syswrite("listen '#@addr:#{port2}'\n")
532
+ Process.kill(:HUP, pid)
533
+
534
+ results = retry_hit(["http://#@addr:#{port2}/"])
535
+ assert_equal String, results[0].class
536
+ results = retry_hit(["http://#@addr:#{port_cli}/"])
537
+ assert_equal String, results[0].class
538
+ reuse = TCPServer.new(@addr, @port)
539
+ reuse.close
540
+ assert_shutdown(pid)
541
+ end
542
+
543
+ def test_unicorn_config_listen_with_options
544
+ File.open("config.ru", "wb") { |fp| fp.syswrite(HI) }
545
+ ucfg = Tempfile.new('unicorn_test_config')
546
+ ucfg.syswrite("listen '#{@addr}:#{@port}', :backlog => 512,\n")
547
+ ucfg.syswrite(" :rcvbuf => 4096,\n")
548
+ ucfg.syswrite(" :sndbuf => 4096\n")
549
+ pid = xfork do
550
+ redirect_test_io { exec($unicorn_bin, "-c#{ucfg.path}") }
551
+ end
552
+ results = retry_hit(["http://#{@addr}:#{@port}/"])
553
+ assert_equal String, results[0].class
554
+ assert_shutdown(pid)
555
+ end
556
+
557
+ def test_unicorn_config_per_worker_listen
558
+ port2 = unused_port
559
+ pid_spit = 'use Rack::ContentLength;' \
560
+ 'run proc { |e| [ 200, {"Content-Type"=>"text/plain"}, ["#$$\\n"] ] }'
561
+ File.open("config.ru", "wb") { |fp| fp.syswrite(pid_spit) }
562
+ tmp = Tempfile.new('test.socket')
563
+ File.unlink(tmp.path)
564
+ ucfg = Tempfile.new('unicorn_test_config')
565
+ ucfg.syswrite("listen '#@addr:#@port'\n")
566
+ ucfg.syswrite("after_fork { |s,w|\n")
567
+ ucfg.syswrite(" s.listen('#{tmp.path}', :backlog => 5, :sndbuf => 8192)\n")
568
+ ucfg.syswrite(" s.listen('#@addr:#{port2}', :rcvbuf => 8192)\n")
569
+ ucfg.syswrite("\n}\n")
570
+ pid = xfork do
571
+ redirect_test_io { exec($unicorn_bin, "-c#{ucfg.path}") }
572
+ end
573
+ results = retry_hit(["http://#{@addr}:#{@port}/"])
574
+ assert_equal String, results[0].class
575
+ worker_pid = results[0].to_i
576
+ assert_not_equal pid, worker_pid
577
+ s = unix_socket(tmp.path)
578
+ s.syswrite("GET / HTTP/1.0\r\n\r\n")
579
+ results = ''
580
+ loop { results << s.sysread(4096) } rescue nil
581
+ s.close
582
+ assert_equal worker_pid, results.split(/\r\n/).last.to_i
583
+ results = hit(["http://#@addr:#{port2}/"])
584
+ assert_equal String, results[0].class
585
+ assert_equal worker_pid, results[0].to_i
586
+ assert_shutdown(pid)
587
+ end
588
+
589
+ def test_unicorn_config_listen_augments_cli
590
+ port2 = unused_port(@addr)
591
+ File.open("config.ru", "wb") { |fp| fp.syswrite(HI) }
592
+ ucfg = Tempfile.new('unicorn_test_config')
593
+ ucfg.syswrite("listen '#{@addr}:#{@port}'\n")
594
+ pid = xfork do
595
+ redirect_test_io do
596
+ exec($unicorn_bin, "-c#{ucfg.path}", "-l#{@addr}:#{port2}")
597
+ end
598
+ end
599
+ uris = [@port, port2].map { |i| "http://#{@addr}:#{i}/" }
600
+ results = retry_hit(uris)
601
+ assert_equal results.size, uris.size
602
+ assert_equal String, results[0].class
603
+ assert_equal String, results[1].class
604
+ assert_shutdown(pid)
605
+ end
606
+
607
+ def test_weird_config_settings
608
+ File.open("config.ru", "wb") { |fp| fp.syswrite(HI) }
609
+ ucfg = Tempfile.new('unicorn_test_config')
610
+ proc_total = HEAVY_WORKERS + 1 # + 1 for master
611
+ ucfg.syswrite(HEAVY_CFG)
612
+ pid = xfork do
613
+ redirect_test_io do
614
+ exec($unicorn_bin, "-c#{ucfg.path}", "-l#{@addr}:#{@port}")
615
+ end
616
+ end
617
+
618
+ results = retry_hit(["http://#{@addr}:#{@port}/"])
619
+ assert_equal String, results[0].class
620
+ wait_master_ready(COMMON_TMP.path)
621
+ wait_workers_ready(COMMON_TMP.path, HEAVY_WORKERS)
622
+ bf = File.readlines(COMMON_TMP.path).grep(/\bbefore_fork: worker=/)
623
+ assert_equal HEAVY_WORKERS, bf.size
624
+ rotate = Tempfile.new('unicorn_rotate')
625
+
626
+ File.rename(COMMON_TMP.path, rotate.path)
627
+ Process.kill(:USR1, pid)
628
+
629
+ wait_for_file(COMMON_TMP.path)
630
+ assert File.exist?(COMMON_TMP.path), "#{COMMON_TMP.path} exists"
631
+ # USR1 should've been passed to all workers
632
+ tries = DEFAULT_TRIES
633
+ log = File.readlines(rotate.path)
634
+ while (tries -= 1) > 0 &&
635
+ log.grep(/reopening logs\.\.\./).size < proc_total
636
+ sleep DEFAULT_RES
637
+ log = File.readlines(rotate.path)
638
+ end
639
+ assert_equal proc_total, log.grep(/reopening logs\.\.\./).size
640
+ assert_equal 0, log.grep(/done reopening logs/).size
641
+
642
+ tries = DEFAULT_TRIES
643
+ log = File.readlines(COMMON_TMP.path)
644
+ while (tries -= 1) > 0 && log.grep(/done reopening logs/).size < proc_total
645
+ sleep DEFAULT_RES
646
+ log = File.readlines(COMMON_TMP.path)
647
+ end
648
+ assert_equal proc_total, log.grep(/done reopening logs/).size
649
+ assert_equal 0, log.grep(/reopening logs\.\.\./).size
650
+
651
+ Process.kill(:QUIT, pid)
652
+ pid, status = Process.waitpid2(pid)
653
+
654
+ assert status.success?, "exited successfully"
655
+ end
656
+
657
+ def test_read_embedded_cli_switches
658
+ File.open("config.ru", "wb") do |fp|
659
+ fp.syswrite("#\\ -p #{@port} -o #{@addr}\n")
660
+ fp.syswrite(HI)
661
+ end
662
+ pid = fork { redirect_test_io { exec($unicorn_bin) } }
663
+ results = retry_hit(["http://#{@addr}:#{@port}/"])
664
+ assert_equal String, results[0].class
665
+ assert_shutdown(pid)
666
+ end
667
+
668
+ def test_config_ru_alt_path
669
+ config_path = "#{@tmpdir}/foo.ru"
670
+ File.open(config_path, "wb") { |fp| fp.syswrite(HI) }
671
+ pid = fork do
672
+ redirect_test_io do
673
+ Dir.chdir("/")
674
+ exec($unicorn_bin, "-l#{@addr}:#{@port}", config_path)
675
+ end
676
+ end
677
+ results = retry_hit(["http://#{@addr}:#{@port}/"])
678
+ assert_equal String, results[0].class
679
+ assert_shutdown(pid)
680
+ end
681
+
682
+ def test_load_module
683
+ libdir = "#{@tmpdir}/lib"
684
+ FileUtils.mkpath([ libdir ])
685
+ config_path = "#{libdir}/hello.rb"
686
+ File.open(config_path, "wb") { |fp| fp.syswrite(HELLO) }
687
+ pid = fork do
688
+ redirect_test_io do
689
+ Dir.chdir("/")
690
+ exec($unicorn_bin, "-l#{@addr}:#{@port}", config_path)
691
+ end
692
+ end
693
+ results = retry_hit(["http://#{@addr}:#{@port}/"])
694
+ assert_equal String, results[0].class
695
+ assert_shutdown(pid)
696
+ end
697
+
698
+ def test_reexec
699
+ File.open("config.ru", "wb") { |fp| fp.syswrite(HI) }
700
+ pid_file = "#{@tmpdir}/test.pid"
701
+ pid = fork do
702
+ redirect_test_io do
703
+ exec($unicorn_bin, "-l#{@addr}:#{@port}", "-P#{pid_file}")
704
+ end
705
+ end
706
+ reexec_basic_test(pid, pid_file)
707
+ end
708
+
709
+ def test_reexec_alt_config
710
+ config_file = "#{@tmpdir}/foo.ru"
711
+ File.open(config_file, "wb") { |fp| fp.syswrite(HI) }
712
+ pid_file = "#{@tmpdir}/test.pid"
713
+ pid = fork do
714
+ redirect_test_io do
715
+ exec($unicorn_bin, "-l#{@addr}:#{@port}", "-P#{pid_file}", config_file)
716
+ end
717
+ end
718
+ reexec_basic_test(pid, pid_file)
719
+ end
720
+
721
+ def test_socket_unlinked_restore
722
+ results = nil
723
+ sock = Tempfile.new('unicorn_test_sock')
724
+ sock_path = sock.path
725
+ @sockets << sock_path
726
+ sock.close!
727
+ ucfg = Tempfile.new('unicorn_test_config')
728
+ ucfg.syswrite("listen \"#{sock_path}\"\n")
729
+
730
+ File.open("config.ru", "wb") { |fp| fp.syswrite(HI) }
731
+ pid = xfork { redirect_test_io { exec($unicorn_bin, "-c#{ucfg.path}") } }
732
+ wait_for_file(sock_path)
733
+ assert File.socket?(sock_path)
734
+
735
+ sock = unix_socket(sock_path)
736
+ sock.syswrite("GET / HTTP/1.0\r\n\r\n")
737
+ results = sock.sysread(4096)
738
+
739
+ assert_equal String, results.class
740
+ File.unlink(sock_path)
741
+ Process.kill(:HUP, pid)
742
+ wait_for_file(sock_path)
743
+ assert File.socket?(sock_path)
744
+
745
+ sock = unix_socket(sock_path)
746
+ sock.syswrite("GET / HTTP/1.0\r\n\r\n")
747
+ results = sock.sysread(4096)
748
+
749
+ assert_equal String, results.class
750
+ end
751
+
752
+ def test_unicorn_config_file
753
+ pid_file = "#{@tmpdir}/test.pid"
754
+ sock = Tempfile.new('unicorn_test_sock')
755
+ sock_path = sock.path
756
+ sock.close!
757
+ @sockets << sock_path
758
+
759
+ log = Tempfile.new('unicorn_test_log')
760
+ ucfg = Tempfile.new('unicorn_test_config')
761
+ ucfg.syswrite("listen \"#{sock_path}\"\n")
762
+ ucfg.syswrite("pid \"#{pid_file}\"\n")
763
+ ucfg.syswrite("logger Logger.new('#{log.path}')\n")
764
+ ucfg.close
765
+
766
+ File.open("config.ru", "wb") { |fp| fp.syswrite(HI) }
767
+ pid = xfork do
768
+ redirect_test_io do
769
+ exec($unicorn_bin, "-l#{@addr}:#{@port}",
770
+ "-P#{pid_file}", "-c#{ucfg.path}")
771
+ end
772
+ end
773
+ results = retry_hit(["http://#{@addr}:#{@port}/"])
774
+ assert_equal String, results[0].class
775
+ wait_master_ready(log.path)
776
+ assert File.exist?(pid_file), "pid_file created"
777
+ assert_equal pid, File.read(pid_file).to_i
778
+ assert File.socket?(sock_path), "socket created"
779
+
780
+ sock = unix_socket(sock_path)
781
+ sock.syswrite("GET / HTTP/1.0\r\n\r\n")
782
+ results = sock.sysread(4096)
783
+
784
+ assert_equal String, results.class
785
+
786
+ # try reloading the config
787
+ sock = Tempfile.new('new_test_sock')
788
+ new_sock_path = sock.path
789
+ @sockets << new_sock_path
790
+ sock.close!
791
+ new_log = Tempfile.new('unicorn_test_log')
792
+ new_log.sync = true
793
+ assert_equal 0, new_log.size
794
+
795
+ ucfg = File.open(ucfg.path, "wb")
796
+ ucfg.syswrite("listen \"#{sock_path}\"\n")
797
+ ucfg.syswrite("listen \"#{new_sock_path}\"\n")
798
+ ucfg.syswrite("pid \"#{pid_file}\"\n")
799
+ ucfg.syswrite("logger Logger.new('#{new_log.path}')\n")
800
+ ucfg.close
801
+ Process.kill(:HUP, pid)
802
+
803
+ wait_for_file(new_sock_path)
804
+ assert File.socket?(new_sock_path), "socket exists"
805
+ @sockets.each do |path|
806
+ sock = unix_socket(path)
807
+ sock.syswrite("GET / HTTP/1.0\r\n\r\n")
808
+ results = sock.sysread(4096)
809
+ assert_equal String, results.class
810
+ end
811
+
812
+ assert_not_equal 0, new_log.size
813
+ reexec_usr2_quit_test(pid, pid_file)
814
+ end
815
+
816
+ def test_daemonize_reexec
817
+ pid_file = "#{@tmpdir}/test.pid"
818
+ log = Tempfile.new('unicorn_test_log')
819
+ ucfg = Tempfile.new('unicorn_test_config')
820
+ ucfg.syswrite("pid \"#{pid_file}\"\n")
821
+ ucfg.syswrite("logger Logger.new('#{log.path}')\n")
822
+ ucfg.close
823
+
824
+ File.open("config.ru", "wb") { |fp| fp.syswrite(HI) }
825
+ pid = xfork do
826
+ redirect_test_io do
827
+ exec($unicorn_bin, "-D", "-l#{@addr}:#{@port}", "-c#{ucfg.path}")
828
+ end
829
+ end
830
+ results = retry_hit(["http://#{@addr}:#{@port}/"])
831
+ assert_equal String, results[0].class
832
+ wait_for_file(pid_file)
833
+ new_pid = File.read(pid_file).to_i
834
+ assert_not_equal pid, new_pid
835
+ pid, status = Process.waitpid2(pid)
836
+ assert status.success?, "original process exited successfully"
837
+ Process.kill(0, new_pid)
838
+ reexec_usr2_quit_test(new_pid, pid_file)
839
+ end
840
+
841
+ def test_daemonize_redirect_fail
842
+ pid_file = "#{@tmpdir}/test.pid"
843
+ ucfg = Tempfile.new('unicorn_test_config')
844
+ ucfg.syswrite("pid #{pid_file}\"\n")
845
+ err = Tempfile.new('stderr')
846
+ out = Tempfile.new('stdout ')
847
+
848
+ File.open("config.ru", "wb") { |fp| fp.syswrite(HI) }
849
+ pid = xfork do
850
+ $stderr.reopen(err.path, "a")
851
+ $stdout.reopen(out.path, "a")
852
+ exec($unicorn_bin, "-D", "-l#{@addr}:#{@port}", "-c#{ucfg.path}")
853
+ end
854
+ pid, status = Process.waitpid2(pid)
855
+ assert ! status.success?, "original process exited successfully"
856
+ sleep 1 # can't waitpid on a daemonized process :<
857
+ assert err.stat.size > 0
858
+ end
859
+
860
+ def test_reexec_fd_leak
861
+ unless RUBY_PLATFORM =~ /linux/ # Solaris may work, too, but I forget...
862
+ warn "FD leak test only works on Linux at the moment"
863
+ return
864
+ end
865
+ pid_file = "#{@tmpdir}/test.pid"
866
+ log = Tempfile.new('unicorn_test_log')
867
+ log.sync = true
868
+ ucfg = Tempfile.new('unicorn_test_config')
869
+ ucfg.syswrite("pid \"#{pid_file}\"\n")
870
+ ucfg.syswrite("logger Logger.new('#{log.path}')\n")
871
+ ucfg.syswrite("stderr_path '#{log.path}'\n")
872
+ ucfg.syswrite("stdout_path '#{log.path}'\n")
873
+ ucfg.close
874
+
875
+ File.open("config.ru", "wb") { |fp| fp.syswrite(HI) }
876
+ pid = xfork do
877
+ redirect_test_io do
878
+ exec($unicorn_bin, "-D", "-l#{@addr}:#{@port}", "-c#{ucfg.path}")
879
+ end
880
+ end
881
+
882
+ wait_master_ready(log.path)
883
+ wait_workers_ready(log.path, 1)
884
+ File.truncate(log.path, 0)
885
+ wait_for_file(pid_file)
886
+ orig_pid = pid = File.read(pid_file).to_i
887
+ orig_fds = `ls -l /proc/#{pid}/fd`.split(/\n/)
888
+ assert $?.success?
889
+ expect_size = orig_fds.size
890
+
891
+ Process.kill(:USR2, pid)
892
+ wait_for_file("#{pid_file}.oldbin")
893
+ Process.kill(:QUIT, pid)
894
+
895
+ wait_for_death(pid)
896
+
897
+ wait_master_ready(log.path)
898
+ wait_workers_ready(log.path, 1)
899
+ File.truncate(log.path, 0)
900
+ wait_for_file(pid_file)
901
+ pid = File.read(pid_file).to_i
902
+ assert_not_equal orig_pid, pid
903
+ curr_fds = `ls -l /proc/#{pid}/fd`.split(/\n/)
904
+ assert $?.success?
905
+
906
+ # we could've inherited descriptors the first time around
907
+ assert expect_size >= curr_fds.size, curr_fds.inspect
908
+ expect_size = curr_fds.size
909
+
910
+ Process.kill(:USR2, pid)
911
+ wait_for_file("#{pid_file}.oldbin")
912
+ Process.kill(:QUIT, pid)
913
+
914
+ wait_for_death(pid)
915
+
916
+ wait_master_ready(log.path)
917
+ wait_workers_ready(log.path, 1)
918
+ File.truncate(log.path, 0)
919
+ wait_for_file(pid_file)
920
+ pid = File.read(pid_file).to_i
921
+ curr_fds = `ls -l /proc/#{pid}/fd`.split(/\n/)
922
+ assert $?.success?
923
+ assert_equal expect_size, curr_fds.size, curr_fds.inspect
924
+
925
+ Process.kill(:QUIT, pid)
926
+ wait_for_death(pid)
927
+ end
928
+
929
+ def hup_test_common(preload, check_client=false)
930
+ File.open("config.ru", "wb") { |fp| fp.syswrite(HI.gsub("HI", '#$$')) }
931
+ pid_file = Tempfile.new('pid')
932
+ ucfg = Tempfile.new('unicorn_test_config')
933
+ ucfg.syswrite("listen '#@addr:#@port'\n")
934
+ ucfg.syswrite("pid '#{pid_file.path}'\n")
935
+ ucfg.syswrite("preload_app true\n") if preload
936
+ ucfg.syswrite("check_client_connection true\n") if check_client
937
+ ucfg.syswrite("stderr_path 'test_stderr.#$$.log'\n")
938
+ ucfg.syswrite("stdout_path 'test_stdout.#$$.log'\n")
939
+ pid = xfork {
940
+ redirect_test_io { exec($unicorn_bin, "-D", "-c", ucfg.path) }
941
+ }
942
+ _, status = Process.waitpid2(pid)
943
+ assert status.success?
944
+ wait_master_ready("test_stderr.#$$.log")
945
+ wait_workers_ready("test_stderr.#$$.log", 1)
946
+ uri = URI.parse("http://#@addr:#@port/")
947
+ pids = Tempfile.new('worker_pids')
948
+ r, w = IO.pipe
949
+ hitter = fork {
950
+ r.close
951
+ bodies = Hash.new(0)
952
+ at_exit { pids.syswrite(bodies.inspect) }
953
+ trap(:TERM) { exit(0) }
954
+ nr = 0
955
+ loop {
956
+ rv = Net::HTTP.get(uri)
957
+ pid = rv.to_i
958
+ exit!(1) if pid <= 0
959
+ bodies[pid] += 1
960
+ nr += 1
961
+ if nr == 1
962
+ w.syswrite('1')
963
+ elsif bodies.size > 1
964
+ w.syswrite('2')
965
+ sleep
966
+ end
967
+ }
968
+ }
969
+ w.close
970
+ assert_equal '1', r.read(1)
971
+ daemon_pid = File.read(pid_file.path).to_i
972
+ assert daemon_pid > 0
973
+ Process.kill(:HUP, daemon_pid)
974
+ assert_equal '2', r.read(1)
975
+ Process.kill(:TERM, hitter)
976
+ _, hitter_status = Process.waitpid2(hitter)
977
+ assert(hitter_status.success?,
978
+ "invalid: #{hitter_status.inspect} #{File.read(pids.path)}" \
979
+ "#{File.read("test_stderr.#$$.log")}")
980
+ pids.sysseek(0)
981
+ pids = eval(pids.read)
982
+ assert_kind_of(Hash, pids)
983
+ assert_equal 2, pids.size
984
+ pids.keys.each { |x|
985
+ assert_kind_of(Integer, x)
986
+ assert x > 0
987
+ assert pids[x] > 0
988
+ }
989
+ Process.kill(:QUIT, daemon_pid)
990
+ wait_for_death(daemon_pid)
991
+ end
992
+
993
+ def test_preload_app_hup
994
+ hup_test_common(true)
995
+ end
996
+
997
+ def test_hup
998
+ hup_test_common(false)
999
+ end
1000
+
1001
+ def test_check_client_hup
1002
+ hup_test_common(false, true)
1003
+ end
1004
+
1005
+ def test_default_listen_hup_holds_listener
1006
+ default_listen_lock do
1007
+ res, pid_path = default_listen_setup
1008
+ daemon_pid = File.read(pid_path).to_i
1009
+ Process.kill(:HUP, daemon_pid)
1010
+ wait_workers_ready("test_stderr.#$$.log", 1)
1011
+ res2 = hit(["http://#{Unicorn::Const::DEFAULT_LISTEN}/"])
1012
+ assert_match %r{\d+}, res2.first
1013
+ assert res2.first != res.first
1014
+ Process.kill(:QUIT, daemon_pid)
1015
+ wait_for_death(daemon_pid)
1016
+ end
1017
+ end
1018
+
1019
+ def test_default_listen_upgrade_holds_listener
1020
+ default_listen_lock do
1021
+ res, pid_path = default_listen_setup
1022
+ daemon_pid = File.read(pid_path).to_i
1023
+
1024
+ Process.kill(:USR2, daemon_pid)
1025
+ wait_for_file("#{pid_path}.oldbin")
1026
+ wait_for_file(pid_path)
1027
+ Process.kill(:QUIT, daemon_pid)
1028
+ wait_for_death(daemon_pid)
1029
+
1030
+ daemon_pid = File.read(pid_path).to_i
1031
+ wait_workers_ready("test_stderr.#$$.log", 1)
1032
+ File.truncate("test_stderr.#$$.log", 0)
1033
+
1034
+ res2 = hit(["http://#{Unicorn::Const::DEFAULT_LISTEN}/"])
1035
+ assert_match %r{\d+}, res2.first
1036
+ assert res2.first != res.first
1037
+
1038
+ Process.kill(:HUP, daemon_pid)
1039
+ wait_workers_ready("test_stderr.#$$.log", 1)
1040
+ File.truncate("test_stderr.#$$.log", 0)
1041
+ res3 = hit(["http://#{Unicorn::Const::DEFAULT_LISTEN}/"])
1042
+ assert res2.first != res3.first
1043
+
1044
+ Process.kill(:QUIT, daemon_pid)
1045
+ wait_for_death(daemon_pid)
1046
+ end
1047
+ end
1048
+
1049
+ def default_listen_setup
1050
+ File.open("config.ru", "wb") { |fp| fp.syswrite(HI.gsub("HI", '#$$')) }
1051
+ pid_path = (tmp = Tempfile.new('pid')).path
1052
+ tmp.close!
1053
+ ucfg = Tempfile.new('unicorn_test_config')
1054
+ ucfg.syswrite("pid '#{pid_path}'\n")
1055
+ ucfg.syswrite("stderr_path 'test_stderr.#$$.log'\n")
1056
+ ucfg.syswrite("stdout_path 'test_stdout.#$$.log'\n")
1057
+ pid = xfork {
1058
+ redirect_test_io { exec($unicorn_bin, "-D", "-c", ucfg.path) }
1059
+ }
1060
+ _, status = Process.waitpid2(pid)
1061
+ assert status.success?
1062
+ wait_master_ready("test_stderr.#$$.log")
1063
+ wait_workers_ready("test_stderr.#$$.log", 1)
1064
+ File.truncate("test_stderr.#$$.log", 0)
1065
+ res = hit(["http://#{Unicorn::Const::DEFAULT_LISTEN}/"])
1066
+ assert_match %r{\d+}, res.first
1067
+ [ res, pid_path ]
1068
+ end
1069
+
1070
+ # we need to flock() something to prevent these tests from running
1071
+ def default_listen_lock(&block)
1072
+ fp = File.open(FLOCK_PATH, "rb")
1073
+ begin
1074
+ fp.flock(File::LOCK_EX)
1075
+ begin
1076
+ TCPServer.new(Unicorn::Const::DEFAULT_HOST,
1077
+ Unicorn::Const::DEFAULT_PORT).close
1078
+ rescue Errno::EADDRINUSE, Errno::EACCES
1079
+ warn "can't bind to #{Unicorn::Const::DEFAULT_LISTEN}"
1080
+ return false
1081
+ end
1082
+
1083
+ # unused_port should never take this, but we may run an environment
1084
+ # where tests are being run against older unicorns...
1085
+ lock_path = "#{Dir::tmpdir}/unicorn_test." \
1086
+ "#{Unicorn::Const::DEFAULT_LISTEN}.lock"
1087
+ begin
1088
+ File.open(lock_path, File::WRONLY|File::CREAT|File::EXCL, 0600)
1089
+ yield
1090
+ rescue Errno::EEXIST
1091
+ lock_path = nil
1092
+ return false
1093
+ ensure
1094
+ File.unlink(lock_path) if lock_path
1095
+ end
1096
+ ensure
1097
+ fp.flock(File::LOCK_UN)
1098
+ end
1099
+ end
1100
+
1101
+ end if do_test