xref: /linux/tools/unittests/test_cmatch.py (revision 056a5087d87ead77dedbe9cf5bde53b7cd4b4651)
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('__counted_by_ptr'), ' '),
324        (CMatch('__guarded_by'), ' '),
325        (CMatch('__pt_guarded_by'), ' '),
326
327        (CMatch('__cacheline_group_(begin|end)'), ''),
328
329        (CMatch('struct_group'), r'\2'),
330        (CMatch('struct_group_attr'), r'\3'),
331        (CMatch('struct_group_tagged'), r'struct \1 { \3+ } \2;'),
332        (CMatch('__struct_group'), r'\4'),
333
334        (CMatch('__ETHTOOL_DECLARE_LINK_MODE_MASK'), r'DECLARE_BITMAP(\1, __ETHTOOL_LINK_MODE_MASK_NBITS)'),
335        (CMatch('DECLARE_PHY_INTERFACE_MASK',), r'DECLARE_BITMAP(\1, PHY_INTERFACE_MODE_MAX)'),
336        (CMatch('DECLARE_BITMAP'), r'unsigned long \1[BITS_TO_LONGS(\2)]'),
337
338        (CMatch('DECLARE_HASHTABLE'), r'unsigned long \1[1 << ((\2) - 1)]'),
339        (CMatch('DECLARE_KFIFO'), r'\2 *\1'),
340        (CMatch('DECLARE_KFIFO_PTR'), r'\2 *\1'),
341        (CMatch('(?:__)?DECLARE_FLEX_ARRAY'), r'\1 \2[]'),
342        (CMatch('DEFINE_DMA_UNMAP_ADDR'), r'dma_addr_t \1'),
343        (CMatch('DEFINE_DMA_UNMAP_LEN'), r'__u32 \1'),
344        (CMatch('VIRTIO_DECLARE_FEATURES'), r'union { u64 \1; u64 \1_array[VIRTIO_FEATURES_U64S]; }'),
345    ]
346
347    function_xforms = [
348        (CMatch('__printf'), ""),
349        (CMatch('__(?:re)?alloc_size'), ""),
350        (CMatch("__diagnose_as"), ""),
351        (CMatch("DECL_BUCKET_PARAMS"), r"\1, \2"),
352
353        (CMatch("__cond_acquires"), ""),
354        (CMatch("__cond_releases"), ""),
355        (CMatch("__acquires"), ""),
356        (CMatch("__releases"), ""),
357        (CMatch("__must_hold"), ""),
358        (CMatch("__must_not_hold"), ""),
359        (CMatch("__must_hold_shared"), ""),
360        (CMatch("__cond_acquires_shared"), ""),
361        (CMatch("__acquires_shared"), ""),
362        (CMatch("__releases_shared"), ""),
363        (CMatch("__attribute__"), ""),
364    ]
365
366    var_xforms = [
367        (CMatch('__guarded_by'), ""),
368        (CMatch('__pt_guarded_by'), ""),
369        (CMatch("LIST_HEAD"), r"struct list_head \1"),
370    ]
371
372    #: Transforms main dictionary used at apply_transforms().
373    xforms = {
374        "struct": struct_xforms,
375        "func": function_xforms,
376        "var": var_xforms,
377    }
378
379    @classmethod
380    def apply_transforms(cls, xform_type, text):
381        """
382        Mimic the behavior of kdoc_parser.apply_transforms() method.
383
384        For each element of STRUCT_XFORMS, apply apply_transforms.
385
386        There are two parameters:
387
388        - ``xform_type``
389            Can be ``func``, ``struct`` or ``var``;
390        - ``text``
391            The text where the sub patterns from CTransforms will be applied.
392        """
393        for search, subst in cls.xforms.get(xform_type):
394            text = search.sub(subst, text)
395
396        return text.strip()
397
398        cls.matcher = CMatch(r"struct_group[\w\_]*")
399
400    def test_struct_group(self):
401        """
402        Test struct_group using a pattern from
403        drivers/net/ethernet/asix/ax88796c_main.h.
404        """
405        line = """
406            struct tx_pkt_info {
407                    struct_group(tx_overhead,
408                            struct tx_sop_header sop;
409                            struct tx_segment_header seg;
410                    );
411                    struct tx_eop_header eop;
412                    u16 pkt_len;
413                    u16 seq_num;
414            };
415        """
416        expected = """
417            struct tx_pkt_info {
418                    struct tx_sop_header sop;
419                    struct tx_segment_header seg;
420                    struct tx_eop_header eop;
421                    u16 pkt_len;
422                    u16 seq_num;
423            };
424        """
425
426        result = self.apply_transforms("struct", line)
427        self.assertLogicallyEqual(result, expected)
428
429    def test_struct_group_attr(self):
430        """
431        Test two struct_group_attr using patterns from fs/smb/client/cifspdu.h.
432        """
433        line = """
434            typedef struct smb_com_open_rsp {
435                struct smb_hdr hdr;     /* wct = 34 BB */
436                __u8 AndXCommand;
437                __u8 AndXReserved;
438                __le16 AndXOffset;
439                __u8 OplockLevel;
440                __u16 Fid;
441                __le32 CreateAction;
442                struct_group_attr(common_attributes,,
443                    __le64 CreationTime;
444                    __le64 LastAccessTime;
445                    __le64 LastWriteTime;
446                    __le64 ChangeTime;
447                    __le32 FileAttributes;
448                );
449                __le64 AllocationSize;
450                __le64 EndOfFile;
451                __le16 FileType;
452                __le16 DeviceState;
453                __u8 DirectoryFlag;
454                __u16 ByteCount;        /* bct = 0 */
455            } OPEN_RSP;
456            typedef struct {
457                struct_group_attr(common_attributes,,
458                    __le64 CreationTime;
459                    __le64 LastAccessTime;
460                    __le64 LastWriteTime;
461                    __le64 ChangeTime;
462                    __le32 Attributes;
463                );
464                __u32 Pad1;
465                __le64 AllocationSize;
466                __le64 EndOfFile;
467                __le32 NumberOfLinks;
468                __u8 DeletePending;
469                __u8 Directory;
470                __u16 Pad2;
471                __le32 EASize;
472                __le32 FileNameLength;
473                union {
474                    char __pad;
475                    DECLARE_FLEX_ARRAY(char, FileName);
476                };
477            } FILE_ALL_INFO;       /* level 0x107 QPathInfo */
478        """
479        expected = """
480            typedef struct smb_com_open_rsp {
481                struct smb_hdr hdr;
482                __u8 AndXCommand;
483                __u8 AndXReserved;
484                __le16 AndXOffset;
485                __u8 OplockLevel;
486                __u16 Fid;
487                __le32 CreateAction;
488                __le64 CreationTime;
489                __le64 LastAccessTime;
490                __le64 LastWriteTime;
491                __le64 ChangeTime;
492                __le32 FileAttributes;
493                __le64 AllocationSize;
494                __le64 EndOfFile;
495                __le16 FileType;
496                __le16 DeviceState;
497                __u8 DirectoryFlag;
498                __u16 ByteCount;
499            } OPEN_RSP;
500        typedef struct {
501            __le64 CreationTime;
502            __le64 LastAccessTime;
503            __le64 LastWriteTime;
504            __le64 ChangeTime;
505            __le32 Attributes;
506            __u32 Pad1;
507            __le64 AllocationSize;
508            __le64 EndOfFile;
509            __le32 NumberOfLinks;
510            __u8 DeletePending;
511            __u8 Directory;
512            __u16 Pad2;
513            __le32 EASize;
514            __le32 FileNameLength;
515            union {
516                char __pad;
517                char FileName[];
518            };
519        } FILE_ALL_INFO;
520        """
521
522        result = self.apply_transforms("struct", line)
523        self.assertLogicallyEqual(result, expected)
524
525    def test_raw_struct_group(self):
526        """
527        Test a __struct_group pattern from include/uapi/cxl/features.h.
528        """
529        line = """
530            struct cxl_mbox_get_sup_feats_out {
531                __struct_group(cxl_mbox_get_sup_feats_out_hdr, hdr, /* empty */,
532                    __le16 num_entries;
533                    __le16 supported_feats;
534                    __u8 reserved[4];
535                );
536                struct cxl_feat_entry ents[] __counted_by_le(num_entries);
537            } __attribute__ ((__packed__));
538        """
539        expected = """
540            struct cxl_mbox_get_sup_feats_out {
541                __le16 num_entries;
542                __le16 supported_feats;
543                __u8 reserved[4];
544                struct cxl_feat_entry ents[];
545            };
546        """
547
548        result = self.apply_transforms("struct", line)
549        self.assertLogicallyEqual(result, expected)
550
551    def test_raw_struct_group_tagged(self):
552        r"""
553        Test cxl_regs with struct_group_tagged patterns from drivers/cxl/cxl.h.
554
555        NOTE:
556
557            This one has actually a violation from what kernel-doc would
558            expect: Kernel-doc regex expects only 3 members, but this is
559            actually defined as::
560
561                #define struct_group_tagged(TAG, NAME, MEMBERS...)
562
563            The replace expression there is::
564
565                struct \1 { \3 } \2;
566
567            but it should be really something like::
568
569                struct \1 { \3 \4 \5 \6 \7 \8 ... } \2;
570
571            a later fix would be needed to address it.
572
573        """
574        line = """
575            struct cxl_regs {
576                struct_group_tagged(cxl_component_regs, component,
577                    void __iomem *hdm_decoder;
578                    void __iomem *ras;
579                );
580
581
582                /* This is actually a violation: too much commas */
583                struct_group_tagged(cxl_device_regs, device_regs,
584                    void __iomem *status, *mbox, *memdev;
585                );
586
587                struct_group_tagged(cxl_pmu_regs, pmu_regs,
588                    void __iomem *pmu;
589                );
590
591                struct_group_tagged(cxl_rch_regs, rch_regs,
592                    void __iomem *dport_aer;
593                );
594
595                struct_group_tagged(cxl_rcd_regs, rcd_regs,
596                    void __iomem *rcd_pcie_cap;
597                );
598            };
599        """
600        expected = """
601        struct cxl_regs {
602            struct cxl_component_regs {
603                void __iomem *hdm_decoder;
604                void __iomem *ras;
605            } component;
606
607            struct cxl_device_regs {
608                void __iomem *status, *mbox, *memdev;
609            } device_regs;
610
611            struct cxl_pmu_regs {
612                void __iomem *pmu;
613            } pmu_regs;
614
615            struct cxl_rch_regs {
616                void __iomem *dport_aer;
617            } rch_regs;
618
619            struct cxl_rcd_regs {
620                void __iomem *rcd_pcie_cap;
621            } rcd_regs;
622        };
623        """
624
625        result = self.apply_transforms("struct", line)
626        self.assertLogicallyEqual(result, expected)
627
628    def test_struct_group_tagged_with_private(self):
629        """
630        Replace struct_group_tagged with private, using the same regex
631        for the replacement as what happens in xforms_lists.py.
632
633        As the private removal happens outside NestedGroup class, we manually
634        dropped the remaining part of the struct, to simulate what happens
635        at kdoc_parser.
636
637        Taken from include/net/page_pool/types.h
638        """
639        line = """
640            struct page_pool_params {
641                struct_group_tagged(page_pool_params_slow, slow,
642                                    struct net_device *netdev;
643                                    unsigned int queue_idx;
644                                    unsigned int    flags;
645                                    /* private: only under "slow" struct */
646                                    unsigned int ignored;
647                );
648                /* Struct below shall not be ignored */
649                struct_group_tagged(page_pool_params_fast, fast,
650                                    unsigned int    order;
651                                    unsigned int    pool_size;
652                                    int             nid;
653                                    struct device   *dev;
654                                    struct napi_struct *napi;
655                                    enum dma_data_direction dma_dir;
656                                    unsigned int    max_len;
657                                    unsigned int    offset;
658                );
659            };
660        """
661        expected = """
662            struct page_pool_params {
663                struct page_pool_params_slow {
664                    struct net_device *netdev;
665                    unsigned int queue_idx;
666                    unsigned int    flags;
667                } slow;
668                struct page_pool_params_fast {
669                    unsigned int order;
670                    unsigned int    pool_size;
671                    int             nid;
672                    struct device   *dev;
673                    struct napi_struct *napi;
674                    enum dma_data_direction dma_dir;
675                    unsigned int    max_len;
676                    unsigned int    offset;
677                } fast;
678            };
679        """
680
681        result = self.apply_transforms("struct", line)
682        self.assertLogicallyEqual(result, expected)
683
684    def test_struct_kcov(self):
685        """
686        """
687        line = """
688            struct kcov {
689                refcount_t              refcount;
690                spinlock_t              lock;
691                enum kcov_mode          mode __guarded_by(&lock);
692                unsigned int            size __guarded_by(&lock);
693                void                    *area __guarded_by(&lock);
694                struct task_struct      *t __guarded_by(&lock);
695                bool                    remote;
696                unsigned int            remote_size;
697                int                     sequence;
698            };
699        """
700        expected = """
701        """
702
703        result = self.apply_transforms("struct", line)
704        self.assertLogicallyEqual(result, expected)
705
706
707    def test_struct_kcov(self):
708        """
709        Test a struct from kernel/kcov.c.
710        """
711        line = """
712            struct kcov {
713                refcount_t              refcount;
714                spinlock_t              lock;
715                enum kcov_mode          mode __guarded_by(&lock);
716                unsigned int            size __guarded_by(&lock);
717                void                    *area __guarded_by(&lock);
718                struct task_struct      *t __guarded_by(&lock);
719                bool                    remote;
720                unsigned int            remote_size;
721                int                     sequence;
722            };
723        """
724        expected = """
725            struct kcov {
726                refcount_t              refcount;
727                spinlock_t              lock;
728                enum kcov_mode          mode;
729                unsigned int            size;
730                void                    *area;
731                struct task_struct      *t;
732                bool                    remote;
733                unsigned int            remote_size;
734                int                     sequence;
735            };
736        """
737
738        result = self.apply_transforms("struct", line)
739        self.assertLogicallyEqual(result, expected)
740
741    def test_vars_stackdepot(self):
742        """
743        Test guarded_by on vars from lib/stackdepot.c.
744        """
745        line = """
746            size_t pool_offset __guarded_by(&pool_lock) = DEPOT_POOL_SIZE;
747            __guarded_by(&pool_lock) LIST_HEAD(free_stacks);
748            void **stack_pools __pt_guarded_by(&pool_lock);
749        """
750        expected = """
751            size_t pool_offset = DEPOT_POOL_SIZE;
752            struct list_head free_stacks;
753            void **stack_pools;
754        """
755
756        result = self.apply_transforms("var", line)
757        self.assertLogicallyEqual(result, expected)
758
759    def test_functions_with_acquires_and_releases(self):
760        """
761        Test guarded_by on vars from lib/stackdepot.c.
762        """
763        line = """
764            bool prepare_report_consumer(unsigned long *flags,
765                                         const struct access_info *ai,
766                                         struct other_info *other_info) \
767                                        __cond_acquires(true, &report_lock);
768
769            int tcp_sigpool_start(unsigned int id, struct tcp_sigpool *c) \
770                                  __cond_acquires(0, RCU_BH);
771
772            bool undo_report_consumer(unsigned long *flags,
773                                      const struct access_info *ai,
774                                      struct other_info *other_info) \
775                                     __cond_releases(true, &report_lock);
776
777            void debugfs_enter_cancellation(struct file *file,
778                                            struct debugfs_cancellation *c) \
779                                           __acquires(cancellation);
780
781            void debugfs_leave_cancellation(struct file *file,
782                                            struct debugfs_cancellation *c) \
783                                           __releases(cancellation);
784
785            acpi_cpu_flags acpi_os_acquire_lock(acpi_spinlock lockp) \
786                                               __acquires(lockp);
787
788            void acpi_os_release_lock(acpi_spinlock lockp,
789                                      acpi_cpu_flags not_used) \
790                                     __releases(lockp)
791        """
792        expected = """
793            bool prepare_report_consumer(unsigned long *flags,
794                                         const struct access_info *ai,
795                                         struct other_info *other_info);
796
797            int tcp_sigpool_start(unsigned int id, struct tcp_sigpool *c);
798
799            bool undo_report_consumer(unsigned long *flags,
800                                      const struct access_info *ai,
801                                      struct other_info *other_info);
802
803            void debugfs_enter_cancellation(struct file *file,
804                                            struct debugfs_cancellation *c);
805
806            void debugfs_leave_cancellation(struct file *file,
807                                            struct debugfs_cancellation *c);
808
809            acpi_cpu_flags acpi_os_acquire_lock(acpi_spinlock lockp);
810
811            void acpi_os_release_lock(acpi_spinlock lockp,
812                                      acpi_cpu_flags not_used)
813        """
814
815        result = self.apply_transforms("func", line)
816        self.assertLogicallyEqual(result, expected)
817
818#
819# Run all tests
820#
821if __name__ == "__main__":
822    run_unittest(__file__)
823