spork 0.4.4 → 0.5.1

Sign up to get free protection for your applications and to get access to all the features.
@@ -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
@@ -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 already_preforked
5
- @already_preforked ||= []
4
+ def already_ran
5
+ @already_ran ||= []
6
6
  end
7
7
 
8
- def already_run
9
- @already_run ||= []
8
+ def each_run_procs
9
+ @each_run_procs ||= []
10
10
  end
11
11
 
12
12
  def prefork(&block)
13
- return if already_preforked.include?(expanded_caller(caller.first))
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 @state == :preforking || (@state != :not_using_spork && already_run.include?(expanded_caller(caller.first)))
20
- already_run << expanded_caller(caller.first)
21
- yield
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 preforking!
25
- @state = :preforking
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 running!
29
- @state = :running
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(helper_file)
37
- preforking!
38
- load(helper_file)
39
+
40
+ def exec_prefork(&block)
41
+ using_spork!
42
+ yield
39
43
  end
40
-
41
- def exec_each_run(helper_file)
42
- running!
43
- load(helper_file)
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