81 lines
4.9 KiB
Python
81 lines
4.9 KiB
Python
"""Real GPU/API smoke test, run on CMP. Uses and revokes a disposable device."""
|
|
import hashlib
|
|
import io
|
|
import json
|
|
import os
|
|
from pathlib import Path
|
|
import time
|
|
import zipfile
|
|
import httpx
|
|
from device_auth import DeviceStore
|
|
|
|
root = Path(os.environ.get('AUDIOBOOK_SERVICE_ROOT', '/home/sevenhill/apps/aletheia-audiobook'))
|
|
base = 'https://argus.kusoft.xyz/aletheia-tts'
|
|
with httpx.Client(timeout=60) as client:
|
|
for attempt in range(30):
|
|
try:
|
|
health = client.get(base + '/health')
|
|
if health.status_code == 200:
|
|
break
|
|
except httpx.TransportError:
|
|
pass
|
|
time.sleep(2)
|
|
else:
|
|
raise RuntimeError('Public receiver health endpoint is unavailable')
|
|
store = DeviceStore(root / 'audiobook-jobs/jobs.db3')
|
|
response = client.post(base + '/v1/devices/enroll', json={'code': store.create_code(), 'label': 'Deployment smoke test'})
|
|
response.raise_for_status()
|
|
device = response.json()
|
|
headers = {'Authorization': 'Bearer ' + device['token']}
|
|
job = None
|
|
try:
|
|
text = 'Это проверка изготовления аудиокниги. Приёмник получил книгу от приложения и читает её вслух. Готовая запись возвращается в библиотеку.'
|
|
book_format = os.environ.get('AUDIOBOOK_SMOKE_FORMAT', 'epub')
|
|
if book_format == 'epub':
|
|
archive = io.BytesIO()
|
|
with zipfile.ZipFile(archive, 'w') as epub:
|
|
epub.writestr('mimetype', 'application/epub+zip')
|
|
epub.writestr('META-INF/container.xml', '<container xmlns="urn:oasis:names:tc:opendocument:xmlns:container" version="1.0"><rootfiles><rootfile full-path="OEBPS/content.opf" media-type="application/oebps-package+xml"/></rootfiles></container>')
|
|
chapters = int(os.environ.get('AUDIOBOOK_SMOKE_CHAPTERS', '1'))
|
|
manifest = ''.join(f'<item id="c{i}" href="chapter{i}.xhtml" media-type="application/xhtml+xml"/>' for i in range(chapters))
|
|
spine = ''.join(f'<itemref idref="c{i}"/>' for i in range(chapters))
|
|
epub.writestr('OEBPS/content.opf', '<package xmlns="http://www.idpf.org/2007/opf" version="2.0" unique-identifier="bookid"><metadata xmlns:dc="http://purl.org/dc/elements/1.1/"><dc:identifier id="bookid">cmp-smoke</dc:identifier><dc:title>Проверка CMP</dc:title><dc:language>ru</dc:language></metadata><manifest>' + manifest + '</manifest><spine>' + spine + '</spine></package>')
|
|
for i in range(chapters):
|
|
body = text if chapters == 1 else text + f' Раздел номер {i + 1}.'
|
|
epub.writestr(f'OEBPS/chapter{i}.xhtml', '<html xmlns="http://www.w3.org/1999/xhtml"><head><title>Проверка</title></head><body><h1>Проверка ' + str(i + 1) + '</h1><p>' + body + '</p></body></html>')
|
|
book = archive.getvalue()
|
|
else:
|
|
book = ('<?xml version="1.0" encoding="UTF-8"?><FictionBook><body><section><title><p>Проверка</p></title><p>' + text + '</p></section></body></FictionBook>').encode()
|
|
response = client.post(base + '/v1/audiobooks', headers=headers, data={'title': 'Проверка CMP', 'author': 'Aletheia'}, files={'book': ('test.' + book_format, book, 'application/epub+zip' if book_format == 'epub' else 'application/xml')})
|
|
response.raise_for_status()
|
|
job = response.json()['id']
|
|
path = base + '/v1/audiobooks/' + job
|
|
assert client.get(path).status_code == 401
|
|
deadline = time.monotonic() + 1200
|
|
previous = None
|
|
while time.monotonic() < deadline:
|
|
response = client.get(path, headers=headers)
|
|
response.raise_for_status()
|
|
status = response.json()
|
|
state = (status['status'], status['stage'], status['processedCharacters'], status['totalCharacters'])
|
|
if state != previous:
|
|
print(json.dumps({'state': state}, ensure_ascii=False), flush=True)
|
|
previous = state
|
|
if status['status'] == 'ready':
|
|
response = client.get(path + '/file', headers=headers)
|
|
response.raise_for_status()
|
|
assert b'ftyp' in response.content[:32] and status['durationMs'] > 0
|
|
output = root / 'smoke-audiobook.m4a'
|
|
output.write_bytes(response.content)
|
|
print(json.dumps({'result': 'PASS', 'bytes': len(response.content), 'durationMs': status['durationMs'], 'sha256': hashlib.sha256(response.content).hexdigest()}), flush=True)
|
|
break
|
|
if status['status'] in ('failed', 'cancelled'):
|
|
raise RuntimeError(status)
|
|
time.sleep(3)
|
|
else:
|
|
raise TimeoutError('Synthesis exceeded smoke test timeout')
|
|
finally:
|
|
if job:
|
|
client.delete(base + '/v1/audiobooks/' + job, headers=headers)
|
|
store.revoke(device['deviceId'])
|