spork 0.6.3 → 0.7.0

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.
@@ -0,0 +1,47 @@
1
+ Feature: Spork Debugger integration
2
+ As a developer
3
+ I want to invoke the debugger my specs within Spork
4
+ In order to drill in and figure out what's wrong
5
+
6
+ Background: Rails App with RSpec and Spork
7
+
8
+ Given a file named "spec/spec_helper.rb" with:
9
+ """
10
+ require 'rubygems'
11
+ require 'spork'
12
+ require 'spork/ext/ruby-debug'
13
+
14
+ Spork.prefork do
15
+ require 'spec'
16
+ end
17
+
18
+ Spork.each_run do
19
+ end
20
+ """
21
+
22
+ Scenario: Invoking the debugger via 'debugger'
23
+ Given a file named "spec/debugger_spec.rb" with:
24
+ """
25
+ require File.dirname(__FILE__) + '/spec_helper.rb'
26
+
27
+ describe "Debugger" do
28
+ it "should debug" do
29
+ @variable = "variable contents"
30
+ debugger
31
+ puts "it worked!"
32
+ end
33
+ end
34
+ """
35
+
36
+ When I fire up a spork instance with "spork rspec"
37
+ And I run this in the background: spec --drb spec/debugger_spec.rb
38
+
39
+ Then the spork window should output a line containing "Debug Session Started"
40
+
41
+ When I type this in the spork window: "e @variable"
42
+ Then the spork window should output a line containing "variable contents"
43
+
44
+ When I type this in the spork window: "continue"
45
+
46
+ Then the spork window should output a line containing "Debug Session Terminated"
47
+ And the output should contain "it worked!"
@@ -33,25 +33,22 @@ Given /^the following code appears in "([^\"]*)" after \/([^\\\/]*)\/:$/ do |fil
33
33
  end
34
34
  end
35
35
 
