rack-unreloader 1.5.0 → 1.6.0

Sign up to get free protection for your applications and to get access to all the features.
checksums.yaml CHANGED
@@ -1,7 +1,7 @@
1
1
  ---
2
2
  SHA1:
3
- metadata.gz: d64b1a401d4679d7a1811a008c2cc5bb951b8722
4
- data.tar.gz: ef510ae0a796be3d5f11e9d80ba842c9f351e764
3
+ metadata.gz: 63e0754f2064aacba7745d52c60d136019dd5fdf
4
+ data.tar.gz: 31d3d8223106f59d5fa739b429cbf08d6d47db31
5
5
  SHA512:
6
- metadata.gz: 9791a0b04d89506251c8fbe9e16cc873f33b581b6927b029b08574170e1dd850048043b97f0e6e15760b5e3961d149132c7b9f7bb4f0ca082bc2be64635b464f
7
- data.tar.gz: a6eefba4399f58d591bb474c027eefec4bacf633641736806d9f3f13127239107ea29bc5c25e9adad173e5c80f5e2827663b669ce530e6b8a496d71230bca405
6
+ metadata.gz: 6645aa444fd1c88c52c09f0de56894f640cf9404624f0e25197071b1498d1aa7745a1bb9a8e9416f3cc7d8aeacf4f993e552adaa06f4b1386be95db43aef4272
7
+ data.tar.gz: 26811ff5218e1fe20e15f0619dcfb4d4dbabcba2819c82e8c46f428fdfb1714a3348a12a931ba8065d5f6cb182336e989e6d98d93e7a2ac633f4c6c7d783fc43
data/CHANGELOG CHANGED
@@ -1,3 +1,7 @@
1
+ = 1.6.0 (2017-02-24)
2
+
3
+ * Add Unreloader#strip_path_prefix, designed to support chrooting (jeremyevans)
4
+
1
5
  = 1.5.0 (2016-03-10)
2
6
 
3
7
  * Handle deletions of monitored files (jeremyevans)
@@ -106,7 +106,7 @@ to false if not in development:
106
106
 
107
107
  dev = ENV['RACK_ENV'] == 'development'
108
108
  require 'rack/unreloader'
109
- Unreloader = Rack::Unreloader.new(:reload=>dev){App}
109
+ Unreloader = Rack::Unreloader.new(:subclasses=>%w'Roda Sequel::Model', :reload=>dev){App}
110
110
  Unreloader.require './models.rb'
111
111
  Unreloader.require './app.rb'
112
112
  run(dev ? Unreloader : App)
@@ -229,6 +229,18 @@ decide that instead of specifying the constants, ObjectSpace should be used to
229
229
  automatically determine the constants loaded. You can specify this by having the
230
230
  block return the :ObjectSpace symbol.
231
231
 
232
+ == chroot Support
233
+
234
+ <tt>Rack::Unreloader#strip_path_prefix</tt> exists for supporting reloading in
235
+ chroot environments, where you chroot an application after it has been fully
236
+ loaded, but still want to pick up changes to files inside the chroot. Example:
237
+
238
+ Unreloader.strip_path_prefix(Dir.pwd)
239
+ Dir.chroot(Dir.pwd)
240
+
241
+ Note that Unreloader.strip_path_prefix also strips the path prefix from
242
+ $LOADED_FEATURES, as that is necessary for correct operation.
243
+
232
244
  == Usage Outside Rack
233
245
 
234
246
  While <tt>Rack::Unreloader</tt> is usually in the development of rack applications,
@@ -262,8 +274,9 @@ for speed when using this library.
262
274
 
263
275
  == Implementation Support
264
276
 
265
- Rack::Unreloader works correctly on Ruby 1.8.7+ and Rubinius. It only works on
266
- JRuby if you use a proc to specify the constants to unload.
277
+ Rack::Unreloader works correctly on Ruby 1.8.7+, JRuby 9.1+, and Rubinius. It
278
+ also works on older versions of JRuby if you use a proc to specify the constants
279
+ to unload.
267
280
 
268
281
  == License
269
282
 
data/Rakefile CHANGED
@@ -12,7 +12,7 @@ end
12
12
 
13
13
  desc "Run specs"
14
14
  task :spec do
15
- sh "#{FileUtils::RUBY} -rubygems -I lib spec/unreloader_spec.rb"
15
+ sh "#{FileUtils::RUBY} spec/unreloader_spec.rb"
16
16
  end
17
17
 
18
18
  task :default => :spec
@@ -116,5 +116,17 @@ module Rack
116
116
  def reload!
117
117
  @reloader.reload! if @reloader
118
118
  end
119
+
120
+ # Strip the given path prefix from all absolute paths used by the
121
+ # reloader. This is designed when chrooting an application.
122
+ #
123
+ # Options:
124
+ # :strip_core :: Also strips the path prefix from $LOADED_FEATURES and
125
+ # $LOAD_PATH.
126
+ def strip_path_prefix(path_prefix, opts={})
127
+ if @reloader
128
+ @reloader.strip_path_prefix(path_prefix)
129
+ end
130
+ end
119
131
  end
120
132
  end
@@ -53,6 +53,47 @@ module Rack
53
53
  @skip_reload = []
54
54
  end
55
55
 
56
+ # Strip the given path prefix from the internal data structures.
57
+ def strip_path_prefix(path_prefix)
58
+ empty = ''.freeze
59
+
60
+ # Strip the path prefix from $LOADED_FEATURES, otherwise the reloading won't work.
61
+ # Hopefully a future version of ruby will do this automatically when chrooting.
62
+ $LOADED_FEATURES.map!{|s| s.sub(path_prefix, empty)}
63
+
64
+ fix_path = lambda do |s|
65
+ s.sub(path_prefix, empty)
66
+ end
67
+
68
+ [@dependency_order, @skip_reload].each do |a|
69
+ a.map!(&fix_path)
70
+ end
71
+
72
+ [@files, @old_entries].each do |hash|
73
+ hash.each do |k,h|
74
+ h[:features].map!(&fix_path)
75
+ end
76
+ end
77
+
78
+ @monitor_dirs.each_value do |a|
79
+ a[1].map!(&fix_path)
80
+ end
81
+
82
+ @dependencies.each_value do |a|
83
+ a.map!(&fix_path)
84
+ end
85
+
86
+ [@files, @old_entries, @monitor_files, @monitor_dirs, @constants_defined, @dependencies].each do |hash|
87
+ hash.keys.each do |k|
88
+ if k.start_with?(path_prefix)
89
+ hash[fix_path.call(k)] = hash.delete(k)
90
+ end
91
+ end
92
+ end
93
+
94
+ nil
95
+ end
96
+
56
97
  # Unload all reloadable constants and features, and clear the list
57
98
  # of files to monitor.
58
99
  def clear!
