mirror of
https://github.com/torvalds/linux.git
synced 2026-05-12 16:18:45 +02:00
The current logic is too sensitive to how c_lex name is placed. Also, it doesn't really check the log. Change it to check if the expected message will be reported after a call to C tokenizer with an invalid source. Signed-off-by: Mauro Carvalho Chehab <mchehab+huawei@kernel.org> Signed-off-by: Jonathan Corbet <corbet@lwn.net> Message-ID: <6e19578bc1ffa96e536dc31997ff658017f60173.1773823995.git.mchehab+huawei@kernel.org>
470 lines
14 KiB
Python
Executable File
470 lines
14 KiB
Python
Executable File
#!/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__)
|