diff options
Diffstat (limited to 'tools/unittests')
| -rw-r--r-- | tools/unittests/kdoc-test-schema.yaml | 156 | ||||
| -rw-r--r-- | tools/unittests/kdoc-test.yaml | 1698 | ||||
| -rwxr-xr-x | tools/unittests/run.py | 17 | ||||
| -rwxr-xr-x | tools/unittests/test_cmatch.py | 821 | ||||
| -rwxr-xr-x | tools/unittests/test_kdoc_parser.py | 560 | ||||
| -rwxr-xr-x | tools/unittests/test_kdoc_test_schema.py | 94 | ||||
| -rwxr-xr-x | tools/unittests/test_tokenizer.py | 469 |
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__) |
