godot-xterm/test/integration/terminal.test.gd

176 lines
5 KiB
GDScript
Raw Normal View History

Fix unicode errors Changes `write()` method of native pipe and terminal to accept a PoolByteArray rather than String. This means that `get_string_from_utf8()` is no longer called on data coming from PTY and being sent to Terminal. The terminal state machine already has a UTF8 parser which maintains its state across calls to `write()`. This means that we can write half the bytes of a single unicode character in one call and the remaining half in the next call and the state machine will parse it correctly. On the other hand, the `get_string_from_utf8()` method of Godot's PoolByteArray requires that the array contains completely valid UTF8, otherwise we get errors such as "Unicode error: invalid skip". The data coming from PTY can be arbitrarily split in the middle of a unicode character meaning that we will sometimes get errors when calling `get_string_from_utf8()` on it. This is more likely to occur when there is a large amount of output (i.e. it's more likely to be split). In other cases, the data might intentionally contain invalid unicode such as when printing binary files or random data (e.g. `cat /bin/sh`, `cat /dev/random`). We avoid these errors by passing the PoolByteArray data directly to the terminal state machine. In addition to fixing unicode errors, this commit: - Prevents repeated calls to pipes `_read_cb()` method that would block Godot and result in a crash with the message "ERROR: All memory pool allocations are in use" that resulted from writing data to an ever-increasing number of PoolByteArrays before any of them could be freed. This could be triggered by running the `cat /dev/urandom` command after making the change to `write()` mentioned above. - Prevents memory leaks by freeing libuv buffers after they have been copied to PoolByteArrays. Fixes #55.
2022-08-14 09:45:17 +02:00
extends "res://addons/gut/test.gd"
const Terminal := preload("res://addons/godot_xterm/terminal.gd")
var terminal: Terminal
func before_each():
terminal = Terminal.new()
terminal.rect_size = Vector2(400, 200)
add_child_autofree(terminal)
func test_bell() -> void:
terminal.bell_cooldown = 0
terminal.write(char(7))
terminal.write(char(0x07))
terminal.write("\a")
terminal.write("\u0007")
terminal.write("'Ask not for whom the \a tolls; it tolls for thee' - John Donne")
yield(yield_to(terminal, "bell", 5), YIELD)
assert_signal_emit_count(terminal, "bell", 5)
func test_bell_cooldown() -> void:
watch_signals(terminal)
terminal.bell_cooldown = 0.5
terminal.write("\a")
terminal.write("\a")
yield(yield_for(1), YIELD)
terminal.write("\a")
yield(yield_to(terminal, "bell", 5), YIELD)
assert_signal_emit_count(terminal, "bell", 2)
Fix unicode errors Changes `write()` method of native pipe and terminal to accept a PoolByteArray rather than String. This means that `get_string_from_utf8()` is no longer called on data coming from PTY and being sent to Terminal. The terminal state machine already has a UTF8 parser which maintains its state across calls to `write()`. This means that we can write half the bytes of a single unicode character in one call and the remaining half in the next call and the state machine will parse it correctly. On the other hand, the `get_string_from_utf8()` method of Godot's PoolByteArray requires that the array contains completely valid UTF8, otherwise we get errors such as "Unicode error: invalid skip". The data coming from PTY can be arbitrarily split in the middle of a unicode character meaning that we will sometimes get errors when calling `get_string_from_utf8()` on it. This is more likely to occur when there is a large amount of output (i.e. it's more likely to be split). In other cases, the data might intentionally contain invalid unicode such as when printing binary files or random data (e.g. `cat /bin/sh`, `cat /dev/random`). We avoid these errors by passing the PoolByteArray data directly to the terminal state machine. In addition to fixing unicode errors, this commit: - Prevents repeated calls to pipes `_read_cb()` method that would block Godot and result in a crash with the message "ERROR: All memory pool allocations are in use" that resulted from writing data to an ever-increasing number of PoolByteArrays before any of them could be freed. This could be triggered by running the `cat /dev/urandom` command after making the change to `write()` mentioned above. - Prevents memory leaks by freeing libuv buffers after they have been copied to PoolByteArrays. Fixes #55.
2022-08-14 09:45:17 +02:00
func test_writing_random_data_to_terminal_does_not_crash_application():
add_child_autofree(preload("res://test/scenes/write_random.tscn").instance())
yield(yield_frames(5, "Writing random data to terminal"), YIELD)
assert_true(true, "Expected no crash when writing random data to terminal.")
class TestTheme:
extends "res://addons/gut/test.gd"
const Terminal := preload("res://addons/godot_xterm/terminal.gd")
const TestScene := preload("../scenes/theme.tscn")
const default_theme := preload("res://addons/godot_xterm/themes/default.tres")
const alt_theme := preload("res://addons/godot_xterm/themes/default_light.tres")
const COLORS := [
"Black",
"Red",
"Green",
"Yellow",
"Blue",
"Magenta",
"Cyan",
"Light Grey",
"Dark Grey",
"Light Red",
"Light Green",
"Light Yellow",
"Light Blue",
"Light Magenta",
"Light Cyan",
"White",
]
var terminal: Terminal
# Current themes don't work with Godot 3.5. So skip tests on this version.
static func _version_gt_3_4() -> bool:
var version := Engine.get_version_info()
return version.major > 3 or (version.major == 3 and version.minor > 4)
func _get_pixelv(src: Vector2) -> Color:
var screen := get_tree().root.get_texture().get_data()
screen.lock()
screen.flip_y()
var pixel := screen.get_pixelv(src)
screen.unlock()
return pixel
func _check_colors(theme: Theme):
var cell_size := Vector2(
int(terminal.rect_size.x / terminal.get_cols()),
int(terminal.rect_size.y / terminal.get_rows())
)
var src := cell_size / 2
for i in range(16):
var color_name = COLORS[i]
var expected_color = "#" + theme.get_color(color_name, "Terminal").to_html(false)
var actual_color = "#" + _get_pixelv(src).to_html(false)
assert_eq(
actual_color,
expected_color,
"Expected color '%s' to be displayed for '%s'." % [expected_color, color_name]
)
src += Vector2(cell_size.x, 0)
func before_each():
terminal = autofree(TestScene.instance())
yield(yield_frames(1), YIELD)
func test_terminal_display_colors_from_default_theme():
if _version_gt_3_4():
return
terminal.theme = null
add_child(terminal)
yield(yield_to(terminal, "theme_changed", 5), YIELD)
_check_colors(default_theme)
func test_terminal_displays_colors_from_theme():
if _version_gt_3_4():
return
terminal.theme = alt_theme
add_child(terminal)
yield(yield_to(terminal, "theme_changed", 5), YIELD)
_check_colors(alt_theme)
func test_visible_characters_still_displayed_after_resize_with_default_theme():
if _version_gt_3_4():
return
terminal.theme = null
add_child(terminal)
yield(yield_frames(1), YIELD)
OS.window_size += Vector2(1, 0)
yield(yield_to(terminal, "size_changed", 5), YIELD)
_check_colors(default_theme)
func test_visible_characters_still_displayed_after_resize_with_custom_theme():
if _version_gt_3_4():
return
# Issue 57: https://github.com/lihop/godot-xterm/issues/57
terminal.theme = alt_theme
add_child(terminal)
yield(yield_to(terminal, "theme_changed", 5), YIELD)
OS.window_size += Vector2(1, 0)
yield(yield_to(terminal, "size_changed", 5), YIELD)
_check_colors(alt_theme)
func test_updates_colors_after_theme_set():
if _version_gt_3_4():
return
# Issue 58: https://github.com/lihop/godot-xterm/issues/58
terminal.theme = null
add_child(terminal)
yield(yield_frames(1), YIELD)
terminal.theme = alt_theme
yield(yield_to(terminal, "theme_changed", 50), YIELD)
_check_colors(alt_theme)
func test_updates_colors_after_theme_unset():
if _version_gt_3_4():
return
# Issue 58: https://github.com/lihop/godot-xterm/issues/58
terminal.theme = alt_theme
add_child(terminal)
yield(yield_to(terminal, "theme_changed", 5), YIELD)
terminal.theme = null
yield(yield_to(terminal, "theme_changed", 5), YIELD)
_check_colors(default_theme)
func test_updates_colors_after_theme_changed():
if _version_gt_3_4():
return
# Issue 58: https://github.com/lihop/godot-xterm/issues/58
terminal.theme = alt_theme
add_child(terminal)
yield(yield_to(terminal, "theme_changed", 5), YIELD)
terminal.theme = default_theme
yield(yield_to(terminal, "theme_changed", 5), YIELD)
_check_colors(default_theme)