@@ -0,0 +1,76 @@
1
+ require File.join(File.dirname(File.expand_path(__FILE__)), '../lib/rack/unreloader')
2
+ require 'rubygems'
3
+ $: << 'lib'
4
+ gem 'minitest'
5
+ require 'minitest/autorun'
6
+ require 'minitest/hooks'
7
+
8
+ module ModifiedAt
9
+ def set_modified_time(file, time)
10
+ time = Time.now + time if time.is_a?(Integer)
11
+ modified_times[File.expand_path(file)] = time
12
+ end
13
+
14
+ def modified_times
15
+ @modified_times ||= {}
16
+ end
17
+
18
+ private
19
+
20
+ def modified_at(file)
21
+ modified_times[file] || super
22
+ end
23
+ end
24
+
25
+ class Minitest::Spec
26
+ def code(i)
27
+ "class App; def self.call(env) @a end; @a ||= []; @a << #{i}; end"
28
+ end
29
+
30
+ def update_app(code, file=@filename)
31
+ ru.reloader.set_modified_time(file, @i += 1) if ru.reloader
32
+ File.open(file, 'wb'){|f| f.write(code)}
33
+ end
34
+
35
+ def logger
36
+ return @logger if @logger
37
+ @logger = []
38
+ def @logger.method_missing(meth, log)
39
+ self << log
40
+ end
41
+ @logger
42
+ end
43
+
44
+ def base_ru(opts={})
45
+ block = opts[:block] || proc{App}
46
+ @ru = Rack::Unreloader.new({:logger=>logger, :cooldown=>0}.merge(opts), &block)
47
+ @ru.reloader.extend ModifiedAt if @ru.reloader
48
+ Object.const_set(:RU, @ru)
49
+ end
50
+
51
+ def ru(opts={})
52
+ return @ru if @ru
53
+ base_ru(opts)
54
+ update_app(opts[:code]||code(1))
55
+ @ru.require @filename
56
+ @ru
57
+ end
58
+
59
+ def log_match(*logs)
60
+ @logger.length.must_equal logs.length
61
+ logs.zip(@logger).each{|l, log| l.is_a?(String) ? log.must_equal(l) : log.must_match(l)}
62
+ end
63
+
64
+ before do
65
+ @i = 0
66
+ @filename = 'spec/app.rb'
67
+ end
68
+
69
+ after do
70
+ ru.reloader.clear! if ru.reloader
71
+ Object.send(:remove_const, :RU)
72
+ Object.send(:remove_const, :App) if defined?(::App)
73
+ Object.send(:remove_const, :App2) if defined?(::App2)
74
+ Dir['spec/app*.rb'].each{|f| File.delete(f)}
75
+ end
76
+ end
@@ -0,0 +1,858 @@
1
+ require File.join(File.dirname(File.expand_path(__FILE__)), 'spec_helper')
2
+
3
+ describe Rack::Unreloader do
4
+ def self.it(*)
5
+ exit(1) unless Process.waitpid2(fork{super}).last.success?
6
+ end
7
+
8
+ def chroot
9
+ @ru.strip_path_prefix(Dir.pwd)
10
+ Dir.chroot(Dir.pwd)
11
+ end
12
+
13
+ it "should not reload files automatically if cooldown option is nil" do
14
+ ru(:cooldown => nil).call({}).must_equal [1]
15
+ chroot
16
+ update_app(code(2))
17
+ ru.call({}).must_equal [1]
18
+ @ru.reload!
19
+ ru.call({}).must_equal [2]
20
+ end
21
+
22
+ it "should not setup a reloader if reload option is false" do
23
+ @filename = 'spec/app_no_reload.rb'
24
+ ru(:reload => false).call({}).must_equal [1]
25
+ file = 'spec/app_no_reload2.rb'
26
+ File.open(file, 'wb'){|f| f.write('ANR2 = 2')}
27
+ chroot
28
+ ru.require 'spec/app_no_*2.rb'
29
+ ANR2.must_equal 2
30
+ end
31
+
32
+ it "should unload constants contained in file and reload file if file changes" do
33
+ ru.call({}).must_equal [1]
34
+ chroot
35
+ update_app(code(2))
36
+ ru.call({}).must_equal [2]
37
+ log_match %r{\ALoading.*spec/app\.rb\z},
38
+ %r{\ANew classes in .*spec/app\.rb: App\z},
39
+ %r{\AUnloading /spec/app\.rb\z},
40
+ "Removed constant App",
41
+ %r{\ALoading /spec/app\.rb\z},
42
+ %r{\ANew classes in /spec/app\.rb: App\z}
43
+ end
44
+
45
+ it "should stop monitoring file for changes if it is deleted constants contained in file and reload file if file changes" do
46
+ ru.call({}).must_equal [1]
47
+ chroot
48
+ File.delete('spec/app.rb')
49
+ proc{ru.call({})}.must_raise NameError
50
+ log_match %r{\ALoading.*spec/app\.rb\z},
51
+ %r{\ANew classes in .*spec/app\.rb: App\z},
52
+ %r{\AUnloading /spec/app\.rb\z},
53
+ "Removed constant App"
54
+ end
55
+
56
+ it "should check constants using ObjectSpace if require proc returns :ObjectSpace" do
57
+ base_ru
58
+ update_app(code(1))
59
+ @ru.require(@filename){|f| :ObjectSpace}
60
+ ru.call({}).must_equal [1]
61
+ chroot
62
+ update_app(code(2))
63
+ ru.call({}).must_equal [2]
64
+ log_match %r{\ALoading.*spec/app\.rb\z},
65
+ %r{\ANew classes in .*spec/app\.rb: App\z},
66
+ %r{\AUnloading /spec/app\.rb\z},
67
+ "Removed constant App",
68
+ %r{\ALoading /spec/app\.rb\z},
69
+ %r{\ANew classes in /spec/app\.rb: App\z}
70
+ end
71
+
72
+ it "should pickup files added as dependencies when chrooting early" do
73
+ ru.call({}).must_equal [1]
74
+ chroot
75
+ update_app("RU.require 'spec/app2.rb'; class App; def self.call(env) [@a, App2.call(env)] end; @a ||= []; @a << 2; end")
76
+ update_app("class App2; def self.call(env) @a end; @a ||= []; @a << 3; end", 'spec/app2.rb')
77
+ ru.call({}).must_equal [[2], [3]]
78
+ update_app("class App2; def self.call(env) @a end; @a ||= []; @a << 4; end", 'spec/app2.rb')
79
+ ru.call({}).must_equal [[2], [4]]
80
+ log_match %r{\ALoading.*spec/app\.rb\z},
81
+ %r{\ANew classes in .*spec/app\.rb: App\z},
82
+ %r{\AUnloading /spec/app\.rb\z},
83
+ "Removed constant App",
84
+ %r{\ALoading /spec/app\.rb\z},
85
+ %r{\ALoading /spec/app2\.rb\z},
86
+ %r{\ANew classes in /spec/app2\.rb: App2\z},
87
+ %r{\ANew classes in /spec/app\.rb: (App App2|App2 App)\z},
88
+ %r{\ANew features in /spec/app\.rb: /spec/app2\.rb\z},
89
+ %r{\AUnloading /spec/app2\.rb\z},
90
+ "Removed constant App2",
91
+ %r{\ALoading /spec/app2\.rb\z},
92
+ %r{\ANew classes in /spec/app2\.rb: App2\z}
93
+ end
94
+
95
+ it "should pickup files added as dependencies when chrooting late" do
96
+ ru.call({}).must_equal [1]
97
+ update_app("RU.require 'spec/app2.rb'; class App; def self.call(env) [@a, App2.call(env)] end; @a ||= []; @a << 2; end")
98
+ update_app("class App2; def self.call(env) @a end; @a ||= []; @a << 3; end", 'spec/app2.rb')
99
+ ru.call({}).must_equal [[2], [3]]
100
+ chroot
101
+ update_app("class App2; def self.call(env) @a end; @a ||= []; @a << 4; end", 'spec/app2.rb')
102
+ ru.call({}).must_equal [[2], [4]]
103
+ log_match %r{\ALoading.*spec/app\.rb\z},
104
+ %r{\ANew classes in .*spec/app\.rb: App\z},
105
+ %r{\AUnloading.*spec/app\.rb\z},
106
+ "Removed constant App",
107
+ %r{\ALoading.*spec/app\.rb\z},
108
+ %r{\ALoading.*spec/app2\.rb\z},
109
+ %r{\ANew classes in .*spec/app2\.rb: App2\z},
110
+ %r{\ANew classes in .*spec/app\.rb: (App App2|App2 App)\z},
111
+ %r{\ANew features in .*spec/app\.rb: .*spec/app2\.rb\z},
112
+ %r{\AUnloading /spec/app2\.rb\z},
113
+ "Removed constant App2",
114
+ %r{\ALoading /spec/app2\.rb\z},
115
+ %r{\ANew classes in /spec/app2\.rb: App2\z}
116
+ end
117
+
118
+ it "should support :subclasses option and only unload subclasses of given class when chrooting early" do
119
+ ru(:subclasses=>'App').call({}).must_equal [1]
120
+ chroot
121
+ update_app("RU.require 'spec/app2.rb'; class App; def self.call(env) [@a, App2.call(env)] end; @a ||= []; @a << 2; end")
122
+ update_app("class App2 < App; def self.call(env) @a end; @a ||= []; @a << 3; end", 'spec/app2.rb')
123
+ ru.call({}).must_equal [[1, 2], [3]]
124
+ update_app("class App2 < App; def self.call(env) @a end; @a ||= []; @a << 4; end", 'spec/app2.rb')
125
+ ru.call({}).must_equal [[1, 2], [4]]
126
+ update_app("RU.require 'spec/app2.rb'; class App; def self.call(env) [@a, App2.call(env)] end; @a ||= []; @a << 2; end")
127
+ log_match %r{\ALoading.*spec/app\.rb\z},
128
+ %r{\AUnloading /spec/app\.rb\z},
129
+ %r{\ALoading /spec/app\.rb\z},
130
+ %r{\ALoading /spec/app2\.rb\z},
131
+ %r{\ANew classes in /spec/app2\.rb: App2\z},
132
+ %r{\ANew classes in /spec/app\.rb: App2\z},
133
+ %r{\ANew features in /spec/app\.rb: /spec/app2\.rb\z},
134
+ %r{\AUnloading /spec/app2\.rb\z},
135
+ "Removed constant App2",
136
+ %r{\ALoading /spec/app2\.rb\z},
137
+ %r{\ANew classes in /spec/app2\.rb: App2\z}
138
+ end
139
+
140
+ it "should support :subclasses option and only unload subclasses of given class when chrooting late" do
141
+ ru(:subclasses=>'App').call({}).must_equal [1]
142
+ update_app("RU.require 'spec/app2.rb'; class App; def self.call(env) [@a, App2.call(env)] end; @a ||= []; @a << 2; end")
143
+ update_app("class App2 < App; def self.call(env) @a end; @a ||= []; @a << 3; end", 'spec/app2.rb')
144
+ ru.call({}).must_equal [[1, 2], [3]]
145
+ chroot
146
+ update_app("class App2 < App; def self.call(env) @a end; @a ||= []; @a << 4; end", 'spec/app2.rb')
147
+ ru.call({}).must_equal [[1, 2], [4]]
148
+ update_app("RU.require 'spec/app2.rb'; class App; def self.call(env) [@a, App2.call(env)] end; @a ||= []; @a << 2; end")
149
+ log_match %r{\ALoading.*spec/app\.rb\z},
150
+ %r{\AUnloading.*spec/app\.rb\z},
151
+ %r{\ALoading.*spec/app\.rb\z},
152
+ %r{\ALoading.*spec/app2\.rb\z},
153
+ %r{\ANew classes in .*spec/app2\.rb: App2\z},
154
+ %r{\ANew classes in .*spec/app\.rb: App2\z},
155
+ %r{\ANew features in .*spec/app\.rb: .*spec/app2\.rb\z},
156
+ %r{\AUnloading /spec/app2\.rb\z},
157
+ "Removed constant App2",
158
+ %r{\ALoading /spec/app2\.rb\z},
159
+ %r{\ANew classes in /spec/app2\.rb: App2\z}
160
+ end
161
+
162
+
163
+ it "should unload modules before reloading similar to classes" do
164
+ ru(:code=>"module App; def self.call(env) @a end; @a ||= []; @a << 1; end").call({}).must_equal [1]
165
+ chroot
166
+ update_app("module App; def self.call(env) @a end; @a ||= []; @a << 2; end")
167
+ ru.call({}).must_equal [2]
168
+ log_match %r{\ALoading.*spec/app\.rb\z},
169
+ %r{\ANew classes in .*spec/app\.rb: App\z},
170
+ %r{\AUnloading /spec/app\.rb\z},
171
+ "Removed constant App",
172
+ %r{\ALoading /spec/app\.rb\z},
173
+ %r{\ANew classes in /spec/app\.rb: App\z}
174
+ end
175
+
176
+ it "should unload specific modules by name via :subclasses option" do
177
+ ru(:subclasses=>'App', :code=>"module App; def self.call(env) @a end; @a ||= []; @a << 1; end").call({}).must_equal [1]
178
+ chroot
179
+ update_app("module App; def self.call(env) @a end; @a ||= []; @a << 2; end")
180
+ ru.call({}).must_equal [2]
181
+ log_match %r{\ALoading.*spec/app\.rb\z},
182
+ %r{\ANew classes in .*spec/app\.rb: App\z},
183
+ %r{\AUnloading /spec/app\.rb\z},
184
+ "Removed constant App",
185
+ %r{\ALoading /spec/app\.rb\z},
186
+ %r{\ANew classes in /spec/app\.rb: App\z}
187
+ end
188
+
189
+ it "should not unload modules by name if :subclasses option used and module not present" do
190
+ ru(:subclasses=>'Foo', :code=>"module App; def self.call(env) @a end; @a ||= []; @a << 1; end").call({}).must_equal [1]
191
+ chroot
192
+ update_app("module App; def self.call(env) @a end; @a ||= []; @a << 2; end")
193
+ ru.call({}).must_equal [1, 2]
194
+ log_match %r{\ALoading.*spec/app\.rb\z},
195
+ %r{\AUnloading /spec/app\.rb\z},
196
+ %r{\ALoading /spec/app\.rb\z}
197
+ end
198
+
199
+ it "should unload partially loaded modules if loading fails, and allow future loading when chrooting early" do
200
+ ru.call({}).must_equal [1]
201
+ chroot
202
+ update_app("module App; def self.call(env) @a end; @a ||= []; raise 'foo'; end")
203
+ proc{ru.call({})}.must_raise RuntimeError
204
+ defined?(::App).must_be_nil
205
+ update_app(code(2))
206
+ ru.call({}).must_equal [2]
207
+ log_match %r{\ALoading.*spec/app\.rb\z},
208
+ %r{\ANew classes in .*spec/app\.rb: App\z},
209
+ %r{\AUnloading /spec/app\.rb\z},
210
+ "Removed constant App",
211
+ %r{\ALoading /spec/app\.rb\z},
212
+ %r{\AFailed to load /spec/app\.rb; removing partially defined constants\z},
213
+ "Removed constant App",
214
+ %r{\ALoading /spec/app\.rb\z},
215
+ %r{\ANew classes in /spec/app\.rb: App\z}
216
+ end
217
+
218
+ it "should unload partially loaded modules if loading fails, and allow future loading when chrooting late" do
219
+ ru.call({}).must_equal [1]
220
+ update_app("module App; def self.call(env) @a end; @a ||= []; raise 'foo'; end")
221
+ proc{ru.call({})}.must_raise RuntimeError
222
+ defined?(::App).must_be_nil
223
+ chroot
224
+ update_app(code(2))
225
+ ru.call({}).must_equal [2]
226
+ log_match %r{\ALoading.*spec/app\.rb\z},
227
+ %r{\ANew classes in .*spec/app\.rb: App\z},
228
+ %r{\AUnloading.*spec/app\.rb\z},
229
+ "Removed constant App",
230
+ %r{\ALoading.*spec/app\.rb\z},
231
+ %r{\AFailed to load .*spec/app\.rb; removing partially defined constants\z},
232
+ "Removed constant App",
233
+ %r{\ALoading /spec/app\.rb\z},
234
+ %r{\ANew classes in /spec/app\.rb: App\z}
235
+ end
236
+
237
+ it "should unload classes in namespaces" do
238
+ ru(:code=>"class Array::App; def self.call(env) @a end; @a ||= []; @a << 1; end", :block=>proc{Array::App}).call({}).must_equal [1]
239
+ chroot
240
+ update_app("class Array::App; def self.call(env) @a end; @a ||= []; @a << 2; end")
241
+ ru.call({}).must_equal [2]
242
+ log_match %r{\ALoading.*spec/app\.rb\z},
243
+ %r{\ANew classes in .*spec/app\.rb: Array::App\z},
244
+ %r{\AUnloading /spec/app\.rb\z},
245
+ "Removed constant Array::App",
246
+ %r{\ALoading /spec/app\.rb\z},
247
+ %r{\ANew classes in /spec/app\.rb: Array::App\z}
248
+ end
249
+
250
+ it "should not unload class defined in dependency if already defined in parent when chrooting early" do
251
+ base_ru
252
+ update_app("class App; def self.call(env) @a end; @a ||= []; @a << 2; RU.require 'spec/app2.rb'; end")
253
+ update_app("class App; @a << 3 end", 'spec/app2.rb')
254
+ @ru.require 'spec/app.rb'
255
+ ru.call({}).must_equal [2, 3]
256
+ chroot
257
+ update_app("class App; @a << 4 end", 'spec/app2.rb')
258
+ ru.call({}).must_equal [2, 3, 4]
259
+ update_app("class App; def self.call(env) @a end; @a ||= []; @a << 2; RU.require 'spec/app2.rb'; end")
260
+ ru.call({}).must_equal [2, 4]
261
+ log_match %r{\ALoading.*spec/app\.rb\z},
262
+ %r{\ALoading.*spec/app2\.rb\z},
263
+ %r{\ANew classes in .*spec/app\.rb: App\z},
264
+ %r{\ANew features in .*spec/app\.rb: .*spec/app2\.rb\z},
265
+ %r{\AUnloading /spec/app2\.rb\z},
266
+ %r{\ALoading /spec/app2\.rb\z},
267
+ %r{\AUnloading /spec/app\.rb\z},
268
+ %r{\AUnloading /spec/app2\.rb\z},
269
+ "Removed constant App",
270
+ %r{\ALoading /spec/app\.rb\z},
271
+ %r{\ALoading /spec/app2\.rb\z},
272
+ %r{\ANew classes in /spec/app\.rb: App\z},
273
+ %r{\ANew features in /spec/app\.rb: /spec/app2\.rb\z}
274
+ end
275
+
276
+ it "should not unload class defined in dependency if already defined in parent when chrooting late" do
277
+ base_ru
278
+ update_app("class App; def self.call(env) @a end; @a ||= []; @a << 2; RU.require 'spec/app2.rb'; end")
279
+ update_app("class App; @a << 3 end", 'spec/app2.rb')
280
+ @ru.require 'spec/app.rb'
281
+ ru.call({}).must_equal [2, 3]
282
+ update_app("class App; @a << 4 end", 'spec/app2.rb')
283
+ ru.call({}).must_equal [2, 3, 4]
284
+ chroot
285
+ update_app("class App; def self.call(env) @a end; @a ||= []; @a << 2; RU.require 'spec/app2.rb'; end")
286
+ ru.call({}).must_equal [2, 4]
287
+ log_match %r{\ALoading.*spec/app\.rb\z},
288
+ %r{\ALoading.*spec/app2\.rb\z},
289
+ %r{\ANew classes in .*spec/app\.rb: App\z},
290
+ %r{\ANew features in .*spec/app\.rb: .*spec/app2\.rb\z},
291
+ %r{\AUnloading.*spec/app2\.rb\z},
292
+ %r{\ALoading.*spec/app2\.rb\z},
293
+ %r{\AUnloading.*spec/app\.rb\z},
294
+ %r{\AUnloading.*spec/app2\.rb\z},
295
+ "Removed constant App",
296
+ %r{\ALoading /spec/app\.rb\z},
297
+ %r{\ALoading /spec/app2\.rb\z},
298
+ %r{\ANew classes in /spec/app\.rb: App\z},
299
+ %r{\ANew features in /spec/app\.rb: /spec/app2\.rb\z}
300
+ end
301
+
302
+ it "should allow specifying proc for which constants get removed" do
303
+ base_ru
304
+ update_app("class App; def self.call(env) [@a, App2.a] end; @a ||= []; @a << 1; end; class App2; def self.a; @a end; @a ||= []; @a << 2; end")
305
+ @ru.require('spec/app.rb'){|f| File.basename(f).sub(/\.rb/, '').capitalize}
306
+ ru.call({}).must_equal [[1], [2]]
307
+ chroot
308
+ update_app("class App; def self.call(env) [@a, App2.a] end; @a ||= []; @a << 3; end; class App2; def self.a; @a end; @a ||= []; @a << 4; end")
309
+ ru.call({}).must_equal [[3], [2, 4]]
310
+ log_match %r{\ALoading.*spec/app\.rb\z},
311
+ %r{\ANew classes in .*spec/app\.rb: App\z},
312
+ %r{\AUnloading /spec/app\.rb\z},
313
+ "Removed constant App",
314
+ %r{\ALoading /spec/app\.rb\z},
315
+ %r{\ANew classes in /spec/app\.rb: App\z}
316
+ end
317
+
318
+ it "should handle anonymous classes" do
319
+ base_ru(:block=>proc{$app})
320
+ update_app("$app = Class.new do def self.call(env) @a end; @a ||= []; @a << 1; end")
321
+ @ru.require('spec/app.rb')
322
+ ru.call({}).must_equal [1]
323
+ chroot
324
+ update_app("$app = Class.new do def self.call(env) @a end; @a ||= []; @a << 2; end")
325
+ ru.call({}).must_equal [2]
326
+ log_match %r{\ALoading.*spec/app\.rb\z},
327
+ %r{\AUnloading /spec/app\.rb\z},
328
+ %r{\ALoading /spec/app\.rb\z}
329
+ end
330
+
331
+ it "should log when attempting to remove a class that doesn't exist" do
332
+ base_ru
333
+ update_app(code(1))
334
+ @ru.require('spec/app.rb'){|f| 'Foo'}
335
+ ru.call({}).must_equal [1]
336
+ chroot
337
+ update_app(code(2))
338
+ ru.call({}).must_equal [1, 2]
339
+ log_match %r{\ALoading.*spec/app\.rb\z},
340
+ %r{\AConstants not defined after loading .*spec/app\.rb: Foo\z},
341
+ %r{\AUnloading /spec/app\.rb\z},
342
+ "Error removing constant: Foo",
343
+ %r{\ALoading /spec/app\.rb\z},
344
+ %r{\AConstants not defined after loading /spec/app\.rb: Foo\z}
345
+ end
346
+
347
+ it "should handle recorded dependencies when chrooting early" do
348
+ base_ru
349
+ update_app("module A; B = 1; end", 'spec/app_mod.rb')
350
+ update_app("class App; A = ::A; def self.call(env) A::B end; end")
351
+ ru.require 'spec/app_mod.rb'
352
+ ru.require 'spec/app.rb'
353
+ ru.record_dependency 'spec/app_mod.rb', 'spec/app.rb'
354
+ ru.call({}).must_equal 1
355
+ chroot
356
+ update_app("module A; B = 2; end", 'spec/app_mod.rb')
357
+ ru.call({}).must_equal 2
358
+ update_app("module A; include C; end", 'spec/app_mod.rb')
359
+ update_app("module C; B = 3; end", 'spec/app_mod2.rb')
360
+ ru.record_dependency 'spec/app_mod2.rb', 'spec/app_mod.rb'
361
+ ru.require 'spec/app_mod2.rb'
362
+ ru.call({}).must_equal 3
363
+ update_app("module C; B = 4; end", 'spec/app_mod2.rb')
364
+ ru.call({}).must_equal 4
365
+ end
366
+
367
+ it "should handle recorded dependencies when chrooting middle" do
368
+ base_ru
369
+ update_app("module A; B = 1; end", 'spec/app_mod.rb')
370
+ update_app("class App; A = ::A; def self.call(env) A::B end; end")
371
+ ru.require 'spec/app_mod.rb'
372
+ ru.require 'spec/app.rb'
373
+ ru.record_dependency 'spec/app_mod.rb', 'spec/app.rb'
374
+ ru.call({}).must_equal 1
375
+ update_app("module A; B = 2; end", 'spec/app_mod.rb')
376
+ ru.call({}).must_equal 2
377
+ chroot
378
+ update_app("module A; include C; end", 'spec/app_mod.rb')
379
+ update_app("module C; B = 3; end", 'spec/app_mod2.rb')
380
+ ru.record_dependency 'spec/app_mod2.rb', 'spec/app_mod.rb'
381
+ ru.require 'spec/app_mod2.rb'
382
+ ru.call({}).must_equal 3
383
+ update_app("module C; B = 4; end", 'spec/app_mod2.rb')
384
+ ru.call({}).must_equal 4
385
+ end
386
+
387
+ it "should handle recorded dependencies when chrooting late" do
388
+ base_ru
389
+ update_app("module A; B = 1; end", 'spec/app_mod.rb')
390
+ update_app("class App; A = ::A; def self.call(env) A::B end; end")
391
+ ru.require 'spec/app_mod.rb'
392
+ ru.require 'spec/app.rb'
393
+ ru.record_dependency 'spec/app_mod.rb', 'spec/app.rb'
394
+ ru.call({}).must_equal 1
395
+ update_app("module A; B = 2; end", 'spec/app_mod.rb')
396
+ ru.call({}).must_equal 2
397
+ update_app("module A; include C; end", 'spec/app_mod.rb')
398
+ update_app("module C; B = 3; end", 'spec/app_mod2.rb')
399
+ ru.record_dependency 'spec/app_mod2.rb', 'spec/app_mod.rb'
400
+ ru.require 'spec/app_mod2.rb'
401
+ ru.call({}).must_equal 3
402
+ chroot
403
+ update_app("module C; B = 4; end", 'spec/app_mod2.rb')
404
+ ru.call({}).must_equal 4
405
+ end
406
+
407
+ describe "with a directory" do
408
+ include Minitest::Hooks
409
+
410
+ before(:all) do
411
+ Dir.mkdir('spec/dir')
412
+ Dir.mkdir('spec/dir/subdir')
413
+ Dir.mkdir('spec/dir/subdir2')
414
+ end
415
+
416
+ after do
417
+ Dir['spec/dir/**/*.rb'].each{|f| File.delete(f)}
418
+ end
419
+
420
+ after(:all) do
421
+ Dir.rmdir('spec/dir/subdir')
422
+ Dir.rmdir('spec/dir/subdir2')
423
+ Dir.rmdir('spec/dir')
424
+ end
425
+
426
+ it "should handle recorded dependencies in directories when chrooting early" do
427
+ base_ru
428
+ update_app("module A; B = 1; end", 'spec/dir/subdir/app_mod.rb')
429
+ update_app("class App; A = ::A; def self.call(env) A::B end; end")
430
+ ru.require 'spec/dir/subdir'
431
+ ru.require 'spec/app.rb'
432
+ ru.record_dependency 'spec/dir/subdir', 'spec/app.rb'
433
+ ru.call({}).must_equal 1
434
+ chroot
435
+ update_app("module A; B = 2; end", 'spec/dir/subdir/app_mod.rb')
436
+ ru.call({}).must_equal 2
437
+ update_app("module A; include C; end", 'spec/dir/subdir/app_mod.rb')
438
+ update_app("module C; B = 3; end", 'spec/dir/subdir2/app_mod2.rb')
439
+ ru.require 'spec/dir/subdir2/app_mod2.rb'
440
+ ru.record_dependency 'spec/dir/subdir2/app_mod2.rb', 'spec/dir/subdir'
441
+ ru.call({}).must_equal 3
442
+ update_app("module C; B = 4; end", 'spec/dir/subdir2/app_mod2.rb')
443
+ ru.call({}).must_equal 4
444
+ end
445
+
446
+ it "should handle recorded dependencies in directories when chrooting middle" do
447
+ base_ru
448
+ update_app("module A; B = 1; end", 'spec/dir/subdir/app_mod.rb')
449
+ update_app("class App; A = ::A; def self.call(env) A::B end; end")
450
+ ru.require 'spec/dir/subdir'
451
+ ru.require 'spec/app.rb'
452
+ ru.record_dependency 'spec/dir/subdir', 'spec/app.rb'
453
+ ru.call({}).must_equal 1
454
+ update_app("module A; B = 2; end", 'spec/dir/subdir/app_mod.rb')
455
+ ru.call({}).must_equal 2
456
+ chroot
457
+ update_app("module A; include C; end", 'spec/dir/subdir/app_mod.rb')
458
+ update_app("module C; B = 3; end", 'spec/dir/subdir2/app_mod2.rb')
459
+ ru.require 'spec/dir/subdir2/app_mod2.rb'
460
+ ru.record_dependency 'spec/dir/subdir2/app_mod2.rb', 'spec/dir/subdir'
461
+ ru.call({}).must_equal 3
462
+ update_app("module C; B = 4; end", 'spec/dir/subdir2/app_mod2.rb')
463
+ ru.call({}).must_equal 4
464
+ end
465
+
466
+ it "should handle recorded dependencies in directories when chrooting late" do
467
+ base_ru
468
+ update_app("module A; B = 1; end", 'spec/dir/subdir/app_mod.rb')
469
+ update_app("class App; A = ::A; def self.call(env) A::B end; end")
470
+ ru.require 'spec/dir/subdir'
471
+ ru.require 'spec/app.rb'
472
+ ru.record_dependency 'spec/dir/subdir', 'spec/app.rb'
473
+ ru.call({}).must_equal 1
474
+ update_app("module A; B = 2; end", 'spec/dir/subdir/app_mod.rb')
475
+ ru.call({}).must_equal 2
476
+ update_app("module A; include C; end", 'spec/dir/subdir/app_mod.rb')
477
+ update_app("module C; B = 3; end", 'spec/dir/subdir2/app_mod2.rb')
478
+ ru.require 'spec/dir/subdir2/app_mod2.rb'
479
+ ru.record_dependency 'spec/dir/subdir2/app_mod2.rb', 'spec/dir/subdir'
480
+ ru.call({}).must_equal 3
481
+ chroot
482
+ update_app("module C; B = 4; end", 'spec/dir/subdir2/app_mod2.rb')
483
+ ru.call({}).must_equal 4
484
+ end
485
+
486
+ it "should handle recorded dependencies in directories when files are added or removed later when chrooting 1" do
487
+ base_ru
488
+ update_app("class App; A = defined?(::A) ? ::A : Module.new{self::B = 0}; def self.call(env) A::B end; end")
489
+ ru.record_dependency 'spec/dir/subdir', 'spec/app.rb'
490
+ ru.record_dependency 'spec/dir/subdir2', 'spec/dir/subdir'
491
+ ru.require 'spec/app.rb'
492
+ ru.require 'spec/dir/subdir'
493
+ ru.require 'spec/dir/subdir2'
494
+ ru.call({}).must_equal 0
495
+ chroot
496
+ update_app("module A; B = 1; end", 'spec/dir/subdir/app_mod.rb')
497
+ ru.call({}).must_equal 1
498
+ update_app("module A; B = 2; end", 'spec/dir/subdir/app_mod.rb')
499
+ ru.call({}).must_equal 2
500
+ update_app("module C; B = 3; end", 'spec/dir/subdir2/app_mod2.rb')
501
+ ru.call({}).must_equal 2
502
+ update_app("module A; include C; end", 'spec/dir/subdir/app_mod.rb')
503
+ ru.call({}).must_equal 3
504
+ update_app("module C; B = 4; end", 'spec/dir/subdir2/app_mod2.rb')
505
+ ru.call({}).must_equal 4
506
+ File.delete 'spec/dir/subdir/app_mod.rb'
507
+ ru.call({}).must_equal 0
508
+ end
509
+
510
+ it "should handle recorded dependencies in directories when files are added or removed later when chrooting 2" do
511
+ base_ru
512
+ update_app("class App; A = defined?(::A) ? ::A : Module.new{self::B = 0}; def self.call(env) A::B end; end")
513
+ ru.record_dependency 'spec/dir/subdir', 'spec/app.rb'
514
+ ru.record_dependency 'spec/dir/subdir2', 'spec/dir/subdir'
515
+ ru.require 'spec/app.rb'
516
+ ru.require 'spec/dir/subdir'
517
+ ru.require 'spec/dir/subdir2'
518
+ ru.call({}).must_equal 0
519
+ update_app("module A; B = 1; end", 'spec/dir/subdir/app_mod.rb')
520
+ ru.call({}).must_equal 1
521
+ chroot
522
+ update_app("module A; B = 2; end", 'spec/dir/subdir/app_mod.rb')
523
+ ru.call({}).must_equal 2
524
+ update_app("module C; B = 3; end", 'spec/dir/subdir2/app_mod2.rb')
525
+ ru.call({}).must_equal 2
526
+ update_app("module A; include C; end", 'spec/dir/subdir/app_mod.rb')
527
+ ru.call({}).must_equal 3
528
+ update_app("module C; B = 4; end", 'spec/dir/subdir2/app_mod2.rb')
529
+ ru.call({}).must_equal 4
530
+ File.delete 'spec/dir/subdir/app_mod.rb'
531
+ ru.call({}).must_equal 0
532
+ end
533
+
534
+ it "should handle recorded dependencies in directories when files are added or removed later when chrooting 3" do
535
+ base_ru
536
+ update_app("class App; A = defined?(::A) ? ::A : Module.new{self::B = 0}; def self.call(env) A::B end; end")
537
+ ru.record_dependency 'spec/dir/subdir', 'spec/app.rb'
538
+ ru.record_dependency 'spec/dir/subdir2', 'spec/dir/subdir'
539
+ ru.require 'spec/app.rb'
540
+ ru.require 'spec/dir/subdir'
541
+ ru.require 'spec/dir/subdir2'
542
+ ru.call({}).must_equal 0
543
+ update_app("module A; B = 1; end", 'spec/dir/subdir/app_mod.rb')
544
+ ru.call({}).must_equal 1
545
+ update_app("module A; B = 2; end", 'spec/dir/subdir/app_mod.rb')
546
+ ru.call({}).must_equal 2
547
+ chroot
548
+ update_app("module C; B = 3; end", 'spec/dir/subdir2/app_mod2.rb')
549
+ ru.call({}).must_equal 2
550
+ update_app("module A; include C; end", 'spec/dir/subdir/app_mod.rb')
551
+ ru.call({}).must_equal 3
552
+ update_app("module C; B = 4; end", 'spec/dir/subdir2/app_mod2.rb')
553
+ ru.call({}).must_equal 4
554
+ File.delete 'spec/dir/subdir/app_mod.rb'
555
+ ru.call({}).must_equal 0
556
+ end
557
+
558
+ it "should handle recorded dependencies in directories when files are added or removed later when chrooting 4" do
559
+ base_ru
560
+ update_app("class App; A = defined?(::A) ? ::A : Module.new{self::B = 0}; def self.call(env) A::B end; end")
561
+ ru.record_dependency 'spec/dir/subdir', 'spec/app.rb'
562
+ ru.record_dependency 'spec/dir/subdir2', 'spec/dir/subdir'
563
+ ru.require 'spec/app.rb'
564
+ ru.require 'spec/dir/subdir'
565
+ ru.require 'spec/dir/subdir2'
566
+ ru.call({}).must_equal 0
567
+ update_app("module A; B = 1; end", 'spec/dir/subdir/app_mod.rb')
568
+ ru.call({}).must_equal 1
569
+ update_app("module A; B = 2; end", 'spec/dir/subdir/app_mod.rb')
570
+ ru.call({}).must_equal 2
571
+ update_app("module C; B = 3; end", 'spec/dir/subdir2/app_mod2.rb')
572
+ ru.call({}).must_equal 2
573
+ chroot
574
+ update_app("module A; include C; end", 'spec/dir/subdir/app_mod.rb')
575
+ ru.call({}).must_equal 3
576
+ update_app("module C; B = 4; end", 'spec/dir/subdir2/app_mod2.rb')
577
+ ru.call({}).must_equal 4
578
+ File.delete 'spec/dir/subdir/app_mod.rb'
579
+ ru.call({}).must_equal 0
580
+ end
581
+
582
+ it "should handle recorded dependencies in directories when files are added or removed later when chrooting 5" do
583
+ base_ru
584
+ update_app("class App; A = defined?(::A) ? ::A : Module.new{self::B = 0}; def self.call(env) A::B end; end")
585
+ ru.record_dependency 'spec/dir/subdir', 'spec/app.rb'
586
+ ru.record_dependency 'spec/dir/subdir2', 'spec/dir/subdir'
587
+ ru.require 'spec/app.rb'
588
+ ru.require 'spec/dir/subdir'
589
+ ru.require 'spec/dir/subdir2'
590
+ ru.call({}).must_equal 0
591
+ update_app("module A; B = 1; end", 'spec/dir/subdir/app_mod.rb')
592
+ ru.call({}).must_equal 1
593
+ update_app("module A; B = 2; end", 'spec/dir/subdir/app_mod.rb')
594
+ ru.call({}).must_equal 2
595
+ update_app("module C; B = 3; end", 'spec/dir/subdir2/app_mod2.rb')
596
+ ru.call({}).must_equal 2
597
+ update_app("module A; include C; end", 'spec/dir/subdir/app_mod.rb')
598
+ ru.call({}).must_equal 3
599
+ chroot
600
+ update_app("module C; B = 4; end", 'spec/dir/subdir2/app_mod2.rb')
601
+ ru.call({}).must_equal 4
602
+ File.delete 'spec/dir/subdir/app_mod.rb'
603
+ ru.call({}).must_equal 0
604
+ end
605
+
606
+ it "should handle recorded dependencies in directories when files are added or removed later when chrooting 6" do
607
+ base_ru
608
+ update_app("class App; A = defined?(::A) ? ::A : Module.new{self::B = 0}; def self.call(env) A::B end; end")
609
+ ru.record_dependency 'spec/dir/subdir', 'spec/app.rb'
610
+ ru.record_dependency 'spec/dir/subdir2', 'spec/dir/subdir'
611
+ ru.require 'spec/app.rb'
612
+ ru.require 'spec/dir/subdir'
613
+ ru.require 'spec/dir/subdir2'
614
+ ru.call({}).must_equal 0
615
+ update_app("module A; B = 1; end", 'spec/dir/subdir/app_mod.rb')
616
+ ru.call({}).must_equal 1
617
+ update_app("module A; B = 2; end", 'spec/dir/subdir/app_mod.rb')
618
+ ru.call({}).must_equal 2
619
+ update_app("module C; B = 3; end", 'spec/dir/subdir2/app_mod2.rb')
620
+ ru.call({}).must_equal 2
621
+ update_app("module A; include C; end", 'spec/dir/subdir/app_mod.rb')
622
+ ru.call({}).must_equal 3
623
+ update_app("module C; B = 4; end", 'spec/dir/subdir2/app_mod2.rb')
624
+ ru.call({}).must_equal 4
625
+ chroot
626
+ File.delete 'spec/dir/subdir/app_mod.rb'
627
+ ru.call({}).must_equal 0
628
+ end
629
+
630
+ it "should handle classes split into multiple files when chrooting 1" do
631
+ base_ru
632
+ update_app("class App; RU.require('spec/dir'); def self.call(env) \"\#{a if respond_to?(:a)}\#{b if respond_to?(:b)}1\".to_i end; end")
633
+ ru.require 'spec/app.rb'
634
+ ru.record_split_class 'spec/app.rb', 'spec/dir'
635
+ ru.call({}).must_equal 1
636
+ chroot
637
+ update_app("class App; def self.a; 2 end end", 'spec/dir/appa.rb')
638
+ ru.call({}).must_equal 21
639
+ update_app("class App; def self.a; 3 end end", 'spec/dir/appa.rb')
640
+ ru.call({}).must_equal 31
641
+ update_app("class App; def self.b; 4 end end", 'spec/dir/appb.rb')
642
+ ru.call({}).must_equal 341
643
+ update_app("class App; def self.a; 5 end end", 'spec/dir/appa.rb')
644
+ update_app("class App; def self.b; 6 end end", 'spec/dir/appb.rb')
645
+ ru.call({}).must_equal 561
646
+ update_app("class App; end", 'spec/dir/appa.rb')
647
+ ru.call({}).must_equal 61
648
+ File.delete 'spec/dir/appb.rb'
649
+ ru.call({}).must_equal 1
650
+ end
651
+
652
+ it "should handle classes split into multiple files when chrooting 2" do
653
+ base_ru
654
+ update_app("class App; RU.require('spec/dir'); def self.call(env) \"\#{a if respond_to?(:a)}\#{b if respond_to?(:b)}1\".to_i end; end")
655
+ ru.require 'spec/app.rb'
656
+ ru.record_split_class 'spec/app.rb', 'spec/dir'
657
+ ru.call({}).must_equal 1
658
+ update_app("class App; def self.a; 2 end end", 'spec/dir/appa.rb')
659
+ ru.call({}).must_equal 21
660
+ chroot
661
+ update_app("class App; def self.a; 3 end end", 'spec/dir/appa.rb')
662
+ ru.call({}).must_equal 31
663
+ update_app("class App; def self.b; 4 end end", 'spec/dir/appb.rb')
664
+ ru.call({}).must_equal 341
665
+ update_app("class App; def self.a; 5 end end", 'spec/dir/appa.rb')
666
+ update_app("class App; def self.b; 6 end end", 'spec/dir/appb.rb')
667
+ ru.call({}).must_equal 561
668
+ update_app("class App; end", 'spec/dir/appa.rb')
669
+ ru.call({}).must_equal 61
670
+ File.delete 'spec/dir/appb.rb'
671
+ ru.call({}).must_equal 1
672
+ end
673
+
674
+ it "should handle classes split into multiple files when chrooting 3" do
675
+ base_ru
676
+ update_app("class App; RU.require('spec/dir'); def self.call(env) \"\#{a if respond_to?(:a)}\#{b if respond_to?(:b)}1\".to_i end; end")
677
+ ru.require 'spec/app.rb'
678
+ ru.record_split_class 'spec/app.rb', 'spec/dir'
679
+ ru.call({}).must_equal 1
680
+ update_app("class App; def self.a; 2 end end", 'spec/dir/appa.rb')
681
+ ru.call({}).must_equal 21
682
+ update_app("class App; def self.a; 3 end end", 'spec/dir/appa.rb')
683
+ ru.call({}).must_equal 31
684
+ chroot
685
+ update_app("class App; def self.b; 4 end end", 'spec/dir/appb.rb')
686
+ ru.call({}).must_equal 341
687
+ update_app("class App; def self.a; 5 end end", 'spec/dir/appa.rb')
688
+ update_app("class App; def self.b; 6 end end", 'spec/dir/appb.rb')
689
+ ru.call({}).must_equal 561
690
+ update_app("class App; end", 'spec/dir/appa.rb')
691
+ ru.call({}).must_equal 61
692
+ File.delete 'spec/dir/appb.rb'
693
+ ru.call({}).must_equal 1
694
+ end
695
+
696
+ it "should handle classes split into multiple files when chrooting 4" do
697
+ base_ru
698
+ update_app("class App; RU.require('spec/dir'); def self.call(env) \"\#{a if respond_to?(:a)}\#{b if respond_to?(:b)}1\".to_i end; end")
699
+ ru.require 'spec/app.rb'
700
+ ru.record_split_class 'spec/app.rb', 'spec/dir'
701
+ ru.call({}).must_equal 1
702
+ update_app("class App; def self.a; 2 end end", 'spec/dir/appa.rb')
703
+ ru.call({}).must_equal 21
704
+ update_app("class App; def self.a; 3 end end", 'spec/dir/appa.rb')
705
+ ru.call({}).must_equal 31
706
+ update_app("class App; def self.b; 4 end end", 'spec/dir/appb.rb')
707
+ ru.call({}).must_equal 341
708
+ chroot
709
+ update_app("class App; def self.a; 5 end end", 'spec/dir/appa.rb')
710
+ update_app("class App; def self.b; 6 end end", 'spec/dir/appb.rb')
711
+ ru.call({}).must_equal 561
712
+ update_app("class App; end", 'spec/dir/appa.rb')
713
+ ru.call({}).must_equal 61
714
+ File.delete 'spec/dir/appb.rb'
715
+ ru.call({}).must_equal 1
716
+ end
717
+
718
+ it "should handle classes split into multiple files when chrooting 5" do
719
+ base_ru
720
+ update_app("class App; RU.require('spec/dir'); def self.call(env) \"\#{a if respond_to?(:a)}\#{b if respond_to?(:b)}1\".to_i end; end")
721
+ ru.require 'spec/app.rb'
722
+ ru.record_split_class 'spec/app.rb', 'spec/dir'
723
+ ru.call({}).must_equal 1
724
+ update_app("class App; def self.a; 2 end end", 'spec/dir/appa.rb')
725
+ ru.call({}).must_equal 21
726
+ update_app("class App; def self.a; 3 end end", 'spec/dir/appa.rb')
727
+ ru.call({}).must_equal 31
728
+ update_app("class App; def self.b; 4 end end", 'spec/dir/appb.rb')
729
+ ru.call({}).must_equal 341
730
+ update_app("class App; def self.a; 5 end end", 'spec/dir/appa.rb')
731
+ update_app("class App; def self.b; 6 end end", 'spec/dir/appb.rb')
732
+ ru.call({}).must_equal 561
733
+ chroot
734
+ update_app("class App; end", 'spec/dir/appa.rb')
735
+ ru.call({}).must_equal 61
736
+ File.delete 'spec/dir/appb.rb'
737
+ ru.call({}).must_equal 1
738
+ end
739
+
740
+ it "should handle classes split into multiple files when chrooting 6" do
741
+ base_ru
742
+ update_app("class App; RU.require('spec/dir'); def self.call(env) \"\#{a if respond_to?(:a)}\#{b if respond_to?(:b)}1\".to_i end; end")
743
+ ru.require 'spec/app.rb'
744
+ ru.record_split_class 'spec/app.rb', 'spec/dir'
745
+ ru.call({}).must_equal 1
746
+ update_app("class App; def self.a; 2 end end", 'spec/dir/appa.rb')
747
+ ru.call({}).must_equal 21
748
+ update_app("class App; def self.a; 3 end end", 'spec/dir/appa.rb')
749
+ ru.call({}).must_equal 31
750
+ update_app("class App; def self.b; 4 end end", 'spec/dir/appb.rb')
751
+ ru.call({}).must_equal 341
752
+ update_app("class App; def self.a; 5 end end", 'spec/dir/appa.rb')
753
+ update_app("class App; def self.b; 6 end end", 'spec/dir/appb.rb')
754
+ ru.call({}).must_equal 561
755
+ update_app("class App; end", 'spec/dir/appa.rb')
756
+ ru.call({}).must_equal 61
757
+ chroot
758
+ File.delete 'spec/dir/appb.rb'
759
+ ru.call({}).must_equal 1
760
+ end
761
+
762
+ it "should pick up changes to files in that directory" do
763
+ base_ru
764
+ update_app("class App; @a = {}; def self.call(env=nil) @a end; end; RU.require 'spec/dir'")
765
+ update_app("App.call[:foo] = 1", 'spec/dir/a.rb')
766
+ @ru.require('spec/app.rb')
767
+ ru.call({}).must_equal(:foo=>1)
768
+ chroot
769
+ update_app("App.call[:foo] = 2", 'spec/dir/a.rb')
770
+ ru.call({}).must_equal(:foo=>2)
771
+ log_match %r{\ALoading.*spec/app\.rb\z},
772
+ %r{\ALoading.*spec/dir/a\.rb\z},
773
+ %r{\ANew classes in .*spec/app\.rb: App\z},
774
+ %r{\ANew features in .*spec/app\.rb: .*spec/dir/a\.rb\z},
775
+ %r{\AUnloading /spec/dir/a.rb\z},
776
+ %r{\ALoading /spec/dir/a.rb\z}
777
+ end
778
+
779
+ it "should pick up changes to files in subdirectories" do
780
+ base_ru
781
+ update_app("class App; @a = {}; def self.call(env=nil) @a end; end; RU.require 'spec/dir'")
782
+ update_app("App.call[:foo] = 1", 'spec/dir/subdir/a.rb')
783
+ @ru.require('spec/app.rb')
784
+ ru.call({}).must_equal(:foo=>1)
785
+ chroot
786
+ update_app("App.call[:foo] = 2", 'spec/dir/subdir/a.rb')
787
+ ru.call({}).must_equal(:foo=>2)
788
+ log_match %r{\ALoading.*spec/app\.rb\z},
789
+ %r{\ALoading.*spec/dir/subdir/a\.rb\z},
790
+ %r{\ANew classes in .*spec/app\.rb: App\z},
791
+ %r{\ANew features in .*spec/app\.rb: .*spec/dir/subdir/a\.rb\z},
792
+ %r{\AUnloading /spec/dir/subdir/a.rb\z},
793
+ %r{\ALoading /spec/dir/subdir/a.rb\z}
794
+ end
795
+
796
+ it "should pick up new files added to the directory" do
797
+ base_ru
798
+ update_app("class App; @a = {}; def self.call(env=nil) @a end; end; RU.require 'spec/dir'")
799
+ @ru.require('spec/app.rb')
800
+ ru.call({}).must_equal({})
801
+ chroot
802
+ update_app("App.call[:foo] = 2", 'spec/dir/a.rb')
803
+ ru.call({}).must_equal(:foo=>2)
804
+ log_match %r{\ALoading.*spec/app\.rb\z},
805
+ %r{\ANew classes in .*spec/app\.rb: App\z},
806
+ %r{\ALoading /spec/dir/a\.rb\z}
807
+ end
808
+
809
+ it "should pick up new files added to subdirectories" do
810
+ base_ru
811
+ update_app("class App; @a = {}; def self.call(env=nil) @a end; end; RU.require 'spec/dir'")
812
+ @ru.require('spec/app.rb')
813
+ ru.call({}).must_equal({})
814
+ chroot
815
+ update_app("App.call[:foo] = 2", 'spec/dir/subdir/a.rb')
816
+ ru.call({}).must_equal(:foo=>2)
817
+ log_match %r{\ALoading.*spec/app\.rb\z},
818
+ %r{\ANew classes in .*spec/app\.rb: App\z},
819
+ %r{\ALoading /spec/dir/subdir/a\.rb\z}
820
+ end
821
+
822
+ it "should drop files deleted from the directory" do
823
+ base_ru
824
+ update_app("class App; @a = {}; def self.call(env=nil) @a end; end; RU.require 'spec/dir'")
825
+ update_app("App.call[:foo] = 1", 'spec/dir/a.rb')
826
+ @ru.require('spec/app.rb')
827
+ ru.call({}).must_equal(:foo=>1)
828
+ chroot
829
+ File.delete('spec/dir/a.rb')
830
+ update_app("App.call[:foo] = 2", 'spec/dir/b.rb')
831
+ ru.call({}).must_equal(:foo=>2)
832
+ log_match %r{\ALoading.*spec/app\.rb\z},
833
+ %r{\ALoading.*spec/dir/a\.rb\z},
834
+ %r{\ANew classes in .*spec/app\.rb: App\z},
835
+ %r{\ANew features in .*spec/app\.rb: .*spec/dir/a\.rb\z},
836
+ %r{\AUnloading /spec/dir/a.rb\z},
837
+ %r{\ALoading /spec/dir/b\.rb\z}
838
+ end
839
+
840
+ it "should drop files deleted from subdirectories" do
841
+ base_ru
842
+ update_app("class App; @a = {}; def self.call(env=nil) @a end; end; RU.require 'spec/dir'")
843
+ update_app("App.call[:foo] = 1", 'spec/dir/subdir/a.rb')
844
+ @ru.require('spec/app.rb')
845
+ ru.call({}).must_equal(:foo=>1)
846
+ chroot
847
+ File.delete('spec/dir/subdir/a.rb')
848
+ update_app("App.call[:foo] = 2", 'spec/dir/subdir/b.rb')
849
+ ru.call({}).must_equal(:foo=>2)
850
+ log_match %r{\ALoading.*spec/app\.rb\z},
851
+ %r{\ALoading.*spec/dir/subdir/a\.rb\z},
852
+ %r{\ANew classes in .*spec/app\.rb: App\z},
853
+ %r{\ANew features in .*spec/app\.rb: .*spec/dir/subdir/a\.rb\z},
854
+ %r{\AUnloading /spec/dir/subdir/a.rb\z},
855
+ %r{\ALoading /spec/dir/subdir/b\.rb\z}
856
+ end
857
+ end
858
+ end
@@ -1,76 +1,6 @@
1
- require File.join(File.dirname(File.expand_path(__FILE__)), '../lib/rack/unreloader')
2
- gem 'minitest'
3
- require 'minitest/autorun'
4
- require 'minitest/hooks'
5
-
6
- module ModifiedAt
7
- def set_modified_time(file, time)
8
- modified_times[File.expand_path(file)] = time
9
- end
10
-
11
- def modified_times
12
- @modified_times ||= {}
13
- end
14
-
15
- private
16
-
17
- def modified_at(file)
18
- modified_times[file] || super
19
- end
20
- end
1
+ require File.join(File.dirname(File.expand_path(__FILE__)), 'spec_helper')
21
2
 
