unicorn 0.2.3 → 0.4.1

Sign up to get free protection for your applications and to get access to all the features.
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