120 行
5.3 KiB
Python
120 行
5.3 KiB
Python
"""Run real Glyphix native UI tests through its local Inspector (Python stdlib).
|
|
|
|
Build the input app and the minimal test fixture first; see the root README. No production
|
|
credentials, paid requests, Windows input injection or third party modules.
|
|
"""
|
|
import argparse
|
|
import datetime
|
|
import hashlib
|
|
import json
|
|
import pathlib
|
|
import shutil
|
|
import socket
|
|
import subprocess
|
|
import time
|
|
import urllib.parse
|
|
import urllib.request
|
|
|
|
ROOT = pathlib.Path(__file__).resolve().parents[1]
|
|
APPS = ['inputmethod', 'imefixture']
|
|
PROFILES = ['round', 'rect', 'round-quickapp', 'rect-quickapp']
|
|
BASE = 'http://127.0.0.1:14201'
|
|
|
|
|
|
def sha256(path):
|
|
return hashlib.sha256(path.read_bytes()).hexdigest().upper()
|
|
|
|
|
|
def request(path, params=None, method='GET'):
|
|
url = BASE + path + ('?' + urllib.parse.urlencode(params) if params else '')
|
|
with urllib.request.urlopen(urllib.request.Request(url, method=method), timeout=15) as response:
|
|
data = response.read()
|
|
try:
|
|
return json.loads(data)
|
|
except (ValueError, UnicodeDecodeError):
|
|
return data.decode('utf-8', errors='replace')
|
|
|
|
|
|
def evaluate(script):
|
|
return request('/fn/eval', {'lang': 'js', 'script': script}, 'POST')
|
|
|
|
|
|
def resume():
|
|
request('/control', {'pause': 'false'}, 'POST')
|
|
|
|
|
|
def snapshot(name, output):
|
|
try:
|
|
tree = request('/snapshot.json')
|
|
(output / (name + '.tree.json')).write_text(json.dumps(tree, ensure_ascii=False), encoding='utf-8')
|
|
with urllib.request.urlopen(BASE + '/snapshot/' + tree['frames'][-1]['snapshot'], timeout=15) as response:
|
|
(output / (name + '.png')).write_bytes(response.read())
|
|
finally:
|
|
# Capturing Inspector history pauses the runtime, including gestures/timers.
|
|
resume()
|
|
|
|
|
|
def prepare(profile, work):
|
|
device = 'generic-watch-' + ('466x466' if profile.startswith('round') else '390x450')
|
|
image = work / profile / 'image'
|
|
image.mkdir(parents=True, exist_ok=True)
|
|
seed = ROOT / 'com.sydxky.rtos.imefixture/.glyphix-work/image/generic-watch-466x466'
|
|
if not (seed / 'global.pkg').is_file() or not (seed / 'apps/emulator.launcher.pkg').is_file():
|
|
raise RuntimeError('Missing global assets/launcher: launch the round fixture emulator once with gx emulate first.')
|
|
shutil.copytree(seed / 'global', image / 'global', dirs_exist_ok=True)
|
|
shutil.copy2(seed / 'global.pkg', image / 'global.pkg')
|
|
(image / 'apps').mkdir(exist_ok=True)
|
|
shutil.copy2(seed / 'apps/emulator.launcher.pkg', image / 'apps/emulator.launcher.pkg')
|
|
artifacts = {}
|
|
for app in APPS:
|
|
package = 'com.sydxky.rtos.' + app
|
|
build = ROOT / package / '.glyphix-work'
|
|
if profile.endswith('-quickapp'):
|
|
source = build / 'dist' / (device + '-quickapp') / package / 'bundle.pkg'
|
|
else:
|
|
source = build / 'image' / device / 'apps' / (package + '.pkg')
|
|
shutil.copy2(source, image / 'apps' / (package + '.pkg'))
|
|
artifacts[app] = {'path': str(source), 'sha256': sha256(source), 'ux_sha256': sha256(ROOT / package / 'src/main/index.ux')}
|
|
packages = ['com.sydxky.rtos.' + app for app in APPS] + ['emulator.launcher']
|
|
(image / 'pkgs.db').write_text(json.dumps({'apps': {p: {'uri': 'pkg://' + p} for p in packages}, 'dials': {}}), encoding='utf-8')
|
|
return image, artifacts
|
|
|
|
|
|
def start(profile, work, first_app):
|
|
# Do not attach to or terminate another user's running emulator.
|
|
with socket.socket() as probe:
|
|
if probe.connect_ex(('127.0.0.1', 14201)) == 0:
|
|
raise RuntimeError('Inspector port 14201 is already in use; close the other emulator first.')
|
|
image, artifacts = prepare(profile, work)
|
|
executable = ROOT / 'temp/glyphix-v0.7.2-windows-x64/bin/glyphix-emu.exe'
|
|
args = [str(executable), '--config', 'pkgs.db', '--target', 'com.sydxky.rtos.' + first_app, '--inspector']
|
|
if profile.startswith('round'):
|
|
args += ['--width', '624', '--height', '624', '--dpi', '326', '--font-size', '32', '--round', '--surface', 'surface/surface-466x466.json']
|
|
else:
|
|
args += ['--width', '390', '--height', '450', '--dpi', '321', '--font-size', '29']
|
|
args += ['--memory-profile', '8192', '--font-map', 'font-faces', '--font-family', 'sans-serif']
|
|
stdout = open(work / profile / 'stdout.log', 'w', encoding='utf-8')
|
|
stderr = open(work / profile / 'stderr.log', 'w', encoding='utf-8')
|
|
# Keep stdin open: the vendor emulator exits when its console input sees EOF.
|
|
process = subprocess.Popen(args, cwd=image, stdin=subprocess.PIPE, stdout=stdout, stderr=stderr, creationflags=subprocess.CREATE_NO_WINDOW)
|
|
try:
|
|
deadline = time.time() + 20
|
|
while time.time() < deadline:
|
|
if process.poll() is not None:
|
|
raise RuntimeError('Emulator exited before Inspector was ready; inspect ' + str(work / profile / 'stderr.log'))
|
|
try:
|
|
request('/logs')
|
|
time.sleep(1)
|
|
return process, artifacts
|
|
except OSError:
|
|
time.sleep(0.25)
|
|
raise RuntimeError('Inspector startup timed out')
|
|
except BaseException:
|
|
if process.poll() is None:
|
|
process.terminate()
|
|
process.wait(timeout=10)
|
|
raise
|
|
finally:
|
|
stdout.close()
|
|
stderr.close()
|