spring 0.0.7 → 0.0.8
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/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
|