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,4 +1,5 @@
1
1
  require "spring/watcher"
2
+ require "spring/command_wrapper"
2
3
 
3
4
  module Spring
4
5
  @commands = {}
@@ -7,8 +8,8 @@ module Spring
7
8
  attr_reader :commands
8
9
  end
9
10
 
10
- def self.register_command(name, klass)
11
- commands[name] = klass
11
+ def self.register_command(name, command = nil)
12
+ commands[name] = CommandWrapper.new(name, command)
12
13
  end
13
14
 
14
15
  def self.command?(name)
@@ -31,8 +32,17 @@ module Spring
31
32
  # then we need to be under bundler.
32
33
  require "bundler/setup"
33
34
 
34
- Gem::Specification.map(&:name).grep(/^spring-commands-/).each do |command|
35
- require command
35
+ # Auto-require any spring extensions which are in the Gemfile
36
+ Gem::Specification.map(&:name).grep(/^spring-/).each do |command|
37
+ begin
38
+ require command
39
+ rescue LoadError => error
40
+ if error.message.include?(command)
41
+ require command.gsub("-", "/")
42
+ else
43
+ raise
44
+ end
45
+ end
36
46
  end
37
47
 
38
48
  config = File.expand_path("./config/spring.rb")
@@ -22,15 +22,24 @@ module Spring
22
22
 
23
23
  def application_root_path
24
24
  @application_root_path ||= begin
25
- path = Pathname.new(File.expand_path(application_root || find_project_root))
25
+ if application_root
26
+ path = Pathname.new(File.expand_path(application_root))
27
+ else
28
+ path = project_root_path
29
+ end
30
+
26
31
  raise MissingApplication.new(path) unless path.join("config/application.rb").exist?
27
32
  path
28
33
  end
29
34
  end
30
35
 
36
+ def project_root_path
37
+ @project_root_path ||= find_project_root(Pathname.new(File.expand_path(Dir.pwd)))
38
+ end
39
+
31
40
  private
32
41
 
33
- def find_project_root(current_dir = Pathname.new(Dir.pwd))
42
+ def find_project_root(current_dir)
34
43
  if current_dir.join(gemfile).exist?
35
44
  current_dir
36
45
  elsif current_dir.root?
data/lib/spring/env.rb CHANGED
@@ -9,31 +9,37 @@ require "spring/configuration"
9
9
 
10
10
  module Spring
11
11
  IGNORE_SIGNALS = %w(INT QUIT)
12
+ STOP_TIMEOUT = 2 # seconds
12
13
 
13
14
  class Env
14
15
  attr_reader :log_file
15
16
 
16
17
  def initialize(root = nil)
17
- @root = root
18
- @log_file = File.open(ENV["SPRING_LOG"] || "/dev/null", "a")
18
+ @root = root
19
+ @project_root = root
20
+ @log_file = File.open(ENV["SPRING_LOG"] || File::NULL, "a")
19
21
  end
20
22
 
21
23
  def root
22
24
  @root ||= Spring.application_root_path
23
25
  end
24
26
 
27
+ def project_root
28
+ @project_root ||= Spring.project_root_path
29
+ end
30
+
25
31
  def version
26
32
  Spring::VERSION
27
33
  end
28
34
 
29
35
  def tmp_path
30
- path = Pathname.new(Dir.tmpdir + "/spring")
36
+ path = Pathname.new(File.join(ENV['XDG_RUNTIME_DIR'] || Dir.tmpdir, "spring"))
31
37
  FileUtils.mkdir_p(path) unless path.exist?
32
38
  path
33
39
  end
34
40
 
35
41
  def application_id
36
- Digest::MD5.hexdigest(root.to_s)
42
+ Digest::MD5.hexdigest(RUBY_VERSION + project_root.to_s)
37
43
  end
38
44
 
39
45
  def socket_path
@@ -60,7 +66,7 @@ module Spring
60
66
  end
61
67
 
62
68
  def server_running?
