Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Handle Concurrent Sessions and Saving Readline::HISTORY #651

Merged
merged 7 commits into from
Sep 16, 2023
Merged
Show file tree
Hide file tree
Changes from 1 commit
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
2 changes: 1 addition & 1 deletion lib/irb/history.rb
Original file line number Diff line number Diff line change
Expand Up @@ -51,7 +51,7 @@ def save_history

if File.exist?(history_file) &&
File.mtime(history_file) != @loaded_history_mtime
history = history[@loaded_history_lines..-1] if @loaded_history_lines
@loaded_history_lines&.times { history.delete_at(0) }
Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Maybe there's a better way to do this but Readline::HISTORY is pretty limited in how it can be manipulated.

The original issue here was a no implicit conversion of Range into Integer error because Readline::HISTORY doesn't take a range in [].

Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Unlike the previous code, Reline::HISTORY will be changed in this operation. Maybe it works, but it's safer and easy to maintain if it doesn't change HISTORY object itself.

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Good point. Updated.

append_history = true
end

Expand Down
83 changes: 49 additions & 34 deletions test/irb/test_history.rb
Original file line number Diff line number Diff line change
Expand Up @@ -14,12 +14,22 @@ def teardown
IRB.conf[:RC_NAME_GENERATOR] = nil
end

class TestInputMethodWithHistory < TestInputMethod
HISTORY = Array.new
class TestInputMethodWithRelineHistory < TestInputMethod
# When IRB.conf[:USE_MULTILINE] is true, IRB::ReidlineInputMethod uses Reline::History
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

IRB::ReidlineInputMethodIRB::RelineInputMethod
IRB::ReidlineInputMethod is not used anymore.

Other part of the change in test_history.rb looks great 👍

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Thanks. Updated.

HISTORY = Reline::History.new(Reline.core.config)
Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Changed from an Array to the history class that is used by default when :USE_MULTILINE is on.


include IRB::HistorySavingAbility
end

class TestInputMethodWithReadlineHistory < TestInputMethod
# When IRB.conf[:USE_MULTILINE] is false, IRB::ReadlineInputMethod uses Readline::HISTORY
HISTORY = Readline::HISTORY
Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I created an additional input method test class that uses Readline::HISTORY. At this point, I've only made use of it in the test_history_concurrent_use test method to illustrate the bug. I wasn't sure if we want to test both input methods for all tests.


include IRB::HistorySavingAbility
end

INPUT_METHODS = [TestInputMethodWithRelineHistory, TestInputMethodWithReadlineHistory].freeze

def test_history_save_1
omit "Skip Editline" if /EditLine/n.match(Readline::VERSION)
IRB.conf[:SAVE_HISTORY] = 1
Expand Down Expand Up @@ -102,31 +112,33 @@ def test_history_save_minus_as_infinity
def test_history_concurrent_use
omit "Skip Editline" if /EditLine/n.match(Readline::VERSION)
IRB.conf[:SAVE_HISTORY] = 1
assert_history(<<~EXPECTED_HISTORY, <<~INITIAL_HISTORY, <<~INPUT) do |history_file|
exit
5
exit
EXPECTED_HISTORY
1
2
3
4
INITIAL_HISTORY
5
exit
INPUT
assert_history(<<~EXPECTED_HISTORY2, <<~INITIAL_HISTORY2, <<~INPUT2)
exit
EXPECTED_HISTORY2
1
2
3
4
INITIAL_HISTORY2
5
exit
INPUT2
File.utime(File.atime(history_file), File.mtime(history_file) + 2, history_file)
INPUT_METHODS.each do |input_method|
assert_history(<<~EXPECTED_HISTORY, <<~INITIAL_HISTORY, <<~INPUT, input_method) do |history_file|
exit
5
exit
EXPECTED_HISTORY
1
2
3
4
INITIAL_HISTORY
5
exit
INPUT
assert_history(<<~EXPECTED_HISTORY2, <<~INITIAL_HISTORY2, <<~INPUT2, input_method)
exit
EXPECTED_HISTORY2
1
2
3
4
INITIAL_HISTORY2
5
exit
INPUT2
File.utime(File.atime(history_file), File.mtime(history_file) + 2, history_file)
end
end
end

Expand All @@ -138,10 +150,11 @@ def test_history_concurrent_use_not_present
IRB.conf[:SAVE_HISTORY] = 1
Dir.mktmpdir("test_irb_history_") do |tmpdir|
ENV["HOME"] = tmpdir
io = TestInputMethodWithHistory.new
io = TestInputMethodWithRelineHistory.new
io.class::HISTORY.clear
io.load_history
io.class::HISTORY.concat(%w"line1 line2")
io.class::HISTORY << 'line1'
io.class::HISTORY << 'line2'
Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

concat isn't available for Readline::HISTORY. I updated this logic so that this test could use TestInputMethodWithReadlineHistory if we wanted.


history_file = IRB.rc_file("_history")
assert_not_send [File, :file?, history_file]
Expand All @@ -157,7 +170,7 @@ def test_history_concurrent_use_not_present

private

def assert_history(expected_history, initial_irb_history, input)
def assert_history(expected_history, initial_irb_history, input, input_method = TestInputMethodWithRelineHistory)
backup_verbose, $VERBOSE = $VERBOSE, nil
backup_home = ENV["HOME"]
backup_xdg_config_home = ENV.delete("XDG_CONFIG_HOME")
Expand All @@ -169,15 +182,17 @@ def assert_history(expected_history, initial_irb_history, input)
f.write(initial_irb_history)
end

io = TestInputMethodWithHistory.new
io = input_method.new
io.class::HISTORY.clear
io.load_history
if block_given?
history = io.class::HISTORY.dup
previous_history = []
io.class::HISTORY.each { |line| previous_history << line }
yield IRB.rc_file("_history")
io.class::HISTORY.replace(history)
Copy link
Contributor Author

@chadrschroeder chadrschroeder Jul 19, 2023

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Readline::HISTORY doesn't respond to replace. Updated so that this works for both input methods. It's also assigned as a constant so it can't just be reassigned or we hit dynamic constant assignment errors.

io.class::HISTORY.clear
previous_history.each { |line| io.class::HISTORY << line }
end
io.class::HISTORY.concat(input.split)
input.split.each { |line| io.class::HISTORY << line }
Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Readline::HISTORY doesn't respond to concat. Updated how this adds the new lines.

io.save_history

io.load_history
Expand Down
Loading