-
Notifications
You must be signed in to change notification settings - Fork 0
Commit
This commit does not belong to any branch on this repository, and may belong to a fork outside of the repository.
- Loading branch information
1 parent
651a478
commit eb84496
Showing
6 changed files
with
155 additions
and
6 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
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
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,32 @@ | ||
from datetime import datetime | ||
from typing import Union | ||
|
||
import anyio | ||
from fastapi import FastAPI, Request, HTTPException | ||
|
||
app = FastAPI() | ||
|
||
|
||
@app.get("/sleep") | ||
async def sleep(seconds: Union[int, float]): | ||
return await wait_for(seconds) | ||
|
||
@app.get("/wait/{seconds}") | ||
async def wait(seconds: Union[int, float]): | ||
return await wait_for(seconds) | ||
|
||
@app.get("/delay/{seconds}") | ||
async def delay(seconds: Union[int, float]): | ||
return await wait_for(seconds) | ||
|
||
|
||
async def wait_for(seconds) -> dict: | ||
start = datetime.now() | ||
await anyio.sleep(seconds) | ||
end = datetime.now() | ||
return {"seconds": seconds, "start": start, "end": end} | ||
|
||
|
||
@app.get("/error") | ||
async def raise_exp(): | ||
raise HTTPException(detail="foo", status_code=400) |
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,34 @@ | ||
from datetime import datetime | ||
from typing import Union | ||
|
||
import anyio | ||
from fastapi import FastAPI, HTTPException | ||
|
||
app = FastAPI() | ||
|
||
|
||
@app.get("/sleep") | ||
async def sleep(seconds: Union[int, float]): | ||
return await wait_for(seconds) | ||
|
||
|
||
@app.get("/wait/{seconds}") | ||
async def wait(seconds: Union[int, float]): | ||
return await wait_for(seconds) | ||
|
||
|
||
@app.get("/delay/{seconds}") | ||
async def delay(seconds: Union[int, float]): | ||
return await wait_for(seconds) | ||
|
||
|
||
async def wait_for(seconds) -> dict: | ||
start = datetime.now() | ||
await anyio.sleep(seconds) | ||
end = datetime.now() | ||
return {"seconds": seconds, "start": start, "end": end} | ||
|
||
|
||
@app.get("/error") | ||
async def raise_exp(): | ||
raise HTTPException(detail="foo", status_code=400) |
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,74 @@ | ||
# mypy: no-disallow-untyped-decorators | ||
import contextlib | ||
import threading | ||
import time | ||
|
||
import pytest | ||
import uvicorn | ||
from httpx import AsyncClient | ||
from main import app | ||
|
||
from fastapi_cdn_host.client import HttpSpider | ||
|
||
|
||
class UvicornServer(uvicorn.Server): | ||
def __init__(self, *args, **kw): | ||
super().__init__(config=uvicorn.Config("main:app")) | ||
|
||
def install_signal_handlers(self): | ||
pass | ||
|
||
@contextlib.contextmanager | ||
def run_in_thread(self): | ||
thread = threading.Thread(target=self.run) | ||
thread.daemon = True | ||
thread.start() | ||
try: | ||
while not self.started: | ||
time.sleep(1e-3) | ||
yield | ||
finally: | ||
self.should_exit = True | ||
thread.join() | ||
|
||
|
||
@pytest.fixture(scope="module") | ||
def anyio_backend(): | ||
return "asyncio" | ||
|
||
|
||
@pytest.fixture(scope="module") | ||
async def client(): | ||
async with AsyncClient(app=app, base_url="http://test") as c: | ||
yield c | ||
|
||
|
||
@pytest.mark.anyio | ||
async def test_fetch(client: AsyncClient): | ||
timestamp = time.time() | ||
results = [timestamp] | ||
await HttpSpider.fetch(client, "/error", results, 0) | ||
assert results[0] == timestamp | ||
await HttpSpider.fetch(client, "/sleep?seconds=0.1", results, 0) | ||
assert results[0] != timestamp | ||
timestamp2 = time.time() | ||
results = [timestamp2] | ||
async with AsyncClient(base_url=client.base_url, timeout=0.1) as c: | ||
await HttpSpider.fetch(c, "/sleep?seconds=0.2", results, 0) | ||
assert results[0] == timestamp2 | ||
async with AsyncClient(base_url=client.base_url, timeout=0.1) as c: | ||
await HttpSpider.fetch(c, "/not-exist", results, 0) | ||
assert results[0] == timestamp2 | ||
|
||
|
||
@pytest.mark.anyio | ||
async def test_find(): | ||
waits = (0.4, 0.2, 0.5) | ||
paths = ("sleep?seconds={}", "wait/{}", "delay/{}") | ||
host = "http://127.0.0.1:8000/" | ||
with UvicornServer().run_in_thread(): | ||
urls = [host + path.format(seconds) for seconds, path in zip(waits, paths)] | ||
fastest = await HttpSpider.find_fastest_host(urls, total_seconds=0.1) | ||
assert fastest == urls[0] | ||
fastest = await HttpSpider.find_fastest_host(urls, loop_interval=0.1) | ||
assert fastest == urls[waits.index(min(waits))] |