Skip to content

Commit

Permalink
Improve test coverage
Browse files Browse the repository at this point in the history
  • Loading branch information
waketzheng committed Dec 13, 2023
1 parent 651a478 commit eb84496
Show file tree
Hide file tree
Showing 6 changed files with 155 additions and 6 deletions.
1 change: 1 addition & 0 deletions README.md
Original file line number Diff line number Diff line change
Expand Up @@ -2,6 +2,7 @@
![Python Versions](https://img.shields.io/pypi/pyversions/fastapi-cdn-host)
[![LatestVersionInPypi](https://img.shields.io/pypi/v/fastapi-cdn-host.svg?style=flat)](https://pypi.python.org/pypi/fastapi-cdn-host)
[![GithubActionResult](https://github.com/waketzheng/fastapi-cdn-host/workflows/ci/badge.svg)](https://github.com/waketzheng/fastapi-cdn-host/actions?query=workflow:ci)
[![Coverage Status](https://coveralls.io/repos/github/waketzheng/fastapi-cdn-host/badge.svg?branch=main)](https://coveralls.io/github/waketzheng/fastapi-cdn-host?branch=main)
![Mypy coverage](https://img.shields.io/badge/mypy-100%25-green.svg)
[![Ruff](https://img.shields.io/endpoint?url=https://raw.githubusercontent.com/astral-sh/ruff/main/assets/badge/v2.json)](https://github.com/astral-sh/ruff)
[![code style: black](https://img.shields.io/badge/code%20style-black-000000.svg)](https://github.com/psf/black)
Expand Down
11 changes: 9 additions & 2 deletions fastapi_cdn_host/client.py
Original file line number Diff line number Diff line change
Expand Up @@ -30,10 +30,17 @@

class HttpSpider:
@staticmethod
async def fetch(client, url, results, index) -> None:
async def fetch(
client: httpx.AsyncClient, url: str, results: list, index: int
) -> None:
try:
r = await client.get(url)
except (httpx.ConnectError, httpx.ReadError, httpx.ConnectTimeout):
except (
httpx.ConnectError,
httpx.ReadError,
httpx.ConnectTimeout,
httpx.ReadTimeout,
):
...
else:
if r.status_code < 300:
Expand Down
9 changes: 5 additions & 4 deletions scripts/test.sh
Original file line number Diff line number Diff line change
Expand Up @@ -3,12 +3,13 @@
set -e
set -x

coverage run -m pytest tests/online_cdn
coverage run -m pytest tests/favicon_online_cdn
cd tests/static_auto && coverage run -m pytest test_*.py
cd tests/online_cdn && coverage run -m pytest test_*.py
cd ../favicon_online_cdn && coverage run -m pytest test_*.py
cd ../static_auto && coverage run -m pytest test_*.py
cd ../static_mounted && coverage run -m pytest test_*.py
cd ../custom_static_root && coverage run -m pytest test_*.py
cd ../static_with_favicon && coverage run -m pytest test_*.py
cd ../defined_root_path && coverage run -m pytest test_*.py
cd ../.. && coverage combine
cd ../http_race && coverage run -m pytest test_*.py
cd ../.. && coverage combine tests/*/.coverage
coverage report -m
32 changes: 32 additions & 0 deletions tests/http_race/:wq
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)
34 changes: 34 additions & 0 deletions tests/http_race/main.py
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)
74 changes: 74 additions & 0 deletions tests/http_race/test_http_race.py
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))]

0 comments on commit eb84496

Please sign in to comment.