Skip to content

Commit 78b2503

Browse files
committed
refactor: move reloadable require internal state out of IRB.conf
1 parent 507ae0b commit 78b2503

3 files changed

Lines changed: 61 additions & 69 deletions

File tree

lib/irb/init.rb

Lines changed: 0 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -4,8 +4,6 @@
44
# by Keiju ISHITSUKA(keiju@ruby-lang.org)
55
#
66

7-
require 'set'
8-
97
module IRB # :nodoc:
108
@CONF = {}
119
@INITIALIZED = false
@@ -201,8 +199,6 @@ def IRB.init_config(ap_path)
201199
@CONF[:COPY_COMMAND] = ENV.fetch("IRB_COPY_COMMAND", nil)
202200

203201
@CONF[:RELOADABLE_REQUIRE] = false
204-
@CONF[:__RELOADABLE_FILES__] = Set.new
205-
@CONF[:__AUTOLOAD_FILES__] = Set.new
206202
end
207203

208204
def IRB.set_measure_callback(type = nil, arg = nil, &block)

lib/irb/reloadable_require.rb

Lines changed: 32 additions & 28 deletions
Original file line numberDiff line numberDiff line change
@@ -4,6 +4,8 @@
44
raise "ReloadableRequire requires Ruby::Box to be enabled"
55
end
66

7+
require 'set'
8+
79
module IRB
810
# Provides reload-aware require functionality for IRB.
911
#
@@ -15,28 +17,6 @@ module IRB
1517
#
1618
# This feature requires Ruby::Box (Ruby 4.0+).
1719

18-
class << self
19-
def track_and_load_files(source, current_box)
20-
before = source.dup
21-
result = yield
22-
new_files = source - before
23-
24-
return result if new_files.empty?
25-
26-
ruby_files, native_extensions = new_files.partition { |path| path.end_with?('.rb') }
27-
28-
native_extensions.each { |path| current_box.require(path) }
29-
30-
IRB.conf[:__RELOADABLE_FILES__].merge(ruby_files)
31-
32-
main_loaded_features = current_box.eval('$LOADED_FEATURES')
33-
main_loaded_features.concat(ruby_files - main_loaded_features)
34-
ruby_files.each { |path| current_box.load(path) }
35-
36-
result
37-
end
38-
end
39-
4020
unless Ruby::Box.method_defined?(:__irb_original_require__)
4121
Ruby::Box.class_eval do
4222
alias_method :__irb_original_require__, :require
@@ -45,14 +25,13 @@ def track_and_load_files(source, current_box)
4525
end
4626

4727
Ruby::Box.class_eval do
48-
4928
def __irb_reloadable_require__(feature)
50-
unless IRB.conf[:__AUTOLOAD_FILES__].include?(feature)
29+
unless IRB::ReloadableRequire.autoload_files.include?(feature)
5130
return __irb_original_require__(feature)
5231
end
5332

54-
IRB.conf[:__AUTOLOAD_FILES__].delete(feature)
55-
IRB.track_and_load_files($LOADED_FEATURES, Ruby::Box.main) { __irb_original_require__(feature) }
33+
IRB::ReloadableRequire.autoload_files.delete(feature)
34+
IRB::ReloadableRequire.track_and_load_files($LOADED_FEATURES, Ruby::Box.main) { __irb_original_require__(feature) }
5635
end
5736

5837
def __irb_reloadable_require_relative__(feature)
@@ -61,7 +40,12 @@ def __irb_reloadable_require_relative__(feature)
6140
end
6241

6342
module ReloadableRequire
43+
@reloadable_files = Set.new
44+
@autoload_files = Set.new
45+
6446
class << self
47+
attr_reader :reloadable_files, :autoload_files
48+
6549
def extended(base)
6650
apply_autoload_hook
6751
end
@@ -72,6 +56,26 @@ def apply_autoload_hook
7256
alias_method :require_relative, :__irb_reloadable_require_relative__
7357
end
7458
end
59+
60+
def track_and_load_files(source, current_box)
61+
before = source.dup
62+
result = yield
63+
new_files = source - before
64+
65+
return result if new_files.empty?
66+
67+
ruby_files, native_extensions = new_files.partition { |path| path.end_with?('.rb') }
68+
69+
native_extensions.each { |path| current_box.require(path) }
70+
71+
@reloadable_files.merge(ruby_files)
72+
73+
main_loaded_features = current_box.eval('$LOADED_FEATURES')
74+
main_loaded_features.concat(ruby_files - main_loaded_features)
75+
ruby_files.each { |path| current_box.load(path) }
76+
77+
result
78+
end
7579
end
7680

7781
private
@@ -81,7 +85,7 @@ def reloadable_require_internal(feature, caller_box)
8185
box.eval("$LOAD_PATH.concat(#{caller_box.eval('$LOAD_PATH')})")
8286
box.eval("$LOADED_FEATURES.concat(#{caller_box.eval('$LOADED_FEATURES')})")
8387

84-
IRB.track_and_load_files(box.eval('$LOADED_FEATURES'), caller_box) { box.__irb_original_require__(feature) }
88+
ReloadableRequire.track_and_load_files(box.eval('$LOADED_FEATURES'), caller_box) { box.__irb_original_require__(feature) }
8589
end
8690

8791
def require(feature)
@@ -108,7 +112,7 @@ def require_relative(feature)
108112
end
109113

