| #!/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('__counted_by_ptr'), ' '), | |
| (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__) |