smtlaissezfaire-spork 0.5.7

Sign up to get free protection for your applications and to get access to all the features.
Files changed (37) hide show
  1. data/MIT-LICENSE +20 -0
  2. data/README.rdoc +129 -0
  3. data/assets/bootstrap.rb +29 -0
  4. data/bin/spork +21 -0
  5. data/features/cucumber_rails_integration.feature +106 -0
  6. data/features/diagnostic_mode.feature +37 -0
  7. data/features/rails_delayed_loading_workarounds.feature +80 -0
  8. data/features/rspec_rails_integration.feature +66 -0
  9. data/features/steps/rails_steps.rb +53 -0
  10. data/features/steps/sandbox_steps.rb +91 -0
  11. data/features/support/env.rb +97 -0
  12. data/features/unknown_app_framework.feature +42 -0
  13. data/lib/spork.rb +96 -0
  14. data/lib/spork/app_framework.rb +74 -0
  15. data/lib/spork/app_framework/rails.rb +158 -0
  16. data/lib/spork/app_framework/rails_stub_files/application.rb +3 -0
  17. data/lib/spork/app_framework/rails_stub_files/application_controller.rb +3 -0
  18. data/lib/spork/app_framework/rails_stub_files/application_helper.rb +3 -0
  19. data/lib/spork/app_framework/unknown.rb +6 -0
  20. data/lib/spork/custom_io_streams.rb +25 -0
  21. data/lib/spork/diagnoser.rb +103 -0
  22. data/lib/spork/forker.rb +70 -0
  23. data/lib/spork/runner.rb +111 -0
  24. data/lib/spork/server.rb +197 -0
  25. data/lib/spork/server/cucumber.rb +28 -0
  26. data/lib/spork/server/rspec.rb +22 -0
  27. data/spec/spec_helper.rb +107 -0
  28. data/spec/spork/app_framework/rails_spec.rb +22 -0
  29. data/spec/spork/app_framework/unknown_spec.rb +12 -0
  30. data/spec/spork/app_framework_spec.rb +16 -0
  31. data/spec/spork/diagnoser_spec.rb +105 -0
  32. data/spec/spork/forker_spec.rb +44 -0
  33. data/spec/spork/runner_spec.rb +63 -0
  34. data/spec/spork/server/rspec_spec.rb +15 -0
  35. data/spec/spork/server_spec.rb +128 -0
  36. data/spec/spork_spec.rb +143 -0
  37. metadata +100 -0
