spin 0.5.3 → 0.6.0
Sign up to get free protection for your applications and to get access to all the features.
- data/README.md +136 -0
- data/bin/spin +2 -356
- data/lib/spin.rb +289 -0
- data/lib/spin/cli.rb +54 -0
- data/lib/spin/hooks.rb +28 -0
- data/lib/spin/version.rb +3 -0
- data/spec/integration_spec.rb +234 -0
- metadata +12 -6
data/README.md
ADDED
@@ -0,0 +1,136 @@
|
|
1
|
+
Spin
|
2
|
+
====
|
3
|
+
|
4
|
+
Spin speeds up your Rails testing workflow.
|
5
|
+
|
6
|
+
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...
|
7
|
+
Spin works with an autotest(ish) workflow.
|
8
|
+
|
9
|
+
Installation
|
10
|
+
===========
|
11
|
+
|
12
|
+
Spin is available as a rubygem.
|
13
|
+
|
14
|
+
``` ruby
|
15
|
+
gem i spin
|
16
|
+
```
|
17
|
+
|
18
|
+
Spin is a tool for Rails 3 apps. It is compatible with the following testing libraries:
|
19
|
+
|
20
|
+
* any version of test/unit or MiniTest
|
21
|
+
* RSpec 2.x
|
22
|
+
|
23
|
+
Usage
|
24
|
+
=====
|
25
|
+
|
26
|
+
There are two components to Spin, a server and client. The server has to be running for anything interesting to happen. You can start the Spin server from your `Rails.root` with the following command:
|
27
|
+
|
28
|
+
``` bash
|
29
|
+
spin serve
|
30
|
+
```
|
31
|
+
|
32
|
+
As soon as the server is running it will be ready to accept from clients. You can use the following command to specify a file for the server to load:
|
33
|
+
|
34
|
+
``` bash
|
35
|
+
spin push test/unit/product_test.rb
|
36
|
+
```
|
37
|
+
|
38
|
+
Or push multiple files to be loaded at once:
|
39
|
+
|
40
|
+
``` bash
|
41
|
+
spin push test/unit/product_test.rb test/unit/shop_test.rb test/unit/cart_test.rb
|
42
|
+
```
|
43
|
+
|
44
|
+
Or, when using RSpec, run the whole suite:
|
45
|
+
|
46
|
+
``` bash
|
47
|
+
spin push spec
|
48
|
+
```
|
49
|
+
|
50
|
+
Running a single RSpec example by adding a line number is also possible, e.g:
|
51
|
+
|
52
|
+
``` bash
|
53
|
+
spin push spec/models/user_spec.rb:14
|
54
|
+
```
|
55
|
+
|
56
|
+
If you experience issues with `test_helper.rb` not being available you may need to add your test directory to the load path using the `-I` option:
|
57
|
+
|
58
|
+
``` bash
|
59
|
+
spin serve -Itest
|
60
|
+
```
|
61
|
+
|
62
|
+
Send a SIGQUIT to spin serve (`Ctrl+\`) if you want to re-run the last files that were ran via `spin push [files]`.
|
63
|
+
|
64
|
+
### With Kicker
|
65
|
+
|
66
|
+
As mentioned, this tool works best with an autotest(ish) workflow. I haven't actually used with with `autotest` itself, but it works great with [kicker](http://github.com/alloy/kicker). Here's the suggested workflow for a Rails app:
|
67
|
+
|
68
|
+
1. Start up the spin server
|
69
|
+
|
70
|
+
``` bash
|
71
|
+
spin serve
|
72
|
+
```
|
73
|
+
|
74
|
+
2. Start up `kicker` using the custom binary option (and any other options you want)
|
75
|
+
|
76
|
+
``` bash
|
77
|
+
kicker -r rails -b 'spin push'
|
78
|
+
```
|
79
|
+
|
80
|
+
3. Faster testing workflow!
|
81
|
+
|
82
|
+
Motivation
|
83
|
+
==========
|
84
|
+
|
85
|
+
A few months back I did an experiment. I opened up the source code to my local copy of the ActiveRecord gem. I added a line at the top of `active_record/base` that incremented a counter in Redis each time it was evaluated. After about a week that counter was well above 2000!
|
86
|
+
|
87
|
+
How did I load the ActiveRecord gem over 2000 times in one week? Autotest. I was using it all day while developing. The Rails version that the app was tracking doesn't change very often, yet I had to load the same code over and over again.
|
88
|
+
|
89
|
+
Given that there's no way to compile Ruby code into a faster representation I immediately thought of fork(2). I just need a process to load up Rails and wait around until I need it. When I want to run the tests I just fork(2) that idle process and run the test. Then I only have to load Rails once at the start of my workflow, fork(2) takes care of sharing the code with each child process.
|
90
|
+
|
91
|
+
I threw together the first version of this project in about 20 minutes and noticed an immediate difference in the speed of my testing workflow. Did I mention that I work on a big app? It takes about 10 seconds(!) to load Rails and all of the gem dependencies. With a bit more hacking I was able to get the idle process to load both Rails and my application dependencies, so each test run just initializes the application and loads the files needed for the test run.
|
92
|
+
|
93
|
+
(10 seconds saved per test run) x (2000 test runs per week) = (lots of time saved!)
|
94
|
+
|
95
|
+
### How is it different from Spork?
|
96
|
+
|
97
|
+
There's another project ([spork](https://github.com/sporkrb/spork)) that aims to solve the same problem, but takes a different approach.
|
98
|
+
|
99
|
+
1. It's unobtrusive.
|
100
|
+
|
101
|
+
Your application needs to know about Spork, Spin works entirely outside of your application.
|
102
|
+
|
103
|
+
You'll need to add spork to your Gemfile and introduce your `test_helper.rb` to spork. Spork needs to know details about your app's loading process.
|
104
|
+
|
105
|
+
Spin is designed so that your app never has to know about it. You can use Spin to run your tests while the rest of your team doesn't even know that Spin exists.
|
106
|
+
|
107
|
+
2. It's simple.
|
108
|
+
|
109
|
+
Spin should work out of the box with any Rails app. No custom configuration required.
|
110
|
+
|
111
|
+
3. It doesn't do any [crazy monkey patching](https://github.com/sporkrb/spork-rails/blob/master/lib/spork/app_framework/rails.rb#L43-80).
|
112
|
+
|
113
|
+
Docs
|
114
|
+
============
|
115
|
+
|
116
|
+
[Rocco](http://rtomayko.github.com/rocco/)-annotated source:
|
117
|
+
|
118
|
+
* [spin](http://jstorimer.github.com/spin/)
|
119
|
+
* [spin serve](http://jstorimer.github.com/spin/#section-spin_serve)
|
120
|
+
* [spin push](http://jstorimer.github.com/spin/#section-spin_push)
|
121
|
+
|
122
|
+
Hacking
|
123
|
+
=======
|
124
|
+
|
125
|
+
I take pull requests, and it's commit bit, and there are no tests.
|
126
|
+
|
127
|
+
Related Projects
|
128
|
+
===============
|
129
|
+
|
130
|
+
If Spin isn't scratching your itch then one of these projects might:
|
131
|
+
|
132
|
+
* [guard-spin](https://github.com/vizjerai/guard-spin)
|
133
|
+
* [Spork](https://github.com/sporkrb/spork)
|
134
|
+
* [TestR](https://github.com/sunaku/testr)
|
135
|
+
* [Zeus](https://github.com/burke/zeus)
|
136
|
+
|
data/bin/spin
CHANGED
@@ -1,357 +1,3 @@
|
|
1
1
|
#!/usr/bin/env ruby
|
2
|
-
|
3
|
-
|
4
|
-
# Spin preloads your Rails environment for testing, so you don't load the same code over and over and over... Spin works best with an autotest(ish) workflow.
|
5
|
-
|
6
|
-
require 'socket'
|
7
|
-
# This brings in `Dir.tmpdir`
|
8
|
-
require 'tempfile'
|
9
|
-
# This lets us hash the parameters we want to include in the filename
|
10
|
-
# without having to worry about subdirectories, special chars, etc.
|
11
|
-
require 'digest/md5'
|
12
|
-
# So we can tell users how much time they're saving by preloading their
|
13
|
-
# environment.
|
14
|
-
require 'benchmark'
|
15
|
-
require 'optparse'
|
16
|
-
require 'pathname'
|
17
|
-
|
18
|
-
SEPARATOR = '|'
|
19
|
-
|
20
|
-
def usage
|
21
|
-
<<-USAGE
|
22
|
-
Usage: spin serve
|
23
|
-
spin push <file> <file>...
|
24
|
-
Spin preloads your Rails environment to speed up your autotest(ish) workflow.
|
25
|
-
USAGE
|
26
|
-
end
|
27
|
-
|
28
|
-
def socket_file
|
29
|
-
key = Digest::MD5.hexdigest [Dir.pwd, 'spin-gem'].join
|
30
|
-
[Dir.tmpdir, key].join('/')
|
31
|
-
end
|
32
|
-
|
33
|
-
def determine_test_framework(force_rspec, force_testunit)
|
34
|
-
if force_rspec
|
35
|
-
:rspec
|
36
|
-
elsif force_testunit
|
37
|
-
:testunit
|
38
|
-
elsif defined?(RSpec)
|
39
|
-
:rspec
|
40
|
-
else
|
41
|
-
:testunit
|
42
|
-
end
|
43
|
-
end
|
44
|
-
|
45
|
-
def disconnect(connection)
|
46
|
-
connection.print "\0"
|
47
|
-
connection.close
|
48
|
-
end
|
49
|
-
|
50
|
-
def rails_root(preload)
|
51
|
-
path = Pathname.pwd
|
52
|
-
until path.join(preload).file?
|
53
|
-
return if path.root?
|
54
|
-
path = path.parent
|
55
|
-
end
|
56
|
-
path
|
57
|
-
end
|
58
|
-
|
59
|
-
# ## spin serve
|
60
|
-
def serve(force_rspec, force_testunit, time, push_results, preload)
|
61
|
-
root_path = rails_root(preload) and Dir.chdir(root_path)
|
62
|
-
file = socket_file
|
63
|
-
Spin.parse_hook_file(root_path) if root_path
|
64
|
-
|
65
|
-
# We delete the tmp file for the Unix socket if it already exists. The file
|
66
|
-
# is scoped to the `pwd`, so if it already exists then it must be from an
|
67
|
-
# old run of `spin serve` and can be cleaned up.
|
68
|
-
File.delete(file) if File.exist?(file)
|
69
|
-
|
70
|
-
# This socket is how we communicate with `spin push`.
|
71
|
-
socket = UNIXServer.open(file)
|
72
|
-
|
73
|
-
# Trap SIGINT (Ctrl-C) so that we exit cleanly.
|
74
|
-
trap('SIGINT') {
|
75
|
-
socket.close
|
76
|
-
exit
|
77
|
-
}
|
78
|
-
|
79
|
-
ENV['RAILS_ENV'] = 'test' unless ENV['RAILS_ENV']
|
80
|
-
|
81
|
-
test_framework = nil
|
82
|
-
|
83
|
-
if root_path
|
84
|
-
sec = Benchmark.realtime {
|
85
|
-
# We require config/application because that file (typically) loads Rails
|
86
|
-
# and any Bundler deps, as well as loading the initialization code for
|
87
|
-
# the app, but it doesn't actually perform the initialization. That happens
|
88
|
-
# in config/environment.
|
89
|
-
#
|
90
|
-
# In my experience that's the best we can do in terms of preloading. Rails
|
91
|
-
# and the gem dependencies rarely change and so don't need to be reloaded.
|
92
|
-
# But you can't initialize the application because any non-trivial app will
|
93
|
-
# involve it's models/controllers, etc. in its initialization, which you
|
94
|
-
# definitely don't want to preload.
|
95
|
-
Spin.execute_hook(:before_preload)
|
96
|
-
require File.expand_path preload.sub('.rb','')
|
97
|
-
Spin.execute_hook(:after_preload)
|
98
|
-
|
99
|
-
# Determine the test framework to use using the passed-in 'force' options
|
100
|
-
# or else default to checking for defined constants.
|
101
|
-
test_framework = determine_test_framework(force_rspec, force_testunit)
|
102
|
-
|
103
|
-
# Preload RSpec to save some time on each test run
|
104
|
-
begin
|
105
|
-
require 'rspec/autorun'
|
106
|
-
|
107
|
-
# Tell RSpec it's running with a tty to allow colored output
|
108
|
-
if RSpec.respond_to?(:configure)
|
109
|
-
RSpec.configure do |c|
|
110
|
-
c.tty = true if c.respond_to?(:tty=)
|
111
|
-
end
|
112
|
-
end
|
113
|
-
rescue LoadError
|
114
|
-
end if test_framework == :rspec
|
115
|
-
}
|
116
|
-
# This is the amount of time that you'll save on each subsequent test run.
|
117
|
-
puts "Preloaded Rails env in #{sec}s..."
|
118
|
-
else
|
119
|
-
warn "Could not find #{preload}. Are you running this from the root of a Rails project?"
|
120
|
-
end
|
121
|
-
|
122
|
-
puts "Pushing test results back to push processes" if push_results
|
123
|
-
|
124
|
-
loop do
|
125
|
-
|
126
|
-
# If we're not going to push the results,
|
127
|
-
# Trap SIGQUIT (Ctrl+\) and re-run the last files that were
|
128
|
-
# pushed.
|
129
|
-
if !push_results
|
130
|
-
trap('QUIT') do
|
131
|
-
fork_and_run(@last_files_ran, push_results, test_framework, nil)
|
132
|
-
# See WAIT below
|
133
|
-
Process.wait
|
134
|
-
end
|
135
|
-
end
|
136
|
-
|
137
|
-
# Since `spin push` reconnects each time it has new files for us we just
|
138
|
-
# need to accept(2) connections from it.
|
139
|
-
conn = socket.accept
|
140
|
-
# This should be a list of relative paths to files.
|
141
|
-
files = conn.gets.chomp
|
142
|
-
files = files.split(SEPARATOR)
|
143
|
-
|
144
|
-
# If spin is started with the time flag we will track total execution so
|
145
|
-
# you can easily compare it with time rspec spec for example
|
146
|
-
start = Time.now if time
|
147
|
-
|
148
|
-
# If we're not sending results back to the push process, we can disconnect
|
149
|
-
# it immediately.
|
150
|
-
disconnect(conn) unless push_results
|
151
|
-
|
152
|
-
fork_and_run(files, push_results, test_framework, conn)
|
153
|
-
|
154
|
-
# WAIT: We don't want the parent process handling multiple test runs at the same
|
155
|
-
# time because then we'd need to deal with multiple test databases, and
|
156
|
-
# that destroys the idea of being simple to use. So we wait(2) until the
|
157
|
-
# child process has finished running the test.
|
158
|
-
Process.wait
|
159
|
-
|
160
|
-
# If we are tracking time we will output it here after everything has
|
161
|
-
# finished running
|
162
|
-
puts "Total execution time was #{Time.now - start} seconds" if start
|
163
|
-
|
164
|
-
# Tests have now run. If we were pushing results to a push process, we can
|
165
|
-
# now disconnect it.
|
166
|
-
begin
|
167
|
-
disconnect(conn) if push_results
|
168
|
-
rescue Errno::EPIPE
|
169
|
-
# Don't abort if the client already disconnected
|
170
|
-
end
|
171
|
-
end
|
172
|
-
ensure
|
173
|
-
File.delete(file) if file && File.exist?(file)
|
174
|
-
end
|
175
|
-
|
176
|
-
def fork_and_run(files, push_results, test_framework, conn)
|
177
|
-
Spin.execute_hook(:before_fork)
|
178
|
-
# We fork(2) before loading the file so that our pristine preloaded
|
179
|
-
# environment is untouched. The child process will load whatever code it
|
180
|
-
# needs to, then it exits and we're back to the baseline preloaded app.
|
181
|
-
fork do
|
182
|
-
# To push the test results to the push process instead of having them
|
183
|
-
# displayed by the server, we reopen $stdout/$stderr to the open
|
184
|
-
# connection.
|
185
|
-
tty = files.delete "tty?"
|
186
|
-
if push_results
|
187
|
-
$stdout.reopen(conn)
|
188
|
-
if tty
|
189
|
-
def $stdout.tty?
|
190
|
-
true
|
191
|
-
end
|
192
|
-
end
|
193
|
-
$stderr.reopen(conn)
|
194
|
-
end
|
195
|
-
|
196
|
-
Spin.execute_hook(:after_fork)
|
197
|
-
|
198
|
-
puts
|
199
|
-
puts "Loading #{files.inspect}"
|
200
|
-
|
201
|
-
# Unfortunately rspec's interface isn't as simple as just requiring the
|
202
|
-
# test file that you want to run (suddenly test/unit seems like the less
|
203
|
-
# crazy one!).
|
204
|
-
if test_framework == :rspec
|
205
|
-
# We pretend the filepath came in as an argument and duplicate the
|
206
|
-
# behaviour of the `rspec` binary.
|
207
|
-
ARGV.push files
|
208
|
-
else
|
209
|
-
# We require the full path of the file here in the child process.
|
210
|
-
files.each { |f| require File.expand_path f }
|
211
|
-
end
|
212
|
-
|
213
|
-
end
|
214
|
-
@last_files_ran = files
|
215
|
-
end
|
216
|
-
|
217
|
-
# ## spin push
|
218
|
-
def push(preload)
|
219
|
-
# The filenames that we will spin up to `spin serve` are passed in as
|
220
|
-
# arguments.
|
221
|
-
files_to_load = ARGV
|
222
|
-
|
223
|
-
# We reject anything in ARGV that isn't a file that exists. This takes
|
224
|
-
# care of scripts that specify files like `spin push -r file.rb`. The `-r`
|
225
|
-
# bit will just be ignored.
|
226
|
-
#
|
227
|
-
# We build a string like `file1.rb|file2.rb` and pass it up to the server.
|
228
|
-
files_to_load = files_to_load.map do |file|
|
229
|
-
args = file.split(':')
|
230
|
-
|
231
|
-
file_name = args.first.to_s
|
232
|
-
line_number = args.last.to_i
|
233
|
-
|
234
|
-
# If the file exists then we can push it up just like it is
|
235
|
-
file_name = if File.exist?(file_name)
|
236
|
-
file_name
|
237
|
-
# kicker-2.5.0 now gives us file names without extensions, so we have to try adding it
|
238
|
-
elsif File.extname(file_name).empty?
|
239
|
-
full_file_name = [file_name, 'rb'].join('.')
|
240
|
-
full_file_name if File.exist?(full_file_name)
|
241
|
-
end
|
242
|
-
|
243
|
-
if line_number > 0
|
244
|
-
abort "You specified a line number. Only one file can be pushed in this case." if files_to_load.length > 1
|
245
|
-
|
246
|
-
"#{file_name}:#{line_number}"
|
247
|
-
else
|
248
|
-
file_name
|
249
|
-
end
|
250
|
-
end.compact.uniq
|
251
|
-
|
252
|
-
if root_path = rails_root(preload)
|
253
|
-
files_to_load.map! do |file|
|
254
|
-
Pathname.new(file).expand_path.relative_path_from(root_path).to_s
|
255
|
-
end
|
256
|
-
Dir.chdir root_path
|
257
|
-
end
|
258
|
-
|
259
|
-
files_to_load << "tty?" if $stdout.tty?
|
260
|
-
f = files_to_load.join(SEPARATOR)
|
261
|
-
|
262
|
-
abort if f.empty?
|
263
|
-
puts "Spinning up #{f}"
|
264
|
-
|
265
|
-
# This is the other end of the socket that `spin serve` opens. At this point
|
266
|
-
# `spin serve` will accept(2) our connection.
|
267
|
-
socket = UNIXSocket.open(socket_file)
|
268
|
-
# We put the filenames on the socket for the server to read and then load.
|
269
|
-
socket.puts f
|
270
|
-
|
271
|
-
while line = socket.readpartial(100)
|
272
|
-
break if line[-1,1] == "\0"
|
273
|
-
print line
|
274
|
-
end
|
275
|
-
rescue Errno::ECONNREFUSED, Errno::ENOENT
|
276
|
-
abort "Connection was refused. Have you started up `spin serve` yet?"
|
277
|
-
end
|
278
|
-
|
279
|
-
module Spin
|
280
|
-
HOOKS = [:before_fork, :after_fork, :before_preload, :after_preload]
|
281
|
-
|
282
|
-
def self.hook(name, &block)
|
283
|
-
raise unless HOOKS.include?(name)
|
284
|
-
_hooks(name) << block
|
285
|
-
end
|
286
|
-
|
287
|
-
def self.execute_hook(name)
|
288
|
-
raise unless HOOKS.include?(name)
|
289
|
-
_hooks(name).each(&:call)
|
290
|
-
end
|
291
|
-
|
292
|
-
def self.parse_hook_file(root)
|
293
|
-
file = root.join(".spin.rb")
|
294
|
-
load(file) if File.exist?(file)
|
295
|
-
end
|
296
|
-
|
297
|
-
private
|
298
|
-
|
299
|
-
def self._hooks(name)
|
300
|
-
@hooks ||= {}
|
301
|
-
@hooks[name] ||= []
|
302
|
-
@hooks[name]
|
303
|
-
end
|
304
|
-
end
|
305
|
-
|
306
|
-
force_rspec = false
|
307
|
-
force_testunit = false
|
308
|
-
time = false
|
309
|
-
push_results = false
|
310
|
-
preload = "config/application.rb"
|
311
|
-
options = OptionParser.new do |opts|
|
312
|
-
opts.banner = usage
|
313
|
-
opts.separator ""
|
314
|
-
opts.separator "Server Options:"
|
315
|
-
|
316
|
-
opts.on("-I", "--load-path=DIR#{File::PATH_SEPARATOR}DIR", "Appends directory to $LOAD_PATH") do |dirs|
|
317
|
-
$LOAD_PATH.concat(dirs.split(File::PATH_SEPARATOR))
|
318
|
-
end
|
319
|
-
|
320
|
-
opts.on('--rspec', 'Force the selected test framework to RSpec') do |v|
|
321
|
-
force_rspec = v
|
322
|
-
end
|
323
|
-
|
324
|
-
opts.on('--test-unit', 'Force the selected test framework to Test::Unit') do |v|
|
325
|
-
force_testunit = v
|
326
|
-
end
|
327
|
-
|
328
|
-
opts.on('-t', '--time', 'See total execution time for each test run') do |v|
|
329
|
-
time = true
|
330
|
-
end
|
331
|
-
|
332
|
-
opts.on('--push-results', 'Push test results to the push process') do |v|
|
333
|
-
push_results = v
|
334
|
-
end
|
335
|
-
|
336
|
-
opts.on('--preload FILE', "Preload this file instead of #{preload}") do |v|
|
337
|
-
preload = v
|
338
|
-
end
|
339
|
-
|
340
|
-
opts.separator "General Options:"
|
341
|
-
opts.on('-e', 'Stub to keep kicker happy')
|
342
|
-
opts.on('-h', '--help') do
|
343
|
-
$stderr.puts opts
|
344
|
-
exit 1
|
345
|
-
end
|
346
|
-
end
|
347
|
-
options.parse!
|
348
|
-
|
349
|
-
subcommand = ARGV.shift
|
350
|
-
case subcommand
|
351
|
-
when 'serve' then serve(force_rspec, force_testunit, time, push_results, preload)
|
352
|
-
when 'push' then push(preload)
|
353
|
-
else
|
354
|
-
$stderr.puts options
|
355
|
-
exit 1
|
356
|
-
end
|
357
|
-
|
2
|
+
require 'spin/cli'
|
3
|
+
Spin::CLI.run(ARGV)
|
data/lib/spin.rb
ADDED
@@ -0,0 +1,289 @@
|
|
1
|
+
require 'spin/version'
|
2
|
+
require 'spin/hooks'
|
3
|
+
require 'socket'
|
4
|
+
require 'tempfile' # Dir.tmpdir
|
5
|
+
# This lets us hash the parameters we want to include in the filename
|
6
|
+
# without having to worry about subdirectories, special chars, etc.
|
7
|
+
require 'digest/md5'
|
8
|
+
# So we can tell users how much time they're saving by preloading their
|
9
|
+
# environment.
|
10
|
+
require 'benchmark'
|
11
|
+
require 'pathname'
|
12
|
+
|
13
|
+
module Spin
|
14
|
+
extend Spin::Hooks
|
15
|
+
|
16
|
+
PUSH_FILE_SEPARATOR = '|'
|
17
|
+
|
18
|
+
class << self
|
19
|
+
def serve(options)
|
20
|
+
ENV['RAILS_ENV'] = 'test' unless ENV['RAILS_ENV']
|
21
|
+
|
22
|
+
if root_path = rails_root(options[:preload])
|
23
|
+
Dir.chdir(root_path)
|
24
|
+
Spin.parse_hook_file(root_path)
|
25
|
+
else
|
26
|
+
warn "Could not find #{options[:preload]}. Are you running this from the root of a Rails project?"
|
27
|
+
end
|
28
|
+
|
29
|
+
open_socket do |socket|
|
30
|
+
preload(options) if root_path
|
31
|
+
|
32
|
+
puts "Pushing test results back to push processes" if options[:push_results]
|
33
|
+
|
34
|
+
loop do
|
35
|
+
run_pushed_tests(socket, options)
|
36
|
+
end
|
37
|
+
end
|
38
|
+
end
|
39
|
+
|
40
|
+
def push(argv, options)
|
41
|
+
files_to_load = convert_push_arguments_to_files(argv)
|
42
|
+
|
43
|
+
if root_path = rails_root(options[:preload])
|
44
|
+
make_files_relative(files_to_load, root_path)
|
45
|
+
Dir.chdir root_path
|
46
|
+
end
|
47
|
+
|
48
|
+
files_to_load << "tty?" if $stdout.tty?
|
49
|
+
|
50
|
+
abort if files_to_load.empty?
|
51
|
+
|
52
|
+
puts "Spinning up #{files_to_load.join(" ")}"
|
53
|
+
send_files_to_serve(files_to_load)
|
54
|
+
end
|
55
|
+
|
56
|
+
private
|
57
|
+
|
58
|
+
def send_files_to_serve(files_to_load)
|
59
|
+
# This is the other end of the socket that `spin serve` opens. At this point
|
60
|
+
# `spin serve` will accept(2) our connection.
|
61
|
+
socket = UNIXSocket.open(socket_file)
|
62
|
+
|
63
|
+
# 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)
|
65
|
+
|
66
|
+
while line = socket.readpartial(100)
|
67
|
+
break if line[-1,1] == "\0"
|
68
|
+
print line
|
69
|
+
end
|
70
|
+
rescue Errno::ECONNREFUSED, Errno::ENOENT
|
71
|
+
abort "Connection was refused. Have you started up `spin serve` yet?"
|
72
|
+
end
|
73
|
+
|
74
|
+
# The filenames that we will spin up to `spin serve` are passed in as
|
75
|
+
# arguments.
|
76
|
+
def convert_push_arguments_to_files(argv)
|
77
|
+
files_to_load = argv
|
78
|
+
|
79
|
+
# We reject anything in ARGV that isn't a file that exists. This takes
|
80
|
+
# care of scripts that specify files like `spin push -r file.rb`. The `-r`
|
81
|
+
# bit will just be ignored.
|
82
|
+
#
|
83
|
+
# We build a string like `file1.rb|file2.rb` and pass it up to the server.
|
84
|
+
files_to_load = files_to_load.map do |file|
|
85
|
+
args = file.split(':')
|
86
|
+
|
87
|
+
file_name = args.first.to_s
|
88
|
+
line_number = args.last.to_i
|
89
|
+
|
90
|
+
# If the file exists then we can push it up just like it is
|
91
|
+
file_name = if File.exist?(file_name)
|
92
|
+
file_name
|
93
|
+
# kicker-2.5.0 now gives us file names without extensions, so we have to try adding it
|
94
|
+
elsif File.extname(file_name).empty?
|
95
|
+
full_file_name = [file_name, 'rb'].join('.')
|
96
|
+
full_file_name if File.exist?(full_file_name)
|
97
|
+
end
|
98
|
+
|
99
|
+
if line_number > 0
|
100
|
+
abort "You specified a line number. Only one file can be pushed in this case." if files_to_load.length > 1
|
101
|
+
|
102
|
+
"#{file_name}:#{line_number}"
|
103
|
+
else
|
104
|
+
file_name
|
105
|
+
end
|
106
|
+
end.reject(&:empty?).uniq
|
107
|
+
end
|
108
|
+
|
109
|
+
def make_files_relative(files_to_load, root_path)
|
110
|
+
files_to_load.map! do |file|
|
111
|
+
Pathname.new(file).expand_path.relative_path_from(root_path).to_s
|
112
|
+
end
|
113
|
+
end
|
114
|
+
|
115
|
+
def run_pushed_tests(socket, options)
|
116
|
+
rerun_last_tests_on_quit(options) unless options[:push_results]
|
117
|
+
|
118
|
+
# Since `spin push` reconnects each time it has new files for us we just
|
119
|
+
# need to accept(2) connections from it.
|
120
|
+
conn = socket.accept
|
121
|
+
# This should be a list of relative paths to files.
|
122
|
+
files = conn.gets.chomp
|
123
|
+
files = files.split(PUSH_FILE_SEPARATOR)
|
124
|
+
|
125
|
+
# If spin is started with the time flag we will track total execution so
|
126
|
+
# you can easily compare it with time rspec spec for example
|
127
|
+
start = Time.now if options[:time]
|
128
|
+
|
129
|
+
# If we're not sending results back to the push process, we can disconnect
|
130
|
+
# it immediately.
|
131
|
+
disconnect(conn) unless options[:push_results]
|
132
|
+
|
133
|
+
fork_and_run(files, conn, options)
|
134
|
+
|
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
|
+
# If we are tracking time we will output it here after everything has
|
142
|
+
# finished running
|
143
|
+
puts "Total execution time was #{Time.now - start} seconds" if start
|
144
|
+
|
145
|
+
# Tests have now run. If we were pushing results to a push process, we can
|
146
|
+
# now disconnect it.
|
147
|
+
begin
|
148
|
+
disconnect(conn) if options[:push_results]
|
149
|
+
rescue Errno::EPIPE
|
150
|
+
# Don't abort if the client already disconnected
|
151
|
+
end
|
152
|
+
end
|
153
|
+
|
154
|
+
# Trap SIGQUIT (Ctrl+\) and re-run the last files that were pushed
|
155
|
+
# TODO test this
|
156
|
+
def rerun_last_tests_on_quit(options)
|
157
|
+
trap('QUIT') do
|
158
|
+
fork_and_run(@last_files_ran, nil, options)
|
159
|
+
Process.wait
|
160
|
+
end
|
161
|
+
end
|
162
|
+
|
163
|
+
def preload(options)
|
164
|
+
duration = Benchmark.realtime do
|
165
|
+
# We require config/application because that file (typically) loads Rails
|
166
|
+
# and any Bundler deps, as well as loading the initialization code for
|
167
|
+
# the app, but it doesn't actually perform the initialization. That happens
|
168
|
+
# in config/environment.
|
169
|
+
#
|
170
|
+
# In my experience that's the best we can do in terms of preloading. Rails
|
171
|
+
# and the gem dependencies rarely change and so don't need to be reloaded.
|
172
|
+
# But you can't initialize the application because any non-trivial app will
|
173
|
+
# involve it's models/controllers, etc. in its initialization, which you
|
174
|
+
# definitely don't want to preload.
|
175
|
+
execute_hook(:before_preload)
|
176
|
+
require File.expand_path options[:preload].sub('.rb', '')
|
177
|
+
execute_hook(:after_preload)
|
178
|
+
|
179
|
+
# Determine the test framework to use using the passed-in 'force' options
|
180
|
+
# or else default to checking for defined constants.
|
181
|
+
options[:test_framework] ||= determine_test_framework
|
182
|
+
|
183
|
+
# Preload RSpec to save some time on each test run
|
184
|
+
if options[:test_framework]
|
185
|
+
begin
|
186
|
+
require 'rspec/autorun'
|
187
|
+
|
188
|
+
# Tell RSpec it's running with a tty to allow colored output
|
189
|
+
if RSpec.respond_to?(:configure)
|
190
|
+
RSpec.configure do |c|
|
191
|
+
c.tty = true if c.respond_to?(:tty=)
|
192
|
+
end
|
193
|
+
end
|
194
|
+
rescue LoadError
|
195
|
+
end
|
196
|
+
end
|
197
|
+
end
|
198
|
+
# This is the amount of time that you'll save on each subsequent test run.
|
199
|
+
puts "Preloaded Rails env in #{duration}s..."
|
200
|
+
end
|
201
|
+
|
202
|
+
# This socket is how we communicate with `spin push`.
|
203
|
+
# We delete the tmp file for the Unix socket if it already exists. The file
|
204
|
+
# is scoped to the `pwd`, so if it already exists then it must be from an
|
205
|
+
# old run of `spin serve` and can be cleaned up.
|
206
|
+
def open_socket
|
207
|
+
file = socket_file
|
208
|
+
File.delete(file) if File.exist?(file)
|
209
|
+
socket = UNIXServer.open(file)
|
210
|
+
|
211
|
+
# Trap SIGINT (Ctrl-C) so that we exit cleanly.
|
212
|
+
trap('SIGINT') do
|
213
|
+
socket.close
|
214
|
+
exit
|
215
|
+
end
|
216
|
+
|
217
|
+
yield socket
|
218
|
+
ensure
|
219
|
+
File.delete(file) if file && File.exist?(file)
|
220
|
+
end
|
221
|
+
|
222
|
+
def determine_test_framework
|
223
|
+
if defined?(RSpec)
|
224
|
+
:rspec
|
225
|
+
else
|
226
|
+
:testunit
|
227
|
+
end
|
228
|
+
end
|
229
|
+
|
230
|
+
def disconnect(connection)
|
231
|
+
connection.print "\0"
|
232
|
+
connection.close
|
233
|
+
end
|
234
|
+
|
235
|
+
def rails_root(preload)
|
236
|
+
path = Pathname.pwd
|
237
|
+
until path.join(preload).file?
|
238
|
+
return if path.root?
|
239
|
+
path = path.parent
|
240
|
+
end
|
241
|
+
path
|
242
|
+
end
|
243
|
+
|
244
|
+
def fork_and_run(files, conn, options)
|
245
|
+
execute_hook(:before_fork)
|
246
|
+
# We fork(2) before loading the file so that our pristine preloaded
|
247
|
+
# environment is untouched. The child process will load whatever code it
|
248
|
+
# needs to, then it exits and we're back to the baseline preloaded app.
|
249
|
+
fork do
|
250
|
+
# To push the test results to the push process instead of having them
|
251
|
+
# displayed by the server, we reopen $stdout/$stderr to the open
|
252
|
+
# connection.
|
253
|
+
tty = files.delete "tty?"
|
254
|
+
if options[:push_results]
|
255
|
+
$stdout.reopen(conn)
|
256
|
+
if tty
|
257
|
+
def $stdout.tty?
|
258
|
+
true
|
259
|
+
end
|
260
|
+
end
|
261
|
+
$stderr.reopen(conn)
|
262
|
+
end
|
263
|
+
|
264
|
+
execute_hook(:after_fork)
|
265
|
+
|
266
|
+
puts
|
267
|
+
puts "Loading #{files.inspect}"
|
268
|
+
|
269
|
+
# Unfortunately rspec's interface isn't as simple as just requiring the
|
270
|
+
# test file that you want to run (suddenly test/unit seems like the less
|
271
|
+
# crazy one!).
|
272
|
+
if options[:test_framework] == :rspec
|
273
|
+
# We pretend the filepath came in as an argument and duplicate the
|
274
|
+
# behaviour of the `rspec` binary.
|
275
|
+
ARGV.concat files
|
276
|
+
else
|
277
|
+
# We require the full path of the file here in the child process.
|
278
|
+
files.each { |f| require File.expand_path f }
|
279
|
+
end
|
280
|
+
end
|
281
|
+
@last_files_ran = files
|
282
|
+
end
|
283
|
+
|
284
|
+
def socket_file
|
285
|
+
key = Digest::MD5.hexdigest [Dir.pwd, 'spin-gem'].join
|
286
|
+
[Dir.tmpdir, key].join('/')
|
287
|
+
end
|
288
|
+
end
|
289
|
+
end
|
data/lib/spin/cli.rb
ADDED
@@ -0,0 +1,54 @@
|
|
1
|
+
require 'spin'
|
2
|
+
require 'optparse'
|
3
|
+
|
4
|
+
module Spin
|
5
|
+
module CLI
|
6
|
+
class << self
|
7
|
+
def run(argv)
|
8
|
+
options = {
|
9
|
+
:preload => "config/application.rb"
|
10
|
+
}
|
11
|
+
|
12
|
+
parser = OptionParser.new do |opts|
|
13
|
+
opts.banner = usage
|
14
|
+
opts.separator ""
|
15
|
+
opts.separator "Server Options:"
|
16
|
+
|
17
|
+
opts.on("-I", "--load-path=DIR#{File::PATH_SEPARATOR}DIR", "Appends directory to $LOAD_PATH") do |dirs|
|
18
|
+
$LOAD_PATH.concat(dirs.split(File::PATH_SEPARATOR))
|
19
|
+
end
|
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 }
|
23
|
+
opts.on("-t", "--time", "See total execution time for each test run") { options[:time] = true }
|
24
|
+
opts.on("--push-results", "Push test results to the push process") { options[:push_results] = true }
|
25
|
+
opts.on("--preload FILE", "Preload this file instead of #{options[:preload]}") { |v| options[:preload] = v }
|
26
|
+
opts.separator "General Options:"
|
27
|
+
opts.on("-e", "Stub to keep kicker happy")
|
28
|
+
opts.on("-v", "--version", "Show Version") { puts Spin::VERSION; exit }
|
29
|
+
opts.on("-h", "--help") { $stderr.puts opts; exit }
|
30
|
+
end
|
31
|
+
parser.parse!
|
32
|
+
|
33
|
+
subcommand = argv.shift
|
34
|
+
case subcommand
|
35
|
+
when "serve" then Spin.serve(options)
|
36
|
+
when "push" then Spin.push(argv, options)
|
37
|
+
else
|
38
|
+
$stderr.puts parser
|
39
|
+
exit 1
|
40
|
+
end
|
41
|
+
end
|
42
|
+
|
43
|
+
private
|
44
|
+
|
45
|
+
def usage
|
46
|
+
<<-USAGE.gsub(/^\s{8}/,"")
|
47
|
+
Usage: spin serve
|
48
|
+
spin push <file> <file>...
|
49
|
+
Spin preloads your Rails environment to speed up your autotest(ish) workflow.
|
50
|
+
USAGE
|
51
|
+
end
|
52
|
+
end
|
53
|
+
end
|
54
|
+
end
|
data/lib/spin/hooks.rb
ADDED
@@ -0,0 +1,28 @@
|
|
1
|
+
module Spin
|
2
|
+
module Hooks
|
3
|
+
HOOKS = [:before_fork, :after_fork, :before_preload, :after_preload]
|
4
|
+
|
5
|
+
def hook(name, &block)
|
6
|
+
raise unless HOOKS.include?(name)
|
7
|
+
_hooks(name) << block
|
8
|
+
end
|
9
|
+
|
10
|
+
def execute_hook(name)
|
11
|
+
raise unless HOOKS.include?(name)
|
12
|
+
_hooks(name).each(&:call)
|
13
|
+
end
|
14
|
+
|
15
|
+
def parse_hook_file(root)
|
16
|
+
file = root.join(".spin.rb")
|
17
|
+
load(file) if File.exist?(file)
|
18
|
+
end
|
19
|
+
|
20
|
+
private
|
21
|
+
|
22
|
+
def _hooks(name)
|
23
|
+
@hooks ||= {}
|
24
|
+
@hooks[name] ||= []
|
25
|
+
@hooks[name]
|
26
|
+
end
|
27
|
+
end
|
28
|
+
end
|
data/lib/spin/version.rb
ADDED
@@ -0,0 +1,234 @@
|
|
1
|
+
|
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
|
+
around do |example|
|
9
|
+
folder = File.expand_path("../tmp", __FILE__)
|
10
|
+
`rm -rf #{folder}`
|
11
|
+
ensure_folder folder
|
12
|
+
Dir.chdir folder do
|
13
|
+
example.call
|
14
|
+
end
|
15
|
+
`rm -rf #{folder}`
|
16
|
+
end
|
17
|
+
|
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]
|
66
|
+
end
|
67
|
+
|
68
|
+
context "with simple setup" do
|
69
|
+
before do
|
70
|
+
write "config/application.rb", "$xxx = 1234"
|
71
|
+
write "test/foo_test.rb", "puts $xxx * 2"
|
72
|
+
@default_pushed = "Spinning up test/foo_test.rb\n"
|
73
|
+
end
|
74
|
+
|
75
|
+
it "shows help when no arguments are given" do
|
76
|
+
spin("", :fail => true).should include("General Options:")
|
77
|
+
end
|
78
|
+
|
79
|
+
it "can serve and push" do
|
80
|
+
served, pushed = serve_and_push("", "test/foo_test.rb")
|
81
|
+
served.should include "Preloaded Rails env in "
|
82
|
+
served.should include "2468"
|
83
|
+
pushed.first.should == @default_pushed
|
84
|
+
end
|
85
|
+
|
86
|
+
it "can run files without .rb extension" do
|
87
|
+
served, pushed = serve_and_push("", "test/foo_test")
|
88
|
+
served.should include "Preloaded Rails env in "
|
89
|
+
served.should include "2468"
|
90
|
+
pushed.first.should == @default_pushed
|
91
|
+
end
|
92
|
+
|
93
|
+
it "can run multiple times" do
|
94
|
+
write "test/foo_test.rb", "puts $xxx *= 2"
|
95
|
+
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 "
|
97
|
+
served.scan("2468").size.should == 3
|
98
|
+
pushed.size.should == 3
|
99
|
+
pushed.each{|x| x.should == @default_pushed }
|
100
|
+
end
|
101
|
+
|
102
|
+
it "can run multiple files at once" do
|
103
|
+
write "test/bar_test.rb", "puts $xxx / 2"
|
104
|
+
served, pushed = serve_and_push("", "test/foo_test.rb test/bar_test.rb")
|
105
|
+
served.should include "Preloaded Rails env in "
|
106
|
+
served.should include "2468"
|
107
|
+
served.should include "617"
|
108
|
+
pushed.first.should == "Spinning up test/foo_test.rb test/bar_test.rb\n"
|
109
|
+
end
|
110
|
+
|
111
|
+
it "complains when the preloaded file cannot be found" do
|
112
|
+
delete "config/application.rb"
|
113
|
+
write "test/foo_test.rb", "puts 2468"
|
114
|
+
served, pushed = serve_and_push("", "test/foo_test.rb")
|
115
|
+
served.should_not include "Preloaded Rails env in "
|
116
|
+
served.should include "Could not find config/application.rb. Are you running"
|
117
|
+
served.should include "2468"
|
118
|
+
pushed.first.should == @default_pushed
|
119
|
+
end
|
120
|
+
|
121
|
+
context "RSpec" do
|
122
|
+
before do
|
123
|
+
write "config/application.rb", "module RSpec;end"
|
124
|
+
end
|
125
|
+
|
126
|
+
it "can run files" do
|
127
|
+
write "spec/foo_spec.rb", "RSpec.configure{}; puts 'YES'"
|
128
|
+
served, pushed = serve_and_push("", "spec/foo_spec.rb")
|
129
|
+
served.should include "YES"
|
130
|
+
end
|
131
|
+
|
132
|
+
it "can run by line" do
|
133
|
+
write "spec/foo_spec.rb", <<-RUBY
|
134
|
+
describe "x" do
|
135
|
+
it("a"){ puts "AAA" }
|
136
|
+
it("b"){ puts "BBB" }
|
137
|
+
it("c"){ puts "CCC" }
|
138
|
+
end
|
139
|
+
RUBY
|
140
|
+
served, pushed = serve_and_push("", "spec/foo_spec.rb:3")
|
141
|
+
served.should_not include "AAA"
|
142
|
+
served.should include "BBB"
|
143
|
+
served.should_not include "CCC"
|
144
|
+
end
|
145
|
+
end
|
146
|
+
|
147
|
+
context "options" do
|
148
|
+
it "can show current version" do
|
149
|
+
spin("--version").should =~ /^\d+\.\d+\.\d+/
|
150
|
+
end
|
151
|
+
|
152
|
+
it "can show help" do
|
153
|
+
spin("--help").should include("General Options:")
|
154
|
+
end
|
155
|
+
|
156
|
+
it "can --push-results" do
|
157
|
+
served, pushed = serve_and_push("--push-results", "test/foo_test.rb")
|
158
|
+
served.should include "Preloaded Rails env in "
|
159
|
+
served.should_not include "2468"
|
160
|
+
pushed.first.should include "2468"
|
161
|
+
end
|
162
|
+
|
163
|
+
it "can --preload a different file" do
|
164
|
+
write "config/application.rb", "raise"
|
165
|
+
write "config/environment.rb", "$xxx = 1234"
|
166
|
+
served, pushed = serve_and_push("--preload config/environment.rb", "test/foo_test.rb")
|
167
|
+
served.should include "Preloaded Rails env in "
|
168
|
+
served.should include "2468"
|
169
|
+
pushed.first.should == @default_pushed
|
170
|
+
end
|
171
|
+
|
172
|
+
it "can add load paths via -I" do
|
173
|
+
write "lib/bar.rb", "puts 'bar'"
|
174
|
+
write "test/foo_test.rb", "require 'bar'"
|
175
|
+
served, pushed = serve_and_push("-Itest:lib", "test/foo_test.rb")
|
176
|
+
served.should include "bar"
|
177
|
+
pushed.first.should == @default_pushed
|
178
|
+
end
|
179
|
+
|
180
|
+
it "ignores -e" do
|
181
|
+
served, pushed = serve_and_push("-e", "test/foo_test.rb -e")
|
182
|
+
served.should include "Preloaded Rails env in "
|
183
|
+
served.should include "2468"
|
184
|
+
pushed.first.should == @default_pushed
|
185
|
+
end
|
186
|
+
|
187
|
+
# TODO process never reaches after the fork block with only 1 push command
|
188
|
+
it "can show total execution time" do
|
189
|
+
served, pushed = serve_and_push("--time", ["test/foo_test.rb", "test/foo_test.rb"])
|
190
|
+
served.should include "Total execution time was 0."
|
191
|
+
pushed.first.should == @default_pushed
|
192
|
+
end
|
193
|
+
end
|
194
|
+
|
195
|
+
context "hooks" do
|
196
|
+
before do
|
197
|
+
write "config/application.rb", "$calls << :real_preload"
|
198
|
+
write "test/calls_test.rb", "puts '>>' + $calls.inspect + '<<'"
|
199
|
+
end
|
200
|
+
|
201
|
+
it "calls preload hooks in correct order" do
|
202
|
+
write ".spin.rb", <<-RUBY
|
203
|
+
$calls = []
|
204
|
+
[:before_preload, :after_preload].each do |hook|
|
205
|
+
Spin.hook(hook) { $calls << hook }
|
206
|
+
end
|
207
|
+
RUBY
|
208
|
+
served, pushed = serve_and_push("--time", "test/calls_test.rb")
|
209
|
+
served[/>>.*<</].should == ">>[:before_preload, :real_preload, :after_preload]<<"
|
210
|
+
end
|
211
|
+
|
212
|
+
it "can have multiple hooks" do
|
213
|
+
write ".spin.rb", <<-RUBY
|
214
|
+
$calls = []
|
215
|
+
Spin.hook(:before_preload) { $calls << :before_preload_1 }
|
216
|
+
Spin.hook(:before_preload) { $calls << :before_preload_2 }
|
217
|
+
RUBY
|
218
|
+
served, pushed = serve_and_push("--time", "test/calls_test.rb")
|
219
|
+
served[/>>.*<</].should == ">>[:before_preload_1, :before_preload_2, :real_preload]<<"
|
220
|
+
end
|
221
|
+
|
222
|
+
it "can hook before/after fork" do
|
223
|
+
write ".spin.rb", <<-RUBY
|
224
|
+
$calls = []
|
225
|
+
[:before_fork, :after_fork].each do |hook|
|
226
|
+
Spin.hook(hook) { $calls << hook }
|
227
|
+
end
|
228
|
+
RUBY
|
229
|
+
served, pushed = serve_and_push("--time", "test/calls_test.rb")
|
230
|
+
served[/>>.*<</].should == ">>[:real_preload, :before_fork, :after_fork]<<"
|
231
|
+
end
|
232
|
+
end
|
233
|
+
end
|
234
|
+
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.6.0
|
5
5
|
prerelease:
|
6
6
|
platform: ruby
|
7
7
|
authors:
|
@@ -9,7 +9,7 @@ authors:
|
|
9
9
|
autorequire:
|
10
10
|
bindir: bin
|
11
11
|
cert_chain: []
|
12
|
-
date:
|
12
|
+
date: 2013-02-13 00:00:00.000000000 Z
|
13
13
|
dependencies: []
|
14
14
|
description: ! 'Spin preloads your Rails environment to speed up your autotest(ish)
|
15
15
|
workflow.
|
@@ -24,8 +24,13 @@ executables:
|
|
24
24
|
extensions: []
|
25
25
|
extra_rdoc_files: []
|
26
26
|
files:
|
27
|
-
-
|
28
|
-
|
27
|
+
- README.md
|
28
|
+
- lib/spin/cli.rb
|
29
|
+
- lib/spin/hooks.rb
|
30
|
+
- lib/spin/version.rb
|
31
|
+
- lib/spin.rb
|
32
|
+
- spec/integration_spec.rb
|
33
|
+
- bin/spin
|
29
34
|
homepage: http://jstorimer.github.com/spin
|
30
35
|
licenses: []
|
31
36
|
post_install_message:
|
@@ -46,8 +51,9 @@ required_rubygems_version: !ruby/object:Gem::Requirement
|
|
46
51
|
version: '0'
|
47
52
|
requirements: []
|
48
53
|
rubyforge_project:
|
49
|
-
rubygems_version: 1.8.
|
54
|
+
rubygems_version: 1.8.25
|
50
55
|
signing_key:
|
51
56
|
specification_version: 3
|
52
57
|
summary: Spin preloads your Rails environment to speed up your autotest(ish) workflow.
|
53
|
-
test_files:
|
58
|
+
test_files:
|
59
|
+
- spec/integration_spec.rb
|