63
- pidfile = pidfile_path.open('r')
69
+ pidfile = pidfile_path.open('r+')
64
70
  !pidfile.flock(File::LOCK_EX | File::LOCK_NB)
65
71
  rescue Errno::ENOENT
66
72
  false
@@ -71,13 +77,33 @@ module Spring
71
77
  end
72
78
  end
73
79
 
74
- def bundle_mtime
75
- [Bundler.default_lockfile, Bundler.default_gemfile].select(&:exist?).map(&:mtime).max
76
- end
77
-
78
80
  def log(message)
79
- log_file.puts "[#{Time.now}] #{message}"
81
+ log_file.puts "[#{Time.now}] [#{Process.pid}] #{message}"
80
82
  log_file.flush
81
83
  end
84
+
85
+ def stop
86
+ if server_running?
87
+ timeout = Time.now + STOP_TIMEOUT
88
+ kill 'TERM'
89
+ sleep 0.1 until !server_running? || Time.now >= timeout
90
+
91
+ if server_running?
92
+ kill 'KILL'
93
+ :killed
94
+ else
95
+ :stopped
96
+ end
97
+ else
98
+ :not_running
99
+ end
100
+ end
101
+
102
+ def kill(sig)
103
+ pid = self.pid
104
+ Process.kill(sig, pid) if pid
105
+ rescue Errno::ESRCH
106
+ # already dead
107
+ end
82
108
  end
83
109
  end
data/lib/spring/server.rb CHANGED
@@ -1,26 +1,13 @@
1
1
  module Spring
2
- class << self
3
- attr_reader :original_env
4
- end
5
- @original_env = ENV.to_hash
2
+ ORIGINAL_ENV = ENV.to_hash
6
3
  end
7
4
 
8
- require "socket"
9
- require "thread"
10
-
11
- require "spring/configuration"
12
- require "spring/env"
5
+ require "spring/boot"
13
6
  require "spring/application_manager"
14
- require "spring/process_title_updater"
15
- require "spring/json"
16
7
 
17
- # Must be last, as it requires bundler/setup
8
+ # Must be last, as it requires bundler/setup, which alters the load path
18
9
  require "spring/commands"
19
10
 
20
- # readline must be required before we setpgid, otherwise the require may hang,
21
- # if readline has been built against libedit. See issue #70.
22
- require "readline"
23
-
24
11
  module Spring
25
12
  class Server
26
13
  def self.boot
@@ -31,7 +18,7 @@ module Spring
31
18
 
32
19
  def initialize(env = Env.new)
33
20
  @env = env
34
- @applications = Hash.new { |h, k| h[k] = ApplicationManager.new(self, k) }
21
+ @applications = Hash.new { |h, k| h[k] = ApplicationManager.new(k) }
35
22
  @pidfile = env.pidfile_path.open('a')
36
23
  @mutex = Mutex.new
37
24
  end
@@ -48,7 +35,6 @@ module Spring
48
35
  ignore_signals
49
36
  set_exit_hook
50
37
  set_process_title
51
- watch_bundle
52
38
  start_server
53
39
  end
54
40
 
@@ -82,13 +68,7 @@ module Spring
82
68
  end
83
69
 
84
70
  def rails_env_for(args, default_rails_env)
85
- command = Spring.command(args.first)
86
-
87
- if command.respond_to?(:env)
88
- env = command.env(args.drop(1))
89
- end
90
-
91
- env || default_rails_env
71
+ Spring.command(args.first).env(args.drop(1)) || default_rails_env
92
72
  end
93
73
 
94
74
  # Boot the server into the process group of the current session.
@@ -101,7 +81,7 @@ module Spring
101
81
  # Ignore SIGINT and SIGQUIT otherwise the user typing ^C or ^\ on the command line
102
82
  # will kill the server/application.
103
83
  def ignore_signals
104
- IGNORE_SIGNALS.each { |sig| trap(sig, "IGNORE") }
84
+ IGNORE_SIGNALS.each { |sig| trap(sig, "IGNORE") }
105
85
  end
