Commit 387fb18c authored by Roman Yurchak's avatar Roman Yurchak

Refactor test web server to allow multiple instances and log capture

parent 2e23079a
...@@ -2,13 +2,16 @@ ...@@ -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): ...@@ -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: ...@@ -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: ...@@ -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): ...@@ -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: ...@@ -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: ...@@ -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
def spawn_web_server(): @pytest.fixture(scope='session')
global PORT def web_server_secondary():
with spawn_web_server() as output:
yield output
print("Spawning webserver...") @contextlib.contextmanager
def spawn_web_server():
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, log_filepath):
"""Start the HTTP web server
def run_web_server(q): 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): ...@@ -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): ...@@ -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): ...@@ -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)
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
......
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): ...@@ -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
Markdown is supported
0%
or
You are about to add 0 people to the discussion. Proceed with caution.
Finish editing this message first!
Please register or to comment