-
-
Notifications
You must be signed in to change notification settings - Fork 1.6k
Commit
This commit does not belong to any branch on this repository, and may belong to a fork outside of the repository.
Add Route Resolution Benchmarking to Unit Test (#1499)
* feat: add benchmark tester for route resolution and cleanup test warnings Signed-off-by: Harsha Narayana <[email protected]> * feat: refactor sanic benchmark test util into fixtures Signed-off-by: Harsha Narayana <[email protected]>
- Loading branch information
1 parent
8a59907
commit 34fe26e
Showing
11 changed files
with
192 additions
and
15 deletions.
There are no files selected for viewing
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,53 @@ | ||
from random import choice, seed | ||
from pytest import mark | ||
|
||
import sanic.router | ||
|
||
seed("Pack my box with five dozen liquor jugs.") | ||
|
||
# Disable Caching for testing purpose | ||
sanic.router.ROUTER_CACHE_SIZE = 0 | ||
|
||
|
||
class TestSanicRouteResolution: | ||
@mark.asyncio | ||
async def test_resolve_route_no_arg_string_path( | ||
self, sanic_router, route_generator, benchmark | ||
): | ||
simple_routes = route_generator.generate_random_direct_route( | ||
max_route_depth=4 | ||
) | ||
router, simple_routes = sanic_router(route_details=simple_routes) | ||
route_to_call = choice(simple_routes) | ||
|
||
result = benchmark.pedantic( | ||
router._get, | ||
("/{}".format(route_to_call[-1]), route_to_call[0], "localhost"), | ||
iterations=1000, | ||
rounds=1000, | ||
) | ||
assert await result[0](None) == 1 | ||
|
||
@mark.asyncio | ||
async def test_resolve_route_with_typed_args( | ||
self, sanic_router, route_generator, benchmark | ||
): | ||
typed_routes = route_generator.add_typed_parameters( | ||
route_generator.generate_random_direct_route(max_route_depth=4), | ||
max_route_depth=8, | ||
) | ||
router, typed_routes = sanic_router(route_details=typed_routes) | ||
route_to_call = choice(typed_routes) | ||
url = route_generator.generate_url_for_template( | ||
template=route_to_call[-1] | ||
) | ||
|
||
print("{} -> {}".format(route_to_call[-1], url)) | ||
|
||
result = benchmark.pedantic( | ||
router._get, | ||
("/{}".format(url), route_to_call[0], "localhost"), | ||
iterations=1000, | ||
rounds=1000, | ||
) | ||
assert await result[0](None) == 1 |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -1,12 +1,130 @@ | ||
import random | ||
import re | ||
import string | ||
import sys | ||
import uuid | ||
|
||
import pytest | ||
|
||
from sanic import Sanic | ||
from sanic.router import RouteExists, Router | ||
|
||
random.seed("Pack my box with five dozen liquor jugs.") | ||
|
||
if sys.platform in ["win32", "cygwin"]: | ||
collect_ignore = ["test_worker.py"] | ||
|
||
|
||
async def _handler(request): | ||
""" | ||
Dummy placeholder method used for route resolver when creating a new | ||
route into the sanic router. This router is not actually called by the | ||
sanic app. So do not worry about the arguments to this method. | ||
If you change the return value of this method, make sure to propagate the | ||
change to any test case that leverages RouteStringGenerator. | ||
""" | ||
return 1 | ||
|
||
|
||
TYPE_TO_GENERATOR_MAP = { | ||
"string": lambda: "".join( | ||
[random.choice(string.ascii_letters + string.digits) for _ in range(4)] | ||
), | ||
"int": lambda: random.choice(range(1000000)), | ||
"number": lambda: random.random(), | ||
"alpha": lambda: "".join( | ||
[random.choice(string.ascii_letters) for _ in range(4)] | ||
), | ||
"uuid": lambda: str(uuid.uuid1()), | ||
} | ||
|
||
|
||
class RouteStringGenerator: | ||
|
||
ROUTE_COUNT_PER_DEPTH = 100 | ||
HTTP_METHODS = ["GET", "PUT", "POST", "PATCH", "DELETE", "OPTION"] | ||
ROUTE_PARAM_TYPES = ["string", "int", "number", "alpha", "uuid"] | ||
|
||
def generate_random_direct_route(self, max_route_depth=4): | ||
routes = [] | ||
for depth in range(1, max_route_depth + 1): | ||
for _ in range(self.ROUTE_COUNT_PER_DEPTH): | ||
route = "/".join( | ||
[ | ||
TYPE_TO_GENERATOR_MAP.get("string")() | ||
for _ in range(depth) | ||
] | ||
) | ||
route = route.replace(".", "", -1) | ||
route_detail = (random.choice(self.HTTP_METHODS), route) | ||
|
||
if route_detail not in routes: | ||
routes.append(route_detail) | ||
return routes | ||
|
||
def add_typed_parameters(self, current_routes, max_route_depth=8): | ||
routes = [] | ||
for method, route in current_routes: | ||
current_length = len(route.split("/")) | ||
new_route_part = "/".join( | ||
[ | ||
"<{}:{}>".format( | ||
TYPE_TO_GENERATOR_MAP.get("string")(), | ||
random.choice(self.ROUTE_PARAM_TYPES), | ||
) | ||
for _ in range(max_route_depth - current_length) | ||
] | ||
) | ||
route = "/".join([route, new_route_part]) | ||
route = route.replace(".", "", -1) | ||
routes.append((method, route)) | ||
return routes | ||
|
||
@staticmethod | ||
def generate_url_for_template(template): | ||
url = template | ||
for pattern, param_type in re.findall( | ||
re.compile(r"((?:<\w+:(string|int|number|alpha|uuid)>)+)"), | ||
template, | ||
): | ||
value = TYPE_TO_GENERATOR_MAP.get(param_type)() | ||
url = url.replace(pattern, str(value), -1) | ||
return url | ||
|
||
|
||
@pytest.fixture(scope="function") | ||
def sanic_router(): | ||
# noinspection PyProtectedMember | ||
def _setup(route_details: tuple) -> (Router, tuple): | ||
router = Router() | ||
added_router = [] | ||
for method, route in route_details: | ||
try: | ||
router._add( | ||
uri="/{}".format(route), | ||
methods=frozenset({method}), | ||
host="localhost", | ||
handler=_handler, | ||
) | ||
added_router.append((method, route)) | ||
except RouteExists: | ||
pass | ||
return router, added_router | ||
|
||
return _setup | ||
|
||
|
||
@pytest.fixture(scope="function") | ||
def route_generator() -> RouteStringGenerator: | ||
return RouteStringGenerator() | ||
|
||
|
||
@pytest.fixture(scope="function") | ||
def url_param_generator(): | ||
return TYPE_TO_GENERATOR_MAP | ||
|
||
|
||
@pytest.fixture | ||
def app(request): | ||
return Sanic(request.node.name) |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters