74 lines
3.1 KiB
Python
74 lines
3.1 KiB
Python
"""Isolated protocol regression runner; never contact/send to a live client."""
|
|
import contextlib
|
|
import json
|
|
import os
|
|
from pathlib import Path
|
|
import socket
|
|
import sys
|
|
import tempfile
|
|
import threading
|
|
import time
|
|
import unittest
|
|
from unittest import mock
|
|
|
|
TARGET = Path(sys.argv[1])
|
|
OUT = Path(__file__).resolve().parent
|
|
MODULES = sys.argv[2:] or [
|
|
'test_native_protocol_pipeline', 'test_protocol_features',
|
|
'test_protocol_cross_machine', 'test_wecom_native_protocol',
|
|
'test_protocol_knowledge_integration',
|
|
]
|
|
os.environ['QT_QPA_PLATFORM'] = 'offscreen'
|
|
sys.path.insert(0, str(TARGET))
|
|
|
|
def blocked(*args, **kwargs):
|
|
raise AssertionError('Audit disallows real network and native process access')
|
|
|
|
internal = threading.local()
|
|
original_pair = socket.socketpair
|
|
original_connect = socket.socket.connect
|
|
|
|
def socketpair(*args, **kwargs):
|
|
# Windows asyncio needs a private socket pair only for its wakeup pipe.
|
|
internal.wakeup = True
|
|
try:
|
|
return original_pair(*args, **kwargs)
|
|
finally:
|
|
internal.wakeup = False
|
|
|
|
def connect(sock, address):
|
|
if getattr(internal, 'wakeup', False):
|
|
return original_connect(sock, address)
|
|
return blocked()
|
|
|
|
with tempfile.TemporaryDirectory(prefix='protocol-audit-') as folder, contextlib.ExitStack() as stack:
|
|
import runtime_paths
|
|
stack.enter_context(mock.patch.object(runtime_paths, 'application_data_dir', return_value=Path(folder)))
|
|
stack.enter_context(mock.patch.object(socket, 'socketpair', new=socketpair))
|
|
stack.enter_context(mock.patch.object(socket.socket, 'connect', new=connect))
|
|
stack.enter_context(mock.patch.object(socket.socket, 'connect_ex', side_effect=blocked))
|
|
if (TARGET/'wecom_native_sender.py').is_file():
|
|
import wecom_native_sender
|
|
stack.enter_context(mock.patch.object(wecom_native_sender, 'kernel', side_effect=blocked))
|
|
import send_lock
|
|
send_lock._MUTEX_NAME = 'Local\\ProtocolAudit_' + str(os.getpid())
|
|
import test_support
|
|
test_support.local_state_redirect.start()
|
|
stack.callback(test_support.local_state_redirect.stop)
|
|
test_support.queue_log_redirect.start()
|
|
stack.callback(test_support.queue_log_redirect.stop)
|
|
suite = unittest.defaultTestLoader.loadTestsFromNames(MODULES)
|
|
started = time.monotonic()
|
|
suffix = os.environ.get('AUDIT_OUTPUT_TAG') or ('boundary' if 'test_protocol_audit_boundaries' in MODULES else 'baseline')
|
|
with (OUT / f'protocol-{suffix}.log').open('w', encoding='utf-8') as log:
|
|
result = unittest.TextTestRunner(stream=log, verbosity=2).run(suite)
|
|
summary = {'target': str(TARGET), 'modules': MODULES, 'tests': result.testsRun,
|
|
'failures': len(result.failures), 'errors': len(result.errors),
|
|
'skipped': len(result.skipped), 'seconds': round(time.monotonic()-started, 3),
|
|
'success': result.wasSuccessful()}
|
|
(OUT / f'protocol-{suffix}.json').write_text(json.dumps(summary, indent=2, ensure_ascii=False), encoding='utf-8')
|
|
print(json.dumps(summary, ensure_ascii=False))
|
|
for _, details in result.failures + result.errors:
|
|
print(details)
|
|
raise SystemExit(0 if result.wasSuccessful() else 1)
|