unicorn 0.2.3 → 0.4.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 (95) hide show
  1. data/.document +1 -1
  2. data/.gitignore +1 -0
  3. data/CHANGELOG +1 -0
  4. data/DESIGN +4 -0
  5. data/GNUmakefile +30 -6
  6. data/Manifest +62 -3
  7. data/README +52 -42
  8. data/SIGNALS +17 -17
  9. data/TODO +27 -5
  10. data/bin/unicorn +15 -13
  11. data/bin/unicorn_rails +59 -22
  12. data/ext/unicorn/http11/http11.c +25 -104
  13. data/ext/unicorn/http11/http11_parser.c +24 -23
  14. data/ext/unicorn/http11/http11_parser.h +1 -3
  15. data/ext/unicorn/http11/http11_parser.rl +2 -1
  16. data/lib/unicorn.rb +58 -44
  17. data/lib/unicorn/app/old_rails.rb +23 -0
  18. data/lib/unicorn/app/old_rails/static.rb +58 -0
  19. data/lib/unicorn/cgi_wrapper.rb +151 -0
  20. data/lib/unicorn/configurator.rb +71 -31
  21. data/lib/unicorn/const.rb +9 -34
  22. data/lib/unicorn/http_request.rb +63 -66
  23. data/lib/unicorn/http_response.rb +6 -1
  24. data/lib/unicorn/socket.rb +15 -2
  25. data/test/benchmark/README +55 -0
  26. data/test/benchmark/big_request.rb +35 -0
  27. data/test/benchmark/dd.ru +18 -0
  28. data/test/benchmark/request.rb +47 -0
  29. data/test/benchmark/response.rb +29 -0
  30. data/test/exec/test_exec.rb +41 -157
  31. data/test/rails/app-1.2.3/.gitignore +2 -0
  32. data/test/rails/app-1.2.3/app/controllers/application.rb +4 -0
  33. data/test/rails/app-1.2.3/app/controllers/foo_controller.rb +34 -0
  34. data/test/rails/app-1.2.3/app/helpers/application_helper.rb +2 -0
  35. data/test/rails/app-1.2.3/config/boot.rb +9 -0
  36. data/test/rails/app-1.2.3/config/database.yml +12 -0
  37. data/test/rails/app-1.2.3/config/environment.rb +10 -0
  38. data/test/rails/app-1.2.3/config/environments/development.rb +7 -0
  39. data/test/rails/app-1.2.3/config/environments/production.rb +3 -0
  40. data/test/rails/app-1.2.3/config/routes.rb +4 -0
  41. data/test/rails/app-1.2.3/db/.gitignore +0 -0
  42. data/test/rails/app-1.2.3/public/404.html +1 -0
  43. data/test/rails/app-1.2.3/public/500.html +1 -0
  44. data/test/rails/app-2.0.2/.gitignore +2 -0
  45. data/test/rails/app-2.0.2/app/controllers/application.rb +2 -0
  46. data/test/rails/app-2.0.2/app/controllers/foo_controller.rb +34 -0
  47. data/test/rails/app-2.0.2/app/helpers/application_helper.rb +2 -0
  48. data/test/rails/app-2.0.2/config/boot.rb +9 -0
  49. data/test/rails/app-2.0.2/config/database.yml +12 -0
  50. data/test/rails/app-2.0.2/config/environment.rb +14 -0
  51. data/test/rails/app-2.0.2/config/environments/development.rb +6 -0
  52. data/test/rails/app-2.0.2/config/environments/production.rb +3 -0
  53. data/test/rails/app-2.0.2/config/routes.rb +4 -0
  54. data/test/rails/app-2.0.2/db/.gitignore +0 -0
  55. data/test/rails/app-2.0.2/public/404.html +1 -0
  56. data/test/rails/app-2.0.2/public/500.html +1 -0
  57. data/test/rails/app-2.2.2/.gitignore +2 -0
  58. data/test/rails/app-2.2.2/app/controllers/application.rb +2 -0
  59. data/test/rails/app-2.2.2/app/controllers/foo_controller.rb +34 -0
  60. data/test/rails/app-2.2.2/app/helpers/application_helper.rb +2 -0
  61. data/test/rails/app-2.2.2/config/boot.rb +109 -0
  62. data/test/rails/app-2.2.2/config/database.yml +12 -0
  63. data/test/rails/app-2.2.2/config/environment.rb +14 -0
  64. data/test/rails/app-2.2.2/config/environments/development.rb +5 -0
  65. data/test/rails/app-2.2.2/config/environments/production.rb +3 -0
  66. data/test/rails/app-2.2.2/config/routes.rb +4 -0
  67. data/test/rails/app-2.2.2/db/.gitignore +0 -0
  68. data/test/rails/app-2.2.2/public/404.html +1 -0
  69. data/test/rails/app-2.2.2/public/500.html +1 -0
  70. data/test/rails/app-2.3.2.1/.gitignore +2 -0
  71. data/test/rails/app-2.3.2.1/app/controllers/application_controller.rb +3 -0
  72. data/test/rails/app-2.3.2.1/app/controllers/foo_controller.rb +34 -0
  73. data/test/rails/app-2.3.2.1/app/helpers/application_helper.rb +2 -0
  74. data/test/rails/app-2.3.2.1/config/boot.rb +107 -0
  75. data/test/rails/app-2.3.2.1/config/database.yml +12 -0
  76. data/test/rails/app-2.3.2.1/config/environment.rb +14 -0
  77. data/test/rails/app-2.3.2.1/config/environments/development.rb +5 -0
  78. data/test/rails/app-2.3.2.1/config/environments/production.rb +4 -0
  79. data/test/rails/app-2.3.2.1/config/routes.rb +4 -0
  80. data/test/rails/app-2.3.2.1/db/.gitignore +0 -0
  81. data/test/rails/app-2.3.2.1/public/404.html +1 -0
  82. data/test/rails/app-2.3.2.1/public/500.html +1 -0
  83. data/test/rails/test_rails.rb +243 -0
  84. data/test/test_helper.rb +149 -2
  85. data/test/unit/test_configurator.rb +46 -0
  86. data/test/unit/test_http_parser.rb +77 -36
  87. data/test/unit/test_request.rb +2 -0
  88. data/test/unit/test_response.rb +20 -4
  89. data/test/unit/test_server.rb +30 -1
  90. data/test/unit/test_socket_helper.rb +159 -0
  91. data/unicorn.gemspec +5 -5
  92. metadata +68 -5
  93. data/test/benchmark/previous.rb +0 -11
  94. data/test/benchmark/simple.rb +0 -11
  95. data/test/benchmark/utils.rb +0 -82
