spring 1.0.0 → 1.3.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.
Files changed (49) hide show
  1. checksums.yaml +4 -4
  2. data/.gitignore +1 -0
  3. data/.travis.yml +6 -1
  4. data/CHANGELOG.md +81 -0
  5. data/CONTRIBUTING.md +52 -0
  6. data/Gemfile +0 -2
  7. data/README.md +137 -54
  8. data/Rakefile +1 -1
  9. data/bin/spring +17 -0
  10. data/lib/spring/application/boot.rb +18 -0
  11. data/lib/spring/application.rb +183 -76
  12. data/lib/spring/application_manager.rb +54 -40
  13. data/lib/spring/binstub.rb +13 -0
  14. data/lib/spring/boot.rb +8 -0
  15. data/lib/spring/client/binstub.rb +150 -39
  16. data/lib/spring/client/help.rb +4 -12
  17. data/lib/spring/client/rails.rb +2 -3
  18. data/lib/spring/client/run.rb +69 -11
  19. data/lib/spring/client/status.rb +2 -2
  20. data/lib/spring/client/stop.rb +6 -21
  21. data/lib/spring/client/version.rb +11 -0
  22. data/lib/spring/client.rb +8 -5
  23. data/lib/spring/command_wrapper.rb +82 -0
  24. data/lib/spring/commands/rails.rb +44 -13
  25. data/lib/spring/commands/rake.rb +0 -4
  26. data/lib/spring/commands.rb +14 -4
  27. data/lib/spring/configuration.rb +11 -2
  28. data/lib/spring/env.rb +36 -10
  29. data/lib/spring/server.rb +9 -35
  30. data/lib/spring/sid.rb +1 -1
  31. data/lib/spring/test/acceptance_test.rb +346 -0
  32. data/lib/spring/test/application.rb +217 -0
  33. data/lib/spring/test/application_generator.rb +121 -0
  34. data/lib/spring/test/rails_version.rb +40 -0
  35. data/lib/spring/test/watcher_test.rb +167 -0
  36. data/lib/spring/test.rb +18 -0
  37. data/lib/spring/version.rb +1 -1
  38. data/lib/spring/watcher/abstract.rb +7 -8
  39. data/lib/spring/watcher/polling.rb +2 -0
  40. data/lib/spring/watcher.rb +4 -8
  41. data/spring.gemspec +2 -2
  42. data/test/acceptance_test.rb +4 -0
  43. data/test/helper.rb +2 -2
  44. data/test/unit/client/version_test.rb +14 -0
  45. data/test/unit/commands_test.rb +23 -1
  46. data/test/unit/watcher_test.rb +2 -184
  47. metadata +30 -17
  48. data/lib/spring/watcher/listen.rb +0 -52
  49. data/test/acceptance/app_test.rb +0 -511
