spin 0.6.0 → 0.7.0

Sign up to get free protection for your applications and to get access to all the features.
data/README.md CHANGED
@@ -1,6 +1,8 @@
1
1
  Spin
2
2
  ====
3
3
 
4
+ [![Build Status](https://travis-ci.org/jstorimer/spin.png)](https://travis-ci.org/jstorimer/spin)
5
+
4
6
  Spin speeds up your Rails testing workflow.
5
7
 
6
8
  By preloading your Rails environment in one process and then using fork(2) for each test run you don't load the same code over and over and over...
@@ -133,4 +135,4 @@ If Spin isn't scratching your itch then one of these projects might:
133
135
  * [Spork](https://github.com/sporkrb/spork)
134
136
  * [TestR](https://github.com/sunaku/testr)
135
137
  * [Zeus](https://github.com/burke/zeus)
136
-
138
+ * [Spring](https://github.com/jonleighton/spring)
@@ -1,5 +1,7 @@
1
1
  require 'spin/version'
2
2
  require 'spin/hooks'
3
+ require 'spin/test_process'
4
+ require 'spin/logger'
3
5
  require 'socket'
4
6
  require 'tempfile' # Dir.tmpdir
5
7
  # This lets us hash the parameters we want to include in the filename
@@ -14,6 +16,9 @@ module Spin
14
16
  extend Spin::Hooks
15
17
 
16
18
  PUSH_FILE_SEPARATOR = '|'
19
+ ARGS_SEPARATOR = ' -- '
20
+ # The time window used to detect 2 successive SIGINT (Ctrl+C) signals.
21
+ SIGINT_TIME_WINDOW = 5
17
22
 
18
23
  class << self
19
24
  def serve(options)
@@ -23,13 +28,15 @@ module Spin
23
28
  Dir.chdir(root_path)
24
29
  Spin.parse_hook_file(root_path)
25
30
  else
26
- warn "Could not find #{options[:preload]}. Are you running this from the root of a Rails project?"
31
+ logger.warn "Could not find #{options[:preload]}. Are you running this from the root of a Rails project?"
27
32
  end
28
33
 
34
+ set_server_process_pid
35
+
29
36
  open_socket do |socket|
30
37
  preload(options) if root_path
31
38
 
32
- puts "Pushing test results back to push processes" if options[:push_results]
39
+ logger.info "Pushing test results back to push processes" if options[:push_results]
33
40
 
34
41
  loop do
35
42
  run_pushed_tests(socket, options)
@@ -37,6 +44,20 @@ module Spin
37
44
  end
38
45
  end
39
46
 
47
+ def logger
48
+ @logger ||= Spin::Logger.new
49
+ end
50
+
51
+ # Called by the Spin server process to store its process pid.
52
+ def set_server_process_pid
53
+ @server_process_pid = Process.pid
54
+ end
55
+
56
+ # Returns +true+ if the current process is the Spin server process.
57
+ def server_process?
58
+ @server_process_pid == Process.pid
59
+ end
60
+
40
61
  def push(argv, options)
41
62
  files_to_load = convert_push_arguments_to_files(argv)
42
63
 
@@ -49,19 +70,21 @@ module Spin
49
70
 
50
71
  abort if files_to_load.empty?
51
72
 
52
- puts "Spinning up #{files_to_load.join(" ")}"
53
- send_files_to_serve(files_to_load)
73
+ logger.info "Spinning up #{files_to_load.join(" ")}"
74
+ send_files_to_serve(files_to_load, options[:trailing_pushed_args] || [])
54
75
  end
55
76
 
56
77
  private
57
78
 
58
- def send_files_to_serve(files_to_load)
79
+ def send_files_to_serve(files_to_load, trailing_args)
59
80
  # This is the other end of the socket that `spin serve` opens. At this point
60
81
  # `spin serve` will accept(2) our connection.
61
82
  socket = UNIXSocket.open(socket_file)
62
83
 
63
84
  # We put the filenames on the socket for the server to read and then load.
64
- socket.puts files_to_load.join(PUSH_FILE_SEPARATOR)
85
+ payload = files_to_load.join(PUSH_FILE_SEPARATOR)
86
+ payload += "#{ARGS_SEPARATOR}#{trailing_args.join(PUSH_FILE_SEPARATOR)}" unless trailing_args.empty?
87
+ socket.puts payload
65
88
 
66
89
  while line = socket.readpartial(100)
67
90
  break if line[-1,1] == "\0"
@@ -101,9 +124,9 @@ module Spin
101
124
 
102
125
  "#{file_name}:#{line_number}"
103
126
  else
104
- file_name
127
+ file_name.to_s
105
128
  end
106
- end.reject(&:empty?).uniq
129
+ end.compact.reject(&:empty?).uniq
107
130
  end
108
131
 
109
132
  def make_files_relative(files_to_load, root_path)
@@ -115,11 +138,15 @@ module Spin
115
138
  def run_pushed_tests(socket, options)
116
139
  rerun_last_tests_on_quit(options) unless options[:push_results]
117
140
 
141
+ notify_ready
142
+
118
143
  # Since `spin push` reconnects each time it has new files for us we just
119
144
  # need to accept(2) connections from it.
120
145
  conn = socket.accept
121
146
  # This should be a list of relative paths to files.
122
147
  files = conn.gets.chomp
148
+ files, trailing_args = files.split(ARGS_SEPARATOR)
149
+ options[:trailing_args] = trailing_args.nil? ? [] : trailing_args.split(PUSH_FILE_SEPARATOR)
123
150
  files = files.split(PUSH_FILE_SEPARATOR)
124
151
 
125
152
  # If spin is started with the time flag we will track total execution so
@@ -132,15 +159,9 @@ module Spin
132
159
 
133
160
  fork_and_run(files, conn, options)
134
161
 
135
- # WAIT: We don't want the parent process handling multiple test runs at the same
136
- # time because then we'd need to deal with multiple test databases, and
137
- # that destroys the idea of being simple to use. So we wait(2) until the
138
- # child process has finished running the test.
139
- Process.wait
140
-
141
162
  # If we are tracking time we will output it here after everything has
142
163
  # finished running
143
- puts "Total execution time was #{Time.now - start} seconds" if start
164
+ logger.info "Total execution time was #{Time.now - start} seconds" if start
144
165
 
145
166
  # Tests have now run. If we were pushing results to a push process, we can
146
167
  # now disconnect it.
@@ -154,10 +175,33 @@ module Spin
154
175
  # Trap SIGQUIT (Ctrl+\) and re-run the last files that were pushed
155
176
  # TODO test this
156
177
  def rerun_last_tests_on_quit(options)
157
- trap('QUIT') do
158
- fork_and_run(@last_files_ran, nil, options)
159
- Process.wait
178
+ trap('QUIT') { sigquit_handler(options) }
179
+ end
180
+
181
+ # This method is called when a SIGQUIT ought to be handled.
182
+ def sigquit_handler(options)
183
+ # If the current process is not the Spin server process, ignore the
184
+ # signal by doing nothing.
185
+ return unless server_process?
186
+
187
+ unless @last_files_ran
188
+ logger.fatal "Cannot rerun last tests, please push a file to Spin server first"
189
+ return
190
+ end
191
+
192
+ if test_process.alive?
193
+ logger.fatal "Cannot rerun last tests, test process #{test_process} still alive"
194
+ return
160
195
  end
196
+
197
+ fork_and_run(@last_files_ran, nil, options.merge(:trailing_args => @last_trailing_args_used))
198
+
199
+ notify_ready
200
+ end
201
+
202
+ # Notify the user that Spin server is ready for new tests.
203
+ def notify_ready
204
+ logger.info "Ready"
161
205
  end
162
206
 
163
207
  def preload(options)
@@ -181,7 +225,7 @@ module Spin
181
225
  options[:test_framework] ||= determine_test_framework
182
226
 
183
227
  # Preload RSpec to save some time on each test run
184
- if options[:test_framework]
228
+ if options[:test_framework] == :rspec
185
229
  begin
186
230
  require 'rspec/autorun'
187
231
 
@@ -196,7 +240,7 @@ module Spin
196
240
  end
197
241
  end
198
242
  # This is the amount of time that you'll save on each subsequent test run.
199
- puts "Preloaded Rails env in #{duration}s..."
243
+ logger.info "Preloaded Rails environment in #{duration.round(2)}s"
200
244
  end
201
245
 
202
246
  # This socket is how we communicate with `spin push`.
@@ -208,17 +252,40 @@ module Spin
208
252
  File.delete(file) if File.exist?(file)
209
253
  socket = UNIXServer.open(file)
210
254
 
211
- # Trap SIGINT (Ctrl-C) so that we exit cleanly.
212
- trap('SIGINT') do
213
- socket.close
214
- exit
215
- end
255
+ trap('SIGINT') { sigint_handler(socket) }
216
256
 
217
257
  yield socket
218
258
  ensure
219
259
  File.delete(file) if file && File.exist?(file)
220
260
  end
221
261
 
262
+ # This method is called when a SIGINT ought to be handled.
263
+ def sigint_handler(socket)
264
+ # If the current process is not the Spin server process, allow the signal
265
+ # to "bubble up" by exiting.
266
+ exit unless server_process?
267
+
268
+ if sigint_recently_sent?
269
+ socket.close
270
+ exit
271
+ else
272
+ set_last_sigint_sent
273
+ logger.info "Press Ctrl+C again (within #{SIGINT_TIME_WINDOW}s) to exit"
274
+ end
275
+ end
276
+
277
+ # Updates the timestamp when the last SIGINT was sent.
278
+ def set_last_sigint_sent
279
+ @last_sigint_sent = Time.now
280
+ end
281
+
282
+ # Returns +true+ if a SIGINT has been sent within the time window.
283
+ def sigint_recently_sent?
284
+ return if @last_sigint_sent.nil?
285
+
286
+ (Time.now - SIGINT_TIME_WINDOW) < @last_sigint_sent
287
+ end
288
+
222
289
  def determine_test_framework
223
290
  if defined?(RSpec)
224
291
  :rspec
@@ -241,12 +308,17 @@ module Spin
241
308
  path
242
309
  end
243
310
 
311
+ # Returns (and caches) a TestProcess instance.
312
+ def test_process
313
+ @test_process ||= Spin::TestProcess.new
314
+ end
315
+
244
316
  def fork_and_run(files, conn, options)
245
317
  execute_hook(:before_fork)
246
318
  # We fork(2) before loading the file so that our pristine preloaded
247
319
  # environment is untouched. The child process will load whatever code it
248
320
  # needs to, then it exits and we're back to the baseline preloaded app.
249
- fork do
321
+ test_process.pid = fork do
250
322
  # To push the test results to the push process instead of having them
251
323
  # displayed by the server, we reopen $stdout/$stderr to the open
252
324
  # connection.
@@ -263,8 +335,11 @@ module Spin
263
335
 
264
336
  execute_hook(:after_fork)
265
337
 
266
- puts
267
- puts "Loading #{files.inspect}"
338
+ logger.info "Loading #{files.inspect}"
339
+
340
+ trailing_args = options[:trailing_args]
341
+ logger.info "Will run with: #{trailing_args.inspect}" unless trailing_args.empty?
342
+
268
343
 
269
344
  # Unfortunately rspec's interface isn't as simple as just requiring the
270
345
  # test file that you want to run (suddenly test/unit seems like the less
@@ -272,13 +347,21 @@ module Spin
272
347
  if options[:test_framework] == :rspec
273
348
  # We pretend the filepath came in as an argument and duplicate the
274
349
  # behaviour of the `rspec` binary.
275
- ARGV.concat files
350
+ ARGV.concat(files + trailing_args)
276
351
  else
352
+ # Pass any additional push arguments to the test runner
353
+ ARGV.concat trailing_args
277
354
  # We require the full path of the file here in the child process.
278
355
  files.each { |f| require File.expand_path f }
279
356
  end
280
357
  end
281
358
  @last_files_ran = files
359
+ @last_trailing_args_used = options[:trailing_args]
360
+
361
+ # WAIT: We don't want the parent process handling multiple test runs at the same
362
+ # time because then we'd need to deal with multiple test databases, and
363
+ # that destroys the idea of being simple to use.
364
+ test_process.wait
282
365
  end
283
366
 
284
367
  def socket_file
@@ -18,8 +18,8 @@ module Spin
18
18
  $LOAD_PATH.concat(dirs.split(File::PATH_SEPARATOR))
19
19
  end
20
20
 
21
- opts.on("--rspec", "Force the selected test framework to RSpec") { options[:test_framework] = :testunit }
22
- opts.on("--test-unit", "Force the selected test framework to Test::Unit") { options[:test_framework] = :rspec }
21
+ opts.on("--rspec", "Force the selected test framework to RSpec") { options[:test_framework] = :rspec }
22
+ opts.on("--test-unit", "Force the selected test framework to Test::Unit") { options[:test_framework] = :testunit }
23
23
  opts.on("-t", "--time", "See total execution time for each test run") { options[:time] = true }
24
24
  opts.on("--push-results", "Push test results to the push process") { options[:push_results] = true }
25
25
  opts.on("--preload FILE", "Preload this file instead of #{options[:preload]}") { |v| options[:preload] = v }
@@ -27,13 +27,22 @@ module Spin
27
27
  opts.on("-e", "Stub to keep kicker happy")
28
28
  opts.on("-v", "--version", "Show Version") { puts Spin::VERSION; exit }
29
29
  opts.on("-h", "--help") { $stderr.puts opts; exit }
30
+ opts.on('--', 'Separates trailing arguments to be forwarded to the test runner') do |v|
31
+ trailing_pushed_args = []
32
+ while opt = ARGV.shift
33
+ trailing_pushed_args << opt
34
+ end
35
+ options[:trailing_pushed_args] = trailing_pushed_args
36
+ end
30
37
  end
31
38
  parser.parse!
32
39
 
33
40
  subcommand = argv.shift
34
41
  case subcommand
35
- when "serve" then Spin.serve(options)
36
- when "push" then Spin.push(argv, options)
42
+ when "serve", "s"
43
+ then Spin.serve(options)
44
+ when "push", "p"
45
+ then Spin.push(argv, options)
37
46
  else
38
47
  $stderr.puts parser
39
48
  exit 1
@@ -0,0 +1,42 @@
1
+ require 'logger'
2
+ require 'forwardable'
3
+
4
+ module Spin
5
+ class Logger
6
+ extend Forwardable
7
+
8
+ attr_reader :logger
9
+ def_delegators :logger, :fatal,
10
+ :error,
11
+ :warn,
12
+ :info,
13
+ :debug
14
+
15
+ def initialize
16
+ @logger = ::Logger.new($stdout)
17
+ @logger.level = level
18
+ @logger.formatter = formatter
19
+ end
20
+
21
+ private
22
+
23
+ def level
24
+ ::Logger::INFO
25
+ end
26
+
27
+ def formatter
28
+ proc { |_, _, _, message| "[#{caller}] #{message}\n" }
29
+ end
30
+
31
+ # Returns a "Spin" label for log entries, with color, if supported.
32
+ def caller
33
+ name = "Spin"
34
+ $stdout.isatty ? cyan(name) : name
35
+ end
36
+
37
+ # Uses ANSI escape codes to create cyan-colored output.
38
+ def cyan(string)
39
+ "\e[36m#{string}\e[0m"
40
+ end
41
+ end
42
+ end
@@ -0,0 +1,21 @@
1
+ module Spin
2
+ class TestProcess
3
+ attr_accessor :pid
4
+
5
+ # Use wait(2) to block execution until the test process has finished. When
6
+ # finished, reset the assigned pid.
7
+ def wait
8
+ Process.wait
9
+ @pid = nil
10
+ end
11
+
12
+ # Returns +true+ if the test process is alive.
13
+ def alive?
14
+ !@pid.nil?
15
+ end
16
+
17
+ def to_s
18
+ @pid.to_s
19
+ end
20
+ end
21
+ end
@@ -1,3 +1,3 @@
1
1
  module Spin
2
- VERSION = "0.6.0"
2
+ VERSION = "0.7.0"
3
3
  end
@@ -1,10 +1,5 @@
1
1
 
2
2
  describe "Spin" do
3
- before do
4
- # kill all Threads that might be hanging around
5
- Thread.list.each { |thread| thread.exit unless thread == Thread.current }
6
- end
7
-
8
3
  around do |example|
9
4
  folder = File.expand_path("../tmp", __FILE__)
10
5
  `rm -rf #{folder}`
@@ -15,61 +10,18 @@ describe "Spin" do
15
10
  `rm -rf #{folder}`
16
11
  end
17
12
 
18
- def root
19
- File.expand_path '../..', __FILE__
20
- end
21
-
22
- def spin(command, options={})
23
- command = spin_command(command)
24
- result = `#{command}`
25
- raise "FAILED #{command}\n#{result}" if $?.success? == !!options[:fail]
26
- result
27
- end
28
-
29
- def spin_command(command)
30
- "ruby -I #{root}/lib #{root}/bin/spin #{command} 2>&1"
31
- end
32
-
33
- def record_serve(output, command)
34
- IO.popen(spin_command("serve #{command}")) do |pipe|
35
- while str = pipe.readpartial(100)
36
- output << str
37
- end rescue EOFError
38
- end
39
- end
40
-
41
- def write(file, content)
42
- ensure_folder File.dirname(file)
43
- File.open(file, 'w'){|f| f.write content }
44
- end
45
-
46
- def read(file)
47
- File.read file
48
- end
49
-
50
- def delete(file)
51
- `rm #{file}`
52
- end
53
-
54
- def ensure_folder(folder)
55
- `mkdir -p #{folder}` unless File.exist?(folder)
56
- end
57
-
58
- def serve_and_push(serve_command, push_commands)
59
- serve_output = ""
60
- t1 = Thread.new { record_serve(serve_output, serve_command) }
61
- sleep 0.1
62
- push_output = [*push_commands].map{ |cmd| spin("push #{cmd}") }
63
- sleep 0.2
64
- t1.kill
65
- [serve_output, push_output]
13
+ after do
14
+ kill_all_threads
15
+ kill_all_children
66
16
  end
67
17
 
68
18
  context "with simple setup" do
69
19
  before do
70
20
  write "config/application.rb", "$xxx = 1234"
71
21
  write "test/foo_test.rb", "puts $xxx * 2"
72
- @default_pushed = "Spinning up test/foo_test.rb\n"
22
+ @log_label = "[Spin]"
23
+ @default_pushed = "#{@log_label} Spinning up test/foo_test.rb\n"
24
+ @preloaded_message = "Preloaded Rails environment in "
73
25
  end
74
26
 
75
27
  it "shows help when no arguments are given" do
@@ -78,14 +30,14 @@ describe "Spin" do
78
30
 
79
31
  it "can serve and push" do
80
32
  served, pushed = serve_and_push("", "test/foo_test.rb")
81
- served.should include "Preloaded Rails env in "
33
+ served.should include @preloaded_message
82
34
  served.should include "2468"
83
35
  pushed.first.should == @default_pushed
84
36
  end
85
37
 
86
38
  it "can run files without .rb extension" do
87
39
  served, pushed = serve_and_push("", "test/foo_test")
88
- served.should include "Preloaded Rails env in "
40
+ served.should include @preloaded_message
89
41
  served.should include "2468"
90
42
  pushed.first.should == @default_pushed
91
43
  end
@@ -93,7 +45,7 @@ describe "Spin" do
93
45
  it "can run multiple times" do
94
46
  write "test/foo_test.rb", "puts $xxx *= 2"
95
47
  served, pushed = serve_and_push("", ["test/foo_test.rb", "test/foo_test.rb", "test/foo_test.rb"])
96
- served.should include "Preloaded Rails env in "
48
+ served.should include @preloaded_message
97
49
  served.scan("2468").size.should == 3
98
50
  pushed.size.should == 3
99
51
  pushed.each{|x| x.should == @default_pushed }
@@ -102,17 +54,17 @@ describe "Spin" do
102
54
  it "can run multiple files at once" do
103
55
  write "test/bar_test.rb", "puts $xxx / 2"
104
56
  served, pushed = serve_and_push("", "test/foo_test.rb test/bar_test.rb")
105
- served.should include "Preloaded Rails env in "
57
+ served.should include @preloaded_message
106
58
  served.should include "2468"
107
59
  served.should include "617"
108
- pushed.first.should == "Spinning up test/foo_test.rb test/bar_test.rb\n"
60
+ pushed.first.should == "#{@log_label} Spinning up test/foo_test.rb test/bar_test.rb\n"
109
61
  end
110
62
 
111
63
  it "complains when the preloaded file cannot be found" do
112
64
  delete "config/application.rb"
113
65
  write "test/foo_test.rb", "puts 2468"
114
66
  served, pushed = serve_and_push("", "test/foo_test.rb")
115
- served.should_not include "Preloaded Rails env in "
67
+ served.should_not include @preloaded_message
116
68
  served.should include "Could not find config/application.rb. Are you running"
117
69
  served.should include "2468"
118
70
  pushed.first.should == @default_pushed
@@ -142,6 +94,19 @@ describe "Spin" do
142
94
  served.should include "BBB"
143
95
  served.should_not include "CCC"
144
96
  end
97
+
98
+ it "can pass trailing arguments to the spec runner" do
99
+ write "spec/foo_spec.rb", <<-RUBY
100
+ describe "x" do
101
+ it("a"){ puts "AAA" }
102
+ it("b"){ puts "BBB" }
103
+ it("c"){ puts "CCC" }
104
+ end
105
+ RUBY
106
+ served, pushed = serve_and_push("", ["spec/foo_spec.rb -- --profile"])
107
+ served.should include 'Will run with: ["--profile"]'
108
+ served.should include 'Top 3 slowest examples'
109
+ end
145
110
  end
146
111
 
147
112
  context "options" do
@@ -155,7 +120,7 @@ describe "Spin" do
155
120
 
156
121
  it "can --push-results" do
157
122
  served, pushed = serve_and_push("--push-results", "test/foo_test.rb")
158
- served.should include "Preloaded Rails env in "
123
+ served.should include @preloaded_message
159
124
  served.should_not include "2468"
160
125
  pushed.first.should include "2468"
161
126
  end
@@ -164,7 +129,7 @@ describe "Spin" do
164
129
  write "config/application.rb", "raise"
165
130
  write "config/environment.rb", "$xxx = 1234"
166
131
  served, pushed = serve_and_push("--preload config/environment.rb", "test/foo_test.rb")
167
- served.should include "Preloaded Rails env in "
132
+ served.should include @preloaded_message
168
133
  served.should include "2468"
169
134
  pushed.first.should == @default_pushed
170
135
  end
@@ -179,7 +144,7 @@ describe "Spin" do
179
144
 
180
145
  it "ignores -e" do
181
146
  served, pushed = serve_and_push("-e", "test/foo_test.rb -e")
182
- served.should include "Preloaded Rails env in "
147
+ served.should include @preloaded_message
183
148
  served.should include "2468"
184
149
  pushed.first.should == @default_pushed
185
150
  end
@@ -190,6 +155,12 @@ describe "Spin" do
190
155
  served.should include "Total execution time was 0."
191
156
  pushed.first.should == @default_pushed
192
157
  end
158
+
159
+ it "can pass trailing arguments to the test runner" do
160
+ served, pushed = serve_and_push("", ["test/foo_test.rb -- -n /validates/"])
161
+ served.should include 'Will run with: ["-n", "/validates/"]'
162
+ pushed.first.should == @default_pushed
163
+ end
193
164
  end
194
165
 
195
166
  context "hooks" do
@@ -231,4 +202,74 @@ describe "Spin" do
231
202
  end
232
203
  end
233
204
  end
205
+
206
+ private
207
+
208
+ def root
209
+ File.expand_path '../..', __FILE__
210
+ end
211
+
212
+ def spin(command, options={})
213
+ command = spin_command(command)
214
+ result = `#{command}`
215
+ raise "FAILED #{command}\n#{result}" if $?.success? == !!options[:fail]
216
+ result
217
+ end
218
+
219
+ def spin_command(command)
220
+ "ruby -I #{root}/lib #{root}/bin/spin #{command} 2>&1"
221
+ end
222
+
223
+ def record_serve(output, command)
224
+ IO.popen(spin_command("serve #{command}")) do |pipe|
225
+ while str = pipe.readpartial(100)
226
+ output << str
227
+ end rescue EOFError
228
+ end
229
+ end
230
+
231
+ def write(file, content)
232
+ ensure_folder File.dirname(file)
233
+ File.open(file, 'w'){|f| f.write content }
234
+ end
235
+
236
+ def read(file)
237
+ File.read file
238
+ end
239
+
240
+ def delete(file)
241
+ `rm #{file}`
242
+ end
243
+
244
+ def ensure_folder(folder)
245
+ `mkdir -p #{folder}` unless File.exist?(folder)
246
+ end
247
+
248
+ def serve_and_push(serve_command, push_commands)
249
+ serve_output = ""
250
+ t1 = Thread.new { record_serve(serve_output, serve_command) }
251
+ sleep 0.1
252
+ push_output = [*push_commands].map{ |cmd| spin("push #{cmd}") }
253
+ sleep 0.2
254
+ t1.kill
255
+ [serve_output, push_output]
256
+ end
257
+
258
+ def kill_all_threads
259
+ Thread.list.each { |thread| thread.exit unless thread == Thread.current }
260
+ end
261
+
262
+ def kill_all_children
263
+ children = child_pids
264
+ `kill -9 #{children.join(" ")}` unless children.empty?
265
+ end
266
+
267
+ def child_pids
268
+ pid = Process.pid
269
+ pipe = IO.popen("ps -ef | grep #{pid}")
270
+ pipe.readlines.map do |line|
271
+ parts = line.split(/\s+/)
272
+ parts[2] if parts[3] == pid.to_s and parts[2] != pipe.pid.to_s
273
+ end.compact
274
+ end
234
275
  end
metadata CHANGED
@@ -1,7 +1,7 @@
1
1
  --- !ruby/object:Gem::Specification
2
2
  name: spin
3
3
  version: !ruby/object:Gem::Version
4
- version: 0.6.0
4
+ version: 0.7.0
5
5
  prerelease:
6
6
  platform: ruby
7
7
  authors:
@@ -9,8 +9,40 @@ authors:
9
9
  autorequire:
10
10
  bindir: bin
11
11
  cert_chain: []
12
- date: 2013-02-13 00:00:00.000000000 Z
13
- dependencies: []
12
+ date: 2013-07-25 00:00:00.000000000 Z
13
+ dependencies:
14
+ - !ruby/object:Gem::Dependency
15
+ name: rake
16
+ requirement: !ruby/object:Gem::Requirement
17
+ none: false
18
+ requirements:
19
+ - - ! '>='
20
+ - !ruby/object:Gem::Version
21
+ version: '0'
22
+ type: :development
23
+ prerelease: false
24
+ version_requirements: !ruby/object:Gem::Requirement
25
+ none: false
26
+ requirements:
27
+ - - ! '>='
28
+ - !ruby/object:Gem::Version
29
+ version: '0'
30
+ - !ruby/object:Gem::Dependency
31
+ name: rspec
32
+ requirement: !ruby/object:Gem::Requirement
33
+ none: false
34
+ requirements:
35
+ - - ~>
36
+ - !ruby/object:Gem::Version
37
+ version: 2.13.0
38
+ type: :development
39
+ prerelease: false
40
+ version_requirements: !ruby/object:Gem::Requirement
41
+ none: false
42
+ requirements:
43
+ - - ~>
44
+ - !ruby/object:Gem::Version
45
+ version: 2.13.0
14
46
  description: ! 'Spin preloads your Rails environment to speed up your autotest(ish)
15
47
  workflow.
16
48
 
@@ -27,6 +59,8 @@ files:
27
59
  - README.md
28
60
  - lib/spin/cli.rb
29
61
  - lib/spin/hooks.rb
62
+ - lib/spin/logger.rb
63
+ - lib/spin/test_process.rb
30
64
  - lib/spin/version.rb
31
65
  - lib/spin.rb
32
66
  - spec/integration_spec.rb
@@ -51,7 +85,7 @@ required_rubygems_version: !ruby/object:Gem::Requirement
51
85
  version: '0'
52
86
  requirements: []
53
87
  rubyforge_project:
54
- rubygems_version: 1.8.25
88
+ rubygems_version: 1.8.23
55
89
  signing_key:
56
90
  specification_version: 3
57
91
  summary: Spin preloads your Rails environment to speed up your autotest(ish) workflow.