Skip to content
This repository
Fetching contributors…

Cannot retrieve contributors at this time

file 222 lines (190 sloc) 5.567 kb
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62 63 64 65 66 67 68 69 70 71 72 73 74 75 76 77 78 79 80 81 82 83 84 85 86 87 88 89 90 91 92 93 94 95 96 97 98 99 100 101 102 103 104 105 106 107 108 109 110 111 112 113 114 115 116 117 118 119 120 121 122 123 124 125 126 127 128 129 130 131 132 133 134 135 136 137 138 139 140 141 142 143 144 145 146 147 148 149 150 151 152 153 154 155 156 157 158 159 160 161 162 163 164 165 166 167 168 169 170 171 172 173 174 175 176 177 178 179 180 181 182 183 184 185 186 187 188 189 190 191 192 193 194 195 196 197 198 199 200 201 202 203 204 205 206 207 208 209 210 211 212 213 214 215 216 217 218 219 220 221 222
require 'sinatra/base'
require 'rbconfig'
require 'open-uri'
require 'net/http'
require 'timeout'

module IntegrationHelper
  class BaseServer
    extend Enumerable
    attr_accessor :server, :port, :pipe
    alias name server

    def self.all
      @all ||= []
    end

    def self.each(&block)
      all.each(&block)
    end

    def self.run(server, port)
      new(server, port).run
    end

    def app_file
      File.expand_path('../integration/app.rb', __FILE__)
    end

    def environment
      "development"
    end

    def initialize(server, port)
      @installed, @pipe, @server, @port = nil, nil, server, port
      Server.all << self
    end

    def run
      return unless installed?
      kill
      @log = ""
      @pipe = IO.popen(command)
      @started = Time.now
      warn "#{server} up and running on port #{port}" if ping
      at_exit { kill }
    end

    def ping(timeout = 30)
      loop do
        return if alive?
        if Time.now - @started > timeout
          $stderr.puts command, log
          fail "timeout"
        else
          sleep 0.1
        end
      end
    end

    def alive?
      3.times { get('/ping') }
      true
    rescue Errno::ECONNREFUSED, Errno::ECONNRESET, EOFError, SystemCallError, OpenURI::HTTPError, Timeout::Error => error
      false
    end

    def get_stream(url = "/stream", &block)
      Net::HTTP.start '127.0.0.1', port do |http|
        request = Net::HTTP::Get.new url
        http.request request do |response|
          response.read_body(&block)
        end
      end
    end

    def get(url)
      Timeout.timeout(1) { open("http://127.0.0.1:#{port}#{url}").read }
    end

    def log
      @log ||= ""
      loop { @log << @pipe.read_nonblock(1) }
    rescue Exception
      @log
    end

    def installed?
      return @installed unless @installed.nil?
      require server
      @installed = true
    rescue LoadError
      warn "#{server} is not installed, skipping integration tests"
      @installed = false
    end

    def command
      @command ||= begin
        cmd = ["RACK_ENV=#{environment}", "exec"]
        if RbConfig.respond_to? :ruby
          cmd << RbConfig.ruby.inspect
        else
          file, dir = RbConfig::CONFIG.values_at('ruby_install_name', 'bindir')
          cmd << File.expand_path(file, dir).inspect
        end
        cmd << "-w" unless thin?
        cmd << "-I" << File.expand_path('../../lib', __FILE__).inspect
        cmd << app_file.inspect << '-s' << server << '-o' << '127.0.0.1' << '-p' << port
        cmd << "-e" << environment.to_s << '2>&1'
        cmd.join " "
      end
    end

    def kill
      return unless pipe
      Process.kill("KILL", pipe.pid)
    rescue NotImplementedError
      system "kill -9 #{pipe.pid}"
    rescue Errno::ESRCH
    end

    def webrick?
      name.to_s == "webrick"
    end

    def thin?
      name.to_s == "thin"
    end

    def puma?
      name.to_s == "puma"
    end

    def trinidad?
      name.to_s == "trinidad"
    end

    def warnings
      log.scan(%r[(?:\(eval|lib/sinatra).*warning:.*$])
    end

    def run_test(target, &block)
      retries ||= 3
      target.server = self
      run unless alive?
      target.instance_eval(&block)
    rescue Exception => error
      retries -= 1
      kill
      retries < 0 ? retry : raise(error)
    end
  end

  if RUBY_ENGINE == "jruby"
    class JRubyServer < BaseServer
      def start_vm
        require 'java'
        # Create a new container, set load paths and env
        # SINGLETHREAD means create a new runtime
        vm = org.jruby.embed.ScriptingContainer.new(org.jruby.embed.LocalContextScope::SINGLETHREAD)
        vm.load_paths = [File.expand_path('../../lib', __FILE__)]
        vm.environment = ENV.merge('RACK_ENV' => environment.to_s)

        # This ensures processing of RUBYOPT which activates Bundler
        vm.provider.ruby_instance_config.process_arguments []
        vm.argv = ['-s', server.to_s, '-o', '127.0.0.1', '-p', port.to_s, '-e', environment.to_s]

        # Set stdout/stderr so we can retrieve log
        @pipe = java.io.ByteArrayOutputStream.new
        vm.output = java.io.PrintStream.new(@pipe)
        vm.error = java.io.PrintStream.new(@pipe)

        Thread.new do
          # Hack to ensure that Kernel#caller has the same info as
          # when run from command-line, for Sintra::Application.app_file.
          # Also, line numbers are zero-based in JRuby's parser
          vm.provider.runtime.current_context.set_file_and_line(app_file, 0)
          # Run the app
          vm.run_scriptlet org.jruby.embed.PathType::ABSOLUTE, app_file
          # terminate launches at_exit hooks which start server
          vm.terminate
        end
      end

      def run
        return unless installed?
        kill
        @thread = start_vm
        @started = Time.now
        warn "#{server} up and running on port #{port}" if ping
        at_exit { kill }
      end

      def log
        String.from_java_bytes @pipe.to_byte_array
      end

      def kill
        @thread.kill if @thread
        @thread = nil
      end
    end
    Server = JRubyServer
  else
    Server = BaseServer
  end

  def it(message, &block)
    Server.each do |server|
      next unless server.installed?
      super("with #{server.name}: #{message}") { server.run_test(self, &block) }
    end
  end

  def self.extend_object(obj)
    super

    base_port = 5000 + Process.pid % 100
    Sinatra::Base.server.each_with_index do |server, index|
      Server.run(server, 5000+index)
    end
  end
end
Something went wrong with that request. Please try again.