Linux kernel mirror (for testing)
git.kernel.org/pub/scm/linux/kernel/git/torvalds/linux.git
kernel
os
linux
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__)