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

155 lines
4.7 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"
var terminal: Terminal
func before_each():
terminal = Terminal.new()
2022-11-09 21:57:46 +01:00
terminal.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")
await wait_for_signal(terminal.bell, 5)
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")
await wait_seconds(1)
terminal.write("\a")
await wait_for_signal(terminal.bell, 5)
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():
2022-11-09 21:57:46 +01:00
add_child_autofree(preload("res://test/scenes/write_random.tscn").instantiate())
await wait_frames(5, "Writing random data to terminal")
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
assert_true(true, "Expected no crash when writing random data to terminal.")
class TestTheme:
extends "res://addons/gut/test.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 := [
"ansi_0_color",
"ansi_1_color",
"ansi_2_color",
"ansi_3_color",
"ansi_4_color",
"ansi_5_color",
"ansi_6_color",
"ansi_7_color",
"ansi_8_color",
"ansi_9_color",
"ansi_10_color",
"ansi_11_color",
"ansi_12_color",
"ansi_13_color",
"ansi_14_color",
"ansi_15_color",
"background",
"foreground",
]
var terminal: Terminal
func _get_pixelv(src: Vector2) -> Color:
var screen: Image = get_tree().root.get_texture().get_image()
2023-01-20 23:34:39 +01:00
false # screen.lock() # TODOConverter40, Image no longer requires locking, `false` helps to not break one line if/else, so it can freely be removed
screen.flip_y()
var pixel := screen.get_pixelv(src)
2023-01-20 23:34:39 +01:00
false # screen.unlock() # TODOConverter40, Image no longer requires locking, `false` helps to not break one line if/else, so it can freely be removed
return pixel
func _check_colors(theme: Theme):
var cell_size := Vector2(
2023-01-20 23:34:39 +01:00
int(terminal.size.x / terminal.get_cols()), int(terminal.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():
2022-11-09 21:57:46 +01:00
terminal = autofree(TestScene.instantiate())
await wait_frames(1)
# FIXME: All tests below are broken.
func _test_terminal_display_colors_from_default_theme():
terminal.theme = null
add_child(terminal)
await wait_for_signal(terminal.theme_changed, 5)
_check_colors(default_theme)
func _test_terminal_displays_colors_from_theme():
terminal.theme = alt_theme
add_child(terminal)
await wait_for_signal(terminal.theme_changed, 5)
_check_colors(alt_theme)
func _test_visible_characters_still_displayed_after_resize_with_default_theme():
terminal.theme = null
add_child(terminal)
await wait_frames(1)
DisplayServer.window_set_size(DisplayServer.window_get_size() + Vector2i(1, 0))
await wait_for_signal(terminal.size_changed, 5)
_check_colors(default_theme)
func _test_visible_characters_still_displayed_after_resize_with_custom_theme():
# Issue 57: https://github.com/lihop/godot-xterm/issues/57
terminal.theme = alt_theme
add_child(terminal)
await wait_for_signal(terminal.theme_changed, 5)
DisplayServer.window_set_size(DisplayServer.window_get_size() + Vector2i(1, 0))
await wait_for_signal(terminal.size_changed, 5)
_check_colors(alt_theme)
func _test_updates_colors_after_theme_set():
# Issue 58: https://github.com/lihop/godot-xterm/issues/58
terminal.theme = null
add_child(terminal)
await wait_frames(1)
terminal.call_deferred("set_theme", alt_theme)
await wait_for_signal(terminal.theme_changed, 5)
_check_colors(alt_theme)
func _test_updates_colors_after_theme_unset():
# Issue 58: https://github.com/lihop/godot-xterm/issues/58
terminal.theme = alt_theme
add_child(terminal)
terminal.call_deferred("set_theme", null)
await wait_for_signal(terminal.theme_changed, 5)
_check_colors(default_theme)
func _test_updates_colors_after_theme_changed():
# Issue 58: https://github.com/lihop/godot-xterm/issues/58
terminal.theme = alt_theme
add_child(terminal)
terminal.call_deferred("set_theme", default_theme)
await wait_for_signal(terminal.theme_changed, 5)
_check_colors(default_theme)