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