spring 0.0.7 → 0.0.8
Sign up to get free protection for your applications and to get access to all the features.
- data/CHANGELOG.md +11 -0
- data/Gemfile +4 -0
- data/README.md +49 -24
- data/lib/spring/application.rb +30 -30
- data/lib/spring/application_manager.rb +34 -15
- data/lib/spring/client.rb +9 -7
- data/lib/spring/client/binstub.rb +8 -3
- data/lib/spring/client/help.rb +20 -32
- data/lib/spring/client/rails.rb +29 -0
- data/lib/spring/client/run.rb +55 -51
- data/lib/spring/client/start.rb +17 -0
- data/lib/spring/client/status.rb +1 -1
- data/lib/spring/client/stop.rb +1 -1
- data/lib/spring/commands.rb +36 -20
- data/lib/spring/errors.rb +3 -0
- data/lib/spring/server.rb +36 -12
- data/lib/spring/version.rb +1 -1
- data/lib/spring/watcher.rb +28 -0
- data/lib/spring/watcher/abstract.rb +83 -0
- data/lib/spring/watcher/listen.rb +54 -0
- data/lib/spring/watcher/polling.rb +59 -0
- data/test/acceptance/app_test.rb +62 -32
- data/test/apps/rails-3-2/Gemfile +5 -0
- data/test/unit/client/help_test.rb +27 -19
- data/test/unit/commands_test.rb +26 -1
- data/test/unit/watcher_test.rb +171 -0
- metadata +12 -6
- data/lib/spring/application_watcher.rb +0 -43
- data/test/unit/application_watcher_test.rb +0 -67
data/CHANGELOG.md
ADDED
@@ -0,0 +1,11 @@
|
|
1
|
+
## 0.0.8
|
2
|
+
|
3
|
+
* Renamed `spring test` to `spring testunit`.
|
4
|
+
* Implemented `spring rails` to replace `spring
|
5
|
+
[console|runner|generate]`.
|
6
|
+
* `config/spring.rb` is only loaded in the server process, so you can
|
7
|
+
require stuff from other gems there without performance implications.
|
8
|
+
* File watcher no longer pays attention to files outside of your
|
9
|
+
application root directory.
|
10
|
+
* You can use the `listen` gem for less CPU intensive file watching. See
|
11
|
+
README.
|
data/Gemfile
CHANGED
data/README.md
CHANGED
@@ -67,7 +67,7 @@ cd /path/to/spring/test/apps/rails-3-2
|
|
67
67
|
We can run a test:
|
68
68
|
|
69
69
|
```
|
70
|
-
$ time spring
|
70
|
+
$ time spring testunit test/functional/posts_controller_test.rb
|
71
71
|
Run options:
|
72
72
|
|
73
73
|
# Running tests:
|
@@ -100,7 +100,7 @@ the processes will be killed automatically.
|
|
100
100
|
Running the test is faster next time:
|
101
101
|
|
102
102
|
```
|
103
|
-
$ time spring
|
103
|
+
$ time spring testunit test/functional/posts_controller_test.rb
|
104
104
|
Run options:
|
105
105
|
|
106
106
|
# Running tests:
|
@@ -116,25 +116,25 @@ user 0m0.276s
|
|
116
116
|
sys 0m0.059s
|
117
117
|
```
|
118
118
|
|
119
|
-
Running `spring
|
119
|
+
Running `spring testunit`, `spring rake`, `spring rails`, etc gets a bit
|
120
120
|
tedious. It also suffers from a performance issue in Rubygems ([which I
|
121
121
|
am actively working on](https://github.com/rubygems/rubygems/pull/435))
|
122
122
|
which means the `spring` command takes a while to start up. The more
|
123
123
|
gems you have, the longer it takes.
|
124
124
|
|
125
125
|
Spring binstubs solve both of these problems. If you will be running the
|
126
|
-
`
|
126
|
+
`testunit` command regularly, run:
|
127
127
|
|
128
128
|
```
|
129
|
-
$ spring binstub
|
129
|
+
$ spring binstub testunit
|
130
130
|
```
|
131
131
|
|
132
|
-
This generates a `bin/spring` and a `bin/
|
133
|
-
`spring` and `spring
|
132
|
+
This generates a `bin/spring` and a `bin/testunit`, which allows you to run
|
133
|
+
`spring` and `spring testunit` in a way that doesn't trigger the Rubygems
|
134
134
|
performance bug:
|
135
135
|
|
136
136
|
```
|
137
|
-
$ time bin/
|
137
|
+
$ time bin/testunit test/functional/posts_controller_test.rb
|
138
138
|
Run options:
|
139
139
|
|
140
140
|
# Running tests:
|
@@ -150,6 +150,10 @@ user 0m0.077s
|
|
150
150
|
sys 0m0.059s
|
151
151
|
```
|
152
152
|
|
153
|
+
You can add "./bin" to your `PATH` when in your application's directory
|
154
|
+
[with direnv](https://github.com/zimbatm/direnv), but you should
|
155
|
+
recognise and understand the security implications of using that.
|
156
|
+
|
153
157
|
If we edit any of the application files, or test files, the change will
|
154
158
|
be picked up on the next run, without the background process
|
155
159
|
having to be restarted.
|
@@ -220,12 +224,12 @@ order to make it easy for people to try spring. However in the future
|
|
220
224
|
the code to use a specific test framework should not be contained in the
|
221
225
|
spring repository.
|
222
226
|
|
223
|
-
### `
|
227
|
+
### `testunit`
|
224
228
|
|
225
229
|
Runs a test (e.g. Test::Unit, MiniTest::Unit, etc.) Preloads the `test_helper` file.
|
226
230
|
|
227
231
|
This command can also recursively run a directory of tests. For example,
|
228
|
-
`spring
|
232
|
+
`spring testunit test/functional` will run `test/functional/**/*_test.rb`.
|
229
233
|
|
230
234
|
### `rspec`
|
231
235
|
|
@@ -240,19 +244,12 @@ Runs a cucumber feature.
|
|
240
244
|
|
241
245
|
Runs a rake task.
|
242
246
|
|
243
|
-
### `console`
|
244
|
-
|
245
|
-
Boots into the Rails console. Currently this is usable but not perfect,
|
246
|
-
for example you can't scroll back through command history. (That will be
|
247
|
-
fixed.)
|
248
|
-
|
249
|
-
### `generate`
|
250
|
-
|
251
|
-
Runs a Rails generator.
|
252
|
-
|
253
|
-
### `runner`
|
247
|
+
### `rails console`, `rails generate`, `rails runner`
|
254
248
|
|
255
|
-
|
249
|
+
These execute the rails command you already know and love. If you run
|
250
|
+
a different sub command (e.g. `rails server`) then spring will automatically
|
251
|
+
pass it through to the underlying `rails` executable (without the
|
252
|
+
speed-up).
|
256
253
|
|
257
254
|
## Configuration
|
258
255
|
|
@@ -279,13 +276,13 @@ preloads for every command:
|
|
279
276
|
|
280
277
|
```ruby
|
281
278
|
# if your test helper is called "helper"
|
282
|
-
Commands::Command::
|
279
|
+
Commands::Command::TestUnit.preloads = %w(helper)
|
283
280
|
|
284
281
|
# if you don't want to preload spec_helper.rb
|
285
282
|
Commands::Command::RSpec.preloads = []
|
286
283
|
|
287
284
|
# if you want to preload additional files for the console
|
288
|
-
Commands::Command::
|
285
|
+
Commands::Command::RailsConsole.preloads << 'extenstions/console_helper'
|
289
286
|
```
|
290
287
|
|
291
288
|
### after fork callbacks
|
@@ -308,3 +305,31 @@ If you want to register multiple callbacks you can simply call
|
|
308
305
|
|
309
306
|
Spring needs a tmp directory. This will default to `Rails.root.join('tmp', 'spring')`.
|
310
307
|
You can set your own configuration directory by setting the `SPRING_TMP_PATH` environment variable.
|
308
|
+
|
309
|
+
### Watching files and directories
|
310
|
+
|
311
|
+
As mentioned above, Spring will automatically detect file changes to any file loaded when the server
|
312
|
+
boots. If you would like to watch additional files or directories, use
|
313
|
+
`Spring.watch`:
|
314
|
+
|
315
|
+
```ruby
|
316
|
+
Spring.watch "#{Rails.root}/spec/factories"
|
317
|
+
```
|
318
|
+
|
319
|
+
### Filesystem polling
|
320
|
+
|
321
|
+
By default Spring will check the filesystem for changes once every 0.2 seconds. This
|
322
|
+
method requires zero configuration, but if you find that it's using too
|
323
|
+
much CPU, then you can turn on event-based file system listening by
|
324
|
+
adding the following to to your `Gemfile`:
|
325
|
+
|
326
|
+
```ruby
|
327
|
+
group :development, :test do
|
328
|
+
gem 'listen'
|
329
|
+
gem 'rb-inotify', :require => false # linux
|
330
|
+
gem 'rb-fsevent', :require => false # mac os x
|
331
|
+
gem 'rb-kqueue', :require => false # bsd
|
332
|
+
end
|
333
|
+
```
|
334
|
+
|
335
|
+
Note that this make the initial application startup slightly slower.
|
data/lib/spring/application.rb
CHANGED
@@ -1,24 +1,21 @@
|
|
1
|
-
require "spring/configuration"
|
2
|
-
require "spring/application_watcher"
|
3
|
-
require "spring/commands"
|
4
1
|
require "set"
|
2
|
+
require "json"
|
5
3
|
|
6
|
-
|
7
|
-
|
8
|
-
attr_accessor :application_watcher
|
9
|
-
end
|
10
|
-
|
11
|
-
self.application_watcher = ApplicationWatcher.new
|
4
|
+
require "spring/configuration"
|
5
|
+
require "spring/watcher"
|
12
6
|
|
7
|
+
module Spring
|
13
8
|
class Application
|
14
|
-
WATCH_INTERVAL = 0.2
|
15
|
-
|
16
9
|
attr_reader :manager, :watcher
|
17
10
|
|
18
|
-
def initialize(manager, watcher = Spring.
|
11
|
+
def initialize(manager, watcher = Spring.watcher)
|
19
12
|
@manager = manager
|
20
13
|
@watcher = watcher
|
21
14
|
@setup = Set.new
|
15
|
+
|
16
|
+
# Workaround for GC bug in Ruby 2 which causes segfaults if watcher.to_io
|
17
|
+
# instances get dereffed.
|
18
|
+
@fds = [manager, watcher.to_io]
|
22
19
|
end
|
23
20
|
|
24
21
|
def start
|
@@ -33,31 +30,33 @@ module Spring
|
|
33
30
|
|
34
31
|
require Spring.application_root_path.join("config", "environment")
|
35
32
|
|
36
|
-
watcher.
|
37
|
-
watcher.
|
38
|
-
watcher.
|
33
|
+
watcher.add loaded_application_features
|
34
|
+
watcher.add "Gemfile", "Gemfile.lock"
|
35
|
+
watcher.add Rails.application.paths["config/initializers"]
|
39
36
|
|
40
37
|
run
|
41
38
|
end
|
42
39
|
|
43
40
|
def run
|
41
|
+
watcher.start
|
42
|
+
|
44
43
|
loop do
|
45
|
-
|
46
|
-
serve manager.recv_io(UNIXSocket)
|
47
|
-
end
|
48
|
-
end
|
44
|
+
IO.select(@fds)
|
49
45
|
|
50
|
-
|
51
|
-
|
52
|
-
|
46
|
+
if watcher.stale?
|
47
|
+
exit
|
48
|
+
else
|
49
|
+
serve manager.recv_io(UNIXSocket)
|
50
|
+
end
|
53
51
|
end
|
54
52
|
end
|
55
53
|
|
56
54
|
def serve(client)
|
57
|
-
|
58
|
-
|
59
|
-
|
60
|
-
|
55
|
+
manager.puts
|
56
|
+
|
57
|
+
streams = 3.times.map { client.recv_io }
|
58
|
+
args = JSON.parse(client.read(client.gets.to_i))
|
59
|
+
command = Spring.command(args.shift)
|
61
60
|
|
62
61
|
setup command
|
63
62
|
|
@@ -92,16 +91,13 @@ module Spring
|
|
92
91
|
# The command might need to require some files in the
|
93
92
|
# main process so that they are cached. For example a test command wants to
|
94
93
|
# load the helper file once and have it cached.
|
95
|
-
#
|
96
|
-
# FIXME: The watcher.add_files will reset the watcher, which may mean that
|
97
|
-
# previous changes to already-loaded files are missed.
|
98
94
|
def setup(command)
|
99
95
|
return if @setup.include?(command.class)
|
100
96
|
@setup << command.class
|
101
97
|
|
102
98
|
if command.respond_to?(:setup)
|
103
99
|
command.setup
|
104
|
-
watcher.
|
100
|
+
watcher.add loaded_application_features # loaded features may have changed
|
105
101
|
end
|
106
102
|
end
|
107
103
|
|
@@ -110,5 +106,9 @@ module Spring
|
|
110
106
|
callback.call
|
111
107
|
end
|
112
108
|
end
|
109
|
+
|
110
|
+
def loaded_application_features
|
111
|
+
$LOADED_FEATURES.select { |f| f.start_with?(File.realpath(Rails.root)) }
|
112
|
+
end
|
113
113
|
end
|
114
114
|
end
|
@@ -1,11 +1,9 @@
|
|
1
1
|
require "socket"
|
2
|
+
require "thread"
|
2
3
|
require "spring/application"
|
3
|
-
require "mutex_m"
|
4
4
|
|
5
5
|
module Spring
|
6
6
|
class ApplicationManager
|
7
|
-
include Mutex_m
|
8
|
-
|
9
7
|
attr_reader :pid, :child, :app_env, :spring_env
|
10
8
|
|
11
9
|
def initialize(app_env)
|
@@ -13,6 +11,16 @@ module Spring
|
|
13
11
|
|
14
12
|
@app_env = app_env
|
15
13
|
@spring_env = Env.new
|
14
|
+
@mutex = Mutex.new
|
15
|
+
end
|
16
|
+
|
17
|
+
# We're not using @mutex.synchronize to avoid the weird "<internal:prelude>:10"
|
18
|
+
# line which messes with backtraces in e.g. rspec
|
19
|
+
def synchronize
|
20
|
+
@mutex.lock
|
21
|
+
yield
|
22
|
+
ensure
|
23
|
+
@mutex.unlock
|
16
24
|
end
|
17
25
|
|
18
26
|
def start
|
@@ -31,31 +39,36 @@ module Spring
|
|
31
39
|
@pid
|
32
40
|
end
|
33
41
|
|
34
|
-
|
35
|
-
def run(client)
|
36
|
-
@client = client
|
37
|
-
|
42
|
+
def with_child
|
38
43
|
synchronize do
|
39
44
|
if alive?
|
40
45
|
begin
|
41
|
-
|
42
|
-
rescue Errno::EPIPE
|
43
|
-
#
|
46
|
+
yield
|
47
|
+
rescue Errno::ECONNRESET, Errno::EPIPE
|
48
|
+
# The child has died but has not been collected by the wait thread yet,
|
49
|
+
# so start a new child and try again.
|
44
50
|
start
|
45
|
-
|
51
|
+
yield
|
46
52
|
end
|
47
53
|
else
|
48
54
|
start
|
49
|
-
|
55
|
+
yield
|
50
56
|
end
|
51
57
|
end
|
58
|
+
end
|
59
|
+
|
60
|
+
# Returns the pid of the process running the command, or nil if the application process died.
|
61
|
+
def run(client)
|
62
|
+
with_child do
|
63
|
+
child.send_io client
|
64
|
+
child.gets
|
65
|
+
end
|
52
66
|
|
53
67
|
child.gets.chomp.to_i # get the pid
|
54
68
|
rescue Errno::ECONNRESET, Errno::EPIPE
|
55
69
|
nil
|
56
70
|
ensure
|
57
|
-
|
58
|
-
@client = nil
|
71
|
+
client.close
|
59
72
|
end
|
60
73
|
|
61
74
|
def stop
|
@@ -68,11 +81,17 @@ module Spring
|
|
68
81
|
@child, child_socket = UNIXSocket.pair
|
69
82
|
@pid = fork {
|
70
83
|
[STDOUT, STDERR].each { |s| s.reopen('/dev/null', 'w') } if silence
|
71
|
-
|
84
|
+
|
85
|
+
(ObjectSpace.each_object(IO).to_a - [STDOUT, STDERR, STDIN, child_socket])
|
86
|
+
.reject(&:closed?)
|
87
|
+
.each(&:close)
|
88
|
+
|
72
89
|
ENV['RAILS_ENV'] = ENV['RACK_ENV'] = app_env
|
90
|
+
|
73
91
|
ProcessTitleUpdater.run { |distance|
|
74
92
|
"spring app | #{spring_env.app_name} | started #{distance} ago | #{app_env} mode"
|
75
93
|
}
|
94
|
+
|
76
95
|
Application.new(child_socket).start
|
77
96
|
}
|
78
97
|
child_socket.close
|
data/lib/spring/client.rb
CHANGED
@@ -3,31 +3,33 @@ require "spring/client/command"
|
|
3
3
|
require "spring/client/run"
|
4
4
|
require "spring/client/help"
|
5
5
|
require "spring/client/binstub"
|
6
|
+
require "spring/client/start"
|
6
7
|
require "spring/client/stop"
|
7
8
|
require "spring/client/status"
|
9
|
+
require "spring/client/rails"
|
8
10
|
|
9
11
|
module Spring
|
10
12
|
module Client
|
11
13
|
COMMANDS = {
|
12
14
|
"help" => Client::Help,
|
13
15
|
"binstub" => Client::Binstub,
|
16
|
+
"start" => Client::Start,
|
14
17
|
"stop" => Client::Stop,
|
15
|
-
"status" => Client::Status
|
18
|
+
"status" => Client::Status,
|
19
|
+
"rails" => Client::Rails
|
16
20
|
}
|
17
21
|
|
18
22
|
def self.run(args)
|
19
23
|
command_for(args.first).call(args)
|
24
|
+
rescue CommandNotFound
|
25
|
+
Client::Help.call(args)
|
20
26
|
rescue ClientError => e
|
21
|
-
|
27
|
+
$stderr.puts e.message
|
22
28
|
exit 1
|
23
29
|
end
|
24
30
|
|
25
31
|
def self.command_for(name)
|
26
|
-
|
27
|
-
Client::Run
|
28
|
-
else
|
29
|
-
COMMANDS.fetch(name) { Client::Help }
|
30
|
-
end
|
32
|
+
COMMANDS[name] || Client::Run
|
31
33
|
end
|
32
34
|
end
|
33
35
|
end
|
@@ -7,6 +7,11 @@ module Spring
|
|
7
7
|
"Generate spring based binstubs."
|
8
8
|
end
|
9
9
|
|
10
|
+
def self.call(args)
|
11
|
+
require "spring/commands"
|
12
|
+
super
|
13
|
+
end
|
14
|
+
|
10
15
|
def initialize(args)
|
11
16
|
super
|
12
17
|
|
@@ -15,12 +20,12 @@ module Spring
|
|
15
20
|
end
|
16
21
|
|
17
22
|
def call
|
18
|
-
if Spring.command?(name)
|
23
|
+
if Spring.command?(name) || name == "rails"
|
19
24
|
bindir.mkdir unless bindir.exist?
|
20
25
|
generate_spring_binstub
|
21
26
|
generate_command_binstub
|
22
27
|
else
|
23
|
-
|
28
|
+
$stderr.puts "The '#{name}' command is not known to spring."
|
24
29
|
exit 1
|
25
30
|
end
|
26
31
|
end
|
@@ -50,7 +55,7 @@ if spec
|
|
50
55
|
spec.activate
|
51
56
|
load spec.bin_file("spring")
|
52
57
|
else
|
53
|
-
|
58
|
+
$stderr.puts "Could not find spring gem in #{Gem::Specification.dirs.join(", ")}."
|
54
59
|
exit 1
|
55
60
|
end
|
56
61
|
CODE
|