xref: /linux/tools/unittests/test_kdoc_parser.py (revision 5181afcdf99527dd92a88f80fc4d0d8013e1b510)
1a5dea56eSMauro Carvalho Chehab#!/usr/bin/env python3
2a5dea56eSMauro Carvalho Chehab# SPDX-License-Identifier: GPL-2.0
3a5dea56eSMauro Carvalho Chehab# Copyright(c) 2026: Mauro Carvalho Chehab <mchehab@kernel.org>.
4a5dea56eSMauro Carvalho Chehab#
5a5dea56eSMauro Carvalho Chehab# pylint: disable=C0200,C0413,W0102,R0914
6a5dea56eSMauro Carvalho Chehab
7a5dea56eSMauro Carvalho Chehab"""
8a5dea56eSMauro Carvalho ChehabUnit tests for kernel-doc parser.
9a5dea56eSMauro Carvalho Chehab"""
10a5dea56eSMauro Carvalho Chehab
11eea0d807SMauro Carvalho Chehabimport logging
12a5dea56eSMauro Carvalho Chehabimport os
13a5dea56eSMauro Carvalho Chehabimport re
14eea0d807SMauro Carvalho Chehabimport shlex
15a5dea56eSMauro Carvalho Chehabimport sys
16eea0d807SMauro Carvalho Chehabimport unittest
17a5dea56eSMauro Carvalho Chehab
18a5dea56eSMauro Carvalho Chehabfrom textwrap import dedent
19a5dea56eSMauro Carvalho Chehabfrom unittest.mock import patch, MagicMock, mock_open
20a5dea56eSMauro Carvalho Chehab
21eea0d807SMauro Carvalho Chehabimport yaml
22eea0d807SMauro Carvalho Chehab
23a5dea56eSMauro Carvalho ChehabSRC_DIR = os.path.dirname(os.path.realpath(__file__))
24a5dea56eSMauro Carvalho Chehabsys.path.insert(0, os.path.join(SRC_DIR, "../lib/python"))
25a5dea56eSMauro Carvalho Chehab
26eea0d807SMauro Carvalho Chehabfrom kdoc.kdoc_files import KdocConfig
27a5dea56eSMauro Carvalho Chehabfrom kdoc.kdoc_item import KdocItem
28eea0d807SMauro Carvalho Chehabfrom kdoc.kdoc_parser import KernelDoc
29eea0d807SMauro Carvalho Chehabfrom kdoc.kdoc_output import RestFormat, ManFormat
30eea0d807SMauro Carvalho Chehab
31a5dea56eSMauro Carvalho Chehabfrom kdoc.xforms_lists import CTransforms
32eea0d807SMauro Carvalho Chehab
333f049a5bSMauro Carvalho Chehabfrom unittest_helper import TestUnits
34a5dea56eSMauro Carvalho Chehab
35eea0d807SMauro Carvalho Chehab
36eea0d807SMauro Carvalho Chehab#
37eea0d807SMauro Carvalho Chehab# Test file
38eea0d807SMauro Carvalho Chehab#
39eea0d807SMauro Carvalho ChehabTEST_FILE = os.path.join(SRC_DIR, "kdoc-test.yaml")
40eea0d807SMauro Carvalho Chehab
413f049a5bSMauro Carvalho Chehabenv = {
423f049a5bSMauro Carvalho Chehab    "yaml_file": TEST_FILE
433f049a5bSMauro Carvalho Chehab}
443f049a5bSMauro Carvalho Chehab
45eea0d807SMauro Carvalho Chehab#
46eea0d807SMauro Carvalho Chehab# Ancillary logic to clean whitespaces
47eea0d807SMauro Carvalho Chehab#
48a5dea56eSMauro Carvalho Chehab#: Regex to help cleaning whitespaces
49eea0d807SMauro Carvalho ChehabRE_WHITESPC = re.compile(r"[ \t]++")
50eea0d807SMauro Carvalho ChehabRE_BEGINSPC = re.compile(r"^\s+", re.MULTILINE)
51eea0d807SMauro Carvalho ChehabRE_ENDSPC = re.compile(r"\s+$", re.MULTILINE)
52a5dea56eSMauro Carvalho Chehab
53a5dea56eSMauro Carvalho Chehabdef clean_whitespc(val, relax_whitespace=False):
54a5dea56eSMauro Carvalho Chehab    """
55a5dea56eSMauro Carvalho Chehab    Cleanup whitespaces to avoid false positives.
56a5dea56eSMauro Carvalho Chehab
57a5dea56eSMauro Carvalho Chehab    By default, strip only bein/end whitespaces, but, when relax_whitespace
58a5dea56eSMauro Carvalho Chehab    is true, also replace multiple whitespaces in the middle.
59a5dea56eSMauro Carvalho Chehab    """
60a5dea56eSMauro Carvalho Chehab
61a5dea56eSMauro Carvalho Chehab    if isinstance(val, str):
62a5dea56eSMauro Carvalho Chehab        val = val.strip()
63a5dea56eSMauro Carvalho Chehab        if relax_whitespace:
64a5dea56eSMauro Carvalho Chehab            val = RE_WHITESPC.sub(" ", val)
65eea0d807SMauro Carvalho Chehab            val = RE_BEGINSPC.sub("", val)
66eea0d807SMauro Carvalho Chehab            val = RE_ENDSPC.sub("", val)
67a5dea56eSMauro Carvalho Chehab    elif isinstance(val, list):
68a5dea56eSMauro Carvalho Chehab        val = [clean_whitespc(item, relax_whitespace) for item in val]
69a5dea56eSMauro Carvalho Chehab    elif isinstance(val, dict):
70a5dea56eSMauro Carvalho Chehab        val = {k: clean_whitespc(v, relax_whitespace) for k, v in val.items()}
71a5dea56eSMauro Carvalho Chehab    return val
72a5dea56eSMauro Carvalho Chehab
73a5dea56eSMauro Carvalho Chehab#
74eea0d807SMauro Carvalho Chehab# Helper classes to help mocking with logger and config
75a5dea56eSMauro Carvalho Chehab#
76eea0d807SMauro Carvalho Chehabclass MockLogging(logging.Handler):
77eea0d807SMauro Carvalho Chehab    """
78eea0d807SMauro Carvalho Chehab    Simple class to store everything on a list
79eea0d807SMauro Carvalho Chehab    """
80eea0d807SMauro Carvalho Chehab
81eea0d807SMauro Carvalho Chehab    def __init__(self, level=logging.NOTSET):
82eea0d807SMauro Carvalho Chehab        super().__init__(level)
83eea0d807SMauro Carvalho Chehab        self.messages = []
84eea0d807SMauro Carvalho Chehab        self.formatter = logging.Formatter()
85eea0d807SMauro Carvalho Chehab
86eea0d807SMauro Carvalho Chehab    def emit(self, record: logging.LogRecord) -> None:
87eea0d807SMauro Carvalho Chehab        """
88eea0d807SMauro Carvalho Chehab        Append a formatted record to self.messages.
89eea0d807SMauro Carvalho Chehab        """
90eea0d807SMauro Carvalho Chehab        try:
91eea0d807SMauro Carvalho Chehab            # The `format` method uses the handler's formatter.
92eea0d807SMauro Carvalho Chehab            message = self.format(record)
93eea0d807SMauro Carvalho Chehab            self.messages.append(message)
94eea0d807SMauro Carvalho Chehab        except Exception:
95eea0d807SMauro Carvalho Chehab            self.handleError(record)
96eea0d807SMauro Carvalho Chehab
97eea0d807SMauro Carvalho Chehabclass MockKdocConfig(KdocConfig):
98eea0d807SMauro Carvalho Chehab    def __init__(self, *args, **kwargs):
99eea0d807SMauro Carvalho Chehab        super().__init__(*args, **kwargs)
100eea0d807SMauro Carvalho Chehab
101eea0d807SMauro Carvalho Chehab        self.log = logging.getLogger(__file__)
102eea0d807SMauro Carvalho Chehab        self.handler = MockLogging()
103eea0d807SMauro Carvalho Chehab        self.log.addHandler(self.handler)
104eea0d807SMauro Carvalho Chehab
105eea0d807SMauro Carvalho Chehab    def warning(self, msg):
106eea0d807SMauro Carvalho Chehab        """Ancillary routine to output a warning and increment error count."""
107eea0d807SMauro Carvalho Chehab
108eea0d807SMauro Carvalho Chehab        self.log.warning(msg)
109eea0d807SMauro Carvalho Chehab
110eea0d807SMauro Carvalho Chehab#
111eea0d807SMauro Carvalho Chehab# Helper class to generate KdocItem and validate its contents
112eea0d807SMauro Carvalho Chehab#
113eea0d807SMauro Carvalho Chehab# TODO: check self.config.handler.messages content
114eea0d807SMauro Carvalho Chehab#
115eea0d807SMauro Carvalho Chehabclass GenerateKdocItem(unittest.TestCase):
116a5dea56eSMauro Carvalho Chehab    """
117a5dea56eSMauro Carvalho Chehab    Base class to run KernelDoc parser class
118a5dea56eSMauro Carvalho Chehab    """
119a5dea56eSMauro Carvalho Chehab
120a5dea56eSMauro Carvalho Chehab    DEFAULT = vars(KdocItem("", "", "", 0))
121a5dea56eSMauro Carvalho Chehab
122eea0d807SMauro Carvalho Chehab    config = MockKdocConfig()
123eea0d807SMauro Carvalho Chehab    xforms = CTransforms()
124eea0d807SMauro Carvalho Chehab
125a5dea56eSMauro Carvalho Chehab    def setUp(self):
126a5dea56eSMauro Carvalho Chehab        self.maxDiff = None
127a5dea56eSMauro Carvalho Chehab
128a5dea56eSMauro Carvalho Chehab    def run_test(self, source, __expected_list, exports={}, fname="test.c",
129a5dea56eSMauro Carvalho Chehab                 relax_whitespace=False):
130a5dea56eSMauro Carvalho Chehab        """
131a5dea56eSMauro Carvalho Chehab        Stores expected values and patch the test to use source as
132a5dea56eSMauro Carvalho Chehab        a "file" input.
133a5dea56eSMauro Carvalho Chehab        """
134a5dea56eSMauro Carvalho Chehab        debug_level = int(os.getenv("VERBOSE", "0"))
135a5dea56eSMauro Carvalho Chehab        source = dedent(source)
136a5dea56eSMauro Carvalho Chehab
137a5dea56eSMauro Carvalho Chehab        # Ensure that default values will be there
138a5dea56eSMauro Carvalho Chehab        expected_list = []
139a5dea56eSMauro Carvalho Chehab        for e in __expected_list:
140eea0d807SMauro Carvalho Chehab            if not isinstance(e, dict):
141eea0d807SMauro Carvalho Chehab                e = vars(e)
142eea0d807SMauro Carvalho Chehab
143a5dea56eSMauro Carvalho Chehab            new_e = self.DEFAULT.copy()
144a5dea56eSMauro Carvalho Chehab            new_e["fname"] = fname
145a5dea56eSMauro Carvalho Chehab            for key, value in e.items():
146a5dea56eSMauro Carvalho Chehab                new_e[key] = value
147a5dea56eSMauro Carvalho Chehab
148a5dea56eSMauro Carvalho Chehab            expected_list.append(new_e)
149a5dea56eSMauro Carvalho Chehab
150a5dea56eSMauro Carvalho Chehab        patcher = patch('builtins.open',
151a5dea56eSMauro Carvalho Chehab                        new_callable=mock_open, read_data=source)
152a5dea56eSMauro Carvalho Chehab
153a5dea56eSMauro Carvalho Chehab        kernel_doc = KernelDoc(self.config, fname, self.xforms)
154a5dea56eSMauro Carvalho Chehab
155a5dea56eSMauro Carvalho Chehab        with patcher:
156a5dea56eSMauro Carvalho Chehab            export_table, entries = kernel_doc.parse_kdoc()
157a5dea56eSMauro Carvalho Chehab
158a5dea56eSMauro Carvalho Chehab            self.assertEqual(export_table, exports)
159a5dea56eSMauro Carvalho Chehab            self.assertEqual(len(entries), len(expected_list))
160a5dea56eSMauro Carvalho Chehab
161a5dea56eSMauro Carvalho Chehab            for i in range(0, len(entries)):
162a5dea56eSMauro Carvalho Chehab
163a5dea56eSMauro Carvalho Chehab                entry = entries[i]
164a5dea56eSMauro Carvalho Chehab                expected = expected_list[i]
165a5dea56eSMauro Carvalho Chehab                self.assertNotEqual(expected, None)
166a5dea56eSMauro Carvalho Chehab                self.assertNotEqual(expected, {})
167a5dea56eSMauro Carvalho Chehab                self.assertIsInstance(entry, KdocItem)
168a5dea56eSMauro Carvalho Chehab
169a5dea56eSMauro Carvalho Chehab                d = vars(entry)
170*99ec67a9SMauro Carvalho Chehab
171*99ec67a9SMauro Carvalho Chehab                other_stuff = d.get("other_stuff", {})
172*99ec67a9SMauro Carvalho Chehab                if "source" in other_stuff:
173*99ec67a9SMauro Carvalho Chehab                    del other_stuff["source"]
174*99ec67a9SMauro Carvalho Chehab
175a5dea56eSMauro Carvalho Chehab                for key, value in expected.items():
176*99ec67a9SMauro Carvalho Chehab                    if key == "other_stuff":
177*99ec67a9SMauro Carvalho Chehab                        if "source" in value:
178*99ec67a9SMauro Carvalho Chehab                            del value["source"]
179*99ec67a9SMauro Carvalho Chehab
180a5dea56eSMauro Carvalho Chehab                    result = clean_whitespc(d[key], relax_whitespace)
181a5dea56eSMauro Carvalho Chehab                    value = clean_whitespc(value, relax_whitespace)
182a5dea56eSMauro Carvalho Chehab
183a5dea56eSMauro Carvalho Chehab                    if debug_level > 1:
184a5dea56eSMauro Carvalho Chehab                        sys.stderr.write(f"{key}: assert('{result}' == '{value}')\n")
185a5dea56eSMauro Carvalho Chehab
186a5dea56eSMauro Carvalho Chehab                    self.assertEqual(result, value, msg=f"at {key}")
187a5dea56eSMauro Carvalho Chehab
188eea0d807SMauro Carvalho Chehab#
189eea0d807SMauro Carvalho Chehab# Ancillary function that replicates kdoc_files way to generate output
190eea0d807SMauro Carvalho Chehab#
191eea0d807SMauro Carvalho Chehabdef cleanup_timestamp(text):
192eea0d807SMauro Carvalho Chehab    lines = text.split("\n")
193eea0d807SMauro Carvalho Chehab
194eea0d807SMauro Carvalho Chehab    for i, line in enumerate(lines):
195eea0d807SMauro Carvalho Chehab        if not line.startswith('.TH'):
196eea0d807SMauro Carvalho Chehab            continue
197eea0d807SMauro Carvalho Chehab
198eea0d807SMauro Carvalho Chehab        parts = shlex.split(line)
199eea0d807SMauro Carvalho Chehab        if len(parts) > 3:
200eea0d807SMauro Carvalho Chehab            parts[3] = ""
201eea0d807SMauro Carvalho Chehab
202eea0d807SMauro Carvalho Chehab        lines[i] = " ".join(parts)
203eea0d807SMauro Carvalho Chehab
204eea0d807SMauro Carvalho Chehab
205eea0d807SMauro Carvalho Chehab    return "\n".join(lines)
206eea0d807SMauro Carvalho Chehab
207eea0d807SMauro Carvalho Chehabdef gen_output(fname, out_style, symbols, expected,
208eea0d807SMauro Carvalho Chehab               config=None, relax_whitespace=False):
209eea0d807SMauro Carvalho Chehab    """
210eea0d807SMauro Carvalho Chehab    Use the output class to return an output content from KdocItem symbols.
211eea0d807SMauro Carvalho Chehab    """
212eea0d807SMauro Carvalho Chehab
213eea0d807SMauro Carvalho Chehab    if not config:
214eea0d807SMauro Carvalho Chehab        config = MockKdocConfig()
215eea0d807SMauro Carvalho Chehab
216eea0d807SMauro Carvalho Chehab    out_style.set_config(config)
217eea0d807SMauro Carvalho Chehab
218eea0d807SMauro Carvalho Chehab    msg = out_style.output_symbols(fname, symbols)
219eea0d807SMauro Carvalho Chehab
220eea0d807SMauro Carvalho Chehab    result = clean_whitespc(msg, relax_whitespace)
221eea0d807SMauro Carvalho Chehab    result = cleanup_timestamp(result)
222eea0d807SMauro Carvalho Chehab
223eea0d807SMauro Carvalho Chehab    expected = clean_whitespc(expected, relax_whitespace)
224eea0d807SMauro Carvalho Chehab    expected = cleanup_timestamp(expected)
225eea0d807SMauro Carvalho Chehab
226eea0d807SMauro Carvalho Chehab    return result, expected
227a5dea56eSMauro Carvalho Chehab
228a5dea56eSMauro Carvalho Chehab#
229eea0d807SMauro Carvalho Chehab# Classes to be used by dynamic test generation from YAML
230a5dea56eSMauro Carvalho Chehab#
231eea0d807SMauro Carvalho Chehabclass CToKdocItem(GenerateKdocItem):
232eea0d807SMauro Carvalho Chehab    def setUp(self):
233eea0d807SMauro Carvalho Chehab        self.maxDiff = None
234eea0d807SMauro Carvalho Chehab
235eea0d807SMauro Carvalho Chehab    def run_parser_test(self, source, symbols, exports, fname):
236eea0d807SMauro Carvalho Chehab        if isinstance(symbols, dict):
237eea0d807SMauro Carvalho Chehab            symbols = [symbols]
238eea0d807SMauro Carvalho Chehab
239eea0d807SMauro Carvalho Chehab        if isinstance(exports, str):
240eea0d807SMauro Carvalho Chehab            exports=set([exports])
241eea0d807SMauro Carvalho Chehab        elif isinstance(exports, list):
242eea0d807SMauro Carvalho Chehab            exports=set(exports)
243eea0d807SMauro Carvalho Chehab
244eea0d807SMauro Carvalho Chehab        self.run_test(source, symbols, exports=exports,
245eea0d807SMauro Carvalho Chehab                      fname=fname, relax_whitespace=True)
246eea0d807SMauro Carvalho Chehab
247eea0d807SMauro Carvalho Chehabclass KdocItemToMan(unittest.TestCase):
248eea0d807SMauro Carvalho Chehab    out_style = ManFormat()
249eea0d807SMauro Carvalho Chehab
250eea0d807SMauro Carvalho Chehab    def setUp(self):
251eea0d807SMauro Carvalho Chehab        self.maxDiff = None
252eea0d807SMauro Carvalho Chehab
253eea0d807SMauro Carvalho Chehab    def run_out_test(self, fname, symbols, expected):
254a5dea56eSMauro Carvalho Chehab        """
255eea0d807SMauro Carvalho Chehab        Generate output using out_style,
256eea0d807SMauro Carvalho Chehab        """
257eea0d807SMauro Carvalho Chehab        result, expected = gen_output(fname, self.out_style,
258eea0d807SMauro Carvalho Chehab                                      symbols, expected)
259eea0d807SMauro Carvalho Chehab
260eea0d807SMauro Carvalho Chehab        self.assertEqual(result, expected)
261eea0d807SMauro Carvalho Chehab
262eea0d807SMauro Carvalho Chehabclass KdocItemToRest(unittest.TestCase):
263eea0d807SMauro Carvalho Chehab    out_style = RestFormat()
264eea0d807SMauro Carvalho Chehab
265eea0d807SMauro Carvalho Chehab    def setUp(self):
266eea0d807SMauro Carvalho Chehab        self.maxDiff = None
267eea0d807SMauro Carvalho Chehab
268eea0d807SMauro Carvalho Chehab    def run_out_test(self, fname, symbols, expected):
269eea0d807SMauro Carvalho Chehab        """
270eea0d807SMauro Carvalho Chehab        Generate output using out_style,
271eea0d807SMauro Carvalho Chehab        """
272eea0d807SMauro Carvalho Chehab        result, expected = gen_output(fname, self.out_style, symbols,
273eea0d807SMauro Carvalho Chehab                                      expected, relax_whitespace=True)
274eea0d807SMauro Carvalho Chehab
275eea0d807SMauro Carvalho Chehab        self.assertEqual(result, expected)
276eea0d807SMauro Carvalho Chehab
277eea0d807SMauro Carvalho Chehab
278eea0d807SMauro Carvalho Chehabclass CToMan(unittest.TestCase):
279eea0d807SMauro Carvalho Chehab    out_style = ManFormat()
280eea0d807SMauro Carvalho Chehab    config = MockKdocConfig()
281eea0d807SMauro Carvalho Chehab    xforms = CTransforms()
282eea0d807SMauro Carvalho Chehab
283eea0d807SMauro Carvalho Chehab    def setUp(self):
284eea0d807SMauro Carvalho Chehab        self.maxDiff = None
285eea0d807SMauro Carvalho Chehab
286eea0d807SMauro Carvalho Chehab    def run_out_test(self, fname, source, expected):
287eea0d807SMauro Carvalho Chehab        """
288eea0d807SMauro Carvalho Chehab        Generate output using out_style,
289eea0d807SMauro Carvalho Chehab        """
290eea0d807SMauro Carvalho Chehab        patcher = patch('builtins.open',
291eea0d807SMauro Carvalho Chehab                        new_callable=mock_open, read_data=source)
292eea0d807SMauro Carvalho Chehab
293eea0d807SMauro Carvalho Chehab        kernel_doc = KernelDoc(self.config, fname, self.xforms)
294eea0d807SMauro Carvalho Chehab
295eea0d807SMauro Carvalho Chehab        with patcher:
296eea0d807SMauro Carvalho Chehab            export_table, entries = kernel_doc.parse_kdoc()
297eea0d807SMauro Carvalho Chehab
298eea0d807SMauro Carvalho Chehab        result, expected = gen_output(fname, self.out_style,
299eea0d807SMauro Carvalho Chehab                                      entries, expected, config=self.config)
300eea0d807SMauro Carvalho Chehab
301eea0d807SMauro Carvalho Chehab        self.assertEqual(result, expected)
302eea0d807SMauro Carvalho Chehab
303eea0d807SMauro Carvalho Chehab
304eea0d807SMauro Carvalho Chehabclass CToRest(unittest.TestCase):
305eea0d807SMauro Carvalho Chehab    out_style = RestFormat()
306eea0d807SMauro Carvalho Chehab    config = MockKdocConfig()
307eea0d807SMauro Carvalho Chehab    xforms = CTransforms()
308eea0d807SMauro Carvalho Chehab
309eea0d807SMauro Carvalho Chehab    def setUp(self):
310eea0d807SMauro Carvalho Chehab        self.maxDiff = None
311eea0d807SMauro Carvalho Chehab
312eea0d807SMauro Carvalho Chehab    def run_out_test(self, fname, source, expected):
313eea0d807SMauro Carvalho Chehab        """
314eea0d807SMauro Carvalho Chehab        Generate output using out_style,
315eea0d807SMauro Carvalho Chehab        """
316eea0d807SMauro Carvalho Chehab        patcher = patch('builtins.open',
317eea0d807SMauro Carvalho Chehab                        new_callable=mock_open, read_data=source)
318eea0d807SMauro Carvalho Chehab
319eea0d807SMauro Carvalho Chehab        kernel_doc = KernelDoc(self.config, fname, self.xforms)
320eea0d807SMauro Carvalho Chehab
321eea0d807SMauro Carvalho Chehab        with patcher:
322eea0d807SMauro Carvalho Chehab            export_table, entries = kernel_doc.parse_kdoc()
323eea0d807SMauro Carvalho Chehab
324eea0d807SMauro Carvalho Chehab        result, expected = gen_output(fname, self.out_style, entries,
325eea0d807SMauro Carvalho Chehab                                      expected, relax_whitespace=True,
326eea0d807SMauro Carvalho Chehab                                      config=self.config)
327eea0d807SMauro Carvalho Chehab
328eea0d807SMauro Carvalho Chehab        self.assertEqual(result, expected)
329eea0d807SMauro Carvalho Chehab
330eea0d807SMauro Carvalho Chehab
331eea0d807SMauro Carvalho Chehab#
332eea0d807SMauro Carvalho Chehab# Selftest class
333eea0d807SMauro Carvalho Chehab#
334eea0d807SMauro Carvalho Chehabclass TestSelfValidate(GenerateKdocItem):
335eea0d807SMauro Carvalho Chehab    """
336eea0d807SMauro Carvalho Chehab    Tests to check if logic inside GenerateKdocItem.run_test() is working.
337a5dea56eSMauro Carvalho Chehab    """
338a5dea56eSMauro Carvalho Chehab
339a5dea56eSMauro Carvalho Chehab    SOURCE = """
340a5dea56eSMauro Carvalho Chehab        /**
341a5dea56eSMauro Carvalho Chehab         * function3: Exported function
342a5dea56eSMauro Carvalho Chehab         * @arg1: @arg1 does nothing
343a5dea56eSMauro Carvalho Chehab         *
344a5dea56eSMauro Carvalho Chehab         * Does nothing
345a5dea56eSMauro Carvalho Chehab         *
346a5dea56eSMauro Carvalho Chehab         * return:
347a5dea56eSMauro Carvalho Chehab         *    always return 0.
348a5dea56eSMauro Carvalho Chehab         */
349a5dea56eSMauro Carvalho Chehab        int function3(char *arg1) { return 0; };
350a5dea56eSMauro Carvalho Chehab        EXPORT_SYMBOL(function3);
351a5dea56eSMauro Carvalho Chehab    """
352a5dea56eSMauro Carvalho Chehab
353a5dea56eSMauro Carvalho Chehab    EXPECTED = [{
354a5dea56eSMauro Carvalho Chehab        'name': 'function3',
355a5dea56eSMauro Carvalho Chehab        'type': 'function',
356a5dea56eSMauro Carvalho Chehab        'declaration_start_line': 2,
357a5dea56eSMauro Carvalho Chehab
358a5dea56eSMauro Carvalho Chehab        'sections_start_lines': {
359a5dea56eSMauro Carvalho Chehab            'Description': 4,
360a5dea56eSMauro Carvalho Chehab            'Return': 7,
361a5dea56eSMauro Carvalho Chehab        },
362a5dea56eSMauro Carvalho Chehab        'sections': {
363a5dea56eSMauro Carvalho Chehab            'Description': 'Does nothing\n\n',
364a5dea56eSMauro Carvalho Chehab            'Return': '\nalways return 0.\n'
365a5dea56eSMauro Carvalho Chehab        },
366eea0d807SMauro Carvalho Chehab
367eea0d807SMauro Carvalho Chehab        'sections_start_lines': {
368eea0d807SMauro Carvalho Chehab            'Description': 4,
369eea0d807SMauro Carvalho Chehab            'Return': 7,
370eea0d807SMauro Carvalho Chehab        },
371eea0d807SMauro Carvalho Chehab
372eea0d807SMauro Carvalho Chehab        'parameterdescs': {'arg1': '@arg1 does nothing\n'},
373eea0d807SMauro Carvalho Chehab        'parameterlist': ['arg1'],
374eea0d807SMauro Carvalho Chehab        'parameterdesc_start_lines': {'arg1': 3},
375eea0d807SMauro Carvalho Chehab        'parametertypes': {'arg1': 'char *arg1'},
376eea0d807SMauro Carvalho Chehab
377a5dea56eSMauro Carvalho Chehab        'other_stuff': {
378a5dea56eSMauro Carvalho Chehab            'func_macro': False,
379a5dea56eSMauro Carvalho Chehab            'functiontype': 'int',
380a5dea56eSMauro Carvalho Chehab            'purpose': 'Exported function',
381a5dea56eSMauro Carvalho Chehab            'typedef': False
382a5dea56eSMauro Carvalho Chehab        },
383a5dea56eSMauro Carvalho Chehab    }]
384a5dea56eSMauro Carvalho Chehab
385a5dea56eSMauro Carvalho Chehab    EXPORTS = {"function3"}
386a5dea56eSMauro Carvalho Chehab
387a5dea56eSMauro Carvalho Chehab    def test_parse_pass(self):
388a5dea56eSMauro Carvalho Chehab        """
389a5dea56eSMauro Carvalho Chehab        Test if export_symbol is properly handled.
390a5dea56eSMauro Carvalho Chehab        """
391a5dea56eSMauro Carvalho Chehab        self.run_test(self.SOURCE, self.EXPECTED, self.EXPORTS)
392a5dea56eSMauro Carvalho Chehab
393a5dea56eSMauro Carvalho Chehab    @unittest.expectedFailure
394a5dea56eSMauro Carvalho Chehab    def test_no_exports(self):
395a5dea56eSMauro Carvalho Chehab        """
396a5dea56eSMauro Carvalho Chehab        Test if export_symbol is properly handled.
397a5dea56eSMauro Carvalho Chehab        """
398a5dea56eSMauro Carvalho Chehab        self.run_test(self.SOURCE, [], {})
399a5dea56eSMauro Carvalho Chehab
400a5dea56eSMauro Carvalho Chehab    @unittest.expectedFailure
401a5dea56eSMauro Carvalho Chehab    def test_with_empty_expected(self):
402a5dea56eSMauro Carvalho Chehab        """
403a5dea56eSMauro Carvalho Chehab        Test if export_symbol is properly handled.
404a5dea56eSMauro Carvalho Chehab        """
405a5dea56eSMauro Carvalho Chehab        self.run_test(self.SOURCE, [], self.EXPORTS)
406a5dea56eSMauro Carvalho Chehab
407a5dea56eSMauro Carvalho Chehab    @unittest.expectedFailure
408a5dea56eSMauro Carvalho Chehab    def test_with_unfilled_expected(self):
409a5dea56eSMauro Carvalho Chehab        """
410a5dea56eSMauro Carvalho Chehab        Test if export_symbol is properly handled.
411a5dea56eSMauro Carvalho Chehab        """
412a5dea56eSMauro Carvalho Chehab        self.run_test(self.SOURCE, [{}], self.EXPORTS)
413a5dea56eSMauro Carvalho Chehab
414a5dea56eSMauro Carvalho Chehab    @unittest.expectedFailure
415a5dea56eSMauro Carvalho Chehab    def test_with_default_expected(self):
416a5dea56eSMauro Carvalho Chehab        """
417a5dea56eSMauro Carvalho Chehab        Test if export_symbol is properly handled.
418a5dea56eSMauro Carvalho Chehab        """
419a5dea56eSMauro Carvalho Chehab        self.run_test(self.SOURCE, [self.DEFAULT.copy()], self.EXPORTS)
420a5dea56eSMauro Carvalho Chehab
421a5dea56eSMauro Carvalho Chehab#
422eea0d807SMauro Carvalho Chehab# Class and logic to create dynamic tests from YAML
423eea0d807SMauro Carvalho Chehab#
424eea0d807SMauro Carvalho Chehab
425eea0d807SMauro Carvalho Chehabclass KernelDocDynamicTests():
426eea0d807SMauro Carvalho Chehab    """
427eea0d807SMauro Carvalho Chehab    Dynamically create a set of tests from a YAML file.
428eea0d807SMauro Carvalho Chehab    """
429eea0d807SMauro Carvalho Chehab
430eea0d807SMauro Carvalho Chehab    @classmethod
431eea0d807SMauro Carvalho Chehab    def create_parser_test(cls, name, fname, source, symbols, exports):
432eea0d807SMauro Carvalho Chehab        """
433eea0d807SMauro Carvalho Chehab        Return a function that will be attached to the test class.
434eea0d807SMauro Carvalho Chehab        """
435eea0d807SMauro Carvalho Chehab        def test_method(self):
436eea0d807SMauro Carvalho Chehab            """Lambda-like function to run tests with provided vars"""
437eea0d807SMauro Carvalho Chehab            self.run_parser_test(source, symbols, exports, fname)
438eea0d807SMauro Carvalho Chehab
439eea0d807SMauro Carvalho Chehab        test_method.__name__ = f"test_gen_{name}"
440eea0d807SMauro Carvalho Chehab
441eea0d807SMauro Carvalho Chehab        setattr(CToKdocItem, test_method.__name__, test_method)
442eea0d807SMauro Carvalho Chehab
443eea0d807SMauro Carvalho Chehab    @classmethod
444eea0d807SMauro Carvalho Chehab    def create_out_test(cls, name, fname, symbols, out_type, data):
445eea0d807SMauro Carvalho Chehab        """
446eea0d807SMauro Carvalho Chehab        Return a function that will be attached to the test class.
447eea0d807SMauro Carvalho Chehab        """
448eea0d807SMauro Carvalho Chehab        def test_method(self):
449eea0d807SMauro Carvalho Chehab            """Lambda-like function to run tests with provided vars"""
450eea0d807SMauro Carvalho Chehab            self.run_out_test(fname, symbols, data)
451eea0d807SMauro Carvalho Chehab
452eea0d807SMauro Carvalho Chehab        test_method.__name__ = f"test_{out_type}_{name}"
453eea0d807SMauro Carvalho Chehab
454eea0d807SMauro Carvalho Chehab        if out_type == "man":
455eea0d807SMauro Carvalho Chehab            setattr(KdocItemToMan, test_method.__name__, test_method)
456eea0d807SMauro Carvalho Chehab        else:
457eea0d807SMauro Carvalho Chehab            setattr(KdocItemToRest, test_method.__name__, test_method)
458eea0d807SMauro Carvalho Chehab
459eea0d807SMauro Carvalho Chehab    @classmethod
460eea0d807SMauro Carvalho Chehab    def create_src2out_test(cls, name, fname, source, out_type, data):
461eea0d807SMauro Carvalho Chehab        """
462eea0d807SMauro Carvalho Chehab        Return a function that will be attached to the test class.
463eea0d807SMauro Carvalho Chehab        """
464eea0d807SMauro Carvalho Chehab        def test_method(self):
465eea0d807SMauro Carvalho Chehab            """Lambda-like function to run tests with provided vars"""
466eea0d807SMauro Carvalho Chehab            self.run_out_test(fname, source,  data)
467eea0d807SMauro Carvalho Chehab
468eea0d807SMauro Carvalho Chehab        test_method.__name__ = f"test_{out_type}_{name}"
469eea0d807SMauro Carvalho Chehab
470eea0d807SMauro Carvalho Chehab        if out_type == "man":
471eea0d807SMauro Carvalho Chehab            setattr(CToMan, test_method.__name__, test_method)
472eea0d807SMauro Carvalho Chehab        else:
473eea0d807SMauro Carvalho Chehab            setattr(CToRest, test_method.__name__, test_method)
474eea0d807SMauro Carvalho Chehab
475eea0d807SMauro Carvalho Chehab    @classmethod
476eea0d807SMauro Carvalho Chehab    def create_tests(cls):
477eea0d807SMauro Carvalho Chehab        """
478eea0d807SMauro Carvalho Chehab        Iterate over all scenarios and add a method to the class for each.
479eea0d807SMauro Carvalho Chehab
480eea0d807SMauro Carvalho Chehab        The logic in this function assumes a valid test that are compliant
481eea0d807SMauro Carvalho Chehab        with kdoc-test-schema.yaml. There is an unit test to check that.
482eea0d807SMauro Carvalho Chehab        As such, it picks mandatory values directly, and uses get() for the
483eea0d807SMauro Carvalho Chehab        optional ones.
484eea0d807SMauro Carvalho Chehab        """
485eea0d807SMauro Carvalho Chehab
4863f049a5bSMauro Carvalho Chehab        test_file = os.environ.get("yaml_file", TEST_FILE)
4873f049a5bSMauro Carvalho Chehab
4883f049a5bSMauro Carvalho Chehab        with open(test_file, encoding="utf-8") as fp:
489eea0d807SMauro Carvalho Chehab            testset = yaml.safe_load(fp)
490eea0d807SMauro Carvalho Chehab
491eea0d807SMauro Carvalho Chehab        tests = testset["tests"]
492eea0d807SMauro Carvalho Chehab
493eea0d807SMauro Carvalho Chehab        for idx, test in enumerate(tests):
494eea0d807SMauro Carvalho Chehab            name = test["name"]
495eea0d807SMauro Carvalho Chehab            fname = test["fname"]
496eea0d807SMauro Carvalho Chehab            source = test["source"]
497eea0d807SMauro Carvalho Chehab            expected_list = test["expected"]
498eea0d807SMauro Carvalho Chehab
499eea0d807SMauro Carvalho Chehab            exports = test.get("exports", [])
500eea0d807SMauro Carvalho Chehab
501eea0d807SMauro Carvalho Chehab            #
502eea0d807SMauro Carvalho Chehab            # The logic below allows setting up to 5 types of test:
503eea0d807SMauro Carvalho Chehab            # 1. from source to kdoc_item: test KernelDoc class;
504eea0d807SMauro Carvalho Chehab            # 2. from kdoc_item to man: test ManOutput class;
505eea0d807SMauro Carvalho Chehab            # 3. from kdoc_item to rst: test RestOutput class;
506eea0d807SMauro Carvalho Chehab            # 4. from source to man without checking expected KdocItem;
507eea0d807SMauro Carvalho Chehab            # 5. from source to rst without checking expected KdocItem.
508eea0d807SMauro Carvalho Chehab            #
509eea0d807SMauro Carvalho Chehab            for expected in expected_list:
510eea0d807SMauro Carvalho Chehab                kdoc_item = expected.get("kdoc_item")
511eea0d807SMauro Carvalho Chehab                man = expected.get("man", [])
512eea0d807SMauro Carvalho Chehab                rst = expected.get("rst", [])
513eea0d807SMauro Carvalho Chehab
514eea0d807SMauro Carvalho Chehab                if kdoc_item:
515eea0d807SMauro Carvalho Chehab                    if isinstance(kdoc_item, dict):
516eea0d807SMauro Carvalho Chehab                        kdoc_item = [kdoc_item]
517eea0d807SMauro Carvalho Chehab
518eea0d807SMauro Carvalho Chehab                    symbols = []
519eea0d807SMauro Carvalho Chehab
520eea0d807SMauro Carvalho Chehab                    for arg in kdoc_item:
521eea0d807SMauro Carvalho Chehab                        arg["fname"] = fname
522eea0d807SMauro Carvalho Chehab                        arg["start_line"] = 1
523eea0d807SMauro Carvalho Chehab
524eea0d807SMauro Carvalho Chehab                        symbols.append(KdocItem.from_dict(arg))
525eea0d807SMauro Carvalho Chehab
526eea0d807SMauro Carvalho Chehab                    if source:
527eea0d807SMauro Carvalho Chehab                        cls.create_parser_test(name, fname, source,
528eea0d807SMauro Carvalho Chehab                                               symbols, exports)
529eea0d807SMauro Carvalho Chehab
530eea0d807SMauro Carvalho Chehab                    if man:
531eea0d807SMauro Carvalho Chehab                        cls.create_out_test(name, fname, symbols, "man", man)
532eea0d807SMauro Carvalho Chehab
533eea0d807SMauro Carvalho Chehab                    if rst:
534eea0d807SMauro Carvalho Chehab                        cls.create_out_test(name, fname, symbols, "rst", rst)
535eea0d807SMauro Carvalho Chehab
536eea0d807SMauro Carvalho Chehab                elif source:
537eea0d807SMauro Carvalho Chehab                    if man:
538eea0d807SMauro Carvalho Chehab                        cls.create_src2out_test(name, fname, source, "man", man)
539eea0d807SMauro Carvalho Chehab
540eea0d807SMauro Carvalho Chehab                    if rst:
541eea0d807SMauro Carvalho Chehab                        cls.create_src2out_test(name, fname, source, "rst", rst)
542eea0d807SMauro Carvalho Chehab
543eea0d807SMauro Carvalho ChehabKernelDocDynamicTests.create_tests()
544eea0d807SMauro Carvalho Chehab
545eea0d807SMauro Carvalho Chehab#
546a5dea56eSMauro Carvalho Chehab# Run all tests
547a5dea56eSMauro Carvalho Chehab#
548a5dea56eSMauro Carvalho Chehabif __name__ == "__main__":
5493f049a5bSMauro Carvalho Chehab    runner = TestUnits()
5503f049a5bSMauro Carvalho Chehab    parser = runner.parse_args()
5513f049a5bSMauro Carvalho Chehab    parser.add_argument("-y", "--yaml-file", "--yaml",
5523f049a5bSMauro Carvalho Chehab                        help='Name of the yaml file to load')
5533f049a5bSMauro Carvalho Chehab
5543f049a5bSMauro Carvalho Chehab    args = parser.parse_args()
5553f049a5bSMauro Carvalho Chehab
5563f049a5bSMauro Carvalho Chehab    if args.yaml_file:
5573f049a5bSMauro Carvalho Chehab        env["yaml_file"] = os.path.expanduser(args.yaml_file)
5583f049a5bSMauro Carvalho Chehab
5593f049a5bSMauro Carvalho Chehab    # Run tests with customized arguments
5603f049a5bSMauro Carvalho Chehab    runner.run(__file__, parser=parser, args=args, env=env)
561