spork 0.9.0.rc8-x86-mingw32

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.
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