spork 0.4.4 → 0.5.1
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/features/diagnostic_mode.feature +37 -0
- data/features/rails_integration.feature +81 -0
- data/features/steps/rails_steps.rb +7 -0
- data/features/steps/sandbox_steps.rb +78 -0
- data/features/support/env.rb +98 -0
- data/lib/spork.rb +54 -25
- data/lib/spork/app_framework.rb +46 -0
- data/lib/spork/app_framework/rails.rb +109 -0
- data/lib/spork/app_framework/rails_stub_files/application.rb +2 -0
- data/lib/spork/app_framework/rails_stub_files/application_controller.rb +2 -0
- data/lib/spork/app_framework/rails_stub_files/application_helper.rb +2 -0
- data/lib/spork/app_framework/unknown.rb +6 -0
- data/lib/spork/custom_io_streams.rb +23 -0
- data/lib/spork/diagnoser.rb +72 -0
- data/lib/spork/runner.rb +24 -9
- data/lib/spork/server.rb +37 -23
- data/spec/spec_helper.rb +95 -13
- data/spec/spork/app_framework/rails_spec.rb +24 -0
- data/spec/spork/app_framework_spec.rb +16 -0
- data/spec/spork/diagnoser_spec.rb +91 -0
- data/spec/spork/runner_spec.rb +2 -2
- data/spec/spork/server_spec.rb +8 -13
- data/spec/spork_spec.rb +18 -15
- metadata +21 -2
@@ -0,0 +1,37 @@
|
|
1
|
+
Feature: Diagnostic Mode
|
2
|
+
To help a developer quickly pinpoint why files are being loaded
|
3
|
+
Spork provides a diagnostic mode
|
4
|
+
That provides a list of which project files were loaded during prefork, and who loaded them.
|
5
|
+
|
6
|
+
Scenario: Running spork --diagnose
|
7
|
+
Given I am in the directory "test_project"
|
8
|
+
And a file named "spec/spec_helper.rb" with:
|
9
|
+
"""
|
10
|
+
require 'rubygems'
|
11
|
+
require 'spork'
|
12
|
+
|
13
|
+
Spork.prefork do
|
14
|
+
require 'lib/awesome.rb'
|
15
|
+
require '../external_dependency/super_duper.rb'
|
16
|
+
end
|
17
|
+
|
18
|
+
Spork.each_run do
|
19
|
+
puts "I'm loading the stuff just for this run..."
|
20
|
+
end
|
21
|
+
"""
|
22
|
+
And a file named "lib/awesome.rb" with:
|
23
|
+
"""
|
24
|
+
class Awesome
|
25
|
+
end
|
26
|
+
"""
|
27
|
+
And a file named "../external_dependency/super_duper.rb" with:
|
28
|
+
"""
|
29
|
+
class Awesome
|
30
|
+
end
|
31
|
+
"""
|
32
|
+
When I run spork --diagnose
|
33
|
+
Then the output should contain "lib/awesome.rb"
|
34
|
+
And the output should contain "spec/spec_helper.rb:5"
|
35
|
+
And the output should not contain "super_duper.rb"
|
36
|
+
And the output should not contain "diagnose.rb"
|
37
|
+
|
@@ -0,0 +1,81 @@
|
|
1
|
+
Feature: Rails Integration
|
2
|
+
To get a developer up and running quickly
|
3
|
+
Spork automatically integrates with rails
|
4
|
+
Providing default hooks and behaviors
|
5
|
+
|
6
|
+
Background: Rails App with RSpec and Spork
|
7
|
+
|
8
|
+
Given I am in a fresh rails project named "test_rails_project"
|
9
|
+
And a file named "spec/spec_helper.rb" with:
|
10
|
+
"""
|
11
|
+
require 'rubygems'
|
12
|
+
require 'spork'
|
13
|
+
require 'spec'
|
14
|
+
|
15
|
+
Spork.prefork do
|
16
|
+
$run_phase = :prefork
|
17
|
+
require File.dirname(__FILE__) + '/../config/environment.rb'
|
18
|
+
end
|
19
|
+
|
20
|
+
Spork.each_run do
|
21
|
+
$run_phase = :each_run
|
22
|
+
puts "I'm loading the stuff just for this run..."
|
23
|
+
end
|
24
|
+
|
25
|
+
class ActiveRecord::Base
|
26
|
+
class << self
|
27
|
+
def establish_connection
|
28
|
+
($loaded_stuff ||= []) << 'establish_connection'
|
29
|
+
puts "Database connection was automatically re-established!"
|
30
|
+
end
|
31
|
+
end
|
32
|
+
end
|
33
|
+
"""
|
34
|
+
And a file named "app/models/user.rb" with:
|
35
|
+
"""
|
36
|
+
class User < ActiveRecord::Base
|
37
|
+
($loaded_stuff ||= []) << 'User'
|
38
|
+
end
|
39
|
+
"""
|
40
|
+
And a file named "app/helpers/application_helper.rb" with:
|
41
|
+
"""
|
42
|
+
module ApplicationHelper
|
43
|
+
($loaded_stuff ||= []) << 'ApplicationHelper'
|
44
|
+
end
|
45
|
+
"""
|
46
|
+
And a file named "app/models/user_observer.rb" with:
|
47
|
+
"""
|
48
|
+
class UserObserver < ActiveRecord::Observer
|
49
|
+
($loaded_stuff ||= []) << 'UserObserver'
|
50
|
+
end
|
51
|
+
"""
|
52
|
+
And the following code appears in "config/environment.rb" after /Rails::Initializer.run/:
|
53
|
+
"""
|
54
|
+
config.active_record.observers = :user_observer
|
55
|
+
"""
|
56
|
+
And a file named "spec/models/user_spec.rb" with:
|
57
|
+
"""
|
58
|
+
describe User do
|
59
|
+
it "does absoluately nothing" do
|
60
|
+
Spork.state.should == :using_spork
|
61
|
+
$loaded_stuff.should include('establish_connection')
|
62
|
+
$loaded_stuff.should include('User')
|
63
|
+
$loaded_stuff.should include('UserObserver')
|
64
|
+
$loaded_stuff.should include('ApplicationHelper')
|
65
|
+
puts "Specs successfully run within spork, and all initialization files were loaded"
|
66
|
+
end
|
67
|
+
end
|
68
|
+
"""
|
69
|
+
Scenario: Analyzing files were preloaded
|
70
|
+
When I run spork --diagnose
|
71
|
+
Then the output should not contain "user_observer.rb"
|
72
|
+
Then the output should not contain "user.rb"
|
73
|
+
Then the output should not contain "app/controllers/application.rb"
|
74
|
+
Then the output should not contain "app/controllers/application_controller.rb"
|
75
|
+
Then the output should not contain "app/controllers/application_helper.rb"
|
76
|
+
|
77
|
+
Scenario: Running spork with a rails app and observers
|
78
|
+
|
79
|
+
When I fire up a spork instance with "spork rspec"
|
80
|
+
And I run spec --drb spec/models/user_spec.rb
|
81
|
+
Then the output should contain "Specs successfully run within spork, and all initialization files were loaded"
|
@@ -0,0 +1,7 @@
|
|
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
|
@@ -0,0 +1,78 @@
|
|
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
|
+
# the following code appears in "config/environment.rb" after /Rails::Initializer.run/:
|
16
|
+
Given /^the following code appears in "([^\"]*)" after \/([^\\\/]*)\/:$/ do |file_name, regex, content|
|
17
|
+
# require 'ruby-debug'; Debugger.start; Debugger.start_control; debugger
|
18
|
+
|
19
|
+
regex = Regexp.new(regex)
|
20
|
+
in_current_dir do
|
21
|
+
content_lines = File.read(file_name).split("\n")
|
22
|
+
0.upto(content_lines.length - 1) do |line_index|
|
23
|
+
if regex.match(content_lines[line_index])
|
24
|
+
puts "found: #{content_lines[line_index]}"
|
25
|
+
content_lines.insert(line_index + 1, content)
|
26
|
+
break
|
27
|
+
end
|
28
|
+
end
|
29
|
+
File.open(file_name, 'wb') { |f| f << (content_lines * "\n") }
|
30
|
+
end
|
31
|
+
end
|
32
|
+
|
33
|
+
When /^I run (spork|spec)($| .*$)/ do |command, spork_opts|
|
34
|
+
if command == 'spork'
|
35
|
+
command = SporkWorld::BINARY
|
36
|
+
else
|
37
|
+
command = %x{which #{command}}.chomp
|
38
|
+
end
|
39
|
+
run "#{SporkWorld::RUBY_BINARY} #{command} #{spork_opts}"
|
40
|
+
end
|
41
|
+
|
42
|
+
When /^I fire up a spork instance with "spork(.*)"$/ do |spork_opts|
|
43
|
+
run_in_background "#{SporkWorld::RUBY_BINARY} #{SporkWorld::BINARY} #{spork_opts}"
|
44
|
+
output = ""
|
45
|
+
begin
|
46
|
+
status = Timeout::timeout(15) do
|
47
|
+
# Something that should be interrupted if it takes too much time...
|
48
|
+
while line = @bg_stderr.gets
|
49
|
+
output << line
|
50
|
+
puts line
|
51
|
+
break if line.include?("Spork is ready and listening")
|
52
|
+
end
|
53
|
+
end
|
54
|
+
rescue Timeout::Error
|
55
|
+
puts "I can't seem to launch Spork properly. Output was:\n#{output}"
|
56
|
+
true.should == false
|
57
|
+
end
|
58
|
+
end
|
59
|
+
|
60
|
+
Then /^the output should contain$/ do |text|
|
61
|
+
last_stdout.should include(text)
|
62
|
+
end
|
63
|
+
|
64
|
+
Then /^the output should contain "(.+)"$/ do |text|
|
65
|
+
last_stdout.should include(text)
|
66
|
+
end
|
67
|
+
|
68
|
+
Then /^the output should not contain$/ do |text|
|
69
|
+
last_stdout.should_not include(text)
|
70
|
+
end
|
71
|
+
|
72
|
+
Then /^the output should not contain "(.+)"$/ do |text|
|
73
|
+
last_stdout.should_not include(text)
|
74
|
+
end
|
75
|
+
|
76
|
+
Then /^the output should be$/ do |text|
|
77
|
+
last_stdout.should == text
|
78
|
+
end
|
@@ -0,0 +1,98 @@
|
|
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
|
+
# grandchild
|
57
|
+
[parent_stdin, parent_stdout, parent_stderr].each { |io| io.close }
|
58
|
+
|
59
|
+
STDIN.reopen(child_stdin)
|
60
|
+
STDOUT.reopen(child_stdout)
|
61
|
+
STDERR.reopen(child_stderr)
|
62
|
+
|
63
|
+
[child_stdin, child_stdout, child_stderr].each { |io| io.close }
|
64
|
+
|
65
|
+
in_current_dir do
|
66
|
+
exec command
|
67
|
+
end
|
68
|
+
end
|
69
|
+
|
70
|
+
[child_stdin, child_stdout, child_stderr].each { |io| io.close }
|
71
|
+
parent_stdin.sync = true
|
72
|
+
|
73
|
+
@bg_stdin, @bg_stdout, @bg_stderr = [parent_stdin, parent_stdout, parent_stderr]
|
74
|
+
end
|
75
|
+
|
76
|
+
def terminate_background_jobs
|
77
|
+
if @background_jobs
|
78
|
+
@background_jobs.each do |pid|
|
79
|
+
Process.kill(Signal.list['TERM'], pid)
|
80
|
+
end
|
81
|
+
end
|
82
|
+
end
|
83
|
+
|
84
|
+
end
|
85
|
+
|
86
|
+
World do
|
87
|
+
SporkWorld.new
|
88
|
+
end
|
89
|
+
|
90
|
+
Before do
|
91
|
+
FileUtils.rm_rf SporkWorld::SANDBOX_DIR
|
92
|
+
FileUtils.mkdir_p SporkWorld::SANDBOX_DIR
|
93
|
+
end
|
94
|
+
|
95
|
+
After do
|
96
|
+
# FileUtils.rm_rf SporkWorld::SANDBOX_DIR
|
97
|
+
terminate_background_jobs
|
98
|
+
end
|
data/lib/spork.rb
CHANGED
@@ -1,46 +1,51 @@
|
|
1
1
|
$LOAD_PATH.unshift(File.expand_path(File.dirname(__FILE__))) unless $LOAD_PATH.include?(File.expand_path(File.dirname(__FILE__)))
|
2
2
|
module Spork
|
3
3
|
class << self
|
4
|
-
def
|
5
|
-
@
|
4
|
+
def already_ran
|
5
|
+
@already_ran ||= []
|
6
6
|
end
|
7
7
|
|
8
|
-
def
|
9
|
-
@
|
8
|
+
def each_run_procs
|
9
|
+
@each_run_procs ||= []
|
10
10
|
end
|
11
11
|
|
12
12
|
def prefork(&block)
|
13
|
-
return if
|
14
|
-
already_preforked << expanded_caller(caller.first)
|
13
|
+
return if already_ran?(caller.first)
|
15
14
|
yield
|
16
15
|
end
|
17
|
-
|
16
|
+
|
18
17
|
def each_run(&block)
|
19
|
-
return if
|
20
|
-
|
21
|
-
|
18
|
+
return if already_ran?(caller.first)
|
19
|
+
if @state == :using_spork
|
20
|
+
each_run_procs << block
|
21
|
+
else
|
22
|
+
yield
|
23
|
+
end
|
22
24
|
end
|
23
|
-
|
24
|
-
def
|
25
|
-
|
25
|
+
|
26
|
+
def already_ran?(caller_script_and_line)
|
27
|
+
return true if already_ran.include?(expanded_caller(caller_script_and_line))
|
28
|
+
already_ran << expanded_caller(caller_script_and_line)
|
29
|
+
false
|
26
30
|
end
|
27
|
-
|
28
|
-
def
|
29
|
-
@state = :
|
31
|
+
|
32
|
+
def using_spork!
|
33
|
+
@state = :using_spork
|
30
34
|
end
|
31
|
-
|
35
|
+
|
32
36
|
def state
|
33
37
|
@state ||= :not_using_spork
|
34
38
|
end
|
35
|
-
|
36
|
-
def exec_prefork(
|
37
|
-
|
38
|
-
|
39
|
+
|
40
|
+
def exec_prefork(&block)
|
41
|
+
using_spork!
|
42
|
+
yield
|
39
43
|
end
|
40
|
-
|
41
|
-
def exec_each_run(
|
42
|
-
|
43
|
-
|
44
|
+
|
45
|
+
def exec_each_run(&block)
|
46
|
+
each_run_procs.each { |p| p.call }
|
47
|
+
each_run_procs.clear
|
48
|
+
yield if block_given?
|
44
49
|
end
|
45
50
|
|
46
51
|
def expanded_caller(caller_line)
|
@@ -48,5 +53,29 @@ module Spork
|
|
48
53
|
line.gsub(/:.+/, '')
|
49
54
|
File.expand_path(Dir.pwd, file) + ":" + line
|
50
55
|
end
|
56
|
+
|
57
|
+
def trap_method(klass, method_name)
|
58
|
+
klass.class_eval <<-EOF, __FILE__, __LINE__ + 1
|
59
|
+
alias :#{method_name}_without_spork :#{method_name} unless method_defined?(:#{method_name}_without_spork)
|
60
|
+
def #{method_name}(*args)
|
61
|
+
Spork.each_run_procs << lambda do
|
62
|
+
#{method_name}_without_spork(*args)
|
63
|
+
end
|
64
|
+
end
|
65
|
+
EOF
|
66
|
+
end
|
67
|
+
|
68
|
+
def trap_class_method(klass, method_name)
|
69
|
+
klass.class_eval <<-EOF, __FILE__, __LINE__ + 1
|
70
|
+
class << self
|
71
|
+
alias :#{method_name}_without_spork :#{method_name} unless method_defined?(:#{method_name}_without_spork)
|
72
|
+
def #{method_name}(*args)
|
73
|
+
Spork.each_run_procs << lambda do
|
74
|
+
#{method_name}_without_spork(*args)
|
75
|
+
end
|
76
|
+
end
|
77
|
+
end
|
78
|
+
EOF
|
79
|
+
end
|
51
80
|
end
|
52
81
|
end
|
@@ -0,0 +1,46 @@
|
|
1
|
+
class Spork::AppFramework
|
2
|
+
SUPPORTED_FRAMEWORKS = {
|
3
|
+
:Rails => lambda do
|
4
|
+
File.exist?("config/environment.rb") && File.read("config/environment.rb").include?('RAILS_GEM_VERSION')
|
5
|
+
end
|
6
|
+
}
|
7
|
+
|
8
|
+
def self.detect_framework_name
|
9
|
+
SUPPORTED_FRAMEWORKS.each do |key, value|
|
10
|
+
return key if value.call
|
11
|
+
end
|
12
|
+
:Unknown
|
13
|
+
end
|
14
|
+
|
15
|
+
def self.detect_framework
|
16
|
+
name = detect_framework_name
|
17
|
+
self[name]
|
18
|
+
end
|
19
|
+
|
20
|
+
def self.[](name)
|
21
|
+
instances[name] ||= (
|
22
|
+
require File.join(File.dirname(__FILE__), "app_framework", name.to_s.downcase)
|
23
|
+
const_get(name).new
|
24
|
+
)
|
25
|
+
end
|
26
|
+
|
27
|
+
def self.instances
|
28
|
+
@instances ||= {}
|
29
|
+
end
|
30
|
+
|
31
|
+
def self.short_name
|
32
|
+
name.gsub('Spork::AppFramework::', '')
|
33
|
+
end
|
34
|
+
|
35
|
+
def bootstrap_required?
|
36
|
+
raise NotImplemented
|
37
|
+
end
|
38
|
+
|
39
|
+
def preload(&block)
|
40
|
+
yield
|
41
|
+
end
|
42
|
+
|
43
|
+
def name
|
44
|
+
self.class.short_name
|
45
|
+
end
|
46
|
+
end
|