xref: /linux/tools/unittests/test_cmatch.py (revision 40286d6379aacfcc053253ef78dc78b09addffda)
1#!/usr/bin/env python3
2# SPDX-License-Identifier: GPL-2.0
3# Copyright(c) 2026: Mauro Carvalho Chehab <mchehab@kernel.org>.
4#
5# pylint: disable=C0413,R0904
6
7
8"""
9Unit tests for kernel-doc CMatch.
10"""
11
12import os
13import re
14import sys
15import unittest
16
17
18# Import Python modules
19
20SRC_DIR = os.path.dirname(os.path.realpath(__file__))
21sys.path.insert(0, os.path.join(SRC_DIR, "../lib/python"))
22
23from kdoc.c_lex import CMatch
24from kdoc.kdoc_re import KernRe
25from unittest_helper import run_unittest
26
27#
28# Override unittest.TestCase to better compare diffs ignoring whitespaces
29#
30class TestCaseDiff(unittest.TestCase):
31    """
32    Disable maximum limit on diffs and add a method to better
33    handle diffs with whitespace differences.
34    """
35
36    @classmethod
37    def setUpClass(cls):
38        """Ensure that there won't be limit for diffs"""
39        cls.maxDiff = None
40
41
42#
43# Tests doing with different macros
44#
45
46class TestSearch(TestCaseDiff):
47    """
48    Test search mechanism
49    """
50
51    def test_search_acquires_simple(self):
52        line = "__acquires(ctx) foo();"
53        result = ", ".join(CMatch("__acquires").search(line))
54        self.assertEqual(result, "__acquires(ctx)")
55
56    def test_search_acquires_multiple(self):
57        line = "__acquires(ctx) __acquires(other) bar();"
58        result = ", ".join(CMatch("__acquires").search(line))
59        self.assertEqual(result, "__acquires(ctx), __acquires(other)")
60
61    def test_search_acquires_nested_paren(self):
62        line = "__acquires((ctx1, ctx2)) baz();"
63        result = ", ".join(CMatch("__acquires").search(line))
64        self.assertEqual(result, "__acquires((ctx1, ctx2))")
65
66    def test_search_must_hold(self):
67        line = "__must_hold(&lock) do_something();"
68        result = ", ".join(CMatch("__must_hold").search(line))
69        self.assertEqual(result, "__must_hold(&lock)")
70
71    def test_search_must_hold_shared(self):
72        line = "__must_hold_shared(RCU) other();"
73        result = ", ".join(CMatch("__must_hold_shared").search(line))
74        self.assertEqual(result, "__must_hold_shared(RCU)")
75
76    def test_search_no_false_positive(self):
77        line = "call__acquires(foo);  // should stay intact"
78        result = ", ".join(CMatch(r"__acquires").search(line))
79        self.assertEqual(result, "")
80
81    def test_search_no_macro_remains(self):
82        line = "do_something_else();"
83        result = ", ".join(CMatch("__acquires").search(line))
84        self.assertEqual(result, "")
85
86    def test_search_no_function(self):
87        line = "something"
88        result = ", ".join(CMatch(line).search(line))
89        self.assertEqual(result, "")
90
91#
92# Override unittest.TestCase to better compare diffs ignoring whitespaces
93#
94class TestCaseDiff(unittest.TestCase):
95    """
96    Disable maximum limit on diffs and add a method to better
97    handle diffs with whitespace differences.
98    """
99
100    @classmethod
101    def setUpClass(cls):
102        """Ensure that there won't be limit for diffs"""
103        cls.maxDiff = None
104
105    def assertLogicallyEqual(self, a, b):
106        """
107        Compare two results ignoring multiple whitespace differences.
108
109        This is useful to check more complex matches picked from examples.
110        On a plus side, we also don't need to use dedent.
111        Please notice that line breaks still need to match. We might
112        remove it at the regex, but this way, checking the diff is easier.
113        """
114        a = re.sub(r"[\t ]+", " ", a.strip())
115        b = re.sub(r"[\t ]+", " ", b.strip())
116
117        a = re.sub(r"\s+\n", "\n", a)
118        b = re.sub(r"\s+\n", "\n", b)
119
120        a = re.sub(" ;", ";", a)
121        b = re.sub(" ;", ";", b)
122
123        self.assertEqual(a, b)
124
125#
126# Tests doing with different macros
127#
128
129class TestSubMultipleMacros(TestCaseDiff):
130    """
131    Tests doing with different macros.
132
133    Here, we won't use assertLogicallyEqual. Instead, we'll check if each
134    of the expected patterns are present at the answer.
135    """
136
137    def test_acquires_simple(self):
138        """Simple replacement test with __acquires"""
139        line = "__acquires(ctx) foo();"
140        result = CMatch(r"__acquires").sub("REPLACED", line)
141
142        self.assertEqual("REPLACED foo();", result)
143
144    def test_acquires_multiple(self):
145        """Multiple __acquires"""
146        line = "__acquires(ctx) __acquires(other) bar();"
147        result = CMatch(r"__acquires").sub("REPLACED", line)
148
149        self.assertEqual("REPLACED REPLACED bar();", result)
150
151    def test_acquires_nested_paren(self):
152        """__acquires with nested pattern"""
153        line = "__acquires((ctx1, ctx2)) baz();"
154        result = CMatch(r"__acquires").sub("REPLACED", line)
155
156        self.assertEqual("REPLACED baz();", result)
157
158    def test_must_hold(self):
159        """__must_hold with a pointer"""
160        line = "__must_hold(&lock) do_something();"
161        result = CMatch(r"__must_hold").sub("REPLACED", line)
162
163        self.assertNotIn("__must_hold(", result)
164        self.assertIn("do_something();", result)
165
166    def test_must_hold_shared(self):
167        """__must_hold with an upercase defined value"""
168        line = "__must_hold_shared(RCU) other();"
169        result = CMatch(r"__must_hold_shared").sub("REPLACED", line)
170
171        self.assertNotIn("__must_hold_shared(", result)
172        self.assertIn("other();", result)
173
174    def test_no_false_positive(self):
175        """
176        Ensure that unrelated text containing similar patterns is preserved
177        """
178        line = "call__acquires(foo);  // should stay intact"
179        result = CMatch(r"\b__acquires").sub("REPLACED", line)
180
181        self.assertLogicallyEqual(result, "call__acquires(foo);")
182
183    def test_mixed_macros(self):
184        """Add a mix of macros"""
185        line = "__acquires(ctx) __releases(ctx) __must_hold(&lock) foo();"
186
187        result = CMatch(r"__acquires").sub("REPLACED", line)
188        result = CMatch(r"__releases").sub("REPLACED", result)
189        result = CMatch(r"__must_hold").sub("REPLACED", result)
190
191        self.assertNotIn("__acquires(", result)
192        self.assertNotIn("__releases(", result)
193        self.assertNotIn("__must_hold(", result)
194
195        self.assertIn("foo();", result)
196
197    def test_no_macro_remains(self):
198        """Ensures that unmatched macros are untouched"""
199        line = "do_something_else();"
200        result = CMatch(r"__acquires").sub("REPLACED", line)
201
202        self.assertEqual(result, line)
203
204    def test_no_function(self):
205        """Ensures that no functions will remain untouched"""
206        line = "something"
207        result = CMatch(line).sub("REPLACED", line)
208
209        self.assertEqual(result, line)
210
211#
212# Check if the diff is logically equivalent. To simplify, the tests here
213# use a single macro name for all replacements.
214#
215
216class TestSubSimple(TestCaseDiff):
217    """
218    Test argument replacements.
219
220    Here, the function name can be anything. So, we picked __attribute__(),
221    to mimic a macro found at the Kernel, but none of the replacements her
222    has any relationship with the Kernel usage.
223    """
224
225    MACRO = "__attribute__"
226
227    @classmethod
228    def setUpClass(cls):
229        """Define a CMatch to be used for all tests"""
230        cls.matcher = CMatch(cls.MACRO)
231
232    def test_sub_with_capture(self):
233        """Test all arguments replacement with a single arg"""
234        line = f"{self.MACRO}(&ctx)\nfoo();"
235
236        result = self.matcher.sub(r"ACQUIRED(\0)", line)
237
238        self.assertLogicallyEqual("ACQUIRED(&ctx)\nfoo();", result)
239
240    def test_sub_zero_placeholder(self):
241        """Test all arguments replacement with a multiple args"""
242        line = f"{self.MACRO}(arg1, arg2)\nbar();"
243
244        result = self.matcher.sub(r"REPLACED(\0)", line)
245
246        self.assertLogicallyEqual("REPLACED(arg1, arg2)\nbar();", result)
247
248    def test_sub_single_placeholder(self):
249        """Single replacement rule for \1"""
250        line = f"{self.MACRO}(ctx, boo)\nfoo();"
251        result = self.matcher.sub(r"ACQUIRED(\1)", line)
252
253        self.assertLogicallyEqual("ACQUIRED(ctx)\nfoo();", result)
254
255    def test_sub_multiple_placeholders(self):
256        """Replacement rule for both \1 and \2"""
257        line = f"{self.MACRO}(arg1, arg2)\nbar();"
258        result = self.matcher.sub(r"REPLACE(\1, \2)", line)
259
260        self.assertLogicallyEqual("REPLACE(arg1, arg2)\nbar();", result)
261
262    def test_sub_mixed_placeholders(self):
263        """Replacement rule for \0, \1 and additional text"""
264        line = f"{self.MACRO}(foo, bar)\nbaz();"
265        result = self.matcher.sub(r"ALL(\0) FIRST(\1)", line)
266
267        self.assertLogicallyEqual("ALL(foo, bar) FIRST(foo)\nbaz();", result)
268
269    def test_sub_no_placeholder(self):
270        """Replacement without placeholders"""
271        line = f"{self.MACRO}(arg)\nfoo();"
272        result = self.matcher.sub(r"NO_BACKREFS()", line)
273
274        self.assertLogicallyEqual("NO_BACKREFS()\nfoo();", result)
275
276    def test_sub_count_parameter(self):
277        """Verify that the algorithm stops after the requested count"""
278        line = f"{self.MACRO}(a1) x();\n{self.MACRO}(a2) y();"
279        result = self.matcher.sub(r"ONLY_FIRST(\1) ", line, count=1)
280
281        self.assertLogicallyEqual(f"ONLY_FIRST(a1) x();\n{self.MACRO}(a2) y();",
282                                  result)
283
284    def test_strip_multiple_acquires(self):
285        """Check if spaces between removed delimiters will be dropped"""
286        line = f"int {self.MACRO}(1)  {self.MACRO}(2 )   {self.MACRO}(3) foo;"
287        result = self.matcher.sub("", line)
288
289        self.assertLogicallyEqual(result, "int foo;")
290
291    def test_rise_early_greedy(self):
292        line = f"{self.MACRO}(a, b, c, d);"
293        sub = r"\1, \2+, \3"
294
295        with self.assertRaises(ValueError):
296            result = self.matcher.sub(sub, line)
297
298    def test_rise_multiple_greedy(self):
299        line = f"{self.MACRO}(a, b, c, d);"
300        sub = r"\1, \2+, \3+"
301
302        with self.assertRaises(ValueError):
303            result = self.matcher.sub(sub, line)
304
305#
306# Test replacements with slashrefs
307#
308
309
310class TestSubWithLocalXforms(TestCaseDiff):
311    """
312    Test diferent usecase patterns found at the Kernel.
313
314    Here, replacements using both CMatch and KernRe can be tested,
315    as it will import the actual replacement rules used by kernel-doc.
316    """
317
318    struct_xforms = [
319        (CMatch("__attribute__"), ' '),
320        (CMatch('__aligned'), ' '),
321        (CMatch('__counted_by'), ' '),
322        (CMatch('__counted_by_(le|be)'), ' '),
323        (CMatch('__guarded_by'), ' '),
324        (CMatch('__pt_guarded_by'), ' '),
325
326        (CMatch('__cacheline_group_(begin|end)'), ''),
327
328        (CMatch('struct_group'), r'\2'),
329        (CMatch('struct_group_attr'), r'\3'),
330        (CMatch('struct_group_tagged'), r'struct \1 { \3+ } \2;'),
331        (CMatch('__struct_group'), r'\4'),
332
333        (CMatch('__ETHTOOL_DECLARE_LINK_MODE_MASK'), r'DECLARE_BITMAP(\1, __ETHTOOL_LINK_MODE_MASK_NBITS)'),
334        (CMatch('DECLARE_PHY_INTERFACE_MASK',), r'DECLARE_BITMAP(\1, PHY_INTERFACE_MODE_MAX)'),
335        (CMatch('DECLARE_BITMAP'), r'unsigned long \1[BITS_TO_LONGS(\2)]'),
336
337        (CMatch('DECLARE_HASHTABLE'), r'unsigned long \1[1 << ((\2) - 1)]'),
338        (CMatch('DECLARE_KFIFO'), r'\2 *\1'),
339        (CMatch('DECLARE_KFIFO_PTR'), r'\2 *\1'),
340        (CMatch('(?:__)?DECLARE_FLEX_ARRAY'), r'\1 \2[]'),
341        (CMatch('DEFINE_DMA_UNMAP_ADDR'), r'dma_addr_t \1'),
342        (CMatch('DEFINE_DMA_UNMAP_LEN'), r'__u32 \1'),
343        (CMatch('VIRTIO_DECLARE_FEATURES'), r'union { u64 \1; u64 \1_array[VIRTIO_FEATURES_U64S]; }'),
344    ]
345
346    function_xforms = [
347        (CMatch('__printf'), ""),
348        (CMatch('__(?:re)?alloc_size'), ""),
349        (CMatch("__diagnose_as"), ""),
350        (CMatch("DECL_BUCKET_PARAMS"), r"\1, \2"),
351
352        (CMatch("__cond_acquires"), ""),
353        (CMatch("__cond_releases"), ""),
354        (CMatch("__acquires"), ""),
355        (CMatch("__releases"), ""),
356        (CMatch("__must_hold"), ""),
357        (CMatch("__must_not_hold"), ""),
358        (CMatch("__must_hold_shared"), ""),
359        (CMatch("__cond_acquires_shared"), ""),
360        (CMatch("__acquires_shared"), ""),
361        (CMatch("__releases_shared"), ""),
362        (CMatch("__attribute__"), ""),
363    ]
364
365    var_xforms = [
366        (CMatch('__guarded_by'), ""),
367        (CMatch('__pt_guarded_by'), ""),
368        (CMatch("LIST_HEAD"), r"struct list_head \1"),
369    ]
370
371    #: Transforms main dictionary used at apply_transforms().
372    xforms = {
373        "struct": struct_xforms,
374        "func": function_xforms,
375        "var": var_xforms,
376    }
377
378    @classmethod
379    def apply_transforms(cls, xform_type, text):
380        """
381        Mimic the behavior of kdoc_parser.apply_transforms() method.
382
383        For each element of STRUCT_XFORMS, apply apply_transforms.
384
385        There are two parameters:
386
387        - ``xform_type``
388            Can be ``func``, ``struct`` or ``var``;
389        - ``text``
390            The text where the sub patterns from CTransforms will be applied.
391        """
392        for search, subst in cls.xforms.get(xform_type):
393            text = search.sub(subst, text)
394
395        return text.strip()
396
397        cls.matcher = CMatch(r"struct_group[\w\_]*")
398
399    def test_struct_group(self):
400        """
401        Test struct_group using a pattern from
402        drivers/net/ethernet/asix/ax88796c_main.h.
403        """
404        line = """
405            struct tx_pkt_info {
406                    struct_group(tx_overhead,
407                            struct tx_sop_header sop;
408                            struct tx_segment_header seg;
409                    );
410                    struct tx_eop_header eop;
411                    u16 pkt_len;
412                    u16 seq_num;
413            };
414        """
415        expected = """
416            struct tx_pkt_info {
417                    struct tx_sop_header sop;
418                    struct tx_segment_header seg;
419                    struct tx_eop_header eop;
420                    u16 pkt_len;
421                    u16 seq_num;
422            };
423        """
424
425        result = self.apply_transforms("struct", line)
426        self.assertLogicallyEqual(result, expected)
427
428    def test_struct_group_attr(self):
429        """
430        Test two struct_group_attr using patterns from fs/smb/client/cifspdu.h.
431        """
432        line = """
433            typedef struct smb_com_open_rsp {
434                struct smb_hdr hdr;     /* wct = 34 BB */
435                __u8 AndXCommand;
436                __u8 AndXReserved;
437                __le16 AndXOffset;
438                __u8 OplockLevel;
439                __u16 Fid;
440                __le32 CreateAction;
441                struct_group_attr(common_attributes,,
442                    __le64 CreationTime;
443                    __le64 LastAccessTime;
444                    __le64 LastWriteTime;
445                    __le64 ChangeTime;
446                    __le32 FileAttributes;
447                );
448                __le64 AllocationSize;
449                __le64 EndOfFile;
450                __le16 FileType;
451                __le16 DeviceState;
452                __u8 DirectoryFlag;
453                __u16 ByteCount;        /* bct = 0 */
454            } OPEN_RSP;
455            typedef struct {
456                struct_group_attr(common_attributes,,
457                    __le64 CreationTime;
458                    __le64 LastAccessTime;
459                    __le64 LastWriteTime;
460                    __le64 ChangeTime;
461                    __le32 Attributes;
462                );
463                __u32 Pad1;
464                __le64 AllocationSize;
465                __le64 EndOfFile;
466                __le32 NumberOfLinks;
467                __u8 DeletePending;
468                __u8 Directory;
469                __u16 Pad2;
470                __le32 EASize;
471                __le32 FileNameLength;
472                union {
473                    char __pad;
474                    DECLARE_FLEX_ARRAY(char, FileName);
475                };
476            } FILE_ALL_INFO;       /* level 0x107 QPathInfo */
477        """
478        expected = """
479            typedef struct smb_com_open_rsp {
480                struct smb_hdr hdr;
481                __u8 AndXCommand;
482                __u8 AndXReserved;
483                __le16 AndXOffset;
484                __u8 OplockLevel;
485                __u16 Fid;
486                __le32 CreateAction;
487                __le64 CreationTime;
488                __le64 LastAccessTime;
489                __le64 LastWriteTime;
490                __le64 ChangeTime;
491                __le32 FileAttributes;
492                __le64 AllocationSize;
493                __le64 EndOfFile;
494                __le16 FileType;
495                __le16 DeviceState;
496                __u8 DirectoryFlag;
497                __u16 ByteCount;
498            } OPEN_RSP;
499        typedef struct {
500            __le64 CreationTime;
501            __le64 LastAccessTime;
502            __le64 LastWriteTime;
503            __le64 ChangeTime;
504            __le32 Attributes;
505            __u32 Pad1;
506            __le64 AllocationSize;
507            __le64 EndOfFile;
508            __le32 NumberOfLinks;
509            __u8 DeletePending;
510            __u8 Directory;
511            __u16 Pad2;
512            __le32 EASize;
513            __le32 FileNameLength;
514            union {
515                char __pad;
516                char FileName[];
517            };
518        } FILE_ALL_INFO;
519        """
520
521        result = self.apply_transforms("struct", line)
522        self.assertLogicallyEqual(result, expected)
523
524    def test_raw_struct_group(self):
525        """
526        Test a __struct_group pattern from include/uapi/cxl/features.h.
527        """
528        line = """
529            struct cxl_mbox_get_sup_feats_out {
530                __struct_group(cxl_mbox_get_sup_feats_out_hdr, hdr, /* empty */,
531                    __le16 num_entries;
532                    __le16 supported_feats;
533                    __u8 reserved[4];
534                );
535                struct cxl_feat_entry ents[] __counted_by_le(num_entries);
536            } __attribute__ ((__packed__));
537        """
538        expected = """
539            struct cxl_mbox_get_sup_feats_out {
540                __le16 num_entries;
541                __le16 supported_feats;
542                __u8 reserved[4];
543                struct cxl_feat_entry ents[];
544            };
545        """
546
547        result = self.apply_transforms("struct", line)
548        self.assertLogicallyEqual(result, expected)
549
550    def test_raw_struct_group_tagged(self):
551        r"""
552        Test cxl_regs with struct_group_tagged patterns from drivers/cxl/cxl.h.
553
554        NOTE:
555
556            This one has actually a violation from what kernel-doc would
557            expect: Kernel-doc regex expects only 3 members, but this is
558            actually defined as::
559
560                #define struct_group_tagged(TAG, NAME, MEMBERS...)
561
562            The replace expression there is::
563
564                struct \1 { \3 } \2;
565
566            but it should be really something like::
567
568                struct \1 { \3 \4 \5 \6 \7 \8 ... } \2;
569
570            a later fix would be needed to address it.
571
572        """
573        line = """
574            struct cxl_regs {
575                struct_group_tagged(cxl_component_regs, component,
576                    void __iomem *hdm_decoder;
577                    void __iomem *ras;
578                );
579
580
581                /* This is actually a violation: too much commas */
582                struct_group_tagged(cxl_device_regs, device_regs,
583                    void __iomem *status, *mbox, *memdev;
584                );
585
586                struct_group_tagged(cxl_pmu_regs, pmu_regs,
587                    void __iomem *pmu;
588                );
589
590                struct_group_tagged(cxl_rch_regs, rch_regs,
591                    void __iomem *dport_aer;
592                );
593
594                struct_group_tagged(cxl_rcd_regs, rcd_regs,
595                    void __iomem *rcd_pcie_cap;
596                );
597            };
598        """
599        expected = """
600        struct cxl_regs {
601            struct cxl_component_regs {
602                void __iomem *hdm_decoder;
603                void __iomem *ras;
604            } component;
605
606            struct cxl_device_regs {
607                void __iomem *status, *mbox, *memdev;
608            } device_regs;
609
610            struct cxl_pmu_regs {
611                void __iomem *pmu;
612            } pmu_regs;
613
614            struct cxl_rch_regs {
615                void __iomem *dport_aer;
616            } rch_regs;
617
618            struct cxl_rcd_regs {
619                void __iomem *rcd_pcie_cap;
620            } rcd_regs;
621        };
622        """
623
624        result = self.apply_transforms("struct", line)
625        self.assertLogicallyEqual(result, expected)
626
627    def test_struct_group_tagged_with_private(self):
628        """
629        Replace struct_group_tagged with private, using the same regex
630        for the replacement as what happens in xforms_lists.py.
631
632        As the private removal happens outside NestedGroup class, we manually
633        dropped the remaining part of the struct, to simulate what happens
634        at kdoc_parser.
635
636        Taken from include/net/page_pool/types.h
637        """
638        line = """
639            struct page_pool_params {
640                struct_group_tagged(page_pool_params_slow, slow,
641                                    struct net_device *netdev;
642                                    unsigned int queue_idx;
643                                    unsigned int    flags;
644                                    /* private: only under "slow" struct */
645                                    unsigned int ignored;
646                );
647                /* Struct below shall not be ignored */
648                struct_group_tagged(page_pool_params_fast, fast,
649                                    unsigned int    order;
650                                    unsigned int    pool_size;
651                                    int             nid;
652                                    struct device   *dev;
653                                    struct napi_struct *napi;
654                                    enum dma_data_direction dma_dir;
655                                    unsigned int    max_len;
656                                    unsigned int    offset;
657                );
658            };
659        """
660        expected = """
661            struct page_pool_params {
662                struct page_pool_params_slow {
663                    struct net_device *netdev;
664                    unsigned int queue_idx;
665                    unsigned int    flags;
666                } slow;
667                struct page_pool_params_fast {
668                    unsigned int order;
669                    unsigned int    pool_size;
670                    int             nid;
671                    struct device   *dev;
672                    struct napi_struct *napi;
673                    enum dma_data_direction dma_dir;
674                    unsigned int    max_len;
675                    unsigned int    offset;
676                } fast;
677            };
678        """
679
680        result = self.apply_transforms("struct", line)
681        self.assertLogicallyEqual(result, expected)
682
683    def test_struct_kcov(self):
684        """
685        """
686        line = """
687            struct kcov {
688                refcount_t              refcount;
689                spinlock_t              lock;
690                enum kcov_mode          mode __guarded_by(&lock);
691                unsigned int            size __guarded_by(&lock);
692                void                    *area __guarded_by(&lock);
693                struct task_struct      *t __guarded_by(&lock);
694                bool                    remote;
695                unsigned int            remote_size;
696                int                     sequence;
697            };
698        """
699        expected = """
700        """
701
702        result = self.apply_transforms("struct", line)
703        self.assertLogicallyEqual(result, expected)
704
705
706    def test_struct_kcov(self):
707        """
708        Test a struct from kernel/kcov.c.
709        """
710        line = """
711            struct kcov {
712                refcount_t              refcount;
713                spinlock_t              lock;
714                enum kcov_mode          mode __guarded_by(&lock);
715                unsigned int            size __guarded_by(&lock);
716                void                    *area __guarded_by(&lock);
717                struct task_struct      *t __guarded_by(&lock);
718                bool                    remote;
719                unsigned int            remote_size;
720                int                     sequence;
721            };
722        """
723        expected = """
724            struct kcov {
725                refcount_t              refcount;
726                spinlock_t              lock;
727                enum kcov_mode          mode;
728                unsigned int            size;
729                void                    *area;
730                struct task_struct      *t;
731                bool                    remote;
732                unsigned int            remote_size;
733                int                     sequence;
734            };
735        """
736
737        result = self.apply_transforms("struct", line)
738        self.assertLogicallyEqual(result, expected)
739
740    def test_vars_stackdepot(self):
741        """
742        Test guarded_by on vars from lib/stackdepot.c.
743        """
744        line = """
745            size_t pool_offset __guarded_by(&pool_lock) = DEPOT_POOL_SIZE;
746            __guarded_by(&pool_lock) LIST_HEAD(free_stacks);
747            void **stack_pools __pt_guarded_by(&pool_lock);
748        """
749        expected = """
750            size_t pool_offset = DEPOT_POOL_SIZE;
751            struct list_head free_stacks;
752            void **stack_pools;
753        """
754
755        result = self.apply_transforms("var", line)
756        self.assertLogicallyEqual(result, expected)
757
758    def test_functions_with_acquires_and_releases(self):
759        """
760        Test guarded_by on vars from lib/stackdepot.c.
761        """
762        line = """
763            bool prepare_report_consumer(unsigned long *flags,
764                                         const struct access_info *ai,
765                                         struct other_info *other_info) \
766                                        __cond_acquires(true, &report_lock);
767
768            int tcp_sigpool_start(unsigned int id, struct tcp_sigpool *c) \
769                                  __cond_acquires(0, RCU_BH);
770
771            bool undo_report_consumer(unsigned long *flags,
772                                      const struct access_info *ai,
773                                      struct other_info *other_info) \
774                                     __cond_releases(true, &report_lock);
775
776            void debugfs_enter_cancellation(struct file *file,
777                                            struct debugfs_cancellation *c) \
778                                           __acquires(cancellation);
779
780            void debugfs_leave_cancellation(struct file *file,
781                                            struct debugfs_cancellation *c) \
782                                           __releases(cancellation);
783
784            acpi_cpu_flags acpi_os_acquire_lock(acpi_spinlock lockp) \
785                                               __acquires(lockp);
786
787            void acpi_os_release_lock(acpi_spinlock lockp,
788                                      acpi_cpu_flags not_used) \
789                                     __releases(lockp)
790        """
791        expected = """
792            bool prepare_report_consumer(unsigned long *flags,
793                                         const struct access_info *ai,
794                                         struct other_info *other_info);
795
796            int tcp_sigpool_start(unsigned int id, struct tcp_sigpool *c);
797
798            bool undo_report_consumer(unsigned long *flags,
799                                      const struct access_info *ai,
800                                      struct other_info *other_info);
801
802            void debugfs_enter_cancellation(struct file *file,
803                                            struct debugfs_cancellation *c);
804
805            void debugfs_leave_cancellation(struct file *file,
806                                            struct debugfs_cancellation *c);
807
808            acpi_cpu_flags acpi_os_acquire_lock(acpi_spinlock lockp);
809
810            void acpi_os_release_lock(acpi_spinlock lockp,
811                                      acpi_cpu_flags not_used)
812        """
813
814        result = self.apply_transforms("func", line)
815        self.assertLogicallyEqual(result, expected)
816
817#
818# Run all tests
819#
820if __name__ == "__main__":
821    run_unittest(__file__)
822