lists.openwall.net   lists  /  announce  owl-users  owl-dev  john-users  john-dev  passwdqc-users  yescrypt  popa3d-users  /  oss-security  kernel-hardening  musl  sabotage  tlsify  passwords  /  crypt-dev  xvendor  /  Bugtraq  Full-Disclosure  linux-kernel  linux-netdev  linux-ext4  linux-hardening  linux-cve-announce  PHC 
Open Source and information security mailing list archives
 
Hash Suite: Windows password security audit tool. GUI, reports in PDF.
[<prev] [next>] [<thread-prev] [thread-next>] [day] [month] [year] [list]
Message-ID: <89897c6344749de5cbf2b59a2a1ae24ff58154c1.1770128540.git.mchehab+huawei@kernel.org>
Date: Tue,  3 Feb 2026 15:55:37 +0100
From: Mauro Carvalho Chehab <mchehab+huawei@...nel.org>
To: Jonathan Corbet <corbet@....net>,
	Linux Doc Mailing List <linux-doc@...r.kernel.org>
Cc: Mauro Carvalho Chehab <mchehab+huawei@...nel.org>,
	linux-kernel@...r.kernel.org,
	Jani Nikula <jani.nikula@...ux.intel.com>,
	Mauro Carvalho Chehab <mchehab@...nel.org>
Subject: [PATCH 09/15] docs: unittests: add a parser to test kernel-doc parser logic

Validating that kernel-doc is parsing data properly is tricky.

Add an unittest skeleton that alllows passing a source code
and check if the corresponding values of export_table and
entries returned by the parser are properly filled.

It works by mocking a file input with the contents of a source
string, an comparing if:

	- exports set matches;
	- expected KernelItem entries match.

Create a new TestSelfValidate meant to check if the logic
inside KdocParser.run_test() does its job of checking for
differences inside KdocItem.

Signed-off-by: Mauro Carvalho Chehab <mchehab+huawei@...nel.org>
---
 tools/unittests/test_kdoc_parser.py | 198 ++++++++++++++++++++++++++++
 1 file changed, 198 insertions(+)
 create mode 100755 tools/unittests/test_kdoc_parser.py

