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.
- data/features/spork_debugger.feature +47 -0
- data/features/steps/sandbox_steps.rb +32 -13
- data/features/support/background_job.rb +63 -0
- data/features/support/env.rb +36 -23
- data/lib/spork/ext/ruby-debug.rb +142 -0
- metadata +6 -3
@@ -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)(
|
37
|
-
|
38
|
-
|
39
|
-
|
40
|
-
|
41
|
-
|
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
|
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 = @
|
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
|
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
|
data/features/support/env.rb
CHANGED
@@ -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
|
-
|
53
|
-
|
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
|
-
|
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 |
|
79
|
-
|
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.
|
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-
|
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.
|
99
|
+
rubygems_version: 1.3.5
|
97
100
|
signing_key:
|
98
101
|
specification_version: 3
|
99
102
|
summary: spork
|