from py.xml import html import pytest import subprocess import xmlrpc import time stderr = list() stdout = list() apperrs = list() def pytest_html_results_summary(prefix, summary, postfix): prefix.extend([html.h3("stderr")]) prefix.extend([html.pre("".join([line for line in stderr]))]) prefix.extend([html.h3("stdout")]) prefix.extend([html.pre("".join([line for line in stdout]))]) prefix.extend([html.h3("App errors")]) prefix.extend([html.pre("".join([line for line in apperrs]))]) @pytest.fixture def process_alive(app, process, process_communicate): def func(): try: global apperrs # We need to call something on the rpc server # the process might still be running but frozen or only almost dead apperrs += app.getErrors() except Exception as e: # Process probably died pass finally: process_communicate() exit_code = process.poll() if exit_code is not None: print(f"Process exited with {exit_code}") return exit_code is None yield func @pytest.fixture def process_communicate(process, report_summary): def func(): try: output_b, errors_b = process.communicate(timeout=0.1) output = output_b.decode("utf-8") errors = errors_b.decode("utf-8") print(errors) print(output) report_summary[0].append(output) report_summary[1].append(errors) except subprocess.TimeoutExpired: pass yield func @pytest.fixture def report_summary(): yield stdout, stderr @pytest.fixture def process(): filenames = [ "./output/bin/qfield", "./output/bin/Release/qfield.exe", "./output/bin/Debug/qfield.exe", "./output/bin/qfield.app/qfield.exe", "./output/bin/qfield.app/Contents/MacOS/qfield", ] for filename in filenames: try: process = subprocess.Popen( filename, stdout=subprocess.PIPE, stderr=subprocess.PIPE ) yield process break except FileNotFoundError: pass else: assert False, f"No qfield executable found in {filenames}" @pytest.fixture def app(process, process_communicate): """ Starts a qfield process and connects an xmlrpc client to it. Returns the xmlrpc client that can send commands to the running process. Will wait up to 30 seconds for the process to start and return as soon as either the process is started and QML initialized, the process exits or the timeout occurs. Also makes sure it cleans up properly after running the app. """ app = xmlrpc.client.ServerProxy("http://localhost:9000") app.process = process start = time.time() while True: try: exit_code = process.poll() if exit_code is not None: process_communicate(process) assert exit_code is None app.existsAndVisible("mainWindow") yield app break except (ConnectionRefusedError, OSError) as e: if time.time() - start > 30: process_communicate(process) assert False # Could not start app after 30 seconds print(str(e)) time.sleep(0.2) except Exception as e: process_communicate(process) assert False # Unexcpected exception while starting up app try: app.quit() app.quit() except Exception: print("Exception while trying to exit app. The process probably died.") process_communicate() raise timeout = 5 try: process.wait(timeout) except subprocess.TimeoutExpired: print(f"Process did not quit after {timeout} second. Killing it.") process.kill() process_communicate()