@@ -0,0 +1,158 @@
1
+ class Spork::AppFramework::Rails < Spork::AppFramework
2
+
3
+ # TODO - subclass this out to handle different versions of rails
4
+ # Also... this is the nastiest duck punch ever. Clean this up.
5
+ module NinjaPatcher
6
+ def self.included(klass)
7
+ klass.class_eval do
8
+ unless method_defined?(:load_environment_without_spork)
9
+ alias :load_environment_without_spork :load_environment
10
+ alias :load_environment :load_environment_with_spork
11
+ end
12
+
13
+ def self.run_with_spork(*args, &block) # it's all fun and games until someone gets an eye poked out
14
+ if ENV['RAILS_ENV']
15
+ Object.send(:remove_const, :RAILS_ENV)
16
+ Object.const_set(:RAILS_ENV, ENV['RAILS_ENV'].dup)
17
+ end
18
+ run_without_spork(*args, &block)
19
+ end
20
+
21
+ class << self
22
+ unless method_defined?(:run_without_spork)
23
+ alias :run_without_spork :run
24
+ alias :run :run_with_spork
25
+ end
26
+ end
27
+ end
28
+ end
29
+
30
+ def load_environment_with_spork
31
+ result = load_environment_without_spork
32
+ install_hooks
33
+ result
34
+ end
35
+
36
+ def install_hooks
37
+ auto_reestablish_db_connection
38
+ delay_observer_loading
39
+ delay_app_preload
40
+ delay_application_controller_loading
41
+ delay_route_loading
42
+ delay_eager_view_loading
43
+ end
44
+
45
+ def reset_rails_env
46
+ return unless ENV['RAILS_ENV']
47
+ Object.send(:remove_const, :RAILS_ENV)
48
+ Object.const_set(:RAILS_ENV, ENV['RAILS_ENV'].dup)
49
+ end
50
+
51
+ def delay_observer_loading
52
+ if ::Rails::Initializer.instance_methods.include?('load_observers')
53
+ Spork.trap_method(::Rails::Initializer, :load_observers)
54
+ end
55
+ if Object.const_defined?(:ActionController)
56
+ require "action_controller/dispatcher.rb"
57
+ Spork.trap_class_method(::ActionController::Dispatcher, :define_dispatcher_callbacks) if ActionController::Dispatcher.respond_to?(:define_dispatcher_callbacks)
58
+ end
59
+ end
60
+
61
+ def delay_app_preload
62
+ if ::Rails::Initializer.instance_methods.include?('load_application_classes')
63
+ Spork.trap_method(::Rails::Initializer, :load_application_classes)
64
+ end
65
+ end
66
+
67
+ def delay_application_controller_loading
68
+ if application_controller_source = ["#{Dir.pwd}/app/controllers/application.rb", "#{Dir.pwd}/app/controllers/application_controller.rb"].find { |f| File.exist?(f) }
69
+ application_helper_source = "#{Dir.pwd}/app/helpers/application_helper.rb"
70
+ load_paths = (::ActiveSupport.const_defined?(:Dependencies) ? ::ActiveSupport::Dependencies : ::Dependencies).load_paths
71
+ load_paths.unshift(File.expand_path('rails_stub_files', File.dirname(__FILE__)))
72
+ Spork.each_run do
73
+ require application_controller_source
74
+ require application_helper_source if File.exist?(application_helper_source)
75
+ # update the rails magic to refresh the module
76
+ ApplicationController.send(:helper, ApplicationHelper)
77
+ end
78
+ end
79
+ end
80
+
81
+ def auto_reestablish_db_connection
82
+ if Object.const_defined?(:ActiveRecord)
83
+ Spork.each_run do
84
+ # rails lib/test_help.rb is very aggressive about overriding RAILS_ENV and will switch it back to test after the cucumber env was loaded
85
+ reset_rails_env
86
+ ActiveRecord::Base.establish_connection
87
+ end
88
+ end
89
+ end
90
+
91
+ def delay_route_loading
92
+ if ::Rails::Initializer.instance_methods.include?('initialize_routing')
93
+ Spork.trap_method(::Rails::Initializer, :initialize_routing)
94
+ end
95
+ end
96
+
97
+ def delay_eager_view_loading
98
+ # So, in testing mode it seems it would be optimal to not eager load
99
+ # views (as your may only run a test that uses one or two views).
100
+ # However, I decided to delay eager loading rather than force it to
101
+ # disable because you may wish to eager load your views (I.E. you're
102
+ # testing concurrency)
103
+
104
+ # Rails 2.3.x +
105
+ if defined?(::ActionView::Template::EagerPath)
106
+ Spork.trap_method(::ActionView::Template::EagerPath, :load!)
107
+ end
108
+ # Rails 2.2.x
109
+ if defined?(::ActionView::PathSet::Path)
110
+ Spork.trap_method(::ActionView::PathSet::Path, :load)
111
+ end
112
+ # Rails 2.0.5 - 2.1.x don't appear to eager cache views.
113
+ end
114
+ end
115
+
116
+ def preload(&block)
117
+ STDERR.puts "Preloading Rails environment"
118
+ STDERR.flush
119
+ ENV["RAILS_ENV"] ||= 'test'
120
+ preload_rails
121
+ yield
122
+ end
123
+
124
+ def entry_point
125
+ @entry_point ||= File.expand_path("config/environment.rb", Dir.pwd)
126
+ end
127
+
128
+ alias :environment_file :entry_point
129
+
130
+ def boot_file
131
+ @boot_file ||= File.join(File.dirname(environment_file), 'boot')
132
+ end
133
+
134
+ def environment_contents
135
+ @environment_contents ||= File.read(environment_file)
136
+ end
137
+
138
+ def vendor
139
+ @vendor ||= File.expand_path("vendor/rails", Dir.pwd)
140
+ end
141
+
142
+ def version
143
+ @version ||= (
144
+ if /^[^#]*RAILS_GEM_VERSION\s*=\s*["']([!~<>=]*\s*[\d.]+)["']/.match(environment_contents)
145
+ $1
146
+ else
147
+ nil
148
+ end
149
+ )
150
+ end
151
+
152
+ def preload_rails
153
+ Object.const_set(:RAILS_GEM_VERSION, version) if version
154
+ require boot_file
155
+ ::Rails::Initializer.send(:include, Spork::AppFramework::Rails::NinjaPatcher)
156
+ end
157
+
158
+ end
@@ -0,0 +1,3 @@
1
+ # This is a stub used to help Spork delay the loading of the real ApplicationController
2
+ class ::ApplicationController < ActionController::Base
3
+ end
@@ -0,0 +1,3 @@
1
+ # This is a stub used to help Spork delay the loading of the real ApplicationController
2
+ class ::ApplicationController < ActionController::Base
3
+ end
@@ -0,0 +1,3 @@
1
+ # This is a stub used to help Spork delay the loading of the real ApplicationHelper
2
+ module ::ApplicationHelper
3
+ end
@@ -0,0 +1,6 @@
1
+ # This is used if no supported appliction framework is detected
2
+ class Spork::AppFramework::Unknown < Spork::AppFramework
3
+ def entry_point
4
+ nil
5
+ end
6
+ 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,103 @@
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
+
34
+ def load(string)
35
+ ::Spork::Diagnoser.add_included_file(string, caller)
36
+ load_without_diagnoser(string)
37
+ end
38
+ end
39
+ end
40
+
41
+ def add_included_file(filename, callstack)
42
+ filename = expand_filename(filename)
43
+ return unless File.exist?(filename)
44
+ loaded_files[filename] = filter_callstack(caller) if subdirectory?(filename)
45
+ end
46
+
47
+ # Uninstall the hook. Generally useful only for testing the Diagnoser.
48
+ def remove_hook!
49
+ return unless Kernel.private_instance_methods.include?('require_without_diagnoser')
50
+ Kernel.class_eval do
51
+ alias :require :require_without_diagnoser
52
+ alias :load :load_without_diagnoser
53
+
54
+ undef_method(:require_without_diagnoser)
55
+ undef_method(:load_without_diagnoser)
56
+ end
57
+ true
58
+ end
59
+
60
+ # output the results of a diagnostic run.
61
+ #
62
+ # == Parameters
63
+ #
64
+ # * +stdout+ - An IO stream to output the results to.
65
+ def output_results(stdout)
66
+ project_prefix = Dir.pwd + "/"
67
+ minimify = lambda { |f| f.gsub(project_prefix, '')}
68
+ stdout.puts "- Spork Diagnosis -\n"
69
+ stdout.puts "-- Summary --"
70
+ stdout.puts loaded_files.keys.sort.map(&minimify)
71
+ stdout.puts "\n\n\n"
72
+ stdout.puts "-- Detail --"
73
+ loaded_files.keys.sort.each do |file|
74
+ stdout.puts "\n\n\n--- #{minimify.call(file)} ---\n"
75
+ stdout.puts loaded_files[file].map(&minimify)
76
+ end
77
+ end
78
+
79
+ private
80
+ def filter_callstack(callstack, entry_file = @entry_file)
81
+ callstack.pop until callstack.empty? || callstack.last.include?(@entry_file) if @entry_file
82
+ callstack.map do |line|
83
+ next if line.include?('lib/spork/diagnoser.rb')
84
+ line.gsub!('require_without_diagnoser', 'require')
85
+ line
86
+ end.compact
87
+ end
88
+
89
+ def expand_filename(filename)
90
+ ([Dir.pwd] + $:).each do |attempted_path|
91
+ attempted_filename = File.expand_path(filename, attempted_path)
92
+ return attempted_filename if File.file?(attempted_filename)
93
+ attempted_filename = attempted_filename + ".rb"
94
+ return attempted_filename if File.file?(attempted_filename)
95
+ end
96
+ filename
97
+ end
98
+
99
+ def subdirectory?(directory)
100
+ File.expand_path(directory, Dir.pwd).include?(@dir)
101
+ end
102
+ end
103
+ end
@@ -0,0 +1,70 @@
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
+ @server_io.close
20
+ Marshal.dump(yield, @child_io)
21
+ # wait for the parent to acknowledge receipt of the result.
22
+ master_response =
23
+ begin
24
+ Marshal.load(@child_io)
25
+ rescue EOFError
26
+ nil
27
+ end
28
+
29
+ # terminate, skipping any at_exit blocks.
30
+ exit!(0)
31
+ end
32
+ @child_io.close
33
+ end
34
+
35
+ # Wait for the fork to finish running, and then return its return value.
36
+ #
37
+ # If the fork was aborted, then result returns nil.
38
+ def result
39
+ return unless running?
40
+ result_thread = Thread.new do
41
+ begin
42
+ @result = Marshal.load(@server_io)
43
+ Marshal.dump('ACK', @server_io)
44
+ rescue ForkDiedException
45
+ @result = nil
46
+ end
47
+ end
48
+ Process.wait(@child_pid)
49
+ result_thread.raise(ForkDiedException) if @result.nil?
50
+ @child_pid = nil
51
+ @result
52
+ end
53
+
54
+ # abort the current running fork
55
+ def abort
56
+ if running?
57
+ Process.kill(Signal.list['TERM'], @child_pid)
58
+ @child_pid = nil
59
+ true
60
+ end
61
+ end
62
+
63
+ def running?
64
+ return false unless @child_pid
65
+ Process.getpgid(@child_pid)
66
+ true
67
+ rescue Errno::ESRCH
68
+ false
69
+ end
70
+ end
@@ -0,0 +1,111 @@
1
+ require 'optparse'
2
+ require 'spork/server'
3
+
4
+ module Spork
5
+ # This is used by bin/spork. It's wrapped in a class because it's easier to test that way.
6
+ class Runner
7
+ attr_reader :server
8
+
9
+ def self.run(args, output, error)
10
+ self.new(args, output, error).run
11
+ end
12
+
13
+ def initialize(args, output, error)
14
+ raise ArgumentError, "expected array of args" unless args.is_a?(Array)
15
+ @output = output
16
+ @error = error
17
+ @options = {}
18
+ opt = OptionParser.new
19
+ opt.banner = "Usage: spork [test framework name] [options]\n\n"
20
+
21
+ opt.separator "Options:"
22
+ opt.on("-b", "--bootstrap") {|ignore| @options[:bootstrap] = true }
23
+ opt.on("-d", "--diagnose") {|ignore| @options[:diagnose] = true }
24
+ opt.on("-h", "--help") {|ignore| @options[:help] = true }
25
+ non_option_args = args.select { |arg| ! args[0].match(/^-/) }
26
+ @options[:server_matcher] = non_option_args[0]
27
+ opt.parse!(args)
28
+
29
+ if @options[:help]
30
+ @output.puts opt
31
+ @output.puts
32
+ @output.puts supported_servers_text
33
+ exit(0)
34
+ end
35
+ end
36
+
37
+ def supported_servers_text
38
+ text = StringIO.new
39
+
40
+ text.puts "Supported test frameworks:"
41
+ text.puts Spork::Server.supported_servers.sort { |a,b| a.server_name <=> b.server_name }.map { |s| (s.available? ? '(*) ' : '( ) ') + s.server_name }
42
+ text.puts "\nLegend: ( ) - not detected in project (*) - detected\n"
43
+ text.string
44
+ end
45
+
46
+ # Returns a server for the specified (or the detected default) testing framework. Returns nil if none detected, or if the specified is not supported or available.
47
+ def find_server
48
+ if options[:server_matcher]
49
+ @server = Spork::Server.supported_servers(options[:server_matcher]).first
50
+ unless @server
51
+ @error.puts <<-ERROR
52
+ #{options[:server_matcher].inspect} didn't match a supported test framework.
53
+
54
+ #{supported_servers_text}
55
+ ERROR
56
+ return
57
+ end
58
+
59
+ unless @server.available?
60
+ @error.puts <<-USEFUL_ERROR
61
+ I can't find the helper file #{@server.helper_file} for the #{@server.server_name} testing framework.
62
+ Are you running me from the project directory?
63
+ USEFUL_ERROR
64
+ return
65
+ end
66
+ else
67
+ @server = Spork::Server.available_servers.first
68
+ if @server.nil?
69
+ @error.puts <<-USEFUL_ERROR
70
+ I can't find any testing frameworks to use.
71
+ Are you running me from a project directory?
72
+ USEFUL_ERROR
73
+ return
74
+ end
75
+ end
76
+ @server
77
+ end
78
+
79
+ def run
80
+ return false unless find_server
81
+ ENV["DRB"] = 'true'
82
+ @error.puts "Using #{server.server_name}"
83
+ @error.flush
84
+ case
85
+ when options[:bootstrap]
86
+ server.bootstrap
87
+ when options[:diagnose]
88
+ require 'spork/diagnoser'
89
+
90
+ Spork::Diagnoser.install_hook!(server.entry_point)
91
+ server.preload
92
+ Spork::Diagnoser.output_results(@output)
93
+ return true
94
+ else
95
+ return(false) unless server.preload
96
+ server.run
97
+ return true
98
+ end
99
+ end
100
+
101
+ private
102
+ attr_reader :options
103
+
104
+ end
105
+ end
106
+
107
+
108
+
109
+
110
+
111
+