106
86
 
107
87
  def set_exit_hook
@@ -112,13 +92,15 @@ module Spring
112
92
  end
113
93
 
114
94
  def shutdown
115
- @applications.values.each(&:stop)
95
+ log "shutting down"
116
96
 
117
97
  [env.socket_path, env.pidfile_path].each do |path|
118
98
  if path.exist?
119
99
  path.unlink rescue nil
120
100
  end
121
101
  end
102
+
103
+ @applications.values.map { |a| Thread.new { a.stop } }.map(&:join)
122
104
  end
123
105
 
124
106
  def write_pidfile
@@ -144,13 +126,5 @@ module Spring
144
126
  "spring server | #{env.app_name} | started #{distance} ago"
145
127
  }
146
128
  end
147
-
148
- def watch_bundle
149
- @bundle_mtime = env.bundle_mtime
150
- end
151
-
152
- def application_starting
153
- @mutex.synchronize { exit if env.bundle_mtime != @bundle_mtime }
154
- end
155
129
  end
156
130
  end
data/lib/spring/sid.rb CHANGED
@@ -25,7 +25,7 @@ module Spring
25
25
  if Process.respond_to?(:getsid)
26
26
  # Ruby 2
27
27
  Process.getsid
28
- elsif defined?(Fiddle)
28
+ elsif defined?(Fiddle) and defined?(DL)
29
29
  # Ruby 1.9.3 compiled with libffi support
30
30
  fiddle_func.call(0)
31
31
  else
