aboutsummaryrefslogblamecommitdiffstats
path: root/railties/test/fcgi_dispatcher_test.rb
blob: cc054c24aa76ebf452e3d7fded92aacfc8692c5b (plain) (tree)
1
2
3
4
5
6
7
8
9
10
11
                       
 
     
                      
 
                                                                   




                                                 

     
                          








                                                   
                               
 

                     
 
                       










                                                 

                     
 
                                             












                                                                              

                     
 
                                          
                                                                          
                                    
 
                                        
                                         
     
 





                                                                                            
 






                                                             

 






                                                             
 





                                                             
 

                                                              

                                                      
     
 






                                                      
                                   



                                                
                     
 
                                  
     



                                                        

















                                                          



                                         
 
                                                  

                                            
                                                     





                                             
                                             
     
 
                                              

                                            
                                                       





                                             
                                             


                                                   

                                            
                                                      







                                                      


                                               

                                            
                                                        





                                                      
                                           
     
 


                                            
                                                      










                                                      
                               

                                            
                                                        




                                             

                                  
 






























                                                                                 

     
 
 


                                                           


              











                                                                

                                               
 


                                                     
 


                                    
                     
 
                                  

     
 


                                  
require 'abstract_unit'

begin
require 'fcgi_handler'

module ActionController; module Routing; module Routes; end end end

class RailsFCGIHandlerTest < Test::Unit::TestCase
  def setup
    @log = StringIO.new
    @handler = RailsFCGIHandler.new(@log)
  end

  def test_process_restart
    cgi = mock
    FCGI.stubs(:each_cgi).yields(cgi)

    @handler.expects(:process_request).once
    @handler.expects(:dispatcher_error).never

    @handler.expects(:when_ready).returns(:restart)
    @handler.expects(:close_connection).with(cgi)
    @handler.expects(:reload!).never
    @handler.expects(:restart!)

    @handler.process!
  end

  def test_process_exit
    cgi = mock
    FCGI.stubs(:each_cgi).yields(cgi)

    @handler.expects(:process_request).once
    @handler.expects(:dispatcher_error).never

    @handler.expects(:when_ready).returns(:exit)
    @handler.expects(:close_connection).with(cgi)
    @handler.expects(:reload!).never
    @handler.expects(:restart!).never

    @handler.process!
  end

  def test_process_with_system_exit_exception
    cgi = mock
    FCGI.stubs(:each_cgi).yields(cgi)

    @handler.expects(:process_request).once.raises(SystemExit)
    @handler.stubs(:dispatcher_log)
    @handler.expects(:dispatcher_log).with(:info, regexp_matches(/^stopping/))
    @handler.expects(:dispatcher_error).never

    @handler.expects(:when_ready).never
    @handler.expects(:close_connection).never
    @handler.expects(:reload!).never
    @handler.expects(:restart!).never

    @handler.process!
  end

  def test_restart_handler_outside_request
    @handler.expects(:dispatcher_log).with(:info, "asked to restart ASAP")
    @handler.expects(:restart!).once

    @handler.send(:restart_handler, nil)
    assert_equal nil, @handler.when_ready
  end

  def test_install_signal_handler_should_log_on_bad_signal
    @handler.stubs(:trap).raises(ArgumentError)

    @handler.expects(:dispatcher_log).with(:warn, "Ignoring unsupported signal CHEESECAKE.")
    @handler.send(:install_signal_handler, "CHEESECAKE", nil)
  end

  def test_reload
    @handler.expects(:restore!)
    @handler.expects(:dispatcher_log).with(:info, "reloaded")

    @handler.send(:reload!)
    assert_nil @handler.when_ready
  end


  def test_reload_runs_gc_when_gc_request_period_set
    @handler.expects(:run_gc!)
    @handler.expects(:restore!)
    @handler.expects(:dispatcher_log).with(:info, "reloaded")
    @handler.gc_request_period = 10
    @handler.send(:reload!)
  end

  def test_reload_doesnt_run_gc_if_gc_request_period_isnt_set
    @handler.expects(:run_gc!).never
    @handler.expects(:restore!)
    @handler.expects(:dispatcher_log).with(:info, "reloaded")
    @handler.send(:reload!)
  end

  def test_restart!
    @handler.expects(:dispatcher_log).with(:info, "restarted")
    @handler.expects(:exec).returns('restarted')
    assert_equal 'restarted', @handler.send(:restart!)
  end

  def test_restore!
    $".expects(:replace)
    Dispatcher.expects(:reset_application!)
    ActionController::Routing::Routes.expects(:reload)
    @handler.send(:restore!)
  end

  def test_uninterrupted_processing
    cgi = mock
    FCGI.expects(:each_cgi).yields(cgi)
    @handler.expects(:process_request).with(cgi)

    @handler.process!

    assert_nil @handler.when_ready
  end
end


