xref: /linux/tools/unittests/test_tokenizer.py (revision 14b7775ef7471fbb9380048aabb3e96faa1e9123)
1#!/usr/bin/env python3
2
3"""
4Unit tests for struct/union member extractor class.
5"""
6
7
8import os
9import re
10import unittest
11import sys
12
13from unittest.mock import MagicMock
14
15SRC_DIR = os.path.dirname(os.path.realpath(__file__))
16sys.path.insert(0, os.path.join(SRC_DIR, "../lib/python"))
17
18from kdoc.c_lex import CToken, CTokenizer
19from unittest_helper import run_unittest
20
21#
22# List of tests.
23#
24# The code will dynamically generate one test for each key on this dictionary.
25#
26def tokens_to_list(tokens):
27    tuples = []
28
29    for tok in tokens:
30        if tok.kind == CToken.SPACE:
31            continue
32
33        tuples += [(tok.kind, tok.value, tok.level)]
34
35    return tuples
36
37
38def make_tokenizer_test(name, data):
39    """
40    Create a test named ``name`` using parameters given by ``data`` dict.
41    """
42
43    def test(self):
44        """In-lined lambda-like function to run the test"""
45
46        #
47        # Check if logger is working
48        #
49        if "log_msg" in data:
50            with self.assertLogs() as cm:
51                tokenizer = CTokenizer(data["source"])
52
53                msg_found = False
54                for result in cm.output:
55                    if data["log_msg"] in result:
56                        msg_found = True
57
58                self.assertTrue(msg_found, f"Missing log {data['log_msg']}")
59
60            return
61
62        #
63        # Check if tokenizer is producing expected results
64        #
65        tokens = CTokenizer(data["source"]).tokens
66
67        result = tokens_to_list(tokens)
68        expected = tokens_to_list(data["expected"])
69
70        self.assertEqual(result, expected, msg=f"{name}")
71
72    return test
73
74#: Tokenizer tests.
75TESTS_TOKENIZER = {
76    "__run__": make_tokenizer_test,
77
78    "basic_tokens": {
79        "source": """
80            int a; // comment
81            float b = 1.23;
82        """,
83        "expected": [
84            CToken(CToken.NAME, "int"),
85            CToken(CToken.NAME, "a"),
86            CToken(CToken.ENDSTMT, ";"),
87            CToken(CToken.COMMENT, "// comment"),
88            CToken(CToken.NAME, "float"),
89            CToken(CToken.NAME, "b"),
90            CToken(CToken.OP, "="),
91            CToken(CToken.NUMBER, "1.23"),
92            CToken(CToken.ENDSTMT, ";"),
93        ],
94    },
95
96    "depth_counters": {
97        "source": """
98            struct X {
99                int arr[10];
100                func(a[0], (b + c));
101            }
102        """,
103        "expected": [
104            CToken(CToken.STRUCT, "struct"),
105            CToken(CToken.NAME, "X"),
106            CToken(CToken.BEGIN, "{", brace_level=1),
107
108            CToken(CToken.NAME, "int", brace_level=1),
109            CToken(CToken.NAME, "arr", brace_level=1),
110            CToken(CToken.BEGIN, "[", brace_level=1, bracket_level=1),
111            CToken(CToken.NUMBER, "10", brace_level=1, bracket_level=1),
112            CToken(CToken.END, "]", brace_level=1),
113            CToken(CToken.ENDSTMT, ";", brace_level=1),
114            CToken(CToken.NAME, "func", brace_level=1),
115            CToken(CToken.BEGIN, "(", brace_level=1, paren_level=1),
116            CToken(CToken.NAME, "a", brace_level=1, paren_level=1),
117            CToken(CToken.BEGIN, "[", brace_level=1, paren_level=1, bracket_level=1),
118            CToken(CToken.NUMBER, "0", brace_level=1, paren_level=1, bracket_level=1),
119            CToken(CToken.END, "]", brace_level=1, paren_level=1),
120            CToken(CToken.PUNC, ",", brace_level=1, paren_level=1),
121            CToken(CToken.BEGIN, "(", brace_level=1, paren_level=2),
122            CToken(CToken.NAME, "b", brace_level=1, paren_level=2),
123            CToken(CToken.OP, "+", brace_level=1, paren_level=2),
124            CToken(CToken.NAME, "c", brace_level=1, paren_level=2),
125            CToken(CToken.END, ")", brace_level=1, paren_level=1),
126            CToken(CToken.END, ")", brace_level=1),
127            CToken(CToken.ENDSTMT, ";", brace_level=1),
128            CToken(CToken.END, "}"),
129        ],
130    },
131
132    "mismatch_error": {
133        "source": "int a$ = 5;",          # $ is illegal
134        "log_msg": "Unexpected token",
135    },
136}
137
138def make_private_test(name, data):
139    """
140    Create a test named ``name`` using parameters given by ``data`` dict.
141    """
142
143    def test(self):
144        """In-lined lambda-like function to run the test"""
145        tokens = CTokenizer(data["source"])
146        result = str(tokens)
147
148        #
149        # Avoid whitespace false positives
150        #
151        result = re.sub(r"\s++", " ", result).strip()
152        expected = re.sub(r"\s++", " ", data["trimmed"]).strip()
153
154        msg = f"failed when parsing this source:\n{data['source']}"
155        self.assertEqual(result, expected, msg=msg)
156
157    return test
158
159#: Tests to check if CTokenizer is handling properly public/private comments.
160TESTS_PRIVATE = {
161    #
162    # Simplest case: no private. Ensure that trimming won't affect struct
163    #
164    "__run__": make_private_test,
165    "no private": {
166        "source": """
167            struct foo {
168                int a;
169                int b;
170                int c;
171            };
172        """,
173        "trimmed": """
174            struct foo {
175                int a;
176                int b;
177                int c;
178            };
179        """,
180    },
181
182    #
183    # Play "by the books" by always having a public in place
184    #
185
186    "balanced_private": {
187        "source": """
188            struct foo {
189                int a;
190                /* private: */
191                int b;
192                /* public: */
193                int c;
194            };
195        """,
196        "trimmed": """
197            struct foo {
198                int a;
199                int c;
200            };
201        """,
202    },
203
204    "balanced_non_greddy_private": {
205        "source": """
206            struct foo {
207                int a;
208                /* private: */
209                int b;
210                /* public: */
211                int c;
212                /* private: */
213                int d;
214                /* public: */
215                int e;
216
217            };
218        """,
219        "trimmed": """
220            struct foo {
221                int a;
222                int c;
223                int e;
224            };
225        """,
226    },
227
228    "balanced_inner_private": {
229        "source": """
230            struct foo {
231                struct {
232                    int a;
233                    /* private: ignore below */
234                    int b;
235                /* public: but this should not be ignored */
236                };
237                int b;
238            };
239        """,
240        "trimmed": """
241            struct foo {
242                struct {
243                    int a;
244                };
245                int b;
246            };
247        """,
248    },
249
250    #
251    # Test what happens if there's no public after private place
252    #
253
254    "unbalanced_private": {
255        "source": """
256            struct foo {
257                int a;
258                /* private: */
259                int b;
260                int c;
261            };
262        """,
263        "trimmed": """
264            struct foo {
265                int a;
266            };
267        """,
268    },
269
270    "unbalanced_inner_private": {
271        "source": """
272            struct foo {
273                struct {
274                    int a;
275                    /* private: ignore below */
276                    int b;
277                /* but this should not be ignored */
278                };
279                int b;
280            };
281        """,
282        "trimmed": """
283            struct foo {
284                struct {
285                    int a;
286                };
287                int b;
288            };
289        """,
290    },
291
292    "unbalanced_struct_group_tagged_with_private": {
293        "source": """
294            struct page_pool_params {
295                struct_group_tagged(page_pool_params_fast, fast,
296                        unsigned int    order;
297                        unsigned int    pool_size;
298                        int             nid;
299                        struct device   *dev;
300                        struct napi_struct *napi;
301                        enum dma_data_direction dma_dir;
302                        unsigned int    max_len;
303                        unsigned int    offset;
304                };
305                struct_group_tagged(page_pool_params_slow, slow,
306                        struct net_device *netdev;
307                        unsigned int queue_idx;
308                        unsigned int    flags;
309                        /* private: used by test code only */
310                        void (*init_callback)(netmem_ref netmem, void *arg);
311                        void *init_arg;
312                };
313            };
314        """,
315        "trimmed": """
316            struct page_pool_params {
317                struct_group_tagged(page_pool_params_fast, fast,
318                        unsigned int    order;
319                        unsigned int    pool_size;
320                        int             nid;
321                        struct device   *dev;
322                        struct napi_struct *napi;
323                        enum dma_data_direction dma_dir;
324                        unsigned int    max_len;
325                        unsigned int    offset;
326                };
327                struct_group_tagged(page_pool_params_slow, slow,
328                        struct net_device *netdev;
329                        unsigned int queue_idx;
330                        unsigned int    flags;
331                };
332            };
333        """,
334    },
335
336    "unbalanced_two_struct_group_tagged_first_with_private": {
337        "source": """
338            struct page_pool_params {
339                struct_group_tagged(page_pool_params_slow, slow,
340                        struct net_device *netdev;
341                        unsigned int queue_idx;
342                        unsigned int    flags;
343                        /* private: used by test code only */
344                        void (*init_callback)(netmem_ref netmem, void *arg);
345                        void *init_arg;
346                };
347                struct_group_tagged(page_pool_params_fast, fast,
348                        unsigned int    order;
349                        unsigned int    pool_size;
350                        int             nid;
351                        struct device   *dev;
352                        struct napi_struct *napi;
353                        enum dma_data_direction dma_dir;
354                        unsigned int    max_len;
355                        unsigned int    offset;
356                };
357            };
358        """,
359        "trimmed": """
360            struct page_pool_params {
361                struct_group_tagged(page_pool_params_slow, slow,
362                        struct net_device *netdev;
363                        unsigned int queue_idx;
364                        unsigned int    flags;
365                };
366                struct_group_tagged(page_pool_params_fast, fast,
367                        unsigned int    order;
368                        unsigned int    pool_size;
369                        int             nid;
370                        struct device   *dev;
371                        struct napi_struct *napi;
372                        enum dma_data_direction dma_dir;
373                        unsigned int    max_len;
374                        unsigned int    offset;
375                };
376            };
377        """,
378    },
379    "unbalanced_without_end_of_line": {
380        "source": """ \
381            struct page_pool_params { \
382                struct_group_tagged(page_pool_params_slow, slow, \
383                        struct net_device *netdev; \
384                        unsigned int queue_idx; \
385                        unsigned int    flags;
386                        /* private: used by test code only */
387                        void (*init_callback)(netmem_ref netmem, void *arg); \
388                        void *init_arg; \
389                }; \
390                struct_group_tagged(page_pool_params_fast, fast, \
391                        unsigned int    order; \
392                        unsigned int    pool_size; \
393                        int             nid; \
394                        struct device   *dev; \
395                        struct napi_struct *napi; \
396                        enum dma_data_direction dma_dir; \
397                        unsigned int    max_len; \
398                        unsigned int    offset; \
399                }; \
400            };
401        """,
402        "trimmed": """
403            struct page_pool_params {
404                struct_group_tagged(page_pool_params_slow, slow,
405                        struct net_device *netdev;
406                        unsigned int queue_idx;
407                        unsigned int    flags;
408                };
409                struct_group_tagged(page_pool_params_fast, fast,
410                        unsigned int    order;
411                        unsigned int    pool_size;
412                        int             nid;
413                        struct device   *dev;
414                        struct napi_struct *napi;
415                        enum dma_data_direction dma_dir;
416                        unsigned int    max_len;
417                        unsigned int    offset;
418                };
419            };
420        """,
421    },
422}
423
424#: Dict containing all test groups fror CTokenizer
425TESTS = {
426    "TestPublicPrivate": TESTS_PRIVATE,
427    "TestTokenizer": TESTS_TOKENIZER,
428}
429
430def setUp(self):
431    self.maxDiff = None
432
433def build_test_class(group_name, table):
434    """
435    Dynamically creates a class instance using type() as a generator
436    for a new class derivated from unittest.TestCase.
437
438    We're opting to do it inside a function to avoid the risk of
439    changing the globals() dictionary.
440    """
441
442    class_dict = {
443        "setUp": setUp
444    }
445
446    run = table["__run__"]
447
448    for test_name, data in table.items():
449        if test_name == "__run__":
450            continue
451
452        class_dict[f"test_{test_name}"] = run(test_name, data)
453
454    cls = type(group_name, (unittest.TestCase,), class_dict)
455
456    return cls.__name__, cls
457
458#
459# Create classes and add them to the global dictionary
460#
461for group, table in TESTS.items():
462    t = build_test_class(group, table)
463    globals()[t[0]] = t[1]
464
465#
466# main
467#
468if __name__ == "__main__":
469    run_unittest(__file__)
470