85 lines
3.6 KiB
Python
85 lines
3.6 KiB
Python
#!/usr/bin/env python3
|
|
"""Submit Lua TestBot jobs through existing authenticated Trinity SOAP commands."""
|
|
import argparse
|
|
import base64
|
|
import json
|
|
import os
|
|
from pathlib import Path
|
|
import time
|
|
import urllib.error
|
|
import urllib.request
|
|
import xml.etree.ElementTree as ET
|
|
|
|
|
|
def command(endpoint, username, password, text, timeout=20):
|
|
envelope = ET.Element('{http://schemas.xmlsoap.org/soap/envelope/}Envelope')
|
|
body = ET.SubElement(envelope, '{http://schemas.xmlsoap.org/soap/envelope/}Body')
|
|
execute = ET.SubElement(body, '{urn:TC}executeCommand')
|
|
ET.SubElement(execute, 'command').text = text
|
|
auth = base64.b64encode((username + ':' + password).encode()).decode()
|
|
request = urllib.request.Request(endpoint, ET.tostring(envelope), headers={
|
|
'Content-Type': 'text/xml; charset=utf-8', 'SOAPAction': 'urn:TC#executeCommand',
|
|
'Authorization': 'Basic ' + auth,
|
|
})
|
|
with urllib.request.urlopen(request, timeout=timeout) as response:
|
|
root = ET.fromstring(response.read())
|
|
for node in root.iter():
|
|
if node.tag.rsplit('}', 1)[-1] == 'Fault':
|
|
raise RuntimeError('SOAP fault: ' + ''.join(node.itertext()))
|
|
for node in root.iter():
|
|
if node.tag.rsplit('}', 1)[-1] in ('result', 'return'):
|
|
return ''.join(node.itertext()).strip()
|
|
raise RuntimeError('SOAP response contains no command result')
|
|
|
|
|
|
def run(endpoint, username, password, run_id, script, deadline=330):
|
|
if not run_id or len(run_id) > 80 or any(c not in 'abcdefghijklmnopqrstuvwxyzABCDEFGHIJKLMNOPQRSTUVWXYZ0123456789_-' for c in run_id):
|
|
raise ValueError('Invalid run ID')
|
|
content = Path(script).read_bytes()
|
|
if not content or len(content) > 65536:
|
|
raise ValueError('Script must contain 1..65536 bytes')
|
|
reply = command(endpoint, username, password,
|
|
f'clientless test submit {run_id} ' + base64.b64encode(content).decode())
|
|
if f'Testbot run {run_id}: queued' not in reply:
|
|
raise RuntimeError(reply)
|
|
end = time.monotonic() + deadline
|
|
while time.monotonic() < end:
|
|
reply = command(endpoint, username, password, f'clientless test result {run_id}')
|
|
try:
|
|
result = json.loads(reply)
|
|
except json.JSONDecodeError:
|
|
if 'not available yet' not in reply:
|
|
raise RuntimeError(reply)
|
|
else:
|
|
if result['run_id'] != run_id:
|
|
raise RuntimeError('Result belongs to another run')
|
|
if result['state'] != 'running':
|
|
return result
|
|
time.sleep(1)
|
|
raise TimeoutError(f'Client deadline exceeded; retrieve/cancel run {run_id} on server')
|
|
|
|
|
|
def main():
|
|
parser = argparse.ArgumentParser(description=__doc__)
|
|
parser.add_argument('run_id')
|
|
parser.add_argument('script', type=Path)
|
|
parser.add_argument('--endpoint', default='http://127.0.0.1:7878/')
|
|
parser.add_argument('--user', required=True)
|
|
parser.add_argument('--output', type=Path)
|
|
parser.add_argument('--timeout', type=float, default=330, help='client wait deadline in seconds')
|
|
args = parser.parse_args()
|
|
try:
|
|
result = run(args.endpoint, args.user, os.environ['TESTBOT_PASSWORD'], args.run_id, args.script, deadline=args.timeout)
|
|
text = json.dumps(result, ensure_ascii=False, indent=2)
|
|
if args.output:
|
|
args.output.write_text(text + '\n')
|
|
print(text)
|
|
return 0 if result['state'] == 'passed' else 1
|
|
except (OSError, ValueError, RuntimeError, TimeoutError, KeyError, ET.ParseError) as error:
|
|
print(json.dumps({'client_error': str(error)}, ensure_ascii=False))
|
|
return 2
|
|
|
|
|
|
if __name__ == '__main__':
|
|
raise SystemExit(main())
|