unittests: add a testbench to check public/private kdoc comments

Add unit tests to check if the public/private and comments strip
is working properly.

Running it shows that, on several cases, public/private is not
doing what it is expected:

  test_private:
    TestPublicPrivate:
        test balanced_inner_private:                                 OK
        test balanced_non_greddy_private:                            OK
        test balanced_private:                                       OK
        test no private:                                             OK
        test unbalanced_inner_private:                               FAIL
        test unbalanced_private:                                     FAIL
        test unbalanced_struct_group_tagged_with_private:            FAIL
        test unbalanced_two_struct_group_tagged_first_with_private:  FAIL
        test unbalanced_without_end_of_line:                         FAIL

  Ran 9 tests

  FAILED (failures=5)

Signed-off-by: Mauro Carvalho Chehab <mchehab+huawei@kernel.org>
Message-ID: <144f4952e0cb74fe9c9adc117e9a21ec8aa1cc10.1773074166.git.mchehab+huawei@kernel.org>
Signed-off-by: Jonathan Corbet <corbet@lwn.net>
Message-ID: <c1574b5b67b0442de3fab570fe6de9bc28b17a75.1773770483.git.mchehab+huawei@kernel.org>
This commit is contained in:
Mauro Carvalho Chehab 2026-03-17 19:09:22 +01:00 committed by Jonathan Corbet
parent 023aabb6cc
commit c6c23449b3

331
tools/unittests/test_private.py Executable file
View File

@ -0,0 +1,331 @@
#!/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.kdoc_parser import trim_private_members
from unittest_helper import run_unittest
#
# List of tests.
#
# The code will dynamically generate one test for each key on this dictionary.
#
#: Tests to check if CTokenizer is handling properly public/private comments.
TESTS_PRIVATE = {
#
# Simplest case: no private. Ensure that trimming won't affect struct
#
"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;
};
};
""",
},
}
class TestPublicPrivate(unittest.TestCase):
"""
Main test class. Populated dynamically at runtime.
"""
def setUp(self):
self.maxDiff = None
def add_test(cls, name, source, trimmed):
"""
Dynamically add a test to the class
"""
def test(cls):
result = trim_private_members(source)
result = re.sub(r"\s++", " ", result).strip()
expected = re.sub(r"\s++", " ", trimmed).strip()
msg = f"failed when parsing this source:\n" + source
cls.assertEqual(result, expected, msg=msg)
test.__name__ = f'test {name}'
setattr(TestPublicPrivate, test.__name__, test)
#
# Populate TestPublicPrivate class
#
test_class = TestPublicPrivate()
for name, test in TESTS_PRIVATE.items():
test_class.add_test(name, test["source"], test["trimmed"])
#
# main
#
if __name__ == "__main__":
run_unittest(__file__)