mirror of https://github.com/pyodide/pyodide.git
Refactor test web server to allow multiple instances and log capture
This commit is contained in:
parent
2e23079aed
commit
387fb18ceb
104
test/conftest.py
104
test/conftest.py
|
@ -2,13 +2,16 @@
|
||||||
Various common utilities for testing.
|
Various common utilities for testing.
|
||||||
"""
|
"""
|
||||||
|
|
||||||
import atexit
|
import contextlib
|
||||||
import multiprocessing
|
import multiprocessing
|
||||||
import textwrap
|
import textwrap
|
||||||
|
import tempfile
|
||||||
|
import time
|
||||||
import os
|
import os
|
||||||
import pathlib
|
import pathlib
|
||||||
import queue
|
import queue
|
||||||
import sys
|
import sys
|
||||||
|
import shutil
|
||||||
|
|
||||||
try:
|
try:
|
||||||
import pytest
|
import pytest
|
||||||
|
@ -46,7 +49,8 @@ def _display_driver_logs(browser, driver):
|
||||||
|
|
||||||
|
|
||||||
class SeleniumWrapper:
|
class SeleniumWrapper:
|
||||||
def __init__(self):
|
def __init__(self, server_port, server_hostname='127.0.0.1',
|
||||||
|
server_log=None):
|
||||||
from selenium.webdriver.support.wait import WebDriverWait
|
from selenium.webdriver.support.wait import WebDriverWait
|
||||||
from selenium.common.exceptions import TimeoutException
|
from selenium.common.exceptions import TimeoutException
|
||||||
|
|
||||||
|
@ -56,7 +60,7 @@ class SeleniumWrapper:
|
||||||
# selenium does not expose HTTP response codes
|
# selenium does not expose HTTP response codes
|
||||||
raise ValueError(f"{(BUILD_PATH / 'test.html').resolve()} "
|
raise ValueError(f"{(BUILD_PATH / 'test.html').resolve()} "
|
||||||
f"does not exist!")
|
f"does not exist!")
|
||||||
driver.get(f'http://127.0.0.1:{PORT}/test.html')
|
driver.get(f'http://{server_hostname}:{server_port}/test.html')
|
||||||
try:
|
try:
|
||||||
wait.until(PyodideInited())
|
wait.until(PyodideInited())
|
||||||
except TimeoutException as exc:
|
except TimeoutException as exc:
|
||||||
|
@ -64,6 +68,9 @@ class SeleniumWrapper:
|
||||||
raise TimeoutException()
|
raise TimeoutException()
|
||||||
self.wait = wait
|
self.wait = wait
|
||||||
self.driver = driver
|
self.driver = driver
|
||||||
|
self.server_port = server_port
|
||||||
|
self.server_hostname = server_hostname
|
||||||
|
self.server_log = server_log
|
||||||
|
|
||||||
@property
|
@property
|
||||||
def logs(self):
|
def logs(self):
|
||||||
|
@ -151,12 +158,15 @@ class ChromeWrapper(SeleniumWrapper):
|
||||||
|
|
||||||
if pytest is not None:
|
if pytest is not None:
|
||||||
@pytest.fixture(params=['firefox', 'chrome'])
|
@pytest.fixture(params=['firefox', 'chrome'])
|
||||||
def selenium_standalone(request):
|
def selenium_standalone(request, web_server_main):
|
||||||
|
server_hostname, server_port, server_log = web_server_main
|
||||||
if request.param == 'firefox':
|
if request.param == 'firefox':
|
||||||
cls = FirefoxWrapper
|
cls = FirefoxWrapper
|
||||||
elif request.param == 'chrome':
|
elif request.param == 'chrome':
|
||||||
cls = ChromeWrapper
|
cls = ChromeWrapper
|
||||||
selenium = cls()
|
selenium = cls(server_port=server_port,
|
||||||
|
server_hostname=server_hostname,
|
||||||
|
server_log=server_log)
|
||||||
try:
|
try:
|
||||||
yield selenium
|
yield selenium
|
||||||
finally:
|
finally:
|
||||||
|
@ -164,14 +174,17 @@ if pytest is not None:
|
||||||
selenium.driver.quit()
|
selenium.driver.quit()
|
||||||
|
|
||||||
@pytest.fixture(params=['firefox', 'chrome'], scope='module')
|
@pytest.fixture(params=['firefox', 'chrome'], scope='module')
|
||||||
def _selenium_cached(request):
|
def _selenium_cached(request, web_server_main):
|
||||||
# Cached selenium instance. This is a copy-paste of
|
# Cached selenium instance. This is a copy-paste of
|
||||||
# selenium_standalone to avoid fixture scope issues
|
# selenium_standalone to avoid fixture scope issues
|
||||||
|
server_hostname, server_port, server_log = web_server_main
|
||||||
if request.param == 'firefox':
|
if request.param == 'firefox':
|
||||||
cls = FirefoxWrapper
|
cls = FirefoxWrapper
|
||||||
elif request.param == 'chrome':
|
elif request.param == 'chrome':
|
||||||
cls = ChromeWrapper
|
cls = ChromeWrapper
|
||||||
selenium = cls()
|
selenium = cls(server_port=server_port,
|
||||||
|
server_hostname=server_hostname,
|
||||||
|
server_log=server_log)
|
||||||
try:
|
try:
|
||||||
yield selenium
|
yield selenium
|
||||||
finally:
|
finally:
|
||||||
|
@ -187,35 +200,61 @@ if pytest is not None:
|
||||||
print(_selenium_cached.logs)
|
print(_selenium_cached.logs)
|
||||||
|
|
||||||
|
|
||||||
PORT = 0
|
@pytest.fixture(scope='session')
|
||||||
|
def web_server_main():
|
||||||
|
with spawn_web_server() as output:
|
||||||
|
yield output
|
||||||
|
|
||||||
|
|
||||||
|
@pytest.fixture(scope='session')
|
||||||
|
def web_server_secondary():
|
||||||
|
with spawn_web_server() as output:
|
||||||
|
yield output
|
||||||
|
|
||||||
|
|
||||||
|
@contextlib.contextmanager
|
||||||
def spawn_web_server():
|
def spawn_web_server():
|
||||||
global PORT
|
|
||||||
|
|
||||||
print("Spawning webserver...")
|
|
||||||
|
|
||||||
|
tmp_dir = tempfile.mkdtemp()
|
||||||
|
log_path = pathlib.Path(tmp_dir) / 'http-server.log'
|
||||||
q = multiprocessing.Queue()
|
q = multiprocessing.Queue()
|
||||||
p = multiprocessing.Process(target=run_web_server, args=(q,))
|
p = multiprocessing.Process(target=run_web_server, args=(q, log_path))
|
||||||
|
|
||||||
def shutdown_webserver():
|
try:
|
||||||
|
p.start()
|
||||||
|
port = q.get()
|
||||||
|
hostname = '127.0.0.1'
|
||||||
|
|
||||||
|
print(f"Spawning webserver at http://{hostname}:{port} "
|
||||||
|
f"(see logs in {log_path})")
|
||||||
|
yield hostname, port, log_path
|
||||||
|
finally:
|
||||||
q.put("TERMINATE")
|
q.put("TERMINATE")
|
||||||
p.join()
|
p.join()
|
||||||
atexit.register(shutdown_webserver)
|
shutil.rmtree(tmp_dir)
|
||||||
|
|
||||||
p.start()
|
|
||||||
PORT = q.get()
|
|
||||||
|
|
||||||
|
|
||||||
def run_web_server(q):
|
def run_web_server(q, log_filepath):
|
||||||
|
"""Start the HTTP web server
|
||||||
|
|
||||||
|
Parameters
|
||||||
|
----------
|
||||||
|
q : Queue
|
||||||
|
communication queue
|
||||||
|
log_path : pathlib.Path
|
||||||
|
path to the file where to store the logs
|
||||||
|
"""
|
||||||
import http.server
|
import http.server
|
||||||
import socketserver
|
import socketserver
|
||||||
|
|
||||||
print("Running webserver...")
|
|
||||||
|
|
||||||
os.chdir(BUILD_PATH)
|
os.chdir(BUILD_PATH)
|
||||||
|
|
||||||
|
log_fh = log_filepath.open('w', buffering=1)
|
||||||
|
sys.stdout = log_fh
|
||||||
|
sys.stderr = log_fh
|
||||||
|
|
||||||
class Handler(http.server.CGIHTTPRequestHandler):
|
class Handler(http.server.CGIHTTPRequestHandler):
|
||||||
|
|
||||||
def translate_path(self, path):
|
def translate_path(self, path):
|
||||||
if path.startswith('/test/'):
|
if path.startswith('/test/'):
|
||||||
return TEST_PATH / path[6:]
|
return TEST_PATH / path[6:]
|
||||||
|
@ -227,14 +266,17 @@ def run_web_server(q):
|
||||||
return True
|
return True
|
||||||
return False
|
return False
|
||||||
|
|
||||||
def log_message(self, *args, **kwargs):
|
def log_message(self, format_, *args):
|
||||||
pass
|
print("[%s] source: %s:%s - %s"
|
||||||
|
% (self.log_date_time_string(),
|
||||||
|
*self.client_address,
|
||||||
|
format_ % args))
|
||||||
|
|
||||||
Handler.extensions_map['.wasm'] = 'application/wasm'
|
Handler.extensions_map['.wasm'] = 'application/wasm'
|
||||||
|
|
||||||
with socketserver.TCPServer(("", 0), Handler) as httpd:
|
with socketserver.TCPServer(("", 0), Handler) as httpd:
|
||||||
host, port = httpd.server_address
|
host, port = httpd.server_address
|
||||||
print("serving at port", port)
|
print(f"Starting webserver at http://{host}:{port}")
|
||||||
httpd.server_name = 'test-server'
|
httpd.server_name = 'test-server'
|
||||||
httpd.server_port = port
|
httpd.server_port = port
|
||||||
q.put(port)
|
q.put(port)
|
||||||
|
@ -242,8 +284,8 @@ def run_web_server(q):
|
||||||
def service_actions():
|
def service_actions():
|
||||||
try:
|
try:
|
||||||
if q.get(False) == "TERMINATE":
|
if q.get(False) == "TERMINATE":
|
||||||
|
print('Stopping server...')
|
||||||
sys.exit(0)
|
sys.exit(0)
|
||||||
httpd.shutdown()
|
|
||||||
except queue.Empty:
|
except queue.Empty:
|
||||||
pass
|
pass
|
||||||
|
|
||||||
|
@ -251,10 +293,10 @@ def run_web_server(q):
|
||||||
httpd.serve_forever()
|
httpd.serve_forever()
|
||||||
|
|
||||||
|
|
||||||
@pytest.fixture
|
if (__name__ == '__main__'
|
||||||
def web_server():
|
and multiprocessing.current_process().name == 'MainProcess'
|
||||||
return '127.0.0.1', PORT
|
and not hasattr(sys, "_pytest_session")):
|
||||||
|
with spawn_web_server():
|
||||||
|
# run forever
|
||||||
if multiprocessing.current_process().name == 'MainProcess':
|
while True:
|
||||||
spawn_web_server()
|
time.sleep(1)
|
||||||
|
|
|
@ -1,9 +1,10 @@
|
||||||
import pytest
|
import pytest
|
||||||
|
|
||||||
|
|
||||||
def test_load_from_url(selenium_standalone, web_server):
|
def test_load_from_url(selenium_standalone):
|
||||||
|
|
||||||
url, port = web_server
|
url = selenium_standalone.server_hostname
|
||||||
|
port = selenium_standalone.server_port
|
||||||
|
|
||||||
selenium_standalone.load_package(f"http://{url}:{port}/pyparsing.js")
|
selenium_standalone.load_package(f"http://{url}:{port}/pyparsing.js")
|
||||||
assert "Invalid package name or URI" not in selenium_standalone.logs
|
assert "Invalid package name or URI" not in selenium_standalone.logs
|
||||||
|
|
|
@ -1,3 +1,6 @@
|
||||||
|
import pathlib
|
||||||
|
|
||||||
|
|
||||||
def test_pytest(selenium):
|
def test_pytest(selenium):
|
||||||
selenium.load_package(['pytest', 'numpy', 'nose'])
|
selenium.load_package(['pytest', 'numpy', 'nose'])
|
||||||
|
|
||||||
|
@ -15,3 +18,9 @@ def test_pytest(selenium):
|
||||||
|
|
||||||
logs = '\n'.join(selenium.logs)
|
logs = '\n'.join(selenium.logs)
|
||||||
assert 'INTERNALERROR' not in logs
|
assert 'INTERNALERROR' not in logs
|
||||||
|
|
||||||
|
|
||||||
|
def test_web_server_secondary(selenium, web_server_secondary):
|
||||||
|
host, port, logs = web_server_secondary
|
||||||
|
assert pathlib.Path(logs).exists()
|
||||||
|
assert selenium.server_port != port
|
||||||
|
|
Loading…
Reference in New Issue