spin 0.6.0 → 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/README.md +3 -1
- data/lib/spin.rb +112 -29
- data/lib/spin/cli.rb +13 -4
- data/lib/spin/logger.rb +42 -0
- data/lib/spin/test_process.rb +21 -0
- data/lib/spin/version.rb +1 -1
- data/spec/integration_spec.rb +104 -63
- metadata +38 -4
data/README.md
CHANGED
|
@@ -1,6 +1,8 @@
|
|
|
1
1
|
Spin
|
|
2
2
|
====
|
|
3
3
|
|
|
4
|
+
[](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)
|
data/lib/spin.rb
CHANGED
|
@@ -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
|
-
|
|
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
|
-
|
|
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
|
-
|
|
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
|
-
|
|
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')
|
|
158
|
-
|
|
159
|
-
|
|
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
|
-
|
|
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
|
-
|
|
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
|
-
|
|
267
|
-
|
|
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
|
|
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
|
data/lib/spin/cli.rb
CHANGED
|
@@ -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] = :
|
|
22
|
-
opts.on("--test-unit", "Force the selected test framework to Test::Unit") { options[:test_framework] = :
|
|
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"
|
|
36
|
-
|
|
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
|
data/lib/spin/logger.rb
ADDED
|
@@ -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
|
data/lib/spin/version.rb
CHANGED
data/spec/integration_spec.rb
CHANGED
|
@@ -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
|
-
|
|
19
|
-
|
|
20
|
-
|
|
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
|
-
@
|
|
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
|
|
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
|
|
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
|
|
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
|
|
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
|
|
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
|
|
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
|
|
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
|
|
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.
|
|
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-
|
|
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.
|
|
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.
|