LCOV - code coverage report
Current view: top level - tty/tests - tty_test_helpers.c (source / functions) Coverage Total Hit
Test: TTY Combined Coverage Lines: 74.0 % 192 142
Test Date: 2025-08-26 15:45:50 Functions: 85.7 % 14 12

            Line data    Source code
       1              : // SPDX-License-Identifier: GPL-2.0
       2              : /*
       3              :  * KUnit test helpers for TTY drivers
       4              :  *
       5              :  * This file is included directly into tty_io.c when CONFIG_TTY_KUNIT_TESTS=y.
       6              :  * This allows the helper functions to access internal TTY functions like
       7              :  * tty_open() and tty_release() while providing exported symbols for use
       8              :  * by test modules.
       9              :  *
      10              :  * All functions are exported via EXPORT_SYMBOL_IF_KUNIT() so they are
      11              :  * only available when KUNIT is enabled, preventing pollution of the
      12              :  * production symbol table.
      13              :  *
      14              :  * Copyright (c) 2025 Abhinav Saxena <[email protected]>
      15              :  */
      16              : 
      17              : #include <kunit/test.h>
      18              : #include <kunit/visibility.h>
      19              : #include <linux/fs.h>
      20              : #include <linux/kdev_t.h>
      21              : #include <linux/uio.h>
      22              : #include <linux/tty.h>
      23              : #include <linux/tty_driver.h>
      24              : #include <linux/tty_flip.h>
      25              : #include <linux/tty_ldisc.h>
      26              : #include <linux/termios.h>
      27              : 
      28              : #include "tests/tty_test_helpers.h"
      29              : 
      30              : 
      31              : static struct cdev tty_cdev;
      32              : 
      33              : /**
      34              :  * _tty_test_cleanup_release - KUnit cleanup action for TTY release
      35              :  * @data: Pointer to tty_test_fixture
      36              :  *
      37              :  * Internal cleanup function registered with kunit_add_action() to ensure
      38              :  * TTY is properly released even if test fails or exits early.
      39              :  * This prevents resource leaks and system instability.
      40              :  */
      41            6 : static void _tty_test_cleanup_release(void *data)
      42              : {
      43            6 :         struct tty_test_fixture *fx = data;
      44            6 :         int ret;
      45              : 
      46            6 :         if (!fx || !fx->opened || !fx->file || !fx->inode)
      47            6 :                 return;
      48              : 
      49            0 :         ret = tty_release(fx->inode, fx->file);
      50            0 :         if (ret)
      51            0 :                 pr_warn("TTY test cleanup failed: %d\n", ret);
      52            0 :         fx->opened = false;
      53            6 : }
      54              : 
      55              : /**
      56              :  * tty_test_create_fixture - Create a test fixture for TTY driver testing
      57              :  */
      58            6 : struct tty_test_fixture *tty_test_create_fixture(struct kunit *test,
      59              :                                                  struct tty_driver *driver,
      60              :                                                  unsigned int index)
      61              : {
      62            6 :         struct tty_test_fixture *fx;
      63              : 
      64            6 :         KUNIT_ASSERT_NOT_NULL(test, driver);
      65              : 
      66            6 :         fx = kunit_kzalloc(test, sizeof(*fx), GFP_KERNEL);
      67            6 :         KUNIT_ASSERT_NOT_NULL(test, fx);
      68              : 
      69            6 :         fx->test = test;
      70            6 :         fx->driver = driver;
      71            6 :         fx->dev = MKDEV(driver->major, driver->minor_start + index);
      72              : 
      73              :         /* Create synthetic VFS structures for real TTY operations */
      74            6 :         fx->file = kunit_kzalloc(test, sizeof(*fx->file), GFP_KERNEL);
      75            6 :         fx->inode = kunit_kzalloc(test, sizeof(*fx->inode), GFP_KERNEL);
      76            6 :         KUNIT_ASSERT_NOT_NULL(test, fx->file);
      77            6 :         KUNIT_ASSERT_NOT_NULL(test, fx->inode);
      78              : 
      79              :         /* Initialize as character device with appropriate permissions */
      80            6 :         init_special_inode(fx->inode, S_IFCHR | 0600, fx->dev);
      81            6 :         fx->inode->i_rdev = fx->dev;
      82            6 :         fx->inode->i_cdev = &tty_cdev;
      83            6 :         KUNIT_ASSERT_NOT_NULL(test, fx->inode->i_cdev);
      84              : 
      85            6 :         fx->file->f_flags = O_RDWR;
      86            6 :         fx->file->f_mode = FMODE_READ | FMODE_WRITE;
      87            6 :         fx->file->f_inode = fx->inode;
      88              : 
      89              :         /* Register cleanup before any operations that might fail */
      90            6 :         kunit_add_action(test, _tty_test_cleanup_release, fx);
      91              : 
      92            6 :         fx->opened = false;
      93           12 :         return fx;
      94            6 : }
      95              : EXPORT_SYMBOL_IF_KUNIT(tty_test_create_fixture);
      96              : 
      97              : /**
      98              :  * tty_test_open - Open TTY through standard kernel path
      99              :  */
     100            6 : int tty_test_open(struct tty_test_fixture *fx)
     101              : {
     102            6 :         int ret;
     103              : 
     104            6 :         KUNIT_ASSERT_NOT_NULL(fx->test, fx);
     105            6 :         KUNIT_ASSERT_NOT_NULL(fx->test, fx->file);
     106            6 :         KUNIT_ASSERT_NOT_NULL(fx->test, fx->inode);
     107              : 
     108            6 :         ret = tty_open(fx->inode, fx->file);
     109            6 :         if (ret)
     110            0 :                 return ret;
     111              : 
     112            6 :         fx->tty = file_tty(fx->file);
     113            6 :         KUNIT_ASSERT_NOT_NULL(fx->test, fx->tty);
     114              : 
     115              :         /* Verify the TTY is properly set up */
     116            6 :         KUNIT_EXPECT_TRUE(fx->test, !list_empty(&fx->tty->tty_files));
     117              :         /* Ldisc must now be fully installed */
     118            6 :         KUNIT_ASSERT_NOT_NULL(fx->test, fx->tty->ldisc);
     119            6 :         KUNIT_EXPECT_TRUE(fx->test, fx->tty->ldisc->ops);
     120            6 :         KUNIT_ASSERT_NOT_NULL(fx->test, fx->tty->disc_data);
     121            6 :         KUNIT_EXPECT_NOT_NULL(fx->test, fx->tty->port);
     122              : 
     123            6 :         fx->port = fx->tty->port;
     124            6 :         ret = fx->tty->ldisc->ops->open(fx->tty);
     125            6 :         if (ret) {
     126            0 :                 tty_release(fx->inode, fx->file);
     127            0 :                 return ret;
     128              :         }
     129              : 
     130              :         /* Enable non-blocking mode for predictable test behavior */
     131            6 :         fx->file->f_flags |= O_NONBLOCK;
     132            6 :         fx->opened = true;
     133            6 :         return 0;
     134            6 : }
     135              : EXPORT_SYMBOL_IF_KUNIT(tty_test_open);
     136              : 
     137              : /**
     138              :  * tty_test_release - Close TTY through standard kernel path
     139              :  */
     140            6 : int tty_test_release(struct tty_test_fixture *fx)
     141              : {
     142            6 :         int ret;
     143              : 
     144            6 :         if (!fx || !fx->opened)
     145            0 :                 return 0;
     146              : 
     147              :         /*
     148              :          * This calls the internal tty_release() function directly.
     149              :          * This works because this code is compiled as part of tty_io.c.
     150              :          */
     151            6 :         ret = tty_release(fx->inode, fx->file);
     152            6 :         if (!ret) {
     153            6 :                 fx->opened = false;
     154            6 :                 fx->tty = NULL;
     155            6 :                 fx->port = NULL;
     156            6 :         }
     157            6 :         return ret;
     158            6 : }
     159              : EXPORT_SYMBOL_IF_KUNIT(tty_test_release);
     160              : 
     161              : /**
     162              :  * tty_test_write - Write data to TTY
     163              :  */
     164            5 : ssize_t tty_test_write(struct tty_test_fixture *fx, const void *buf,
     165              :                        size_t count)
     166              : {
     167            5 :         struct kiocb iocb;
     168            5 :         struct iov_iter from;
     169            5 :         struct kvec kvec = { .iov_base = (void *)buf, .iov_len = count };
     170              : 
     171            5 :         KUNIT_ASSERT_NOT_NULL(fx->test, fx);
     172            5 :         KUNIT_ASSERT_NOT_NULL(fx->test, fx->file);
     173            5 :         KUNIT_ASSERT_TRUE(fx->test, fx->opened);
     174              : 
     175            5 :         init_sync_kiocb(&iocb, fx->file);
     176            5 :         iov_iter_kvec(&from, WRITE, &kvec, 1, count);
     177              : 
     178              :         /* tty_write() is exported, so this works */
     179           10 :         return tty_write(&iocb, &from);
     180            5 : }
     181              : EXPORT_SYMBOL_IF_KUNIT(tty_test_write);
     182              : 
     183              : /**
     184              :  * tty_test_write_all - Write all data or fail
     185              :  */
     186            1 : int tty_test_write_all(struct tty_test_fixture *fx, const void *buf, size_t len)
     187              : {
     188            1 :         size_t off = 0;
     189            1 :         int retries = 10;
     190              : 
     191            1 :         KUNIT_ASSERT_NOT_NULL(fx->test, fx);
     192            1 :         KUNIT_ASSERT_TRUE(fx->test, fx->opened);
     193              : 
     194            2 :         while (off < len && retries--) {
     195            2 :                 ssize_t n =
     196            1 :                         tty_test_write(fx, (const char *)buf + off, len - off);
     197            1 :                 if (n < 0)
     198            0 :                         return n;
     199            1 :                 if (n == 0) {
     200              :                         /* No progress - prevent infinite loop */
     201            0 :                         if (--retries <= 0) {
     202            0 :                                 KUNIT_FAIL(fx->test,
     203              :                                            "Write stalled after %zu bytes",
     204              :                                            off);
     205            0 :                                 return -EIO;
     206              :                         }
     207            0 :                         continue;
     208              :                 }
     209            1 :                 off += n;
     210            1 :         }
     211              : 
     212            1 :         if (off < len) {
     213            0 :                 KUNIT_FAIL(fx->test, "Incomplete write: %zu/%zu bytes", off,
     214              :                            len);
     215            0 :                 return -EIO;
     216              :         }
     217              : 
     218            1 :         return 0;
     219            1 : }
     220              : EXPORT_SYMBOL_IF_KUNIT(tty_test_write_all);
     221              : 
     222              : /**
     223              :  * tty_test_read - Read data from TTY (non-blocking)
     224              :  */
     225            0 : ssize_t tty_test_read(struct tty_test_fixture *fx, void *buf, size_t count)
     226              : {
     227            0 :         struct kiocb iocb;
     228            0 :         struct iov_iter to;
     229            0 :         struct kvec kvec = { .iov_base = buf, .iov_len = count };
     230              : 
     231            0 :         KUNIT_ASSERT_NOT_NULL(fx->test, fx);
     232            0 :         KUNIT_ASSERT_NOT_NULL(fx->test, fx->file);
     233            0 :         KUNIT_ASSERT_TRUE(fx->test, fx->opened);
     234              : 
     235            0 :         init_sync_kiocb(&iocb, fx->file);
     236            0 :         iov_iter_kvec(&to, READ, &kvec, 1, count);
     237              : 
     238            0 :         return tty_read(&iocb, &to);
     239            0 : }
     240              : EXPORT_SYMBOL_IF_KUNIT(tty_test_read);
     241              : 
     242              : /**
     243              :  * tty_test_read_all - Attempt to read all requested data
     244              :  */
     245            0 : ssize_t tty_test_read_all(struct tty_test_fixture *fx, void *buf, size_t want)
     246              : {
     247            0 :         size_t off = 0;
     248            0 :         int tries = 8;
     249              : 
     250            0 :         KUNIT_ASSERT_NOT_NULL(fx->test, fx);
     251            0 :         KUNIT_ASSERT_TRUE(fx->test, fx->opened);
     252              : 
     253            0 :         while (off < want && tries--) {
     254            0 :                 ssize_t n = tty_test_read(fx, (char *)buf + off, want - off);
     255              : 
     256            0 :                 if (n == -EAGAIN)
     257            0 :                         continue;
     258            0 :                 if (n < 0)
     259            0 :                         return n;
     260            0 :                 if (n == 0)
     261            0 :                         continue;
     262            0 :                 off += n;
     263            0 :         }
     264            0 :         return off;
     265            0 : }
     266              : EXPORT_SYMBOL_IF_KUNIT(tty_test_read_all);
     267              : 
     268              : /**
     269              :  * tty_test_simulate_rx - Inject received data for testing
     270              :  */
     271            1 : int tty_test_simulate_rx(struct tty_test_fixture *fx, const unsigned char *data,
     272              :                          size_t len)
     273              : {
     274            1 :         int ret;
     275              : 
     276            1 :         KUNIT_ASSERT_NOT_NULL(fx->test, fx);
     277            1 :         KUNIT_ASSERT_NOT_NULL(fx->test, fx->port);
     278            1 :         KUNIT_ASSERT_TRUE(fx->test, fx->opened);
     279              : 
     280            1 :         ret = tty_insert_flip_string(fx->port, data, len);
     281            1 :         if (ret > 0)
     282            1 :                 tty_flip_buffer_push(fx->port);
     283              : 
     284            2 :         return ret;
     285            1 : }
     286              : EXPORT_SYMBOL_IF_KUNIT(tty_test_simulate_rx);
     287              : 
     288              : /**
     289              :  * tty_fx_supports_rx - Check if fixture supports RX testing
     290              :  */
     291            1 : bool tty_fx_supports_rx(const struct tty_test_fixture *fx)
     292              : {
     293            1 :         struct tty_ldisc *ld;
     294            1 :         const struct tty_ldisc_ops *ops;
     295              : 
     296            1 :         if (!fx || !fx->tty || !fx->opened)
     297            0 :                 return false;
     298              : 
     299            1 :         ld = tty_ldisc_ref(fx->tty);
     300            1 :         if (!ld)
     301            0 :                 return false;
     302              : 
     303            1 :         ops = READ_ONCE(ld->ops);
     304            1 :         if (ops && (ops->receive_buf || ops->receive_buf2)) {
     305            1 :                 tty_ldisc_deref(ld);
     306            1 :                 return true;
     307              :         }
     308              : 
     309            0 :         tty_ldisc_deref(ld);
     310            0 :         return false;
     311            1 : }
     312              : EXPORT_SYMBOL_IF_KUNIT(tty_fx_supports_rx);
     313              : 
     314              : /**
     315              :  * tty_test_assert_valid_ops - Validate driver has required operations
     316              :  */
     317            2 : void tty_test_assert_valid_ops(struct kunit *test,
     318              :                                const struct tty_driver *driver)
     319              : {
     320            2 :         KUNIT_ASSERT_NOT_NULL(test, driver);
     321            2 :         KUNIT_ASSERT_NOT_NULL(test, driver->ops);
     322            2 :         KUNIT_ASSERT_NOT_NULL(test, driver->ops->open);
     323            2 :         KUNIT_ASSERT_NOT_NULL(test, driver->ops->close);
     324            2 :         KUNIT_ASSERT_NOT_NULL(test, driver->ops->write);
     325            2 :         KUNIT_ASSERT_NOT_NULL(test, driver->ops->write_room);
     326            2 :         KUNIT_EXPECT_TRUE(test, driver->flags & TTY_DRIVER_INSTALLED);
     327            2 : }
     328              : EXPORT_SYMBOL_IF_KUNIT(tty_test_assert_valid_ops);
     329              : 
     330              : /**
     331              :  * tty_test_get_chars_in_buffer - Get number of chars in output buffer
     332              :  */
     333            1 : unsigned int tty_test_get_chars_in_buffer(struct tty_test_fixture *fx)
     334              : {
     335            1 :         KUNIT_ASSERT_NOT_NULL(fx->test, fx);
     336            1 :         KUNIT_ASSERT_TRUE(fx->test, fx->opened);
     337            1 :         KUNIT_ASSERT_NOT_NULL(fx->test, fx->tty);
     338              : 
     339            1 :         if (fx->tty->ops->chars_in_buffer)
     340            0 :                 return fx->tty->ops->chars_in_buffer(fx->tty);
     341              : 
     342            1 :         return 0;
     343            1 : }
     344              : EXPORT_SYMBOL_IF_KUNIT(tty_test_get_chars_in_buffer);
     345              : 
     346              : /**
     347              :  * tty_test_get_write_room - Get available write room
     348              :  */
     349            2 : unsigned int tty_test_get_write_room(struct tty_test_fixture *fx)
     350              : {
     351            2 :         KUNIT_ASSERT_NOT_NULL(fx->test, fx);
     352            2 :         KUNIT_ASSERT_TRUE(fx->test, fx->opened);
     353            2 :         KUNIT_ASSERT_NOT_NULL(fx->test, fx->tty);
     354              : 
     355            2 :         if (fx->tty->ops->write_room)
     356            2 :                 return fx->tty->ops->write_room(fx->tty);
     357              : 
     358            0 :         return 0;
     359            2 : }
     360              : EXPORT_SYMBOL_IF_KUNIT(tty_test_get_write_room);
     361              : 
     362              : /**
     363              :  * tty_test_set_termios - Set terminal attributes for testing
     364              :  */
     365            5 : int tty_test_set_termios(struct tty_test_fixture *fx,
     366              :                          const struct ktermios *termios)
     367              : {
     368            5 :         struct ktermios old_termios;
     369              : 
     370            5 :         KUNIT_ASSERT_NOT_NULL(fx->test, fx);
     371            5 :         KUNIT_ASSERT_TRUE(fx->test, fx->opened);
     372            5 :         KUNIT_ASSERT_NOT_NULL(fx->test, fx->tty);
     373            5 :         KUNIT_ASSERT_NOT_NULL(fx->test, termios);
     374              : 
     375              :         /* Save old termios for potential restoration */
     376            5 :         old_termios = fx->tty->termios;
     377              : 
     378              :         /* Update termios */
     379            5 :         fx->tty->termios = *termios;
     380              : 
     381              :         /* Call driver's set_termios if it exists */
     382            5 :         if (fx->tty->ops->set_termios)
     383            0 :                 fx->tty->ops->set_termios(fx->tty, &old_termios);
     384              : 
     385            5 :         return 0;
     386            5 : }
     387              : EXPORT_SYMBOL_IF_KUNIT(tty_test_set_termios);
        

Generated by: LCOV version 2.0-1