spring 0.0.5 → 0.0.6

Sign up to get free protection for your applications and to get access to all the features.
data/README.md CHANGED
@@ -1,5 +1,7 @@
1
1
  # Spring
2
2
 
3
+ [![Build Status](https://travis-ci.org/jonleighton/spring.png?branch=master)](https://travis-ci.org/jonleighton/spring)
4
+
3
5
  Spring is a Rails application preloader. It's trying to solve the same
4
6
  problem as [spork](https://github.com/sporkrb/spork),
5
7
  [zeus](https://github.com/burke/zeus) and
@@ -42,16 +44,9 @@ At the moment only MRI 1.9.3 / Rails 3.2 is supported.
42
44
 
43
45
  ## Usage
44
46
 
45
- Install the `spring` gem. You may wish to add it to your bundle, but
46
- it's not necessary. If you do, I don't recommend invoking `spring`
47
- via `bundle exec` as that will make it slow. Just call `spring`
48
- on its own.
49
-
50
- You now have a `spring` command. Do a `rbenv rehash` if necessary. Note
51
- that on my machine I had over 700 gems installed, and activating the gem
52
- to run the `spring` command added over 0.5s to the runtime. Clearing out
53
- my gems solved the problem, but I'd like to figure out a way to speed
54
- this up.
47
+ Install the `spring` gem. You can add it to your Gemfile if you like but
48
+ it's optional. You now have a `spring` command. Don't use it with
49
+ `bundle exec` or it will be extremely slow.
55
50
 
56
51
  For this walkthrough, I'm using the test app in the Spring repository:
57
52
 
@@ -69,28 +64,28 @@ Run options:
69
64
 
70
65
  .......
71
66
 
72
- Finished tests in 0.169882s, 41.2051 tests/s, 58.8644 assertions/s.
67
+ Finished tests in 0.127245s, 55.0121 tests/s, 78.5887 assertions/s.
73
68
 
74
69
  7 tests, 10 assertions, 0 failures, 0 errors, 0 skips
75
70
 
76
- real 0m1.858s
77
- user 0m0.184s
78
- sys 0m0.067s
71
+ real 0m2.165s
72
+ user 0m0.281s
73
+ sys 0m0.066s
79
74
  ```
80
75
 
81
76
  That booted our app in the background:
82
77
 
83
78
  ```
84
79
  $ ps ax | grep spring
85
- 8692 pts/6 Sl 0:00 /home/turnip/.rbenv/versions/1.9.3-p194/bin/ruby -I ... -r spring/server -r bundler/setup -e Spring::Server.boot
86
- 8698 pts/6 Sl 0:02 /home/turnip/.rbenv/versions/1.9.3-p194/bin/ruby -I ... -r spring/server -r bundler/setup -e Spring::Server.boot
80
+ 26150 pts/3 Sl 0:00 spring server | rails-3-2 | started 2013-02-01 20:16:40 +0000
81
+ 26155 pts/3 Sl 0:02 spring app | rails-3-2 | started 2013-02-01 20:16:40 +0000 | test mode
87
82
  ```
88
83
 
89
84
  We can see two processes, one is the Spring server, the other is the
90
85
  application running in the test environment. When we close the terminal,
91
86
  the processes will be killed automatically.
92
87
 
93
- Running the tests is faster next time:
88
+ Running the test is faster next time:
94
89
 
95
90
  ```
96
91
  $ time spring test test/functional/posts_controller_test.rb
@@ -100,42 +95,76 @@ Run options:
100
95
 
101
96
  .......
102
97
 
103
- Finished tests in 0.162963s, 42.9546 tests/s, 61.3637 assertions/s.
98
+ Finished tests in 0.176896s, 39.5714 tests/s, 56.5305 assertions/s.
104
99
 
105
100
  7 tests, 10 assertions, 0 failures, 0 errors, 0 skips
106
101
 
107
- real 0m0.492s
108
- user 0m0.179s
109
- sys 0m0.063s
102
+ real 0m0.610s
103
+ user 0m0.276s
104
+ sys 0m0.059s
105
+ ```
106
+
107
+ Running `spring test`, `spring rake`, `spring console`, etc gets a bit
108
+ tedious. It also suffers from a performance issue in Rubygems ([which I
109
+ am actively working on](https://github.com/rubygems/rubygems/pull/435))
110
+ which means the `spring` command takes a while to start up. The more
111
+ gems you have, the longer it takes.
112
+
113
+ Spring binstubs solve both of these problems. If you will be running the
114
+ `test` command regularly, run:
115
+
116
+ ```
117
+ $ spring binstub test
118
+ ```
119
+
120
+ This generates a `bin/spring` and a `bin/test`, which allows you to run
121
+ `spring` and `spring test` in a way that doesn't trigger the Rubygems
122
+ performance bug:
123
+
124
+ ```
125
+ $ time bin/test test/functional/posts_controller_test.rb
126
+ Run options:
127
+
128
+ # Running tests:
129
+
130
+ .......
131
+
132
+ Finished tests in 0.166585s, 42.0207 tests/s, 60.0296 assertions/s.
133
+
134
+ 7 tests, 10 assertions, 0 failures, 0 errors, 0 skips
135
+
136
+ real 0m0.407s
137
+ user 0m0.077s
138
+ sys 0m0.059s
110
139
  ```
111
140
 
112
141
  If we edit any of the application files, or test files, the change will
113
- be picked up on the next run, without having the background process
114
- having to be restarted. This works even if you e.g. referenced your
115
- `Post` model in an initializer and then edited it.
142
+ be picked up on the next run, without the background process
143
+ having to be restarted.
116
144
 
117
145
  If we edit any of the preloaded files, the application needs to restart
118
- automatically. Note that the application process id is 8698 above. Let's
119
- "edit" the `config/application.rb`:
146
+ automatically. Let's "edit" `config/application.rb`:
120
147
 
121
148
  ```
122
149
  $ touch config/application.rb
123
150
  $ ps ax | grep spring
124
- 8692 pts/6 Sl 0:00 /home/turnip/.rbenv/versions/1.9.3-p194/bin/ruby -I ... -r spring/server -r bundler/setup -e Spring::Server.boot
125
- 8876 pts/6 Sl 0:00 /home/turnip/.rbenv/versions/1.9.3-p194/bin/ruby -I ... -r spring/server -r bundler/setup -e Spring::Server.boot
151
+ 26150 pts/3 Sl 0:00 spring server | rails-3-2 | started 2013-02-01 20:16:40 +0000
152
+ 26556 pts/3 Sl 0:00 spring app | rails-3-2 | started 2013-02-01 20:20:07 +0000 | test mode
126
153
  ```
127
154
 
128
155
  The application process detected the change and exited. The server process
129
156
  then detected that the application process exited, so it started a new application.
130
- All of this happens automatically in the background. Next time we run a
131
- command we'll be running against a fresh application.
157
+ All of this happened automatically. Next time we run a
158
+ command we'll be running against a fresh application. We can see that
159
+ the start time and PID of the app process has changed.
132
160
 
133
161
  If we run a command that uses a different environment, then it gets
134
162
  booted up. For example, the `rake` command uses the `development`
135
163
  environment by default:
136
164
 
137
165
  ```
138
- $ time spring rake routes
166
+ $ spring binstub rake
167
+ $ bin/rake routes
139
168
  posts GET /posts(.:format) posts#index
140
169
  POST /posts(.:format) posts#create
141
170
  new_post GET /posts/new(.:format) posts#new
@@ -143,10 +172,6 @@ edit_post GET /posts/:id/edit(.:format) posts#edit
143
172
  post GET /posts/:id(.:format) posts#show
144
173
  PUT /posts/:id(.:format) posts#update
145
174
  DELETE /posts/:id(.:format) posts#destroy
146
-
147
- real 0m0.763s
148
- user 0m0.185s
149
- sys 0m0.063s
150
175
  ```
151
176
 
152
177
  We now have 3 processes: the server, and application in test mode and
@@ -154,26 +179,9 @@ the application in development mode.
154
179
 
155
180
  ```
156
181
  $ ps ax | grep spring
157
- 8692 pts/6 Sl 0:00 /home/turnip/.rbenv/versions/1.9.3-p194/bin/ruby -I ... -r spring/server -r bundler/setup -e Spring::Server.boot
158
- 8876 pts/6 Sl 0:15 /home/turnip/.rbenv/versions/1.9.3-p194/bin/ruby -I ... -r spring/server -r bundler/setup -e Spring::Server.boot
159
- 9088 pts/6 Sl 0:01 /home/turnip/.rbenv/versions/1.9.3-p194/bin/ruby -I ... -r spring/server -r bundler/setup -e Spring::Server.boot
160
- ```
161
-
162
- Running rake is faster the second time:
163
-
164
- ```
165
- $ time spring rake routes
166
- posts GET /posts(.:format) posts#index
167
- POST /posts(.:format) posts#create
168
- new_post GET /posts/new(.:format) posts#new
169
- edit_post GET /posts/:id/edit(.:format) posts#edit
170
- post GET /posts/:id(.:format) posts#show
171
- PUT /posts/:id(.:format) posts#update
172
- DELETE /posts/:id(.:format) posts#destroy
173
-
174
- real 0m0.341s
175
- user 0m0.177s
176
- sys 0m0.070s
182
+ 26150 pts/3 Sl 0:00 spring server | rails-3-2 | started 2013-02-01 20:16:40 +0000
183
+ 26556 pts/3 Sl 0:08 spring app | rails-3-2 | started 2013-02-01 20:20:07 +0000 | test mode
184
+ 26707 pts/3 Sl 0:00 spring app | rails-3-2 | started 2013-02-01 20:22:41 +0000 | development mode
177
185
  ```
178
186
 
179
187
  ## Commands
@@ -184,15 +192,27 @@ Custom commands can be specified in `config/spring.rb`. See
184
192
  [`lib/spring/commands.rb`](https://github.com/jonleighton/spring/blob/master/lib/spring/commands.rb)
185
193
  for examples.
186
194
 
195
+ A bunch of different test frameworks are supported at the moment in
196
+ order to make it easy for people to try spring. However in the future
197
+ the code to use a specific test framework should not be contained in the
198
+ spring repository.
199
+
187
200
  ### `test`
188
201
 
189
202
  Runs a test (e.g. Test::Unit, MiniTest::Unit, etc.) Preloads the `test_helper` file.
190
203
 
204
+ This command can also recursively run a directory of tests. For example,
205
+ `spring test test/functional` will run `test/functional/**/*_test.rb`.
206
+
191
207
  ### `rspec`
192
208
 
193
209
  Runs an rspec spec, exactly the same as the `rspec` executable. Preloads
194
210
  the `spec_helper` file.
195
211
 
212
+ ### `cucumber`
213
+
214
+ Runs a cucumber feature.
215
+
196
216
  ### `rake`
197
217
 
198
218
  Runs a rake task.
@@ -210,3 +230,23 @@ Runs a Rails generator.
210
230
  ### `runner`
211
231
 
212
232
  The Rails runner.
233
+
234
+ ## Configuration
235
+
236
+ ### application_root
237
+
238
+ Spring must know how to find your rails application. If you have a
239
+ normal app everything works out of the box. If you are working on a
240
+ project with a special setup (an engine for example), you must tell
241
+ Spring where your app is located:
242
+
243
+ **config/spring.rb**
244
+
245
+ ```ruby
246
+ Spring.application_root = './test/dummy'
247
+ ```
248
+
249
+ ### tmp directory
250
+
251
+ Spring needs a tmp directory. This will default to `Rails.root.join('tmp', 'spring')`.
252
+ You can set your own configuration directory by setting the `SPRING_TMP_PATH` environment variable.
data/bin/spring CHANGED
@@ -1,5 +1,4 @@
1
1
  #!/usr/bin/env ruby
2
2
 
3
- $LOAD_PATH.unshift File.expand_path('../../lib', __FILE__)
4
- require 'spring'
5
- Spring.run(ARGV)
3
+ require 'spring/client'
4
+ Spring::Client.run(ARGV)
@@ -1,8 +1,9 @@
1
+ require "spring/configuration"
1
2
  require "spring/application_watcher"
2
3
  require "spring/commands"
3
4
  require "set"
4
5
 
5
- class Spring
6
+ module Spring
6
7
  class << self
7
8
  attr_accessor :application_watcher
8
9
  end
@@ -21,13 +22,10 @@ class Spring
21
22
 
22
23
  @stdout = IO.new(STDOUT.fileno)
23
24
  @stderr = IO.new(STDERR.fileno)
24
- @stdin = File.open('/dev/null', 'r')
25
-
26
- STDIN.reopen(@stdin)
27
25
  end
28
26
 
29
27
  def start
30
- require "./config/application"
28
+ require Spring.application_root_path.join("config", "application")
31
29
 
32
30
  # The test environment has config.cache_classes = true set by default.
33
31
  # However, we don't want this to prevent us from performing class reloading,
@@ -36,7 +34,7 @@ class Spring
36
34
  ActiveSupport::Dependencies.mechanism = :load
37
35
  end
38
36
 
39
- require "./config/environment"
37
+ require Spring.application_root_path.join("config", "environment")
40
38
 
41
39
  watcher.add_files $LOADED_FEATURES
42
40
  watcher.add_files ["Gemfile", "Gemfile.lock"].map { |f| "#{Rails.root}/#{f}" }
@@ -60,6 +58,7 @@ class Spring
60
58
 
61
59
  def serve(client)
62
60
  redirect_output(client) do
61
+ stdin = client.recv_io
63
62
  args_length = client.gets.to_i
64
63
  args = args_length.times.map { client.read(client.gets.to_i) }
65
64
  command = Spring.command(args.shift)
@@ -70,6 +69,8 @@ class Spring
70
69
  ActionDispatch::Reloader.prepare!
71
70
 
72
71
  pid = fork {
72
+ Process.setsid
73
+ STDIN.reopen(stdin)
73
74
  IGNORE_SIGNALS.each { |sig| trap(sig, "DEFAULT") }
74
75
  command.call(args)
75
76
  }
@@ -101,13 +102,11 @@ class Spring
101
102
  def redirect_output(socket)
102
103
  STDOUT.reopen socket.recv_io
103
104
  STDERR.reopen socket.recv_io
104
- STDIN.reopen socket.recv_io
105
105
 
106
106
  yield
107
107
  ensure
108
108
  STDOUT.reopen @stdout
109
109
  STDERR.reopen @stderr
110
- STDIN.reopen @stdin
111
110
  end
112
111
  end
113
112
  end
@@ -2,15 +2,17 @@ require "socket"
2
2
  require "spring/application"
3
3
  require "mutex_m"
4
4
 
5
- class Spring
5
+ module Spring
6
6
  class ApplicationManager
7
7
  include Mutex_m
8
8
 
9
- attr_reader :pid, :child, :env
9
+ attr_reader :pid, :child, :app_env, :spring_env
10
10
 
11
- def initialize(env)
11
+ def initialize(app_env)
12
12
  super()
13
- @env = env
13
+
14
+ @app_env = app_env
15
+ @spring_env = Env.new
14
16
  end
15
17
 
16
18
  def start
@@ -56,6 +58,10 @@ class Spring
56
58
  @client = nil
57
59
  end
58
60
 
61
+ def stop
62
+ Process.kill('TERM', pid) if pid
63
+ end
64
+
59
65
  private
60
66
 
61
67
  def start_child(silence = false)
@@ -63,7 +69,8 @@ class Spring
63
69
  @pid = fork {
64
70
  [STDOUT, STDERR].each { |s| s.reopen('/dev/null', 'w') } if silence
65
71
  @client.close if @client
66
- ENV['RAILS_ENV'] = ENV['RACK_ENV'] = env
72
+ ENV['RAILS_ENV'] = ENV['RACK_ENV'] = app_env
73
+ $0 = "spring app | #{spring_env.app_name} | started #{Time.now} | #{app_env} mode"
67
74
  Application.new(child_socket).start
68
75
  }
69
76
  child_socket.close
@@ -1,4 +1,4 @@
1
- class Spring
1
+ module Spring
2
2
  class ApplicationWatcher
3
3
  attr_reader :mtime, :files, :globs
4
4
 
@@ -31,6 +31,9 @@ class Spring
31
31
 
32
32
  def compute_mtime
33
33
  expanded_files.map { |f| File.mtime(f).to_f }.max || 0
34
+ rescue Errno::ENOENT
35
+ # if a file does no longer exist, the watcher is always stale.
36
+ Float::MAX
34
37
  end
35
38
 
36
39
  def expanded_files
@@ -0,0 +1,31 @@
1
+ require "spring/configuration"
2
+ require "spring/client/command"
3
+ require "spring/client/run"
4
+ require "spring/client/help"
5
+ require "spring/client/binstub"
6
+ require "spring/client/stop"
7
+
8
+ module Spring
9
+ module Client
10
+ COMMANDS = {
11
+ "help" => Client::Help,
12
+ "binstub" => Client::Binstub,
13
+ "stop" => Client::Stop
14
+ }
15
+
16
+ def self.run(args)
17
+ command_for(args.first).call(args)
18
+ rescue ClientError => e
19
+ STDERR.puts e.message
20
+ exit 1
21
+ end
22
+
23
+ def self.command_for(name)
24
+ if Spring.command?(name)
25
+ Client::Run
26
+ else
27
+ COMMANDS.fetch(name) { Client::Help }
28
+ end
29
+ end
30
+ end
31
+ end
@@ -0,0 +1,67 @@
1
+ module Spring
2
+ module Client
3
+ class Binstub < Command
4
+ attr_reader :bindir, :name
5
+
6
+ def initialize(args)
7
+ super
8
+
9
+ @bindir = env.root.join("bin")
10
+ @name = args[1]
11
+ end
12
+
13
+ def call
14
+ if Spring.command?(name)
15
+ bindir.mkdir unless bindir.exist?
16
+ generate_spring_binstub
17
+ generate_command_binstub
18
+ else
19
+ STDERR.puts "The '#{name}' command is not known to spring."
20
+ exit 1
21
+ end
22
+ end
23
+
24
+ def spring_binstub
25
+ bindir.join("spring")
26
+ end
27
+
28
+ def command_binstub
29
+ bindir.join(name)
30
+ end
31
+
32
+ def generate_spring_binstub
33
+ File.write(spring_binstub, <<'CODE')
34
+ #!/usr/bin/env ruby
35
+
36
+ # This is a special way of invoking the spring gem in order to
37
+ # work around the performance issue discussed in
38
+ # https://github.com/rubygems/rubygems/pull/435
39
+
40
+ glob = "{#{Gem::Specification.dirs.join(",")}}/spring-*.gemspec"
41
+ candidates = Dir[glob].to_a.sort
42
+
43
+ spec = Gem::Specification.load(candidates.last)
44
+
45
+ if spec
46
+ spec.activate
47
+ load spec.bin_file("spring")
48
+ else
49
+ STDERR.puts "Could not find spring gem in #{Gem::Specification.dirs.join(", ")}."
50
+ exit 1
51
+ end
52
+ CODE
53
+
54
+ spring_binstub.chmod 0755
55
+ end
56
+
57
+ def generate_command_binstub
58
+ File.write(command_binstub, <<CODE)
59
+ #!/usr/bin/env bash
60
+ exec $(dirname $0)/spring #{name} "$@"
61
+ CODE
62
+
63
+ command_binstub.chmod 0755
64
+ end
65
+ end
66
+ end
67
+ end