@@ -1,511 +0,0 @@
1
- # encoding: utf-8
2
- require 'helper'
3
- require 'io/wait'
4
- require "timeout"
5
- require "spring/sid"
6
- require "spring/env"
7
-
8
- class AppTest < ActiveSupport::TestCase
9
- DEFAULT_SPEEDUP = 0.8
10
- DEFAULT_TIMEOUT = ENV['CI'] ? 30 : 10
11
-
12
- def rails_version
13
- ENV['RAILS_VERSION'] || '~> 4.0.0'
14
- end
15
-
16
- def rails_3?
17
- rails_version.split(" ").last =~ /^3/
18
- end
19
-
20
- def app_root
21
- Pathname.new("#{TEST_ROOT}/apps/rails-#{rails_version.scan(/\d/)[0..1].join("-")}")
22
- end
23
-
24
- def gem_home
25
- app_root.join "vendor/gems/#{RUBY_VERSION}"
26
- end
27
-
28
- def user_home
29
- app_root.join "user_home"
30
- end
31
-
32
- def spring
33
- gem_home.join "bin/spring"
34
- end
35
-
36
- def spring_env
37
- @spring_env ||= Spring::Env.new(app_root)
38
- end
39
-
40
- def stdout
41
- @stdout ||= IO.pipe
42
- end
43
-
44
- def stderr
45
- @stderr ||= IO.pipe
46
- end
47
-
48
- def log_file
49
- @log_file ||= File.open("/tmp/spring.log", "w+")
50
- end
51
-
52
- def env
53
- @env ||= {
54
- "GEM_HOME" => gem_home.to_s,
55
- "GEM_PATH" => "",
56
- "HOME" => user_home.to_s,
57
- "RAILS_ENV" => nil,
58
- "RACK_ENV" => nil,
59
- "SPRING_LOG" => log_file.path
60
- }
61
- end
62
-
63
- def app_run(command, opts = {})
64
- start_time = Time.now
65
-
66
- Bundler.with_clean_env do
67
- Process.spawn(
68
- env,
69
- command.to_s,
70
- out: stdout.last,
71
- err: stderr.last,
72
- in: :close,
73
- chdir: app_root.to_s,
74
- )
75
- end
76
-
77
- _, status = Timeout.timeout(opts.fetch(:timeout, DEFAULT_TIMEOUT)) { Process.wait2 }
78
-
79
- if pid = spring_env.pid
80
- @server_pid = pid
81
- lines = `ps -A -o ppid= -o pid= | egrep '^\\s*#{@server_pid}'`.lines
82
- @application_pids = lines.map { |l| l.split.last.to_i }
83
- end
84
-
85
- output = read_streams
86
- puts dump_streams(command, output) if ENV["SPRING_DEBUG"]
87
-
88
- @times << (Time.now - start_time) if @times
89
-
90
- output.merge(status: status, command: command)
91
- rescue Timeout::Error => e
92
- raise e, "Output:\n\n#{dump_streams(command, read_streams)}"
93
- end
94
-
95
- def read_streams
96
- {
97
- stdout: read_stream(stdout.first),
98
- stderr: read_stream(stderr.first),
99
- log: read_stream(log_file)
100
- }
101
- end
102
-
103
- def read_stream(stream)
104
- output = ""
105
- while IO.select([stream], [], [], 0.5) && !stream.eof?
106
- output << stream.readpartial(10240)
107
- end
108
- output
109
- end
110
-
111
- def dump_streams(command, streams)
112
- output = "$ #{command}\n"
113
-
114
- streams.each do |name, stream|
115
- unless stream.chomp.empty?
116
- output << "--- #{name} ---\n"
117
- output << "#{stream.chomp}\n"
118
- end
119
- end
120
-
121
- output << "\n"
122
- output
123
- end
124
-
125
- def alive?(pid)
126
- Process.kill 0, pid
127
- true
128
- rescue Errno::ESRCH
129
- false
130
- end
131
-
132
- def await_reload
133
- raise "no pid" if @application_pids.nil? || @application_pids.empty?
134
-
135
- Timeout.timeout(DEFAULT_TIMEOUT) do
136
- sleep 0.1 while @application_pids.any? { |p| alive?(p) }
137
- end
138
- end
139
-
140
- def debug(artifacts)
141
- artifacts = artifacts.dup
142
- artifacts.delete :status
143
- dump_streams(artifacts.delete(:command), artifacts)
144
- end
145
-
146
- def assert_output(artifacts, expected)
147
- expected.each do |stream, output|
148
- assert artifacts[stream].include?(output),
149
- "expected #{stream} to include '#{output}'.\n\n#{debug(artifacts)}"
150
- end
151
- end
152
-
153
- def assert_success(command, expected_output = nil)
154
- artifacts = app_run(*Array(command))
155
- assert artifacts[:status].success?, "expected successful exit status\n\n#{debug(artifacts)}"
156
- assert_output artifacts, expected_output if expected_output
157
- end
158
-
159
- def assert_failure(command, expected_output = nil)
160
- artifacts = app_run(*Array(command))
161
- assert !artifacts[:status].success?, "expected unsuccessful exit status\n\n#{debug(artifacts)}"
162
- assert_output artifacts, expected_output if expected_output
163
- end
164
-
165
- def assert_speedup(ratio = DEFAULT_SPEEDUP)
166
- if ENV['CI']
167
- yield
168
- else
169
- @times = []
170
- yield
171
- assert (@times.last / @times.first) < ratio, "#{@times.last} was not less than #{ratio} of #{@times.first}"
172
- @times = nil
173
- end
174
- end
175
-
176
- def spring_test_command
177
- "#{spring} #{rails_3? ? 'testunit' : 'rake test'} #{@test}"
178
- end
179
-
180
- def generate_app
181
- Bundler.with_clean_env do
182
- # Sporadic SSL errors keep causing test failures so there are anti-SSL workarounds here
183
-
184
- assert system("(gem list rails --installed --version '#{rails_version}' || " \
185
- "gem install rails --clear-sources --source http://rubygems.org --version '#{rails_version}') > /dev/null")
186
-
187
- # Have to shell out otherwise bundler prevents us finding the gem
188
- version = `ruby -e 'puts Gem::Specification.find_by_name("rails", "#{rails_version}").version'`.chomp
189
-
190
- assert system("rails _#{version}_ new #{app_root} --skip-bundle --skip-javascript --skip-sprockets > /dev/null")
191
-
192
- FileUtils.mkdir_p(gem_home)
193
- FileUtils.mkdir_p(user_home)
194
- FileUtils.rm_rf("#{app_root}/test/performance/")
195
-
196
- if rails_3?
197
- File.write(
198
- "#{app_root}/Gemfile",
199
- File.read("#{app_root}/Gemfile") + "gem 'spring-commands-testunit'\n"
200
- )
201
- end
202
-
203
- File.write(
204
- "#{app_root}/Gemfile",
205
- File.read("#{app_root}/Gemfile").sub("https://rubygems.org", "http://rubygems.org")
206
- )
207
- end
208
- end
209
-
210
- def install
211
- generate_app unless app_root.exist?
212
-
213
- assert system("gem build spring.gemspec 2>/dev/null 1>/dev/null")
214
-
215
- assert_success ["gem install ../../../spring-#{Spring::VERSION}.gem", timeout: nil]
216
- assert_success ["(gem list bundler | grep bundler) || gem install bundler", timeout: nil]
217
- assert_success ["bundle check || bundle update", timeout: nil]
218
-
219
- unless File.exist?(@controller)
220
- assert_success "bundle exec rails g scaffold post title:string"
221
- end
222
-
223
- assert_success "bundle exec rake db:migrate db:test:clone"
224
- @@installed = true
225
- end
226
-
227
- @@installed = false
228
-
229
- setup do
230
- @test = "#{app_root}/test/#{rails_3? ? 'functional' : 'controllers'}/posts_controller_test.rb"
231
- @controller = "#{app_root}/app/controllers/posts_controller.rb"
232
-
233
- install unless @@installed
234
-
235
- @test_contents = File.read(@test)
236
- @controller_contents = File.read(@controller)
237
- end
238
-
239
- teardown do
240
- app_run "#{spring} stop"
241
- File.write(@test, @test_contents)
242
- File.write(@controller, @controller_contents)
243
- FileUtils.rm_f("#{app_root}/config/spring.rb")
244
- end
245
-
246
- test "basic" do
247
- assert_speedup do
248
- 2.times { app_run spring_test_command }
249
- end
250
- end
251
-
252
- test "help message when called without arguments" do
253
- assert_success spring, stdout: 'Usage: spring COMMAND [ARGS]'
254
- end
255
-
256
- test "test changes are picked up" do
257
- assert_speedup do
258
- assert_success spring_test_command, stdout: "0 failures"
259
-
260
- File.write(@test, @test_contents.sub("get :index", "raise 'omg'"))
261
- assert_failure spring_test_command, stdout: "RuntimeError: omg"
262
- end
263
- end
264
-
265
- test "code changes are picked up" do
266
- assert_speedup do
267
- assert_success spring_test_command, stdout: "0 failures"
268
-
269
- File.write(@controller, @controller_contents.sub("@posts = Post.all", "raise 'omg'"))
270
- assert_failure spring_test_command, stdout: "RuntimeError: omg"
271
- end
272
- end
273
-
274
- test "code changes in pre-referenced app files are picked up" do
275
- begin
276
- initializer = "#{app_root}/config/initializers/load_posts_controller.rb"
277
- File.write(initializer, "PostsController\n")
278
-
279
- assert_speedup do
280
- assert_success spring_test_command, stdout: "0 failures"
281
-
282
- File.write(@controller, @controller_contents.sub("@posts = Post.all", "raise 'omg'"))
283
- assert_failure spring_test_command, stdout: "RuntimeError: omg"
284
- end
285
- ensure
286
- FileUtils.rm_f(initializer)
287
- end
288
- end
289
-
290
- def assert_app_reloaded
291
- application = "#{app_root}/config/application.rb"
292
- application_contents = File.read(application)
293
-
294
- assert_success spring_test_command
295
-
296
- File.write(application, application_contents + <<-CODE)
297
- class Foo
298
- def self.omg
299
- raise "omg"
300
- end
301
- end
302
- CODE
303
- File.write(@test, @test_contents.sub("get :index", "Foo.omg"))
304
-
305
- await_reload
306
- assert_failure spring_test_command, stdout: "RuntimeError: omg"
307
- ensure
308
- File.write(application, application_contents)
309
- end
310
-
311
- test "app gets reloaded when preloaded files change (polling watcher)" do
312
- env["RAILS_ENV"] = "test"
313
- assert_success "#{spring} rails runner 'puts Spring.watcher.class'", stdout: "Polling"
314
- assert_app_reloaded
315
- end
316
-
317
- test "app gets reloaded when preloaded files change (listen watcher)" do
318
- begin
319
- gemfile = app_root.join("Gemfile")
320
- gemfile_contents = gemfile.read
321
- File.write(gemfile, gemfile_contents + "\ngem 'listen', '~> 1.0'")
322
-
323
- File.write("#{app_root}/config/spring.rb", "Spring.watch_method = :listen")
324
-
325
- assert_success ["bundle install", timeout: nil]
326
-
327
- env["RAILS_ENV"] = "test"
328
- assert_success "#{spring} rails runner 'puts Spring.watcher.class'", stdout: "Listen"
329
-
330
- assert_app_reloaded
331
- ensure
332
- File.write(gemfile, gemfile_contents)
333
- assert_success "bundle check"
334
- end
335
- end
336
-
337
- test "app recovers when a boot-level error is introduced" do
338
- begin
339
- application = "#{app_root}/config/application.rb"
340
- application_contents = File.read(application)
341
-
342
- assert_success spring_test_command
343
-
344
- File.write(application, application_contents + "\nomg")
345
- await_reload
346
-
347
- assert_failure spring_test_command
348
-
349
- File.write(application, application_contents)
350
- assert_success spring_test_command
351
- ensure
352
- File.write(application, application_contents)
353
- end
354
- end
355
-
356
- test "stop command kills server" do
357
- app_run spring_test_command
358
- assert spring_env.server_running?, "The server should be running but it isn't"
359
-
360
- assert_success "#{spring} stop"
361
- assert !spring_env.server_running?, "The server should not be running but it is"
362
- end
363
-
364
- test "custom commands" do
365
- File.write("#{app_root}/config/spring.rb", <<-CODE)
366
- class CustomCommand
367
- def call
368
- puts "omg"
369
- end
370
- end
371
-
372
- Spring.register_command "custom", CustomCommand.new
373
- CODE
374
-
375
- assert_success "#{spring} custom", stdout: "omg"
376
- end
377
-
378
- test "binstubs" do
379
- begin
380
- FileUtils.mv "#{app_root}/bin", "#{app_root}/bin~" if File.exist?("#{app_root}/bin")
381
-
382
- app_run "#{spring} binstub rake"
383
- assert_success "bin/rake -T", stdout: "rake db:migrate"
384
-
385
- app_run "#{spring} binstub rake rails"
386
- assert_success "bin/rails runner 'puts %(omg)'", stdout: "omg"
387
- assert_success "bin/rails server --help", stdout: "Usage: rails server"
388
-
389
- FileUtils.rm ["#{app_root}/bin/rails", "#{app_root}/bin/rake"]
390
-
391
- app_run "#{spring} binstub --all"
392
- assert_success "bin/rake -T", stdout: "rake db:migrate"
393
- assert_success "bin/rails runner 'puts %(omg)'", stdout: "omg"
394
- ensure
395
- if File.exist?("#{app_root}/bin~")
396
- FileUtils.rm_rf "#{app_root}/bin"
397
- FileUtils.mv "#{app_root}/bin~", "#{app_root}/bin"
398
- end
399
- end
400
- end
401
-
402
- test "after fork callback" do
403
- File.write("#{app_root}/config/spring.rb", "Spring.after_fork { puts '!callback!' }")
404
- assert_success "#{spring} rails runner 'puts 2'", stdout: "!callback!\n2"
405
- end
406
-
407
- test "global config file evaluated" do
408
- begin
409
- File.write("#{user_home}/.spring.rb", "Spring.after_fork { puts '!callback!' }")
410
- assert_success "#{spring} rails runner 'puts 2'", stdout: "!callback!\n2"
411
- ensure
412
- FileUtils.rm_r("#{user_home}/.spring.rb")
413
- end
414
- end
415
-
416
- test "missing config/application.rb" do
417
- begin
418
- FileUtils.mv app_root.join("config/application.rb"), app_root.join("config/application.rb.bak")
419
-
420
- assert_failure "#{spring} rake -T", stderr: "unable to find your config/application.rb"
421
- ensure
422
- FileUtils.mv app_root.join("config/application.rb.bak"), app_root.join("config/application.rb")
423
- end
424
- end
425
-
426
- test "piping" do
427
- assert_success "#{spring} rake -T | grep db", stdout: "rake db:migrate"
428
- end
429
-
430
- test "status" do
431
- assert_success "#{spring} status", stdout: "Spring is not running"
432
- app_run "#{spring} rails runner ''"
433
- assert_success "#{spring} status", stdout: "Spring is running"
434
- end
435
-
436
- test "runner command sets Rails environment from command-line options" do
437
- assert_success "#{spring} rails runner -e production 'puts Rails.env'", stdout: "production"
438
- assert_success "#{spring} rails runner --environment=production 'puts Rails.env'", stdout: "production"
439
- end
440
-
441
- test "forcing rails env via environment variable" do
442
- env['RAILS_ENV'] = 'production'
443
- assert_success "#{spring} rake -p 'Rails.env'", stdout: "production"
444
- end
445
-
446
- test "setting env vars with rake" do
447
- begin
448
- File.write("#{app_root}/lib/tasks/env.rake", <<-'CODE')
449
- task :print_rails_env => :environment do
450
- puts Rails.env
451
- end
452
-
453
- task :print_env do
454
- ENV.each { |k, v| puts "#{k}=#{v}" }
455
- end
456
-
457
- task(:default).clear.enhance [:print_rails_env]
458
- CODE
459
-
460
- assert_success "#{spring} rake RAILS_ENV=test print_rails_env", stdout: "test"
461
- assert_success "#{spring} rake FOO=bar print_env", stdout: "FOO=bar"
462
- assert_success "#{spring} rake", stdout: "test"
463
- ensure
464
- FileUtils.rm_f("#{app_root}/lib/tasks/env.rake")
465
- end
466
- end
467
-
468
- test "changing the Gemfile restarts the server" do
469
- begin
470
- gemfile = app_root.join("Gemfile")
471
- gemfile_contents = gemfile.read
472
-
473
- assert_success %(#{spring} rails runner 'require "sqlite3"')
474
-
475
- File.write(gemfile, gemfile_contents.sub(%{gem 'sqlite3'}, %{# gem 'sqlite3'}))
476
- app_run "bundle check"
477
-
478
- await_reload
479
- assert_failure %(#{spring} rails runner 'require "sqlite3"'), stderr: "sqlite3"
480
- ensure
481
- File.write(gemfile, gemfile_contents)
482
- assert_success "bundle check"
483
- end
484
- end
485
-
486
- test "changing the environment between runs" do
487
- begin
488
- application = "#{app_root}/config/application.rb"
489
- application_contents = File.read(application)
490
-
491
- File.write(application, "#{application_contents}\nENV['BAR'] = 'bar'")
492
-
493
- env["OMG"] = "1"
494
- env["FOO"] = "1"
495
- env["RUBYOPT"] = "-rubygems"
496
-
497
- assert_success %(#{spring} rails runner 'p ENV["OMG"]'), stdout: "1"
498
- assert_success %(#{spring} rails runner 'p ENV["BAR"]'), stdout: "bar"
499
- assert_success %(#{spring} rails runner 'p ENV.key?("BUNDLE_GEMFILE")'), stdout: "true"
500
- assert_success %(#{spring} rails runner 'p ENV["RUBYOPT"]'), stdout: "bundler"
501
-
502
- env["OMG"] = "2"
503
- env.delete "FOO"
504
-
505
- assert_success %(#{spring} rails runner 'p ENV["OMG"]'), stdout: "2"
506
- assert_success %(#{spring} rails runner 'p ENV.key?("FOO")'), stdout: "false"
507
- ensure
508
- File.write(application, application_contents)
509
- end
510
- end
511
- end