class RailsFCGIHandlerSignalsTest < Test::Unit::TestCase
  class ::RailsFCGIHandler
    attr_accessor :signal
    alias_method :old_gc_countdown, :gc_countdown
    def gc_countdown
      signal ? Process.kill(signal, $$) : old_gc_countdown
    end
  end

  class ::Dispatcher
    class << self
      attr_accessor :signal
      alias_method :old_dispatch, :dispatch
      def dispatch(cgi)
        signal ? Process.kill(signal, $$) : old_dispatch
      end
    end
  end

  def setup
    @log = StringIO.new
    @handler = RailsFCGIHandler.new(@log)
  end

  def test_interrupted_via_HUP_when_not_in_request
    cgi = mock
    FCGI.expects(:each_cgi).once.yields(cgi)
    @handler.expects(:signal).times(2).returns('HUP')

    @handler.expects(:reload!).once
    @handler.expects(:close_connection).never
    @handler.expects(:exit).never

    @handler.process!
    assert_equal :reload, @handler.when_ready
  end

  def test_interrupted_via_HUP_when_in_request
    cgi = mock
    FCGI.expects(:each_cgi).once.yields(cgi)
    Dispatcher.expects(:signal).times(2).returns('HUP')

    @handler.expects(:reload!).once
    @handler.expects(:close_connection).never
    @handler.expects(:exit).never

    @handler.process!
    assert_equal :reload, @handler.when_ready
  end

  def test_interrupted_via_USR1_when_not_in_request
    cgi = mock
    FCGI.expects(:each_cgi).once.yields(cgi)
    @handler.expects(:signal).times(2).returns('USR1')
    @handler.expects(:exit_handler).never

    @handler.expects(:reload!).never
    @handler.expects(:close_connection).with(cgi).once
    @handler.expects(:exit).never

    @handler.process!
    assert_nil @handler.when_ready
  end

  def test_interrupted_via_USR1_when_in_request
    cgi = mock
    FCGI.expects(:each_cgi).once.yields(cgi)
    Dispatcher.expects(:signal).times(2).returns('USR1')

    @handler.expects(:reload!).never
    @handler.expects(:close_connection).with(cgi).once
    @handler.expects(:exit).never

    @handler.process!
    assert_equal :exit, @handler.when_ready
  end

  def test_restart_via_USR2_when_in_request
    cgi = mock
    FCGI.expects(:each_cgi).once.yields(cgi)
    @handler.expects(:signal).times(2).returns('USR2')
    @handler.expects(:exit_handler).never

    @handler.expects(:reload!).never
    @handler.expects(:close_connection).with(cgi).once
    @handler.expects(:exit).never
    @handler.expects(:restart!).once

    @handler.process!
    assert_equal :restart, @handler.when_ready
  end

  def test_interrupted_via_TERM
    cgi = mock
    FCGI.expects(:each_cgi).once.yields(cgi)
    Dispatcher.expects(:signal).times(2).returns('TERM')

    @handler.expects(:reload!).never
    @handler.expects(:close_connection).never

    @handler.process!
    assert_nil @handler.when_ready
  end

  def test_runtime_exception_in_fcgi
    error = RuntimeError.new('foo')
    FCGI.expects(:each_cgi).times(2).raises(error)
    @handler.expects(:dispatcher_error).with(error, regexp_matches(/^retrying/))
    @handler.expects(:dispatcher_error).with(error, regexp_matches(/^stopping/))
    @handler.process!
  end

  def test_runtime_error_in_dispatcher
    cgi = mock
    error = RuntimeError.new('foo')
    FCGI.expects(:each_cgi).once.yields(cgi)
    Dispatcher.expects(:dispatch).once.with(cgi).raises(error)
    @handler.expects(:dispatcher_error).with(error, regexp_matches(/^unhandled/))
    @handler.process!
  end

  def test_signal_exception_in_fcgi
    error = SignalException.new('USR2')
    FCGI.expects(:each_cgi).once.raises(error)
    @handler.expects(:dispatcher_error).with(error, regexp_matches(/^stopping/))
    @handler.process!
  end

  def test_signal_exception_in_dispatcher
    cgi = mock
    error = SignalException.new('USR2')
    FCGI.expects(:each_cgi).once.yields(cgi)
    Dispatcher.expects(:dispatch).once.with(cgi).raises(error)
    @handler.expects(:dispatcher_error).with(error, regexp_matches(/^stopping/))
    @handler.process!
  end
end


class RailsFCGIHandlerPeriodicGCTest < Test::Unit::TestCase
  def setup
    @log = StringIO.new
  end

  def teardown
    GC.enable
  end

  def test_normal_gc
    @handler = RailsFCGIHandler.new(@log)
    assert_nil @handler.gc_request_period

    # When GC is enabled, GC.disable disables and returns false.
    assert_equal false, GC.disable
  end

  def test_periodic_gc
    @handler = RailsFCGIHandler.new(@log, 10)
    assert_equal 10, @handler.gc_request_period

    cgi = mock
    FCGI.expects(:each_cgi).times(10).yields(cgi)
    Dispatcher.expects(:dispatch).times(10).with(cgi)

    @handler.expects(:run_gc!).never
    9.times { @handler.process! }
    @handler.expects(:run_gc!).once
    @handler.process!

    assert_nil @handler.when_ready
  end
end

rescue LoadError => e
  raise unless e.message =~ /fcgi/
end