#!/usr/bin/env python3 """ Unit tests for struct/union member extractor class. """ import os import re import unittest import sys from unittest.mock import MagicMock SRC_DIR = os.path.dirname(os.path.realpath(__file__)) sys.path.insert(0, os.path.join(SRC_DIR, "../lib/python")) from kdoc.c_lex import CToken, CTokenizer from unittest_helper import run_unittest # # List of tests. # # The code will dynamically generate one test for each key on this dictionary. # def tokens_to_list(tokens): tuples = [] for tok in tokens: if tok.kind == CToken.SPACE: continue tuples += [(tok.kind, tok.value, tok.level)] return tuples def make_tokenizer_test(name, data): """ Create a test named ``name`` using parameters given by ``data`` dict. """ def test(self): """In-lined lambda-like function to run the test""" # # Check if logger is working # if "log_msg" in data: with self.assertLogs() as cm: tokenizer = CTokenizer(data["source"]) msg_found = False for result in cm.output: if data["log_msg"] in result: msg_found = True self.assertTrue(msg_found, f"Missing log {data['log_msg']}") return # # Check if tokenizer is producing expected results # tokens = CTokenizer(data["source"]).tokens result = tokens_to_list(tokens) expected = tokens_to_list(data["expected"]) self.assertEqual(result, expected, msg=f"{name}") return test #: Tokenizer tests. TESTS_TOKENIZER = { "__run__": make_tokenizer_test, "basic_tokens": { "source": """ int a; // comment float b = 1.23; """, "expected": [ CToken(CToken.NAME, "int"), CToken(CToken.NAME, "a"), CToken(CToken.ENDSTMT, ";"), CToken(CToken.COMMENT, "// comment"), CToken(CToken.NAME, "float"), CToken(CToken.NAME, "b"), CToken(CToken.OP, "="), CToken(CToken.NUMBER, "1.23"), CToken(CToken.ENDSTMT, ";"), ], }, "depth_counters": { "source": """ struct X { int arr[10]; func(a[0], (b + c)); } """, "expected": [ CToken(CToken.STRUCT, "struct"), CToken(CToken.NAME, "X"), CToken(CToken.BEGIN, "{", brace_level=1), CToken(CToken.NAME, "int", brace_level=1), CToken(CToken.NAME, "arr", brace_level=1), CToken(CToken.BEGIN, "[", brace_level=1, bracket_level=1), CToken(CToken.NUMBER, "10", brace_level=1, bracket_level=1), CToken(CToken.END, "]", brace_level=1), CToken(CToken.ENDSTMT, ";", brace_level=1), CToken(CToken.NAME, "func", brace_level=1), CToken(CToken.BEGIN, "(", brace_level=1, paren_level=1), CToken(CToken.NAME, "a", brace_level=1, paren_level=1), CToken(CToken.BEGIN, "[", brace_level=1, paren_level=1, bracket_level=1), CToken(CToken.NUMBER, "0", brace_level=1, paren_level=1, bracket_level=1), CToken(CToken.END, "]", brace_level=1, paren_level=1), CToken(CToken.PUNC, ",", brace_level=1, paren_level=1), CToken(CToken.BEGIN, "(", brace_level=1, paren_level=2), CToken(CToken.NAME, "b", brace_level=1, paren_level=2), CToken(CToken.OP, "+", brace_level=1, paren_level=2), CToken(CToken.NAME, "c", brace_level=1, paren_level=2), CToken(CToken.END, ")", brace_level=1, paren_level=1), CToken(CToken.END, ")", brace_level=1), CToken(CToken.ENDSTMT, ";", brace_level=1), CToken(CToken.END, "}"), ], }, "mismatch_error": { "source": "int a$ = 5;", # $ is illegal "log_msg": "Unexpected token", }, } def make_private_test(name, data): """ Create a test named ``name`` using parameters given by ``data`` dict. """ def test(self): """In-lined lambda-like function to run the test""" tokens = CTokenizer(data["source"]) result = str(tokens) # # Avoid whitespace false positives # result = re.sub(r"\s++", " ", result).strip() expected = re.sub(r"\s++", " ", data["trimmed"]).strip() msg = f"failed when parsing this source:\n{data['source']}" self.assertEqual(result, expected, msg=msg) return test #: Tests to check if CTokenizer is handling properly public/private comments. TESTS_PRIVATE = { # # Simplest case: no private. Ensure that trimming won't affect struct # "__run__": make_private_test, "no private": { "source": """ struct foo { int a; int b; int c; }; """, "trimmed": """ struct foo { int a; int b; int c; }; """, }, # # Play "by the books" by always having a public in place # "balanced_private": { "source": """ struct foo { int a; /* private: */ int b; /* public: */ int c; }; """, "trimmed": """ struct foo { int a; int c; }; """, }, "balanced_non_greddy_private": { "source": """ struct foo { int a; /* private: */ int b; /* public: */ int c; /* private: */ int d; /* public: */ int e; }; """, "trimmed": """ struct foo { int a; int c; int e; }; """, }, "balanced_inner_private": { "source": """ struct foo { struct { int a; /* private: ignore below */ int b; /* public: but this should not be ignored */ }; int b; }; """, "trimmed": """ struct foo { struct { int a; }; int b; }; """, }, # # Test what happens if there's no public after private place # "unbalanced_private": { "source": """ struct foo { int a; /* private: */ int b; int c; }; """, "trimmed": """ struct foo { int a; }; """, }, "unbalanced_inner_private": { "source": """ struct foo { struct { int a; /* private: ignore below */ int b; /* but this should not be ignored */ }; int b; }; """, "trimmed": """ struct foo { struct { int a; }; int b; }; """, }, "unbalanced_struct_group_tagged_with_private": { "source": """ struct page_pool_params { struct_group_tagged(page_pool_params_fast, fast, unsigned int order; unsigned int pool_size; int nid; struct device *dev; struct napi_struct *napi; enum dma_data_direction dma_dir; unsigned int max_len; unsigned int offset; }; struct_group_tagged(page_pool_params_slow, slow, struct net_device *netdev; unsigned int queue_idx; unsigned int flags; /* private: used by test code only */ void (*init_callback)(netmem_ref netmem, void *arg); void *init_arg; }; }; """, "trimmed": """ struct page_pool_params { struct_group_tagged(page_pool_params_fast, fast, unsigned int order; unsigned int pool_size; int nid; struct device *dev; struct napi_struct *napi; enum dma_data_direction dma_dir; unsigned int max_len; unsigned int offset; }; struct_group_tagged(page_pool_params_slow, slow, struct net_device *netdev; unsigned int queue_idx; unsigned int flags; }; }; """, }, "unbalanced_two_struct_group_tagged_first_with_private": { "source": """ struct page_pool_params { struct_group_tagged(page_pool_params_slow, slow, struct net_device *netdev; unsigned int queue_idx; unsigned int flags; /* private: used by test code only */ void (*init_callback)(netmem_ref netmem, void *arg); void *init_arg; }; struct_group_tagged(page_pool_params_fast, fast, unsigned int order; unsigned int pool_size; int nid; struct device *dev; struct napi_struct *napi; enum dma_data_direction dma_dir; unsigned int max_len; unsigned int offset; }; }; """, "trimmed": """ struct page_pool_params { struct_group_tagged(page_pool_params_slow, slow, struct net_device *netdev; unsigned int queue_idx; unsigned int flags; }; struct_group_tagged(page_pool_params_fast, fast, unsigned int order; unsigned int pool_size; int nid; struct device *dev; struct napi_struct *napi; enum dma_data_direction dma_dir; unsigned int max_len; unsigned int offset; }; }; """, }, "unbalanced_without_end_of_line": { "source": """ \ struct page_pool_params { \ struct_group_tagged(page_pool_params_slow, slow, \ struct net_device *netdev; \ unsigned int queue_idx; \ unsigned int flags; /* private: used by test code only */ void (*init_callback)(netmem_ref netmem, void *arg); \ void *init_arg; \ }; \ struct_group_tagged(page_pool_params_fast, fast, \ unsigned int order; \ unsigned int pool_size; \ int nid; \ struct device *dev; \ struct napi_struct *napi; \ enum dma_data_direction dma_dir; \ unsigned int max_len; \ unsigned int offset; \ }; \ }; """, "trimmed": """ struct page_pool_params { struct_group_tagged(page_pool_params_slow, slow, struct net_device *netdev; unsigned int queue_idx; unsigned int flags; }; struct_group_tagged(page_pool_params_fast, fast, unsigned int order; unsigned int pool_size; int nid; struct device *dev; struct napi_struct *napi; enum dma_data_direction dma_dir; unsigned int max_len; unsigned int offset; }; }; """, }, } #: Dict containing all test groups fror CTokenizer TESTS = { "TestPublicPrivate": TESTS_PRIVATE, "TestTokenizer": TESTS_TOKENIZER, } def setUp(self): self.maxDiff = None def build_test_class(group_name, table): """ Dynamically creates a class instance using type() as a generator for a new class derivated from unittest.TestCase. We're opting to do it inside a function to avoid the risk of changing the globals() dictionary. """ class_dict = { "setUp": setUp } run = table["__run__"] for test_name, data in table.items(): if test_name == "__run__": continue class_dict[f"test_{test_name}"] = run(test_name, data) cls = type(group_name, (unittest.TestCase,), class_dict) return cls.__name__, cls # # Create classes and add them to the global dictionary # for group, table in TESTS.items(): t = build_test_class(group, table) globals()[t[0]] = t[1] # # main # if __name__ == "__main__": run_unittest(__file__)