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