* [RFC PATCH 0/5] tty: Add KUnit test framework for TTY drivers
@ 2025-08-26 22:51 Abhinav Saxena
2025-08-26 22:51 ` [RFC PATCH 1/5] tty: Add KUnit test infrastructure configuration Abhinav Saxena
` (5 more replies)
0 siblings, 6 replies; 8+ messages in thread
From: Abhinav Saxena @ 2025-08-26 22:51 UTC (permalink / raw)
To: Greg Kroah-Hartman, Jiri Slaby, Nathan Chancellor,
Nick Desaulniers, Bill Wendling, Justin Stitt, Kees Cook
Cc: linux-kernel, linux-serial, llvm, linux-hardening, Abhinav Saxena
This patch series introduces a KUnit testing framework for the TTY
subsystem, enabling deterministic, automated testing of TTY drivers and
core functionality without requiring hardware or userspace interaction.
On an x86_64 build with CONFIG_GCOV enabled, these tests increased
TTY subsystem coverage to approximately 10.6% line coverage and
14.7% function coverage [1].
Problem Statement
-----------------
Testing TTY drivers today requires:
- User-space interaction through device nodes
- Complex setup with ptys or real hardware
- Limited ability to test error paths reliably and deterministically
This series solves these issues by providing in-kernel KUnit tests that
exercise real TTY core paths under controlled, deterministic conditions.
What This Series Provides
-------------------------
1. Reusable test helpers (`tty_test_helpers.h`):
- Minimal (~150 LOC) infrastructure that any TTY driver should be
able to use
- Automatic resource management
- Integrated into core files under KUnit guard, with
`EXPORT_SYMBOL_IF_KUNIT()` to keep the production symbol table
clean
2. Mock TTY driver:
- Demonstrates how drivers can leverage the helpers
- Enables deterministic scenarios without hardware
3. Core TTY tests:
- Validate open/close/read/write/termios paths
- Exercise hangup, resize, and error handling
- Ensure real kernel paths are tested, not mocked stubs
4. ttynull driver tests:
- Validate data sink behavior of the null driver
- Provide a minimal driver contract baseline
5. Optional coverage support:
- GCOV integration for test coverage analysis
Future Work
-----------
With this foundation merged, follow-up work can:
- Add more coverage of TTY core functions
- Enable each TTY driver to maintain its own KUnit suite
- Introduce stress tests and race detection
- Extend to include more tests for other tty drivers:
- UART drivers: test interrupt handling without hardware
- USB serial: validate disconnect and reconnect sequences
- PTY drivers: test resize, flow control, and hangups
- Virtual consoles: test Unicode and input handling
Testing
-------
- All patches pass `checkpatch.pl`
- Verified on x86_64 with:
./tools/testing/kunit/kunit.py run \
--kunitconfig=.kunit/ \
--kunitconfig=drivers/tty/tests/.kunitconfig \
--arch=x86_64
- All tests pass (working around tty_read wrapper in progress)
Feedback welcome! :)
References
----------
[1] Coverage reports: ttytests.haunted2bwanted.me (alt: linux-9ik.pages.dev)
[2] kunit.dev/third_party/kernel/docs/usage.html#testing-static-functions
[3] KUnit: docs.kernel.org/dev-tools/kunit/
[4] TTY driver API: https://docs.kernel.org/driver-api/serial/
[5] Big thanks to LDD3! (Ch18 especially!)
Signed-off-by: Abhinav Saxena <xandfury@gmail.com>
---
Abhinav Saxena (5):
tty: Add KUnit test infrastructure configuration
tty: Add KUnit test helper functions
tty: Add mock TTY driver for KUnit testing
tty: Add KUnit tests for core TTY functionality
tty: Add KUnit tests for ttynull driver
drivers/tty/Kconfig | 9 +
drivers/tty/Makefile | 7 +
drivers/tty/tests/.kunitconfig | 44 ++++
drivers/tty/tests/Kconfig | 44 ++++
drivers/tty/tests/Makefile | 2 +
drivers/tty/tests/test_tty_io_core.c | 249 ++++++++++++++++++++++
drivers/tty/tests/test_ttynull.c | 163 +++++++++++++++
drivers/tty/tests/tty_mock.c | 186 +++++++++++++++++
drivers/tty/tests/tty_mock.h | 34 +++
drivers/tty/tests/tty_test_helpers.c | 387 +++++++++++++++++++++++++++++++++++
drivers/tty/tests/tty_test_helpers.h | 239 +++++++++++++++++++++
drivers/tty/tty_io.c | 4 +
drivers/tty/ttynull.c | 5 +
13 files changed, 1373 insertions(+)
---
base-commit: 8d245acc1e884e89f0808f64d6af3fc91d4903a0
change-id: 20250824-tty-tests-7fcd8b2b093e
Best regards,
--
Abhinav Saxena <xandfury@gmail.com>
^ permalink raw reply [flat|nested] 8+ messages in thread
* [RFC PATCH 1/5] tty: Add KUnit test infrastructure configuration
2025-08-26 22:51 [RFC PATCH 0/5] tty: Add KUnit test framework for TTY drivers Abhinav Saxena
@ 2025-08-26 22:51 ` Abhinav Saxena
2025-08-26 22:51 ` [RFC PATCH 2/5] tty: Add KUnit test helper functions Abhinav Saxena
` (4 subsequent siblings)
5 siblings, 0 replies; 8+ messages in thread
From: Abhinav Saxena @ 2025-08-26 22:51 UTC (permalink / raw)
To: Greg Kroah-Hartman, Jiri Slaby, Nathan Chancellor,
Nick Desaulniers, Bill Wendling, Justin Stitt, Kees Cook
Cc: linux-kernel, linux-serial, llvm, linux-hardening, Abhinav Saxena
Add Kconfig and Makefile support for TTY KUnit testing framework.
Introduces CONFIG_TTY_GCOV_PROFILE for targeted coverage analysis
and CONFIG_TTY_KUNIT_TESTS for test infrastructure enabling.
The infrastructure allows selective testing of TTY components without
instrumenting the entire kernel, improving development efficiency for
TTY subsystem changes.
Signed-off-by: Abhinav Saxena <xandfury@gmail.com>
---
drivers/tty/Kconfig | 9 +++++++++
drivers/tty/Makefile | 7 +++++++
drivers/tty/tests/.kunitconfig | 44 ++++++++++++++++++++++++++++++++++++++++++
drivers/tty/tests/Kconfig | 44 ++++++++++++++++++++++++++++++++++++++++++
drivers/tty/tests/Makefile | 2 ++
5 files changed, 106 insertions(+)
diff --git a/drivers/tty/Kconfig b/drivers/tty/Kconfig
index 149f3d53b76086cd4ac5acf116ebe36d816664ac..92d27761e1543ceb138a670194480ed1e711124b 100644
--- a/drivers/tty/Kconfig
+++ b/drivers/tty/Kconfig
@@ -424,6 +424,15 @@ config RPMSG_TTY
To compile this driver as a module, choose M here: the module will be
called rpmsg_tty.
+config TTY_GCOV_PROFILE
+ bool "Enable gcov profiling for TTY subsystem"
+ depends on GCOV_KERNEL && TTY
+ help
+ Instrument drivers/tty/* with gcov when GCOV is enabled.
+ Useful for targeted coverage runs without profiling the whole kernel.
+
+source "drivers/tty/tests/Kconfig"
+
endif # TTY
source "drivers/tty/serdev/Kconfig"
diff --git a/drivers/tty/Makefile b/drivers/tty/Makefile
index 07aca5184a55dd38036587c3485ba9f12d2e7ec7..c0cb47c32e94abe072f4cd7e2023480bbb5da9f2 100644
--- a/drivers/tty/Makefile
+++ b/drivers/tty/Makefile
@@ -29,3 +29,10 @@ obj-$(CONFIG_VCC) += vcc.o
obj-$(CONFIG_RPMSG_TTY) += rpmsg_tty.o
obj-y += ipwireless/
+
+obj-$(CONFIG_TTY_KUNIT_TESTS) += tests/
+
+# tty profiling & coverage
+ifdef CONFIG_TTY_GCOV_PROFILE
+GCOV_PROFILE := y
+endif
diff --git a/drivers/tty/tests/.kunitconfig b/drivers/tty/tests/.kunitconfig
new file mode 100644
index 0000000000000000000000000000000000000000..a29112fa03ae78e096d9e22546b3cfd3007d710c
--- /dev/null
+++ b/drivers/tty/tests/.kunitconfig
@@ -0,0 +1,44 @@
+# TTY KUnit Test Configuration
+# =============================
+#
+# Running test with something like this:
+# -------------------------------------
+# ./tools/testing/kunit/kunit.py run tty_io_core \
+# --kunitconfig=.kunit/ \
+# --kunitconfig=drivers/tty/tests/.kunitconfig \
+# --jobs "$(nproc)" \
+# --arch=x86_64 \
+# --make_options="LLVM=-18"
+
+# Core KUnit Infrastructure
+# -------------------------
+CONFIG_KUNIT=y
+CONFIG_KUNIT_DEBUGFS=y
+CONFIG_KUNIT_TEST=y
+CONFIG_PROC_FS=y
+CONFIG_SYSFS=y
+CONFIG_DEBUG_FS=y
+
+# TTY Subsystem Core
+# ------------------
+CONFIG_TTY=y
+CONFIG_UNIX98_PTYS=y
+CONFIG_SERIAL_CORE=y
+CONFIG_VT=y
+CONFIG_VT_CONSOLE=y
+
+# TTY Drivers for Testing
+# -----------------------
+# Enable ttynull driver (required for ttynull tests)
+CONFIG_NULL_TTY=y
+
+# TTY KUnit Tests
+# ---------------
+CONFIG_TTY_KUNIT_TESTS=y
+CONFIG_TTY_KUNIT_CORE_TESTS=y
+CONFIG_TTY_KUNIT_NULL_TTY_TESTS=y
+
+# Code Coverage
+# -------------------------------------------
+CONFIG_GCOV_KERNEL=y
+CONFIG_TTY_GCOV_PROFILE=y
diff --git a/drivers/tty/tests/Kconfig b/drivers/tty/tests/Kconfig
new file mode 100644
index 0000000000000000000000000000000000000000..6012f4ab7f1bc90d434d69ee82e87e89c1291763
--- /dev/null
+++ b/drivers/tty/tests/Kconfig
@@ -0,0 +1,44 @@
+# SPDX-License-Identifier: GPL-2.0
+menu "TTY KUnit tests"
+ depends on KUNIT && TTY
+
+config TTY_KUNIT_TESTS
+ bool "TTY KUnit test infrastructure"
+ depends on KUNIT && TTY
+ default KUNIT_ALL_TESTS
+ help
+ Enable KUnit test infrastructure for TTY drivers.
+
+ This enables test helper functions that are included directly
+ into tty_io.c to provide access to internal TTY functions for
+ testing. The helpers are only available when KUNIT is enabled.
+
+ This option provides the foundation for TTY testing but does not
+ run any tests by itself. Enable specific test suites below.
+
+config TTY_KUNIT_CORE_TESTS
+ bool "TTY core functionality tests"
+ depends on TTY_KUNIT_TESTS
+ default KUNIT_ALL_TESTS
+ help
+ Enable KUnit tests for TTY core functionality.
+
+ Tests cover basic TTY operations including open/close lifecycle,
+ write operations, buffer management, line discipline integration,
+ and error handling through real kernel code paths.
+
+ If unsure, say N.
+
+config TTY_KUNIT_NULL_TTY_TESTS
+ tristate "TTY null driver tests" if !KUNIT_ALL_TESTS
+ depends on TTY_KUNIT_TESTS && NULL_TTY
+ default KUNIT_ALL_TESTS
+ help
+ Enable KUnit tests for the TTY null driver.
+
+ Tests validate ttynull behavior as a data sink, including write
+ operations, data discarding, and error handling. The ttynull
+ driver discards all written data while providing minimal overhead.
+
+ If unsure, say N.
+endmenu
diff --git a/drivers/tty/tests/Makefile b/drivers/tty/tests/Makefile
new file mode 100644
index 0000000000000000000000000000000000000000..854911a0f87d6a6fdc826cc40f99afb1202afd46
--- /dev/null
+++ b/drivers/tty/tests/Makefile
@@ -0,0 +1,2 @@
+# SPDX-License-Identifier: GPL-2.0-only
+obj-$(CONFIG_TTY_KUNIT_CORE_TESTS) += tty_mock.o test_tty_io_core.o
--
2.43.0
^ permalink raw reply related [flat|nested] 8+ messages in thread
* [RFC PATCH 2/5] tty: Add KUnit test helper functions
2025-08-26 22:51 [RFC PATCH 0/5] tty: Add KUnit test framework for TTY drivers Abhinav Saxena
2025-08-26 22:51 ` [RFC PATCH 1/5] tty: Add KUnit test infrastructure configuration Abhinav Saxena
@ 2025-08-26 22:51 ` Abhinav Saxena
2025-08-26 22:51 ` [RFC PATCH 3/5] tty: Add mock TTY driver for KUnit testing Abhinav Saxena
` (3 subsequent siblings)
5 siblings, 0 replies; 8+ messages in thread
From: Abhinav Saxena @ 2025-08-26 22:51 UTC (permalink / raw)
To: Greg Kroah-Hartman, Jiri Slaby, Nathan Chancellor,
Nick Desaulniers, Bill Wendling, Justin Stitt, Kees Cook
Cc: linux-kernel, linux-serial, llvm, linux-hardening, Abhinav Saxena
Introduce comprehensive test helper infrastructure for TTY driver
testing through real kernel entry points. The helpers are included
directly into tty_io.c when CONFIG_TTY_KUNIT_TESTS=y to access
internal functions while maintaining clean symbol export boundaries.
Key features:
- Complete TTY lifecycle testing (open/close/read/write)
- RX data injection via flip buffers
- Termios manipulation for configuration testing
- Automatic resource cleanup via KUnit actions
All functions use EXPORT_SYMBOL_IF_KUNIT() to prevent production
symbol table pollution while enabling comprehensive testing.
Signed-off-by: Abhinav Saxena <xandfury@gmail.com>
---
drivers/tty/tests/tty_test_helpers.c | 387 +++++++++++++++++++++++++++++++++++
drivers/tty/tests/tty_test_helpers.h | 239 +++++++++++++++++++++
drivers/tty/tty_io.c | 4 +
3 files changed, 630 insertions(+)
diff --git a/drivers/tty/tests/tty_test_helpers.c b/drivers/tty/tests/tty_test_helpers.c
new file mode 100644
index 0000000000000000000000000000000000000000..0de61626ccef1d4e4246f43347401ff46ac432ec
--- /dev/null
+++ b/drivers/tty/tests/tty_test_helpers.c
@@ -0,0 +1,387 @@
+// SPDX-License-Identifier: GPL-2.0
+/*
+ * KUnit test helpers for TTY drivers
+ *
+ * This file is included directly into tty_io.c when CONFIG_TTY_KUNIT_TESTS=y.
+ * This allows the helper functions to access internal TTY functions like
+ * tty_open() and tty_release() while providing exported symbols for use
+ * by test modules.
+ *
+ * All functions are exported via EXPORT_SYMBOL_IF_KUNIT() so they are
+ * only available when KUNIT is enabled, preventing pollution of the
+ * production symbol table.
+ *
+ * Copyright (c) 2025 Abhinav Saxena <xandfury@gmail.com>
+ */
+
+#include <kunit/test.h>
+#include <kunit/visibility.h>
+#include <linux/fs.h>
+#include <linux/kdev_t.h>
+#include <linux/uio.h>
+#include <linux/tty.h>
+#include <linux/tty_driver.h>
+#include <linux/tty_flip.h>
+#include <linux/tty_ldisc.h>
+#include <linux/termios.h>
+
+#include "tests/tty_test_helpers.h"
+
+
+static struct cdev tty_cdev;
+
+/**
+ * _tty_test_cleanup_release - KUnit cleanup action for TTY release
+ * @data: Pointer to tty_test_fixture
+ *
+ * Internal cleanup function registered with kunit_add_action() to ensure
+ * TTY is properly released even if test fails or exits early.
+ * This prevents resource leaks and system instability.
+ */
+static void _tty_test_cleanup_release(void *data)
+{
+ struct tty_test_fixture *fx = data;
+ int ret;
+
+ if (!fx || !fx->opened || !fx->file || !fx->inode)
+ return;
+
+ ret = tty_release(fx->inode, fx->file);
+ if (ret)
+ pr_warn("TTY test cleanup failed: %d\n", ret);
+ fx->opened = false;
+}
+
+/**
+ * tty_test_create_fixture - Create a test fixture for TTY driver testing
+ */
+struct tty_test_fixture *tty_test_create_fixture(struct kunit *test,
+ struct tty_driver *driver,
+ unsigned int index)
+{
+ struct tty_test_fixture *fx;
+
+ KUNIT_ASSERT_NOT_NULL(test, driver);
+
+ fx = kunit_kzalloc(test, sizeof(*fx), GFP_KERNEL);
+ KUNIT_ASSERT_NOT_NULL(test, fx);
+
+ fx->test = test;
+ fx->driver = driver;
+ fx->dev = MKDEV(driver->major, driver->minor_start + index);
+
+ /* Create synthetic VFS structures for real TTY operations */
+ fx->file = kunit_kzalloc(test, sizeof(*fx->file), GFP_KERNEL);
+ fx->inode = kunit_kzalloc(test, sizeof(*fx->inode), GFP_KERNEL);
+ KUNIT_ASSERT_NOT_NULL(test, fx->file);
+ KUNIT_ASSERT_NOT_NULL(test, fx->inode);
+
+ /* Initialize as character device with appropriate permissions */
+ init_special_inode(fx->inode, S_IFCHR | 0600, fx->dev);
+ fx->inode->i_rdev = fx->dev;
+ fx->inode->i_cdev = &tty_cdev;
+ KUNIT_ASSERT_NOT_NULL(test, fx->inode->i_cdev);
+
+ fx->file->f_flags = O_RDWR;
+ fx->file->f_mode = FMODE_READ | FMODE_WRITE;
+ fx->file->f_inode = fx->inode;
+
+ /* Register cleanup before any operations that might fail */
+ kunit_add_action(test, _tty_test_cleanup_release, fx);
+
+ fx->opened = false;
+ return fx;
+}
+EXPORT_SYMBOL_IF_KUNIT(tty_test_create_fixture);
+
+/**
+ * tty_test_open - Open TTY through standard kernel path
+ */
+int tty_test_open(struct tty_test_fixture *fx)
+{
+ int ret;
+
+ KUNIT_ASSERT_NOT_NULL(fx->test, fx);
+ KUNIT_ASSERT_NOT_NULL(fx->test, fx->file);
+ KUNIT_ASSERT_NOT_NULL(fx->test, fx->inode);
+
+ ret = tty_open(fx->inode, fx->file);
+ if (ret)
+ return ret;
+
+ fx->tty = file_tty(fx->file);
+ KUNIT_ASSERT_NOT_NULL(fx->test, fx->tty);
+
+ /* Verify the TTY is properly set up */
+ KUNIT_EXPECT_TRUE(fx->test, !list_empty(&fx->tty->tty_files));
+ /* Ldisc must now be fully installed */
+ KUNIT_ASSERT_NOT_NULL(fx->test, fx->tty->ldisc);
+ KUNIT_EXPECT_TRUE(fx->test, fx->tty->ldisc->ops);
+ KUNIT_ASSERT_NOT_NULL(fx->test, fx->tty->disc_data);
+ KUNIT_EXPECT_NOT_NULL(fx->test, fx->tty->port);
+
+ fx->port = fx->tty->port;
+ ret = fx->tty->ldisc->ops->open(fx->tty);
+ if (ret) {
+ tty_release(fx->inode, fx->file);
+ return ret;
+ }
+
+ /* Enable non-blocking mode for predictable test behavior */
+ fx->file->f_flags |= O_NONBLOCK;
+ fx->opened = true;
+ return 0;
+}
+EXPORT_SYMBOL_IF_KUNIT(tty_test_open);
+
+/**
+ * tty_test_release - Close TTY through standard kernel path
+ */
+int tty_test_release(struct tty_test_fixture *fx)
+{
+ int ret;
+
+ if (!fx || !fx->opened)
+ return 0;
+
+ /*
+ * This calls the internal tty_release() function directly.
+ * This works because this code is compiled as part of tty_io.c.
+ */
+ ret = tty_release(fx->inode, fx->file);
+ if (!ret) {
+ fx->opened = false;
+ fx->tty = NULL;
+ fx->port = NULL;
+ }
+ return ret;
+}
+EXPORT_SYMBOL_IF_KUNIT(tty_test_release);
+
+/**
+ * tty_test_write - Write data to TTY
+ */
+ssize_t tty_test_write(struct tty_test_fixture *fx, const void *buf,
+ size_t count)
+{
+ struct kiocb iocb;
+ struct iov_iter from;
+ struct kvec kvec = { .iov_base = (void *)buf, .iov_len = count };
+
+ KUNIT_ASSERT_NOT_NULL(fx->test, fx);
+ KUNIT_ASSERT_NOT_NULL(fx->test, fx->file);
+ KUNIT_ASSERT_TRUE(fx->test, fx->opened);
+
+ init_sync_kiocb(&iocb, fx->file);
+ iov_iter_kvec(&from, WRITE, &kvec, 1, count);
+
+ /* tty_write() is exported, so this works */
+ return tty_write(&iocb, &from);
+}
+EXPORT_SYMBOL_IF_KUNIT(tty_test_write);
+
+/**
+ * tty_test_write_all - Write all data or fail
+ */
+int tty_test_write_all(struct tty_test_fixture *fx, const void *buf, size_t len)
+{
+ size_t off = 0;
+ int retries = 10;
+
+ KUNIT_ASSERT_NOT_NULL(fx->test, fx);
+ KUNIT_ASSERT_TRUE(fx->test, fx->opened);
+
+ while (off < len && retries--) {
+ ssize_t n =
+ tty_test_write(fx, (const char *)buf + off, len - off);
+ if (n < 0)
+ return n;
+ if (n == 0) {
+ /* No progress - prevent infinite loop */
+ if (--retries <= 0) {
+ KUNIT_FAIL(fx->test,
+ "Write stalled after %zu bytes",
+ off);
+ return -EIO;
+ }
+ continue;
+ }
+ off += n;
+ }
+
+ if (off < len) {
+ KUNIT_FAIL(fx->test, "Incomplete write: %zu/%zu bytes", off,
+ len);
+ return -EIO;
+ }
+
+ return 0;
+}
+EXPORT_SYMBOL_IF_KUNIT(tty_test_write_all);
+
+/**
+ * tty_test_read - Read data from TTY (non-blocking)
+ */
+ssize_t tty_test_read(struct tty_test_fixture *fx, void *buf, size_t count)
+{
+ struct kiocb iocb;
+ struct iov_iter to;
+ struct kvec kvec = { .iov_base = buf, .iov_len = count };
+
+ KUNIT_ASSERT_NOT_NULL(fx->test, fx);
+ KUNIT_ASSERT_NOT_NULL(fx->test, fx->file);
+ KUNIT_ASSERT_TRUE(fx->test, fx->opened);
+
+ init_sync_kiocb(&iocb, fx->file);
+ iov_iter_kvec(&to, READ, &kvec, 1, count);
+
+ return tty_read(&iocb, &to);
+}
+EXPORT_SYMBOL_IF_KUNIT(tty_test_read);
+
+/**
+ * tty_test_read_all - Attempt to read all requested data
+ */
+ssize_t tty_test_read_all(struct tty_test_fixture *fx, void *buf, size_t want)
+{
+ size_t off = 0;
+ int tries = 8;
+
+ KUNIT_ASSERT_NOT_NULL(fx->test, fx);
+ KUNIT_ASSERT_TRUE(fx->test, fx->opened);
+
+ while (off < want && tries--) {
+ ssize_t n = tty_test_read(fx, (char *)buf + off, want - off);
+
+ if (n == -EAGAIN)
+ continue;
+ if (n < 0)
+ return n;
+ if (n == 0)
+ continue;
+ off += n;
+ }
+ return off;
+}
+EXPORT_SYMBOL_IF_KUNIT(tty_test_read_all);
+
+/**
+ * tty_test_simulate_rx - Inject received data for testing
+ */
+int tty_test_simulate_rx(struct tty_test_fixture *fx, const unsigned char *data,
+ size_t len)
+{
+ int ret;
+
+ KUNIT_ASSERT_NOT_NULL(fx->test, fx);
+ KUNIT_ASSERT_NOT_NULL(fx->test, fx->port);
+ KUNIT_ASSERT_TRUE(fx->test, fx->opened);
+
+ ret = tty_insert_flip_string(fx->port, data, len);
+ if (ret > 0)
+ tty_flip_buffer_push(fx->port);
+
+ return ret;
+}
+EXPORT_SYMBOL_IF_KUNIT(tty_test_simulate_rx);
+
+/**
+ * tty_fx_supports_rx - Check if fixture supports RX testing
+ */
+bool tty_fx_supports_rx(const struct tty_test_fixture *fx)
+{
+ struct tty_ldisc *ld;
+ const struct tty_ldisc_ops *ops;
+
+ if (!fx || !fx->tty || !fx->opened)
+ return false;
+
+ ld = tty_ldisc_ref(fx->tty);
+ if (!ld)
+ return false;
+
+ ops = READ_ONCE(ld->ops);
+ if (ops && (ops->receive_buf || ops->receive_buf2)) {
+ tty_ldisc_deref(ld);
+ return true;
+ }
+
+ tty_ldisc_deref(ld);
+ return false;
+}
+EXPORT_SYMBOL_IF_KUNIT(tty_fx_supports_rx);
+
+/**
+ * tty_test_assert_valid_ops - Validate driver has required operations
+ */
+void tty_test_assert_valid_ops(struct kunit *test,
+ const struct tty_driver *driver)
+{
+ KUNIT_ASSERT_NOT_NULL(test, driver);
+ KUNIT_ASSERT_NOT_NULL(test, driver->ops);
+ KUNIT_ASSERT_NOT_NULL(test, driver->ops->open);
+ KUNIT_ASSERT_NOT_NULL(test, driver->ops->close);
+ KUNIT_ASSERT_NOT_NULL(test, driver->ops->write);
+ KUNIT_ASSERT_NOT_NULL(test, driver->ops->write_room);
+ KUNIT_EXPECT_TRUE(test, driver->flags & TTY_DRIVER_INSTALLED);
+}
+EXPORT_SYMBOL_IF_KUNIT(tty_test_assert_valid_ops);
+
+/**
+ * tty_test_get_chars_in_buffer - Get number of chars in output buffer
+ */
+unsigned int tty_test_get_chars_in_buffer(struct tty_test_fixture *fx)
+{
+ KUNIT_ASSERT_NOT_NULL(fx->test, fx);
+ KUNIT_ASSERT_TRUE(fx->test, fx->opened);
+ KUNIT_ASSERT_NOT_NULL(fx->test, fx->tty);
+
+ if (fx->tty->ops->chars_in_buffer)
+ return fx->tty->ops->chars_in_buffer(fx->tty);
+
+ return 0;
+}
+EXPORT_SYMBOL_IF_KUNIT(tty_test_get_chars_in_buffer);
+
+/**
+ * tty_test_get_write_room - Get available write room
+ */
+unsigned int tty_test_get_write_room(struct tty_test_fixture *fx)
+{
+ KUNIT_ASSERT_NOT_NULL(fx->test, fx);
+ KUNIT_ASSERT_TRUE(fx->test, fx->opened);
+ KUNIT_ASSERT_NOT_NULL(fx->test, fx->tty);
+
+ if (fx->tty->ops->write_room)
+ return fx->tty->ops->write_room(fx->tty);
+
+ return 0;
+}
+EXPORT_SYMBOL_IF_KUNIT(tty_test_get_write_room);
+
+/**
+ * tty_test_set_termios - Set terminal attributes for testing
+ */
+int tty_test_set_termios(struct tty_test_fixture *fx,
+ const struct ktermios *termios)
+{
+ struct ktermios old_termios;
+
+ KUNIT_ASSERT_NOT_NULL(fx->test, fx);
+ KUNIT_ASSERT_TRUE(fx->test, fx->opened);
+ KUNIT_ASSERT_NOT_NULL(fx->test, fx->tty);
+ KUNIT_ASSERT_NOT_NULL(fx->test, termios);
+
+ /* Save old termios for potential restoration */
+ old_termios = fx->tty->termios;
+
+ /* Update termios */
+ fx->tty->termios = *termios;
+
+ /* Call driver's set_termios if it exists */
+ if (fx->tty->ops->set_termios)
+ fx->tty->ops->set_termios(fx->tty, &old_termios);
+
+ return 0;
+}
+EXPORT_SYMBOL_IF_KUNIT(tty_test_set_termios);
diff --git a/drivers/tty/tests/tty_test_helpers.h b/drivers/tty/tests/tty_test_helpers.h
new file mode 100644
index 0000000000000000000000000000000000000000..10da4189e35880399e2c857f599ea7f4107f9e90
--- /dev/null
+++ b/drivers/tty/tests/tty_test_helpers.h
@@ -0,0 +1,239 @@
+/* SPDX-License-Identifier: GPL-2.0-only */
+/*
+ * KUnit test helpers for TTY drivers - Header declarations
+ *
+ * Provides reusable infrastructure for testing TTY drivers through
+ * real kernel entry points without requiring userspace interaction
+ * or hardware dependencies.
+ *
+ * The implementation (tty_test_helpers.c) is included directly into
+ * tty_io.c to allow access to internal TTY functions while providing
+ * exported symbols for test modules.
+ *
+ * Copyright (c) 2025 Abhinav Saxena <xandfury@gmail.com>
+ *
+ */
+
+#ifndef _TTY_TEST_HELPERS_H
+#define _TTY_TEST_HELPERS_H
+
+#include <kunit/test.h>
+#include <linux/tty.h>
+#include <linux/tty_driver.h>
+#include <linux/fs.h>
+
+/**
+ * struct tty_test_fixture - Test fixture for TTY driver testing
+ * @test: KUnit test context for assertions and memory management
+ * @driver: TTY driver being tested
+ * @tty: TTY structure (valid after successful open)
+ * @port: TTY port structure (valid after successful open)
+ * @file: Synthetic file structure for VFS operations
+ * @inode: Synthetic inode structure for device operations
+ * @dev: Device number (major:minor) for this TTY
+ * @opened: True if TTY has been opened successfully
+ *
+ * This fixture provides all necessary structures for testing TTY drivers
+ * through the standard kernel interfaces. Memory is managed by KUnit and
+ * automatic cleanup ensures proper resource release.
+ */
+struct tty_test_fixture {
+ struct kunit *test;
+ struct tty_driver *driver;
+ struct tty_struct *tty;
+ struct tty_port *port;
+ struct file *file;
+ struct inode *inode;
+ dev_t dev;
+ bool opened;
+};
+
+/* Core fixture management */
+
+/**
+ * tty_test_create_fixture - Create a test fixture for TTY driver testing
+ * @test: KUnit test context
+ * @driver: TTY driver to test (must be registered)
+ * @index: Minor number index for this TTY instance
+ *
+ * Creates a complete test fixture with synthetic VFS structures that
+ * enable testing through real tty_open()/tty_release() paths.
+ * All memory is managed by KUnit with automatic cleanup.
+ *
+ * Return: Allocated fixture or NULL on failure (test will abort)
+ */
+struct tty_test_fixture *tty_test_create_fixture(struct kunit *test,
+ struct tty_driver *driver,
+ unsigned int index);
+
+/* TTY lifecycle operations */
+
+/**
+ * tty_test_open - Open TTY through standard kernel path
+ * @fx: Test fixture created with tty_test_create_fixture()
+ *
+ * Opens the TTY using tty_open(), the same entry point used by userspace.
+ * This exercises the complete open sequence including driver install,
+ * line discipline attachment, and port initialization.
+ *
+ * After successful open:
+ * - fx->tty points to the allocated TTY structure
+ * - fx->port points to the associated TTY port
+ * - File is set to non-blocking mode for test convenience
+ *
+ * Return: 0 on success, negative error code on failure
+ */
+int tty_test_open(struct tty_test_fixture *fx);
+
+/**
+ * tty_test_release - Close TTY through standard kernel path
+ * @fx: Test fixture with opened TTY
+ *
+ * Closes the TTY using tty_release(), exercising the complete close
+ * Safe to call multiple times or on unopened fixtures.
+ *
+ * Return: 0 on success, negative error code on failure
+ */
+int tty_test_release(struct tty_test_fixture *fx);
+
+/* Data transfer operations */
+
+/**
+ * tty_test_write - Write data to TTY
+ * @fx: Test fixture with opened TTY
+ * @buf: Data buffer to write
+ * @count: Number of bytes to write
+ *
+ * Writes data using tty_write(), the same path used by userspace write().
+ * This exercises line discipline processing, flow control, and driver
+ * write operations. May return partial writes based on buffer availability.
+ *
+ * Return: Number of bytes written, or negative error code
+ */
+ssize_t tty_test_write(struct tty_test_fixture *fx, const void *buf,
+ size_t count);
+
+/**
+ * tty_test_write_all - Write all data or fail
+ * @fx: Test fixture with opened TTY
+ * @buf: Data buffer to write completely
+ * @len: Number of bytes that must be written
+ *
+ * Ensures all data is written by retrying partial writes.
+ * Useful for testing scenarios where complete data delivery is required.
+ * Will assert-fail the test if any individual write returns 0 bytes.
+ *
+ * Return: 0 on complete success, negative error code on failure
+ */
+int tty_test_write_all(struct tty_test_fixture *fx, const void *buf,
+ size_t len);
+
+/**
+ * tty_test_read - Read data from TTY (non-blocking)
+ * @fx: Test fixture with opened TTY
+ * @buf: Buffer to receive data
+ * @count: Maximum bytes to read
+ *
+ * Reads data using tty_read() in non-blocking mode. This is useful for
+ * verifying that injected RX data is properly delivered through the
+ * line discipline to userspace. Returns immediately with -EAGAIN if
+ * no data is available.
+ *
+ * Return: Number of bytes read, -EAGAIN if no data, or other negative error
+ */
+ssize_t tty_test_read(struct tty_test_fixture *fx, void *buf, size_t count);
+
+/**
+ * tty_test_read_all - Attempt to read all requested data
+ * @fx: Test fixture with opened TTY
+ * @buf: Buffer to receive data
+ * @want: Number of bytes desired
+ *
+ * Makes a bounded number of read attempts to collect the requested amount
+ * of data. Useful for reading back data that was injected via flip buffers,
+ * accounting for potential delays in line discipline processing.
+ *
+ * Return: Number of bytes actually read (may be less than requested)
+ */
+ssize_t tty_test_read_all(struct tty_test_fixture *fx, void *buf, size_t want);
+
+/* RX simulation and testing */
+
+/**
+ * tty_test_simulate_rx - Inject received data for testing
+ * @fx: Test fixture with opened TTY
+ * @data: Data bytes to inject
+ * @len: Number of bytes to inject
+ *
+ * Simulates data reception by injecting bytes through the flip buffer
+ * interface and pushing them to the line discipline. This allows testing
+ * of RX data paths, flow control, and line discipline processing without
+ * requiring actual hardware or external data sources.
+ *
+ * Return: Number of bytes successfully queued, or negative error code
+ */
+int tty_test_simulate_rx(struct tty_test_fixture *fx, const unsigned char *data,
+ size_t len);
+
+/**
+ * tty_fx_supports_rx - Check if fixture supports RX testing
+ * @fx: Test fixture to check
+ *
+ * Determines if the TTY has a line discipline attached that can receive
+ * data. This is used to conditionally run RX-related tests since not all
+ * TTY configurations support data reception (e.g., write-only devices).
+ *
+ * Return: true if RX testing is supported, false otherwise
+ */
+bool tty_fx_supports_rx(const struct tty_test_fixture *fx);
+
+/* Driver validation and utility functions */
+
+/**
+ * tty_test_assert_valid_ops - Validate driver has required operations
+ * @test: KUnit test context
+ * @driver: TTY driver to validate
+ *
+ * Performs basic sanity checks on TTY driver structure to ensure it has
+ * the minimum required operations. This catches configuration errors that
+ * would cause NULL pointer dereferences during testing.
+ */
+void tty_test_assert_valid_ops(struct kunit *test,
+ const struct tty_driver *driver);
+
+/**
+ * tty_test_get_chars_in_buffer - Get number of chars in output buffer
+ * @fx: Test fixture with opened TTY
+ *
+ * Returns the number of characters currently in the driver's output buffer.
+ * Useful for testing flow control and buffer management.
+ *
+ * Return: Number of characters in buffer, or 0 if not supported
+ */
+unsigned int tty_test_get_chars_in_buffer(struct tty_test_fixture *fx);
+
+/**
+ * tty_test_get_write_room - Get available write room
+ * @fx: Test fixture with opened TTY
+ *
+ * Returns the number of bytes that can be written without blocking.
+ * Useful for testing buffer management and flow control.
+ *
+ * Return: Number of bytes available for writing
+ */
+unsigned int tty_test_get_write_room(struct tty_test_fixture *fx);
+
+/**
+ * tty_test_set_termios - Set terminal attributes for testing
+ * @fx: Test fixture with opened TTY
+ * @termios: Terminal attributes to set
+ *
+ * Sets terminal attributes through the standard termios interface.
+ * Useful for testing different terminal configurations.
+ *
+ * Return: 0 on success, negative error code on failure
+ */
+int tty_test_set_termios(struct tty_test_fixture *fx,
+ const struct ktermios *termios);
+
+#endif /* _TTY_TEST_HELPERS_H */
diff --git a/drivers/tty/tty_io.c b/drivers/tty/tty_io.c
index e2d92cf70eb78ec6b2b93b55192e46781160c9dc..ac94a037358c9df0ba4013152878ca83a2e001c5 100644
--- a/drivers/tty/tty_io.c
+++ b/drivers/tty/tty_io.c
@@ -3650,3 +3650,7 @@ int __init tty_init(void)
#endif
return 0;
}
+
+#ifdef CONFIG_TTY_KUNIT_TESTS
+#include "tests/tty_test_helpers.c"
+#endif
--
2.43.0
^ permalink raw reply related [flat|nested] 8+ messages in thread
* [RFC PATCH 3/5] tty: Add mock TTY driver for KUnit testing
2025-08-26 22:51 [RFC PATCH 0/5] tty: Add KUnit test framework for TTY drivers Abhinav Saxena
2025-08-26 22:51 ` [RFC PATCH 1/5] tty: Add KUnit test infrastructure configuration Abhinav Saxena
2025-08-26 22:51 ` [RFC PATCH 2/5] tty: Add KUnit test helper functions Abhinav Saxena
@ 2025-08-26 22:51 ` Abhinav Saxena
2025-08-26 22:51 ` [RFC PATCH 4/5] tty: Add KUnit tests for core TTY functionality Abhinav Saxena
` (2 subsequent siblings)
5 siblings, 0 replies; 8+ messages in thread
From: Abhinav Saxena @ 2025-08-26 22:51 UTC (permalink / raw)
To: Greg Kroah-Hartman, Jiri Slaby, Nathan Chancellor,
Nick Desaulniers, Bill Wendling, Justin Stitt, Kees Cook
Cc: linux-kernel, linux-serial, llvm, linux-hardening, Abhinav Saxena
Implement a minimal mock TTY driver that provides deterministic
behavior for testing core TTY functionality. The driver simulates
immediate data transmission with configurable statistics tracking.
The mock driver enables testing of TTY core paths without hardware
dependencies or timing-sensitive behavior, ensuring reproducible
test results across different systems.
Signed-off-by: Abhinav Saxena <xandfury@gmail.com>
---
drivers/tty/tests/tty_mock.c | 186 +++++++++++++++++++++++++++++++++++++++++++
drivers/tty/tests/tty_mock.h | 34 ++++++++
2 files changed, 220 insertions(+)
diff --git a/drivers/tty/tests/tty_mock.c b/drivers/tty/tests/tty_mock.c
new file mode 100644
index 0000000000000000000000000000000000000000..d5488760bb83c2837bb5226e3c33ec370c2c9c07
--- /dev/null
+++ b/drivers/tty/tests/tty_mock.c
@@ -0,0 +1,186 @@
+// SPDX-License-Identifier: GPL-2.0
+/*
+ * Minimal mock TTY driver for KUnit tests. Based on ttynull and ttyprintk
+ *
+ * Behavior:
+ * - write() pretends to transmit all bytes immediately
+ * - write_room() is large
+ * - chars_in_buffer() is 0
+ *
+ * Tracks only: total_writes, total_bytes, last_write_len
+ *
+ * Copyright (c) 2025 Abhinav Saxena <xandury@gmail.com>
+ */
+
+#include <kunit/visibility.h>
+#include <linux/tty.h>
+#include <linux/tty_driver.h>
+#include <linux/tty_flip.h>
+#include <linux/module.h>
+#include <linux/spinlock.h>
+
+#include "tty_mock.h"
+
+#define TTYMOCK_NAME "ttymock"
+#define TTYMOCK_ROOM 4096
+
+static struct tty_port mock_port; /* single port */
+
+/* --- Stats (private) --- */
+static struct {
+ u64 total_writes;
+ u64 total_bytes;
+ u32 last_write_len;
+ spinlock_t lock;
+} mock_state;
+
+/* --- tty_operations --- */
+
+static int mock_open(struct tty_struct *tty, struct file *file)
+{
+ tty->driver_data = &mock_port;
+ return tty_port_open(&mock_port, tty, file);
+}
+
+static void mock_close(struct tty_struct *tty, struct file *file)
+{
+ tty_port_close(&mock_port, tty, file);
+ tty->driver_data = NULL;
+}
+
+static ssize_t mock_write(struct tty_struct *tty, const u8 *buf, size_t cnt)
+{
+ unsigned long flags;
+
+ if (!buf)
+ return -EINVAL;
+
+ spin_lock_irqsave(&mock_state.lock, flags);
+ mock_state.total_writes++;
+ mock_state.total_bytes += cnt;
+ mock_state.last_write_len = cnt;
+ spin_unlock_irqrestore(&mock_state.lock, flags);
+
+ return cnt; /* everything written immediately */
+}
+
+static unsigned int mock_write_room(struct tty_struct *tty)
+{
+ return TTYMOCK_ROOM;
+}
+
+static unsigned int mock_chars_in_buffer(struct tty_struct *tty)
+{
+ return 0;
+}
+
+static const struct tty_operations mock_ops = {
+ .open = mock_open,
+ .close = mock_close,
+ .write = mock_write,
+ .write_room = mock_write_room,
+ .chars_in_buffer = mock_chars_in_buffer,
+};
+
+/* --- tty_port_operations --- */
+
+static bool mock_carrier_raised(struct tty_port *port)
+{
+ return true;
+}
+
+static void mock_shutdown(struct tty_port *port) { }
+
+static const struct tty_port_operations mock_port_ops = {
+ .carrier_raised = mock_carrier_raised,
+ .shutdown = mock_shutdown,
+};
+
+/* --- Public helpers --- */
+
+int tty_mock_register(struct tty_driver **out_drv, struct device *parent)
+{
+ struct tty_driver *drv;
+ struct device *dev;
+ int ret;
+
+ spin_lock_init(&mock_state.lock);
+
+ drv = tty_alloc_driver(1, TTY_DRIVER_RESET_TERMIOS |
+ TTY_DRIVER_REAL_RAW |
+ TTY_DRIVER_UNNUMBERED_NODE |
+ TTY_DRIVER_DYNAMIC_DEV);
+ if (IS_ERR(drv))
+ return PTR_ERR(drv);
+
+ drv->driver_name = TTYMOCK_NAME;
+ drv->name = TTYMOCK_NAME;
+ drv->type = TTY_DRIVER_TYPE_SERIAL;
+ drv->subtype = SERIAL_TYPE_NORMAL;
+ drv->init_termios = tty_std_termios;
+ tty_set_operations(drv, &mock_ops);
+
+ ret = tty_register_driver(drv);
+ if (ret) {
+ tty_driver_kref_put(drv);
+ return ret;
+ }
+
+ tty_port_init(&mock_port);
+ mock_port.ops = &mock_port_ops;
+
+ dev = tty_port_register_device(&mock_port, drv, 0, parent);
+ if (IS_ERR(dev)) {
+ ret = PTR_ERR(dev);
+ tty_unregister_driver(drv);
+ tty_driver_kref_put(drv);
+ tty_port_destroy(&mock_port);
+ return ret;
+ }
+
+ if (out_drv)
+ *out_drv = drv;
+ return 0;
+}
+EXPORT_SYMBOL_IF_KUNIT(tty_mock_register);
+
+void tty_mock_unregister(struct tty_driver *drv)
+{
+ if (!drv)
+ return;
+
+ tty_port_unregister_device(&mock_port, drv, 0);
+ tty_unregister_driver(drv);
+ tty_driver_kref_put(drv);
+ tty_port_destroy(&mock_port);
+}
+EXPORT_SYMBOL_IF_KUNIT(tty_mock_unregister);
+
+struct tty_mock_stats tty_mock_get_stats(void)
+{
+ unsigned long flags;
+ struct tty_mock_stats state;
+
+ spin_lock_irqsave(&mock_state.lock, flags);
+ state.total_writes = mock_state.total_writes;
+ state.total_bytes = mock_state.total_bytes;
+ state.last_write_len = mock_state.last_write_len;
+ spin_unlock_irqrestore(&mock_state.lock, flags);
+
+ return state;
+}
+EXPORT_SYMBOL_IF_KUNIT(tty_mock_get_stats);
+
+void tty_mock_reset_stats(void)
+{
+ unsigned long flags;
+
+ spin_lock_irqsave(&mock_state.lock, flags);
+ mock_state.total_writes = 0;
+ mock_state.total_bytes = 0;
+ mock_state.last_write_len = 0;
+ spin_unlock_irqrestore(&mock_state.lock, flags);
+}
+EXPORT_SYMBOL_IF_KUNIT(tty_mock_reset_stats);
+
+MODULE_LICENSE("GPL");
diff --git a/drivers/tty/tests/tty_mock.h b/drivers/tty/tests/tty_mock.h
new file mode 100644
index 0000000000000000000000000000000000000000..e61eeccc6181fc459d8db790b29350dbf3d9f588
--- /dev/null
+++ b/drivers/tty/tests/tty_mock.h
@@ -0,0 +1,34 @@
+/* SPDX-License-Identifier: GPL-2.0-only */
+/*
+ * TTY - Mock driver
+ *
+ * Copyright (c) 2025 Abhinav Saxena <xandury@gmail.com>
+ *
+ */
+
+#ifndef _TTY_MOCK_H
+#define _TTY_MOCK_H
+
+#include <linux/device.h>
+#include <linux/tty_driver.h>
+#include <linux/types.h>
+
+/* Register a single-port mock tty driver and create device #0. */
+int tty_mock_register(struct tty_driver **out_drv, struct device *parent);
+/* Tear down device, unregister driver and destroy port. */
+void tty_mock_unregister(struct tty_driver *drv);
+
+/* --- Stats available to KUnit tests --- */
+struct tty_mock_stats {
+ u64 total_writes;
+ u64 total_bytes;
+ u32 last_write_len;
+};
+
+/* Returns a snapshot of counters. */
+struct tty_mock_stats tty_mock_get_stats(void);
+
+/* Reset all statistics counters to zero. */
+void tty_mock_reset_stats(void);
+
+#endif /* _TTY_MOCK_H */
--
2.43.0
^ permalink raw reply related [flat|nested] 8+ messages in thread
* [RFC PATCH 4/5] tty: Add KUnit tests for core TTY functionality
2025-08-26 22:51 [RFC PATCH 0/5] tty: Add KUnit test framework for TTY drivers Abhinav Saxena
` (2 preceding siblings ...)
2025-08-26 22:51 ` [RFC PATCH 3/5] tty: Add mock TTY driver for KUnit testing Abhinav Saxena
@ 2025-08-26 22:51 ` Abhinav Saxena
2025-08-26 22:51 ` [RFC PATCH 5/5] tty: Add KUnit tests for ttynull driver Abhinav Saxena
2025-08-31 6:07 ` [RFC PATCH 0/5] tty: Add KUnit test framework for TTY drivers Jiri Slaby
5 siblings, 0 replies; 8+ messages in thread
From: Abhinav Saxena @ 2025-08-26 22:51 UTC (permalink / raw)
To: Greg Kroah-Hartman, Jiri Slaby, Nathan Chancellor,
Nick Desaulniers, Bill Wendling, Justin Stitt, Kees Cook
Cc: linux-kernel, linux-serial, llvm, linux-hardening, Abhinav Saxena
Add comprehensive KUnit tests covering fundamental TTY operations:
- Driver registration and operation validation
- Open/close lifecycle with proper cleanup
- Write operations and buffer management
- Flow control via write_room() and chars_in_buffer()
- RX data injection via line discipline
- Termios configuration for hardware parameters
Tests exercise real kernel code paths using the mock driver to ensure
TTY subsystem changes don't introduce regressions in core functionality.
Signed-off-by: Abhinav Saxena <xandfury@gmail.com>
---
drivers/tty/tests/test_tty_io_core.c | 249 +++++++++++++++++++++++++++++++++++
1 file changed, 249 insertions(+)
diff --git a/drivers/tty/tests/test_tty_io_core.c b/drivers/tty/tests/test_tty_io_core.c
new file mode 100644
index 0000000000000000000000000000000000000000..626160e6ed738d56575cd340b3662aaa94f46a0a
--- /dev/null
+++ b/drivers/tty/tests/test_tty_io_core.c
@@ -0,0 +1,249 @@
+// SPDX-License-Identifier: GPL-2.0
+/*
+ * Minimal KUnit tests for TTY core using the mock driver.
+ *
+ * Scope:
+ * - open/close via tty_port_* paths
+ * - write() returns cnt, write_room() is large, chars_in_buffer() == 0
+ * - stats (writes, bytes, last len) observable via tty_mock_get_stats()
+ * - file_ops read functionality with various termios configurations
+ * - set_termios operations for practical use cases
+ *
+ * Keep this small and obvious—no driver-side buffering or timers.
+ *
+ * Copyright (c) 2025 Abhinav Saxena <xandury@gmail.com>
+ */
+
+#include <kunit/test.h>
+#include <linux/tty.h>
+#include <linux/tty_driver.h>
+#include <linux/tty_flip.h>
+#include <linux/string.h>
+
+#include "tty_test_helpers.h"
+#include "tty_mock.h" /* mock driver API: register/unregister/stats */
+
+/* Single-port driver used across tests (minor 0). */
+static struct tty_driver *mock_driver;
+
+/* ---------- Per-test init: sanity only ---------- */
+static int tty_core_init(struct kunit *test)
+{
+ KUNIT_ASSERT_NOT_NULL(test, mock_driver);
+
+ /* Reset mock statistics before each test */
+ tty_mock_reset_stats();
+
+ if (mock_driver && mock_driver->ports)
+ KUNIT_EXPECT_NOT_NULL(test, mock_driver->ports[0]);
+ return 0;
+}
+
+/* ---------- Tests ---------- */
+
+/*
+ * Test: Verify mock driver has all required operations wired correctly.
+ * Expected: All mandatory ops present, ports array initialised, no NULL pointers.
+ */
+static void test_driver_ops_present(struct kunit *test)
+{
+ KUNIT_ASSERT_NOT_NULL(test, mock_driver);
+
+ /* Basic ops presence and wiring checks via helper. */
+ tty_test_assert_valid_ops(test, mock_driver);
+ KUNIT_EXPECT_NOT_NULL(test, mock_driver->ops->write);
+ KUNIT_EXPECT_NOT_NULL(test, mock_driver->ops->write_room);
+ KUNIT_EXPECT_NOT_NULL(test, mock_driver->ops->chars_in_buffer);
+
+ if (mock_driver->ports)
+ KUNIT_EXPECT_NOT_NULL(test, mock_driver->ports[0]);
+}
+
+/*
+ * Test: Basic TTY lifecycle (open/write/close).
+ * Expected: write() returns byte count, stats increment correctly,
+ * ldisc/port setup.
+ */
+static void test_basic_open_write_close(struct kunit *test)
+{
+ unsigned int idx = 0;
+ struct tty_test_fixture *fx;
+ const char *msg = "Hello, tty!\n";
+ struct tty_mock_stats before, after;
+ ssize_t ret;
+
+ fx = tty_test_create_fixture(test, mock_driver, idx);
+ KUNIT_ASSERT_NOT_NULL(test, fx);
+ KUNIT_ASSERT_EQ(test, tty_test_open(fx), 0);
+ KUNIT_ASSERT_TRUE(test, fx->opened);
+
+ KUNIT_EXPECT_TRUE(test,
+ fx->tty && fx->tty->ldisc && fx->tty->ldisc->ops);
+ KUNIT_EXPECT_TRUE(test, !list_empty(&fx->tty->tty_files));
+ KUNIT_EXPECT_NOT_NULL(test, fx->tty->port);
+
+ before = tty_mock_get_stats();
+
+ ret = tty_test_write(fx, msg, strlen(msg));
+ KUNIT_EXPECT_EQ(test, ret, (ssize_t)strlen(msg));
+
+ after = tty_mock_get_stats();
+
+ /* Test interface contract, not implementation details */
+ KUNIT_EXPECT_GT(test, after.total_writes, before.total_writes);
+ KUNIT_EXPECT_GE(test, after.total_bytes,
+ before.total_bytes + strlen(msg));
+ KUNIT_EXPECT_GT(test, after.last_write_len, 0u);
+
+ KUNIT_EXPECT_EQ(test, tty_test_release(fx), 0);
+}
+
+/*
+ * Test: write_room() and chars_in_buffer() consistency during operations.
+ * Expected: write_room() > 0, chars_in_buffer() == 0, room unchanged after
+ * writes.
+ */
+static void test_write_room_and_chars_in_buffer_invariants(struct kunit *test)
+{
+ unsigned int idx = 0;
+ struct tty_test_fixture *fx;
+ char buf[64];
+ unsigned int room_before, room_after;
+ ssize_t ret;
+
+ memset(buf, 'A', sizeof(buf));
+
+ fx = tty_test_create_fixture(test, mock_driver, idx);
+ KUNIT_ASSERT_NOT_NULL(test, fx);
+ KUNIT_ASSERT_EQ(test, tty_test_open(fx), 0);
+
+ room_before = tty_write_room(fx->tty);
+ KUNIT_EXPECT_GT(test, room_before, 0u);
+ KUNIT_EXPECT_EQ(test, fx->tty->ops->chars_in_buffer(fx->tty), 0u);
+
+ ret = tty_test_write(fx, buf, sizeof(buf));
+ KUNIT_EXPECT_EQ(test, ret, (ssize_t)sizeof(buf));
+
+ room_after = tty_write_room(fx->tty);
+ KUNIT_EXPECT_EQ(test, fx->tty->ops->chars_in_buffer(fx->tty), 0u);
+ KUNIT_EXPECT_GE(test, room_after, room_before);
+
+ KUNIT_EXPECT_EQ(test, tty_test_release(fx), 0);
+}
+
+/*
+ * Test: RX data injection via flip buffers if line discipline supports it.
+ * Expected: tty_test_simulate_rx() returns injected byte count, or test
+ * skipped.
+ */
+static void test_flip_rx_if_supported(struct kunit *test)
+{
+ unsigned int idx = 0;
+ struct tty_test_fixture *fx;
+ int rx_result;
+
+ /* Raw byte buffer, not NUL-terminated */
+ static const unsigned char rx_data[] = "rx-line\n";
+
+ fx = tty_test_create_fixture(test, mock_driver, idx);
+ KUNIT_ASSERT_NOT_NULL(test, fx);
+ KUNIT_ASSERT_EQ(test, tty_test_open(fx), 0);
+
+ KUNIT_EXPECT_TRUE(test,
+ fx->tty && fx->tty->ldisc && fx->tty->ldisc->ops);
+ KUNIT_EXPECT_TRUE(test, !list_empty(&fx->tty->tty_files));
+ KUNIT_EXPECT_NOT_NULL(test, fx->tty->port);
+
+ if (tty_fx_supports_rx(fx)) {
+ rx_result = tty_test_simulate_rx(fx, rx_data, sizeof(rx_data));
+ KUNIT_EXPECT_EQ(test, rx_result, (int)sizeof(rx_data));
+ } else {
+ kunit_skip(test,
+ "ldisc does not support RX; skipping injection");
+ }
+
+ KUNIT_EXPECT_EQ(test, tty_test_release(fx), 0);
+}
+
+/*
+ * Test: set_termios() for hardware settings (baud rate, character size, parity).
+ * Expected: c_cflag settings persist correctly, hardware parameters validated.
+ */
+static void test_set_termios_baud_rate_and_character_size(struct kunit *test)
+{
+ unsigned int idx = 0;
+ struct tty_test_fixture *fx;
+ struct ktermios termios_before, termios_after;
+
+ fx = tty_test_create_fixture(test, mock_driver, idx);
+ KUNIT_ASSERT_NOT_NULL(test, fx);
+ KUNIT_ASSERT_EQ(test, tty_test_open(fx), 0);
+
+ /* Get initial termios */
+ termios_before = fx->tty->termios;
+
+ /* Test baud rate changes */
+ termios_after = termios_before;
+ termios_after.c_cflag &= ~CBAUD;
+ termios_after.c_cflag |= B9600;
+ KUNIT_ASSERT_EQ(test, tty_test_set_termios(fx, &termios_after), 0);
+ KUNIT_EXPECT_EQ(test, fx->tty->termios.c_cflag & CBAUD, B9600);
+
+ /* Test higher baud rate */
+ termios_after.c_cflag &= ~CBAUD;
+ termios_after.c_cflag |= B115200;
+ KUNIT_ASSERT_EQ(test, tty_test_set_termios(fx, &termios_after), 0);
+ KUNIT_EXPECT_EQ(test, fx->tty->termios.c_cflag & CBAUD, B115200);
+
+ /* Test character size changes */
+ termios_after.c_cflag &= ~CSIZE;
+ termios_after.c_cflag |= CS7; /* 7-bit characters */
+ KUNIT_ASSERT_EQ(test, tty_test_set_termios(fx, &termios_after), 0);
+ KUNIT_EXPECT_EQ(test, fx->tty->termios.c_cflag & CSIZE, CS7);
+
+ termios_after.c_cflag &= ~CSIZE;
+ termios_after.c_cflag |= CS8; /* 8-bit characters */
+ KUNIT_ASSERT_EQ(test, tty_test_set_termios(fx, &termios_after), 0);
+ KUNIT_EXPECT_EQ(test, fx->tty->termios.c_cflag & CSIZE, CS8);
+
+ /* Test parity settings */
+ termios_after.c_cflag |= PARENB; /* Enable parity */
+ termios_after.c_cflag &= ~PARODD; /* Even parity */
+ KUNIT_ASSERT_EQ(test, tty_test_set_termios(fx, &termios_after), 0);
+ KUNIT_EXPECT_TRUE(test, fx->tty->termios.c_cflag & PARENB);
+ KUNIT_EXPECT_FALSE(test, fx->tty->termios.c_cflag & PARODD);
+
+ KUNIT_EXPECT_EQ(test, tty_test_release(fx), 0);
+}
+
+/* ---------- Suite registration ---------- */
+
+static int tty_core_suite_init(struct kunit_suite *suite)
+{
+ return tty_mock_register(&mock_driver, NULL);
+}
+
+static void tty_core_suite_exit(struct kunit_suite *suite)
+{
+ tty_mock_unregister(mock_driver);
+ mock_driver = NULL;
+}
+
+static struct kunit_case tty_core_cases[] = {
+ KUNIT_CASE(test_driver_ops_present),
+ KUNIT_CASE(test_basic_open_write_close),
+ KUNIT_CASE(test_write_room_and_chars_in_buffer_invariants),
+ KUNIT_CASE(test_flip_rx_if_supported),
+ KUNIT_CASE(test_set_termios_baud_rate_and_character_size),
+ {}
+};
+
+static struct kunit_suite tty_core_suite = {
+ .name = "tty_io_core",
+ .init = tty_core_init,
+ .suite_init = tty_core_suite_init,
+ .suite_exit = tty_core_suite_exit,
+ .test_cases = tty_core_cases,
+};
+
+kunit_test_suite(tty_core_suite);
--
2.43.0
^ permalink raw reply related [flat|nested] 8+ messages in thread
* [RFC PATCH 5/5] tty: Add KUnit tests for ttynull driver
2025-08-26 22:51 [RFC PATCH 0/5] tty: Add KUnit test framework for TTY drivers Abhinav Saxena
` (3 preceding siblings ...)
2025-08-26 22:51 ` [RFC PATCH 4/5] tty: Add KUnit tests for core TTY functionality Abhinav Saxena
@ 2025-08-26 22:51 ` Abhinav Saxena
2025-08-31 6:07 ` [RFC PATCH 0/5] tty: Add KUnit test framework for TTY drivers Jiri Slaby
5 siblings, 0 replies; 8+ messages in thread
From: Abhinav Saxena @ 2025-08-26 22:51 UTC (permalink / raw)
To: Greg Kroah-Hartman, Jiri Slaby, Nathan Chancellor,
Nick Desaulniers, Bill Wendling, Justin Stitt, Kees Cook
Cc: linux-kernel, linux-serial, llvm, linux-hardening, Abhinav Saxena
Add targeted tests for the TTY null driver covering its data sink
behavior and driver characteristics. Tests verify that ttynull
properly discards written data while maintaining standard TTY
semantics for applications requiring TTY interfaces without
caring about output.
The tests are integrated directly into ttynull.c when
CONFIG_TTY_KUNIT_NULL_TTY_TESTS=y to test the actual driver
implementation.
Signed-off-by: Abhinav Saxena <xandfury@gmail.com>
---
drivers/tty/tests/test_ttynull.c | 163 +++++++++++++++++++++++++++++++++++++++
drivers/tty/ttynull.c | 5 ++
2 files changed, 168 insertions(+)
diff --git a/drivers/tty/tests/test_ttynull.c b/drivers/tty/tests/test_ttynull.c
new file mode 100644
index 0000000000000000000000000000000000000000..c062d69bd5d5975ab84442a83426a1d44440b0a6
--- /dev/null
+++ b/drivers/tty/tests/test_ttynull.c
@@ -0,0 +1,163 @@
+// SPDX-License-Identifier: GPL-2.0-only
+/*
+ * KUnit tests for the TTY null driver
+ *
+ * Tests for the ttynull driver covering basic lifecycle and sink behavior.
+ * The ttynull driver acts as a data sink, discarding all written data
+ * while providing minimal overhead for applications that need a TTY
+ * but don't care about the output.
+ *
+ * Copyright (c) 2025 Abhinav Saxena <xandury@gmail.com>
+ *
+ */
+
+#include <kunit/test.h>
+#include <linux/tty.h>
+#include <linux/tty_driver.h>
+#include <linux/tty_flip.h>
+#include <linux/string.h>
+
+#include "tests/tty_test_helpers.h"
+
+/**
+ * test_ttynull_write_sink - Verify ttynull acts as data sink
+ * @test: KUnit test context
+ *
+ * ttynull should accept all write data and discard it silently.
+ * This tests the core functionality of the null TTY driver.
+ */
+static void test_ttynull_write_sink(struct kunit *test)
+{
+ struct tty_driver *drv = ttynull_driver;
+ struct tty_test_fixture *fx;
+ const char *msg = "test data; discard me";
+ unsigned int room;
+ ssize_t write_result;
+
+ fx = tty_test_create_fixture(test, drv, 0);
+ KUNIT_ASSERT_NOT_NULL(test, fx);
+
+ KUNIT_ASSERT_EQ(test, tty_test_open(fx), 0);
+ KUNIT_ASSERT_TRUE(test, fx->opened);
+
+ /* Verify TTY is properly initialized */
+ KUNIT_EXPECT_NOT_NULL(test, fx->tty);
+ KUNIT_EXPECT_NOT_NULL(test, fx->tty->ldisc);
+ KUNIT_EXPECT_TRUE(test, !list_empty(&fx->tty->tty_files));
+
+ /* Check initial write room - should be available */
+ room = tty_test_get_write_room(fx);
+ KUNIT_EXPECT_GT(test, room, 0U);
+
+ /* Write data - should be completely accepted */
+ KUNIT_ASSERT_EQ(test, tty_test_write_all(fx, msg, strlen(msg)), 0);
+
+ /* ttynull discards writes; buffer should remain empty */
+ KUNIT_EXPECT_EQ(test, tty_test_get_chars_in_buffer(fx), 0U);
+
+ /* Write room should remain available for a sink */
+ room = tty_test_get_write_room(fx);
+ KUNIT_EXPECT_GT(test, room, 0U);
+
+ /* ttynull should accept all data */
+ write_result = tty_test_write(fx, msg, strlen(msg));
+ KUNIT_EXPECT_EQ(test, write_result, strlen(msg));
+
+ /* Multiple writes should all succeed */
+ write_result = tty_test_write(fx, msg, strlen(msg));
+ KUNIT_EXPECT_EQ(test, write_result, strlen(msg));
+
+ /*
+ * TODO: Simulate hangup condition making subsequent writes fail
+ * For now, just release.
+ */
+ KUNIT_ASSERT_EQ(test, tty_test_release(fx), 0);
+}
+
+/**
+ * test_ttynull_read_behavior - Verify read behavior on null device
+ * @test: KUnit test context
+ *
+ * While ttynull technically supports read (via N_TTY), reading should
+ * behave predictably (likely EOF or blocking).
+ */
+static void test_ttynull_read_behavior(struct kunit *test)
+{
+ struct tty_driver *drv = ttynull_driver;
+ struct tty_test_fixture *fx;
+ struct tty_ldisc *ld;
+ /* char read_buffer[128]; */
+ /* ssize_t bytes_read; */
+
+ fx = tty_test_create_fixture(test, drv, 0);
+ KUNIT_ASSERT_NOT_NULL(test, fx);
+
+ KUNIT_ASSERT_EQ(test, tty_test_open(fx), 0);
+ KUNIT_ASSERT_TRUE(test, fx->opened);
+
+ ld = tty_ldisc_ref(fx->tty);
+ KUNIT_ASSERT_NOT_ERR_OR_NULL(test, ld);
+ KUNIT_ASSERT_TRUE(test, ld->ops && ld->ops->read);
+ tty_ldisc_deref(ld);
+
+ /*
+ * Reading from ttynull should behave consistently.
+ * Depending on implementation, this might:
+ * - Return 0 (EOF)
+ * - Block (if no data available)
+ * - Return -EAGAIN (if non-blocking)
+ */
+ KUNIT_ASSERT_NOT_NULL(test, fx->tty->disc_data);
+ /* bytes_read = tty_test_read(fx, read_buffer, sizeof(read_buffer)); */
+
+ /*
+ * Document the expected behavior
+ * - adjust based on actual ttynull implementation
+ */
+ /* KUNIT_EXPECT_GE(test, bytes_read, 0); /\* Should not return error *\/ */
+
+ KUNIT_ASSERT_EQ(test, tty_test_release(fx), 0);
+}
+
+/**
+ * test_ttynull_driver_properties - Verify driver characteristics
+ * @test: KUnit test context
+ *
+ * Test the driver's static properties and configuration. Also, ensure that
+ * it implements the required file_operation ops.
+ */
+static void test_ttynull_driver_properties(struct kunit *test)
+{
+ struct tty_driver *drv = ttynull_driver;
+
+ KUNIT_ASSERT_NOT_NULL(test, drv);
+
+ /* Verify driver identification */
+ KUNIT_EXPECT_STREQ(test, drv->driver_name, "ttynull");
+ KUNIT_EXPECT_STREQ(test, drv->name, "ttynull");
+
+ /* Ensure that driver implements the required ops. */
+ KUNIT_ASSERT_NOT_NULL(test, drv);
+ tty_test_assert_valid_ops(test, drv);
+
+ /* Verify driver type */
+ KUNIT_EXPECT_EQ(test, drv->type, TTY_DRIVER_TYPE_CONSOLE);
+
+ /* Verify driver flags */
+ KUNIT_EXPECT_TRUE(test, drv->flags & TTY_DRIVER_REAL_RAW);
+ KUNIT_EXPECT_TRUE(test, drv->flags & TTY_DRIVER_RESET_TERMIOS);
+}
+
+static struct kunit_case ttynull_test_cases[] = {
+ KUNIT_CASE(test_ttynull_write_sink),
+ KUNIT_CASE(test_ttynull_read_behavior),
+ KUNIT_CASE(test_ttynull_driver_properties),
+ {}
+};
+
+static struct kunit_suite ttynull_test_suite = {
+ .name = "ttynull",
+ .test_cases = ttynull_test_cases,
+};
+
+kunit_test_suite(ttynull_test_suite);
diff --git a/drivers/tty/ttynull.c b/drivers/tty/ttynull.c
index 6b2f7208b564b659bf7faa4113541fcea7ec6ac0..baad9f0e3d27d97409a571e8da953384b7c64891 100644
--- a/drivers/tty/ttynull.c
+++ b/drivers/tty/ttynull.c
@@ -6,6 +6,7 @@
* Copyright (C) 2010 Samo Pogacnik
*/
+#include <kunit/visibility.h>
#include <linux/console.h>
#include <linux/module.h>
#include <linux/tty.h>
@@ -106,5 +107,9 @@ static void __exit ttynull_exit(void)
module_init(ttynull_init);
module_exit(ttynull_exit);
+#ifdef CONFIG_TTY_KUNIT_NULL_TTY_TESTS
+#include "tests/test_ttynull.c"
+#endif /* CONFIG_TTY_KUNIT_TTYNULL_TESTS */
+
MODULE_DESCRIPTION("NULL TTY driver");
MODULE_LICENSE("GPL v2");
--
2.43.0
^ permalink raw reply related [flat|nested] 8+ messages in thread
* Re: [RFC PATCH 0/5] tty: Add KUnit test framework for TTY drivers
2025-08-26 22:51 [RFC PATCH 0/5] tty: Add KUnit test framework for TTY drivers Abhinav Saxena
` (4 preceding siblings ...)
2025-08-26 22:51 ` [RFC PATCH 5/5] tty: Add KUnit tests for ttynull driver Abhinav Saxena
@ 2025-08-31 6:07 ` Jiri Slaby
2025-09-03 0:56 ` Abhinav Saxena
5 siblings, 1 reply; 8+ messages in thread
From: Jiri Slaby @ 2025-08-31 6:07 UTC (permalink / raw)
To: Abhinav Saxena, Greg Kroah-Hartman, Nathan Chancellor,
Nick Desaulniers, Bill Wendling, Justin Stitt, Kees Cook
Cc: linux-kernel, linux-serial, llvm, linux-hardening
On 27. 08. 25, 0:51, Abhinav Saxena wrote:
> This patch series introduces a KUnit testing framework for the TTY
> subsystem, enabling deterministic, automated testing of TTY drivers and
> core functionality without requiring hardware or userspace interaction.
>
> On an x86_64 build with CONFIG_GCOV enabled, these tests increased
> TTY subsystem coverage to approximately 10.6% line coverage and
> 14.7% function coverage [1].
>
> Problem Statement
> -----------------
> Testing TTY drivers today requires:
> - User-space interaction through device nodes
> - Complex setup with ptys or real hardware
> - Limited ability to test error paths reliably and deterministically
>
> This series solves these issues by providing in-kernel KUnit tests that
> exercise real TTY core paths under controlled, deterministic conditions.
>
> What This Series Provides
> -------------------------
> 1. Reusable test helpers (`tty_test_helpers.h`):
> - Minimal (~150 LOC) infrastructure that any TTY driver should be
> able to use
> - Automatic resource management
> - Integrated into core files under KUnit guard, with
> `EXPORT_SYMBOL_IF_KUNIT()` to keep the production symbol table
> clean
>
> 2. Mock TTY driver:
> - Demonstrates how drivers can leverage the helpers
> - Enables deterministic scenarios without hardware
>
> 3. Core TTY tests:
> - Validate open/close/read/write/termios paths
> - Exercise hangup, resize, and error handling
> - Ensure real kernel paths are tested, not mocked stubs
>
> 4. ttynull driver tests:
> - Validate data sink behavior of the null driver
> - Provide a minimal driver contract baseline
>
> 5. Optional coverage support:
> - GCOV integration for test coverage analysis
>
> Future Work
> -----------
> With this foundation merged, follow-up work can:
> - Add more coverage of TTY core functions
> - Enable each TTY driver to maintain its own KUnit suite
> - Introduce stress tests and race detection
> - Extend to include more tests for other tty drivers:
> - UART drivers: test interrupt handling without hardware
> - USB serial: validate disconnect and reconnect sequences
> - PTY drivers: test resize, flow control, and hangups
> - Virtual consoles: test Unicode and input handling
>
> Testing
> -------
> - All patches pass `checkpatch.pl`
> - Verified on x86_64 with:
> ./tools/testing/kunit/kunit.py run \
> --kunitconfig=.kunit/ \
> --kunitconfig=drivers/tty/tests/.kunitconfig \
> --arch=x86_64
> - All tests pass (working around tty_read wrapper in progress)
>
> Feedback welcome! :)
Wow, looks good. Has it found something yet :)?
FWIW
Reviewed-by: Jiri Slaby <jirislaby@kernel.org>
thanks,
--
js
suse labs
^ permalink raw reply [flat|nested] 8+ messages in thread
* Re: [RFC PATCH 0/5] tty: Add KUnit test framework for TTY drivers
2025-08-31 6:07 ` [RFC PATCH 0/5] tty: Add KUnit test framework for TTY drivers Jiri Slaby
@ 2025-09-03 0:56 ` Abhinav Saxena
0 siblings, 0 replies; 8+ messages in thread
From: Abhinav Saxena @ 2025-09-03 0:56 UTC (permalink / raw)
To: Jiri Slaby
Cc: Greg Kroah-Hartman, Nathan Chancellor, Nick Desaulniers,
Bill Wendling, Justin Stitt, Kees Cook, linux-kernel,
linux-serial, llvm, linux-hardening
[-- Attachment #1: Type: text/plain, Size: 3196 bytes --]
Hi!
Jiri Slaby <jirislaby@kernel.org> writes:
> On 27. 08. 25, 0:51, Abhinav Saxena wrote:
>> This patch series introduces a KUnit testing framework for the TTY
>> subsystem, enabling deterministic, automated testing of TTY drivers and
>> core functionality without requiring hardware or userspace interaction.
>> On an x86_64 build with CONFIG_GCOV enabled, these tests increased
>> TTY subsystem coverage to approximately 10.6% line coverage and
>> 14.7% function coverage [1].
>> Problem Statement
>> —————–
>> Testing TTY drivers today requires:
>> - User-space interaction through device nodes
>> - Complex setup with ptys or real hardware
>> - Limited ability to test error paths reliably and deterministically
>> This series solves these issues by providing in-kernel KUnit tests
>> that
>> exercise real TTY core paths under controlled, deterministic conditions.
>> What This Series Provides
>> ————————-
>> 1. Reusable test helpers (`tty_test_helpers.h`):
>> - Minimal (~150 LOC) infrastructure that any TTY driver should be
>> able to use
>> - Automatic resource management
>> - Integrated into core files under KUnit guard, with
>> `EXPORT_SYMBOL_IF_KUNIT()` to keep the production symbol table
>> clean
>> 2. Mock TTY driver:
>> - Demonstrates how drivers can leverage the helpers
>> - Enables deterministic scenarios without hardware
>> 3. Core TTY tests:
>> - Validate open/close/read/write/termios paths
>> - Exercise hangup, resize, and error handling
>> - Ensure real kernel paths are tested, not mocked stubs
>> 4. ttynull driver tests:
>> - Validate data sink behavior of the null driver
>> - Provide a minimal driver contract baseline
>> 5. Optional coverage support:
>> - GCOV integration for test coverage analysis
>> Future Work
>> ———–
>> With this foundation merged, follow-up work can:
>> - Add more coverage of TTY core functions
>> - Enable each TTY driver to maintain its own KUnit suite
>> - Introduce stress tests and race detection
>> - Extend to include more tests for other tty drivers:
>> - UART drivers: test interrupt handling without hardware
>> - USB serial: validate disconnect and reconnect sequences
>> - PTY drivers: test resize, flow control, and hangups
>> - Virtual consoles: test Unicode and input handling
>> Testing
>> ——-
>> - All patches pass `checkpatch.pl`
>> - Verified on x86_64 with:
>> ./tools/testing/kunit/kunit.py run \
>> –kunitconfig=.kunit/ \
>> –kunitconfig=drivers/tty/tests/.kunitconfig \
>> –arch=x86_64
>> - All tests pass (working around tty_read wrapper in progress)
>> Feedback welcome! :)
>
> Wow, looks good. Has it found something yet :)?
>
Not yet. But I am kinda excited about having fixture based tests which
can be applied to different tty drivers and test things like
race conditions, CVE-class vulnerabilities, edge cases among other things.
One step at a time, I guess :)
>
> FWIW
> Reviewed-by: Jiri Slaby <jirislaby@kernel.org>
>
> thanks,
Thanks for the review!
-Abhinav
^ permalink raw reply [flat|nested] 8+ messages in thread
end of thread, other threads:[~2025-09-03 1:06 UTC | newest]
Thread overview: 8+ messages (download: mbox.gz follow: Atom feed
-- links below jump to the message on this page --
2025-08-26 22:51 [RFC PATCH 0/5] tty: Add KUnit test framework for TTY drivers Abhinav Saxena
2025-08-26 22:51 ` [RFC PATCH 1/5] tty: Add KUnit test infrastructure configuration Abhinav Saxena
2025-08-26 22:51 ` [RFC PATCH 2/5] tty: Add KUnit test helper functions Abhinav Saxena
2025-08-26 22:51 ` [RFC PATCH 3/5] tty: Add mock TTY driver for KUnit testing Abhinav Saxena
2025-08-26 22:51 ` [RFC PATCH 4/5] tty: Add KUnit tests for core TTY functionality Abhinav Saxena
2025-08-26 22:51 ` [RFC PATCH 5/5] tty: Add KUnit tests for ttynull driver Abhinav Saxena
2025-08-31 6:07 ` [RFC PATCH 0/5] tty: Add KUnit test framework for TTY drivers Jiri Slaby
2025-09-03 0:56 ` Abhinav Saxena
This is a public inbox, see mirroring instructions
for how to clone and mirror all data and code used for this inbox;
as well as URLs for NNTP newsgroup(s).