From f9b340ab913b2ade8027d149c74182bb4ac2f638 Mon Sep 17 00:00:00 2001 From: Hood Chatham Date: Thu, 2 Apr 2026 16:43:07 -0700 Subject: [PATCH 1/2] Add a test for libtty --- src/lib/libtty.js | 4 +- test/other/tty.c | 208 +++++++++++++++++++++++++++++++++++++++++++++ test/other/tty.out | 60 +++++++++++++ test/test_other.py | 3 + 4 files changed, 273 insertions(+), 2 deletions(-) create mode 100644 test/other/tty.c create mode 100644 test/other/tty.out diff --git a/src/lib/libtty.js b/src/lib/libtty.js index ba392b12c32ff..69addab8fab80 100644 --- a/src/lib/libtty.js +++ b/src/lib/libtty.js @@ -54,10 +54,10 @@ addToLibrary({ }, close(stream) { // flush any pending line data - stream.tty.ops.fsync(stream.tty); + stream.tty.ops.fsync?.(stream.tty); }, fsync(stream) { - stream.tty.ops.fsync(stream.tty); + stream.tty.ops.fsync?.(stream.tty); }, read(stream, buffer, offset, length, pos /* ignored */) { if (!stream.tty || !stream.tty.ops.get_char) { diff --git a/test/other/tty.c b/test/other/tty.c new file mode 100644 index 0000000000000..85f84a972be14 --- /dev/null +++ b/test/other/tty.c @@ -0,0 +1,208 @@ +#include +#include +#include +#include +#include +#include +#include +#include + +EM_JS_DEPS(main, "$TTY"); + +// clang-format off +EM_JS(void, init, (void), { + var major = 100; + var tty_ops = { + get_char: function (tty) { + if (tty.input.length > 0) { + return tty.input.shift(); + } + return undefined; + }, + put_char: function (tty, val) { + if (val !== 0 && val !== 10) { + tty.output.push(val); + } + }, + fsync: function (tty) { + console.log('fsync called'); + tty.output = []; + }, + ioctl_tcgets: function (tty) { + return { + c_iflag: 0, + c_oflag: 0, + c_cflag: 0, + c_lflag: 0, + c_cc: new Array(32).fill(0), + }; + }, + ioctl_tcsets: function (tty, optional_actions, data) { + return 0; + }, + ioctl_tiocgwinsz: function (tty) { + return [25, 80]; + }, + }; + var device = FS.makedev(major, 0); + TTY.register(device, tty_ops); + FS.mkdev('/custom_tty', device); + // Populate the TTY input buffer with test data "ABCD" + TTY.ttys[device].input = [65, 66, 67, 68]; + + // TTY without get_char - should cause ENXIO on read + var tty_no_getchar = {put_char: function (tty, val) {}}; + var device_no_getchar = FS.makedev(major + 1, 0); + TTY.register(device_no_getchar, tty_no_getchar); + FS.mkdev('/tty_no_getchar', device_no_getchar); + + // TTY without put_char - should cause ENXIO on write + var tty_no_putchar = { + get_char: function (tty) { + return 0; + }, + }; + var device_no_putchar = FS.makedev(major + 2, 0); + TTY.register(device_no_putchar, tty_no_putchar); + FS.mkdev('/tty_no_putchar', device_no_putchar); + + // TTY with throwing get_char - should cause EIO on read + var tty_throw_getchar = { + get_char: function (tty) { + throw new Error('get_char error'); + }, + put_char: function (tty, val) {}, + }; + var device_throw_getchar = FS.makedev(major + 3, 0); + TTY.register(device_throw_getchar, tty_throw_getchar); + FS.mkdev('/tty_throw_getchar', device_throw_getchar); + + // TTY with throwing put_char - should cause EIO on write + var tty_throw_putchar = { + get_char: function (tty) { + return 0; + }, + put_char: function (tty, val) { + throw new Error('put_char error'); + }, + }; + var device_throw_putchar = FS.makedev(major + 4, 0); + TTY.register(device_throw_putchar, tty_throw_putchar); + FS.mkdev('/tty_throw_putchar', device_throw_putchar); + + // TTY with empty input (returns undefined immediately) - should cause EAGAIN on + // read + var tty_empty = { + get_char: function (tty) { + return undefined; + }, + put_char: function (tty, val) {}, + }; + var device_empty = FS.makedev(major + 5, 0); + TTY.register(device_empty, tty_empty); + FS.mkdev('/tty_empty', device_empty); +}); +// clang-format on + +int main() { + init(); + char readBuffer[256] = {0}; + char writeBuffer[] = "Test"; + struct winsize ws; + struct termios term; + + printf("\nTest 1: open custom TTY device and check isatty\n"); + int fd = open("/custom_tty", O_RDWR); + printf("open custom_tty: %d\n", fd >= 0 ? 1 : 0); + printf("isatty: %d\n", isatty(fd)); + printf("errno after open: %s\n", strerror(errno)); + errno = 0; + + printf("\nTest 2: read from TTY with data\n"); + ssize_t bytesRead = read(fd, readBuffer, sizeof(readBuffer)); + printf("read bytes: %zd\n", bytesRead); + printf("read data: %s\n", bytesRead > 0 ? readBuffer : "(none)"); + printf("errno after read: %s\n", strerror(errno)); + errno = 0; + + printf("\nTest 3: write to TTY\n"); + ssize_t bytesWritten = write(fd, writeBuffer, strlen(writeBuffer)); + printf("write bytes: %zd\n", bytesWritten); + printf("errno after write: %s\n", strerror(errno)); + errno = 0; + + printf("\nTest 4: ioctl TIOCGWINSZ\n"); + int result = ioctl(fd, TIOCGWINSZ, &ws); + printf("ioctl TIOCGWINSZ: %d\n", result); + printf("ws_row: %d ws_col: %d\n", ws.ws_row, ws.ws_col); + printf("errno after ioctl: %s\n", strerror(errno)); + errno = 0; + + printf("\nTest 5: ioctl TCGETS\n"); + result = ioctl(fd, TCGETS, &term); + printf("ioctl TCGETS: %d\n", result); + printf("errno after TCGETS: %s\n", strerror(errno)); + errno = 0; + + printf("\nTest 6: ioctl TCSETS\n"); + result = ioctl(fd, TCSETS, &term); + printf("ioctl TCSETS: %d\n", result); + printf("errno after TCSETS: %s\n", strerror(errno)); + errno = 0; + + printf("\nTest 7: fsync\n"); + result = fsync(fd); + printf("fsync: %d\n", result); + printf("errno after fsync: %s\n", strerror(errno)); + errno = 0; + + close(fd); + + printf("\nTest 8: no put_char\n"); + fd = open("/tty_no_putchar", O_WRONLY); + printf("open: %d\n", fd >= 0 ? 1 : 0); + bytesWritten = write(fd, writeBuffer, strlen(writeBuffer)); + printf("write: %zd\n", bytesWritten); + printf("errno: %s\n", strerror(errno)); + errno = 0; + close(fd); + + printf("\nTest 9: no get_char\n"); + fd = open("/tty_no_getchar", O_RDONLY); + printf("open: %d\n", fd >= 0 ? 1 : 0); + bytesRead = read(fd, readBuffer, sizeof(readBuffer)); + printf("read: %zd\n", bytesRead); + printf("errno: %s\n", strerror(errno)); + errno = 0; + close(fd); + + printf("\nTest 10: put_char throws\n"); + fd = open("/tty_throw_putchar", O_WRONLY); + printf("open: %d\n", fd >= 0 ? 1 : 0); + bytesWritten = write(fd, writeBuffer, strlen(writeBuffer)); + printf("write: %zd\n", bytesWritten); + printf("errno: %s\n", strerror(errno)); + errno = 0; + close(fd); + + printf("\nTest 11: get_char throws\n"); + fd = open("/tty_throw_getchar", O_RDONLY); + printf("open: %d\n", fd >= 0 ? 1 : 0); + bytesRead = read(fd, readBuffer, sizeof(readBuffer)); + printf("read: %zd\n", bytesRead); + printf("errno: %s\n", strerror(errno)); + errno = 0; + close(fd); + + printf("\nTest 12: get_char returns undefined\n"); + fd = open("/tty_empty", O_RDONLY); + printf("open: %d\n", fd >= 0 ? 1 : 0); + bytesRead = read(fd, readBuffer, sizeof(readBuffer)); + printf("read: %zd\n", bytesRead); + printf("errno: %s\n", strerror(errno)); + errno = 0; + close(fd); + + printf("\ndone\n"); + return 0; +} diff --git a/test/other/tty.out b/test/other/tty.out new file mode 100644 index 0000000000000..63f5a05a4dcf8 --- /dev/null +++ b/test/other/tty.out @@ -0,0 +1,60 @@ + +Test 1: open custom TTY device and check isatty +open custom_tty: 1 +isatty: 1 +errno after open: Success + +Test 2: read from TTY with data +read bytes: 4 +read data: ABCD +errno after read: Success + +Test 3: write to TTY +write bytes: 4 +errno after write: Success + +Test 4: ioctl TIOCGWINSZ +ioctl TIOCGWINSZ: 0 +ws_row: 25 ws_col: 80 +errno after ioctl: Success + +Test 5: ioctl TCGETS +ioctl TCGETS: 0 +errno after TCGETS: Success + +Test 6: ioctl TCSETS +ioctl TCSETS: 0 +errno after TCSETS: Success + +Test 7: fsync +fsync called +fsync: 0 +errno after fsync: Success +fsync called + +Test 8: no put_char +open: 1 +write: -1 +errno: No such device or address + +Test 9: no get_char +open: 1 +read: -1 +errno: No such device or address + +Test 10: put_char throws +open: 1 +write: -1 +errno: I/O error + +Test 11: get_char throws +open: 1 +read: -1 +errno: I/O error + +Test 12: get_char returns undefined +open: 1 +read: -1 +errno: Resource temporarily unavailable + +done diff --git a/test/test_other.py b/test/test_other.py index f5fab92327817..05bbbeacaa17c 100644 --- a/test/test_other.py +++ b/test/test_other.py @@ -13175,6 +13175,9 @@ def test_unistd_isatty(self): self.skipTest('depends on /dev filesystem') self.do_runf('unistd/isatty.c', 'success') + def test_libtty(self): + self.do_other_test('tty.c') + def test_unistd_login(self): self.do_run_in_out_file_test('unistd/login.c') From e9a03c0f8d8502509d3f05dceb946d172db16521 Mon Sep 17 00:00:00 2001 From: Hood Chatham Date: Fri, 3 Apr 2026 08:48:01 -0700 Subject: [PATCH 2/2] Address sbc100 review comments --- test/other/{tty.c => libtty.c} | 13 +++++++------ test/other/{tty.out => libtty.out} | 6 ------ test/test_other.py | 2 +- 3 files changed, 8 insertions(+), 13 deletions(-) rename test/other/{tty.c => libtty.c} (95%) rename test/other/{tty.out => libtty.out} (93%) diff --git a/test/other/tty.c b/test/other/libtty.c similarity index 95% rename from test/other/tty.c rename to test/other/libtty.c index 85f84a972be14..8146dbef65176 100644 --- a/test/other/tty.c +++ b/test/other/libtty.c @@ -1,3 +1,4 @@ +#include #include #include #include @@ -113,7 +114,7 @@ int main() { printf("\nTest 1: open custom TTY device and check isatty\n"); int fd = open("/custom_tty", O_RDWR); - printf("open custom_tty: %d\n", fd >= 0 ? 1 : 0); + assert(fd >= 0); printf("isatty: %d\n", isatty(fd)); printf("errno after open: %s\n", strerror(errno)); errno = 0; @@ -160,7 +161,7 @@ int main() { printf("\nTest 8: no put_char\n"); fd = open("/tty_no_putchar", O_WRONLY); - printf("open: %d\n", fd >= 0 ? 1 : 0); + assert(fd >= 0); bytesWritten = write(fd, writeBuffer, strlen(writeBuffer)); printf("write: %zd\n", bytesWritten); printf("errno: %s\n", strerror(errno)); @@ -169,7 +170,7 @@ int main() { printf("\nTest 9: no get_char\n"); fd = open("/tty_no_getchar", O_RDONLY); - printf("open: %d\n", fd >= 0 ? 1 : 0); + assert(fd >= 0); bytesRead = read(fd, readBuffer, sizeof(readBuffer)); printf("read: %zd\n", bytesRead); printf("errno: %s\n", strerror(errno)); @@ -178,7 +179,7 @@ int main() { printf("\nTest 10: put_char throws\n"); fd = open("/tty_throw_putchar", O_WRONLY); - printf("open: %d\n", fd >= 0 ? 1 : 0); + assert(fd >= 0); bytesWritten = write(fd, writeBuffer, strlen(writeBuffer)); printf("write: %zd\n", bytesWritten); printf("errno: %s\n", strerror(errno)); @@ -187,7 +188,7 @@ int main() { printf("\nTest 11: get_char throws\n"); fd = open("/tty_throw_getchar", O_RDONLY); - printf("open: %d\n", fd >= 0 ? 1 : 0); + assert(fd >= 0); bytesRead = read(fd, readBuffer, sizeof(readBuffer)); printf("read: %zd\n", bytesRead); printf("errno: %s\n", strerror(errno)); @@ -196,7 +197,7 @@ int main() { printf("\nTest 12: get_char returns undefined\n"); fd = open("/tty_empty", O_RDONLY); - printf("open: %d\n", fd >= 0 ? 1 : 0); + assert(fd >= 0); bytesRead = read(fd, readBuffer, sizeof(readBuffer)); printf("read: %zd\n", bytesRead); printf("errno: %s\n", strerror(errno)); diff --git a/test/other/tty.out b/test/other/libtty.out similarity index 93% rename from test/other/tty.out rename to test/other/libtty.out index 63f5a05a4dcf8..0cd5c04e16eb2 100644 --- a/test/other/tty.out +++ b/test/other/libtty.out @@ -1,6 +1,5 @@ Test 1: open custom TTY device and check isatty -open custom_tty: 1 isatty: 1 errno after open: Success @@ -33,27 +32,22 @@ errno after fsync: Success fsync called Test 8: no put_char -open: 1 write: -1 errno: No such device or address Test 9: no get_char -open: 1 read: -1 errno: No such device or address Test 10: put_char throws -open: 1 write: -1 errno: I/O error Test 11: get_char throws -open: 1 read: -1 errno: I/O error Test 12: get_char returns undefined -open: 1 read: -1 errno: Resource temporarily unavailable diff --git a/test/test_other.py b/test/test_other.py index 05bbbeacaa17c..cad477c74a019 100644 --- a/test/test_other.py +++ b/test/test_other.py @@ -13176,7 +13176,7 @@ def test_unistd_isatty(self): self.do_runf('unistd/isatty.c', 'success') def test_libtty(self): - self.do_other_test('tty.c') + self.do_other_test('libtty.c') def test_unistd_login(self): self.do_run_in_out_file_test('unistd/login.c')