giraffesoft-unicorn 0.93.5

Sign up to get free protection for your applications and to get access to all the features.
Files changed (141) hide show
  1. data/.CHANGELOG.old +25 -0
  2. data/.document +16 -0
  3. data/.gitignore +20 -0
  4. data/.mailmap +26 -0
  5. data/CONTRIBUTORS +31 -0
  6. data/COPYING +339 -0
  7. data/DESIGN +105 -0
  8. data/Documentation/.gitignore +5 -0
  9. data/Documentation/GNUmakefile +30 -0
  10. data/Documentation/unicorn.1.txt +167 -0
  11. data/Documentation/unicorn_rails.1.txt +169 -0
  12. data/GIT-VERSION-GEN +40 -0
  13. data/GNUmakefile +270 -0
  14. data/HACKING +113 -0
  15. data/KNOWN_ISSUES +40 -0
  16. data/LICENSE +55 -0
  17. data/PHILOSOPHY +144 -0
  18. data/README +153 -0
  19. data/Rakefile +108 -0
  20. data/SIGNALS +97 -0
  21. data/TODO +16 -0
  22. data/TUNING +70 -0
  23. data/bin/unicorn +165 -0
  24. data/bin/unicorn_rails +208 -0
  25. data/examples/echo.ru +27 -0
  26. data/examples/git.ru +13 -0
  27. data/examples/init.sh +53 -0
  28. data/ext/unicorn_http/c_util.h +107 -0
  29. data/ext/unicorn_http/common_field_optimization.h +111 -0
  30. data/ext/unicorn_http/ext_help.h +73 -0
  31. data/ext/unicorn_http/extconf.rb +14 -0
  32. data/ext/unicorn_http/global_variables.h +91 -0
  33. data/ext/unicorn_http/unicorn_http.rl +715 -0
  34. data/ext/unicorn_http/unicorn_http_common.rl +74 -0
  35. data/lib/unicorn.rb +730 -0
  36. data/lib/unicorn/app/exec_cgi.rb +150 -0
  37. data/lib/unicorn/app/inetd.rb +109 -0
  38. data/lib/unicorn/app/old_rails.rb +31 -0
  39. data/lib/unicorn/app/old_rails/static.rb +60 -0
  40. data/lib/unicorn/cgi_wrapper.rb +145 -0
  41. data/lib/unicorn/configurator.rb +403 -0
  42. data/lib/unicorn/const.rb +37 -0
  43. data/lib/unicorn/http_request.rb +74 -0
  44. data/lib/unicorn/http_response.rb +74 -0
  45. data/lib/unicorn/launcher.rb +39 -0
  46. data/lib/unicorn/socket_helper.rb +138 -0
  47. data/lib/unicorn/tee_input.rb +174 -0
  48. data/lib/unicorn/util.rb +64 -0
  49. data/local.mk.sample +53 -0
  50. data/setup.rb +1586 -0
  51. data/test/aggregate.rb +15 -0
  52. data/test/benchmark/README +50 -0
  53. data/test/benchmark/dd.ru +18 -0
  54. data/test/exec/README +5 -0
  55. data/test/exec/test_exec.rb +855 -0
  56. data/test/rails/app-1.2.3/.gitignore +2 -0
  57. data/test/rails/app-1.2.3/Rakefile +7 -0
  58. data/test/rails/app-1.2.3/app/controllers/application.rb +6 -0
  59. data/test/rails/app-1.2.3/app/controllers/foo_controller.rb +36 -0
  60. data/test/rails/app-1.2.3/app/helpers/application_helper.rb +4 -0
  61. data/test/rails/app-1.2.3/config/boot.rb +11 -0
  62. data/test/rails/app-1.2.3/config/database.yml +12 -0
  63. data/test/rails/app-1.2.3/config/environment.rb +13 -0
  64. data/test/rails/app-1.2.3/config/environments/development.rb +9 -0
  65. data/test/rails/app-1.2.3/config/environments/production.rb +5 -0
  66. data/test/rails/app-1.2.3/config/routes.rb +6 -0
  67. data/test/rails/app-1.2.3/db/.gitignore +0 -0
  68. data/test/rails/app-1.2.3/public/404.html +1 -0
  69. data/test/rails/app-1.2.3/public/500.html +1 -0
  70. data/test/rails/app-2.0.2/.gitignore +2 -0
  71. data/test/rails/app-2.0.2/Rakefile +7 -0
  72. data/test/rails/app-2.0.2/app/controllers/application.rb +4 -0
  73. data/test/rails/app-2.0.2/app/controllers/foo_controller.rb +36 -0
  74. data/test/rails/app-2.0.2/app/helpers/application_helper.rb +4 -0
  75. data/test/rails/app-2.0.2/config/boot.rb +11 -0
  76. data/test/rails/app-2.0.2/config/database.yml +12 -0
  77. data/test/rails/app-2.0.2/config/environment.rb +17 -0
  78. data/test/rails/app-2.0.2/config/environments/development.rb +8 -0
  79. data/test/rails/app-2.0.2/config/environments/production.rb +5 -0
  80. data/test/rails/app-2.0.2/config/routes.rb +6 -0
  81. data/test/rails/app-2.0.2/db/.gitignore +0 -0
  82. data/test/rails/app-2.0.2/public/404.html +1 -0
  83. data/test/rails/app-2.0.2/public/500.html +1 -0
  84. data/test/rails/app-2.1.2/.gitignore +2 -0
  85. data/test/rails/app-2.1.2/Rakefile +7 -0
  86. data/test/rails/app-2.1.2/app/controllers/application.rb +4 -0
  87. data/test/rails/app-2.1.2/app/controllers/foo_controller.rb +36 -0
  88. data/test/rails/app-2.1.2/app/helpers/application_helper.rb +4 -0
  89. data/test/rails/app-2.1.2/config/boot.rb +111 -0
  90. data/test/rails/app-2.1.2/config/database.yml +12 -0
  91. data/test/rails/app-2.1.2/config/environment.rb +17 -0
  92. data/test/rails/app-2.1.2/config/environments/development.rb +7 -0
  93. data/test/rails/app-2.1.2/config/environments/production.rb +5 -0
  94. data/test/rails/app-2.1.2/config/routes.rb +6 -0
  95. data/test/rails/app-2.1.2/db/.gitignore +0 -0
  96. data/test/rails/app-2.1.2/public/404.html +1 -0
  97. data/test/rails/app-2.1.2/public/500.html +1 -0
  98. data/test/rails/app-2.2.2/.gitignore +2 -0
  99. data/test/rails/app-2.2.2/Rakefile +7 -0
  100. data/test/rails/app-2.2.2/app/controllers/application.rb +4 -0
  101. data/test/rails/app-2.2.2/app/controllers/foo_controller.rb +36 -0
  102. data/test/rails/app-2.2.2/app/helpers/application_helper.rb +4 -0
  103. data/test/rails/app-2.2.2/config/boot.rb +111 -0
  104. data/test/rails/app-2.2.2/config/database.yml +12 -0
  105. data/test/rails/app-2.2.2/config/environment.rb +17 -0
  106. data/test/rails/app-2.2.2/config/environments/development.rb +7 -0
  107. data/test/rails/app-2.2.2/config/environments/production.rb +5 -0
  108. data/test/rails/app-2.2.2/config/routes.rb +6 -0
  109. data/test/rails/app-2.2.2/db/.gitignore +0 -0
  110. data/test/rails/app-2.2.2/public/404.html +1 -0
  111. data/test/rails/app-2.2.2/public/500.html +1 -0
  112. data/test/rails/app-2.3.3.1/.gitignore +2 -0
  113. data/test/rails/app-2.3.3.1/Rakefile +7 -0
  114. data/test/rails/app-2.3.3.1/app/controllers/application_controller.rb +5 -0
  115. data/test/rails/app-2.3.3.1/app/controllers/foo_controller.rb +36 -0
  116. data/test/rails/app-2.3.3.1/app/helpers/application_helper.rb +4 -0
  117. data/test/rails/app-2.3.3.1/config/boot.rb +109 -0
  118. data/test/rails/app-2.3.3.1/config/database.yml +12 -0
  119. data/test/rails/app-2.3.3.1/config/environment.rb +17 -0
  120. data/test/rails/app-2.3.3.1/config/environments/development.rb +7 -0
  121. data/test/rails/app-2.3.3.1/config/environments/production.rb +6 -0
  122. data/test/rails/app-2.3.3.1/config/routes.rb +6 -0
  123. data/test/rails/app-2.3.3.1/db/.gitignore +0 -0
  124. data/test/rails/app-2.3.3.1/public/404.html +1 -0
  125. data/test/rails/app-2.3.3.1/public/500.html +1 -0
  126. data/test/rails/app-2.3.3.1/public/x.txt +1 -0
  127. data/test/rails/test_rails.rb +280 -0
  128. data/test/test_helper.rb +296 -0
  129. data/test/unit/test_configurator.rb +150 -0
  130. data/test/unit/test_http_parser.rb +492 -0
  131. data/test/unit/test_http_parser_ng.rb +308 -0
  132. data/test/unit/test_request.rb +184 -0
  133. data/test/unit/test_response.rb +110 -0
  134. data/test/unit/test_server.rb +188 -0
  135. data/test/unit/test_signals.rb +202 -0
  136. data/test/unit/test_socket_helper.rb +133 -0
  137. data/test/unit/test_tee_input.rb +229 -0
  138. data/test/unit/test_upload.rb +297 -0
  139. data/test/unit/test_util.rb +96 -0
  140. data/unicorn.gemspec +42 -0
  141. metadata +228 -0
