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