rack-unreloader 1.5.0 → 1.6.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.
- checksums.yaml +4 -4
- data/CHANGELOG +4 -0
- data/README.rdoc +16 -3
- data/Rakefile +1 -1
- data/lib/rack/unreloader.rb +12 -0
- data/lib/rack/unreloader/reloader.rb +41 -0
- data/spec/spec_helper.rb +76 -0
- data/spec/strip_paths_spec.rb +858 -0
- data/spec/unreloader_spec.rb +2 -72
- metadata +5 -3
checksums.yaml
CHANGED
@@ -1,7 +1,7 @@
|
|
1
1
|
---
|
2
2
|
SHA1:
|
3
|
-
metadata.gz:
|
4
|
-
data.tar.gz:
|
3
|
+
metadata.gz: 63e0754f2064aacba7745d52c60d136019dd5fdf
|
4
|
+
data.tar.gz: 31d3d8223106f59d5fa739b429cbf08d6d47db31
|
5
5
|
SHA512:
|
6
|
-
metadata.gz:
|
7
|
-
data.tar.gz:
|
6
|
+
metadata.gz: 6645aa444fd1c88c52c09f0de56894f640cf9404624f0e25197071b1498d1aa7745a1bb9a8e9416f3cc7d8aeacf4f993e552adaa06f4b1386be95db43aef4272
|
7
|
+
data.tar.gz: 26811ff5218e1fe20e15f0619dcfb4d4dbabcba2819c82e8c46f428fdfb1714a3348a12a931ba8065d5f6cb182336e989e6d98d93e7a2ac633f4c6c7d783fc43
|
data/CHANGELOG
CHANGED
data/README.rdoc
CHANGED
@@ -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
|
266
|
-
JRuby if you use a proc to specify the constants
|
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
data/lib/rack/unreloader.rb
CHANGED
@@ -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!
|
data/spec/spec_helper.rb
ADDED
@@ -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
|
data/spec/unreloader_spec.rb
CHANGED
@@ -1,76 +1,6 @@
|
|
1
|
-
require File.join(File.dirname(File.expand_path(__FILE__)), '
|
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).
|
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.
|
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:
|
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.
|
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
|