linux-serial.vger.kernel.org archive mirror
 help / color / mirror / Atom feed
From: Abhinav Saxena <xandfury@gmail.com>
To: Greg Kroah-Hartman <gregkh@linuxfoundation.org>,
	 Jiri Slaby <jirislaby@kernel.org>,
	Nathan Chancellor <nathan@kernel.org>,
	 Nick Desaulniers <nick.desaulniers+lkml@gmail.com>,
	 Bill Wendling <morbo@google.com>,
	Justin Stitt <justinstitt@google.com>,
	 Kees Cook <kees@kernel.org>
Cc: linux-kernel@vger.kernel.org, linux-serial@vger.kernel.org,
	 llvm@lists.linux.dev, linux-hardening@vger.kernel.org,
	 Abhinav Saxena <xandfury@gmail.com>
Subject: [RFC PATCH 4/5] tty: Add KUnit tests for core TTY functionality
Date: Tue, 26 Aug 2025 16:51:34 -0600	[thread overview]
Message-ID: <20250826-tty-tests-v1-4-e904a817df92@gmail.com> (raw)
In-Reply-To: <20250826-tty-tests-v1-0-e904a817df92@gmail.com>

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


  parent reply	other threads:[~2025-08-26 22:51 UTC|newest]

Thread overview: 8+ messages / expand[flat|nested]  mbox.gz  Atom feed  top
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 ` Abhinav Saxena [this message]
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

Reply instructions:

You may reply publicly to this message via plain-text email
using any one of the following methods:

* Save the following mbox file, import it into your mail client,
  and reply-to-all from there: mbox

  Avoid top-posting and favor interleaved quoting:
  https://en.wikipedia.org/wiki/Posting_style#Interleaved_style

* Reply using the --to, --cc, and --in-reply-to
  switches of git-send-email(1):

  git send-email \
    --in-reply-to=20250826-tty-tests-v1-4-e904a817df92@gmail.com \
    --to=xandfury@gmail.com \
    --cc=gregkh@linuxfoundation.org \
    --cc=jirislaby@kernel.org \
    --cc=justinstitt@google.com \
    --cc=kees@kernel.org \
    --cc=linux-hardening@vger.kernel.org \
    --cc=linux-kernel@vger.kernel.org \
    --cc=linux-serial@vger.kernel.org \
    --cc=llvm@lists.linux.dev \
    --cc=morbo@google.com \
    --cc=nathan@kernel.org \
    --cc=nick.desaulniers+lkml@gmail.com \
    /path/to/YOUR_REPLY

  https://kernel.org/pub/software/scm/git/docs/git-send-email.html

* If your mail client supports setting the In-Reply-To header
  via mailto: links, try the mailto: link
Be sure your reply has a Subject: header at the top and a blank line before the message body.
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).