nimboids-spork 0.8.99
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.
- data/Gemfile +6 -0
- data/MIT-LICENSE +20 -0
- data/README.rdoc +129 -0
- data/assets/bootstrap.rb +29 -0
- data/bin/spork +20 -0
- data/ext/mkrf_conf.rb +26 -0
- data/features/at_exit_during_each_run.feature +36 -0
- data/features/cucumber_rails_integration.feature +107 -0
- data/features/diagnostic_mode.feature +41 -0
- data/features/gemfiles/rails3.0/Gemfile +10 -0
- data/features/gemfiles/rails3.0/Gemfile.lock +116 -0
- data/features/rails_delayed_loading_workarounds.feature +150 -0
- data/features/rspec_rails_integration.feature +92 -0
- data/features/spork_debugger.feature +108 -0
- data/features/steps/general_steps.rb +3 -0
- data/features/steps/rails_steps.rb +67 -0
- data/features/steps/sandbox_steps.rb +115 -0
- data/features/support/background_job.rb +63 -0
- data/features/support/bundler_helpers.rb +41 -0
- data/features/support/env.rb +105 -0
- data/features/unknown_app_framework.feature +42 -0
- data/lib/spork.rb +155 -0
- data/lib/spork/app_framework.rb +80 -0
- data/lib/spork/app_framework/padrino.rb +22 -0
- data/lib/spork/app_framework/rails.rb +82 -0
- data/lib/spork/app_framework/unknown.rb +6 -0
- data/lib/spork/custom_io_streams.rb +25 -0
- data/lib/spork/diagnoser.rb +105 -0
- data/lib/spork/ext/rails-reloader.rb +14 -0
- data/lib/spork/ext/ruby-debug.rb +150 -0
- data/lib/spork/forker.rb +71 -0
- data/lib/spork/run_strategy.rb +44 -0
- data/lib/spork/run_strategy/forking.rb +32 -0
- data/lib/spork/run_strategy/magazine.rb +141 -0
- data/lib/spork/run_strategy/magazine/magazine_slave.rb +30 -0
- data/lib/spork/run_strategy/magazine/magazine_slave_provider.rb +27 -0
- data/lib/spork/run_strategy/magazine/ring_server.rb +10 -0
- data/lib/spork/runner.rb +90 -0
- data/lib/spork/server.rb +76 -0
- data/lib/spork/test_framework.rb +167 -0
- data/lib/spork/test_framework/cucumber.rb +24 -0
- data/lib/spork/test_framework/rspec.rb +14 -0
- data/spec/spec_helper.rb +113 -0
- data/spec/spork/app_framework/rails_spec.rb +22 -0
- data/spec/spork/app_framework/unknown_spec.rb +12 -0
- data/spec/spork/app_framework_spec.rb +16 -0
- data/spec/spork/diagnoser_spec.rb +105 -0
- data/spec/spork/forker_spec.rb +44 -0
- data/spec/spork/run_strategy/forking_spec.rb +38 -0
- data/spec/spork/runner_spec.rb +50 -0
- data/spec/spork/server_spec.rb +15 -0
- data/spec/spork/test_framework/cucumber_spec.rb +11 -0
- data/spec/spork/test_framework/rspec_spec.rb +10 -0
- data/spec/spork/test_framework_shared_examples.rb +23 -0
- data/spec/spork/test_framework_spec.rb +90 -0
- data/spec/spork_spec.rb +153 -0
- data/spec/support/fake_framework.rb +15 -0
- data/spec/support/fake_run_strategy.rb +21 -0
- metadata +158 -0
@@ -0,0 +1,22 @@
|
|
1
|
+
class Spork::AppFramework::Padrino < Spork::AppFramework
|
2
|
+
|
3
|
+
def preload(&block)
|
4
|
+
STDERR.puts "Preloading Padrino environment"
|
5
|
+
STDERR.flush
|
6
|
+
ENV["PADRINO_ENV"] ||= "test"
|
7
|
+
require boot_file
|
8
|
+
# Make it so that we don't have to restart Spork if we change, say, a model or routes
|
9
|
+
Spork.each_run { ::Padrino.reload! }
|
10
|
+
yield
|
11
|
+
end
|
12
|
+
|
13
|
+
def entry_point
|
14
|
+
@entry_point ||= File.expand_path("config/boot.rb", Dir.pwd)
|
15
|
+
end
|
16
|
+
alias :boot_file :entry_point
|
17
|
+
|
18
|
+
def boot_contents
|
19
|
+
@boot_contents ||= File.read(boot_file)
|
20
|
+
end
|
21
|
+
|
22
|
+
end
|
@@ -0,0 +1,82 @@
|
|
1
|
+
class Spork::AppFramework::Rails < Spork::AppFramework
|
2
|
+
|
3
|
+
def preload(&block)
|
4
|
+
STDERR.puts "Preloading Rails environment"
|
5
|
+
STDERR.flush
|
6
|
+
ENV["RAILS_ENV"] ||= 'test'
|
7
|
+
preload_rails
|
8
|
+
yield
|
9
|
+
end
|
10
|
+
|
11
|
+
def entry_point
|
12
|
+
@entry_point ||= File.expand_path("config/environment.rb", Dir.pwd)
|
13
|
+
end
|
14
|
+
|
15
|
+
alias :environment_file :entry_point
|
16
|
+
|
17
|
+
def boot_file
|
18
|
+
@boot_file ||= File.join(File.dirname(environment_file), 'boot')
|
19
|
+
end
|
20
|
+
|
21
|
+
def application_file
|
22
|
+
@application_file ||= File.join(File.dirname(environment_file), 'application')
|
23
|
+
end
|
24
|
+
|
25
|
+
def environment_contents
|
26
|
+
@environment_contents ||= File.read(environment_file)
|
27
|
+
end
|
28
|
+
|
29
|
+
def vendor
|
30
|
+
@vendor ||= File.expand_path("vendor/rails", Dir.pwd)
|
31
|
+
end
|
32
|
+
|
33
|
+
def deprecated_version
|
34
|
+
@version ||= (
|
35
|
+
if /^[^#]*RAILS_GEM_VERSION\s*=\s*["']([!~<>=]*\s*[\d.]+)["']/.match(environment_contents)
|
36
|
+
$1
|
37
|
+
else
|
38
|
+
nil
|
39
|
+
end
|
40
|
+
)
|
41
|
+
end
|
42
|
+
|
43
|
+
def preload_rails
|
44
|
+
if deprecated_version && (not deprecated_version.match?(/^3/))
|
45
|
+
puts "This version of spork only supports Rails 3.0. To use spork with rails 2.3.x, downgrade to spork 0.8.x."
|
46
|
+
exit 1
|
47
|
+
end
|
48
|
+
require application_file
|
49
|
+
::Rails.application
|
50
|
+
::Rails::Engine.class_eval do
|
51
|
+
def eager_load!
|
52
|
+
# turn off eager_loading, all together
|
53
|
+
end
|
54
|
+
end
|
55
|
+
# Spork.trap_method(::AbstractController::Helpers::ClassMethods, :helper)
|
56
|
+
Spork.trap_method(::ActiveModel::Observing::ClassMethods, :instantiate_observers)
|
57
|
+
Spork.each_run { ActiveRecord::Base.establish_connection rescue nil } if Object.const_defined?(:ActiveRecord)
|
58
|
+
|
59
|
+
|
60
|
+
AbstractController::Helpers::ClassMethods.module_eval do
|
61
|
+
def helper(*args, &block)
|
62
|
+
([args].flatten - [:all]).each do |arg|
|
63
|
+
next unless arg.is_a?(String)
|
64
|
+
filename = arg + "_helper"
|
65
|
+
unless ::ActiveSupport::Dependencies.search_for_file(filename)
|
66
|
+
# this error message must raise in the format such that LoadError#path returns the filename
|
67
|
+
raise LoadError.new("Missing helper file helpers/%s.rb" % filename)
|
68
|
+
end
|
69
|
+
end
|
70
|
+
|
71
|
+
Spork.each_run(false) do
|
72
|
+
modules_for_helpers(args).each do |mod|
|
73
|
+
add_template_helper(mod)
|
74
|
+
end
|
75
|
+
|
76
|
+
_helpers.module_eval(&block) if block_given?
|
77
|
+
end
|
78
|
+
end
|
79
|
+
end
|
80
|
+
end
|
81
|
+
|
82
|
+
end
|
@@ -0,0 +1,25 @@
|
|
1
|
+
# This class is mainly used for testing.
|
2
|
+
# When included (and used), it gives us an opportunity to stub out the output streams used for a given class
|
3
|
+
module Spork::CustomIOStreams
|
4
|
+
def self.included(klass)
|
5
|
+
klass.send(:extend, ::Spork::CustomIOStreams::ClassMethods)
|
6
|
+
end
|
7
|
+
|
8
|
+
def stderr
|
9
|
+
self.class.stderr
|
10
|
+
end
|
11
|
+
|
12
|
+
def stdout
|
13
|
+
self.class.stdout
|
14
|
+
end
|
15
|
+
|
16
|
+
module ClassMethods
|
17
|
+
def stderr
|
18
|
+
$stderr
|
19
|
+
end
|
20
|
+
|
21
|
+
def stdout
|
22
|
+
$stdout
|
23
|
+
end
|
24
|
+
end
|
25
|
+
end
|
@@ -0,0 +1,105 @@
|
|
1
|
+
# The Diagnoser hooks into load and require and keeps track of when files are required / loaded, and who loaded them.
|
2
|
+
# It's used when you run spork --diagnose
|
3
|
+
#
|
4
|
+
# = Example
|
5
|
+
#
|
6
|
+
# Spork::Diagnoser.install_hook!('/path/env.rb', '/path')
|
7
|
+
# require '/path/to/env.rb'
|
8
|
+
# Spork::Diagnoser.output_results(STDOUT)
|
9
|
+
class Spork::Diagnoser
|
10
|
+
class << self
|
11
|
+
def loaded_files
|
12
|
+
@loaded_files ||= {}
|
13
|
+
end
|
14
|
+
|
15
|
+
# Installs the diagnoser hook into Kernel#require and Kernel#load
|
16
|
+
#
|
17
|
+
# == Parameters
|
18
|
+
#
|
19
|
+
# * +entry_file+ - The file that is used to load the project. Used to filter the backtrace so anything that happens after it is hidden.
|
20
|
+
# * +dir+ - The project directory. Any file loaded outside of this directory will not be logged.
|
21
|
+
def install_hook!(entry_file = nil, dir = Dir.pwd)
|
22
|
+
@dir = File.expand_path(Dir.pwd, dir)
|
23
|
+
@entry_file = entry_file
|
24
|
+
|
25
|
+
Kernel.class_eval do
|
26
|
+
alias :require_without_diagnoser :require
|
27
|
+
alias :load_without_diagnoser :load
|
28
|
+
|
29
|
+
def require(string)
|
30
|
+
::Spork::Diagnoser.add_included_file(string, caller)
|
31
|
+
require_without_diagnoser(string)
|
32
|
+
end
|
33
|
+
private :require
|
34
|
+
|
35
|
+
def load(string)
|
36
|
+
::Spork::Diagnoser.add_included_file(string, caller)
|
37
|
+
load_without_diagnoser(string)
|
38
|
+
end
|
39
|
+
private :load
|
40
|
+
end
|
41
|
+
end
|
42
|
+
|
43
|
+
def add_included_file(filename, callstack)
|
44
|
+
filename = expand_filename(filename)
|
45
|
+
return unless File.exist?(filename)
|
46
|
+
loaded_files[filename] = filter_callstack(caller) if subdirectory?(filename)
|
47
|
+
end
|
48
|
+
|
49
|
+
# Uninstall the hook. Generally useful only for testing the Diagnoser.
|
50
|
+
def remove_hook!
|
51
|
+
return unless Kernel.private_instance_methods.include?('require_without_diagnoser')
|
52
|
+
Kernel.class_eval do
|
53
|
+
alias :require :require_without_diagnoser
|
54
|
+
alias :load :load_without_diagnoser
|
55
|
+
|
56
|
+
undef_method(:require_without_diagnoser)
|
57
|
+
undef_method(:load_without_diagnoser)
|
58
|
+
end
|
59
|
+
true
|
60
|
+
end
|
61
|
+
|
62
|
+
# output the results of a diagnostic run.
|
63
|
+
#
|
64
|
+
# == Parameters
|
65
|
+
#
|
66
|
+
# * +stdout+ - An IO stream to output the results to.
|
67
|
+
def output_results(stdout)
|
68
|
+
project_prefix = Dir.pwd + "/"
|
69
|
+
minimify = lambda { |f| f.gsub(project_prefix, '')}
|
70
|
+
stdout.puts "- Spork Diagnosis -\n"
|
71
|
+
stdout.puts "-- Summary --"
|
72
|
+
stdout.puts loaded_files.keys.sort.map(&minimify)
|
73
|
+
stdout.puts "\n\n\n"
|
74
|
+
stdout.puts "-- Detail --"
|
75
|
+
loaded_files.keys.sort.each do |file|
|
76
|
+
stdout.puts "\n\n\n--- #{minimify.call(file)} ---\n"
|
77
|
+
stdout.puts loaded_files[file].map(&minimify)
|
78
|
+
end
|
79
|
+
end
|
80
|
+
|
81
|
+
private
|
82
|
+
def filter_callstack(callstack, entry_file = @entry_file)
|
83
|
+
callstack.pop until callstack.empty? || callstack.last.include?(@entry_file) if @entry_file
|
84
|
+
callstack.map do |line|
|
85
|
+
next if line.include?('lib/spork/diagnoser.rb')
|
86
|
+
line.gsub!('require_without_diagnoser', 'require')
|
87
|
+
line
|
88
|
+
end.compact
|
89
|
+
end
|
90
|
+
|
91
|
+
def expand_filename(filename)
|
92
|
+
([Dir.pwd] + $:).each do |attempted_path|
|
93
|
+
attempted_filename = File.expand_path(filename, attempted_path)
|
94
|
+
return attempted_filename if File.file?(attempted_filename)
|
95
|
+
attempted_filename = attempted_filename + ".rb"
|
96
|
+
return attempted_filename if File.file?(attempted_filename)
|
97
|
+
end
|
98
|
+
filename
|
99
|
+
end
|
100
|
+
|
101
|
+
def subdirectory?(directory)
|
102
|
+
File.expand_path(directory, Dir.pwd).include?(@dir)
|
103
|
+
end
|
104
|
+
end
|
105
|
+
end
|
@@ -0,0 +1,14 @@
|
|
1
|
+
Spork.each_run do
|
2
|
+
::ActiveSupport.const_defined?(:Dependencies) ?
|
3
|
+
::ActiveSupport::Dependencies.mechanism = :load :
|
4
|
+
::Dependencies.mechanism = :load
|
5
|
+
|
6
|
+
require 'action_controller/dispatcher'
|
7
|
+
dispatcher = ::ActionController::Dispatcher.new($stdout)
|
8
|
+
|
9
|
+
if ::ActionController::Dispatcher.respond_to?(:reload_application)
|
10
|
+
::ActionController::Dispatcher.reload_application
|
11
|
+
else
|
12
|
+
dispatcher.reload_application
|
13
|
+
end
|
14
|
+
end if Spork.using_spork?
|
@@ -0,0 +1,150 @@
|
|
1
|
+
require 'socket'
|
2
|
+
require 'forwardable'
|
3
|
+
|
4
|
+
begin
|
5
|
+
require 'ruby-debug'
|
6
|
+
|
7
|
+
# Experimental!
|
8
|
+
|
9
|
+
class SporkDebugger
|
10
|
+
DEFAULT_PORT = 10_123
|
11
|
+
HOST = '127.0.0.1'
|
12
|
+
|
13
|
+
extend Forwardable
|
14
|
+
def_delegators :state, *[:prepare_debugger, :initialize]
|
15
|
+
attr_reader :state
|
16
|
+
|
17
|
+
class << self
|
18
|
+
attr_reader :instance
|
19
|
+
def run
|
20
|
+
@instance ||= new
|
21
|
+
end
|
22
|
+
end
|
23
|
+
|
24
|
+
def initialize
|
25
|
+
@state = SporkDebugger::PreloadState.new
|
26
|
+
Spork.send(:each_run_procs).unshift(lambda do
|
27
|
+
@state = @state.transition_to_each_run_state
|
28
|
+
end)
|
29
|
+
end
|
30
|
+
|
31
|
+
module NetworkHelpers
|
32
|
+
def find_port(starting_with)
|
33
|
+
port = starting_with
|
34
|
+
begin
|
35
|
+
server = TCPServer.new(HOST, port)
|
36
|
+
server.close
|
37
|
+
rescue Errno::EADDRINUSE
|
38
|
+
port += 1
|
39
|
+
retry
|
40
|
+
end
|
41
|
+
|
42
|
+
port
|
43
|
+
end
|
44
|
+
end
|
45
|
+
|
46
|
+
class PreloadState
|
47
|
+
include NetworkHelpers
|
48
|
+
def initialize
|
49
|
+
Spork.each_run { install_hook }
|
50
|
+
listen_for_connection_signals
|
51
|
+
end
|
52
|
+
|
53
|
+
def finish
|
54
|
+
@tcp_service.close; @tcp_service = nil;
|
55
|
+
end
|
56
|
+
|
57
|
+
def transition_to_each_run_state
|
58
|
+
finish
|
59
|
+
SporkDebugger::EachRunState.new(@port)
|
60
|
+
end
|
61
|
+
|
62
|
+
protected
|
63
|
+
def install_hook
|
64
|
+
Kernel.class_eval do
|
65
|
+
alias :debugger_without_spork_hook :debugger
|
66
|
+
def debugger(steps = 1)
|
67
|
+
SporkDebugger.instance.prepare_debugger
|
68
|
+
debugger_without_spork_hook
|
69
|
+
end
|
70
|
+
end
|
71
|
+
end
|
72
|
+
|
73
|
+
def listen_for_connection_signals
|
74
|
+
@port = SporkDebugger::DEFAULT_PORT
|
75
|
+
begin
|
76
|
+
@tcp_service = TCPServer.new(SporkDebugger::HOST, @port)
|
77
|
+
rescue Errno::EADDRINUSE
|
78
|
+
@port += 1
|
79
|
+
retry
|
80
|
+
end
|
81
|
+
Thread.new { main_loop }
|
82
|
+
end
|
83
|
+
|
84
|
+
def main_loop
|
85
|
+
while @tcp_service do
|
86
|
+
socket = @tcp_service.accept
|
87
|
+
port = Marshal.load(socket)
|
88
|
+
Marshal.dump(true, socket)
|
89
|
+
connect_rdebug_client(port)
|
90
|
+
socket.close
|
91
|
+
end
|
92
|
+
rescue => e
|
93
|
+
puts "error: #{e.class} - #{e}"
|
94
|
+
end
|
95
|
+
|
96
|
+
def connect_rdebug_client(port = Debugger::PORT)
|
97
|
+
puts "\n\n - Debug Session Started - \n\n\n"
|
98
|
+
begin
|
99
|
+
Debugger.start_client(SporkDebugger::HOST, port)
|
100
|
+
rescue Errno::EPIPE, Errno::ECONNRESET, Errno::ECONNREFUSED
|
101
|
+
end
|
102
|
+
puts "\n\n - Debug Session Terminated - \n\n\n"
|
103
|
+
end
|
104
|
+
end
|
105
|
+
|
106
|
+
class EachRunState
|
107
|
+
include NetworkHelpers
|
108
|
+
def initialize(connection_request_port)
|
109
|
+
@connection_request_port = connection_request_port
|
110
|
+
end
|
111
|
+
|
112
|
+
def prepare_debugger
|
113
|
+
return if @debugger_prepared
|
114
|
+
@debugger_prepared = true
|
115
|
+
port, cport = start_rdebug_server
|
116
|
+
signal_spork_server_to_connect_to_rdebug_server(port)
|
117
|
+
wait_for_connection
|
118
|
+
puts "\n\n - breakpoint - see your spork server for the debug terminal - \n\n"
|
119
|
+
end
|
120
|
+
|
121
|
+
def signal_spork_server_to_connect_to_rdebug_server(rdebug_server_port)
|
122
|
+
socket = TCPSocket.new(SporkDebugger::HOST, @connection_request_port)
|
123
|
+
Marshal.dump(rdebug_server_port, socket)
|
124
|
+
response = Marshal.load(socket)
|
125
|
+
socket.close
|
126
|
+
end
|
127
|
+
|
128
|
+
def start_rdebug_server
|
129
|
+
Debugger.stop if Debugger.started?
|
130
|
+
port = find_port(Debugger::PORT)
|
131
|
+
cport = find_port(port + 1)
|
132
|
+
Debugger.start_remote(SporkDebugger::HOST, [port, cport]) do
|
133
|
+
Debugger.run_init_script(StringIO.new)
|
134
|
+
end
|
135
|
+
Debugger.start
|
136
|
+
[port, cport]
|
137
|
+
end
|
138
|
+
|
139
|
+
protected
|
140
|
+
def wait_for_connection
|
141
|
+
while Debugger.handler.interface.nil?; sleep 0.10; end
|
142
|
+
end
|
143
|
+
end
|
144
|
+
end
|
145
|
+
|
146
|
+
Spork.prefork { SporkDebugger.run } if Spork.using_spork?
|
147
|
+
|
148
|
+
rescue LoadError
|
149
|
+
raise LoadError, "Your project has loaded spork/ext/ruby-debug, which relies on the ruby-debug gem. It appears that ruby-debug is not installed. Please install it."
|
150
|
+
end
|
data/lib/spork/forker.rb
ADDED
@@ -0,0 +1,71 @@
|
|
1
|
+
# A helper class that allows you to run a block inside of a fork, and then get the result from that block.
|
2
|
+
#
|
3
|
+
# == Example:
|
4
|
+
#
|
5
|
+
# forker = Spork::Forker.new do
|
6
|
+
# sleep 3
|
7
|
+
# "success"
|
8
|
+
# end
|
9
|
+
#
|
10
|
+
# forker.result # => "success"
|
11
|
+
class Spork::Forker
|
12
|
+
|
13
|
+
# Raised if the fork died (was killed) before it sent it's response back.
|
14
|
+
class ForkDiedException < Exception; end
|
15
|
+
def initialize(&block)
|
16
|
+
return unless block_given?
|
17
|
+
@child_io, @server_io = UNIXSocket.socketpair
|
18
|
+
@child_pid = Kernel.fork do
|
19
|
+
begin
|
20
|
+
@server_io.close
|
21
|
+
Marshal.dump(yield, @child_io)
|
22
|
+
# wait for the parent to acknowledge receipt of the result.
|
23
|
+
master_response = Marshal.load(@child_io)
|
24
|
+
rescue EOFError
|
25
|
+
nil
|
26
|
+
rescue Exception => e
|
27
|
+
puts "Exception encountered: #{e.inspect}\nbacktrace:\n#{e.backtrace * %(\n)}"
|
28
|
+
end
|
29
|
+
|
30
|
+
# terminate, skipping any at_exit blocks.
|
31
|
+
exit!(0)
|
32
|
+
end
|
33
|
+
@child_io.close
|
34
|
+
end
|
35
|
+
|
36
|
+
# Wait for the fork to finish running, and then return its return value.
|
37
|
+
#
|
38
|
+
# If the fork was aborted, then result returns nil.
|
39
|
+
def result
|
40
|
+
return unless running?
|
41
|
+
result_thread = Thread.new do
|
42
|
+
begin
|
43
|
+
@result = Marshal.load(@server_io)
|
44
|
+
Marshal.dump('ACK', @server_io)
|
45
|
+
rescue ForkDiedException, EOFError
|
46
|
+
@result = nil
|
47
|
+
end
|
48
|
+
end
|
49
|
+
Process.wait(@child_pid)
|
50
|
+
result_thread.raise(ForkDiedException) if @result.nil?
|
51
|
+
@child_pid = nil
|
52
|
+
@result
|
53
|
+
end
|
54
|
+
|
55
|
+
# abort the current running fork
|
56
|
+
def abort
|
57
|
+
if running?
|
58
|
+
Process.kill(Signal.list['TERM'], @child_pid)
|
59
|
+
@child_pid = nil
|
60
|
+
true
|
61
|
+
end
|
62
|
+
end
|
63
|
+
|
64
|
+
def running?
|
65
|
+
return false unless @child_pid
|
66
|
+
Process.getpgid(@child_pid)
|
67
|
+
true
|
68
|
+
rescue Errno::ESRCH
|
69
|
+
false
|
70
|
+
end
|
71
|
+
end
|