36
- When /^I run (spork|spec|cucumber)($| .*$)/ do |command, spork_opts|
37
- case command
38
- when 'spork'
39
- command = SporkWorld::BINARY
40
- when 'cucumber'
41
- command = Cucumber::BINARY
42
- else
43
- command = %x{which #{command}}.chomp
44
- end
45
- run "#{SporkWorld::RUBY_BINARY} -I #{Cucumber::LIBDIR} #{command} #{spork_opts}"
36
+ When /^I run (spork|spec|cucumber)(| .*)$/ do |command, args|
37
+ run(localized_command(command, args))
38
+ end
39
+
40
+ When /^I run this in the background: (spork|spec|cucumber)(| .*)$/ do |command, args|
41
+ @background_script = run_in_background(localized_command(command, args))
46
42
  end
47
43
 
48
44
  When /^I fire up a spork instance with "spork(.*)"$/ do |spork_opts|
49
- run_in_background "#{SporkWorld::RUBY_BINARY} -I #{Cucumber::LIBDIR} #{SporkWorld::BINARY} #{spork_opts}"
45
+ @spork_server = run_in_background("#{SporkWorld::RUBY_BINARY} -I #{Cucumber::LIBDIR} #{SporkWorld::BINARY} #{spork_opts}")
46
+
50
47
  output = ""
51
48
  begin
52
49
  status = Timeout::timeout(15) do
53
50
  # Something that should be interrupted if it takes too much time...
54
- while line = @bg_stderr.gets
51
+ while line = @spork_server.stderr.gets
55
52
  output << line
56
53
  puts line
57
54
  break if line.include?("Spork is ready and listening")
@@ -63,7 +60,29 @@ When /^I fire up a spork instance with "spork(.*)"$/ do |spork_opts|
63
60
  end
64
61
  end
65
62
 
66
- Then /the file "([^\"]*)" should include "([^\"]*)"/ do |filename, content|
63
+ Then /^the spork window should output a line containing "(.+)"/ do |expected|
64
+ output = ""
65
+ begin
66
+ status = Timeout::timeout(5) do
67
+ # Something that should be interrupted if it takes too much time...
68
+ while line = @spork_server.stdout.gets
69
+ output << line
70
+ puts line
71
+ break if output.include?(expected)
72
+ end
73
+ end
74
+ rescue Timeout::Error
75
+ output.should include(expected)
76
+ end
77
+ end
78
+
79
+ When /^I type this in the spork window: "(.+)"/ do |line|
80
+ @spork_server.stdin.puts(line)
81
+ @spork_server.stdin.flush
82
+ end
83
+
84
+
85
+ Then /^the file "([^\"]*)" should include "([^\"]*)"$/ do |filename, content|
67
86
  in_current_dir do
68
87
  File.read(filename).should include(content)
69
88
  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
@@ -6,6 +6,8 @@ require 'spec/expectations'
6
6
  require 'timeout'
7
7
  require 'spork'
8
8
 
9
+ require(File.dirname(__FILE__) + '/background_job.rb')
10
+
9
11
  class SporkWorld
10
12
  BINARY = File.expand_path(File.dirname(__FILE__) + '/../../bin/spork')
11
13
  RUBY_BINARY = File.join(Config::CONFIG['bindir'], Config::CONFIG['ruby_install_name'])
@@ -25,6 +27,20 @@ class SporkWorld
25
27
 
26
28
  private
27
29
  attr_reader :last_exit_status, :last_stderr, :last_stdout, :background_jobs
30
+ def last_stderr
31
+ return @last_stderr if @last_stderr
32
+ if @background_job
33
+ @last_stderr = @background_job.stderr.read
34
+ end
35
+ end
36
+
37
+
38
+ def last_stdout
39
+ return @last_stdout if @last_stdout
40
+ if @background_job
41
+ @last_stdout = @background_job.stdout.read
42
+ end
43
+ end
28
44
 
29
45
  def create_file(file_name, file_content)
30
46
  file_content.gsub!("SPORK_LIB", "'#{spork_lib_dir}'") # Some files, such as Rakefiles need to use the lib dir
@@ -38,6 +54,18 @@ class SporkWorld
38
54
  Dir.chdir(@current_dir, &block)
39
55
  end
40
56
 
57
+ def localized_command(command, args)
58
+ case command
59
+ when 'spork'
60
+ command = SporkWorld::BINARY
61
+ when 'cucumber'
62
+ command = Cucumber::BINARY
63
+ else
64
+ command = %x{which #{command}}.chomp
65
+ end
66
+ "#{SporkWorld::RUBY_BINARY} -I #{Cucumber::LIBDIR} #{command} #{args}"
67
+ end
68
+
41
69
  def run(command)
42
70
  stderr_file = Tempfile.new('spork')
43
71
  stderr_file.close
@@ -49,36 +77,21 @@ class SporkWorld
49
77
  end
50
78
 
51
79
  def run_in_background(command)
52
- child_stdin, parent_stdin = IO::pipe
53
- parent_stdout, child_stdout = IO::pipe
54
- parent_stderr, child_stderr = IO::pipe
55
-
56
- background_jobs << Kernel.fork do
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
80
+ in_current_dir do
81
+ @background_job = BackgroundJob.run(command)
68
82
  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]
83
+ @background_jobs << @background_job
84
+ @background_job
74
85
  end
75
86
 
76
87
  def terminate_background_jobs
77
88
  if @background_jobs
78
- @background_jobs.each do |pid|
79
- Process.kill(Signal.list['TERM'], pid)
89
+ @background_jobs.each do |background_job|
90
+ background_job.kill
80
91
  end
81
92
  end
93
+ @background_jobs.clear
94
+ @background_job = nil
82
95
  end
83
96
 
84
97
  end
@@ -0,0 +1,142 @@
1
+ require 'ruby-debug'
2
+ require 'socket'
3
+ require 'forwardable'
4
+
5
+ # Experimental! No automated tests are checking this, use at your own risk!
6
+
7
+ class SporkDebugger
8
+ DEFAULT_PORT = 10_123
9
+ HOST = '127.0.0.1'
10
+
11
+ extend Forwardable
12
+ def_delegators :state, *[:prepare_debugger, :initialize]
13
+ attr_reader :state
14
+
15
+ class << self
16
+ attr_reader :instance
17
+ def run
18
+ @instance = new
19
+ end
20
+ end
21
+
22
+ def initialize
23
+ @state = SporkDebugger::PreloadState.new
24
+ Spork.send(:each_run_procs).unshift(lambda do
25
+ @state = @state.transition_to_each_run_state
26
+ end)
27
+ end
28
+
29
+ module NetworkHelpers
30
+ def find_port(starting_with)
31
+ port = starting_with
32
+ begin
33
+ server = TCPServer.new(HOST, port)
34
+ server.close
35
+ rescue Errno::EADDRINUSE
36
+ port += 1
37
+ retry
38
+ end
39
+
40
+ port
41
+ end
42
+ end
43
+
44
+ class PreloadState
45
+ include NetworkHelpers
46
+ def initialize
47
+ install_hook
48
+ listen_for_connection_signals
49
+ end
50
+
51
+ def finish
52
+ @tcp_service.close; @tcp_service = nil;
53
+ end
54
+
55
+ def transition_to_each_run_state
56
+ finish
57
+ SporkDebugger::EachRunState.new(@port)
58
+ end
59
+
60
+ protected
61
+ def install_hook
62
+ Kernel.class_eval do
63
+ alias :debugger_without_spork_hook :debugger
64
+ def debugger(steps = 1)
65
+ SporkDebugger.instance.prepare_debugger
66
+ debugger_without_spork_hook
67
+ end
68
+ end
69
+ end
70
+
71
+ def listen_for_connection_signals
72
+ @port = SporkDebugger::DEFAULT_PORT
73
+ begin
74
+ @tcp_service = TCPServer.new(SporkDebugger::HOST, @port)
75
+ rescue Errno::EADDRINUSE
76
+ @port += 1
77
+ retry
78
+ end
79
+ Thread.new { main_loop }
80
+ end
81
+
82
+ def main_loop
83
+ while @tcp_service do
84
+ socket = @tcp_service.accept
85
+ port = Marshal.load(socket)
86
+ Marshal.dump(true, socket)
87
+ connect_rdebug_client(port)
88
+ socket.close
89
+ end
90
+ rescue => e
91
+ puts "#{$$} - error: #{e}"
92
+ end
93
+
94
+ def connect_rdebug_client(port = Debugger::PORT)
95
+ puts "\n\n - Debug Session Started - \n\n\n"
96
+ Debugger.start_client(SporkDebugger::HOST, port)
97
+ puts "\n\n - Debug Session Terminated - \n\n\n"
98
+ end
99
+ end
100
+
101
+ class EachRunState
102
+ include NetworkHelpers
103
+ def initialize(connection_request_port)
104
+ @connection_request_port = connection_request_port
105
+ end
106
+
107
+ def prepare_debugger
108
+ port, cport = start_rdebug_server
109
+ signal_spork_server_to_connect_to_rdebug_server(port)
110
+ wait_for_connection
111
+ puts "\n\n - breakpoint - see your spork server for the debug terminal - \n\n"
112
+ end
113
+
114
+ def signal_spork_server_to_connect_to_rdebug_server(rdebug_server_port)
115
+ socket = TCPSocket.new(SporkDebugger::HOST, @connection_request_port)
116
+ Marshal.dump(rdebug_server_port, socket)
117
+ response = Marshal.load(socket)
118
+ socket.close
119
+ end
120
+
121
+ def start_rdebug_server
122
+ Debugger.stop if Debugger.started?
123
+ port = find_port(Debugger::PORT)
124
+ cport = find_port(port + 1)
125
+ Debugger.start_remote(SporkDebugger::HOST, [port, cport]) do
126
+ Debugger.run_init_script(StringIO.new)
127
+ end
128
+ Debugger.start
129
+ [port, cport]
130
+ end
131
+
132
+ protected
133
+ def wait_for_connection
134
+ while Debugger.handler.interface.nil?; sleep 0.10; end
135
+ end
136
+ end
137
+ end
138
+
139
+ Spork.prefork do
140
+ SporkDebugger.run
141
+ end
142
+
metadata CHANGED
@@ -1,7 +1,7 @@
1
1
  --- !ruby/object:Gem::Specification
2
2
  name: spork
3
3
  version: !ruby/object:Gem::Version
4
- version: 0.6.3
4
+ version: 0.7.0
5
5
  platform: ruby
6
6
  authors:
7
7
  - Tim Harper
@@ -9,7 +9,7 @@ autorequire:
9
9
  bindir: bin
10
10
  cert_chain: []
11
11
 
12
- date: 2009-09-17 00:00:00 -06:00
12
+ date: 2009-09-23 00:00:00 -06:00
13
13
  default_executable: spork
14
14
  dependencies: []
15
15
 
@@ -31,9 +31,11 @@ files:
31
31
  - features/diagnostic_mode.feature
32
32
  - features/rails_delayed_loading_workarounds.feature
33
33
  - features/rspec_rails_integration.feature
34
+ - features/spork_debugger.feature
34
35
  - features/steps/general_steps.rb
35
36
  - features/steps/rails_steps.rb
36
37
  - features/steps/sandbox_steps.rb
38
+ - features/support/background_job.rb
37
39
  - features/support/env.rb
38
40
  - features/unknown_app_framework.feature
39
41
  - lib/spork.rb
@@ -45,6 +47,7 @@ files:
45
47
  - lib/spork/app_framework/unknown.rb
46
48
  - lib/spork/custom_io_streams.rb
47
49
  - lib/spork/diagnoser.rb
50
+ - lib/spork/ext/ruby-debug.rb
48
51
  - lib/spork/forker.rb
49
52
  - lib/spork/run_strategy.rb
50
53
  - lib/spork/run_strategy/forking.rb
@@ -93,7 +96,7 @@ required_rubygems_version: !ruby/object:Gem::Requirement
93
96
  requirements: []
94
97
 
95
98
  rubyforge_project: spork
96
- rubygems_version: 1.3.4
99
+ rubygems_version: 1.3.5
97
100
  signing_key:
98
101
  specification_version: 3
99
102
  summary: spork