110114
def autoload(const, feature)
111-
IRB.conf[:__AUTOLOAD_FILES__] << feature
115+
ReloadableRequire.autoload_files << feature
112116
Ruby::Box.main.eval("Kernel.autoload(:#{const}, #{feature.dump})")
113117
end
114118
end

test/irb/test_reloadable_require.rb

Lines changed: 29 additions & 37 deletions
Original file line numberDiff line numberDiff line change
@@ -203,6 +203,20 @@ def test_reload_preserves_loaded_features
203203
assert_equal 2, output.scan("=> true").count
204204
end
205205

206+
def test_require_native_extension
207+
write_ruby <<~'RUBY'
208+
binding.irb
209+
RUBY
210+
211+
output = run_ruby_file do
212+
type "require 'etc'"
213+
type "Etc.getlogin"
214+
type "exit!"
215+
end
216+
217+
assert_include output, "=> true"
218+
end
219+
206220
def test_require_does_not_modify_load_path
207221
write_ruby <<~'RUBY'
208222
binding.irb
@@ -267,9 +281,11 @@ def create_pwd_file(name, content)
267281
end
268282
end
269283

270-
class ReloadCommandTest < TestCase
284+
class BoxReloadCommandTest < TestCase
271285
def setup
272286
super
287+
omit "BoxReload requires Ruby::Box" if !defined?(Ruby::Box) || !Ruby::Box.enabled?
288+
273289
@tmpdir = Dir.mktmpdir
274290
@valid_file = File.join(@tmpdir, "valid.rb")
275291
File.write(@valid_file, "VALID = true\n")
@@ -279,25 +295,28 @@ def setup
279295
require "irb/command/reload"
280296
IRB.setup(__FILE__, argv: [])
281297
@original_reloadable = IRB.conf[:RELOADABLE_REQUIRE]
282-
@original_files = IRB.conf[:__RELOADABLE_FILES__]
283298
IRB.conf[:RELOADABLE_REQUIRE] = true
284-
IRB.conf[:__RELOADABLE_FILES__] = Set.new
299+
300+
@saved_files = IRB::ReloadableRequire.reloadable_files.dup
301+
IRB::ReloadableRequire.reloadable_files.clear
285302
end
286303

287304
def teardown
288305
super
289-
FileUtils.rm_rf(@tmpdir)
290-
IRB.conf[:RELOADABLE_REQUIRE] = @original_reloadable
291-
IRB.conf[:__RELOADABLE_FILES__] = @original_files
306+
FileUtils.rm_rf(@tmpdir) if @tmpdir
307+
if defined?(IRB::ReloadableRequire) && @saved_files
308+
IRB.conf[:RELOADABLE_REQUIRE] = @original_reloadable
309+
IRB::ReloadableRequire.reloadable_files.replace(@saved_files)
310+
end
292311
end
293312

294313
def test_reload_file_preserves_loaded_features_on_syntax_error
295314
$LOADED_FEATURES << @valid_file
296315

297316
File.write(@valid_file, "def broken(")
298317

299-
cmd = IRB::Command::Reload.new(nil)
300-
IRB.conf[:__RELOADABLE_FILES__] << @valid_file
318+
cmd = IRB::Command::BoxReload.new(nil)
319+
IRB::ReloadableRequire.reloadable_files << @valid_file
301320
cmd.execute(nil)
302321

303322
assert_equal true, $LOADED_FEATURES.include?(@valid_file)
@@ -309,8 +328,8 @@ def test_reload_file_preserves_loaded_features_on_load_error
309328
missing_file = File.join(@tmpdir, "missing.rb")
310329
$LOADED_FEATURES << missing_file
311330

312-
cmd = IRB::Command::Reload.new(nil)
313-
IRB.conf[:__RELOADABLE_FILES__] << missing_file
331+
cmd = IRB::Command::BoxReload.new(nil)
332+
IRB::ReloadableRequire.reloadable_files << missing_file
314333
cmd.execute(nil)
315334

316335
assert_equal true, $LOADED_FEATURES.include?(missing_file)
@@ -319,33 +338,6 @@ def test_reload_file_preserves_loaded_features_on_load_error
319338
end
320339
end
321340

322-
class ReloadableRequireMonkeyPatchTest < TestCase
323-
def setup
324-
super
325-
omit "ReloadableRequire requires Ruby::Box" if !defined?(Ruby::Box) || !Ruby::Box.enabled?
326-
require "irb/reloadable_require"
327-
@saved_original_require = Ruby::Box.instance_method(:__irb_original_require__)
328-
end
329-
330-
def teardown
331-
super
332-
saved = @saved_original_require
333-
if saved
334-
Ruby::Box.define_method(:__irb_original_require__, saved)
335-
end
336-
end
337-
338-
def test_original_require_alias_preserved_on_double_load
339-
IRB::ReloadableRequire.apply_autoload_hook
340-
341-
# Double-loading the file should not overwrite __irb_original_require__
342-
# with the already-patched require method.
343-
load File.expand_path("../../lib/irb/reloadable_require.rb", __dir__)
344-
345-
assert_equal @saved_original_require, Ruby::Box.instance_method(:__irb_original_require__)
346-
end
347-
end
348-
349341
class ReloadableRequireDisabledTest < IntegrationTestCase
350342
def setup
351343
super

0 commit comments

Comments
 (0)