110 lines
5.1 KiB
Python
'''Проверка создания настоящего 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())
|