Add admin port support with other major refactorations
This commit is contained in:
108
check_public_calls.py
Executable file
108
check_public_calls.py
Executable file
@@ -0,0 +1,108 @@
|
||||
#!/usr/bin/env python3
|
||||
import ast
|
||||
import inspect
|
||||
import sys
|
||||
import os
|
||||
|
||||
# Add lib and tests to path
|
||||
sys.path.insert(0, os.path.join(os.path.dirname(__file__), 'lib'))
|
||||
sys.path.insert(0, os.path.dirname(__file__))
|
||||
|
||||
from openttd import OpenTTDClient, OpenTTDAdminClient
|
||||
from openttd.protocol import OpenTTDProtocol, OpenTTDAdminProtocol
|
||||
|
||||
# 1. Gather public functions dynamically at runtime using reflection markers
|
||||
classes = [OpenTTDClient, OpenTTDAdminClient, OpenTTDProtocol, OpenTTDAdminProtocol]
|
||||
public_funcs = {} # name -> has_params
|
||||
|
||||
for cls in classes:
|
||||
# Class must be marked as public
|
||||
if not getattr(cls, '__is_public_api__', False):
|
||||
continue
|
||||
|
||||
# Class constructor call (e.g. OpenTTDClient(host, ...))
|
||||
sig_init = inspect.signature(cls.__init__)
|
||||
has_params_init = len([p for name, p in sig_init.parameters.items() if name != 'self']) > 0
|
||||
public_funcs[cls.__name__] = has_params_init
|
||||
|
||||
# Get all functions on the class (including inherited)
|
||||
for name, val in inspect.getmembers(cls):
|
||||
if name == '__init__':
|
||||
continue
|
||||
if not (inspect.isfunction(val) or inspect.ismethod(val) or inspect.iscoroutinefunction(val)):
|
||||
continue
|
||||
# Exclude private methods
|
||||
if name.startswith('_'):
|
||||
continue
|
||||
# Exclude protocol dispatch targets (receive_* except receive_packet)
|
||||
if name.startswith('receive_') and name != 'receive_packet':
|
||||
continue
|
||||
# Exclude lifecycle callbacks and manually decorated ones
|
||||
if getattr(val, '__exclude_call_check__', False):
|
||||
continue
|
||||
|
||||
sig = inspect.signature(val)
|
||||
has_params = len([p for p_name, p in sig.parameters.items() if p_name != 'self']) > 0
|
||||
public_funcs[name] = has_params
|
||||
|
||||
# 2. Inspect E2E test module dynamically at runtime using reflection
|
||||
import tests.test_e2e as test_e2e
|
||||
|
||||
calls = {name: [] for name in public_funcs}
|
||||
|
||||
class E2ECallVisitor(ast.NodeVisitor):
|
||||
def visit_Call(self, node):
|
||||
method_name = None
|
||||
if isinstance(node.func, ast.Attribute):
|
||||
method_name = node.func.attr
|
||||
elif isinstance(node.func, ast.Name):
|
||||
method_name = node.func.id
|
||||
|
||||
if method_name in public_funcs:
|
||||
# Serialize arguments to string to compare data
|
||||
args_str = [ast.unparse(a) for a in node.args]
|
||||
kwargs_str = [f"{kw.arg}={ast.unparse(kw.value)}" for kw in node.keywords]
|
||||
call_data = (tuple(args_str), tuple(sorted(kwargs_str)))
|
||||
calls[method_name].append(call_data)
|
||||
|
||||
self.generic_visit(node)
|
||||
|
||||
visitor = E2ECallVisitor()
|
||||
|
||||
# Reflectively iterate through all test functions in tests.test_e2e
|
||||
for name, val in inspect.getmembers(test_e2e, predicate=inspect.isfunction):
|
||||
if name.startswith("test_e2e_"):
|
||||
# Retrieve function source dynamically via reflection
|
||||
source = inspect.getsource(val)
|
||||
func_tree = ast.parse(source)
|
||||
visitor.visit(func_tree)
|
||||
|
||||
# 3. Perform assertion checks
|
||||
failed = False
|
||||
print("=== Public Function E2E Calls Verification (Reflection API Inspection) ===")
|
||||
for name, has_params in sorted(public_funcs.items()):
|
||||
func_calls = calls[name]
|
||||
num_calls = len(func_calls)
|
||||
|
||||
if num_calls < 2:
|
||||
print(f"❌ {name}: Called {num_calls} time(s) (expected at least 2).")
|
||||
failed = True
|
||||
continue
|
||||
|
||||
if has_params:
|
||||
unique_calls = set(func_calls)
|
||||
num_unique = len(unique_calls)
|
||||
if num_unique < 2:
|
||||
print(f"❌ {name}: Called {num_calls} times but with identical data: {unique_calls}")
|
||||
failed = True
|
||||
else:
|
||||
print(f"✅ {name}: Called {num_calls} times with {num_unique} different inputs.")
|
||||
else:
|
||||
print(f"✅ {name}: Called {num_calls} times (no parameters).")
|
||||
|
||||
if failed:
|
||||
print("❌ Verification FAILED: Some public functions do not meet E2E call requirement.")
|
||||
sys.exit(1)
|
||||
else:
|
||||
print("✅ Verification PASSED: All public functions called at least 2 times with different data.")
|
||||
sys.exit(0)
|
||||
Reference in New Issue
Block a user