summaryrefslogtreecommitdiff
path: root/tools/unittests
diff options
context:
space:
mode:
Diffstat (limited to 'tools/unittests')
-rw-r--r--tools/unittests/kdoc-test-schema.yaml156
-rw-r--r--tools/unittests/kdoc-test.yaml1698
-rwxr-xr-xtools/unittests/run.py17
-rwxr-xr-xtools/unittests/test_cmatch.py821
-rwxr-xr-xtools/unittests/test_kdoc_parser.py560
-rwxr-xr-xtools/unittests/test_kdoc_test_schema.py94
-rwxr-xr-xtools/unittests/test_tokenizer.py469
7 files changed, 3815 insertions, 0 deletions
diff --git a/tools/unittests/kdoc-test-schema.yaml b/tools/unittests/kdoc-test-schema.yaml
new file mode 100644
index 000000000000..cf5079711cd8
--- /dev/null
+++ b/tools/unittests/kdoc-test-schema.yaml
@@ -0,0 +1,156 @@
+# SPDX-License-Identifier: GPL-2.0
+# Copyright(c) 2026: Mauro Carvalho Chehab <mchehab@kernel.org>.
+
+# KDoc Test File Schema
+
+# This schema contains objects and properties needed to run kernel-doc
+# self-tests.
+
+$schema: "http://json-schema.org/draft-07/schema#"
+
+tests:
+ type: array
+ minItems: 1
+ description: |
+ A list of kernel-doc tests.
+
+ properties:
+ type: object
+ properties:
+ name:
+ type: string
+ description: |
+ Test name. Should be an unique identifier within the schema.
+ Don't prepend it with "test", as the dynamic test creation will
+ do it.
+
+ description:
+ type: string
+ description: |
+ Test description
+
+ source:
+ type: string
+ description: |
+ C source code that should be parsed by kernel-doc.
+
+ fname:
+ type: string
+ description: |
+ The filename that contains the element.
+ When placing real testcases, please use here the name of
+ the C file (or header) from where the source code was picked.
+
+ exports:
+ type: array
+ items: { type: string }
+ description: |
+ A list of export identifiers that are expected when parsing source.
+
+ expected:
+ type: array
+ minItems: 1
+ description: |
+ A list of expected values. This list consists on objects to check
+ both kdoc_parser and/or kdoc_output objects.
+
+ items:
+ type: object
+ properties:
+ #
+ # kdoc_item
+ #
+ kdoc_item:
+ type: object
+ description: |
+ Object expected to represent the C source code after parsed
+ by tools/lib/python/kdoc/kdoc_parser.py KernelDoc class.
+ See tools/lib/python/kdoc/kdoc_item.py for its contents.
+
+ properties:
+ name:
+ type: string
+ description: |
+ The name of the identifier (function name, struct name, etc).
+ type:
+ type: string
+ description: |
+ Type of the object, as filled by kdoc_parser. can be:
+ - enum
+ - typedef
+ - union
+ - struct
+ - var
+ - function
+ declaration_start_line:
+ type: integer
+ description: |
+ The line number where the kernel-doc markup started.
+ The first line of the code is line number 1.
+ sections:
+ type: object
+ additionalProperties: { type: string }
+ description: |
+ Sections inside the kernel-doc markups:
+ - "description"
+ - "return"
+ - any other part of the markup that starts with "something:"
+ sections_start_lines:
+ type: object
+ additionalProperties: { type: integer }
+ description: |
+ a list of section names and the starting line of it.
+ parameterlist:
+ type: array
+ items: { type: string }
+ description: |
+ Ordered list of parameter names.
+
+ parameterdesc_start_lines:
+ type: object
+ additionalProperties: { type: integer }
+ description: |
+ Mapping from parameter name to the line where its
+ description starts.
+ parameterdescs:
+ type: object
+ additionalProperties: { type: string }
+ description: |
+ Mapping from parameter name to its description.
+
+ parametertypes:
+ type: object
+ additionalProperties: { type: string }
+ description: |
+ Mapping from parameter name to its type.
+
+ other_stuff:
+ type: object
+ additionalProperties: {}
+ description: |
+ Extra properties that will be stored at the item.
+ Should match what kdoc_output expects.
+
+ required:
+ - name
+ - type
+ - declaration_start_line
+
+ rst:
+ type: string
+ description: |
+ The expected output for RestOutput class.
+
+ man:
+ type: string
+ description: |
+ The expected output for ManOutput class.
+
+ anyOf:
+ required: kdoc_item
+ required: source
+
+ required:
+ - name
+ - fname
+ - expected
diff --git a/tools/unittests/kdoc-test.yaml b/tools/unittests/kdoc-test.yaml
new file mode 100644
index 000000000000..14d36daa1bba
--- /dev/null
+++ b/tools/unittests/kdoc-test.yaml
@@ -0,0 +1,1698 @@
+# SPDX-License-Identifier: GPL-2.0
+# Copyright (c) 2026: Mauro Carvalho Chehab <mchehab@kernel.org>
+
+# Test cases for the dynamic tests.
+# Useful to test if kernel-doc classes are doing what it is expected.
+#
+
+tests:
+- name: func1
+ fname: mock_functions.c
+ description: "Simplest function test: do nothing, just rst output"
+
+ source: |
+ /**
+ * func1 - Not exported function
+ * @arg1: @arg1 does nothing
+ *
+ * Does nothing
+ *
+ * return:
+ * always return 0.
+ */
+ int func1(char *arg1) { return 0; };
+
+ expected:
+ - rst: |
+ .. c:function:: int func1 (char *arg1)
+
+ Not exported function
+
+ .. container:: kernelindent
+
+ **Parameters**
+
+ ``char *arg1``
+ **arg1** does nothing
+
+ **Description**
+
+ Does nothing
+
+ **Return**
+
+ always return 0.
+
+ # TODO: how to handle timestamps at .TH?
+ man: |
+ .TH "func1" 9 "February 2026" "" "Kernel API Manual"
+ .SH NAME
+ func1 \- Not exported function
+ .SH SYNOPSIS
+ .B "int" func1
+ .BI "(char *arg1 " ");"
+ .SH ARGUMENTS
+ .IP "arg1" 12
+ \fIarg1\fP does nothing
+ .SH "DESCRIPTION"
+ Does nothing
+ .SH "RETURN"
+ always return 0.
+ .SH "SEE ALSO"
+ .PP
+ Kernel file \fBmock_functions.c\fR
+
+- name: func2
+ fname: func2.c
+ description: Simple test with exports
+
+ source: |
+ /**
+ * func2() - Exported function
+ * @arg1: @arg1 does nothing
+ *
+ * Does nothing
+ *
+ * return:
+ * always return 0.
+ */
+ int func2(char *arg1) { return 0; };
+ EXPORT_SYMBOL(func2);
+
+ exports: func2
+ expected:
+ - kdoc_item:
+ name: func2
+ type: function
+ declaration_start_line: 1
+
+ sections:
+ Description: |
+ Does nothing
+
+ Return: |
+ always return 0.
+
+ sections_start_lines:
+ Description: 3
+ Return: 6
+
+ parameterdescs:
+ arg1: |
+ @arg1 does nothing
+ parameterlist:
+ - arg1
+ parameterdesc_start_lines:
+ arg1: 2
+ parametertypes:
+ arg1: char *arg1
+
+ other_stuff:
+ func_macro: false
+ functiontype: int
+ purpose: "Exported function"
+ typedef: false
+
+ rst: |
+ .. c:function:: int func2 (char *arg1)
+
+ Exported function
+
+ .. container:: kernelindent
+
+ **Parameters**
+
+ ``char *arg1``
+ **arg1** does nothing
+
+ **Description**
+
+ Does nothing
+
+ **Return**
+
+ always return 0.
+
+ man: |
+ .TH "func2" 9 "February 2026" "" "Kernel API Manual"
+ .SH NAME
+ func2 \- Exported function
+ .SH SYNOPSIS
+ .B "int" func2
+ .BI "(char *arg1 " ");"
+ .SH ARGUMENTS
+ .IP "arg1" 12
+ \fIarg1\fP does nothing
+ .SH "DESCRIPTION"
+ Does nothing
+ .SH "RETURN"
+ always return 0.
+ .SH "SEE ALSO"
+ .PP
+ Kernel file \fBfunc2.c\fR
+
+- name: doc_with_complex_table
+ description: Test if complex tables are handled
+ fname: mock.c
+ source: |
+ /**
+ * DOC: Supported input formats and encodings
+ *
+ * Depending on the Hardware configuration of the Controller IP, it supports
+ * a subset of the following input formats and encodings on its internal
+ * 48bit bus.
+ *
+ * +----------------------+----------------------------------+------------------------------+
+ * | Format Name | Format Code | Encodings |
+ * +======================+==================================+==============================+
+ * | RGB 4:4:4 8bit | ``MEDIA_BUS_FMT_RGB888_1X24`` | ``V4L2_YCBCR_ENC_DEFAULT`` |
+ * +----------------------+----------------------------------+------------------------------+
+ * | RGB 4:4:4 10bits | ``MEDIA_BUS_FMT_RGB101010_1X30`` | ``V4L2_YCBCR_ENC_DEFAULT`` |
+ * +----------------------+----------------------------------+------------------------------+
+ */
+ expected:
+ - man: |
+ .TH "Supported input formats and encodings" 9 "March 2026" "" "Kernel API Manual"
+ .SH "Supported input formats and encodings"
+ Depending on the Hardware configuration of the Controller IP, it supports
+ a subset of the following input formats and encodings on its internal
+ 48bit bus.
+ .PP
+
+
+ .TS
+ box;
+ l l l.
+ \fBFormat Name\fP \fBFormat Code\fP \fBEncodings\fP
+ _
+ RGB 4:4:4 8bit ``MEDIA_BUS_FMT_RGB888_1X24 V4L2_YCBCR_ENC_DEFAULT
+ RGB 4:4:4 10bits MEDIA_BUS_FMT_RGB101010_1X30 V4L2_YCBCR_ENC_DEFAULT``
+ .TE
+ .SH "SEE ALSO"
+ .PP
+ Kernel file \fBmock.c\fR
+
+ rst: |-
+ .. _Supported input formats and encodings:
+ **Supported input formats and encodings**
+ Depending on the Hardware configuration of the Controller IP, it supports
+ a subset of the following input formats and encodings on its internal
+ 48bit bus.
+ +----------------------+----------------------------------+------------------------------+
+ | Format Name | Format Code | Encodings |
+ +======================+==================================+==============================+
+ | RGB 4:4:4 8bit | ``MEDIA_BUS_FMT_RGB888_1X24`` | ``V4L2_YCBCR_ENC_DEFAULT`` |
+ +----------------------+----------------------------------+------------------------------+
+ | RGB 4:4:4 10bits | ``MEDIA_BUS_FMT_RGB101010_1X30`` | ``V4L2_YCBCR_ENC_DEFAULT`` |
+ +----------------------+----------------------------------+------------------------------+
+- name: func_with_ascii_artwork
+ description: Test if ascii artwork is properly output
+ fname: mock.c
+ source: |
+ /**
+ * add_cxl_resources() - reflect CXL fixed memory windows in iomem_resource
+ * @cxl_res: A standalone resource tree where each CXL window is a sibling
+ *
+ * Walk each CXL window in @cxl_res and add it to iomem_resource potentially
+ * expanding its boundaries to ensure that any conflicting resources become
+ * children. If a window is expanded it may then conflict with a another window
+ * entry and require the window to be truncated or trimmed. Consider this
+ * situation::
+ *
+ * |-- "CXL Window 0" --||----- "CXL Window 1" -----|
+ * |--------------- "System RAM" -------------|
+ *
+ * ...where platform firmware has established as System RAM resource across 2
+ * windows, but has left some portion of window 1 for dynamic CXL region
+ * provisioning. In this case "Window 0" will span the entirety of the "System
+ * RAM" span, and "CXL Window 1" is truncated to the remaining tail past the end
+ * of that "System RAM" resource.
+ */
+ static int add_cxl_resources(struct resource *cxl_res);
+ expected:
+ - man: |-
+ .TH "add_cxl_resources" 9 "March 2026" "" "Kernel API Manual"
+ .SH NAME
+ add_cxl_resources \- reflect CXL fixed memory windows in iomem_resource
+ .SH SYNOPSIS
+ .B "int" add_cxl_resources
+ .BI "(struct resource *cxl_res " ");"
+ .SH ARGUMENTS
+ .IP "cxl_res" 12
+ A standalone resource tree where each CXL window is a sibling
+ .SH "DESCRIPTION"
+ Walk each CXL window in \fIcxl_res\fP and add it to iomem_resource potentially
+ expanding its boundaries to ensure that any conflicting resources become
+ children. If a window is expanded it may then conflict with a another window
+ entry and require the window to be truncated or trimmed. Consider this
+ situation:
+ .nf
+
+ |-- "CXL Window 0" --||----- "CXL Window 1" -----|
+ |--------------- "System RAM" -------------|
+
+
+ .fi
+ .PP
+
+ \&...where platform firmware has established as System RAM resource across 2
+ windows, but has left some portion of window 1 for dynamic CXL region
+ provisioning. In this case "Window 0" will span the entirety of the "System
+ RAM" span, and "CXL Window 1" is truncated to the remaining tail past the end
+ of that "System RAM" resource.
+ .SH "SEE ALSO"
+ .PP
+ Kernel file \fBmock.c\fR
+ rst: |
+ .. c:function:: int add_cxl_resources (struct resource *cxl_res)
+
+ reflect CXL fixed memory windows in iomem_resource
+
+ .. container:: kernelindent
+
+ **Parameters**
+
+ ``struct resource *cxl_res``
+ A standalone resource tree where each CXL window is a sibling
+
+ **Description**
+
+ Walk each CXL window in **cxl_res** and add it to iomem_resource potentially
+ expanding its boundaries to ensure that any conflicting resources become
+ children. If a window is expanded it may then conflict with a another window
+ entry and require the window to be truncated or trimmed. Consider this
+ situation::
+
+ |-- "CXL Window 0" --||----- "CXL Window 1" -----|
+ |--------------- "System RAM" -------------|
+
+ ...where platform firmware has established as System RAM resource across 2
+ windows, but has left some portion of window 1 for dynamic CXL region
+ provisioning. In this case "Window 0" will span the entirety of the "System
+ RAM" span, and "CXL Window 1" is truncated to the remaining tail past the end
+ of that "System RAM" resource.
+
+- name: simple_tables
+ description: Test formatting two simple tables
+ fname: mock.c
+ source: |
+ /**
+ * bitmap_onto - translate one bitmap relative to another
+ * @dst: resulting translated bitmap
+ * @orig: original untranslated bitmap
+ * @relmap: bitmap relative to which translated
+ * @bits: number of bits in each of these bitmaps
+ *
+ * =============== ============== =================
+ * @orig tmp @dst
+ * 0 0 40
+ * 1 1 41
+ * =============== ============== =================
+ *
+ * And:
+ *
+ * =============== ============== =================
+ * @orig tmp @dst
+ * =============== ============== =================
+ * 9 9 95
+ * 10 0 40 [#f1]_
+ * =============== ============== =================
+ */
+ void bitmap_onto(unsigned long *dst, const unsigned long *orig,
+ const unsigned long *relmap, unsigned int bits);
+ expected:
+ - man: |
+ .TH "bitmap_onto" 9 "March 2026" "" "Kernel API Manual"
+ .SH NAME
+ bitmap_onto \- translate one bitmap relative to another
+ .SH SYNOPSIS
+ .B "void" bitmap_onto
+ .BI "(unsigned long *dst " ","
+ .BI "const unsigned long *orig " ","
+ .BI "const unsigned long *relmap " ","
+ .BI "unsigned int bits " ");"
+ .SH ARGUMENTS
+ .IP "dst" 12
+ resulting translated bitmap
+ .IP "orig" 12
+ original untranslated bitmap
+ .IP "relmap" 12
+ bitmap relative to which translated
+ .IP "bits" 12
+ number of bits in each of these bitmaps
+ .SH "DESCRIPTION"
+
+ .TS
+ box;
+ l l l.
+ \fIorig\fP tmp \fIdst\fP
+ 0 0 40
+ 1 1 41
+ .TE
+ .PP
+
+ And:
+ .PP
+
+
+ .TS
+ box;
+ l l l.
+ \fIorig\fP tmp \fIdst\fP
+ .TE
+ 9 9 95
+ 10 0 40 [#f1]_
+ .SH "SEE ALSO"
+ .PP
+ Kernel file \fBmock.c\fR
+
+ rst: |
+ .. c:function:: void bitmap_onto (unsigned long *dst, const unsigned long *orig, const unsigned long *relmap, unsigned int bits)
+
+ translate one bitmap relative to another
+
+ .. container:: kernelindent
+
+ **Parameters**
+
+ ``unsigned long *dst``
+ resulting translated bitmap
+
+ ``const unsigned long *orig``
+ original untranslated bitmap
+
+ ``const unsigned long *relmap``
+ bitmap relative to which translated
+
+ ``unsigned int bits``
+ number of bits in each of these bitmaps
+
+ **Description**
+
+ =============== ============== =================
+ **orig** tmp **dst**
+ 0 0 40
+ 1 1 41
+ =============== ============== =================
+
+ And:
+
+ =============== ============== =================
+ **orig** tmp **dst**
+ =============== ============== =================
+ 9 9 95
+ 10 0 40 [#f1]_
+ =============== ============== =================
+
+#
+# Variable tests from Randy Dunlap's testset
+#
+- name: unsigned_long_var_on_uppercase
+ description: Test an unsigned long varaible in uppercase
+ fname: mock-vars.c
+ source: |
+ /**
+ * var ROOT_DEV - system root device
+ *
+ * @ROOT_DEV is either the successful root device or the root device
+ * that failed boot in the boot failure message.
+ */
+ unsigned long ROOT_DEV;
+ expected:
+ - man: |
+ .TH "var ROOT_DEV" 9 "February 2026" "" "Kernel API Manual"
+ .SH NAME
+ ROOT_DEV \- system root device
+ .SH SYNOPSIS
+ unsigned long ROOT_DEV;
+ .SH "Description"
+ \fIROOT_DEV\fP is either the successful root device or the root device
+ that failed boot in the boot failure message.
+ .SH "SEE ALSO"
+ .PP
+ Kernel file \fBmock-vars.c\fR
+ rst: |
+ .. c:macro:: ROOT_DEV
+
+ ``unsigned long ROOT_DEV;``
+
+ system root device
+
+ **Description**
+
+ **ROOT_DEV** is either the successful root device or the root device
+ that failed boot in the boot failure message.
+- name: enum_var
+ description: Test an enum var with __read_mostly
+ fname: mock-vars.c
+ source: |
+ /**
+ * var system_state - system state used during boot or suspend/hibernate/resume
+ *
+ * @system_state can be used during boot to determine if it is safe to
+ * make certain calls to other parts of the kernel. It can also be used
+ * during suspend/hibernate or resume to determine the order of actions
+ * that need to be executed. The numerical values of system_state are
+ * sometimes used in numerical ordering tests, so the relative values
+ * must not be altered.
+ */
+ enum system_states system_state __read_mostly;
+ expected:
+ - man: |
+ .TH "var system_state" 9 "February 2026" "" "Kernel API Manual"
+ .SH NAME
+ system_state \- system state used during boot or suspend/hibernate/resume
+ .SH SYNOPSIS
+ enum system_states system_state __read_mostly;
+ .SH "Description"
+ \fIsystem_state\fP can be used during boot to determine if it is safe to
+ make certain calls to other parts of the kernel. It can also be used
+ during suspend/hibernate or resume to determine the order of actions
+ that need to be executed. The numerical values of system_state are
+ sometimes used in numerical ordering tests, so the relative values
+ must not be altered.
+ .SH "SEE ALSO"
+ .PP
+ Kernel file \fBmock-vars.c\fR
+ rst: |
+ .. c:macro:: system_state
+
+ ``enum system_states system_state __read_mostly;``
+
+ system state used during boot or suspend/hibernate/resume
+
+ **Description**
+
+ **system_state** can be used during boot to determine if it is safe to
+ make certain calls to other parts of the kernel. It can also be used
+ during suspend/hibernate or resume to determine the order of actions
+ that need to be executed. The numerical values of system_state are
+ sometimes used in numerical ordering tests, so the relative values
+ must not be altered.
+- name: char_pointer_var
+ description: Test char * var with __ro_after_init
+ fname: mock-vars.c
+ source: |
+ /**
+ * var saved_command_line - kernel's command line, saved from use at
+ * any later time in the kernel.
+ */
+ char *saved_command_line __ro_after_init;
+ expected:
+ - man: |
+ .TH "var saved_command_line" 9 "February 2026" "" "Kernel API Manual"
+ .SH NAME
+ saved_command_line \- kernel's command line, saved from use at any later time in the kernel.
+ .SH SYNOPSIS
+ char *saved_command_line __ro_after_init;
+ .SH "SEE ALSO"
+ .PP
+ Kernel file \fBmock-vars.c\fR
+ rst: |
+ .. c:macro:: saved_command_line
+
+ ``char *saved_command_line __ro_after_init;``
+
+ kernel's command line, saved from use at any later time in the kernel.
+- name: unsigned_long_with_default
+ description: Test an unsigned long var that is set to a default value
+ fname: mock-vars.c
+ source: |
+ /**
+ * var loop_per_jiffy - calculated loop count needed to consume one jiffy
+ * of time
+ */
+ unsigned long loops_per_jiffy = (1<<12);
+ expected:
+ - man: |
+ .TH "var loops_per_jiffy" 9 "February 2026" "" "Kernel API Manual"
+ .SH NAME
+ loops_per_jiffy \- calculated loop count needed to consume one jiffy of time
+ .SH SYNOPSIS
+ unsigned long loops_per_jiffy = (1<<12);
+ .SH "Initialization"
+ default: (1<<12)
+ .SH "SEE ALSO"
+ .PP
+ Kernel file \fBmock-vars.c\fR
+ rst: |
+ .. c:macro:: loops_per_jiffy
+
+ ``unsigned long loops_per_jiffy = (1<<12);``
+
+ calculated loop count needed to consume one jiffy of time
+
+ **Initialization**
+
+ default: ``(1<<12)``
+- name: unsigned_long
+ description: test a simple unsigned long variable.
+ fname: mock-vars.c
+ source: |
+ /**
+ * var preset_lpj - lpj (loops per jiffy) value set from kernel
+ * command line using "lpj=VALUE"
+ *
+ * See Documentation/admin-guide/kernel-parameters.txt ("lpj=") for details.
+ */
+ unsigned long preset_lpj;
+ expected:
+ - man: |
+ .TH "var preset_lpj" 9 "February 2026" "" "Kernel API Manual"
+ .SH NAME
+ preset_lpj \- lpj (loops per jiffy) value set from kernel command line using "lpj=VALUE"
+ .SH SYNOPSIS
+ unsigned long preset_lpj;
+ .SH "Description"
+ See Documentation/admin-guide/kernel-parameters.txt ("lpj=") for details.
+ .SH "SEE ALSO"
+ .PP
+ Kernel file \fBmock-vars.c\fR
+ rst: |
+ .. c:macro:: preset_lpj
+
+ ``unsigned long preset_lpj;``
+
+ lpj (loops per jiffy) value set from kernel command line using "lpj=VALUE"
+
+ **Description**
+
+ See Documentation/admin-guide/kernel-parameters.txt ("lpj=") for details.
+- name: char_array
+ description: test a char array variable
+ fname: mock-vars.c
+ source: |
+ /**
+ * var linux_proc_banner - text used from /proc/version file
+ *
+ * * first %s is sysname (e.g., "Linux")
+ * * second %s is release
+ * * third %s is version
+ */
+ char linux_proc_banner[];
+ expected:
+ - man: |
+ .TH "var linux_proc_banner" 9 "February 2026" "" "Kernel API Manual"
+ .SH NAME
+ linux_proc_banner \- text used from /proc/version file
+ .SH SYNOPSIS
+ char linux_proc_banner[];
+ .SH "Description"
+ .IP \[bu]
+ first s is sysname (e.g., "Linux")
+ .IP \[bu]
+ second s is release
+ .IP \[bu]
+ third s is version
+ .SH "SEE ALSO"
+ .PP
+ Kernel file \fBmock-vars.c\fR
+ rst: |
+ .. c:macro:: linux_proc_banner
+
+ ``char linux_proc_banner[];``
+
+ text used from /proc/version file
+
+ **Description**
+
+ * first ``s`` is sysname (e.g., "Linux")
+ * second ``s`` is release
+ * third ``s`` is version
+- name: const_char_array
+ description: test a const char array variable
+ fname: mock-vars.c
+ source: |
+ /**
+ * var linux_banner - Linux boot banner, usually printed at boot time
+ */
+ const char linux_banner[];
+ expected:
+ - man: |
+ .TH "var linux_banner" 9 "February 2026" "" "Kernel API Manual"
+ .SH NAME
+ linux_banner \- Linux boot banner, usually printed at boot time
+ .SH SYNOPSIS
+ const char linux_banner[];
+ .SH "SEE ALSO"
+ .PP
+ Kernel file \fBmock-vars.c\fR
+ rst: |
+ .. c:macro:: linux_banner
+
+ ``const char linux_banner[];``
+
+ Linux boot banner, usually printed at boot time
+- name: static_atomic64_t_var
+ description: test a static atomi64_t variable
+ fname: mock-vars.c
+ source: |
+ /**
+ * var diskseq - unique sequence number for block device instances
+ *
+ * Allows userspace to associate uevents to the lifetime of a device
+ */
+ static atomic64_t diskseq;
+ expected:
+ - man: |
+ .TH "var diskseq" 9 "February 2026" "" "Kernel API Manual"
+ .SH NAME
+ diskseq \- unique sequence number for block device instances
+ .SH SYNOPSIS
+ static atomic64_t diskseq;
+ .SH "Description"
+ Allows userspace to associate uevents to the lifetime of a device
+ .SH "SEE ALSO"
+ .PP
+ Kernel file \fBmock-vars.c\fR
+ rst: |
+ .. c:macro:: diskseq
+
+ ``static atomic64_t diskseq;``
+
+ unique sequence number for block device instances
+
+ **Description**
+
+ Allows userspace to associate uevents to the lifetime of a device
+- name: unsigned_long_on_init
+ description: test an unsigned long var at "init" with a different timestamp.
+ fname: init/mock-vars.c
+ source: |
+ /**
+ * var rtnl_mutex - historical global lock for networking control operations.
+ *
+ * @rtnl_mutex is used to serialize rtnetlink requests
+ * and protect all kernel internal data structures related to networking.
+ *
+ * See Documentation/networking/netdevices.rst for details.
+ * Often known as the rtnl_lock, although rtnl_lock is a kernel function.
+ */
+ unsigned long rtnl_mutex;
+ expected:
+ - man: |
+ .TH "var rtnl_mutex" 9 "February 2026" "init" "Kernel API Manual"
+ .SH NAME
+ rtnl_mutex \- historical global lock for networking control operations.
+ .SH SYNOPSIS
+ unsigned long rtnl_mutex;
+ .SH "Description"
+ \fIrtnl_mutex\fP is used to serialize rtnetlink requests
+ and protect all kernel internal data structures related to networking.
+ .PP
+
+ See Documentation/networking/netdevices.rst for details.
+ Often known as the rtnl_lock, although rtnl_lock is a kernel function.
+ .SH "SEE ALSO"
+ .PP
+ Kernel file \fBinit/mock-vars.c\fR
+ rst: |
+ .. c:macro:: rtnl_mutex
+
+ ``unsigned long rtnl_mutex;``
+
+ historical global lock for networking control operations.
+
+ **Description**
+
+ **rtnl_mutex** is used to serialize rtnetlink requests
+ and protect all kernel internal data structures related to networking.
+
+ See Documentation/networking/netdevices.rst for details.
+ Often known as the rtnl_lock, although rtnl_lock is a kernel function.
+
+
+- name: struct_kcov
+ fname: mock_tests/kdoc-drop-ctx-lock.c
+ source: |
+ /**
+ * struct kcov - kcov descriptor (one per opened debugfs file).
+ * State transitions of the descriptor:
+ *
+ * - initial state after open()
+ * - then there must be a single ioctl(KCOV_INIT_TRACE) call
+ * - then, mmap() call (several calls are allowed but not useful)
+ * - then, ioctl(KCOV_ENABLE, arg), where arg is
+ * KCOV_TRACE_PC - to trace only the PCs
+ * or
+ * KCOV_TRACE_CMP - to trace only the comparison operands
+ * - then, ioctl(KCOV_DISABLE) to disable the task.
+ *
+ * Enabling/disabling ioctls can be repeated (only one task a time allowed).
+ */
+ struct kcov {
+ /**
+ * @refcount: Reference counter. We keep one for:
+ * - opened file descriptor
+ * - task with enabled coverage (we can't unwire it from another task)
+ * - each code section for remote coverage collection
+ */
+ refcount_t refcount;
+ /**
+ * @lock: The lock protects mode, size, area and t.
+ */
+ spinlock_t lock;
+ /**
+ * @mode: the kcov_mode
+ */
+ enum kcov_mode mode __guarded_by(&lock);
+ /**
+ * @size: Size of arena (in long's).
+ */
+ unsigned int size __guarded_by(&lock);
+ /**
+ * @area: Coverage buffer shared with user space.
+ */
+ void *area __guarded_by(&lock);
+ /**
+ * @t: Task for which we collect coverage, or NULL.
+ */
+ struct task_struct *t __guarded_by(&lock);
+ /**
+ * @remote: Collecting coverage from remote (background) threads.
+ */
+ bool remote;
+ /**
+ * @remote_size: Size of remote area (in long's).
+ */
+ unsigned int remote_size;
+ /**
+ * @sequence: Sequence is incremented each time kcov is reenabled,
+ * used by kcov_remote_stop(), see the comment there.
+ */
+ int sequence;
+ };
+ expected:
+ - man: |
+ .TH "struct kcov" 9 "February 2026" "mock_tests" "Kernel API Manual"
+ .SH NAME
+ struct kcov \- kcov descriptor (one per opened debugfs file). State transitions of the descriptor:
+ .SH SYNOPSIS
+ struct kcov {
+ .br
+ .BI " refcount_t refcount;"
+ .br
+ .BI " spinlock_t lock;"
+ .br
+ .BI " enum kcov_mode mode;"
+ .br
+ .BI " unsigned int size;"
+ .br
+ .BI " void *area;"
+ .br
+ .BI " struct task_struct *t;"
+ .br
+ .BI " bool remote;"
+ .br
+ .BI " unsigned int remote_size;"
+ .br
+ .BI " int sequence;"
+ .br
+ .BI "
+ };
+ .br
+
+ .SH Members
+ .IP "refcount" 12
+ Reference counter. We keep one for:
+ .IP \[bu]
+ opened file descriptor
+ .IP \[bu]
+ task with enabled coverage (we can't unwire it from another task)
+ .IP \[bu]
+ each code section for remote coverage collection
+ .IP "lock" 12
+ The lock protects mode, size, area and t.
+ .IP "mode" 12
+ the kcov_mode
+ .IP "size" 12
+ Size of arena (in long's).
+ .IP "area" 12
+ Coverage buffer shared with user space.
+ .IP "t" 12
+ Task for which we collect coverage, or NULL.
+ .IP "remote" 12
+ Collecting coverage from remote (background) threads.
+ .IP "remote_size" 12
+ Size of remote area (in long's).
+ .IP "sequence" 12
+ Sequence is incremented each time kcov is reenabled,
+ used by \fBkcov_remote_stop\fP, see the comment there.
+ .SH "Description"
+ .IP \[bu]
+ initial state after \fBopen\fP
+ .IP \[bu]
+ then there must be a single ioctl(KCOV_INIT_TRACE) call
+ .IP \[bu]
+ then, \fBmmap\fP call (several calls are allowed but not useful)
+ .IP \[bu]
+ then, ioctl(KCOV_ENABLE, arg), where arg is
+ KCOV_TRACE_PC - to trace only the PCs
+ or
+ KCOV_TRACE_CMP - to trace only the comparison operands
+ .IP \[bu]
+ then, ioctl(KCOV_DISABLE) to disable the task.
+ .PP
+
+ Enabling/disabling ioctls can be repeated (only one task a time allowed).
+ .SH "SEE ALSO"
+ .PP
+ Kernel file \fBmock_tests/kdoc-drop-ctx-lock.c\fR
+ rst: |
+ .. c:struct:: kcov
+
+ kcov descriptor (one per opened debugfs file). State transitions of the descriptor:
+
+ .. container:: kernelindent
+
+ **Definition**::
+
+ struct kcov {
+ refcount_t refcount;
+ spinlock_t lock;
+ enum kcov_mode mode;
+ unsigned int size;
+ void *area;
+ struct task_struct *t;
+ bool remote;
+ unsigned int remote_size;
+ int sequence;
+ };
+
+ **Members**
+
+ ``refcount``
+ Reference counter. We keep one for:
+ - opened file descriptor
+ - task with enabled coverage (we can't unwire it from another task)
+ - each code section for remote coverage collection
+
+ ``lock``
+ The lock protects mode, size, area and t.
+
+ ``mode``
+ the kcov_mode
+
+ ``size``
+ Size of arena (in long's).
+
+ ``area``
+ Coverage buffer shared with user space.
+
+ ``t``
+ Task for which we collect coverage, or NULL.
+
+ ``remote``
+ Collecting coverage from remote (background) threads.
+
+ ``remote_size``
+ Size of remote area (in long's).
+
+ ``sequence``
+ Sequence is incremented each time kcov is reenabled,
+ used by kcov_remote_stop(), see the comment there.
+
+
+ **Description**
+
+ - initial state after open()
+ - then there must be a single ioctl(KCOV_INIT_TRACE) call
+ - then, mmap() call (several calls are allowed but not useful)
+ - then, ioctl(KCOV_ENABLE, arg), where arg is
+ KCOV_TRACE_PC - to trace only the PCs
+ or
+ KCOV_TRACE_CMP - to trace only the comparison operands
+ - then, ioctl(KCOV_DISABLE) to disable the task.
+
+ Enabling/disabling ioctls can be repeated (only one task a time allowed).
+
+- name: pool_offset
+ description: mock_tests/kdoc-drop-ctx-lock.c line 83
+ fname: mock_tests/kdoc-drop-ctx-lock.c
+ source: |
+ /**
+ * var pool_offset - Offset to the unused space in the currently used pool.
+ *
+ */
+ size_t pool_offset __guarded_by(&pool_lock) = DEPOT_POOL_SIZE;
+ expected:
+ - man: |
+ .TH "var pool_offset" 9 "February 2026" "mock_tests" "Kernel API Manual"
+ .SH NAME
+ pool_offset \- Offset to the unused space in the currently used pool.
+ .SH SYNOPSIS
+ size_t pool_offset __guarded_by(&pool_lock) = DEPOT_POOL_SIZE;
+ .SH "Initialization"
+ default: DEPOT_POOL_SIZE
+ .SH "SEE ALSO"
+ .PP
+ Kernel file \fBmock_tests/kdoc-drop-ctx-lock.c\fR
+ rst: |
+ .. c:macro:: pool_offset
+
+ ``size_t pool_offset __guarded_by(&pool_lock) = DEPOT_POOL_SIZE;``
+
+ Offset to the unused space in the currently used pool.
+
+ **Initialization**
+
+ default: ``DEPOT_POOL_SIZE``
+- name: free_stacks
+ description: mock_tests/kdoc-drop-ctx-lock.c line 88
+ fname: mock_tests/kdoc-drop-ctx-lock.c
+ source: |
+ /**
+ * var free_stacks - Freelist of stack records within stack_pools.
+ *
+ */
+ __guarded_by(&pool_lock) LIST_HEAD(free_stacks);
+ expected:
+ - man: |
+ .TH "var free_stacks" 9 "February 2026" "mock_tests" "Kernel API Manual"
+ .SH NAME
+ free_stacks \- Freelist of stack records within stack_pools.
+ .SH SYNOPSIS
+ __guarded_by(&pool_lock) LIST_HEAD(free_stacks);
+ .SH "SEE ALSO"
+ .PP
+ Kernel file \fBmock_tests/kdoc-drop-ctx-lock.c\fR
+ rst: |
+ .. c:macro:: free_stacks
+
+ ``__guarded_by(&pool_lock) LIST_HEAD(free_stacks);``
+
+ Freelist of stack records within stack_pools.
+- name: stack_pools
+ description: mock_tests/kdoc-drop-ctx-lock.c line 94
+ fname: mock_tests/kdoc-drop-ctx-lock.c
+ source: |
+ /**
+ * var stack_pools - Array of memory regions that store stack records.
+ *
+ */
+ void **stack_pools __pt_guarded_by(&pool_lock);
+ expected:
+ - man: |
+ .TH "var stack_pools" 9 "February 2026" "mock_tests" "Kernel API Manual"
+ .SH NAME
+ stack_pools \- Array of memory regions that store stack records.
+ .SH SYNOPSIS
+ void **stack_pools __pt_guarded_by(&pool_lock);
+ .SH "SEE ALSO"
+ .PP
+ Kernel file \fBmock_tests/kdoc-drop-ctx-lock.c\fR
+ rst: |
+ .. c:macro:: stack_pools
+
+ ``void **stack_pools __pt_guarded_by(&pool_lock);``
+
+ Array of memory regions that store stack records.
+- name: prepare_report_consumer
+ description: mock_tests/kdoc-drop-ctx-lock.c line 103
+ fname: mock_tests/kdoc-drop-ctx-lock.c
+ source: |
+ /**
+ * prepare_report_consumer - prepare the report consumer
+ * @flags: flags
+ * @ai: not that AI
+ * @other_info: yes that
+ */
+ bool prepare_report_consumer(unsigned long *flags,
+ const struct access_info *ai,
+ struct other_info *other_info)
+ __cond_acquires(true, &report_lock)
+ {
+ expected:
+ - man: |
+ .TH "prepare_report_consumer" 9 "February 2026" "mock_tests" "Kernel API Manual"
+ .SH NAME
+ prepare_report_consumer \- prepare the report consumer
+ .SH SYNOPSIS
+ .B "bool" prepare_report_consumer
+ .BI "(unsigned long *flags " ","
+ .BI "const struct access_info *ai " ","
+ .BI "struct other_info *other_info " ");"
+ .SH ARGUMENTS
+ .IP "flags" 12
+ flags
+ .IP "ai" 12
+ not that AI
+ .IP "other_info" 12
+ yes that
+ .SH "SEE ALSO"
+ .PP
+ Kernel file \fBmock_tests/kdoc-drop-ctx-lock.c\fR
+ rst: |
+ .. c:function:: bool prepare_report_consumer (unsigned long *flags, const struct access_info *ai, struct other_info *other_info)
+
+ prepare the report consumer
+
+ .. container:: kernelindent
+
+ **Parameters**
+
+ ``unsigned long *flags``
+ flags
+
+ ``const struct access_info *ai``
+ not that AI
+
+ ``struct other_info *other_info``
+ yes that
+- name: tcp_sigpool_start
+ description: mock_tests/kdoc-drop-ctx-lock.c line 117
+ fname: mock_tests/kdoc-drop-ctx-lock.c
+ source: |
+ /**
+ * tcp_sigpool_start - start a tcp message of @id, using @c
+ * @id: TCP message ID
+ * @c: the &tcp_sigpool to use
+ */
+ int tcp_sigpool_start(unsigned int id, struct tcp_sigpool *c) __cond_acquires(0, RCU_BH)
+ {
+ expected:
+ - man: |
+ .TH "tcp_sigpool_start" 9 "February 2026" "mock_tests" "Kernel API Manual"
+ .SH NAME
+ tcp_sigpool_start \- start a tcp message of @id, using @c
+ .SH SYNOPSIS
+ .B "int" tcp_sigpool_start
+ .BI "(unsigned int id " ","
+ .BI "struct tcp_sigpool *c " ");"
+ .SH ARGUMENTS
+ .IP "id" 12
+ TCP message ID
+ .IP "c" 12
+ the \fItcp_sigpool\fP to use
+ .SH "SEE ALSO"
+ .PP
+ Kernel file \fBmock_tests/kdoc-drop-ctx-lock.c\fR
+ rst: |
+ .. c:function:: int tcp_sigpool_start (unsigned int id, struct tcp_sigpool *c)
+
+ start a tcp message of **id**, using **c**
+
+ .. container:: kernelindent
+
+ **Parameters**
+
+ ``unsigned int id``
+ TCP message ID
+
+ ``struct tcp_sigpool *c``
+ the :c:type:`tcp_sigpool` to use
+- name: undo_report_consumer
+ description: mock_tests/kdoc-drop-ctx-lock.c line 129
+ fname: mock_tests/kdoc-drop-ctx-lock.c
+ source: |
+ /**
+ * undo_report_consumer - teardown a report consumer
+ * @flags: those flags
+ * @ai: not that AI
+ * @other_info: yes that
+ */
+ bool undo_report_consumer(unsigned long *flags,
+ const struct access_info *ai,
+ struct other_info *other_info)
+ __cond_releases(true, &report_lock)
+ {
+ expected:
+ - man: |
+ .TH "undo_report_consumer" 9 "February 2026" "mock_tests" "Kernel API Manual"
+ .SH NAME
+ undo_report_consumer \- teardown a report consumer
+ .SH SYNOPSIS
+ .B "bool" undo_report_consumer
+ .BI "(unsigned long *flags " ","
+ .BI "const struct access_info *ai " ","
+ .BI "struct other_info *other_info " ");"
+ .SH ARGUMENTS
+ .IP "flags" 12
+ those flags
+ .IP "ai" 12
+ not that AI
+ .IP "other_info" 12
+ yes that
+ .SH "SEE ALSO"
+ .PP
+ Kernel file \fBmock_tests/kdoc-drop-ctx-lock.c\fR
+ rst: |
+ .. c:function:: bool undo_report_consumer (unsigned long *flags, const struct access_info *ai, struct other_info *other_info)
+
+ teardown a report consumer
+
+ .. container:: kernelindent
+
+ **Parameters**
+
+ ``unsigned long *flags``
+ those flags
+
+ ``const struct access_info *ai``
+ not that AI
+
+ ``struct other_info *other_info``
+ yes that
+- name: debugfs_enter_cancellation
+ description: mock_tests/kdoc-drop-ctx-lock.c line 143
+ fname: mock_tests/kdoc-drop-ctx-lock.c
+ source: |
+ /**
+ * debugfs_enter_cancellation - begin a cancellation operation on @file
+ * @file: the target file
+ * @cancellation: the operation to execute
+ */
+ void debugfs_enter_cancellation(struct file *file,
+ struct debugfs_cancellation *cancellation) __acquires(cancellation)
+ { }
+ expected:
+ - man: |
+ .TH "debugfs_enter_cancellation" 9 "February 2026" "mock_tests" "Kernel API Manual"
+ .SH NAME
+ debugfs_enter_cancellation \- begin a cancellation operation on @file
+ .SH SYNOPSIS
+ .B "void" debugfs_enter_cancellation
+ .BI "(struct file *file " ","
+ .BI "struct debugfs_cancellation *cancellation " ");"
+ .SH ARGUMENTS
+ .IP "file" 12
+ the target file
+ .IP "cancellation" 12
+ the operation to execute
+ .SH "SEE ALSO"
+ .PP
+ Kernel file \fBmock_tests/kdoc-drop-ctx-lock.c\fR
+ rst: |
+ .. c:function:: void debugfs_enter_cancellation (struct file *file, struct debugfs_cancellation *cancellation)
+
+ begin a cancellation operation on **file**
+
+ .. container:: kernelindent
+
+ **Parameters**
+
+ ``struct file *file``
+ the target file
+
+ ``struct debugfs_cancellation *cancellation``
+ the operation to execute
+- name: debugfs_leave_cancellation
+ description: mock_tests/kdoc-drop-ctx-lock.c line 152
+ fname: mock_tests/kdoc-drop-ctx-lock.c
+ source: |
+ /**
+ * debugfs_leave_cancellation - wrapup the cancellation operation on @file
+ * @file: the target file
+ * @cancellation: the operation to wrapup
+ */
+ void debugfs_leave_cancellation(struct file *file,
+ struct debugfs_cancellation *cancellation) __releases(cancellation)
+ { }
+ expected:
+ - man: |
+ .TH "debugfs_leave_cancellation" 9 "February 2026" "mock_tests" "Kernel API Manual"
+ .SH NAME
+ debugfs_leave_cancellation \- wrapup the cancellation operation on @file
+ .SH SYNOPSIS
+ .B "void" debugfs_leave_cancellation
+ .BI "(struct file *file " ","
+ .BI "struct debugfs_cancellation *cancellation " ");"
+ .SH ARGUMENTS
+ .IP "file" 12
+ the target file
+ .IP "cancellation" 12
+ the operation to wrapup
+ .SH "SEE ALSO"
+ .PP
+ Kernel file \fBmock_tests/kdoc-drop-ctx-lock.c\fR
+ rst: |
+ .. c:function:: void debugfs_leave_cancellation (struct file *file, struct debugfs_cancellation *cancellation)
+
+ wrapup the cancellation operation on **file**
+
+ .. container:: kernelindent
+
+ **Parameters**
+
+ ``struct file *file``
+ the target file
+
+ ``struct debugfs_cancellation *cancellation``
+ the operation to wrapup
+- name: acpi_os_acquire_lock
+ description: mock_tests/kdoc-drop-ctx-lock.c line 161
+ fname: mock_tests/kdoc-drop-ctx-lock.c
+ source: |
+ /**
+ * acpi_os_acquire_lock - Acquire a spinlock.
+ * @lockp: pointer to the spinlock_t.
+ */
+ acpi_cpu_flags acpi_os_acquire_lock(acpi_spinlock lockp)
+ __acquires(lockp)
+ {
+ expected:
+ - man: |
+ .TH "acpi_os_acquire_lock" 9 "February 2026" "mock_tests" "Kernel API Manual"
+ .SH NAME
+ acpi_os_acquire_lock \- Acquire a spinlock.
+ .SH SYNOPSIS
+ .B "acpi_cpu_flags" acpi_os_acquire_lock
+ .BI "(acpi_spinlock lockp " ");"
+ .SH ARGUMENTS
+ .IP "lockp" 12
+ pointer to the spinlock_t.
+ .SH "SEE ALSO"
+ .PP
+ Kernel file \fBmock_tests/kdoc-drop-ctx-lock.c\fR
+ rst: |
+ .. c:function:: acpi_cpu_flags acpi_os_acquire_lock (acpi_spinlock lockp)
+
+ Acquire a spinlock.
+
+ .. container:: kernelindent
+
+ **Parameters**
+
+ ``acpi_spinlock lockp``
+ pointer to the spinlock_t.
+- name: acpi_os_release_lock
+ description: mock_tests/kdoc-drop-ctx-lock.c line 172
+ fname: mock_tests/kdoc-drop-ctx-lock.c
+ source: |
+ /**
+ * acpi_os_release_lock - Release a spinlock.
+ * @lockp: pointer to the spinlock_t.
+ * @not_used: these flags are not used.
+ */
+ void acpi_os_release_lock(acpi_spinlock lockp, acpi_cpu_flags not_used)
+ __releases(lockp)
+ {
+ expected:
+ - man: |
+ .TH "acpi_os_release_lock" 9 "February 2026" "mock_tests" "Kernel API Manual"
+ .SH NAME
+ acpi_os_release_lock \- Release a spinlock.
+ .SH SYNOPSIS
+ .B "void" acpi_os_release_lock
+ .BI "(acpi_spinlock lockp " ","
+ .BI "acpi_cpu_flags not_used " ");"
+ .SH ARGUMENTS
+ .IP "lockp" 12
+ pointer to the spinlock_t.
+ .IP "not_used" 12
+ these flags are not used.
+ .SH "SEE ALSO"
+ .PP
+ Kernel file \fBmock_tests/kdoc-drop-ctx-lock.c\fR
+ rst: |
+ .. c:function:: void acpi_os_release_lock (acpi_spinlock lockp, acpi_cpu_flags not_used)
+
+ Release a spinlock.
+
+ .. container:: kernelindent
+
+ **Parameters**
+
+ ``acpi_spinlock lockp``
+ pointer to the spinlock_t.
+
+ ``acpi_cpu_flags not_used``
+ these flags are not used.
+- name: tx
+ description: mock_tests/kdoc-drop-ctx-lock.c line 183
+ fname: mock_tests/kdoc-drop-ctx-lock.c
+ source: |
+ /**
+ * tx - transmit message ID @id
+ * @id: message ID to transmit
+ */
+ int tx(int id) __must_hold(&txlock)
+ {
+ expected:
+ - man: |
+ .TH "tx" 9 "February 2026" "mock_tests" "Kernel API Manual"
+ .SH NAME
+ tx \- transmit message ID @id
+ .SH SYNOPSIS
+ .B "int" tx
+ .BI "(int id " ");"
+ .SH ARGUMENTS
+ .IP "id" 12
+ message ID to transmit
+ .SH "SEE ALSO"
+ .PP
+ Kernel file \fBmock_tests/kdoc-drop-ctx-lock.c\fR
+ rst: |
+ .. c:function:: int tx (int id)
+
+ transmit message ID **id**
+
+ .. container:: kernelindent
+
+ **Parameters**
+
+ ``int id``
+ message ID to transmit
+- name: contend_for_bm
+ description: mock_tests/kdoc-drop-ctx-lock.c line 192
+ fname: mock_tests/kdoc-drop-ctx-lock.c
+ source: |
+ /**
+ * contend_for_bm - try to become the bus master
+ * @card: the &fw_card (describes the bus)
+ */
+ enum bm_contention_outcome contend_for_bm(struct fw_card *card)
+ __must_hold(&card->lock)
+ {
+ expected:
+ - man: |
+ .TH "contend_for_bm" 9 "February 2026" "mock_tests" "Kernel API Manual"
+ .SH NAME
+ contend_for_bm \- try to become the bus master
+ .SH SYNOPSIS
+ .B "enum bm_contention_outcome" contend_for_bm
+ .BI "(struct fw_card *card " ");"
+ .SH ARGUMENTS
+ .IP "card" 12
+ the \fIfw_card\fP (describes the bus)
+ .SH "SEE ALSO"
+ .PP
+ Kernel file \fBmock_tests/kdoc-drop-ctx-lock.c\fR
+ rst: |
+ .. c:function:: enum bm_contention_outcome contend_for_bm (struct fw_card *card)
+
+ try to become the bus master
+
+ .. container:: kernelindent
+
+ **Parameters**
+
+ ``struct fw_card *card``
+ the :c:type:`fw_card` (describes the bus)
+- name: prepare_report_producer
+ description: mock_tests/kdoc-drop-ctx-lock.c line 202
+ fname: mock_tests/kdoc-drop-ctx-lock.c
+ source: |
+ /**
+ * prepare_report_producer - prepare the report producer
+ * @flags: still flags
+ * @ai: some AI
+ * @other_info: Populate @other_info; requires that the provided
+ * @other_info not in use.
+ */
+ void prepare_report_producer(unsigned long *flags,
+ const struct access_info *ai,
+ struct other_info *other_info)
+ __must_not_hold(&report_lock)
+ { }
+ expected:
+ - man: |
+ .TH "prepare_report_producer" 9 "February 2026" "mock_tests" "Kernel API Manual"
+ .SH NAME
+ prepare_report_producer \- prepare the report producer
+ .SH SYNOPSIS
+ .B "void" prepare_report_producer
+ .BI "(unsigned long *flags " ","
+ .BI "const struct access_info *ai " ","
+ .BI "struct other_info *other_info " ");"
+ .SH ARGUMENTS
+ .IP "flags" 12
+ still flags
+ .IP "ai" 12
+ some AI
+ .IP "other_info" 12
+ Populate \fIother_info\fP; requires that the provided
+ \fIother_info\fP not in use.
+ .SH "SEE ALSO"
+ .PP
+ Kernel file \fBmock_tests/kdoc-drop-ctx-lock.c\fR
+ rst: |
+ .. c:function:: void prepare_report_producer (unsigned long *flags, const struct access_info *ai, struct other_info *other_info)
+
+ prepare the report producer
+
+ .. container:: kernelindent
+
+ **Parameters**
+
+ ``unsigned long *flags``
+ still flags
+
+ ``const struct access_info *ai``
+ some AI
+
+ ``struct other_info *other_info``
+ Populate **other_info**; requires that the provided
+ **other_info** not in use.
+- name: crypto_alg_lookup
+ description: mock_tests/kdoc-drop-ctx-lock.c line 215
+ fname: mock_tests/kdoc-drop-ctx-lock.c
+ source: |
+ /**
+ * __crypto_alg_lookup() - lookup the algorithm by name/type/mask
+ * @name: name to search for
+ * @type: type to search for
+ * @mask: mask to match
+ */
+ struct crypto_alg *__crypto_alg_lookup(const char *name, u32 type,
+ u32 mask)
+ __must_hold_shared(&crypto_alg_sem)
+ {
+ expected:
+ - man: |
+ .TH "__crypto_alg_lookup" 9 "February 2026" "mock_tests" "Kernel API Manual"
+ .SH NAME
+ __crypto_alg_lookup \- lookup the algorithm by name/type/mask
+ .SH SYNOPSIS
+ .B "struct crypto_alg *" __crypto_alg_lookup
+ .BI "(const char *name " ","
+ .BI "u32 type " ","
+ .BI "u32 mask " ");"
+ .SH ARGUMENTS
+ .IP "name" 12
+ name to search for
+ .IP "type" 12
+ type to search for
+ .IP "mask" 12
+ mask to match
+ .SH "SEE ALSO"
+ .PP
+ Kernel file \fBmock_tests/kdoc-drop-ctx-lock.c\fR
+ rst: |
+ .. c:function:: struct crypto_alg * __crypto_alg_lookup (const char *name, u32 type, u32 mask)
+
+ lookup the algorithm by name/type/mask
+
+ .. container:: kernelindent
+
+ **Parameters**
+
+ ``const char *name``
+ name to search for
+
+ ``u32 type``
+ type to search for
+
+ ``u32 mask``
+ mask to match
+- name: down_read_trylock
+ description: mock_tests/kdoc-drop-ctx-lock.c line 228
+ fname: mock_tests/kdoc-drop-ctx-lock.c
+ source: |
+ /**
+ * down_read_trylock - trylock for reading
+ * @sem: the semaphore to try to lock
+ *
+ * Returns: 1 if successful, 0 if contention
+ */
+ extern int down_read_trylock(struct rw_semaphore *sem) __cond_acquires_shared(true, sem);
+ expected:
+ - man: |
+ .TH "down_read_trylock" 9 "February 2026" "mock_tests" "Kernel API Manual"
+ .SH NAME
+ down_read_trylock \- trylock for reading
+ .SH SYNOPSIS
+ .B "int" down_read_trylock
+ .BI "(struct rw_semaphore *sem " ");"
+ .SH ARGUMENTS
+ .IP "sem" 12
+ the semaphore to try to lock
+ .SH "RETURN"
+ 1 if successful, 0 if contention
+ .SH "SEE ALSO"
+ .PP
+ Kernel file \fBmock_tests/kdoc-drop-ctx-lock.c\fR
+ rst: |
+ .. c:function:: int down_read_trylock (struct rw_semaphore *sem)
+
+ trylock for reading
+
+ .. container:: kernelindent
+
+ **Parameters**
+
+ ``struct rw_semaphore *sem``
+ the semaphore to try to lock
+
+ **Return**
+
+ 1 if successful, 0 if contention
+- name: tomoyo_read_lock
+ description: mock_tests/kdoc-drop-ctx-lock.c line 236
+ fname: mock_tests/kdoc-drop-ctx-lock.c
+ source: |
+ /**
+ * tomoyo_read_lock - Take lock for protecting policy.
+ *
+ * Returns: index number for tomoyo_read_unlock().
+ */
+ int tomoyo_read_lock(void)
+ __acquires_shared(&tomoyo_ss)
+ {
+ expected:
+ - man: |
+ .TH "tomoyo_read_lock" 9 "February 2026" "mock_tests" "Kernel API Manual"
+ .SH NAME
+ tomoyo_read_lock \- Take lock for protecting policy.
+ .SH SYNOPSIS
+ .B "int" tomoyo_read_lock
+ .BI "(void " ");"
+ .SH ARGUMENTS
+ .IP "void" 12
+ no arguments
+ .SH "RETURN"
+ index number for \fBtomoyo_read_unlock\fP.
+ .SH "SEE ALSO"
+ .PP
+ Kernel file \fBmock_tests/kdoc-drop-ctx-lock.c\fR
+ rst: |
+ .. c:function:: int tomoyo_read_lock (void)
+
+ Take lock for protecting policy.
+
+ .. container:: kernelindent
+
+ **Parameters**
+
+ ``void``
+ no arguments
+
+ **Return**
+
+ index number for tomoyo_read_unlock().
+- name: tomoyo_read_unlock
+ description: mock_tests/kdoc-drop-ctx-lock.c line 247
+ fname: mock_tests/kdoc-drop-ctx-lock.c
+ source: |
+ /**
+ * tomoyo_read_unlock - Release lock for protecting policy.
+ *
+ * @idx: Index number returned by tomoyo_read_lock().
+ */
+ void tomoyo_read_unlock(int idx)
+ __releases_shared(&tomoyo_ss)
+ { }
+ expected:
+ - man: |
+ .TH "tomoyo_read_unlock" 9 "February 2026" "mock_tests" "Kernel API Manual"
+ .SH NAME
+ tomoyo_read_unlock \- Release lock for protecting policy.
+ .SH SYNOPSIS
+ .B "void" tomoyo_read_unlock
+ .BI "(int idx " ");"
+ .SH ARGUMENTS
+ .IP "idx" 12
+ Index number returned by \fBtomoyo_read_lock\fP.
+ .SH "SEE ALSO"
+ .PP
+ Kernel file \fBmock_tests/kdoc-drop-ctx-lock.c\fR
+ rst: |
+ .. c:function:: void tomoyo_read_unlock (int idx)
+
+ Release lock for protecting policy.
+
+ .. container:: kernelindent
+
+ **Parameters**
+
+ ``int idx``
+ Index number returned by tomoyo_read_lock().
+- name: c_stop
+ description: mock_tests/kdoc-drop-ctx-lock.c line 256
+ fname: mock_tests/kdoc-drop-ctx-lock.c
+ source: |
+ /**
+ * c_stop - stop the seq_file iteration
+ * @m: the &struct seq_file
+ * @p: handle
+ */
+ void c_stop(struct seq_file *m, void *p)
+ __releases_shared(&crypto_alg_sem)
+ { }
+ expected:
+ - man: |
+ .TH "c_stop" 9 "February 2026" "mock_tests" "Kernel API Manual"
+ .SH NAME
+ c_stop \- stop the seq_file iteration
+ .SH SYNOPSIS
+ .B "void" c_stop
+ .BI "(struct seq_file *m " ","
+ .BI "void *p " ");"
+ .SH ARGUMENTS
+ .IP "m" 12
+ the \fIstruct seq_file\fP
+ .IP "p" 12
+ handle
+ .SH "SEE ALSO"
+ .PP
+ Kernel file \fBmock_tests/kdoc-drop-ctx-lock.c\fR
+ rst: |
+ .. c:function:: void c_stop (struct seq_file *m, void *p)
+
+ stop the seq_file iteration
+
+ .. container:: kernelindent
+
+ **Parameters**
+
+ ``struct seq_file *m``
+ the :c:type:`struct seq_file <seq_file>`
+
+ ``void *p``
+ handle
+- name: spin_lock
+ description: mock_tests/kdoc-drop-ctx-lock.c line 265
+ fname: mock_tests/kdoc-drop-ctx-lock.c
+ source: |
+ /**
+ * spin_lock - spin until the @lock is acquired
+ * @lock: the spinlock
+ */
+ void spin_lock(spinlock_t *lock)
+ __acquires(lock) __no_context_analysis
+ { }
+ expected:
+ - man: |
+ .TH "spin_lock" 9 "February 2026" "mock_tests" "Kernel API Manual"
+ .SH NAME
+ spin_lock \- spin until the @lock is acquired
+ .SH SYNOPSIS
+ .B "void" spin_lock
+ .BI "(spinlock_t *lock " ");"
+ .SH ARGUMENTS
+ .IP "lock" 12
+ the spinlock
+ .SH "SEE ALSO"
+ .PP
+ Kernel file \fBmock_tests/kdoc-drop-ctx-lock.c\fR
+ rst: |
+ .. c:function:: void spin_lock (spinlock_t *lock)
+
+ spin until the **lock** is acquired
+
+ .. container:: kernelindent
+
+ **Parameters**
+
+ ``spinlock_t *lock``
+ the spinlock
diff --git a/tools/unittests/run.py b/tools/unittests/run.py
new file mode 100755
index 000000000000..8c19036d43a1
--- /dev/null
+++ b/tools/unittests/run.py
@@ -0,0 +1,17 @@
+#!/bin/env python3
+import os
+import unittest
+import sys
+
+TOOLS_DIR=os.path.join(os.path.dirname(os.path.realpath(__file__)), "..")
+sys.path.insert(0, TOOLS_DIR)
+
+from lib.python.unittest_helper import TestUnits
+
+if __name__ == "__main__":
+ loader = unittest.TestLoader()
+
+ suite = loader.discover(start_dir=os.path.join(TOOLS_DIR, "unittests"),
+ pattern="test*.py")
+
+ TestUnits().run("", suite=suite)
diff --git a/tools/unittests/test_cmatch.py b/tools/unittests/test_cmatch.py
new file mode 100755
index 000000000000..7b996f83784d
--- /dev/null
+++ b/tools/unittests/test_cmatch.py
@@ -0,0 +1,821 @@
+#!/usr/bin/env python3
+# SPDX-License-Identifier: GPL-2.0
+# Copyright(c) 2026: Mauro Carvalho Chehab <mchehab@kernel.org>.
+#
+# pylint: disable=C0413,R0904
+
+
+"""
+Unit tests for kernel-doc CMatch.
+"""
+
+import os
+import re
+import sys
+import unittest
+
+
+# Import Python modules
+
+SRC_DIR = os.path.dirname(os.path.realpath(__file__))
+sys.path.insert(0, os.path.join(SRC_DIR, "../lib/python"))
+
+from kdoc.c_lex import CMatch
+from kdoc.kdoc_re import KernRe
+from unittest_helper import run_unittest
+
+#
+# Override unittest.TestCase to better compare diffs ignoring whitespaces
+#
+class TestCaseDiff(unittest.TestCase):
+ """
+ Disable maximum limit on diffs and add a method to better
+ handle diffs with whitespace differences.
+ """
+
+ @classmethod
+ def setUpClass(cls):
+ """Ensure that there won't be limit for diffs"""
+ cls.maxDiff = None
+
+
+#
+# Tests doing with different macros
+#
+
+class TestSearch(TestCaseDiff):
+ """
+ Test search mechanism
+ """
+
+ def test_search_acquires_simple(self):
+ line = "__acquires(ctx) foo();"
+ result = ", ".join(CMatch("__acquires").search(line))
+ self.assertEqual(result, "__acquires(ctx)")
+
+ def test_search_acquires_multiple(self):
+ line = "__acquires(ctx) __acquires(other) bar();"
+ result = ", ".join(CMatch("__acquires").search(line))
+ self.assertEqual(result, "__acquires(ctx), __acquires(other)")
+
+ def test_search_acquires_nested_paren(self):
+ line = "__acquires((ctx1, ctx2)) baz();"
+ result = ", ".join(CMatch("__acquires").search(line))
+ self.assertEqual(result, "__acquires((ctx1, ctx2))")
+
+ def test_search_must_hold(self):
+ line = "__must_hold(&lock) do_something();"
+ result = ", ".join(CMatch("__must_hold").search(line))
+ self.assertEqual(result, "__must_hold(&lock)")
+
+ def test_search_must_hold_shared(self):
+ line = "__must_hold_shared(RCU) other();"
+ result = ", ".join(CMatch("__must_hold_shared").search(line))
+ self.assertEqual(result, "__must_hold_shared(RCU)")
+
+ def test_search_no_false_positive(self):
+ line = "call__acquires(foo); // should stay intact"
+ result = ", ".join(CMatch(r"__acquires").search(line))
+ self.assertEqual(result, "")
+
+ def test_search_no_macro_remains(self):
+ line = "do_something_else();"
+ result = ", ".join(CMatch("__acquires").search(line))
+ self.assertEqual(result, "")
+
+ def test_search_no_function(self):
+ line = "something"
+ result = ", ".join(CMatch(line).search(line))
+ self.assertEqual(result, "")
+
+#
+# Override unittest.TestCase to better compare diffs ignoring whitespaces
+#
+class TestCaseDiff(unittest.TestCase):
+ """
+ Disable maximum limit on diffs and add a method to better
+ handle diffs with whitespace differences.
+ """
+
+ @classmethod
+ def setUpClass(cls):
+ """Ensure that there won't be limit for diffs"""
+ cls.maxDiff = None
+
+ def assertLogicallyEqual(self, a, b):
+ """
+ Compare two results ignoring multiple whitespace differences.
+
+ This is useful to check more complex matches picked from examples.
+ On a plus side, we also don't need to use dedent.
+ Please notice that line breaks still need to match. We might
+ remove it at the regex, but this way, checking the diff is easier.
+ """
+ a = re.sub(r"[\t ]+", " ", a.strip())
+ b = re.sub(r"[\t ]+", " ", b.strip())
+
+ a = re.sub(r"\s+\n", "\n", a)
+ b = re.sub(r"\s+\n", "\n", b)
+
+ a = re.sub(" ;", ";", a)
+ b = re.sub(" ;", ";", b)
+
+ self.assertEqual(a, b)
+
+#
+# Tests doing with different macros
+#
+
+class TestSubMultipleMacros(TestCaseDiff):
+ """
+ Tests doing with different macros.
+
+ Here, we won't use assertLogicallyEqual. Instead, we'll check if each
+ of the expected patterns are present at the answer.
+ """
+
+ def test_acquires_simple(self):
+ """Simple replacement test with __acquires"""
+ line = "__acquires(ctx) foo();"
+ result = CMatch(r"__acquires").sub("REPLACED", line)
+
+ self.assertEqual("REPLACED foo();", result)
+
+ def test_acquires_multiple(self):
+ """Multiple __acquires"""
+ line = "__acquires(ctx) __acquires(other) bar();"
+ result = CMatch(r"__acquires").sub("REPLACED", line)
+
+ self.assertEqual("REPLACED REPLACED bar();", result)
+
+ def test_acquires_nested_paren(self):
+ """__acquires with nested pattern"""
+ line = "__acquires((ctx1, ctx2)) baz();"
+ result = CMatch(r"__acquires").sub("REPLACED", line)
+
+ self.assertEqual("REPLACED baz();", result)
+
+ def test_must_hold(self):
+ """__must_hold with a pointer"""
+ line = "__must_hold(&lock) do_something();"
+ result = CMatch(r"__must_hold").sub("REPLACED", line)
+
+ self.assertNotIn("__must_hold(", result)
+ self.assertIn("do_something();", result)
+
+ def test_must_hold_shared(self):
+ """__must_hold with an upercase defined value"""
+ line = "__must_hold_shared(RCU) other();"
+ result = CMatch(r"__must_hold_shared").sub("REPLACED", line)
+
+ self.assertNotIn("__must_hold_shared(", result)
+ self.assertIn("other();", result)
+
+ def test_no_false_positive(self):
+ """
+ Ensure that unrelated text containing similar patterns is preserved
+ """
+ line = "call__acquires(foo); // should stay intact"
+ result = CMatch(r"\b__acquires").sub("REPLACED", line)
+
+ self.assertLogicallyEqual(result, "call__acquires(foo);")
+
+ def test_mixed_macros(self):
+ """Add a mix of macros"""
+ line = "__acquires(ctx) __releases(ctx) __must_hold(&lock) foo();"
+
+ result = CMatch(r"__acquires").sub("REPLACED", line)
+ result = CMatch(r"__releases").sub("REPLACED", result)
+ result = CMatch(r"__must_hold").sub("REPLACED", result)
+
+ self.assertNotIn("__acquires(", result)
+ self.assertNotIn("__releases(", result)
+ self.assertNotIn("__must_hold(", result)
+
+ self.assertIn("foo();", result)
+
+ def test_no_macro_remains(self):
+ """Ensures that unmatched macros are untouched"""
+ line = "do_something_else();"
+ result = CMatch(r"__acquires").sub("REPLACED", line)
+
+ self.assertEqual(result, line)
+
+ def test_no_function(self):
+ """Ensures that no functions will remain untouched"""
+ line = "something"
+ result = CMatch(line).sub("REPLACED", line)
+
+ self.assertEqual(result, line)
+
+#
+# Check if the diff is logically equivalent. To simplify, the tests here
+# use a single macro name for all replacements.
+#
+
+class TestSubSimple(TestCaseDiff):
+ """
+ Test argument replacements.
+
+ Here, the function name can be anything. So, we picked __attribute__(),
+ to mimic a macro found at the Kernel, but none of the replacements her
+ has any relationship with the Kernel usage.
+ """
+
+ MACRO = "__attribute__"
+
+ @classmethod
+ def setUpClass(cls):
+ """Define a CMatch to be used for all tests"""
+ cls.matcher = CMatch(cls.MACRO)
+
+ def test_sub_with_capture(self):
+ """Test all arguments replacement with a single arg"""
+ line = f"{self.MACRO}(&ctx)\nfoo();"
+
+ result = self.matcher.sub(r"ACQUIRED(\0)", line)
+
+ self.assertLogicallyEqual("ACQUIRED(&ctx)\nfoo();", result)
+
+ def test_sub_zero_placeholder(self):
+ """Test all arguments replacement with a multiple args"""
+ line = f"{self.MACRO}(arg1, arg2)\nbar();"
+
+ result = self.matcher.sub(r"REPLACED(\0)", line)
+
+ self.assertLogicallyEqual("REPLACED(arg1, arg2)\nbar();", result)
+
+ def test_sub_single_placeholder(self):
+ """Single replacement rule for \1"""
+ line = f"{self.MACRO}(ctx, boo)\nfoo();"
+ result = self.matcher.sub(r"ACQUIRED(\1)", line)
+
+ self.assertLogicallyEqual("ACQUIRED(ctx)\nfoo();", result)
+
+ def test_sub_multiple_placeholders(self):
+ """Replacement rule for both \1 and \2"""
+ line = f"{self.MACRO}(arg1, arg2)\nbar();"
+ result = self.matcher.sub(r"REPLACE(\1, \2)", line)
+
+ self.assertLogicallyEqual("REPLACE(arg1, arg2)\nbar();", result)
+
+ def test_sub_mixed_placeholders(self):
+ """Replacement rule for \0, \1 and additional text"""
+ line = f"{self.MACRO}(foo, bar)\nbaz();"
+ result = self.matcher.sub(r"ALL(\0) FIRST(\1)", line)
+
+ self.assertLogicallyEqual("ALL(foo, bar) FIRST(foo)\nbaz();", result)
+
+ def test_sub_no_placeholder(self):
+ """Replacement without placeholders"""
+ line = f"{self.MACRO}(arg)\nfoo();"
+ result = self.matcher.sub(r"NO_BACKREFS()", line)
+
+ self.assertLogicallyEqual("NO_BACKREFS()\nfoo();", result)
+
+ def test_sub_count_parameter(self):
+ """Verify that the algorithm stops after the requested count"""
+ line = f"{self.MACRO}(a1) x();\n{self.MACRO}(a2) y();"
+ result = self.matcher.sub(r"ONLY_FIRST(\1) ", line, count=1)
+
+ self.assertLogicallyEqual(f"ONLY_FIRST(a1) x();\n{self.MACRO}(a2) y();",
+ result)
+
+ def test_strip_multiple_acquires(self):
+ """Check if spaces between removed delimiters will be dropped"""
+ line = f"int {self.MACRO}(1) {self.MACRO}(2 ) {self.MACRO}(3) foo;"
+ result = self.matcher.sub("", line)
+
+ self.assertLogicallyEqual(result, "int foo;")
+
+ def test_rise_early_greedy(self):
+ line = f"{self.MACRO}(a, b, c, d);"
+ sub = r"\1, \2+, \3"
+
+ with self.assertRaises(ValueError):
+ result = self.matcher.sub(sub, line)
+
+ def test_rise_multiple_greedy(self):
+ line = f"{self.MACRO}(a, b, c, d);"
+ sub = r"\1, \2+, \3+"
+
+ with self.assertRaises(ValueError):
+ result = self.matcher.sub(sub, line)
+
+#
+# Test replacements with slashrefs
+#
+
+
+class TestSubWithLocalXforms(TestCaseDiff):
+ """
+ Test diferent usecase patterns found at the Kernel.
+
+ Here, replacements using both CMatch and KernRe can be tested,
+ as it will import the actual replacement rules used by kernel-doc.
+ """
+
+ struct_xforms = [
+ (CMatch("__attribute__"), ' '),
+ (CMatch('__aligned'), ' '),
+ (CMatch('__counted_by'), ' '),
+ (CMatch('__counted_by_(le|be)'), ' '),
+ (CMatch('__guarded_by'), ' '),
+ (CMatch('__pt_guarded_by'), ' '),
+
+ (CMatch('__cacheline_group_(begin|end)'), ''),
+
+ (CMatch('struct_group'), r'\2'),
+ (CMatch('struct_group_attr'), r'\3'),
+ (CMatch('struct_group_tagged'), r'struct \1 { \3+ } \2;'),
+ (CMatch('__struct_group'), r'\4'),
+
+ (CMatch('__ETHTOOL_DECLARE_LINK_MODE_MASK'), r'DECLARE_BITMAP(\1, __ETHTOOL_LINK_MODE_MASK_NBITS)'),
+ (CMatch('DECLARE_PHY_INTERFACE_MASK',), r'DECLARE_BITMAP(\1, PHY_INTERFACE_MODE_MAX)'),
+ (CMatch('DECLARE_BITMAP'), r'unsigned long \1[BITS_TO_LONGS(\2)]'),
+
+ (CMatch('DECLARE_HASHTABLE'), r'unsigned long \1[1 << ((\2) - 1)]'),
+ (CMatch('DECLARE_KFIFO'), r'\2 *\1'),
+ (CMatch('DECLARE_KFIFO_PTR'), r'\2 *\1'),
+ (CMatch('(?:__)?DECLARE_FLEX_ARRAY'), r'\1 \2[]'),
+ (CMatch('DEFINE_DMA_UNMAP_ADDR'), r'dma_addr_t \1'),
+ (CMatch('DEFINE_DMA_UNMAP_LEN'), r'__u32 \1'),
+ (CMatch('VIRTIO_DECLARE_FEATURES'), r'union { u64 \1; u64 \1_array[VIRTIO_FEATURES_U64S]; }'),
+ ]
+
+ function_xforms = [
+ (CMatch('__printf'), ""),
+ (CMatch('__(?:re)?alloc_size'), ""),
+ (CMatch("__diagnose_as"), ""),
+ (CMatch("DECL_BUCKET_PARAMS"), r"\1, \2"),
+
+ (CMatch("__cond_acquires"), ""),
+ (CMatch("__cond_releases"), ""),
+ (CMatch("__acquires"), ""),
+ (CMatch("__releases"), ""),
+ (CMatch("__must_hold"), ""),
+ (CMatch("__must_not_hold"), ""),
+ (CMatch("__must_hold_shared"), ""),
+ (CMatch("__cond_acquires_shared"), ""),
+ (CMatch("__acquires_shared"), ""),
+ (CMatch("__releases_shared"), ""),
+ (CMatch("__attribute__"), ""),
+ ]
+
+ var_xforms = [
+ (CMatch('__guarded_by'), ""),
+ (CMatch('__pt_guarded_by'), ""),
+ (CMatch("LIST_HEAD"), r"struct list_head \1"),
+ ]
+
+ #: Transforms main dictionary used at apply_transforms().
+ xforms = {
+ "struct": struct_xforms,
+ "func": function_xforms,
+ "var": var_xforms,
+ }
+
+ @classmethod
+ def apply_transforms(cls, xform_type, text):
+ """
+ Mimic the behavior of kdoc_parser.apply_transforms() method.
+
+ For each element of STRUCT_XFORMS, apply apply_transforms.
+
+ There are two parameters:
+
+ - ``xform_type``
+ Can be ``func``, ``struct`` or ``var``;
+ - ``text``
+ The text where the sub patterns from CTransforms will be applied.
+ """
+ for search, subst in cls.xforms.get(xform_type):
+ text = search.sub(subst, text)
+
+ return text.strip()
+
+ cls.matcher = CMatch(r"struct_group[\w\_]*")
+
+ def test_struct_group(self):
+ """
+ Test struct_group using a pattern from
+ drivers/net/ethernet/asix/ax88796c_main.h.
+ """
+ line = """
+ struct tx_pkt_info {
+ struct_group(tx_overhead,
+ struct tx_sop_header sop;
+ struct tx_segment_header seg;
+ );
+ struct tx_eop_header eop;
+ u16 pkt_len;
+ u16 seq_num;
+ };
+ """
+ expected = """
+ struct tx_pkt_info {
+ struct tx_sop_header sop;
+ struct tx_segment_header seg;
+ struct tx_eop_header eop;
+ u16 pkt_len;
+ u16 seq_num;
+ };
+ """
+
+ result = self.apply_transforms("struct", line)
+ self.assertLogicallyEqual(result, expected)
+
+ def test_struct_group_attr(self):
+ """
+ Test two struct_group_attr using patterns from fs/smb/client/cifspdu.h.
+ """
+ line = """
+ typedef struct smb_com_open_rsp {
+ struct smb_hdr hdr; /* wct = 34 BB */
+ __u8 AndXCommand;
+ __u8 AndXReserved;
+ __le16 AndXOffset;
+ __u8 OplockLevel;
+ __u16 Fid;
+ __le32 CreateAction;
+ struct_group_attr(common_attributes,,
+ __le64 CreationTime;
+ __le64 LastAccessTime;
+ __le64 LastWriteTime;
+ __le64 ChangeTime;
+ __le32 FileAttributes;
+ );
+ __le64 AllocationSize;
+ __le64 EndOfFile;
+ __le16 FileType;
+ __le16 DeviceState;
+ __u8 DirectoryFlag;
+ __u16 ByteCount; /* bct = 0 */
+ } OPEN_RSP;
+ typedef struct {
+ struct_group_attr(common_attributes,,
+ __le64 CreationTime;
+ __le64 LastAccessTime;
+ __le64 LastWriteTime;
+ __le64 ChangeTime;
+ __le32 Attributes;
+ );
+ __u32 Pad1;
+ __le64 AllocationSize;
+ __le64 EndOfFile;
+ __le32 NumberOfLinks;
+ __u8 DeletePending;
+ __u8 Directory;
+ __u16 Pad2;
+ __le32 EASize;
+ __le32 FileNameLength;
+ union {
+ char __pad;
+ DECLARE_FLEX_ARRAY(char, FileName);
+ };
+ } FILE_ALL_INFO; /* level 0x107 QPathInfo */
+ """
+ expected = """
+ typedef struct smb_com_open_rsp {
+ struct smb_hdr hdr;
+ __u8 AndXCommand;
+ __u8 AndXReserved;
+ __le16 AndXOffset;
+ __u8 OplockLevel;
+ __u16 Fid;
+ __le32 CreateAction;
+ __le64 CreationTime;
+ __le64 LastAccessTime;
+ __le64 LastWriteTime;
+ __le64 ChangeTime;
+ __le32 FileAttributes;
+ __le64 AllocationSize;
+ __le64 EndOfFile;
+ __le16 FileType;
+ __le16 DeviceState;
+ __u8 DirectoryFlag;
+ __u16 ByteCount;
+ } OPEN_RSP;
+ typedef struct {
+ __le64 CreationTime;
+ __le64 LastAccessTime;
+ __le64 LastWriteTime;
+ __le64 ChangeTime;
+ __le32 Attributes;
+ __u32 Pad1;
+ __le64 AllocationSize;
+ __le64 EndOfFile;
+ __le32 NumberOfLinks;
+ __u8 DeletePending;
+ __u8 Directory;
+ __u16 Pad2;
+ __le32 EASize;
+ __le32 FileNameLength;
+ union {
+ char __pad;
+ char FileName[];
+ };
+ } FILE_ALL_INFO;
+ """
+
+ result = self.apply_transforms("struct", line)
+ self.assertLogicallyEqual(result, expected)
+
+ def test_raw_struct_group(self):
+ """
+ Test a __struct_group pattern from include/uapi/cxl/features.h.
+ """
+ line = """
+ struct cxl_mbox_get_sup_feats_out {
+ __struct_group(cxl_mbox_get_sup_feats_out_hdr, hdr, /* empty */,
+ __le16 num_entries;
+ __le16 supported_feats;
+ __u8 reserved[4];
+ );
+ struct cxl_feat_entry ents[] __counted_by_le(num_entries);
+ } __attribute__ ((__packed__));
+ """
+ expected = """
+ struct cxl_mbox_get_sup_feats_out {
+ __le16 num_entries;
+ __le16 supported_feats;
+ __u8 reserved[4];
+ struct cxl_feat_entry ents[];
+ };
+ """
+
+ result = self.apply_transforms("struct", line)
+ self.assertLogicallyEqual(result, expected)
+
+ def test_raw_struct_group_tagged(self):
+ r"""
+ Test cxl_regs with struct_group_tagged patterns from drivers/cxl/cxl.h.
+
+ NOTE:
+
+ This one has actually a violation from what kernel-doc would
+ expect: Kernel-doc regex expects only 3 members, but this is
+ actually defined as::
+
+ #define struct_group_tagged(TAG, NAME, MEMBERS...)
+
+ The replace expression there is::
+
+ struct \1 { \3 } \2;
+
+ but it should be really something like::
+
+ struct \1 { \3 \4 \5 \6 \7 \8 ... } \2;
+
+ a later fix would be needed to address it.
+
+ """
+ line = """
+ struct cxl_regs {
+ struct_group_tagged(cxl_component_regs, component,
+ void __iomem *hdm_decoder;
+ void __iomem *ras;
+ );
+
+
+ /* This is actually a violation: too much commas */
+ struct_group_tagged(cxl_device_regs, device_regs,
+ void __iomem *status, *mbox, *memdev;
+ );
+
+ struct_group_tagged(cxl_pmu_regs, pmu_regs,
+ void __iomem *pmu;
+ );
+
+ struct_group_tagged(cxl_rch_regs, rch_regs,
+ void __iomem *dport_aer;
+ );
+
+ struct_group_tagged(cxl_rcd_regs, rcd_regs,
+ void __iomem *rcd_pcie_cap;
+ );
+ };
+ """
+ expected = """
+ struct cxl_regs {
+ struct cxl_component_regs {
+ void __iomem *hdm_decoder;
+ void __iomem *ras;
+ } component;
+
+ struct cxl_device_regs {
+ void __iomem *status, *mbox, *memdev;
+ } device_regs;
+
+ struct cxl_pmu_regs {
+ void __iomem *pmu;
+ } pmu_regs;
+
+ struct cxl_rch_regs {
+ void __iomem *dport_aer;
+ } rch_regs;
+
+ struct cxl_rcd_regs {
+ void __iomem *rcd_pcie_cap;
+ } rcd_regs;
+ };
+ """
+
+ result = self.apply_transforms("struct", line)
+ self.assertLogicallyEqual(result, expected)
+
+ def test_struct_group_tagged_with_private(self):
+ """
+ Replace struct_group_tagged with private, using the same regex
+ for the replacement as what happens in xforms_lists.py.
+
+ As the private removal happens outside NestedGroup class, we manually
+ dropped the remaining part of the struct, to simulate what happens
+ at kdoc_parser.
+
+ Taken from include/net/page_pool/types.h
+ """
+ line = """
+ struct page_pool_params {
+ struct_group_tagged(page_pool_params_slow, slow,
+ struct net_device *netdev;
+ unsigned int queue_idx;
+ unsigned int flags;
+ /* private: only under "slow" struct */
+ unsigned int ignored;
+ );
+ /* Struct below shall not be ignored */
+ struct_group_tagged(page_pool_params_fast, fast,
+ unsigned int order;
+ unsigned int pool_size;
+ int nid;
+ struct device *dev;
+ struct napi_struct *napi;
+ enum dma_data_direction dma_dir;
+ unsigned int max_len;
+ unsigned int offset;
+ );
+ };
+ """
+ expected = """
+ struct page_pool_params {
+ struct page_pool_params_slow {
+ struct net_device *netdev;
+ unsigned int queue_idx;
+ unsigned int flags;
+ } slow;
+ struct page_pool_params_fast {
+ unsigned int order;
+ unsigned int pool_size;
+ int nid;
+ struct device *dev;
+ struct napi_struct *napi;
+ enum dma_data_direction dma_dir;
+ unsigned int max_len;
+ unsigned int offset;
+ } fast;
+ };
+ """
+
+ result = self.apply_transforms("struct", line)
+ self.assertLogicallyEqual(result, expected)
+
+ def test_struct_kcov(self):
+ """
+ """
+ line = """
+ struct kcov {
+ refcount_t refcount;
+ spinlock_t lock;
+ enum kcov_mode mode __guarded_by(&lock);
+ unsigned int size __guarded_by(&lock);
+ void *area __guarded_by(&lock);
+ struct task_struct *t __guarded_by(&lock);
+ bool remote;
+ unsigned int remote_size;
+ int sequence;
+ };
+ """
+ expected = """
+ """
+
+ result = self.apply_transforms("struct", line)
+ self.assertLogicallyEqual(result, expected)
+
+
+ def test_struct_kcov(self):
+ """
+ Test a struct from kernel/kcov.c.
+ """
+ line = """
+ struct kcov {
+ refcount_t refcount;
+ spinlock_t lock;
+ enum kcov_mode mode __guarded_by(&lock);
+ unsigned int size __guarded_by(&lock);
+ void *area __guarded_by(&lock);
+ struct task_struct *t __guarded_by(&lock);
+ bool remote;
+ unsigned int remote_size;
+ int sequence;
+ };
+ """
+ expected = """
+ struct kcov {
+ refcount_t refcount;
+ spinlock_t lock;
+ enum kcov_mode mode;
+ unsigned int size;
+ void *area;
+ struct task_struct *t;
+ bool remote;
+ unsigned int remote_size;
+ int sequence;
+ };
+ """
+
+ result = self.apply_transforms("struct", line)
+ self.assertLogicallyEqual(result, expected)
+
+ def test_vars_stackdepot(self):
+ """
+ Test guarded_by on vars from lib/stackdepot.c.
+ """
+ line = """
+ size_t pool_offset __guarded_by(&pool_lock) = DEPOT_POOL_SIZE;
+ __guarded_by(&pool_lock) LIST_HEAD(free_stacks);
+ void **stack_pools __pt_guarded_by(&pool_lock);
+ """
+ expected = """
+ size_t pool_offset = DEPOT_POOL_SIZE;
+ struct list_head free_stacks;
+ void **stack_pools;
+ """
+
+ result = self.apply_transforms("var", line)
+ self.assertLogicallyEqual(result, expected)
+
+ def test_functions_with_acquires_and_releases(self):
+ """
+ Test guarded_by on vars from lib/stackdepot.c.
+ """
+ line = """
+ bool prepare_report_consumer(unsigned long *flags,
+ const struct access_info *ai,
+ struct other_info *other_info) \
+ __cond_acquires(true, &report_lock);
+
+ int tcp_sigpool_start(unsigned int id, struct tcp_sigpool *c) \
+ __cond_acquires(0, RCU_BH);
+
+ bool undo_report_consumer(unsigned long *flags,
+ const struct access_info *ai,
+ struct other_info *other_info) \
+ __cond_releases(true, &report_lock);
+
+ void debugfs_enter_cancellation(struct file *file,
+ struct debugfs_cancellation *c) \
+ __acquires(cancellation);
+
+ void debugfs_leave_cancellation(struct file *file,
+ struct debugfs_cancellation *c) \
+ __releases(cancellation);
+
+ acpi_cpu_flags acpi_os_acquire_lock(acpi_spinlock lockp) \
+ __acquires(lockp);
+
+ void acpi_os_release_lock(acpi_spinlock lockp,
+ acpi_cpu_flags not_used) \
+ __releases(lockp)
+ """
+ expected = """
+ bool prepare_report_consumer(unsigned long *flags,
+ const struct access_info *ai,
+ struct other_info *other_info);
+
+ int tcp_sigpool_start(unsigned int id, struct tcp_sigpool *c);
+
+ bool undo_report_consumer(unsigned long *flags,
+ const struct access_info *ai,
+ struct other_info *other_info);
+
+ void debugfs_enter_cancellation(struct file *file,
+ struct debugfs_cancellation *c);
+
+ void debugfs_leave_cancellation(struct file *file,
+ struct debugfs_cancellation *c);
+
+ acpi_cpu_flags acpi_os_acquire_lock(acpi_spinlock lockp);
+
+ void acpi_os_release_lock(acpi_spinlock lockp,
+ acpi_cpu_flags not_used)
+ """
+
+ result = self.apply_transforms("func", line)
+ self.assertLogicallyEqual(result, expected)
+
+#
+# Run all tests
+#
+if __name__ == "__main__":
+ run_unittest(__file__)
diff --git a/tools/unittests/test_kdoc_parser.py b/tools/unittests/test_kdoc_parser.py
new file mode 100755
index 000000000000..c4a76ed13dbc
--- /dev/null
+++ b/tools/unittests/test_kdoc_parser.py
@@ -0,0 +1,560 @@
+#!/usr/bin/env python3
+# SPDX-License-Identifier: GPL-2.0
+# Copyright(c) 2026: Mauro Carvalho Chehab <mchehab@kernel.org>.
+#
+# pylint: disable=C0200,C0413,W0102,R0914
+
+"""
+Unit tests for kernel-doc parser.
+"""
+
+import logging
+import os
+import re
+import shlex
+import sys
+import unittest
+
+from textwrap import dedent
+from unittest.mock import patch, MagicMock, mock_open
+
+import yaml
+
+SRC_DIR = os.path.dirname(os.path.realpath(__file__))
+sys.path.insert(0, os.path.join(SRC_DIR, "../lib/python"))
+
+from kdoc.kdoc_files import KdocConfig
+from kdoc.kdoc_item import KdocItem
+from kdoc.kdoc_parser import KernelDoc
+from kdoc.kdoc_output import RestFormat, ManFormat
+
+from kdoc.xforms_lists import CTransforms
+
+from unittest_helper import TestUnits
+
+
+#
+# Test file
+#
+TEST_FILE = os.path.join(SRC_DIR, "kdoc-test.yaml")
+
+env = {
+ "yaml_file": TEST_FILE
+}
+
+#
+# Ancillary logic to clean whitespaces
+#
+#: Regex to help cleaning whitespaces
+RE_WHITESPC = re.compile(r"[ \t]++")
+RE_BEGINSPC = re.compile(r"^\s+", re.MULTILINE)
+RE_ENDSPC = re.compile(r"\s+$", re.MULTILINE)
+
+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)
+ val = RE_BEGINSPC.sub("", val)
+ val = RE_ENDSPC.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 classes to help mocking with logger and config
+#
+class MockLogging(logging.Handler):
+ """
+ Simple class to store everything on a list
+ """
+
+ def __init__(self, level=logging.NOTSET):
+ super().__init__(level)
+ self.messages = []
+ self.formatter = logging.Formatter()
+
+ def emit(self, record: logging.LogRecord) -> None:
+ """
+ Append a formatted record to self.messages.
+ """
+ try:
+ # The `format` method uses the handler's formatter.
+ message = self.format(record)
+ self.messages.append(message)
+ except Exception:
+ self.handleError(record)
+
+class MockKdocConfig(KdocConfig):
+ def __init__(self, *args, **kwargs):
+ super().__init__(*args, **kwargs)
+
+ self.log = logging.getLogger(__file__)
+ self.handler = MockLogging()
+ self.log.addHandler(self.handler)
+
+ def warning(self, msg):
+ """Ancillary routine to output a warning and increment error count."""
+
+ self.log.warning(msg)
+
+#
+# Helper class to generate KdocItem and validate its contents
+#
+# TODO: check self.config.handler.messages content
+#
+class GenerateKdocItem(unittest.TestCase):
+ """
+ Base class to run KernelDoc parser class
+ """
+
+ DEFAULT = vars(KdocItem("", "", "", 0))
+
+ config = MockKdocConfig()
+ xforms = CTransforms()
+
+ def setUp(self):
+ self.maxDiff = None
+
+ 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:
+ if not isinstance(e, dict):
+ e = vars(e)
+
+ 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)
+
+ other_stuff = d.get("other_stuff", {})
+ if "source" in other_stuff:
+ del other_stuff["source"]
+
+ for key, value in expected.items():
+ if key == "other_stuff":
+ if "source" in value:
+ del value["source"]
+
+ 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}")
+
+#
+# Ancillary function that replicates kdoc_files way to generate output
+#
+def cleanup_timestamp(text):
+ lines = text.split("\n")
+
+ for i, line in enumerate(lines):
+ if not line.startswith('.TH'):
+ continue
+
+ parts = shlex.split(line)
+ if len(parts) > 3:
+ parts[3] = ""
+
+ lines[i] = " ".join(parts)
+
+
+ return "\n".join(lines)
+
+def gen_output(fname, out_style, symbols, expected,
+ config=None, relax_whitespace=False):
+ """
+ Use the output class to return an output content from KdocItem symbols.
+ """
+
+ if not config:
+ config = MockKdocConfig()
+
+ out_style.set_config(config)
+
+ msg = out_style.output_symbols(fname, symbols)
+
+ result = clean_whitespc(msg, relax_whitespace)
+ result = cleanup_timestamp(result)
+
+ expected = clean_whitespc(expected, relax_whitespace)
+ expected = cleanup_timestamp(expected)
+
+ return result, expected
+
+#
+# Classes to be used by dynamic test generation from YAML
+#
+class CToKdocItem(GenerateKdocItem):
+ def setUp(self):
+ self.maxDiff = None
+
+ def run_parser_test(self, source, symbols, exports, fname):
+ if isinstance(symbols, dict):
+ symbols = [symbols]
+
+ if isinstance(exports, str):
+ exports=set([exports])
+ elif isinstance(exports, list):
+ exports=set(exports)
+
+ self.run_test(source, symbols, exports=exports,
+ fname=fname, relax_whitespace=True)
+
+class KdocItemToMan(unittest.TestCase):
+ out_style = ManFormat()
+
+ def setUp(self):
+ self.maxDiff = None
+
+ def run_out_test(self, fname, symbols, expected):
+ """
+ Generate output using out_style,
+ """
+ result, expected = gen_output(fname, self.out_style,
+ symbols, expected)
+
+ self.assertEqual(result, expected)
+
+class KdocItemToRest(unittest.TestCase):
+ out_style = RestFormat()
+
+ def setUp(self):
+ self.maxDiff = None
+
+ def run_out_test(self, fname, symbols, expected):
+ """
+ Generate output using out_style,
+ """
+ result, expected = gen_output(fname, self.out_style, symbols,
+ expected, relax_whitespace=True)
+
+ self.assertEqual(result, expected)
+
+
+class CToMan(unittest.TestCase):
+ out_style = ManFormat()
+ config = MockKdocConfig()
+ xforms = CTransforms()
+
+ def setUp(self):
+ self.maxDiff = None
+
+ def run_out_test(self, fname, source, expected):
+ """
+ Generate output using out_style,
+ """
+ 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()
+
+ result, expected = gen_output(fname, self.out_style,
+ entries, expected, config=self.config)
+
+ self.assertEqual(result, expected)
+
+
+class CToRest(unittest.TestCase):
+ out_style = RestFormat()
+ config = MockKdocConfig()
+ xforms = CTransforms()
+
+ def setUp(self):
+ self.maxDiff = None
+
+ def run_out_test(self, fname, source, expected):
+ """
+ Generate output using out_style,
+ """
+ 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()
+
+ result, expected = gen_output(fname, self.out_style, entries,
+ expected, relax_whitespace=True,
+ config=self.config)
+
+ self.assertEqual(result, expected)
+
+
+#
+# Selftest class
+#
+class TestSelfValidate(GenerateKdocItem):
+ """
+ Tests to check if logic inside GenerateKdocItem.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_start_lines': {
+ 'Description': 4,
+ 'Return': 7,
+ },
+ 'sections': {
+ 'Description': 'Does nothing\n\n',
+ 'Return': '\nalways return 0.\n'
+ },
+
+ 'sections_start_lines': {
+ 'Description': 4,
+ 'Return': 7,
+ },
+
+ 'parameterdescs': {'arg1': '@arg1 does nothing\n'},
+ 'parameterlist': ['arg1'],
+ 'parameterdesc_start_lines': {'arg1': 3},
+ 'parametertypes': {'arg1': 'char *arg1'},
+
+ 'other_stuff': {
+ 'func_macro': False,
+ 'functiontype': 'int',
+ 'purpose': 'Exported function',
+ 'typedef': False
+ },
+ }]
+
+ 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)
+
+#
+# Class and logic to create dynamic tests from YAML
+#
+
+class KernelDocDynamicTests():
+ """
+ Dynamically create a set of tests from a YAML file.
+ """
+
+ @classmethod
+ def create_parser_test(cls, name, fname, source, symbols, exports):
+ """
+ Return a function that will be attached to the test class.
+ """
+ def test_method(self):
+ """Lambda-like function to run tests with provided vars"""
+ self.run_parser_test(source, symbols, exports, fname)
+
+ test_method.__name__ = f"test_gen_{name}"
+
+ setattr(CToKdocItem, test_method.__name__, test_method)
+
+ @classmethod
+ def create_out_test(cls, name, fname, symbols, out_type, data):
+ """
+ Return a function that will be attached to the test class.
+ """
+ def test_method(self):
+ """Lambda-like function to run tests with provided vars"""
+ self.run_out_test(fname, symbols, data)
+
+ test_method.__name__ = f"test_{out_type}_{name}"
+
+ if out_type == "man":
+ setattr(KdocItemToMan, test_method.__name__, test_method)
+ else:
+ setattr(KdocItemToRest, test_method.__name__, test_method)
+
+ @classmethod
+ def create_src2out_test(cls, name, fname, source, out_type, data):
+ """
+ Return a function that will be attached to the test class.
+ """
+ def test_method(self):
+ """Lambda-like function to run tests with provided vars"""
+ self.run_out_test(fname, source, data)
+
+ test_method.__name__ = f"test_{out_type}_{name}"
+
+ if out_type == "man":
+ setattr(CToMan, test_method.__name__, test_method)
+ else:
+ setattr(CToRest, test_method.__name__, test_method)
+
+ @classmethod
+ def create_tests(cls):
+ """
+ Iterate over all scenarios and add a method to the class for each.
+
+ The logic in this function assumes a valid test that are compliant
+ with kdoc-test-schema.yaml. There is an unit test to check that.
+ As such, it picks mandatory values directly, and uses get() for the
+ optional ones.
+ """
+
+ test_file = os.environ.get("yaml_file", TEST_FILE)
+
+ with open(test_file, encoding="utf-8") as fp:
+ testset = yaml.safe_load(fp)
+
+ tests = testset["tests"]
+
+ for idx, test in enumerate(tests):
+ name = test["name"]
+ fname = test["fname"]
+ source = test["source"]
+ expected_list = test["expected"]
+
+ exports = test.get("exports", [])
+
+ #
+ # The logic below allows setting up to 5 types of test:
+ # 1. from source to kdoc_item: test KernelDoc class;
+ # 2. from kdoc_item to man: test ManOutput class;
+ # 3. from kdoc_item to rst: test RestOutput class;
+ # 4. from source to man without checking expected KdocItem;
+ # 5. from source to rst without checking expected KdocItem.
+ #
+ for expected in expected_list:
+ kdoc_item = expected.get("kdoc_item")
+ man = expected.get("man", [])
+ rst = expected.get("rst", [])
+
+ if kdoc_item:
+ if isinstance(kdoc_item, dict):
+ kdoc_item = [kdoc_item]
+
+ symbols = []
+
+ for arg in kdoc_item:
+ arg["fname"] = fname
+ arg["start_line"] = 1
+
+ symbols.append(KdocItem.from_dict(arg))
+
+ if source:
+ cls.create_parser_test(name, fname, source,
+ symbols, exports)
+
+ if man:
+ cls.create_out_test(name, fname, symbols, "man", man)
+
+ if rst:
+ cls.create_out_test(name, fname, symbols, "rst", rst)
+
+ elif source:
+ if man:
+ cls.create_src2out_test(name, fname, source, "man", man)
+
+ if rst:
+ cls.create_src2out_test(name, fname, source, "rst", rst)
+
+KernelDocDynamicTests.create_tests()
+
+#
+# Run all tests
+#
+if __name__ == "__main__":
+ runner = TestUnits()
+ parser = runner.parse_args()
+ parser.add_argument("-y", "--yaml-file", "--yaml",
+ help='Name of the yaml file to load')
+
+ args = parser.parse_args()
+
+ if args.yaml_file:
+ env["yaml_file"] = os.path.expanduser(args.yaml_file)
+
+ # Run tests with customized arguments
+ runner.run(__file__, parser=parser, args=args, env=env)
diff --git a/tools/unittests/test_kdoc_test_schema.py b/tools/unittests/test_kdoc_test_schema.py
new file mode 100755
index 000000000000..9eceeba00440
--- /dev/null
+++ b/tools/unittests/test_kdoc_test_schema.py
@@ -0,0 +1,94 @@
+#!/usr/bin/env python3
+# SPDX-License-Identifier: GPL-2.0
+"""
+Unit‑test driver for kernel‑doc YAML tests.
+
+Two kinds of tests are defined:
+
+* **Schema‑validation tests** – if ``jsonschema`` is available, the
+ YAML files in this directory are validated against the JSON‑Schema
+ described in ``kdoc-test-schema.yaml``. When the library is not
+ present, a warning is emitted and the validation step is simply
+ skipped – the dynamic kernel‑doc tests still run.
+
+* **Kernel‑doc tests** – dynamically generate one test method per
+ scenario in ``kdoc-test.yaml``. Each method simply forwards
+ the data to ``self.run_test`` – you only need to implement that
+ helper in your own code.
+
+File names are kept as module‑level constants so that the
+implementation stays completely independent of ``pathlib``.
+"""
+
+import os
+import sys
+import warnings
+import yaml
+import unittest
+from typing import Any, Dict, List
+
+SRC_DIR = os.path.dirname(os.path.realpath(__file__))
+sys.path.insert(0, os.path.join(SRC_DIR, "../lib/python"))
+
+from unittest_helper import run_unittest
+
+
+#
+# Files to read
+#
+BASE = os.path.realpath(os.path.dirname(__file__))
+
+SCHEMA_FILE = os.path.join(BASE, "kdoc-test-schema.yaml")
+TEST_FILE = os.path.join(BASE, "kdoc-test.yaml")
+
+#
+# Schema‑validation test
+#
+class TestYAMLSchemaValidation(unittest.TestCase):
+ """
+ Checks if TEST_FILE matches SCHEMA_FILE.
+ """
+
+ @classmethod
+ def setUpClass(cls):
+ """
+ Import jsonschema if available.
+ """
+
+ try:
+ from jsonschema import Draft7Validator
+ except ImportError:
+ print("Warning: jsonschema package not available. Skipping schema validation")
+ cls.validator = None
+ return
+
+ with open(SCHEMA_FILE, encoding="utf-8") as fp:
+ cls.schema = yaml.safe_load(fp)
+
+ cls.validator = Draft7Validator(cls.schema)
+
+ def test_kdoc_test_yaml_followsschema(self):
+ """
+ Run jsonschema validation if the validator is available.
+ If not, emit a warning and return without failing.
+ """
+ if self.validator is None:
+ return
+
+ with open(TEST_FILE, encoding="utf-8") as fp:
+ data = yaml.safe_load(fp)
+
+ errors = self.validator.iter_errors(data)
+
+ msgs = []
+ for error in errors:
+ msgs.append(error.message)
+
+ if msgs:
+ self.fail("Schema validation failed:\n\t" + "\n\t".join(msgs))
+
+# --------------------------------------------------------------------
+# Entry point
+# --------------------------------------------------------------------
+if __name__ == "__main__":
+ run_unittest(__file__)
diff --git a/tools/unittests/test_tokenizer.py b/tools/unittests/test_tokenizer.py
new file mode 100755
index 000000000000..d1f3c565b9cf
--- /dev/null
+++ b/tools/unittests/test_tokenizer.py
@@ -0,0 +1,469 @@
+#!/usr/bin/env python3
+
+"""
+Unit tests for struct/union member extractor class.
+"""
+
+
+import os
+import re
+import unittest
+import sys
+
+from unittest.mock import MagicMock
+
+SRC_DIR = os.path.dirname(os.path.realpath(__file__))
+sys.path.insert(0, os.path.join(SRC_DIR, "../lib/python"))
+
+from kdoc.c_lex import CToken, CTokenizer
+from unittest_helper import run_unittest
+
+#
+# List of tests.
+#
+# The code will dynamically generate one test for each key on this dictionary.
+#
+def tokens_to_list(tokens):
+ tuples = []
+
+ for tok in tokens:
+ if tok.kind == CToken.SPACE:
+ continue
+
+ tuples += [(tok.kind, tok.value, tok.level)]
+
+ return tuples
+
+
+def make_tokenizer_test(name, data):
+ """
+ Create a test named ``name`` using parameters given by ``data`` dict.
+ """
+
+ def test(self):
+ """In-lined lambda-like function to run the test"""
+
+ #
+ # Check if logger is working
+ #
+ if "log_msg" in data:
+ with self.assertLogs() as cm:
+ tokenizer = CTokenizer(data["source"])
+
+ msg_found = False
+ for result in cm.output:
+ if data["log_msg"] in result:
+ msg_found = True
+
+ self.assertTrue(msg_found, f"Missing log {data['log_msg']}")
+
+ return
+
+ #
+ # Check if tokenizer is producing expected results
+ #
+ tokens = CTokenizer(data["source"]).tokens
+
+ result = tokens_to_list(tokens)
+ expected = tokens_to_list(data["expected"])
+
+ self.assertEqual(result, expected, msg=f"{name}")
+
+ return test
+
+#: Tokenizer tests.
+TESTS_TOKENIZER = {
+ "__run__": make_tokenizer_test,
+
+ "basic_tokens": {
+ "source": """
+ int a; // comment
+ float b = 1.23;
+ """,
+ "expected": [
+ CToken(CToken.NAME, "int"),
+ CToken(CToken.NAME, "a"),
+ CToken(CToken.ENDSTMT, ";"),
+ CToken(CToken.COMMENT, "// comment"),
+ CToken(CToken.NAME, "float"),
+ CToken(CToken.NAME, "b"),
+ CToken(CToken.OP, "="),
+ CToken(CToken.NUMBER, "1.23"),
+ CToken(CToken.ENDSTMT, ";"),
+ ],
+ },
+
+ "depth_counters": {
+ "source": """
+ struct X {
+ int arr[10];
+ func(a[0], (b + c));
+ }
+ """,
+ "expected": [
+ CToken(CToken.STRUCT, "struct"),
+ CToken(CToken.NAME, "X"),
+ CToken(CToken.BEGIN, "{", brace_level=1),
+
+ CToken(CToken.NAME, "int", brace_level=1),
+ CToken(CToken.NAME, "arr", brace_level=1),
+ CToken(CToken.BEGIN, "[", brace_level=1, bracket_level=1),
+ CToken(CToken.NUMBER, "10", brace_level=1, bracket_level=1),
+ CToken(CToken.END, "]", brace_level=1),
+ CToken(CToken.ENDSTMT, ";", brace_level=1),
+ CToken(CToken.NAME, "func", brace_level=1),
+ CToken(CToken.BEGIN, "(", brace_level=1, paren_level=1),
+ CToken(CToken.NAME, "a", brace_level=1, paren_level=1),
+ CToken(CToken.BEGIN, "[", brace_level=1, paren_level=1, bracket_level=1),
+ CToken(CToken.NUMBER, "0", brace_level=1, paren_level=1, bracket_level=1),
+ CToken(CToken.END, "]", brace_level=1, paren_level=1),
+ CToken(CToken.PUNC, ",", brace_level=1, paren_level=1),
+ CToken(CToken.BEGIN, "(", brace_level=1, paren_level=2),
+ CToken(CToken.NAME, "b", brace_level=1, paren_level=2),
+ CToken(CToken.OP, "+", brace_level=1, paren_level=2),
+ CToken(CToken.NAME, "c", brace_level=1, paren_level=2),
+ CToken(CToken.END, ")", brace_level=1, paren_level=1),
+ CToken(CToken.END, ")", brace_level=1),
+ CToken(CToken.ENDSTMT, ";", brace_level=1),
+ CToken(CToken.END, "}"),
+ ],
+ },
+
+ "mismatch_error": {
+ "source": "int a$ = 5;", # $ is illegal
+ "log_msg": "Unexpected token",
+ },
+}
+
+def make_private_test(name, data):
+ """
+ Create a test named ``name`` using parameters given by ``data`` dict.
+ """
+
+ def test(self):
+ """In-lined lambda-like function to run the test"""
+ tokens = CTokenizer(data["source"])
+ result = str(tokens)
+
+ #
+ # Avoid whitespace false positives
+ #
+ result = re.sub(r"\s++", " ", result).strip()
+ expected = re.sub(r"\s++", " ", data["trimmed"]).strip()
+
+ msg = f"failed when parsing this source:\n{data['source']}"
+ self.assertEqual(result, expected, msg=msg)
+
+ return test
+
+#: Tests to check if CTokenizer is handling properly public/private comments.
+TESTS_PRIVATE = {
+ #
+ # Simplest case: no private. Ensure that trimming won't affect struct
+ #
+ "__run__": make_private_test,
+ "no private": {
+ "source": """
+ struct foo {
+ int a;
+ int b;
+ int c;
+ };
+ """,
+ "trimmed": """
+ struct foo {
+ int a;
+ int b;
+ int c;
+ };
+ """,
+ },
+
+ #
+ # Play "by the books" by always having a public in place
+ #
+
+ "balanced_private": {
+ "source": """
+ struct foo {
+ int a;
+ /* private: */
+ int b;
+ /* public: */
+ int c;
+ };
+ """,
+ "trimmed": """
+ struct foo {
+ int a;
+ int c;
+ };
+ """,
+ },
+
+ "balanced_non_greddy_private": {
+ "source": """
+ struct foo {
+ int a;
+ /* private: */
+ int b;
+ /* public: */
+ int c;
+ /* private: */
+ int d;
+ /* public: */
+ int e;
+
+ };
+ """,
+ "trimmed": """
+ struct foo {
+ int a;
+ int c;
+ int e;
+ };
+ """,
+ },
+
+ "balanced_inner_private": {
+ "source": """
+ struct foo {
+ struct {
+ int a;
+ /* private: ignore below */
+ int b;
+ /* public: but this should not be ignored */
+ };
+ int b;
+ };
+ """,
+ "trimmed": """
+ struct foo {
+ struct {
+ int a;
+ };
+ int b;
+ };
+ """,
+ },
+
+ #
+ # Test what happens if there's no public after private place
+ #
+
+ "unbalanced_private": {
+ "source": """
+ struct foo {
+ int a;
+ /* private: */
+ int b;
+ int c;
+ };
+ """,
+ "trimmed": """
+ struct foo {
+ int a;
+ };
+ """,
+ },
+
+ "unbalanced_inner_private": {
+ "source": """
+ struct foo {
+ struct {
+ int a;
+ /* private: ignore below */
+ int b;
+ /* but this should not be ignored */
+ };
+ int b;
+ };
+ """,
+ "trimmed": """
+ struct foo {
+ struct {
+ int a;
+ };
+ int b;
+ };
+ """,
+ },
+
+ "unbalanced_struct_group_tagged_with_private": {
+ "source": """
+ struct page_pool_params {
+ struct_group_tagged(page_pool_params_fast, fast,
+ unsigned int order;
+ unsigned int pool_size;
+ int nid;
+ struct device *dev;
+ struct napi_struct *napi;
+ enum dma_data_direction dma_dir;
+ unsigned int max_len;
+ unsigned int offset;
+ };
+ struct_group_tagged(page_pool_params_slow, slow,
+ struct net_device *netdev;
+ unsigned int queue_idx;
+ unsigned int flags;
+ /* private: used by test code only */
+ void (*init_callback)(netmem_ref netmem, void *arg);
+ void *init_arg;
+ };
+ };
+ """,
+ "trimmed": """
+ struct page_pool_params {
+ struct_group_tagged(page_pool_params_fast, fast,
+ unsigned int order;
+ unsigned int pool_size;
+ int nid;
+ struct device *dev;
+ struct napi_struct *napi;
+ enum dma_data_direction dma_dir;
+ unsigned int max_len;
+ unsigned int offset;
+ };
+ struct_group_tagged(page_pool_params_slow, slow,
+ struct net_device *netdev;
+ unsigned int queue_idx;
+ unsigned int flags;
+ };
+ };
+ """,
+ },
+
+ "unbalanced_two_struct_group_tagged_first_with_private": {
+ "source": """
+ struct page_pool_params {
+ struct_group_tagged(page_pool_params_slow, slow,
+ struct net_device *netdev;
+ unsigned int queue_idx;
+ unsigned int flags;
+ /* private: used by test code only */
+ void (*init_callback)(netmem_ref netmem, void *arg);
+ void *init_arg;
+ };
+ struct_group_tagged(page_pool_params_fast, fast,
+ unsigned int order;
+ unsigned int pool_size;
+ int nid;
+ struct device *dev;
+ struct napi_struct *napi;
+ enum dma_data_direction dma_dir;
+ unsigned int max_len;
+ unsigned int offset;
+ };
+ };
+ """,
+ "trimmed": """
+ struct page_pool_params {
+ struct_group_tagged(page_pool_params_slow, slow,
+ struct net_device *netdev;
+ unsigned int queue_idx;
+ unsigned int flags;
+ };
+ struct_group_tagged(page_pool_params_fast, fast,
+ unsigned int order;
+ unsigned int pool_size;
+ int nid;
+ struct device *dev;
+ struct napi_struct *napi;
+ enum dma_data_direction dma_dir;
+ unsigned int max_len;
+ unsigned int offset;
+ };
+ };
+ """,
+ },
+ "unbalanced_without_end_of_line": {
+ "source": """ \
+ struct page_pool_params { \
+ struct_group_tagged(page_pool_params_slow, slow, \
+ struct net_device *netdev; \
+ unsigned int queue_idx; \
+ unsigned int flags;
+ /* private: used by test code only */
+ void (*init_callback)(netmem_ref netmem, void *arg); \
+ void *init_arg; \
+ }; \
+ struct_group_tagged(page_pool_params_fast, fast, \
+ unsigned int order; \
+ unsigned int pool_size; \
+ int nid; \
+ struct device *dev; \
+ struct napi_struct *napi; \
+ enum dma_data_direction dma_dir; \
+ unsigned int max_len; \
+ unsigned int offset; \
+ }; \
+ };
+ """,
+ "trimmed": """
+ struct page_pool_params {
+ struct_group_tagged(page_pool_params_slow, slow,
+ struct net_device *netdev;
+ unsigned int queue_idx;
+ unsigned int flags;
+ };
+ struct_group_tagged(page_pool_params_fast, fast,
+ unsigned int order;
+ unsigned int pool_size;
+ int nid;
+ struct device *dev;
+ struct napi_struct *napi;
+ enum dma_data_direction dma_dir;
+ unsigned int max_len;
+ unsigned int offset;
+ };
+ };
+ """,
+ },
+}
+
+#: Dict containing all test groups fror CTokenizer
+TESTS = {
+ "TestPublicPrivate": TESTS_PRIVATE,
+ "TestTokenizer": TESTS_TOKENIZER,
+}
+
+def setUp(self):
+ self.maxDiff = None
+
+def build_test_class(group_name, table):
+ """
+ Dynamically creates a class instance using type() as a generator
+ for a new class derivated from unittest.TestCase.
+
+ We're opting to do it inside a function to avoid the risk of
+ changing the globals() dictionary.
+ """
+
+ class_dict = {
+ "setUp": setUp
+ }
+
+ run = table["__run__"]
+
+ for test_name, data in table.items():
+ if test_name == "__run__":
+ continue
+
+ class_dict[f"test_{test_name}"] = run(test_name, data)
+
+ cls = type(group_name, (unittest.TestCase,), class_dict)
+
+ return cls.__name__, cls
+
+#
+# Create classes and add them to the global dictionary
+#
+for group, table in TESTS.items():
+ t = build_test_class(group, table)
+ globals()[t[0]] = t[1]
+
+#
+# main
+#
+if __name__ == "__main__":
+ run_unittest(__file__)