diff --git a/tools/unittests/test_kdoc_parser.py b/tools/unittests/test_kdoc_parser.py
new file mode 100755
index 000000000000..f3ff750ac0e9
--- /dev/null
+++ b/tools/unittests/test_kdoc_parser.py
@@ -0,0 +1,198 @@
+#!/usr/bin/env python3
+# SPDX-License-Identifier: GPL-2.0
+# Copyright(c) 2026: Mauro Carvalho Chehab <mchehab@...nel.org>.
+#
+# pylint: disable=C0200,C0413,W0102,R0914
+
+"""
+Unit tests for kernel-doc parser.
+"""
+
+import os
+import unittest
+import re
+import sys
+
+from textwrap import dedent
+from unittest.mock import patch, MagicMock, mock_open
+
+SRC_DIR = os.path.dirname(os.path.realpath(__file__))
+sys.path.insert(0, os.path.join(SRC_DIR, "../lib/python"))
+
+from kdoc.kdoc_parser import KernelDoc
+from kdoc.kdoc_item import KdocItem
+from kdoc.xforms_lists import CTransforms
+from unittest_helper import run_unittest
+
+#: Regex to help cleaning whitespaces
+RE_WHITESPC = re.compile(r"\s++")
+
+def clean_whitespc(val, relax_whitespace=False):
+    """
+    Cleanup whitespaces to avoid false positives.
+
+    By default, strip only bein/end whitespaces, but, when relax_whitespace
+    is true, also replace multiple whitespaces in the middle.
+    """
+
+    if isinstance(val, str):
+        val = val.strip()
+        if relax_whitespace:
+            val = RE_WHITESPC.sub("", val)
+    elif isinstance(val, list):
+        val = [clean_whitespc(item, relax_whitespace) for item in val]
+    elif isinstance(val, dict):
+        val = {k: clean_whitespc(v, relax_whitespace) for k, v in val.items()}
+    return val
+
+#
+# Helper class to help mocking with
+#
+class KdocParser(unittest.TestCase):
+    """
+    Base class to run KernelDoc parser class
+    """
+
+    DEFAULT = vars(KdocItem("", "", "", 0))
+
+    def setUp(self):
+        self.maxDiff = None
+        self.config = MagicMock()
+        self.config.log = MagicMock()
+        self.config.log.debug = MagicMock()
+        self.xforms = CTransforms()
+
+
+    def run_test(self, source, __expected_list, exports={}, fname="test.c",
+                 relax_whitespace=False):
+        """
+        Stores expected values and patch the test to use source as
+        a "file" input.
+        """
+        debug_level = int(os.getenv("VERBOSE", "0"))
+        source = dedent(source)
+
+        # Ensure that default values will be there
+        expected_list = []
+        for e in __expected_list:
+            new_e = self.DEFAULT.copy()
+            new_e["fname"] = fname
+            for key, value in e.items():
+                new_e[key] = value
+
+            expected_list.append(new_e)
+
+        patcher = patch('builtins.open',
+                        new_callable=mock_open, read_data=source)
+
+        kernel_doc = KernelDoc(self.config, fname, self.xforms)
+
+        with patcher:
+            export_table, entries = kernel_doc.parse_kdoc()
+
+            self.assertEqual(export_table, exports)
+            self.assertEqual(len(entries), len(expected_list))
+
+            for i in range(0, len(entries)):
+
+                entry = entries[i]
+                expected = expected_list[i]
+                self.assertNotEqual(expected, None)
+                self.assertNotEqual(expected, {})
+                self.assertIsInstance(entry, KdocItem)
+
+                d = vars(entry)
+                for key, value in expected.items():
+                    result = clean_whitespc(d[key], relax_whitespace)
+                    value = clean_whitespc(value, relax_whitespace)
+
+                    if debug_level > 1:
+                        sys.stderr.write(f"{key}: assert('{result}' == '{value}')\n")
+
+                    self.assertEqual(result, value, msg=f"at {key}")
+
+
+#
+# Selttest class
+#
+class TestSelfValidate(KdocParser):
+    """
+    Tests to check if logic inside KdocParser.run_test() is working.
+    """
+
+    SOURCE = """
+        /**
+         * function3: Exported function
+         * @arg1: @arg1 does nothing
+         *
+         * Does nothing
+         *
+         * return:
+         *    always return 0.
+         */
+        int function3(char *arg1) { return 0; };
+        EXPORT_SYMBOL(function3);
+    """
+
+    EXPECTED = [{
+        'name': 'function3',
+        'type': 'function',
+        'declaration_start_line': 2,
+
+        'sections': {
+            'Description': 'Does nothing\n\n',
+            'Return': '\nalways return 0.\n'
+        },
+        'other_stuff': {
+            'func_macro': False,
+            'functiontype': 'int',
+            'purpose': 'Exported function',
+            'typedef': False
+        },
+        'parameterdescs': {'arg1': '@...1 does nothing\n'},
+        'parameterlist': ['arg1'],
+        'parameterdesc_start_lines': {'arg1': 3},
+        'parametertypes': {'arg1': 'char *arg1'},
+    }]
+
+    EXPORTS = {"function3"}
+
+    def test_parse_pass(self):
+        """
+        Test if export_symbol is properly handled.
+        """
+        self.run_test(self.SOURCE, self.EXPECTED, self.EXPORTS)
+
+    @unittest.expectedFailure
+    def test_no_exports(self):
+        """
+        Test if export_symbol is properly handled.
+        """
+        self.run_test(self.SOURCE, [], {})
+
+    @unittest.expectedFailure
+    def test_with_empty_expected(self):
+        """
+        Test if export_symbol is properly handled.
+        """
+        self.run_test(self.SOURCE, [], self.EXPORTS)
+
+    @unittest.expectedFailure
+    def test_with_unfilled_expected(self):
+        """
+        Test if export_symbol is properly handled.
+        """
+        self.run_test(self.SOURCE, [{}], self.EXPORTS)
+
+    @unittest.expectedFailure
+    def test_with_default_expected(self):
+        """
+        Test if export_symbol is properly handled.
+        """
+        self.run_test(self.SOURCE, [self.DEFAULT.copy()], self.EXPORTS)
+
+#
+# Run all tests
+#
+if __name__ == "__main__":
+    run_unittest(__file__)
-- 
2.52.0


Powered by blists - more mailing lists

Powered by Openwall GNU/*/Linux Powered by OpenVZ