@@ -0,0 +1,346 @@
1
+ # encoding: utf-8
2
+
3
+ require "io/wait"
4
+ require "timeout"
5
+ require "spring/sid"
6
+ require "spring/client"
7
+
8
+ module Spring
9
+ module Test
10
+ class AcceptanceTest < ActiveSupport::TestCase
11
+ runnables.delete self # prevent Minitest running this class
12
+
13
+ DEFAULT_SPEEDUP = 0.8
14
+
15
+ def rails_version
16
+ ENV['RAILS_VERSION'] || '~> 4.2.0'
17
+ end
18
+
19
+ # Extension point for spring-watchers-listen
20
+ def generator_klass
21
+ Spring::Test::ApplicationGenerator
22
+ end
23
+
24
+ def generator
25
+ @@generator ||= generator_klass.new(rails_version)
26
+ end
27
+
28
+ def app
29
+ @app ||= Spring::Test::Application.new("#{Spring::Test.root}/apps/tmp")
30
+ end
31
+
32
+ def assert_output(artifacts, expected)
33
+ expected.each do |stream, output|
34
+ assert artifacts[stream].include?(output),
35
+ "expected #{stream} to include '#{output}'.\n\n#{app.debug(artifacts)}"
36
+ end
37
+ end
38
+
39
+ def assert_success(command, expected_output = nil)
40
+ artifacts = app.run(*Array(command))
41
+ assert artifacts[:status].success?, "expected successful exit status\n\n#{app.debug(artifacts)}"
42
+ assert_output artifacts, expected_output if expected_output
43
+ end
44
+
45
+ def assert_failure(command, expected_output = nil)
46
+ artifacts = app.run(*Array(command))
47
+ assert !artifacts[:status].success?, "expected unsuccessful exit status\n\n#{app.debug(artifacts)}"
48
+ assert_output artifacts, expected_output if expected_output
49
+ end
50
+
51
+ def assert_speedup(ratio = DEFAULT_SPEEDUP)
52
+ if ENV['CI']
53
+ yield
54
+ else
55
+ app.with_timing do
56
+ yield
57
+ assert app.timing_ratio < ratio, "#{app.last_time} was not less than #{ratio} of #{app.first_time}"
58
+ end
59
+ end
60
+ end
61
+
62
+ setup do
63
+ generator.generate_if_missing
64
+ generator.install_spring
65
+ generator.copy_to(app.root)
66
+ end
67
+
68
+ teardown do
69
+ app.stop_spring
70
+ end
71
+
72
+ test "basic" do
73
+ assert_speedup do
74
+ 2.times { app.run app.spring_test_command }
75
+ end
76
+ end
77
+
78
+ test "help message when called without arguments" do
79
+ assert_success "bin/spring", stdout: 'Usage: spring COMMAND [ARGS]'
80
+ end
81
+
82
+ test "test changes are picked up" do
83
+ assert_speedup do
84
+ assert_success app.spring_test_command, stdout: "0 failures"
85
+
86
+ File.write(app.test, app.test.read.sub("get :index", "raise 'omg'"))
87
+ assert_failure app.spring_test_command, stdout: "RuntimeError: omg"
88
+ end
89
+ end
90
+
91
+ test "code changes are picked up" do
92
+ assert_speedup do
93
+ assert_success app.spring_test_command, stdout: "0 failures"
94
+
95
+ File.write(app.controller, app.controller.read.sub("@posts = Post.all", "raise 'omg'"))
96
+ assert_failure app.spring_test_command, stdout: "RuntimeError: omg"
97
+ end
98
+ end
99
+
100
+ test "code changes in pre-referenced app files are picked up" do
101
+ File.write(app.path("config/initializers/load_posts_controller.rb"), "PostsController\n")
102
+
103
+ assert_speedup do
104
+ assert_success app.spring_test_command, stdout: "0 failures"
105
+
106
+ File.write(app.controller, app.controller.read.sub("@posts = Post.all", "raise 'omg'"))
107
+ assert_failure app.spring_test_command, stdout: "RuntimeError: omg"
108
+ end
109
+ end
110
+
111
+ test "app gets reloaded when preloaded files change" do
112
+ assert_success app.spring_test_command
113
+
114
+ File.write(app.application_config, app.application_config.read + <<-CODE)
115
+ class Foo
116
+ def self.omg
117
+ raise "omg"
118
+ end
119
+ end
120
+ CODE
121
+ File.write(app.test, app.test.read.sub("get :index", "Foo.omg"))
122
+
123
+ app.await_reload
124
+ assert_failure app.spring_test_command, stdout: "RuntimeError: omg"
125
+ end
126
+
127
+ test "app gets reloaded even with a ton of boot output" do
128
+ limit = UNIXSocket.pair.first.getsockopt(:SOCKET, :SNDBUF).int
129
+
130
+ assert_success app.spring_test_command
131
+ File.write(app.path("config/initializers/verbose.rb"), "#{limit}.times { puts 'x' }")
132
+
133
+ app.await_reload
134
+ assert_success app.spring_test_command
135
+ end
136
+
137
+ test "app recovers when a boot-level error is introduced" do
138
+ config = app.application_config.read
139
+
140
+ assert_success app.spring_test_command
141
+
142
+ File.write(app.application_config, "#{config}\nomg")
143
+ app.await_reload
144
+
145
+ assert_failure app.spring_test_command
146
+
147
+ File.write(app.application_config, config)
148
+ assert_success app.spring_test_command
149
+ end
150
+
151
+ test "stop command kills server" do
152
+ app.run app.spring_test_command
153
+ assert app.spring_env.server_running?, "The server should be running but it isn't"
154
+
155
+ assert_success "bin/spring stop"
156
+ assert !app.spring_env.server_running?, "The server should not be running but it is"
157
+ end
158
+
159
+ test "custom commands" do
160
+ # Start spring before setting up the command, to test that it gracefully upgrades itself
161
+ assert_success "bin/rails runner ''"
162
+
163
+ File.write(app.spring_config, <<-CODE)
164
+ class CustomCommand
165
+ def call
166
+ puts "omg"
167
+ end
168
+
169
+ def exec_name
170
+ "rake"
171
+ end
172
+ end
173
+
174
+ Spring.register_command "custom", CustomCommand.new
175
+ CODE
176
+
177
+ assert_success "bin/spring custom", stdout: "omg"
178
+
179
+ assert_success "bin/spring binstub custom"
180
+ assert_success "bin/custom", stdout: "omg"
181
+
182
+ app.env["DISABLE_SPRING"] = "1"
183
+ assert_success %{bin/custom -e 'puts "foo"'}, stdout: "foo"
184
+ end
185
+
186
+ test "binstub" do
187
+ assert_success "bin/rails server --help", stdout: "Usage: rails server" # rails command fallback
188
+
189
+ assert_success "#{app.spring} binstub rake", stdout: "bin/rake: spring already present"
190
+
191
+ assert_success "#{app.spring} binstub --remove rake", stdout: "bin/rake: spring removed"
192
+ assert !app.path("bin/rake").read.include?(Spring::Client::Binstub::LOADER)
193
+ assert_success "bin/rake -T", stdout: "rake db:migrate"
194
+ end
195
+
196
+ test "binstub when spring is uninstalled" do
197
+ app.run! "gem uninstall --ignore-dependencies spring"
198
+ File.write(app.gemfile, app.gemfile.read.gsub(/gem 'spring.*/, ""))
199
+ assert_success "bin/rake -T", stdout: "rake db:migrate"
200
+ end
201
+
202
+ test "binstub upgrade" do
203
+ File.write(app.path("bin/rake"), <<CODE)
204
+ #!/usr/bin/env ruby
205
+
206
+ if !Process.respond_to?(:fork) || Gem::Specification.find_all_by_name("spring").empty?
207
+ exec "bundle", "exec", "rake", *ARGV
208
+ else
209
+ ARGV.unshift "rake"
210
+ load Gem.bin_path("spring", "spring")
211
+ end
212
+ CODE
213
+
214
+ File.write(app.path("bin/rails"), <<CODE)
215
+ #!/usr/bin/env ruby
216
+
217
+ if !Process.respond_to?(:fork) || Gem::Specification.find_all_by_name("spring").empty?
218
+ APP_PATH = File.expand_path('../../config/application', __FILE__)
219
+ require_relative '../config/boot'
220
+ require 'rails/commands'
221
+ else
222
+ ARGV.unshift "rails"
223
+ load Gem.bin_path("spring", "spring")
224
+ end
225
+ CODE
226
+
227
+ assert_success "bin/spring binstub --all", stdout: "upgraded"
228
+
229
+ assert_equal app.path("bin/rake").read, <<CODE
230
+ #!/usr/bin/env ruby
231
+ #{Spring::Client::Binstub::LOADER.strip}
232
+ require 'bundler/setup'
233
+ load Gem.bin_path('rake', 'rake')
234
+ CODE
235
+
236
+ assert_equal app.path("bin/rails").read, <<CODE
237
+ #!/usr/bin/env ruby
238
+ #{Spring::Client::Binstub::LOADER.strip}
239
+ APP_PATH = File.expand_path('../../config/application', __FILE__)
240
+ require_relative '../config/boot'
241
+ require 'rails/commands'
242
+ CODE
243
+ end
244
+
245
+ test "after fork callback" do
246
+ File.write(app.spring_config, "Spring.after_fork { puts '!callback!' }")
247
+ assert_success "bin/rails runner 'puts 2'", stdout: "!callback!\n2"
248
+ end
249
+
250
+ test "global config file evaluated" do
251
+ File.write("#{app.user_home}/.spring.rb", "Spring.after_fork { puts '!callback!' }")
252
+ assert_success "bin/rails runner 'puts 2'", stdout: "!callback!\n2"
253
+ end
254
+
255
+ test "missing config/application.rb" do
256
+ app.application_config.delete
257
+ assert_failure "bin/rake -T", stderr: "unable to find your config/application.rb"
258
+ end
259
+
260
+ test "piping" do
261
+ assert_success "bin/rake -T | grep db", stdout: "rake db:migrate"
262
+ end
263
+
264
+ test "status" do
265
+ assert_success "bin/spring status", stdout: "Spring is not running"
266
+ assert_success "bin/rails runner ''"
267
+ assert_success "bin/spring status", stdout: "Spring is running"
268
+ end
269
+
270
+ test "runner command sets Rails environment from command-line options" do
271
+ assert_success "bin/rails runner -e test 'puts Rails.env'", stdout: "test"
272
+ assert_success "bin/rails runner --environment=test 'puts Rails.env'", stdout: "test"
273
+ end
274
+
275
+ test "forcing rails env via environment variable" do
276
+ app.env['RAILS_ENV'] = 'test'
277
+ assert_success "bin/rake -p 'Rails.env'", stdout: "test"
278
+ end
279
+
280
+ test "setting env vars with rake" do
281
+ File.write(app.path("lib/tasks/env.rake"), <<-'CODE')
282
+ task :print_rails_env => :environment do
283
+ puts Rails.env
284
+ end
285
+
286
+ task :print_env do
287
+ ENV.each { |k, v| puts "#{k}=#{v}" }
288
+ end
289
+
290
+ task(:default).clear.enhance [:print_rails_env]
291
+ CODE
292
+
293
+ assert_success "bin/rake RAILS_ENV=test print_rails_env", stdout: "test"
294
+ assert_success "bin/rake FOO=bar print_env", stdout: "FOO=bar"
295
+ assert_success "bin/rake", stdout: "test"
296
+ end
297
+
298
+ test "changing the Gemfile works" do
299
+ assert_success %(bin/rails runner 'require "sqlite3"')
300
+
301
+ File.write(app.gemfile, app.gemfile.read.sub(%{gem 'sqlite3'}, %{# gem 'sqlite3'}))
302
+ app.await_reload
303
+
304
+ assert_failure %(bin/rails runner 'require "sqlite3"'), stderr: "sqlite3"
305
+ end
306
+
307
+ test "changing the Gemfile works when spring calls into itself" do
308
+ File.write(app.path("script.rb"), <<-CODE)
309
+ gemfile = Rails.root.join("Gemfile")
310
+ File.write(gemfile, "\#{gemfile.read}gem 'devise'\\n")
311
+ Bundler.with_clean_env do
312
+ system(#{app.env.inspect}, "bundle install")
313
+ end
314
+ output = `\#{Rails.root.join('bin/rails')} runner 'require "devise"; puts "done";'`
315
+ exit output == "done\n"
316
+ CODE
317
+
318
+ assert_success [%(bin/rails runner 'load Rails.root.join("script.rb")'), timeout: 60]
319
+ end
320
+
321
+ test "changing the environment between runs" do
322
+ File.write(app.application_config, "#{app.application_config.read}\nENV['BAR'] = 'bar'")
323
+
324
+ app.env["OMG"] = "1"
325
+ app.env["FOO"] = "1"
326
+ app.env["RUBYOPT"] = "-rubygems"
327
+
328
+ assert_success %(bin/rails runner 'p ENV["OMG"]'), stdout: "1"
329
+ assert_success %(bin/rails runner 'p ENV["BAR"]'), stdout: "bar"
330
+ assert_success %(bin/rails runner 'p ENV.key?("BUNDLE_GEMFILE")'), stdout: "true"
331
+ assert_success %(bin/rails runner 'p ENV["RUBYOPT"]'), stdout: "bundler"
332
+
333
+ app.env["OMG"] = "2"
334
+ app.env.delete "FOO"
335
+
336
+ assert_success %(bin/rails runner 'p ENV["OMG"]'), stdout: "2"
337
+ assert_success %(bin/rails runner 'p ENV.key?("FOO")'), stdout: "false"
338
+ end
339
+
340
+ test "Kernel.raise remains private" do
341
+ expr = "p Kernel.private_instance_methods.include?(:raise)"
342
+ assert_success %(bin/rails runner '#{expr}'), stdout: "true"
343
+ end
344
+ end
345
+ end
346
+ end