22
3
  describe Rack::Unreloader do
23
- def code(i)
24
- "class App; def self.call(env) @a end; @a ||= []; @a << #{i}; end"
25
- end
26
-
27
- def update_app(code, file=@filename)
28
- ru.reloader.set_modified_time(file, @i += 1) if ru.reloader
29
- File.open(file, 'wb'){|f| f.write(code)}
30
- end
31
-
32
- def logger
33
- return @logger if @logger
34
- @logger = []
35
- def @logger.method_missing(meth, log)
36
- self << log
37
- end
38
- @logger
39
- end
40
-
41
- def base_ru(opts={})
42
- block = opts[:block] || proc{App}
43
- @ru = Rack::Unreloader.new({:logger=>logger, :cooldown=>0}.merge(opts), &block)
44
- @ru.reloader.extend ModifiedAt if @ru.reloader
45
- Object.const_set(:RU, @ru)
46
- end
47
-
48
- def ru(opts={})
49
- return @ru if @ru
50
- base_ru(opts)
51
- update_app(opts[:code]||code(1))
52
- @ru.require @filename
53
- @ru
54
- end
55
-
56
- def log_match(*logs)
57
- @logger.length.must_equal logs.length
58
- logs.zip(@logger).each{|l, log| l.is_a?(String) ? log.must_equal(l) : log.must_match(l)}
59
- end
60
-
61
- before do
62
- @i = 0
63
- @filename = 'spec/app.rb'
64
- end
65
-
66
- after do
67
- ru.reloader.clear! if ru.reloader
68
- Object.send(:remove_const, :RU)
69
- Object.send(:remove_const, :App) if defined?(::App)
70
- Object.send(:remove_const, :App2) if defined?(::App2)
71
- Dir['spec/app*.rb'].each{|f| File.delete(f)}
72
- end
73
-
74
4
  it "should not reload files automatically if cooldown option is nil" do