@@ -0,0 +1,12 @@
1
+ development:
2
+ adapter: sqlite3
3
+ database: db/development.sqlite3
4
+ timeout: 5000
5
+ test:
6
+ adapter: sqlite3
7
+ database: db/test.sqlite3
8
+ timeout: 5000
9
+ production:
10
+ adapter: sqlite3
11
+ database: db/production.sqlite3
12
+ timeout: 5000
@@ -0,0 +1,17 @@
1
+ # -*- encoding: binary -*-
2
+
3
+ unless defined? RAILS_GEM_VERSION
4
+ RAILS_GEM_VERSION = ENV['UNICORN_RAILS_VERSION']
5
+ end
6
+
7
+ # Bootstrap the Rails environment, frameworks, and default configuration
8
+ require File.join(File.dirname(__FILE__), 'boot')
9
+
10
+ Rails::Initializer.run do |config|
11
+ config.frameworks -= [ :active_resource, :action_mailer ]
12
+ config.action_controller.session_store = :active_record_store
13
+ config.action_controller.session = {
14
+ :session_key => "_unicorn_rails_test.#{rand}",
15
+ :secret => "#{rand}#{rand}#{rand}#{rand}",
16
+ }
17
+ end
@@ -0,0 +1,7 @@
1
+ # -*- encoding: binary -*-
2
+
3
+ config.cache_classes = false
4
+ config.whiny_nils = true
5
+ config.action_controller.consider_all_requests_local = true
6
+ config.action_view.debug_rjs = true
7
+ config.action_controller.perform_caching = false
@@ -0,0 +1,6 @@
1
+ # -*- encoding: binary -*-
2
+
3
+ config.cache_classes = true
4
+ config.action_controller.consider_all_requests_local = false
5
+ config.action_controller.perform_caching = true
6
+ config.action_view.cache_template_loading = true
@@ -0,0 +1,6 @@
1
+ # -*- encoding: binary -*-
2
+
3
+ ActionController::Routing::Routes.draw do |map|
4
+ map.connect ':controller/:action/:id'
5
+ map.connect ':controller/:action/:id.:format'
6
+ end
File without changes
@@ -0,0 +1 @@
1
+ 404 Not Found
@@ -0,0 +1 @@
1
+ 500 Internal Server Error
@@ -0,0 +1 @@
1
+ HELLO
@@ -0,0 +1,280 @@
1
+ # -*- encoding: binary -*-
2
+
3
+ # Copyright (c) 2009 Eric Wong
4
+ require 'test/test_helper'
5
+
6
+ # don't call exit(0) since it may be run under rake (but gmake is recommended)
7
+ do_test = true
8
+
9
+ $unicorn_rails_bin = ENV['UNICORN_RAILS_TEST_BIN'] || "unicorn_rails"
10
+ redirect_test_io { do_test = system($unicorn_rails_bin, '-v') }
11
+
12
+ unless do_test
13
+ warn "#$unicorn_rails_bin not found in PATH=#{ENV['PATH']}, " \
14
+ "skipping this test"
15
+ end
16
+
17
+ unless which('git')
18
+ warn "git not found in PATH=#{ENV['PATH']}, skipping this test"
19
+ do_test = false
20
+ end
21
+
22
+ if RAILS_GIT_REPO = ENV['RAILS_GIT_REPO']
23
+ unless File.directory?(RAILS_GIT_REPO)
24
+ warn "#{RAILS_GIT_REPO} not found, create it with:\n" \
25
+ "\tgit clone --mirror git://github.com/rails/rails #{RAILS_GIT_REPO}" \
26
+ "skipping this test for now"
27
+ do_test = false
28
+ end
29
+ else
30
+ warn "RAILS_GIT_REPO not defined, don't know where to git clone from"
31
+ do_test = false
32
+ end
33
+
34
+ unless UNICORN_RAILS_TEST_VERSION = ENV['UNICORN_RAILS_TEST_VERSION']
35
+ warn 'UNICORN_RAILS_TEST_VERSION not defined in environment, ' \
36
+ 'skipping this test'
37
+ do_test = false
38
+ end
39
+
40
+ RAILS_ROOT = "#{File.dirname(__FILE__)}/app-#{UNICORN_RAILS_TEST_VERSION}"
41
+ unless File.directory?(RAILS_ROOT)
42
+ warn "unsupported UNICORN_RAILS_TEST_VERSION=#{UNICORN_RAILS_TEST_VERSION}"
43
+ do_test = false
44
+ end
45
+
46
+ ROR_V = UNICORN_RAILS_TEST_VERSION.split(/\./).map { |x| x.to_i }
47
+ RB_V = RUBY_VERSION.split(/\./).map { |x| x.to_i }
48
+ if RB_V[0] >= 1 && RB_V[1] >= 9
49
+ unless ROR_V[0] >= 2 && ROR_V[1] >= 3
50
+ warn "skipping Ruby >=1.9 test with Rails <2.3"
51
+ do_test = false
52
+ end
53
+ end
54
+
55
+ class RailsTest < Test::Unit::TestCase
56
+ trap(:QUIT, 'IGNORE')
57
+
58
+ COMMON_TMP = Tempfile.new('unicorn_tmp') unless defined?(COMMON_TMP)
59
+
60
+ HEAVY_CFG = <<-EOS
61
+ worker_processes 2
62
+ timeout 30
63
+ logger Logger.new('#{COMMON_TMP.path}')
64
+ EOS
65
+
66
+ def setup
67
+ @pwd = Dir.pwd
68
+ @tmpfile = Tempfile.new('unicorn_rails_test')
69
+ @tmpdir = @tmpfile.path
70
+ @tmpfile.close!
71
+ assert_nothing_raised do
72
+ FileUtils.cp_r(RAILS_ROOT, @tmpdir, :preserve => true)
73
+ end
74
+ Dir.chdir(@tmpdir)
75
+ system('git', 'clone', '-nsq', RAILS_GIT_REPO, 'vendor/rails')
76
+ Dir.chdir("#@tmpdir/vendor/rails") do
77
+ system('git', 'reset', '-q', '--hard', "v#{UNICORN_RAILS_TEST_VERSION}")
78
+ end
79
+
80
+ assert(system('rake', 'db:sessions:create'))
81
+ assert(system('rake', 'db:migrate'))
82
+
83
+ @addr = ENV['UNICORN_TEST_ADDR'] || '127.0.0.1'
84
+ @port = unused_port(@addr)
85
+ @start_pid = $$
86
+ @pid = nil
87
+ end
88
+
89
+ def test_launcher
90
+ tmp_dirs = %w(cache pids sessions sockets)
91
+ tmp_dirs.each { |dir| assert(! File.exist?("tmp/#{dir}")) }
92
+ redirect_test_io { @pid = fork { exec 'unicorn_rails', "-l#@addr:#@port" } }
93
+ wait_master_ready("test_stderr.#$$.log")
94
+
95
+ # temp dirs exist
96
+ tmp_dirs.each { |dir| assert(File.directory?("tmp/#{dir}")) }
97
+
98
+ # basic GET
99
+ res = Net::HTTP.get_response(URI.parse("http://#@addr:#@port/foo"))
100
+ assert_equal "FOO\n", res.body
101
+ assert_match %r{^text/html\b}, res['Content-Type']
102
+ assert_equal "4", res['Content-Length']
103
+ assert_equal "200 OK", res['Status']
104
+
105
+ # can we set cookies?
106
+ res = Net::HTTP.get_response(URI.parse("http://#@addr:#@port/foo/xcookie"))
107
+ assert_equal "200", res.code
108
+ assert_equal "200 OK", res['Status']
109
+ cookies = res.get_fields('Set-Cookie')
110
+ assert_equal 2, cookies.size
111
+ assert_equal 1, cookies.grep(/\A_unicorn_rails_test\./).size
112
+ assert_equal 1, cookies.grep(/\Afoo=cookie/).size
113
+
114
+ # how about just a session?
115
+ res = Net::HTTP.get_response(URI.parse("http://#@addr:#@port/foo/xnotice"))
116
+ assert_equal "200", res.code
117
+ assert_equal "200 OK", res['Status']
118
+ cookies = res.get_fields('Set-Cookie')
119
+ assert_equal 1, cookies.size
120
+ assert_equal 1, cookies.grep(/\A_unicorn_rails_test\./).size
121
+
122
+ # posting forms?
123
+ uri = URI.parse("http://#@addr:#@port/foo/xpost")
124
+ wait_master_ready("test_stderr.#$$.log")
125
+ res = Net::HTTP.post_form(uri, {"a" => "b", "c"=>"d"})
126
+ assert_equal "200", res.code
127
+ params = res.body.split(/\n/).grep(/^params:/)
128
+ assert_equal 1, params.size
129
+ params = eval(params[0].gsub!(/\Aparams:/, ''))
130
+ assert_equal Hash, params.class
131
+ assert_equal 'b', params['a']
132
+ assert_equal 'd', params['c']
133
+ assert_equal "200 OK", res['Status']
134
+
135
+ # try uploading a big file
136
+ tmp = Tempfile.new('random')
137
+ sha1 = Digest::SHA1.new
138
+ assert_nothing_raised do
139
+ File.open("/dev/urandom", "rb") do |fp|
140
+ 256.times do
141
+ buf = fp.sysread(4096)
142
+ sha1.update(buf)
143
+ tmp.syswrite(buf)
144
+ end
145
+ end
146
+ end
147
+
148
+ # fixed in Rack commit 44ed4640f077504a49b7f1cabf8d6ad7a13f6441,
149
+ # no released version of Rails or Rack has this fix
150
+ if RB_V[0] >= 1 && RB_V[1] >= 9
151
+ warn "multipart broken with Rack 1.0.0 and Rails 2.3.2.1 under 1.9"
152
+ else
153
+ resp = `curl -isSfN -Ffile=@#{tmp.path} http://#@addr:#@port/foo/xpost`
154
+ assert $?.success?
155
+ resp = resp.split(/\r?\n/)
156
+ grepped = resp.grep(/^sha1: (.{40})/)
157
+ assert_equal 1, grepped.size
158
+ assert_equal(sha1.hexdigest, /^sha1: (.{40})/.match(grepped.first)[1])
159
+
160
+ grepped = resp.grep(/^Content-Type:\s+(.+)/i)
161
+ assert_equal 1, grepped.size
162
+ assert_match %r{^text/plain}, grepped.first.split(/\s*:\s*/)[1]
163
+ assert_equal 1, resp.grep(/^Status:/i).size
164
+ end
165
+
166
+ # make sure we can get 403 responses, too
167
+ uri = URI.parse("http://#@addr:#@port/foo/xpost")
168
+ wait_master_ready("test_stderr.#$$.log")
169
+ res = Net::HTTP.get_response(uri)
170
+ assert_equal "403", res.code
171
+ assert_equal "403 Forbidden", res['Status']
172
+
173
+ # non existent controller
174
+ uri = URI.parse("http://#@addr:#@port/asdf")
175
+ res = Net::HTTP.get_response(uri)
176
+ assert_equal "404", res.code
177
+ assert_equal "404 Not Found", res['Status']
178
+
179
+ # static files
180
+
181
+ # ensure file we're about to serve is not there yet
182
+ res = Net::HTTP.get_response(URI.parse("http://#@addr:#@port/pid.txt"))
183
+ assert_equal "404 Not Found", res['Status']
184
+ assert_equal '404', res.code
185
+
186
+ # can we serve text files based on suffix?
187
+ File.open("public/pid.txt", "wb") { |fp| fp.syswrite("#$$\n") }
188
+ res = Net::HTTP.get_response(URI.parse("http://#@addr:#@port/pid.txt"))
189
+ assert_equal '200', res.code
190
+ assert_equal "200 OK", res['Status']
191
+ assert_match %r{^text/plain}, res['Content-Type']
192
+ assert_equal "#$$\n", res.body
193
+
194
+ # can we serve HTML files based on suffix?
195
+ assert File.exist?("public/500.html")
196
+ res = Net::HTTP.get_response(URI.parse("http://#@addr:#@port/500.html"))
197
+ assert_equal '200', res.code
198
+ assert_equal '200 OK', res['Status']
199
+ assert_match %r{^text/html}, res['Content-Type']
200
+ five_hundred_body = res.body
201
+
202
+ # lets try pretending 500 is a controller that got cached
203
+ assert ! File.exist?("public/500")
204
+ assert_equal five_hundred_body, File.read("public/500.html")
205
+ res = Net::HTTP.get_response(URI.parse("http://#@addr:#@port/500"))
206
+ assert_equal '200', res.code
207
+ assert_equal '200 OK', res['Status']
208
+ assert_match %r{^text/html}, res['Content-Type']
209
+ assert_equal five_hundred_body, res.body
210
+ end
211
+
212
+ def test_alt_url_root
213
+ # cbf to actually work on this since I never use this feature (ewong)
214
+ return unless ROR_V[0] >= 2 && ROR_V[1] >= 3
215
+ redirect_test_io do
216
+ @pid = fork { exec 'unicorn_rails', "-l#@addr:#@port", '-P/poo' }
217
+ end
218
+ wait_master_ready("test_stderr.#$$.log")
219
+ res = Net::HTTP.get_response(URI.parse("http://#@addr:#@port/poo/foo"))
220
+ # p res
221
+ # p res.body
222
+ # system 'cat', 'log/development.log'
223
+ assert_equal "200", res.code
224
+ assert_equal '200 OK', res['Status']
225
+ assert_equal "FOO\n", res.body
226
+ assert_match %r{^text/html\b}, res['Content-Type']
227
+ assert_equal "4", res['Content-Length']
228
+
229
+ res = Net::HTTP.get_response(URI.parse("http://#@addr:#@port/foo"))
230
+ assert_equal "404", res.code
231
+ assert_equal '404 Not Found', res['Status']
232
+ end
233
+
234
+ def test_alt_url_root_config_env
235
+ # cbf to actually work on this since I never use this feature (ewong)
236
+ return unless ROR_V[0] >= 2 && ROR_V[1] >= 3
237
+ tmp = Tempfile.new(nil)
238
+ tmp.syswrite("ENV['RAILS_RELATIVE_URL_ROOT'] = '/poo'\n")
239
+ redirect_test_io do
240
+ @pid = fork { exec 'unicorn_rails', "-l#@addr:#@port", "-c", tmp.path }
241
+ end
242
+ wait_master_ready("test_stderr.#$$.log")
243
+ res = Net::HTTP.get_response(URI.parse("http://#@addr:#@port/poo/foo"))
244
+ assert_equal "200", res.code
245
+ assert_equal '200 OK', res['Status']
246
+ assert_equal "FOO\n", res.body
247
+ assert_match %r{^text/html\b}, res['Content-Type']
248
+ assert_equal "4", res['Content-Length']
249
+
250
+ res = Net::HTTP.get_response(URI.parse("http://#@addr:#@port/foo"))
251
+ assert_equal "404", res.code
252
+ assert_equal '404 Not Found', res['Status']
253
+
254
+ res = Net::HTTP.get_response(URI.parse("http://#@addr:#@port/poo/x.txt"))
255
+ assert_equal "200", res.code
256
+ assert_equal "HELLO\n", res.body
257
+ end
258
+
259
+ def teardown
260
+ return if @start_pid != $$
261
+
262
+ if @pid
263
+ Process.kill(:QUIT, @pid)
264
+ pid2, status = Process.waitpid2(@pid)
265
+ assert status.success?
266
+ end
267
+
268
+ Dir.chdir(@pwd)
269
+ FileUtils.rmtree(@tmpdir)
270
+ loop do
271
+ Process.kill('-QUIT', 0)
272
+ begin
273
+ Process.waitpid(-1, Process::WNOHANG) or break
274
+ rescue Errno::ECHILD
275
+ break
276
+ end
277
+ end
278
+ end
279
+
280
+ end if do_test
@@ -0,0 +1,296 @@
1
+ # -*- encoding: binary -*-
2
+
3
+ # Copyright (c) 2005 Zed A. Shaw
4
+ # You can redistribute it and/or modify it under the same terms as Ruby.
5
+ #
6
+ # Additional work donated by contributors. See http://mongrel.rubyforge.org/attributions.html
7
+ # for more information.
8
+
9
+ STDIN.sync = STDOUT.sync = STDERR.sync = true # buffering makes debugging hard
10
+
11
+ # Some tests watch a log file or a pid file to spring up to check state
12
+ # Can't rely on inotify on non-Linux and logging to a pipe makes things
13
+ # more complicated
14
+ DEFAULT_TRIES = 1000
15
+ DEFAULT_RES = 0.2
16
+
17
+ HERE = File.dirname(__FILE__) unless defined?(HERE)
18
+ %w(lib ext).each do |dir|
19
+ $LOAD_PATH.unshift "#{HERE}/../#{dir}"
20
+ end
21
+
22
+ require 'test/unit'
23
+ require 'net/http'
24
+ require 'digest/sha1'
25
+ require 'uri'
26
+ require 'stringio'
27
+ require 'pathname'
28
+ require 'tempfile'
29
+ require 'fileutils'
30
+ require 'logger'
31
+ require 'unicorn'
32
+ require 'unicorn_http'
33
+
34
+ if ENV['DEBUG']
35
+ require 'ruby-debug'
36
+ Debugger.start
37
+ end
38
+
39
+ def redirect_test_io
40
+ orig_err = STDERR.dup
41
+ orig_out = STDOUT.dup
42
+ STDERR.reopen("test_stderr.#{$$}.log", "a")
43
+ STDOUT.reopen("test_stdout.#{$$}.log", "a")
44
+ STDERR.sync = STDOUT.sync = true
45
+
46
+ at_exit do
47
+ File.unlink("test_stderr.#{$$}.log") rescue nil
48
+ File.unlink("test_stdout.#{$$}.log") rescue nil
49
+ end
50
+
51
+ begin
52
+ yield
53
+ ensure
54
+ STDERR.reopen(orig_err)
55
+ STDOUT.reopen(orig_out)
56
+ end
57
+ end
58
+
59
+ # which(1) exit codes cannot be trusted on some systems
60
+ # We use UNIX shell utilities in some tests because we don't trust
61
+ # ourselves to write Ruby 100% correctly :)
62
+ def which(bin)
63
+ ex = ENV['PATH'].split(/:/).detect do |x|
64
+ x << "/#{bin}"
65
+ File.executable?(x)
66
+ end or warn "`#{bin}' not found in PATH=#{ENV['PATH']}"
67
+ ex
68
+ end
69
+
70
+ # Either takes a string to do a get request against, or a tuple of [URI, HTTP] where
71
+ # HTTP is some kind of Net::HTTP request object (POST, HEAD, etc.)
72
+ def hit(uris)
73
+ results = []
74
+ uris.each do |u|
75
+ res = nil
76
+
77
+ if u.kind_of? String
78
+ res = Net::HTTP.get(URI.parse(u))
79
+ else
80
+ url = URI.parse(u[0])
81
+ res = Net::HTTP.new(url.host, url.port).start {|h| h.request(u[1]) }
82
+ end
83
+
84
+ assert res != nil, "Didn't get a response: #{u}"
85
+ results << res
86
+ end
87
+
88
+ return results
89
+ end
90
+
91
+ # unused_port provides an unused port on +addr+ usable for TCP that is
92
+ # guaranteed to be unused across all unicorn builds on that system. It
93
+ # prevents race conditions by using a lock file other unicorn builds
94
+ # will see. This is required if you perform several builds in parallel
95
+ # with a continuous integration system or run tests in parallel via
96
+ # gmake. This is NOT guaranteed to be race-free if you run other
97
+ # processes that bind to random ports for testing (but the window
98
+ # for a race condition is very small). You may also set UNICORN_TEST_ADDR
99
+ # to override the default test address (127.0.0.1).
100
+ def unused_port(addr = '127.0.0.1')
101
+ retries = 100
102
+ base = 5000
103
+ port = sock = nil
104
+ begin
105
+ begin
106
+ port = base + rand(32768 - base)
107
+ while port == Unicorn::Const::DEFAULT_PORT
108
+ port = base + rand(32768 - base)
109
+ end
110
+
111
+ sock = Socket.new(Socket::AF_INET, Socket::SOCK_STREAM, 0)
112
+ sock.bind(Socket.pack_sockaddr_in(port, addr))
113
+ sock.listen(5)
114
+ rescue Errno::EADDRINUSE, Errno::EACCES
115
+ sock.close rescue nil
116
+ retry if (retries -= 1) >= 0
117
+ end
118
+
119
+ # since we'll end up closing the random port we just got, there's a race
120
+ # condition could allow the random port we just chose to reselect itself
121
+ # when running tests in parallel with gmake. Create a lock file while
122
+ # we have the port here to ensure that does not happen .
123
+ lock_path = "#{Dir::tmpdir}/unicorn_test.#{addr}:#{port}.lock"
124
+ lock = File.open(lock_path, File::WRONLY|File::CREAT|File::EXCL, 0600)
125
+ at_exit { File.unlink(lock_path) rescue nil }
126
+ rescue Errno::EEXIST
127
+ sock.close rescue nil
128
+ retry
129
+ end
130
+ sock.close rescue nil
131
+ port
132
+ end
133
+
134
+ def try_require(lib)
135
+ begin
136
+ require lib
137
+ true
138
+ rescue LoadError
139
+ false
140
+ end
141
+ end
142
+
143
+ # sometimes the server may not come up right away
144
+ def retry_hit(uris = [])
145
+ tries = DEFAULT_TRIES
146
+ begin
147
+ hit(uris)
148
+ rescue Errno::EINVAL, Errno::ECONNREFUSED => err
149
+ if (tries -= 1) > 0
150
+ sleep DEFAULT_RES
151
+ retry
152
+ end
153
+ raise err
154
+ end
155
+ end
156
+
157
+ def assert_shutdown(pid)
158
+ wait_master_ready("test_stderr.#{pid}.log")
159
+ assert_nothing_raised { Process.kill(:QUIT, pid) }
160
+ status = nil
161
+ assert_nothing_raised { pid, status = Process.waitpid2(pid) }
162
+ assert status.success?, "exited successfully"
163
+ end
164
+
165
+ def wait_workers_ready(path, nr_workers)
166
+ tries = DEFAULT_TRIES
167
+ lines = []
168
+ while (tries -= 1) > 0
169
+ begin
170
+ lines = File.readlines(path).grep(/worker=\d+ ready/)
171
+ lines.size == nr_workers and return
172
+ rescue Errno::ENOENT
173
+ end
174
+ sleep DEFAULT_RES
175
+ end
176
+ raise "#{nr_workers} workers never became ready:" \
177
+ "\n\t#{lines.join("\n\t")}\n"
178
+ end
179
+
180
+ def wait_master_ready(master_log)
181
+ tries = DEFAULT_TRIES
182
+ while (tries -= 1) > 0
183
+ begin
184
+ File.readlines(master_log).grep(/master process ready/)[0] and return
185
+ rescue Errno::ENOENT
186
+ end
187
+ sleep DEFAULT_RES
188
+ end
189
+ raise "master process never became ready"
190
+ end
191
+
192
+ def reexec_usr2_quit_test(pid, pid_file)
193
+ assert File.exist?(pid_file), "pid file OK"
194
+ assert ! File.exist?("#{pid_file}.oldbin"), "oldbin pid file"
195
+ assert_nothing_raised { Process.kill(:USR2, pid) }
196
+ assert_nothing_raised { retry_hit(["http://#{@addr}:#{@port}/"]) }
197
+ wait_for_file("#{pid_file}.oldbin")
198
+ wait_for_file(pid_file)
199
+
200
+ old_pid = File.read("#{pid_file}.oldbin").to_i
201
+ new_pid = File.read(pid_file).to_i
202
+
203
+ # kill old master process
204
+ assert_not_equal pid, new_pid
205
+ assert_equal pid, old_pid
206
+ assert_nothing_raised { Process.kill(:QUIT, old_pid) }
207
+ assert_nothing_raised { retry_hit(["http://#{@addr}:#{@port}/"]) }
208
+ wait_for_death(old_pid)
209
+ assert_equal new_pid, File.read(pid_file).to_i
210
+ assert_nothing_raised { retry_hit(["http://#{@addr}:#{@port}/"]) }
211
+ assert_nothing_raised { Process.kill(:QUIT, new_pid) }
212
+ end
213
+
214
+ def reexec_basic_test(pid, pid_file)
215
+ results = retry_hit(["http://#{@addr}:#{@port}/"])
216
+ assert_equal String, results[0].class
217
+ assert_nothing_raised { Process.kill(0, pid) }
218
+ master_log = "#{@tmpdir}/test_stderr.#{pid}.log"
219
+ wait_master_ready(master_log)
220
+ File.truncate(master_log, 0)
221
+ nr = 50
222
+ kill_point = 2
223
+ assert_nothing_raised do
224
+ nr.times do |i|
225
+ hit(["http://#{@addr}:#{@port}/#{i}"])
226
+ i == kill_point and Process.kill(:HUP, pid)
227
+ end
228
+ end
229
+ wait_master_ready(master_log)
230
+ assert File.exist?(pid_file), "pid=#{pid_file} exists"
231
+ new_pid = File.read(pid_file).to_i
232
+ assert_not_equal pid, new_pid
233
+ assert_nothing_raised { Process.kill(0, new_pid) }
234
+ assert_nothing_raised { Process.kill(:QUIT, new_pid) }
235
+ end
236
+
237
+ def wait_for_file(path)
238
+ tries = DEFAULT_TRIES
239
+ while (tries -= 1) > 0 && ! File.exist?(path)
240
+ sleep DEFAULT_RES
241
+ end
242
+ assert File.exist?(path), "path=#{path} exists #{caller.inspect}"
243
+ end
244
+
245
+ def xfork(&block)
246
+ fork do
247
+ ObjectSpace.each_object(Tempfile) do |tmp|
248
+ ObjectSpace.undefine_finalizer(tmp)
249
+ end
250
+ yield
251
+ end
252
+ end
253
+
254
+ # can't waitpid on detached processes
255
+ def wait_for_death(pid)
256
+ tries = DEFAULT_TRIES
257
+ while (tries -= 1) > 0
258
+ begin
259
+ Process.kill(0, pid)
260
+ begin
261
+ Process.waitpid(pid, Process::WNOHANG)
262
+ rescue Errno::ECHILD
263
+ end
264
+ sleep(DEFAULT_RES)
265
+ rescue Errno::ESRCH
266
+ return
267
+ end
268
+ end
269
+ raise "PID:#{pid} never died!"
270
+ end
271
+
272
+ # executes +cmd+ and chunks its STDOUT
273
+ def chunked_spawn(stdout, *cmd)
274
+ fork {
275
+ crd, cwr = IO.pipe
276
+ crd.binmode
277
+ cwr.binmode
278
+ crd.sync = cwr.sync = true
279
+
280
+ pid = fork {
281
+ STDOUT.reopen(cwr)
282
+ crd.close
283
+ cwr.close
284
+ exec(*cmd)
285
+ }
286
+ cwr.close
287
+ begin
288
+ buf = crd.readpartial(16384)
289
+ stdout.write("#{'%x' % buf.size}\r\n#{buf}")
290
+ rescue EOFError
291
+ stdout.write("0\r\n")
292
+ pid, status = Process.waitpid(pid)
293
+ exit status.exitstatus
294
+ end while true
295
+ }
296
+ end