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,53 @@
1
+ Given /^I am in a fresh rails project named "(.+)"$/ do |folder_name|
2
+ @current_dir = SporkWorld::SANDBOX_DIR
3
+ version_argument = ENV['RAILS_VERSION'] ? "_#{ENV['RAILS_VERSION']}_" : nil
4
+ # run("#{SporkWorld::RUBY_BINARY} #{%x{which rails}.chomp} #{folder_name}")
5
+ run([SporkWorld::RUBY_BINARY, %x{which rails}.chomp, version_argument, folder_name].compact * " ")
6
+ @current_dir = File.join(File.join(SporkWorld::SANDBOX_DIR, folder_name))
7
+ end
8
+
9
+
10
+ Given "the application has a model, observer, route, and application helper" do
11
+ Given 'a file named "app/models/user.rb" with:',
12
+ """
13
+ class User < ActiveRecord::Base
14
+ $loaded_stuff << 'User'
15
+ end
16
+ """
17
+ Given 'a file named "app/models/user_observer.rb" with:',
18
+ """
19
+ class UserObserver < ActiveRecord::Observer
20
+ $loaded_stuff << 'UserObserver'
21
+ end
22
+ """
23
+ Given 'a file named "app/helpers/application_helper.rb" with:',
24
+ """
25
+ module ApplicationHelper
26
+ $loaded_stuff << 'ApplicationHelper'
27
+ end
28
+ """
29
+ Given 'the following code appears in "config/environment.rb" after /Rails::Initializer.run/:',
30
+ """
31
+ config.active_record.observers = :user_observer
32
+ """
33
+ Given 'the following code appears in "config/routes.rb" after /^end/:',
34
+ """
35
+ $loaded_stuff << 'config/routes.rb'
36
+ """
37
+ Given 'a file named "config/initializers/initialize_loaded_stuff.rb" with:',
38
+ """
39
+ $loaded_stuff ||= []
40
+ """
41
+ Given 'a file named "config/initializers/log_establish_connection_calls.rb" with:',
42
+ """
43
+ class ActiveRecord::Base
44
+ class << self
45
+ def establish_connection_with_load_logging(*args)
46
+ establish_connection_without_load_logging(*args)
47
+ $loaded_stuff << 'ActiveRecord::Base.establish_connection'
48
+ end
49
+ alias_method_chain :establish_connection, :load_logging
50
+ end
51
+ end
52
+ """
53
+ end
@@ -0,0 +1,91 @@
1
+ Given /^I am in the directory "(.*)"$/ do |sandbox_dir_relative_path|
2
+ path = File.join(SporkWorld::SANDBOX_DIR, sandbox_dir_relative_path)
3
+ FileUtils.mkdir_p(path)
4
+ @current_dir = File.join(path)
5
+ end
6
+
7
+ Given /^a file named "([^\"]*)"$/ do |file_name|
8
+ create_file(file_name, '')
9
+ end
10
+
11
+ Given /^a file named "([^\"]*)" with:$/ do |file_name, file_content|
12
+ create_file(file_name, file_content)
13
+ end
14
+
15
+ When /^the contents of "([^\"]*)" are changed to:$/ do |file_name, file_content|
16
+ create_file(file_name, file_content)
17
+ end
18
+
19
+ # the following code appears in "config/environment.rb" after /Rails::Initializer.run/:
20
+ Given /^the following code appears in "([^\"]*)" after \/([^\\\/]*)\/:$/ do |file_name, regex, content|
21
+ # require 'ruby-debug'; Debugger.start; Debugger.start_control; debugger
22
+
23
+ regex = Regexp.new(regex)
24
+ in_current_dir do
25
+ content_lines = File.read(file_name).split("\n")
26
+ 0.upto(content_lines.length - 1) do |line_index|
27
+ if regex.match(content_lines[line_index])
28
+ content_lines.insert(line_index + 1, content)
29
+ break
30
+ end
31
+ end
32
+ File.open(file_name, 'wb') { |f| f << (content_lines * "\n") }
33
+ end
34
+ end
35
+
36
+ When /^I run (spork|spec|cucumber)($| .*$)/ do |command, spork_opts|
37
+ if command == 'spork'
38
+ command = SporkWorld::BINARY
39
+ else
40
+ command = %x{which #{command}}.chomp
41
+ end
42
+ run "#{SporkWorld::RUBY_BINARY} #{command} #{spork_opts}"
43
+ end
44
+
45
+ When /^I fire up a spork instance with "spork(.*)"$/ do |spork_opts|
46
+ run_in_background "#{SporkWorld::RUBY_BINARY} #{SporkWorld::BINARY} #{spork_opts}"
47
+ output = ""
48
+ begin
49
+ status = Timeout::timeout(15) do
50
+ # Something that should be interrupted if it takes too much time...
51
+ while line = @bg_stderr.gets
52
+ output << line
53
+ puts line
54
+ break if line.include?("Spork is ready and listening")
55
+ end
56
+ end
57
+ rescue Timeout::Error
58
+ puts "I can't seem to launch Spork properly. Output was:\n#{output}"
59
+ true.should == false
60
+ end
61
+ end
62
+
63
+ Then /the file "([^\"]*)" should include "([^\"]*)"/ do |filename, content|
64
+ in_current_dir do
65
+ File.read(filename).should include(content)
66
+ end
67
+ end
68
+
69
+ Then /^the (error output|output) should contain$/ do |which, text|
70
+ (which == "error output" ? last_stderr : last_stdout).should include(text)
71
+ end
72
+
73
+ Then /^the (error output|output) should contain "(.+)"$/ do |which, text|
74
+ (which == "error output" ? last_stderr : last_stdout).should include(text)
75
+ end
76
+
77
+ Then /^the (error output|output) should match \/(.+)\/$/ do |which, regex|
78
+ (which == "error output" ? last_stderr : last_stdout).should match(Regexp.new(regex))
79
+ end
80
+
81
+ Then /^the (error output|output) should not contain$/ do |which, text|
82
+ (which == "error output" ? last_stderr : last_stdout).should_not include(text)
83
+ end
84
+
85
+ Then /^the (error output|output) should not contain "(.+)"$/ do |which, text|
86
+ (which == "error output" ? last_stderr : last_stdout).should_not include(text)
87
+ end
88
+
89
+ Then /^the (error output|output) should be$/ do |which, text|
90
+ (which == "error output" ? last_stderr : last_stdout).should == text
91
+ end
@@ -0,0 +1,97 @@
1
+ require 'rubygems'
2
+ require 'fileutils'
3
+ require 'forwardable'
4
+ require 'tempfile'
5
+ require 'spec/expectations'
6
+ require 'timeout'
7
+
8
+ class SporkWorld
9
+ RUBY_BINARY = File.join(Config::CONFIG['bindir'], Config::CONFIG['ruby_install_name'])
10
+ BINARY = File.expand_path(File.dirname(__FILE__) + '/../../bin/spork')
11
+ SANDBOX_DIR = File.expand_path(File.join(File.dirname(__FILE__), '../../tmp/sandbox'))
12
+
13
+ extend Forwardable
14
+ def_delegators SporkWorld, :sandbox_dir, :spork_lib_dir
15
+
16
+ def spork_lib_dir
17
+ @spork_lib_dir ||= File.expand_path(File.join(File.dirname(__FILE__), '../../lib'))
18
+ end
19
+
20
+ def initialize
21
+ @current_dir = SANDBOX_DIR
22
+ @background_jobs = []
23
+ end
24
+
25
+ private
26
+ attr_reader :last_exit_status, :last_stderr, :last_stdout, :background_jobs
27
+
28
+ def create_file(file_name, file_content)
29
+ file_content.gsub!("SPORK_LIB", "'#{spork_lib_dir}'") # Some files, such as Rakefiles need to use the lib dir
30
+ in_current_dir do
31
+ FileUtils.mkdir_p(File.dirname(file_name))
32
+ File.open(file_name, 'w') { |f| f << file_content }
33
+ end
34
+ end
35
+
36
+ def in_current_dir(&block)
37
+ Dir.chdir(@current_dir, &block)
38
+ end
39
+
40
+ def run(command)
41
+ stderr_file = Tempfile.new('spork')
42
+ stderr_file.close
43
+ in_current_dir do
44
+ @last_stdout = `#{command} 2> #{stderr_file.path}`
45
+ @last_exit_status = $?.exitstatus
46
+ end
47
+ @last_stderr = IO.read(stderr_file.path)
48
+ end
49
+
50
+ def run_in_background(command)
51
+ child_stdin, parent_stdin = IO::pipe
52
+ parent_stdout, child_stdout = IO::pipe
53
+ parent_stderr, child_stderr = IO::pipe
54
+
55
+ background_jobs << Kernel.fork do
56
+ [parent_stdin, parent_stdout, parent_stderr].each { |io| io.close }
57
+
58
+ STDIN.reopen(child_stdin)
59
+ STDOUT.reopen(child_stdout)
60
+ STDERR.reopen(child_stderr)
61
+
62
+ [child_stdin, child_stdout, child_stderr].each { |io| io.close }
63
+
64
+ in_current_dir do
65
+ exec command
66
+ end
67
+ end
68
+
69
+ [child_stdin, child_stdout, child_stderr].each { |io| io.close }
70
+ parent_stdin.sync = true
71
+
72
+ @bg_stdin, @bg_stdout, @bg_stderr = [parent_stdin, parent_stdout, parent_stderr]
73
+ end
74
+
75
+ def terminate_background_jobs
76
+ if @background_jobs
77
+ @background_jobs.each do |pid|
78
+ Process.kill(Signal.list['TERM'], pid)
79
+ end
80
+ end
81
+ end
82
+
83
+ end
84
+
85
+ World do
86
+ SporkWorld.new
87
+ end
88
+
89
+ Before do
90
+ FileUtils.rm_rf SporkWorld::SANDBOX_DIR
91
+ FileUtils.mkdir_p SporkWorld::SANDBOX_DIR
92
+ end
93
+
94
+ After do
95
+ # FileUtils.rm_rf SporkWorld::SANDBOX_DIR
96
+ terminate_background_jobs
97
+ end
@@ -0,0 +1,42 @@
1
+ Feature: Unknown app frameworks
2
+ To increase to usefulness of Spork
3
+ Spork will work with unknown (or no) application frameworks
4
+
5
+ Scenario: Unsporked spec_helper
6
+
7
+ Given a file named "spec/spec_helper.rb" with:
8
+ """
9
+ require 'rubygems'
10
+ require 'spec'
11
+ """
12
+ When I run spork
13
+ Then the error output should contain "Using RSpec"
14
+ Then the error output should match /You must bootstrap .+spec\/spec_helper\.rb to continue/
15
+
16
+ Scenario: Sporked spec_helper
17
+ Given a file named "spec/spec_helper.rb" with:
18
+ """
19
+ require 'rubygems'
20
+ require 'spork'
21
+
22
+ Spork.prefork do
23
+ require 'spec'
24
+ end
25
+
26
+ Spork.each_run do
27
+ $each_run
28
+ end
29
+ """
30
+ And a file named "spec/did_it_work_spec.rb" with:
31
+ """
32
+ describe "Did it work?" do
33
+ it "checks to see if all worked" do
34
+ Spork.state.should == :using_spork
35
+ puts "Specs successfully run within spork"
36
+ end
37
+ end
38
+ """
39
+ When I fire up a spork instance with "spork rspec"
40
+ And I run spec --drb spec/did_it_work_spec.rb
41
+ Then the output should contain "Specs successfully run within spork"
42
+
data/lib/spork.rb ADDED
@@ -0,0 +1,96 @@
1
+ $LOAD_PATH.unshift(File.expand_path(File.dirname(__FILE__))) unless $LOAD_PATH.include?(File.expand_path(File.dirname(__FILE__)))
2
+ module Spork
3
+ class << self
4
+ # Run a block, during prefork mode. By default, if prefork is called twice in the same file and line number, the supplied block will only be ran once.
5
+ #
6
+ # == Parameters
7
+ #
8
+ # * +prevent_double_run+ - Pass false to disable double run prevention
9
+ def prefork(prevent_double_run = true, &block)
10
+ return if prevent_double_run && already_ran?(caller.first)
11
+ yield
12
+ end
13
+
14
+ # Run a block AFTER the fork occurs. By default, if prefork is called twice in the same file and line number, the supplied block will only be ran once.
15
+ #
16
+ # == Parameters
17
+ #
18
+ # * +prevent_double_run+ - Pass false to disable double run prevention
19
+ def each_run(prevent_double_run = true, &block)
20
+ return if prevent_double_run && already_ran?(caller.first)
21
+ if @state == :using_spork
22
+ each_run_procs << block
23
+ else
24
+ yield
25
+ end
26
+ end
27
+
28
+ # Used by the server. Sets the state to activate spork. Otherwise, prefork and each_run are run in passive mode, allowing specs without a Spork server.
29
+ def using_spork!
30
+ @state = :using_spork
31
+ end
32
+
33
+ # Used by the server. Returns the current state of Spork.
34
+ def state
35
+ @state ||= :not_using_spork
36
+ end
37
+
38
+ # Used by the server. Called when loading the prefork blocks of the code.
39
+ def exec_prefork(&block)
40
+ using_spork!
41
+ yield
42
+ end
43
+
44
+ # Used by the server. Called to run all of the prefork blocks.
45
+ def exec_each_run(&block)
46
+ each_run_procs.each { |p| p.call }
47
+ each_run_procs.clear
48
+ yield if block_given?
49
+ end
50
+
51
+ # Traps an instance method of a class (or module) so any calls to it don't actually run until Spork.exec_each_run
52
+ def trap_method(klass, method_name)
53
+ method_name_without_spork, method_name_with_spork = alias_method_names(method_name, :spork)
54
+
55
+ klass.class_eval <<-EOF, __FILE__, __LINE__ + 1
56
+ alias :#{method_name_without_spork} :#{method_name} unless method_defined?(:#{method_name_without_spork})
57
+ def #{method_name}(*args)
58
+ Spork.each_run(false) do
59
+ #{method_name_without_spork}(*args)
60
+ end
61
+ end
62
+ EOF
63
+ end
64
+
65
+ # Same as trap_method, but for class methods instead
66
+ def trap_class_method(klass, method_name)
67
+ trap_method((class << klass; self; end), method_name)
68
+ end
69
+
70
+ private
71
+ def alias_method_names(method_name, feature)
72
+ /^(.+?)([\?\!]{0,1})$/.match(method_name.to_s)
73
+ ["#{$1}_without_spork#{$2}", "#{$1}_with_spork#{$2}"]
74
+ end
75
+
76
+ def already_ran
77
+ @already_ran ||= []
78
+ end
79
+
80
+ def expanded_caller(caller_line)
81
+ file, line = caller_line.split(":")
82
+ line.gsub(/:.+/, '')
83
+ File.expand_path(file, Dir.pwd) + ":" + line
84
+ end
85
+
86
+ def already_ran?(caller_script_and_line)
87
+ return true if already_ran.include?(expanded_caller(caller_script_and_line))
88
+ already_ran << expanded_caller(caller_script_and_line)
89
+ false
90
+ end
91
+
92
+ def each_run_procs
93
+ @each_run_procs ||= []
94
+ end
95
+ end
96
+ end
@@ -0,0 +1,74 @@
1
+ class Spork::AppFramework
2
+ # A hash of procs where the key is the class name, and the proc takes no arguments and returns true if it detects that said application framework is being used in the project.
3
+ #
4
+ # The key :Rails maps to Spork::AppFramework::Rails
5
+ #
6
+ # This is used to reduce the amount of code needed to be loaded - only the detected application framework's support code is loaded.
7
+ SUPPORTED_FRAMEWORKS = {
8
+ :Rails => lambda {
9
+ File.exist?("config/environment.rb") && File.read("config/environment.rb").include?('RAILS_GEM_VERSION')
10
+ }
11
+ }
12
+
13
+ def self.setup_autoload
14
+ ([:Unknown] + SUPPORTED_FRAMEWORKS.keys).each do |name|
15
+ autoload name, File.join(File.dirname(__FILE__), "app_framework", name.to_s.downcase)
16
+ end
17
+ end
18
+
19
+ # Iterates through all SUPPORTED_FRAMEWORKS and returns the symbolic name of the project application framework detected. Otherwise, returns :Unknown
20
+ def self.detect_framework_name
21
+ SUPPORTED_FRAMEWORKS.each do |key, value|
22
+ return key if value.call
23
+ end
24
+ :Unknown
25
+ end
26
+
27
+ # Same as detect_framework_name, but returns an instance of the specific AppFramework class.
28
+ def self.detect_framework
29
+ name = detect_framework_name
30
+ self[name]
31
+ end
32
+
33
+ # Initializes, stores, and returns a singleton instance of the named AppFramework.
34
+ #
35
+ # == Parameters
36
+ #
37
+ # # +name+ - A symbolic name of a AppFramework subclass
38
+ #
39
+ # == Example
40
+ #
41
+ # Spork::AppFramework[:Rails]
42
+ def self.[](name)
43
+ instances[name] ||= const_get(name).new
44
+ end
45
+
46
+ def self.short_name
47
+ name.gsub('Spork::AppFramework::', '')
48
+ end
49
+
50
+ # If there is some stuff out of the box that the Spork can do to speed up tests without the test helper file being bootstrapped, this should return false.
51
+ def bootstrap_required?
52
+ entry_point.nil?
53
+ end
54
+
55
+ # Abstract: The path to the file that loads the project environment, ie config/environment.rb. Returns nil if there is none.
56
+ def entry_point
57
+ raise NotImplemented
58
+ end
59
+
60
+ def preload(&block)
61
+ yield
62
+ end
63
+
64
+ def short_name
65
+ self.class.short_name
66
+ end
67
+
68
+ protected
69
+ def self.instances
70
+ @instances ||= {}
71
+ end
72
+ end
73
+
74
+ Spork::AppFramework.setup_autoload