From 0daf2202913dc194e9f6efe0c4cec10b810c433a Mon Sep 17 00:00:00 2001 From: James Roberts Date: Sat, 18 Dec 2021 17:39:30 +0200 Subject: [PATCH] Testing overhaul --- clean_install.sh | 4 + fastwsgi.py | 8 +- lgtm.yml | 5 +- tests/__init__.py | 0 tests/apps_under_test/__init__.py | 4 + tests/apps_under_test/basic_app.py | 4 + tests/apps_under_test/flask_app.py | 18 +++++ tests/apps_under_test/wsgi_app.py | 30 +++++++ tests/apps_under_test/wsgi_validator_app.py | 11 +++ tests/conftest.py | 86 ++++++++++++++++++--- tests/test_flask.py | 48 ++++-------- tests/test_raw_requests.py | 21 +++++ tests/test_uwsgi.py | 38 --------- tests/test_valid_wsgi_server.py | 25 ------ tests/test_wsgi.py | 29 +++++++ 15 files changed, 221 insertions(+), 110 deletions(-) create mode 100644 clean_install.sh create mode 100644 tests/__init__.py create mode 100644 tests/apps_under_test/__init__.py create mode 100644 tests/apps_under_test/basic_app.py create mode 100644 tests/apps_under_test/flask_app.py create mode 100644 tests/apps_under_test/wsgi_app.py create mode 100644 tests/apps_under_test/wsgi_validator_app.py create mode 100644 tests/test_raw_requests.py delete mode 100644 tests/test_uwsgi.py delete mode 100644 tests/test_valid_wsgi_server.py create mode 100644 tests/test_wsgi.py diff --git a/clean_install.sh b/clean_install.sh new file mode 100644 index 0000000..0ea8d7e --- /dev/null +++ b/clean_install.sh @@ -0,0 +1,4 @@ +sudo rm -rf /usr/local/lib/python3.8/dist-packages/_fastwsgi*; +sudo rm -rf /usr/local/lib/python3.8/dist-packages/fastwsgi*; +rm -rf build/ dist/ bin/ __pycache__/ +sudo python3 setup.py install \ No newline at end of file diff --git a/fastwsgi.py b/fastwsgi.py index 6f9994a..1d3a082 100644 --- a/fastwsgi.py +++ b/fastwsgi.py @@ -48,9 +48,9 @@ def import_from_string(import_str): return module -def print_server_details(): +def print_server_details(host, port): print(f"\n==== FastWSGI ==== ") - print(f"Host: {HOST}\nPort: {PORT}") + print(f"Host: {host}\nPort: {port}") print("==================\n") @@ -65,8 +65,8 @@ def run_from_cli(): _fastwsgi.run_server(wsgi_app, "", PORT, BACKLOG, LOGGING) -def run(wsgi_app, host, port, backlog=1024): - print_server_details() +def run(wsgi_app, host=HOST, port=PORT, backlog=1024): + print_server_details(host, port) print(f"Server listening at http://{host}:{port}") _fastwsgi.run_server(wsgi_app, host, port, backlog, LOGGING) # run_multi_process_server(wsgi_app) diff --git a/lgtm.yml b/lgtm.yml index 789a122..06def13 100644 --- a/lgtm.yml +++ b/lgtm.yml @@ -1,4 +1,7 @@ path_classifiers: library: - llhttp/**/*.c - - llhttp/**/*.h \ No newline at end of file + - llhttp/**/*.h + - libuv/**/*.c + - libuv/**/*.h + - performance_benchmarks/**/*.py \ No newline at end of file diff --git a/tests/__init__.py b/tests/__init__.py new file mode 100644 index 0000000..e69de29 diff --git a/tests/apps_under_test/__init__.py b/tests/apps_under_test/__init__.py new file mode 100644 index 0000000..22e9b14 --- /dev/null +++ b/tests/apps_under_test/__init__.py @@ -0,0 +1,4 @@ +from .basic_app import basic_app +from .wsgi_app import wsgi_app +from .flask_app import app as flask_app +from .wsgi_validator_app import validator_app \ No newline at end of file diff --git a/tests/apps_under_test/basic_app.py b/tests/apps_under_test/basic_app.py new file mode 100644 index 0000000..d77ff01 --- /dev/null +++ b/tests/apps_under_test/basic_app.py @@ -0,0 +1,4 @@ +def basic_app(environ, start_response): + headers = [("Content-Type", "text/plain")] + start_response("200 OK", headers) + return [b"Hello World"] \ No newline at end of file diff --git a/tests/apps_under_test/flask_app.py b/tests/apps_under_test/flask_app.py new file mode 100644 index 0000000..e36c19e --- /dev/null +++ b/tests/apps_under_test/flask_app.py @@ -0,0 +1,18 @@ +from flask import Flask + +app = Flask(__name__) + + +@app.get("/get") +def get(): + return "get", 200 + + +@app.post("/post") +def post(): + return "post", 201 + + +@app.delete("/delete") +def delete(): + return "delete", 204 \ No newline at end of file diff --git a/tests/apps_under_test/wsgi_app.py b/tests/apps_under_test/wsgi_app.py new file mode 100644 index 0000000..d7d908f --- /dev/null +++ b/tests/apps_under_test/wsgi_app.py @@ -0,0 +1,30 @@ +def _get(environ, start_repsonse): + assert environ.get("REQUEST_METHOD") == "GET" + headers = [("Content-Type", "text/plain")] + start_repsonse("200 OK", headers) + return [b"OK"] + + +def _post(environ, start_repsonse): + assert environ.get("REQUEST_METHOD") == "POST" + headers = [("Content-Type", "text/plain")] + start_repsonse("201 Created", headers) + return [b"OK"] + + +def _delete(environ, start_response): + assert environ.get("REQUEST_METHOD") == "DELETE" + start_response("204 No Content", []) + return [b""] + + +routes = { + "/get": _get, + "/post": _post, + "/delete": _delete, +} + + +def wsgi_app(environ, start_response): + app = routes.get(environ["PATH_INFO"]) + return app(environ, start_response) diff --git a/tests/apps_under_test/wsgi_validator_app.py b/tests/apps_under_test/wsgi_validator_app.py new file mode 100644 index 0000000..7cbf7bb --- /dev/null +++ b/tests/apps_under_test/wsgi_validator_app.py @@ -0,0 +1,11 @@ +from wsgiref.validate import validator + + +def simple_app(environ, start_response): + status = "200 OK" + headers = [("Content-type", "text/plain")] + start_response(status, headers) + return [b"Valid"] + + +validator_app = validator(simple_app) \ No newline at end of file diff --git a/tests/conftest.py b/tests/conftest.py index c973cb4..1056149 100644 --- a/tests/conftest.py +++ b/tests/conftest.py @@ -1,28 +1,96 @@ +import os +import sys import pytest import fastwsgi -import time +from enum import Enum + +from contextlib import contextmanager from multiprocessing import Process, set_start_method +from tests.apps_under_test import ( + basic_app, + wsgi_app, + flask_app, + validator_app, +) HOST = "127.0.0.1" -PORT = 8080 +PORT = 5000 class ServerProcess: def __init__(self, application, host=HOST, port=PORT) -> None: self.process = Process(target=fastwsgi.run, args=(application, host, port)) self.endpoint = f"http://{host}:{port}" + self.host = host + self.port = port - def __enter__(self): + def start(self): self.process.start() - time.sleep(1) # Allow server to start - return self - def __exit__(self, exc_type, exc_value, exc_tb): + def kill(self): self.process.kill() -@pytest.fixture(autouse=True, scope="session") -def server_process(): +class Servers(Enum): + BASIC_TEST_SERVER = 1 + WSGI_TEST_SERVER = 2 + FLASK_TEST_SERVER = 3 + VALIDATOR_TEST_SERVER = 4 + + +servers = { + Servers.BASIC_TEST_SERVER: basic_app, + Servers.WSGI_TEST_SERVER: wsgi_app, + Servers.FLASK_TEST_SERVER: flask_app, + Servers.VALIDATOR_TEST_SERVER: validator_app, +} + + +@contextmanager +def mute_stdout(): + old_out = sys.stdout + sys.stdout = open(os.devnull, "w") + yield + sys.stdout = old_out + + +def pytest_sessionstart(session): set_start_method("fork") - return ServerProcess + for i, server in enumerate(servers.items()): + with mute_stdout(): + name, app = server + server_process = ServerProcess(app, port=PORT + i) + server_process.start() + print(f"{name} is listening on port={PORT+i}") + servers[name] = server_process + + +def pytest_sessionfinish(session, exitstatus): + for server_process in servers.values(): + server_process.kill() + + +@pytest.fixture +def server_process(): + return servers.get(Servers.BASIC_TEST_SERVER) + + +@pytest.fixture +def basic_test_server(): + return servers.get(Servers.BASIC_TEST_SERVER) + + +@pytest.fixture +def flask_test_server(): + return servers.get(Servers.FLASK_TEST_SERVER) + + +@pytest.fixture +def wsgi_test_server(): + return servers.get(Servers.WSGI_TEST_SERVER) + + +@pytest.fixture +def validator_test_server(): + return servers.get(Servers.VALIDATOR_TEST_SERVER) diff --git a/tests/test_flask.py b/tests/test_flask.py index 428ebe4..05a3eba 100644 --- a/tests/test_flask.py +++ b/tests/test_flask.py @@ -1,40 +1,22 @@ import requests -from flask import Flask - -app = Flask(__name__) -@app.get("/") -def get(): - return "get", 200 +def test_flask_get(flask_test_server): + url = f"{flask_test_server.endpoint}/get" + result = requests.get(url) + assert result.status_code == 200 + assert result.text == "get" -@app.post("/") -def post(): - return "post", 201 +def test_flask_post(flask_test_server): + url = f"{flask_test_server.endpoint}/post" + result = requests.post(url, json={"test": "data"}) + assert result.status_code == 201 + assert result.text == "post" -@app.delete("/") -def delete(): - return "", 204 - - -def test_flask_get(server_process): - with server_process(app) as server: - result = requests.get(server.endpoint) - assert result.status_code == 200 - assert result.text == "get" - - -def test_flask_post(server_process): - with server_process(app) as server: - result = requests.post(server.endpoint, json={"test": "data"}) - assert result.status_code == 201 - assert result.text == "post" - - -def test_flask_delete(server_process): - with server_process(app) as server: - result = requests.delete(server.endpoint) - assert result.status_code == 204 - assert result.text == "" +def test_flask_delete(flask_test_server): + url = f"{flask_test_server.endpoint}/delete" + result = requests.delete(url) + assert result.status_code == 204 + assert result.text == "" diff --git a/tests/test_raw_requests.py b/tests/test_raw_requests.py new file mode 100644 index 0000000..02fbc5d --- /dev/null +++ b/tests/test_raw_requests.py @@ -0,0 +1,21 @@ +import socket +import pytest + +BAD_REQUEST_RESPONSE = b"HTTP/1.1 400 Bad Request\r\n\r\n" + +raw_requests = [ + "GET\r\n\r\n", + "/\r\n\r\n", + "GET ???\r\n\r\n", + "GET / HTTP\r\n\r\n", + "\r\n", +] + + +@pytest.mark.parametrize("raw_request", raw_requests) +def test_bad_requests(basic_test_server, raw_request): + host, port = basic_test_server.host, basic_test_server.port + connection = socket.create_connection((host, port)) + connection.send(raw_request.encode()) + data = connection.recv(4096) + assert data == BAD_REQUEST_RESPONSE \ No newline at end of file diff --git a/tests/test_uwsgi.py b/tests/test_uwsgi.py deleted file mode 100644 index 4cb759a..0000000 --- a/tests/test_uwsgi.py +++ /dev/null @@ -1,38 +0,0 @@ -import requests - - -def wsgi_app(environ, start_response): - start_response('200 OK', [('Content-Type', 'text/plain')]) - return [b"Hello, WSGI!"] - - -def wsgi_app_delete(environ, start_response): - start_response('204 No Content', []) - return [b""] - - -def test_uwsgi_get(server_process): - with server_process(wsgi_app) as server: - result = requests.get(server.endpoint) - assert result.status_code == 200 - assert result.text == "Hello, WSGI!" - - -def test_uwsgi_post(server_process): - with server_process(wsgi_app) as server: - # Post with no data - result = requests.get(server.endpoint) - assert result.status_code == 200 - assert result.text == "Hello, WSGI!" - - # Post with data - result = requests.get(server.endpoint, {"test": "data"}) - assert result.status_code == 200 - assert result.text == "Hello, WSGI!" - - -def test_uwsgi_delete(server_process): - with server_process(wsgi_app_delete) as server: - result = requests.get(server.endpoint) - assert result.status_code == 204 - assert result.text == "" diff --git a/tests/test_valid_wsgi_server.py b/tests/test_valid_wsgi_server.py deleted file mode 100644 index 3161a50..0000000 --- a/tests/test_valid_wsgi_server.py +++ /dev/null @@ -1,25 +0,0 @@ -import requests - -from wsgiref.validate import validator - - -def simple_app(environ, start_response): - status = '200 OK' - headers = [('Content-type', 'text/plain')] - start_response(status, headers) - return [b"Valid"] - - -validator_app = validator(simple_app) - - -def test_get_valid_wsgi_server(server_process): - with server_process(validator_app) as server: - result = requests.get(server.endpoint) - assert result.text == "Valid" - - -def test_post_valid_wsgi_server(server_process): - with server_process(validator_app) as server: - result = requests.post(server.endpoint, json={"test": "data"}) - assert result.text == "Valid" diff --git a/tests/test_wsgi.py b/tests/test_wsgi.py new file mode 100644 index 0000000..e2d9833 --- /dev/null +++ b/tests/test_wsgi.py @@ -0,0 +1,29 @@ +import requests + + +def test_wsgi_get(wsgi_test_server): + url = f"{wsgi_test_server.endpoint}/get" + result = requests.get(url) + assert result.status_code == 200 + assert result.text == "OK" + + +def test_wsgi_post(wsgi_test_server): + # Post with no data + url = f"{wsgi_test_server.endpoint}/post" + + result = requests.post(url) + assert result.status_code == 201 + assert result.text == "OK" + + # Post with data + result = requests.post(url, json={"test": "data"}) + assert result.status_code == 201 + assert result.text == "OK" + + +def test_wsgi_delete(wsgi_test_server): + url = f"{wsgi_test_server.endpoint}/delete" + result = requests.delete(url) + assert result.status_code == 204 + assert result.text == ""