@@ -0,0 +1,34 @@
1
+ require 'digest/sha1'
2
+ class FooController < ApplicationController
3
+ def index
4
+ render :text => "FOO\n"
5
+ end
6
+
7
+ def xcookie
8
+ cookies["foo"] = "cookie-#$$-#{session[:gotta_use_the_session_in_2_3]}"
9
+ render :text => ""
10
+ end
11
+
12
+ def xnotice
13
+ flash[:notice] = "session #$$"
14
+ render :text => ""
15
+ end
16
+
17
+ def xpost
18
+ if request.post?
19
+ digest = Digest::SHA1.new
20
+ out = "params: #{params.inspect}\n"
21
+ if file = params[:file]
22
+ loop do
23
+ buf = file.read(4096) or break
24
+ digest.update(buf)
25
+ end
26
+ out << "sha1: #{digest.to_s}\n"
27
+ end
28
+ headers['content-type'] = 'text/plain'
29
+ render :text => out
30
+ else
31
+ render :status => 403, :text => "need post\n"
32
+ end
33
+ end
34
+ end
@@ -0,0 +1,2 @@
1
+ module ApplicationHelper
2
+ end
@@ -0,0 +1,107 @@
1
+ RAILS_ROOT = "#{File.dirname(__FILE__)}/.." unless defined?(RAILS_ROOT)
2
+
3
+ module Rails
4
+ class << self
5
+ def boot!
6
+ unless booted?
7
+ preinitialize
8
+ pick_boot.run
9
+ end
10
+ end
11
+
12
+ def booted?
13
+ defined? Rails::Initializer
14
+ end
15
+
16
+ def pick_boot
17
+ (vendor_rails? ? VendorBoot : GemBoot).new
18
+ end
19
+
20
+ def vendor_rails?
21
+ File.exist?("#{RAILS_ROOT}/vendor/rails")
22
+ end
23
+
24
+ def preinitialize
25
+ load(preinitializer_path) if File.exist?(preinitializer_path)
26
+ end
27
+
28
+ def preinitializer_path
29
+ "#{RAILS_ROOT}/config/preinitializer.rb"
30
+ end
31
+ end
32
+
33
+ class Boot
34
+ def run
35
+ load_initializer
36
+ Rails::Initializer.run(:set_load_path)
37
+ end
38
+ end
39
+
40
+ class VendorBoot < Boot
41
+ def load_initializer
42
+ require "#{RAILS_ROOT}/vendor/rails/railties/lib/initializer"
43
+ Rails::Initializer.run(:install_gem_spec_stubs)
44
+ Rails::GemDependency.add_frozen_gem_path
45
+ end
46
+ end
47
+
48
+ class GemBoot < Boot
49
+ def load_initializer
50
+ self.class.load_rubygems
51
+ load_rails_gem
52
+ require 'initializer'
53
+ end
54
+
55
+ def load_rails_gem
56
+ if version = self.class.gem_version
57
+ gem 'rails', version
58
+ else
59
+ gem 'rails'
60
+ end
61
+ rescue Gem::LoadError => load_error
62
+ $stderr.puts %(Missing the Rails #{version} gem. Please `gem install -v=#{version} rails`, update your RAILS_GEM_VERSION setting in config/environment.rb for the Rails version you do have installed, or comment out RAILS_GEM_VERSION to use the latest version installed.)
63
+ exit 1
64
+ end
65
+
66
+ class << self
67
+ def rubygems_version
68
+ Gem::RubyGemsVersion rescue nil
69
+ end
70
+
71
+ def gem_version
72
+ if defined? RAILS_GEM_VERSION
73
+ RAILS_GEM_VERSION
74
+ elsif ENV.include?('RAILS_GEM_VERSION')
75
+ ENV['RAILS_GEM_VERSION']
76
+ else
77
+ parse_gem_version(read_environment_rb)
78
+ end
79
+ end
80
+
81
+ def load_rubygems
82
+ require 'rubygems'
83
+ min_version = '1.3.1'
84
+ unless rubygems_version >= min_version
85
+ $stderr.puts %Q(Rails requires RubyGems >= #{min_version} (you have #{rubygems_version}). Please `gem update --system` and try again.)
86
+ exit 1
87
+ end
88
+
89
+ rescue LoadError
90
+ $stderr.puts %Q(Rails requires RubyGems >= #{min_version}. Please install RubyGems and try again: http://rubygems.rubyforge.org)
91
+ exit 1
92
+ end
93
+
94
+ def parse_gem_version(text)
95
+ $1 if text =~ /^[^#]*RAILS_GEM_VERSION\s*=\s*["']([!~<>=]*\s*[\d.]+)["']/
96
+ end
97
+
98
+ private
99
+ def read_environment_rb
100
+ File.read("#{RAILS_ROOT}/config/environment.rb")
101
+ end
102
+ end
103
+ end
104
+ end
105
+
106
+ # All that for this:
107
+ Rails.boot!
@@ -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,14 @@
1
+ unless defined? RAILS_GEM_VERSION
2
+ RAILS_GEM_VERSION = ENV['UNICORN_RAILS_VERSION']
3
+ end
4
+
5
+ # Bootstrap the Rails environment, frameworks, and default configuration
6
+ require File.join(File.dirname(__FILE__), 'boot')
7
+
8
+ Rails::Initializer.run do |config|
9
+ config.frameworks -= [ :active_record, :active_resource, :action_mailer ]
10
+ config.action_controller.session = {
11
+ :session_key => "_unicorn_rails_test.#{rand}",
12
+ :secret => "#{rand}#{rand}#{rand}#{rand}",
13
+ }
14
+ end
@@ -0,0 +1,5 @@
1
+ config.cache_classes = false
2
+ config.whiny_nils = true
3
+ config.action_controller.consider_all_requests_local = true
4
+ config.action_view.debug_rjs = true
5
+ config.action_controller.perform_caching = false
@@ -0,0 +1,4 @@
1
+ config.cache_classes = true
2
+ config.action_controller.consider_all_requests_local = false
3
+ config.action_controller.perform_caching = true
4
+ config.action_view.cache_template_loading = true
@@ -0,0 +1,4 @@
1
+ ActionController::Routing::Routes.draw do |map|
2
+ map.connect ':controller/:action/:id'
3
+ map.connect ':controller/:action/:id.:format'
4
+ end
File without changes
@@ -0,0 +1 @@
1
+ 404 Not Found
@@ -0,0 +1 @@
1
+ 500 Internal Server Error
@@ -0,0 +1,243 @@
1
+ # Copyright (c) 2009 Eric Wong
2
+ require 'test/test_helper'
3
+
4
+ # don't call exit(0) since it may be run under rake (but gmake is recommended)
5
+ do_test = true
6
+
7
+ $unicorn_rails_bin = ENV['UNICORN_RAILS_TEST_BIN'] || "unicorn_rails"
8
+ redirect_test_io { do_test = system($unicorn_rails_bin, '-v') }
9
+
10
+ unless do_test
11
+ warn "#$unicorn_rails_bin not found in PATH=#{ENV['PATH']}, " \
12
+ "skipping this test"
13
+ end
14
+
15
+ unless which('git')
16
+ warn "git not found in PATH=#{ENV['PATH']}, skipping this test"
17
+ do_test = false
18
+ end
19
+
20
+ if RAILS_GIT_REPO = ENV['RAILS_GIT_REPO']
21
+ unless File.directory?(RAILS_GIT_REPO)
22
+ warn "#{RAILS_GIT_REPO} not found, create it with:\n" \
23
+ "\tgit clone --mirror git://github.com/rails/rails #{RAILS_GIT_REPO}" \
24
+ "skipping this test for now"
25
+ do_test = false
26
+ end
27
+ else
28
+ warn "RAILS_GIT_REPO not defined, don't know where to git clone from"
29
+ do_test = false
30
+ end
31
+
32
+ unless UNICORN_RAILS_TEST_VERSION = ENV['UNICORN_RAILS_TEST_VERSION']
33
+ warn 'UNICORN_RAILS_TEST_VERSION not defined in environment, ' \
34
+ 'skipping this test'
35
+ do_test = false
36
+ end
37
+
38
+ RAILS_ROOT = "#{File.dirname(__FILE__)}/app-#{UNICORN_RAILS_TEST_VERSION}"
39
+ unless File.directory?(RAILS_ROOT)
40
+ warn "unsupported UNICORN_RAILS_TEST_VERSION=#{UNICORN_RAILS_TEST_VERSION}"
41
+ do_test = false
42
+ end
43
+
44
+ ROR_V = UNICORN_RAILS_TEST_VERSION.split(/\./).map { |x| x.to_i }
45
+ RB_V = RUBY_VERSION.split(/\./).map { |x| x.to_i }
46
+ if RB_V[0] >= 1 && RB_V[1] >= 9
47
+ unless ROR_V[0] >= 2 && ROR_V[1] >= 3
48
+ warn "skipping Ruby >=1.9 test with Rails <2.3"
49
+ do_test = false
50
+ end
51
+ end
52
+
53
+ class RailsTest < Test::Unit::TestCase
54
+ trap(:QUIT, 'IGNORE')
55
+
56
+ COMMON_TMP = Tempfile.new('unicorn_tmp') unless defined?(COMMON_TMP)
57
+
58
+ HEAVY_CFG = <<-EOS
59
+ worker_processes 2
60
+ timeout 30
61
+ logger Logger.new('#{COMMON_TMP.path}')
62
+ EOS
63
+
64
+ def setup
65
+ @pwd = Dir.pwd
66
+ @tmpfile = Tempfile.new('unicorn_rails_test')
67
+ @tmpdir = @tmpfile.path
68
+ @tmpfile.close!
69
+ assert_nothing_raised do
70
+ FileUtils.cp_r(RAILS_ROOT, @tmpdir, :preserve => true)
71
+ end
72
+ Dir.chdir(@tmpdir)
73
+ system('git', 'clone', '-nsq', RAILS_GIT_REPO, 'vendor/rails')
74
+ Dir.chdir("#@tmpdir/vendor/rails") do
75
+ system('git', 'reset', '-q', '--hard', "v#{UNICORN_RAILS_TEST_VERSION}")
76
+ end
77
+ @addr = ENV['UNICORN_TEST_ADDR'] || '127.0.0.1'
78
+ @port = unused_port(@addr)
79
+ @start_pid = $$
80
+ @pid = nil
81
+ end
82
+
83
+ def test_launcher
84
+ tmp_dirs = %w(cache pids sessions sockets)
85
+ tmp_dirs.each { |dir| assert(! File.exist?("tmp/#{dir}")) }
86
+ redirect_test_io { @pid = fork { exec 'unicorn_rails', "-l#@addr:#@port" } }
87
+ wait_master_ready("test_stderr.#$$.log")
88
+
89
+ # temp dirs exist
90
+ tmp_dirs.each { |dir| assert(File.directory?("tmp/#{dir}")) }
91
+
92
+ # basic GET
93
+ res = Net::HTTP.get_response(URI.parse("http://#@addr:#@port/foo"))
94
+ assert_equal "FOO\n", res.body
95
+ assert_match %r{^text/html\b}, res['Content-Type']
96
+ assert_equal "4", res['Content-Length']
97
+ assert_nil res['Status']
98
+
99
+ # can we set cookies?
100
+ res = Net::HTTP.get_response(URI.parse("http://#@addr:#@port/foo/xcookie"))
101
+ assert_equal "200", res.code
102
+ assert_nil res['Status']
103
+ cookies = res.get_fields('Set-Cookie')
104
+ assert_equal 2, cookies.size
105
+ assert_equal 1, cookies.grep(/\A_unicorn_rails_test\./).size
106
+ assert_equal 1, cookies.grep(/\Afoo=cookie/).size
107
+
108
+ # how about just a session?
109
+ res = Net::HTTP.get_response(URI.parse("http://#@addr:#@port/foo/xnotice"))
110
+ assert_equal "200", res.code
111
+ assert_nil res['Status']
112
+ cookies = res.get_fields('Set-Cookie')
113
+ assert_equal 1, cookies.size
114
+ assert_equal 1, cookies.grep(/\A_unicorn_rails_test\./).size
115
+
116
+ # posting forms?
117
+ uri = URI.parse("http://#@addr:#@port/foo/xpost")
118
+ wait_master_ready("test_stderr.#$$.log")
119
+ res = Net::HTTP.post_form(uri, {"a" => "b", "c"=>"d"})
120
+ assert_equal "200", res.code
121
+ params = res.body.split(/\n/).grep(/^params:/)
122
+ assert_equal 1, params.size
123
+ params = eval(params[0].gsub!(/\Aparams:/, ''))
124
+ assert_equal Hash, params.class
125
+ assert_equal 'b', params['a']
126
+ assert_equal 'd', params['c']
127
+ assert_nil res['Status']
128
+
129
+ # try uploading a big file
130
+ tmp = Tempfile.new('random')
131
+ sha1 = Digest::SHA1.new
132
+ assert_nothing_raised do
133
+ File.open("/dev/urandom", "rb") do |fp|
134
+ 256.times do
135
+ buf = fp.sysread(4096)
136
+ sha1.update(buf)
137
+ tmp.syswrite(buf)
138
+ end
139
+ end
140
+ end
141
+ resp = `curl -isSfN -Ffile=@#{tmp.path} http://#@addr:#@port/foo/xpost`
142
+ assert $?.success?
143
+ resp = resp.split(/\r?\n/)
144
+ grepped = resp.grep(/^sha1: (.{40})/)
145
+ assert_equal 1, grepped.size
146
+ assert_equal(sha1.hexdigest, /^sha1: (.{40})/.match(grepped.first)[1])
147
+
148
+ grepped = resp.grep(/^Content-Type:\s+(.+)/i)
149
+ assert_equal 1, grepped.size
150
+ assert_match %r{^text/plain}, grepped.first.split(/\s*:\s*/)[1]
151
+
152
+ assert_equal 0, resp.grep(/^Status:/i).size # Rack hates "Status: " lines
153
+
154
+ # make sure we can get 403 responses, too
155
+ uri = URI.parse("http://#@addr:#@port/foo/xpost")
156
+ wait_master_ready("test_stderr.#$$.log")
157
+ res = Net::HTTP.get_response(uri)
158
+ assert_equal "403", res.code
159
+ assert_nil res['Status']
160
+
161
+ # non existent controller
162
+ uri = URI.parse("http://#@addr:#@port/asdf")
163
+ res = Net::HTTP.get_response(uri)
164
+ assert_equal "404", res.code
165
+ assert_nil res['Status']
166
+
167
+ # static files
168
+
169
+ # ensure file we're about to serve is not there yet
170
+ res = Net::HTTP.get_response(URI.parse("http://#@addr:#@port/pid.txt"))
171
+ assert_nil res['Status']
172
+ assert_equal '404', res.code
173
+
174
+ # can we serve text files based on suffix?
175
+ File.open("public/pid.txt", "wb") { |fp| fp.syswrite("#$$\n") }
176
+ res = Net::HTTP.get_response(URI.parse("http://#@addr:#@port/pid.txt"))
177
+ assert_equal '200', res.code
178
+ assert_match %r{^text/plain}, res['Content-Type']
179
+ assert_equal "#$$\n", res.body
180
+ assert_nil res['Status']
181
+
182
+ # can we serve HTML files based on suffix?
183
+ assert File.exist?("public/500.html")
184
+ res = Net::HTTP.get_response(URI.parse("http://#@addr:#@port/500.html"))
185
+ assert_equal '200', res.code
186
+ assert_match %r{^text/html}, res['Content-Type']
187
+ five_hundred_body = res.body
188
+ assert_nil res['Status']
189
+
190
+ # lets try pretending 500 is a controller that got cached
191
+ assert ! File.exist?("public/500")
192
+ assert_equal five_hundred_body, File.read("public/500.html")
193
+ res = Net::HTTP.get_response(URI.parse("http://#@addr:#@port/500"))
194
+ assert_equal '200', res.code
195
+ assert_match %r{^text/html}, res['Content-Type']
196
+ assert_equal five_hundred_body, res.body
197
+ assert_nil res['Status']
198
+ end
199
+
200
+ def test_alt_url_root
201
+ # cbf to actually work on this since I never use this feature (ewong)
202
+ return unless ROR_V[0] >= 2 && ROR_V[1] >= 3
203
+ redirect_test_io do
204
+ @pid = fork { exec 'unicorn_rails', "-l#@addr:#@port", '-P/poo' }
205
+ end
206
+ wait_master_ready("test_stderr.#$$.log")
207
+ res = Net::HTTP.get_response(URI.parse("http://#@addr:#@port/poo/foo"))
208
+ # p res
209
+ # p res.body
210
+ # system 'cat', 'log/development.log'
211
+ assert_equal "200", res.code
212
+ assert_equal "FOO\n", res.body
213
+ assert_match %r{^text/html\b}, res['Content-Type']
214
+ assert_equal "4", res['Content-Length']
215
+ assert_nil res['Status']
216
+
217
+ res = Net::HTTP.get_response(URI.parse("http://#@addr:#@port/foo"))
218
+ assert_equal "404", res.code
219
+ assert_nil res['Status']
220
+ end
221
+
222
+ def teardown
223
+ return if @start_pid != $$
224
+
225
+ if @pid
226
+ Process.kill(:QUIT, @pid)
227
+ pid2, status = Process.waitpid2(@pid)
228
+ assert status.success?
229
+ end
230
+
231
+ Dir.chdir(@pwd)
232
+ FileUtils.rmtree(@tmpdir)
233
+ loop do
234
+ Process.kill('-QUIT', 0)
235
+ begin
236
+ Process.waitpid(-1, Process::WNOHANG) or break
237
+ rescue Errno::ECHILD
238
+ break
239
+ end
240
+ end
241
+ end
242
+
243
+ end if do_test
@@ -4,9 +4,16 @@
4
4
  # Additional work donated by contributors. See http://mongrel.rubyforge.org/attributions.html
5
5
  # for more information.
6
6
 
7
+ STDIN.sync = STDOUT.sync = STDERR.sync = true # buffering makes debugging hard
8
+
9
+ # Some tests watch a log file or a pid file to spring up to check state
10
+ # Can't rely on inotify on non-Linux and logging to a pipe makes things
11
+ # more complicated
12
+ DEFAULT_TRIES = 1000
13
+ DEFAULT_RES = 0.2
7
14
 
8
15
  HERE = File.dirname(__FILE__) unless defined?(HERE)
9
- %w(lib ext bin test).each do |dir|
16
+ %w(lib ext).each do |dir|
10
17
  $LOAD_PATH.unshift "#{HERE}/../#{dir}"
11
18
  end
12
19
 
@@ -15,8 +22,10 @@ require 'net/http'
15
22
  require 'digest/sha1'
16
23
  require 'uri'
17
24
  require 'stringio'
25
+ require 'pathname'
26
+ require 'tempfile'
27
+ require 'fileutils'
18
28
  require 'unicorn'
19
- require 'tmpdir'
20
29
 
21
30
  if ENV['DEBUG']
22
31
  require 'ruby-debug'
@@ -112,3 +121,141 @@ def unused_port(addr = '127.0.0.1')
112
121
  sock.close rescue nil
113
122
  port
114
123
  end
124
+
125
+ def try_require(lib)
126
+ begin
127
+ require lib
128
+ true
129
+ rescue LoadError
130
+ false
131
+ end
132
+ end
133
+
134
+ # sometimes the server may not come up right away
135
+ def retry_hit(uris = [])
136
+ tries = DEFAULT_TRIES
137
+ begin
138
+ hit(uris)
139
+ rescue Errno::ECONNREFUSED => err
140
+ if (tries -= 1) > 0
141
+ sleep DEFAULT_RES
142
+ retry
143
+ end
144
+ raise err
145
+ end
146
+ end
147
+
148
+ def assert_shutdown(pid)
149
+ wait_master_ready("test_stderr.#{pid}.log")
150
+ assert_nothing_raised { Process.kill(:QUIT, pid) }
151
+ status = nil
152
+ assert_nothing_raised { pid, status = Process.waitpid2(pid) }
153
+ assert status.success?, "exited successfully"
154
+ end
155
+
156
+ def wait_workers_ready(path, nr_workers)
157
+ tries = DEFAULT_TRIES
158
+ lines = []
159
+ while (tries -= 1) > 0
160
+ begin
161
+ lines = File.readlines(path).grep(/worker=\d+ ready/)
162
+ lines.size == nr_workers and return
163
+ rescue Errno::ENOENT
164
+ end
165
+ sleep DEFAULT_RES
166
+ end
167
+ raise "#{nr_workers} workers never became ready:" \
168
+ "\n\t#{lines.join("\n\t")}\n"
169
+ end
170
+
171
+ def wait_master_ready(master_log)
172
+ tries = DEFAULT_TRIES
173
+ while (tries -= 1) > 0
174
+ begin
175
+ File.readlines(master_log).grep(/master process ready/)[0] and return
176
+ rescue Errno::ENOENT
177
+ end
178
+ sleep DEFAULT_RES
179
+ end
180
+ raise "master process never became ready"
181
+ end
182
+
183
+ def reexec_usr2_quit_test(pid, pid_file)
184
+ assert File.exist?(pid_file), "pid file OK"
185
+ assert ! File.exist?("#{pid_file}.oldbin"), "oldbin pid file"
186
+ assert_nothing_raised { Process.kill(:USR2, pid) }
187
+ assert_nothing_raised { retry_hit(["http://#{@addr}:#{@port}/"]) }
188
+ wait_for_file("#{pid_file}.oldbin")
189
+ wait_for_file(pid_file)
190
+
191
+ old_pid = File.read("#{pid_file}.oldbin").to_i
192
+ new_pid = File.read(pid_file).to_i
193
+
194
+ # kill old master process
195
+ assert_not_equal pid, new_pid
196
+ assert_equal pid, old_pid
197
+ assert_nothing_raised { Process.kill(:QUIT, old_pid) }
198
+ assert_nothing_raised { retry_hit(["http://#{@addr}:#{@port}/"]) }
199
+ wait_for_death(old_pid)
200
+ assert_equal new_pid, File.read(pid_file).to_i
201
+ assert_nothing_raised { retry_hit(["http://#{@addr}:#{@port}/"]) }
202
+ assert_nothing_raised { Process.kill(:QUIT, new_pid) }
203
+ end
204
+
205
+ def reexec_basic_test(pid, pid_file)
206
+ results = retry_hit(["http://#{@addr}:#{@port}/"])
207
+ assert_equal String, results[0].class
208
+ assert_nothing_raised { Process.kill(0, pid) }
209
+ master_log = "#{@tmpdir}/test_stderr.#{pid}.log"
210
+ wait_master_ready(master_log)
211
+ File.truncate(master_log, 0)
212
+ nr = 50
213
+ kill_point = 2
214
+ assert_nothing_raised do
215
+ nr.times do |i|
216
+ hit(["http://#{@addr}:#{@port}/#{i}"])
217
+ i == kill_point and Process.kill(:HUP, pid)
218
+ end
219
+ end
220
+ wait_master_ready(master_log)
221
+ assert File.exist?(pid_file), "pid=#{pid_file} exists"
222
+ new_pid = File.read(pid_file).to_i
223
+ assert_not_equal pid, new_pid
224
+ assert_nothing_raised { Process.kill(0, new_pid) }
225
+ assert_nothing_raised { Process.kill(:QUIT, new_pid) }
226
+ end
227
+
228
+ def wait_for_file(path)
229
+ tries = DEFAULT_TRIES
230
+ while (tries -= 1) > 0 && ! File.exist?(path)
231
+ sleep DEFAULT_RES
232
+ end
233
+ assert File.exist?(path), "path=#{path} exists #{caller.inspect}"
234
+ end
235
+
236
+ def xfork(&block)
237
+ fork do
238
+ ObjectSpace.each_object(Tempfile) do |tmp|
239
+ ObjectSpace.undefine_finalizer(tmp)
240
+ end
241
+ yield
242
+ end
243
+ end
244
+
245
+ # can't waitpid on detached processes
246
+ def wait_for_death(pid)
247
+ tries = DEFAULT_TRIES
248
+ while (tries -= 1) > 0
249
+ begin
250
+ Process.kill(0, pid)
251
+ begin
252
+ Process.waitpid(pid, Process::WNOHANG)
253
+ rescue Errno::ECHILD
254
+ end
255
+ sleep(DEFAULT_RES)
256
+ rescue Errno::ESRCH
257
+ return
258
+ end
259
+ end
260
+ raise "PID:#{pid} never died!"
261
+ end