From 0bd0d39f41f6b60e117ae98f6e1eee1cb7873762 Mon Sep 17 00:00:00 2001 From: Leroy Hopson Date: Thu, 28 Mar 2024 22:37:42 +1300 Subject: [PATCH] feat(pty): add cols/rows methods and resize tests --- addons/godot_xterm/native/src/pty.cpp | 38 +++++++- addons/godot_xterm/native/src/pty.h | 10 ++- test/test_nix.gd | 120 +++++++++++++++++--------- test/test_pty.gd | 6 +- 4 files changed, 122 insertions(+), 52 deletions(-) diff --git a/addons/godot_xterm/native/src/pty.cpp b/addons/godot_xterm/native/src/pty.cpp index 740526c..4974594 100644 --- a/addons/godot_xterm/native/src/pty.cpp +++ b/addons/godot_xterm/native/src/pty.cpp @@ -47,6 +47,14 @@ void PTY::_bind_methods() { ADD_SIGNAL(MethodInfo("data_received", PropertyInfo(Variant::PACKED_BYTE_ARRAY, "data"))); ADD_SIGNAL(MethodInfo("exited", PropertyInfo(Variant::INT, "exit_code"), PropertyInfo(Variant::INT, "signal_code"))); + ClassDB::bind_method(D_METHOD("set_cols", "num_cols"), &PTY::set_cols); + ClassDB::bind_method(D_METHOD("get_cols"), &PTY::get_cols); + ClassDB::add_property("PTY", PropertyInfo(Variant::INT, "cols"), "set_cols", "get_cols"); + + ClassDB::bind_method(D_METHOD("set_rows", "num_rows"), &PTY::set_rows); + ClassDB::bind_method(D_METHOD("get_rows"), &PTY::get_rows); + ClassDB::add_property("PTY", PropertyInfo(Variant::INT, "rows"), "set_rows", "get_rows"); + ClassDB::bind_method(D_METHOD("get_env"), &PTY::get_env); ClassDB::bind_method(D_METHOD("set_env", "env"), &PTY::set_env); ClassDB::add_property("PTY", PropertyInfo(Variant::DICTIONARY, "env"), "set_env", "get_env"); @@ -90,10 +98,24 @@ PTY::PTY() { #endif } +void PTY::set_cols(const int num_cols) { + if (cols != num_cols) { + cols = num_cols; + resize(cols, rows); + } +} + int PTY::get_cols() const { return cols; } +void PTY::set_rows(const int num_rows) { + if (rows != num_rows) { + rows = num_rows; + resize(cols, rows); + } +} + int PTY::get_rows() const { return rows; } @@ -127,14 +149,14 @@ String PTY::get_pts_name() const { return pts_name; } -Error PTY::fork(const String &file, const PackedStringArray &args, const String &cwd, const int cols, const int rows) { +Error PTY::fork(const String &file, const PackedStringArray &args, const String &cwd, const int p_cols, const int p_rows) { String fork_file = _get_fork_file(file); Dictionary fork_env = _get_fork_env(); Dictionary result; #if defined(__linux__) || defined(__APPLE__) String helper_path = ProjectSettings::get_singleton()->globalize_path("res://addons/godot_xterm/native/bin/spawn-helper"); - result = PTYUnix::fork(fork_file, args, _parse_env(fork_env), cwd, cols, rows, -1, -1, true, helper_path, Callable(this, "_on_exit")); + result = PTYUnix::fork(fork_file, args, _parse_env(fork_env), cwd, p_cols, p_rows, -1, -1, true, helper_path, Callable(this, "_on_exit")); #endif Error err = static_cast((int)result["error"]); @@ -178,14 +200,22 @@ Error PTY::open(const int cols, const int rows) { Error err = static_cast((int)result["error"]); ERR_FAIL_COND_V(err != OK, err); + fd = result["master"]; pts_name = result["pty"]; return OK; } -void PTY::resize(const int cols, const int rows) const { +void PTY::resize(const int p_cols, const int p_rows) { + cols = p_cols; + rows = p_rows; + #if defined(__linux__) || defined(__APPLE__) - PTYUnix::resize(fd, cols, rows); + if (fd > -1) { + PTYUnix::resize(fd, cols, rows); + } else { + ERR_PRINT("fd <= -1"); + } #endif } diff --git a/addons/godot_xterm/native/src/pty.h b/addons/godot_xterm/native/src/pty.h index 4b3e8f9..8802dbf 100644 --- a/addons/godot_xterm/native/src/pty.h +++ b/addons/godot_xterm/native/src/pty.h @@ -43,7 +43,9 @@ namespace godot Status status = STATUS_CLOSED; + void set_cols(const int num_cols); int get_cols() const; + void set_rows(const int num_rows); int get_rows() const; Dictionary get_env() const; @@ -60,8 +62,8 @@ namespace godot Error fork(const String &file = "", const PackedStringArray &args = PackedStringArray(), const String &cwd = ".", const int cols = 80, const int rows = 24); void kill(const int signum = Signal::SIGNAL_SIGHUP); Error open(const int cols = 80, const int rows = 24); - void resize(const int cols, const int rows) const; - void resizev(const Vector2i &size) const { resize(size.x, size.y); }; + void resize(const int cols, const int rows); + void resizev(const Vector2i &size) { resize(size.x, size.y); }; void write(const Variant &data) const; void _notification(int p_what); @@ -73,8 +75,8 @@ namespace godot int pid = -1; int fd = -1; - unsigned int cols = 0; - unsigned int rows = 0; + unsigned int cols = 80; + unsigned int rows = 24; Dictionary env = Dictionary(); bool use_os_env = true; diff --git a/test/test_nix.gd b/test/test_nix.gd index ab285c3..2fee26b 100644 --- a/test/test_nix.gd +++ b/test/test_nix.gd @@ -152,55 +152,95 @@ class Helper: return {rows = int(size.x), cols = int(size.y)} -class XTestPTYSize: - extends NixTest - # Tests to check that psuedoterminal size (as reported by the stty command) - # matches the size of the Terminal node. Uses various scene tree layouts with - # Terminal and PTY nodes in different places. - # See: https://github.com/lihop/godot-xterm/issues/56 +class TestPTYSize: + extends GodotXtermTest - var terminal: Terminal - var scene: Node var regex := RegEx.new() + func get_described_class(): + return PTY + func before_all(): - regex.compile(".*rows (?[0-9]+).*columns (?[0-9]+).*") + # Depending on the implementation, the output of stty -a may vary. + # For example, on linux the format is "rows 24; columns 80;", while on + # macOS it is "rows 24; columns 80;". This regex should match both. + ( + regex + . compile( + ".*rows (?[0-9]+).*columns (?[0-9]+).*|.*; (?[0-9]+) rows; (?[0-9]+) columns.*" + ) + ) + + # Get the size as reported by stty. + func get_stty_size() -> Vector2i: + await wait_frames(1) + subject.call_deferred("write", "stty -a | head -n1\n") + var output := "" + while not "rows" in output or not "columns" in output: + output += (await subject.data_received).get_string_from_utf8() + var regex_match = regex.search(output) + var stty_rows = int(regex_match.get_string("rows")) + var stty_cols = int(regex_match.get_string("columns")) + return Vector2i(stty_cols, stty_rows) func before_each(): - scene = add_child_autofree(preload("res://test/scenes/pty_and_terminal.tscn").instantiate()) + super.before_each() + subject.call_deferred("fork", OS.get_environment("SHELL")) + await wait_for_signal(subject.data_received, 1) - func xtest_correct_stty_reports_correct_size(): - for s in [ - "PTYChild", - "PTYSiblingAbove", - "PTYSiblingBelow", - "PTYCousinAbove", - "PTYCousinBelow", - "PTYCousinAbove2", - "PTYCousinBelow2" - ]: - subject = scene.get_node(s).find_child("PTY") - terminal = scene.get_node(s).find_child("Terminal") + func after_each(): + subject.call_deferred("kill", PTY.SIGNAL_SIGHUP) + await wait_for_signal(subject.exited, 1) - subject.call_deferred("fork", OS.get_environment("SHELL")) - subject.call_deferred("write", "stty -a | head -n1\n") - var output := "" - while not "rows" in output and not "columns" in output: - output = (await subject.data_received).get_string_from_utf8() - var regex_match = regex.search(output) - var stty_rows = int(regex_match.get_string("rows")) - var stty_cols = int(regex_match.get_string("columns")) + func test_pty_default_size(): + var stty_size = await get_stty_size() + assert_eq(stty_size, Vector2i(80, 24)) - assert_eq( - stty_rows, - terminal.get_rows(), - "Expected stty to report correct number of rows for layout '%s'" % s - ) - assert_eq( - stty_cols, - terminal.get_cols(), - "Expected stty to report correct number of columns for layout '%s'" % s - ) + func test_pty_set_cols(): + subject.set_cols(5768) + var stty_size = await get_stty_size() + assert_eq(stty_size, Vector2i(5768, 24)) + + func test_pty_set_rows(): + subject.set_rows(5768) + var stty_size = await get_stty_size() + assert_eq(stty_size, Vector2i(80, 5768)) + + func test_pty_resize(): + subject.resize(2778, 8120) + var stty_size = await get_stty_size() + assert_eq(stty_size, Vector2i(2778, 8120)) + + func test_pty_resizev(): + subject.resizev(Vector2i(2778, 8120)) + var stty_size = await get_stty_size() + assert_eq(stty_size, Vector2i(2778, 8120)) + + func test_pty_min_size(): + subject.resize(0, 0) + var stty_size = await get_stty_size() + assert_eq(stty_size, Vector2i.ZERO) + + func test_pty_max_size(): + subject.resize(65535, 65535) + var stty_size = await get_stty_size() + assert_eq(stty_size, Vector2i(65535, 65535)) + + func test_pty_set_size_on_open(): + subject = described_class.new() + add_child_autofree(subject) + subject.call_deferred("fork", OS.get_environment("SHELL"), [], ".", 2236, 1998) + var stty_size = await get_stty_size() + assert_eq(stty_size, Vector2i(2236, 1998)) + + +# FIXME: Currently tests fail when threads are disabled. +class XTestPTYSizeNoThreads: + extends TestPTYSize + + func before_each(): + super.before_each() + subject.use_threads = false class LinuxHelper: diff --git a/test/test_pty.gd b/test/test_pty.gd index 1fd1b55..f0f3f40 100644 --- a/test/test_pty.gd +++ b/test/test_pty.gd @@ -13,8 +13,7 @@ class TestInterface: # Properties. - # TODO: Implement cols property. - func xtest_has_property_cols() -> void: + func test_has_property_cols() -> void: assert_has_property_with_default_value("cols", 80) func test_has_property_env() -> void: @@ -22,8 +21,7 @@ class TestInterface: "env", {"TERM": "xterm-256color", "COLORTERM": "truecolor"} ) - # TODO: Implement rows property. - func xtest_has_property_rows() -> void: + func test_has_property_rows() -> void: assert_has_property_with_default_value("rows", 24) # TODO: Implement terminal_path property.