'''Проверка создания настоящего MT5 без счёта в изолированной папке. Берёт файлы из первого терминала. После проверки удаляет только собственные временные папки и отдельную тестовую базу Common/Files. ''' import argparse import getpass import json import shutil import sys import time import uuid from pathlib import Path import psutil PROJECT = Path(__file__).resolve().parents[1] sys.path.insert(0, str(PROJECT)) from config import ConfigStore from terminal_manager import TerminalManager class DiagnosticManager(TerminalManager): def wait_state(self, folder, since, account=None): try: return super().wait_state(folder, since, account) except RuntimeError: row = self.control.read_state(folder) if row is not None: print('Last service state:', json.dumps({key: row[key] for key in ('updated_at', 'connected', 'login', 'server', 'terminal_path')}, ensure_ascii=False), flush=True) log_dir = Path(self.control.instance_path(folder)).parent / 'Logs' for path in sorted(log_dir.glob('*.log')): if not path.stem.isdecimal(): continue for line in path.read_text(encoding='utf-16', errors='replace').splitlines(): if ('\tNetwork\t' in line or '\tStartup\t' in line) and 'previous successful' not in line: print(line, flush=True) raise def main(): parser = argparse.ArgumentParser(description=__doc__) parser.add_argument('--with-account', action='store_true', help='Запросить реквизиты счёта при запуске (пароль вводится скрыто)') parser.add_argument('--login', type=int, help='Логин для --with-account') parser.add_argument('--server', help='Торговый сервер для --with-account') parser.add_argument('--timeout', type=int, default=180) args = parser.parse_args() account = {} if args.with_account: try: login = args.login or int(input('Логин счёта: ').strip()) server = args.server or input('Торговый сервер: ').strip() password = getpass.getpass('Пароль счёта (не отображается): ') except (ValueError, EOFError): parser.error('Не удалось прочитать реквизиты счёта.') if login <= 0 or not server or not password: parser.error('Укажите положительный логин, сервер и непустой пароль.') account = {'login': login, 'server': server, 'password': password} elif args.login is not None or args.server: parser.error('--login и --server используются вместе с --with-account.') token = uuid.uuid4().hex[:8] check_root = (PROJECT / '.checks' / token).resolve() check_root.mkdir(parents=True) common = Path(json.loads((PROJECT / 'config.json').read_text(encoding='utf-8-sig'))['database']).parent database = common / f'manager-article5-check-{token}.sqlite' path = check_root / 'config.json' path.write_text(json.dumps({'terminals': {}, 'mt5_folder': str(check_root / 'instances'), 'mt5_exe': 'terminal64.exe', 'database': str(database), 'terminal_template': r'C:\MT5\MetaTrader5.1', 'startup_timeout_sec': args.timeout}), encoding='utf-8') manager = DiagnosticManager(ConfigStore(path)) try: job = manager.submit('create', f'Article5Check-{token}', account) previous = None while True: job = manager.job(job['id']) if job['stage'] != previous: print(job['stage'], flush=True) previous = job['stage'] if job['status'] in ('done', 'error'): break time.sleep(1) print(json.dumps(job, ensure_ascii=False), flush=True) if job['status'] != 'done': return 1 info = manager.info(job['folder']) assert info['pid'] and info['status'] == 'running', info print('Real terminal process and service heartbeat: OK', flush=True) manager.stop(job['folder']) return 0 finally: manager.close() for process in psutil.process_iter(['exe']): try: if process.info['exe'] and Path(process.info['exe']).resolve().is_relative_to(check_root): process.terminate() process.wait(10) except (psutil.NoSuchProcess, psutil.AccessDenied): continue assert check_root.parent == (PROJECT / '.checks').resolve() shutil.rmtree(check_root) for suffix in ('', '-wal', '-shm'): Path(str(database) + suffix).unlink(missing_ok=True) if __name__ == '__main__': raise SystemExit(main())