spork 0.9.0.rc8-x86-mingw32

Sign up to get free protection for your applications and to get access to all the features.
Files changed (59) hide show
  1. data/Gemfile +10 -0
  2. data/MIT-LICENSE +20 -0
  3. data/README.rdoc +134 -0
  4. data/assets/bootstrap.rb +47 -0
  5. data/bin/spork +20 -0
  6. data/features/at_exit_during_each_run.feature +36 -0
  7. data/features/cucumber_rails_integration.feature +107 -0
  8. data/features/diagnostic_mode.feature +41 -0
  9. data/features/gemfiles/rails3.0/Gemfile +14 -0
  10. data/features/gemfiles/rails3.0/Gemfile.lock +120 -0
  11. data/features/rails_delayed_loading_workarounds.feature +177 -0
  12. data/features/rspec_rails_integration.feature +92 -0
  13. data/features/spork_debugger.feature +108 -0
  14. data/features/steps/general_steps.rb +3 -0
  15. data/features/steps/rails_steps.rb +67 -0
  16. data/features/steps/sandbox_steps.rb +115 -0
  17. data/features/support/background_job.rb +63 -0
  18. data/features/support/bundler_helpers.rb +41 -0
  19. data/features/support/env.rb +105 -0
  20. data/features/unknown_app_framework.feature +42 -0
  21. data/lib/spork.rb +155 -0
  22. data/lib/spork/app_framework.rb +80 -0
  23. data/lib/spork/app_framework/padrino.rb +22 -0
  24. data/lib/spork/app_framework/rails.rb +82 -0
  25. data/lib/spork/app_framework/unknown.rb +6 -0
  26. data/lib/spork/custom_io_streams.rb +25 -0
  27. data/lib/spork/diagnoser.rb +105 -0
  28. data/lib/spork/ext/rails-reloader.rb +14 -0
  29. data/lib/spork/ext/ruby-debug.rb +150 -0
  30. data/lib/spork/forker.rb +71 -0
  31. data/lib/spork/gem_helpers.rb +38 -0
  32. data/lib/spork/run_strategy.rb +48 -0
  33. data/lib/spork/run_strategy/forking.rb +35 -0
  34. data/lib/spork/run_strategy/magazine.rb +151 -0
  35. data/lib/spork/run_strategy/magazine/magazine_slave.rb +30 -0
  36. data/lib/spork/run_strategy/magazine/magazine_slave_provider.rb +30 -0
  37. data/lib/spork/run_strategy/magazine/ring_server.rb +10 -0
  38. data/lib/spork/runner.rb +90 -0
  39. data/lib/spork/server.rb +77 -0
  40. data/lib/spork/test_framework.rb +167 -0
  41. data/lib/spork/test_framework/cucumber.rb +38 -0
  42. data/lib/spork/test_framework/rspec.rb +14 -0
  43. data/spec/spec_helper.rb +113 -0
  44. data/spec/spork/app_framework/rails_spec.rb +22 -0
  45. data/spec/spork/app_framework/unknown_spec.rb +12 -0
  46. data/spec/spork/app_framework_spec.rb +16 -0
  47. data/spec/spork/diagnoser_spec.rb +105 -0
  48. data/spec/spork/forker_spec.rb +44 -0
  49. data/spec/spork/run_strategy/forking_spec.rb +38 -0
  50. data/spec/spork/runner_spec.rb +50 -0
  51. data/spec/spork/server_spec.rb +15 -0
  52. data/spec/spork/test_framework/cucumber_spec.rb +11 -0
  53. data/spec/spork/test_framework/rspec_spec.rb +10 -0
  54. data/spec/spork/test_framework_shared_examples.rb +23 -0
  55. data/spec/spork/test_framework_spec.rb +90 -0
  56. data/spec/spork_spec.rb +153 -0
  57. data/spec/support/fake_framework.rb +15 -0
  58. data/spec/support/fake_run_strategy.rb +21 -0
  59. metadata +173 -0
