-
Notifications
You must be signed in to change notification settings - Fork 6
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
Better assertion with less wait time #10
Changes from all commits
File filter
Filter by extension
Conversations
Jump to
Diff view
Diff view
There are no files selected for viewing
Original file line number | Diff line number | Diff line change |
---|---|---|
|
@@ -2,9 +2,11 @@ | |
require 'vterm' | ||
require 'pty' | ||
require 'io/console' | ||
require 'io/wait' | ||
|
||
module Yamatanooroti::VTermTestCaseModule | ||
def start_terminal(height, width, command, wait: 0.1, startup_message: nil) | ||
def start_terminal(height, width, command, wait: 0.01, timeout: 2, startup_message: nil) | ||
@timeout = timeout | ||
@wait = wait | ||
@result = nil | ||
|
||
|
@@ -18,14 +20,10 @@ def start_terminal(height, width, command, wait: 0.1, startup_message: nil) | |
|
||
case startup_message | ||
when String | ||
@startup_message = ->(message) { message.start_with?(startup_message) } | ||
wait_startup_message { |message| message.start_with?(startup_message) } | ||
when Regexp | ||
@startup_message = ->(message) { startup_message.match?(message) } | ||
else | ||
@startup_message = nil | ||
wait_startup_message { |message| startup_message.match?(message) } | ||
end | ||
|
||
sync | ||
end | ||
|
||
def write(str) | ||
|
@@ -42,76 +40,126 @@ def write(str) | |
end | ||
end | ||
@pty_input.write(str_to_write) | ||
sync | ||
# Write str (e.g. `exit`) to pty_input might terminate the process. | ||
try_sync | ||
end | ||
|
||
def close | ||
sync | ||
@pty_input.close | ||
sync | ||
Process.kill('KILL', @pid) | ||
Process.waitpid(@pid) | ||
begin | ||
sync | ||
@pty_input.close | ||
sync | ||
rescue IOError, Errno::EIO | ||
end | ||
begin | ||
Process.kill('KILL', @pid) | ||
Process.waitpid(@pid) | ||
rescue Errno::ESRCH | ||
end | ||
end | ||
|
||
private def sync | ||
startup_message = +'' if @startup_message | ||
private def wait_startup_message | ||
wait_until = Time.now + @timeout | ||
chunks = +'' | ||
loop do | ||
sleep @wait | ||
chunk = @pty_output.read_nonblock(1024) | ||
if @startup_message | ||
startup_message << chunk | ||
if @startup_message.(startup_message) | ||
@startup_message = nil | ||
chunk = startup_message | ||
else | ||
redo | ||
end | ||
wait = wait_until - Time.now | ||
if wait.negative? || !@pty_output.wait_readable(wait) | ||
raise "Startup message didn't arrive within timeout: #{chunks.inspect}" | ||
end | ||
@vterm.write(chunk) | ||
chunk = @vterm.read | ||
@pty_input.write(chunk) | ||
rescue Errno::EAGAIN, Errno::EWOULDBLOCK | ||
retry if @startup_message | ||
break | ||
rescue Errno::EIO # EOF | ||
retry if @startup_message | ||
break | ||
rescue IO::EAGAINWaitReadable # emtpy buffer | ||
retry if @startup_message | ||
break | ||
|
||
chunk = @pty_output.read_nonblock(65536) | ||
vterm_write(chunk) | ||
chunks << chunk | ||
break if yield chunks | ||
end | ||
end | ||
|
||
private def vterm_write(chunk) | ||
@vterm.write(chunk) | ||
response = @vterm.read | ||
begin | ||
@pty_input.write(response) | ||
rescue Errno::EIO | ||
# In case process terminates suddenly after writing "\e[6n" | ||
end | ||
@result = nil | ||
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. Is exception handling no longer necessary? There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more.
|
||
end | ||
|
||
private def sync(wait = @wait) | ||
sync_until = Time.now + @timeout | ||
while @pty_output.wait_readable(wait) | ||
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. |
||
vterm_write(@pty_output.read_nonblock(65536)) | ||
break if Time.now > sync_until | ||
end | ||
end | ||
|
||
private def try_sync(wait = @wait) | ||
sync(wait) | ||
true | ||
rescue IOError, Errno::EIO | ||
false | ||
end | ||
|
||
|
||
def result | ||
return @result if @result | ||
@result = [] | ||
try_sync(0) | ||
@result ||= retrieve_screen | ||
end | ||
|
||
private def retrieve_screen | ||
result = [] | ||
rows, cols = @vterm.size | ||
rows.times do |r| | ||
@result << +'' | ||
result << +'' | ||
cols.times do |c| | ||
cell = @screen.cell_at(r, c) | ||
if cell.char # The second cell of fullwidth char will be nil. | ||
if cell.char.empty? | ||
# There will be no char to the left of the rendered area if moves | ||
# the cursor. | ||
@result.last << ' ' | ||
result.last << ' ' | ||
else | ||
@result.last << cell.char | ||
result.last << cell.char | ||
end | ||
end | ||
end | ||
@result.last.gsub!(/ *$/, '') | ||
result.last.gsub!(/ *$/, '') | ||
end | ||
result | ||
end | ||
|
||
private def retryable_screen_assertion_with_proc(check_proc, assert_proc, convert_proc = :itself.to_proc) | ||
retry_until = Time.now + @timeout | ||
while Time.now < retry_until | ||
break unless try_sync | ||
|
||
@result ||= retrieve_screen | ||
break if check_proc.call(convert_proc.call(@result)) | ||
end | ||
@result | ||
@result ||= retrieve_screen | ||
assert_proc.call(convert_proc.call(@result)) | ||
end | ||
|
||
def assert_screen(expected_lines, message = nil) | ||
actual_lines = result | ||
lines_to_string = ->(lines) { lines.join("\n").sub(/\n*\z/, "\n") } | ||
case expected_lines | ||
when Array | ||
assert_equal(expected_lines, actual_lines, message) | ||
retryable_screen_assertion_with_proc( | ||
->(actual) { expected_lines == actual }, | ||
->(actual) { assert_equal(expected_lines, actual, message) } | ||
) | ||
when String | ||
assert_equal(expected_lines, actual_lines.join("\n").sub(/\n*\z/, "\n"), message) | ||
retryable_screen_assertion_with_proc( | ||
->(actual) { expected_lines == actual }, | ||
->(actual) { assert_equal(expected_lines, actual, message) }, | ||
lines_to_string | ||
) | ||
when Regexp | ||
retryable_screen_assertion_with_proc( | ||
->(actual) { expected_lines.match?(actual) }, | ||
->(actual) { assert_match(expected_lines, actual, message) }, | ||
lines_to_string | ||
) | ||
end | ||
end | ||
end | ||
|
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
It seems to
require 'io/wait'
required for RUBY_VERSION <3.2.0There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Thank you 👍 added