75
5
  ru(:cooldown => nil).call({}).must_equal [1]
76
6
  update_app(code(2))
@@ -213,7 +143,7 @@ describe Rack::Unreloader do
213
143
  ru.call({}).must_equal [1]
214
144
  update_app("module App; def self.call(env) @a end; @a ||= []; raise 'foo'; end")
215
145
  proc{ru.call({})}.must_raise RuntimeError
216
- defined?(::App).must_equal nil
146
+ defined?(::App).must_be_nil
217
147
  update_app(code(2))
218
148
  ru.call({}).must_equal [2]
219
149
  log_match %r{\ALoading.*spec/app\.rb\z},
metadata CHANGED
@@ -1,14 +1,14 @@
1
1
  --- !ruby/object:Gem::Specification
2
2
  name: rack-unreloader
3
3
  version: !ruby/object:Gem::Version
4
- version: 1.5.0
4
+ version: 1.6.0
5
5
  platform: ruby
6
6
  authors:
7
7
  - Jeremy Evans
8
8
  autorequire:
9
9
  bindir: bin
10
10
  cert_chain: []
11
- date: 2016-03-10 00:00:00.000000000 Z
11
+ date: 2017-02-24 00:00:00.000000000 Z
12
12
  dependencies:
13
13
  - !ruby/object:Gem::Dependency
14
14
  name: minitest
@@ -55,6 +55,8 @@ files:
55
55
  - Rakefile
56
56
  - lib/rack/unreloader.rb
57
57
  - lib/rack/unreloader/reloader.rb
58
+ - spec/spec_helper.rb
59
+ - spec/strip_paths_spec.rb
58
60
  - spec/unreloader_spec.rb
59
61
  homepage: http://github.com/jeremyevans/rack-unreloader
60
62
  licenses:
@@ -83,7 +85,7 @@ required_rubygems_version: !ruby/object:Gem::Requirement
83
85
  version: '0'
84
86
  requirements: []
85
87
  rubyforge_project:
86
- rubygems_version: 2.5.1
88
+ rubygems_version: 2.6.8
87
89
  signing_key:
88
90
  specification_version: 4
89
91
  summary: Reload application when files change, unloading constants first