@@ -0,0 +1,115 @@
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
+ regex = Regexp.new(regex)
22
+ in_current_dir do
23
+ content_lines = File.read(file_name).split("\n")
24
+ 0.upto(content_lines.length - 1) do |line_index|
25
+ if regex.match(content_lines[line_index])
26
+ content_lines.insert(line_index + 1, content)
27
+ break
28
+ end
29
+ end
30
+ File.open(file_name, 'wb') { |f| f << (content_lines * "\n") }
31
+ end
32
+ end
33
+
34
+ When /^I run (spork|rspec|cucumber)(| .*)$/ do |command, args|
35
+ run("#{command} #{args}")
36
+ end
37
+
38
+ When /^I run this in the background: (spork|rspec|cucumber)(| .*)$/ do |command, args|
39
+ @background_script = run_in_background("#{command} #{args}")
40
+ end
41
+
42
+ When /^I fire up a spork instance with "spork(.*)"$/ do |spork_opts|
43
+ @spork_server = run_in_background("#{SporkWorld::RUBY_BINARY} -I #{Cucumber::LIBDIR} #{SporkWorld::BINARY} #{spork_opts}")
44
+
45
+ output = ""
46
+ begin
47
+ status = Timeout::timeout(15) do
48
+ # Something that should be interrupted if it takes too much time...
49
+ while line = @spork_server.stderr.gets
50
+ output << line
51
+ puts line
52
+ break if line.include?("Spork is ready and listening")
53
+ end
54
+ end
55
+ rescue Timeout::Error
56
+ puts "I can't seem to launch Spork properly. Output was:\n#{output}"
57
+ true.should == false
58
+ end
59
+ end
60
+
61
+ Then /^the spork window should output a line containing "(.+)"/ do |expected|
62
+ output = ""
63
+ begin
64
+ status = Timeout::timeout(5) do
65
+ # Something that should be interrupted if it takes too much time...
66
+ while line = @spork_server.stdout.gets
67
+ output << line
68
+ puts line
69
+ break if output.include?(expected)
70
+ end
71
+ end
72
+ rescue Timeout::Error
73
+ output.should include(expected)
74
+ end
75
+ end
76
+
77
+ When /^I type this in the spork window: "(.+)"/ do |line|
78
+ @spork_server.stdin.puts(line)
79
+ @spork_server.stdin.flush
80
+ end
81
+
82
+
83
+ Then /^the file "([^\"]*)" should include "([^\"]*)"$/ do |filename, content|
84
+ in_current_dir do
85
+ File.read(filename).should include(content)
86
+ end
87
+ end
88
+
89
+ Then /^the (error output|output) should contain$/ do |which, text|
90
+ (which == "error output" ? last_stderr : last_stdout).should include(text)
91
+ end
92
+
93
+ Then /^the (error output|output) should contain "(.+)"$/ do |which, text|
94
+ (which == "error output" ? last_stderr : last_stdout).should include(text)
95
+ end
96
+
97
+ Then /^the (error output|output) should match \/(.+)\/$/ do |which, regex|
98
+ (which == "error output" ? last_stderr : last_stdout).should match(Regexp.new(regex))
99
+ end
100
+
101
+ Then /^the (error output|output) should not contain$/ do |which, text|
102
+ (which == "error output" ? last_stderr : last_stdout).should_not include(text)
103
+ end
104
+
105
+ Then /^the (error output|output) should not contain "(.+)"$/ do |which, text|
106
+ (which == "error output" ? last_stderr : last_stdout).should_not include(text)
107
+ end
108
+
109
+ Then /^the (error output|output) should be empty$/ do |which|
110
+ (which == "error output" ? last_stderr : last_stdout).should == ""
111
+ end
112
+
113
+ Then /^the (error output|output) should be$/ do |which, text|
114
+ (which == "error output" ? last_stderr : last_stdout).should == text
115
+ end
@@ -0,0 +1,63 @@
1
+ class BackgroundJob
2
+ attr_reader :stdin, :stdout, :stderr, :pid
3
+ def initialize(pid, stdin, stdout, stderr)
4
+ @pid, @stdin, @stdout, @stderr = pid, stdin, stdout, stderr
5
+ ObjectSpace.define_finalizer(self) { kill }
6
+ end
7
+
8
+ def self.run(command)
9
+ command = sanitize_params(command) if command.is_a?(Array)
10
+ child_stdin, parent_stdin = IO::pipe
11
+ parent_stdout, child_stdout = IO::pipe
12
+ parent_stderr, child_stderr = IO::pipe
13
+
14
+ pid = Kernel.fork do
15
+ [parent_stdin, parent_stdout, parent_stderr].each { |io| io.close }
16
+
17
+ STDIN.reopen(child_stdin)
18
+ STDOUT.reopen(child_stdout)
19
+ STDERR.reopen(child_stderr)
20
+
21
+ [child_stdin, child_stdout, child_stderr].each { |io| io.close }
22
+
23
+ exec command
24
+ end
25
+
26
+ [child_stdin, child_stdout, child_stderr].each { |io| io.close }
27
+ parent_stdin.sync = true
28
+
29
+ new(pid, parent_stdin, parent_stdout, parent_stderr)
30
+ end
31
+
32
+ def self.sanitize_params(params)
33
+ params.map { |p| p.gsub(' ', '\ ') }.join(" ")
34
+ end
35
+
36
+ def kill(signal = 'TERM')
37
+ if running?
38
+ Process.kill(Signal.list[signal], @pid)
39
+ true
40
+ end
41
+ end
42
+
43
+ def interrupt
44
+ kill('INT')
45
+ end
46
+
47
+ def running?
48
+ return false unless @pid
49
+ Process.getpgid(@pid)
50
+ true
51
+ rescue Errno::ESRCH
52
+ false
53
+ end
54
+
55
+ def wait(timeout = 1000)
56
+ Timeout.timeout(timeout) do
57
+ Process.wait(@pid)
58
+ end
59
+ true
60
+ rescue Timeout::Error
61
+ false
62
+ end
63
+ end
@@ -0,0 +1,41 @@
1
+ require 'bundler'
2
+ module BundlerHelpers
3
+ extend self
4
+ def install_bundle(dir)
5
+ Dir.chdir(dir) do
6
+ command = "env RUBYOPT= BUNDLE_GEMFILE=Gemfile bundle install"
7
+ system(command)
8
+ $?.exitstatus
9
+ end
10
+ end
11
+
12
+ def ensure_installed(dir)
13
+ gemfile_lock = dir + "/Gemfile.lock"
14
+ gemfile = dir + "/Gemfile"
15
+ bundle_environment = dir + "/.bundle/environment.rb"
16
+ case
17
+ when File.exist?(gemfile_lock) && File.mtime(gemfile) > File.mtime(gemfile_lock)
18
+ puts "Gemfile #{gemfile} has changed since it was locked. Re-locking..."
19
+ FileUtils.rm(gemfile_lock)
20
+ FileUtils.rm_rf(dir + "/.bundle")
21
+ when ! File.exist?(bundle_environment)
22
+ puts "Bundle #{gemfile} not installed. Installing..."
23
+ when File.mtime(bundle_environment) < File.mtime(gemfile_lock)
24
+ puts "#{gemfile_lock} is newer than #{bundle_environment}. Reinstalling"
25
+ else
26
+ return false
27
+ end
28
+ install_bundle(dir)
29
+ end
30
+
31
+ def expand_gemfile(gemfile)
32
+ possibilities = [File.expand_path(gemfile, Dir.pwd), SporkWorld::GEMFILES_ROOT + gemfile + "Gemfile"]
33
+ possibilities.detect {|f| File.exist?(f)} || raise(RuntimeError, %(Gemfile not found:\n #{possibilities * "\n"}))
34
+ end
35
+
36
+ def set_gemfile(gemfile)
37
+ gemfile = expand_gemfile(gemfile || "rails3.0")
38
+ ensure_installed(File.dirname(gemfile))
39
+ ENV["BUNDLE_GEMFILE"] = gemfile.to_s
40
+ end
41
+ end
@@ -0,0 +1,105 @@
1
+ require 'rubygems'
2
+ require 'pathname'
3
+ require 'fileutils'
4
+ require 'forwardable'
5
+ require 'tempfile'
6
+ require 'rspec/expectations'
7
+ require 'timeout'
8
+
9
+ require(File.dirname(__FILE__) + '/background_job.rb')
10
+
11
+ SPORK_ROOT = Pathname.new(File.expand_path('../../', File.dirname(__FILE__)))
12
+ class SporkWorld
13
+ RUBY_BINARY = File.join(Config::CONFIG['bindir'], Config::CONFIG['ruby_install_name'])
14
+ BINARY = SPORK_ROOT + 'bin/spork'
15
+ SANDBOX_DIR = SPORK_ROOT + "tmp/sandbox"
16
+ GEMFILES_ROOT = SPORK_ROOT + "features/gemfiles"
17
+ SPORK_LIBDIR = SPORK_ROOT + "lib"
18
+
19
+ extend Forwardable
20
+ def_delegators SporkWorld, :sandbox_dir, :spork_lib_dir
21
+
22
+ def spork_lib_dir
23
+ @spork_lib_dir ||= File.expand_path(File.join(File.dirname(__FILE__), '../../lib'))
24
+ end
25
+
26
+ def initialize
27
+ @current_dir = SANDBOX_DIR
28
+ @background_jobs = []
29
+ end
30
+
31
+ private
32
+ attr_reader :last_exit_status, :last_stderr, :last_stdout, :background_jobs
33
+ def last_stderr
34
+ return @last_stderr if @last_stderr
35
+ if @background_job
36
+ @last_stderr = @background_job.stderr.read
37
+ end
38
+ end
39
+
40
+
41
+ def last_stdout
42
+ return @last_stdout if @last_stdout
43
+ if @background_job
44
+ @last_stdout = @background_job.stdout.read
45
+ end
46
+ end
47
+
48
+ def create_file(file_name, file_content)
49
+ file_content.gsub!("SPORK_LIB", "'#{spork_lib_dir}'") # Some files, such as Rakefiles need to use the lib dir
50
+ in_current_dir do
51
+ FileUtils.mkdir_p(File.dirname(file_name))
52
+ File.open(file_name, 'w') { |f| f << file_content }
53
+ end
54
+ end
55
+
56
+ def in_current_dir(&block)
57
+ Dir.chdir(@current_dir, &block)
58
+ end
59
+
60
+ def run(command)
61
+ stderr_file = Tempfile.new('spork')
62
+ stderr_file.close
63
+ in_current_dir do
64
+ @last_stdout = `env RUBYOPT= bundle exec #{command} 2> #{stderr_file.path}`
65
+ @last_exit_status = $?.exitstatus
66
+ end
67
+ @last_stderr = IO.read(stderr_file.path)
68
+ end
69
+
70
+ def run_in_background(command)
71
+ in_current_dir do
72
+ @background_job = BackgroundJob.run("env RUBYOPT= bundle exec " + command)
73
+ end
74
+ @background_jobs << @background_job
75
+ @background_job
76
+ end
77
+
78
+ def terminate_background_jobs
79
+ if @background_jobs
80
+ @background_jobs.each do |background_job|
81
+ background_job.kill
82
+ end
83
+ end
84
+ @background_jobs.clear
85
+ @background_job = nil
86
+ end
87
+ end
88
+
89
+ require(SPORK_ROOT + "features/support/bundler_helpers.rb")
90
+ BundlerHelpers.set_gemfile(ENV["GEMFILE"])
91
+
92
+
93
+ World do
94
+ SporkWorld.new
95
+ end
96
+
97
+ Before do
98
+ FileUtils.rm_rf SporkWorld::SANDBOX_DIR
99
+ FileUtils.mkdir_p SporkWorld::SANDBOX_DIR
100
+ end
101
+
102
+ After do
103
+ # FileUtils.rm_rf SporkWorld::SANDBOX_DIR
104
+ terminate_background_jobs
105
+ 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,155 @@
1
+ $LOAD_PATH.unshift(File.expand_path(File.dirname(__FILE__))) unless $LOAD_PATH.include?(File.expand_path(File.dirname(__FILE__)))
2
+ require 'pathname'
3
+ module Spork
4
+ BINARY = File.expand_path(File.dirname(__FILE__) + '/../bin/spork')
5
+ LIBDIR = Pathname.new(File.expand_path(File.dirname(__FILE__)))
6
+
7
+ autoload :Server, (LIBDIR + 'spork/server').to_s
8
+ autoload :TestFramework, (LIBDIR + 'spork/test_framework').to_s
9
+ autoload :AppFramework, (LIBDIR + 'spork/app_framework').to_s
10
+ autoload :RunStrategy, (LIBDIR + 'spork/run_strategy').to_s
11
+ autoload :Runner, (LIBDIR + 'spork/runner').to_s
12
+ autoload :Forker, (LIBDIR + 'spork/forker').to_s
13
+ autoload :Diagnoser, (LIBDIR + 'spork/diagnoser').to_s
14
+ autoload :GemHelpers, (LIBDIR + 'spork/gem_helpers').to_s
15
+
16
+ class << self
17
+ # 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.
18
+ #
19
+ # == Parameters
20
+ #
21
+ # * +prevent_double_run+ - Pass false to disable double run prevention
22
+ def prefork(prevent_double_run = true, &block)
23
+ return if prevent_double_run && already_ran?(caller.first)
24
+ yield
25
+ end
26
+
27
+ # 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.
28
+ #
29
+ # == Parameters
30
+ #
31
+ # * +prevent_double_run+ - Pass false to disable double run prevention
32
+ def each_run(prevent_double_run = true, &block)
33
+ return if prevent_double_run && already_ran?(caller.first)
34
+ if state == :prefork
35
+ each_run_procs << block
36
+ else
37
+ yield
38
+ end
39
+ end
40
+
41
+ # Run a block after specs are run.
42
+ #
43
+ # == Parameters
44
+ #
45
+ # * +prevent_double_run+ - Pass false to disable double run prevention
46
+ def after_each_run(prevent_double_run = true, &block)
47
+ return if prevent_double_run && already_ran?(caller.first)
48
+ after_each_run_procs << block
49
+ end
50
+
51
+ def using_spork?
52
+ state != :not_using_spork
53
+ end
54
+
55
+ def state
56
+ @state ||= :not_using_spork
57
+ end
58
+
59
+ # Used by the server. Called when loading the prefork blocks of the code.
60
+ def exec_prefork(&block)
61
+ @state = :prefork
62
+ yield
63
+ end
64
+
65
+ # Used by the server. Called to run all of the prefork blocks.
66
+ def exec_each_run(&block)
67
+ @state = :run
68
+ activate_after_each_run_at_exit_hook
69
+ each_run_procs.each { |p| p.call }
70
+ each_run_procs.clear
71
+ yield if block_given?
72
+ end
73
+
74
+ # Used by the server. Called to run all of the after_each_run blocks.
75
+ def exec_after_each_run
76
+ # processes in reverse order similar to at_exit
77
+ while p = after_each_run_procs.pop; p.call; end
78
+ true
79
+ end
80
+
81
+ # Traps an instance method of a class (or module) so any calls to it don't actually run until Spork.exec_each_run
82
+ def trap_method(klass, method_name)
83
+ method_name_without_spork, method_name_with_spork = alias_method_names(method_name, :spork)
84
+
85
+ klass.class_eval <<-EOF, __FILE__, __LINE__ + 1
86
+ alias :#{method_name_without_spork} :#{method_name} unless method_defined?(:#{method_name_without_spork})
87
+ def #{method_name}(*args, &block)
88
+ Spork.each_run(false) do
89
+ #{method_name_without_spork}(*args, &block)
90
+ end
91
+ end
92
+ EOF
93
+ end
94
+
95
+ # Same as trap_method, but for class methods instead
96
+ def trap_class_method(klass, method_name)
97
+ trap_method((class << klass; self; end), method_name)
98
+ end
99
+
100
+ def detect_and_require(subfolder)
101
+ ([LIBDIR.to_s] + other_spork_gem_load_paths).uniq.each do |gem_path|
102
+ Dir.glob(File.join(gem_path, subfolder)).each { |file| require file }
103
+ end
104
+ end
105
+
106
+ # This method is used to auto-discover peer plugins such as spork-testunit.
107
+ def other_spork_gem_load_paths
108
+ @other_spork_gem_load_paths ||= Spork::GemHelpers.latest_load_paths.grep(/spork/).select do |g|
109
+ not g.match(%r{/spork-[0-9\-.]+/lib}) # don't include other versions of spork
110
+ end
111
+ end
112
+
113
+ private
114
+ def activate_after_each_run_at_exit_hook
115
+ Kernel.module_eval do
116
+ def at_exit(&block)
117
+ Spork.after_each_run(false, &block)
118
+ end
119
+ end
120
+ end
121
+
122
+ def alias_method_names(method_name, feature)
123
+ /^(.+?)([\?\!]{0,1})$/.match(method_name.to_s)
124
+ ["#{$1}_without_spork#{$2}", "#{$1}_with_spork#{$2}"]
125
+ end
126
+
127
+ def already_ran
128
+ @already_ran ||= []
129
+ end
130
+
131
+ def expanded_caller(caller_line)
132
+ file, line = caller_line.split(/:(\d+)/)
133
+ line.gsub(/:.+/, '')
134
+ expanded = File.expand_path(file, Dir.pwd) + ":" + line
135
+ if ENV['OS'] == 'Windows_NT' # windows
136
+ expanded = expanded[2..-1]
137
+ end
138
+ expanded
139
+ end
140
+
141
+ def already_ran?(caller_script_and_line)
142
+ return true if already_ran.include?(expanded_caller(caller_script_and_line))
143
+ already_ran << expanded_caller(caller_script_and_line)
144
+ false
145
+ end
146
+
147
+ def each_run_procs
148
+ @each_run_procs ||= []
149
+ end
150
+
151
+ def after_each_run_procs
152
+ @after_each_run_procs ||= []
153
+ end
154
+ end
155
+ end