Skip to content

Commit

Permalink
Add 100% cover to uvicorn/main.py
Browse files Browse the repository at this point in the history
- Mostly test main as a cli client
- Test main functions directly when it's impossible to do is as a cli client (i.e. app non string case)
- Add no cover to the module entrypoint main call

Related to: encode#102
  • Loading branch information
humrochagf committed Aug 15, 2021
1 parent a6dff71 commit 21deebb
Show file tree
Hide file tree
Showing 3 changed files with 107 additions and 2 deletions.
92 changes: 91 additions & 1 deletion tests/test_cli.py
Original file line number Diff line number Diff line change
@@ -1,15 +1,40 @@
import importlib
import platform
import sys
from pathlib import Path
from unittest import mock

import pytest
from click.testing import CliRunner

import uvicorn
from uvicorn.config import Config
from uvicorn.main import main as cli
from uvicorn.server import Server
from uvicorn.supervisors import ChangeReload, Multiprocess

HEADERS = "Content-Security-Policy:default-src 'self'; script-src https://example.com"
main = importlib.import_module("uvicorn.main")


def test_cli_headers():
def test_cli_print_version() -> None:
runner = CliRunner()

result = runner.invoke(cli, ["--version"])

assert result.exit_code == 0
assert (
"Running uvicorn %s with %s %s on %s"
% (
uvicorn.__version__,
platform.python_implementation(),
platform.python_version(),
platform.system(),
)
) in result.output


def test_cli_headers() -> None:
runner = CliRunner()

with mock.patch.object(main, "run") as mock_run:
Expand All @@ -26,5 +51,70 @@ def test_cli_headers():
]


def test_cli_call_server_run() -> None:
runner = CliRunner()

with mock.patch.object(Server, "run") as mock_run:
result = runner.invoke(cli, ["tests.test_cli:App"])

assert result.exit_code == 0
mock_run.assert_called_once()


def test_cli_call_change_reload_run() -> None:
runner = CliRunner()

with mock.patch.object(Config, "bind_socket") as mock_bind_socket:
with mock.patch.object(ChangeReload, "run") as mock_run:
result = runner.invoke(cli, ["tests.test_cli:App", "--reload"])

assert result.exit_code == 0
mock_bind_socket.assert_called_once()
mock_run.assert_called_once()


def test_cli_call_multiprocess_run() -> None:
runner = CliRunner()

with mock.patch.object(Config, "bind_socket") as mock_bind_socket:
with mock.patch.object(Multiprocess, "run") as mock_run:
result = runner.invoke(cli, ["tests.test_cli:App", "--workers=2"])

assert result.exit_code == 0
mock_bind_socket.assert_called_once()
mock_run.assert_called_once()


@pytest.mark.skipif(sys.platform == "win32", reason="require unix-like system")
def test_cli_uds(tmp_path: Path) -> None:
runner = CliRunner()
uds_file = tmp_path / "uvicorn.sock"
uds_file.touch(exist_ok=True)

with mock.patch.object(Config, "bind_socket") as mock_bind_socket:
with mock.patch.object(Multiprocess, "run") as mock_run:
result = runner.invoke(
cli, ["tests.test_cli:App", "--workers=2", "--uds", str(uds_file)]
)

assert result.exit_code == 0
assert result.output == ""
mock_bind_socket.assert_called_once()
mock_run.assert_called_once()
assert not uds_file.exists()


def test_cli_incomplete_app_parameter() -> None:
runner = CliRunner()

result = runner.invoke(cli, ["tests.test_cli"])

assert (
'Error loading ASGI app. Import string "tests.test_cli" '
'must be in format "<module>:<attribute>".'
) in result.output
assert result.exit_code == 1


class App:
pass
15 changes: 15 additions & 0 deletions tests/test_main.py
Original file line number Diff line number Diff line change
@@ -1,8 +1,11 @@
from logging import WARNING

import httpx
import pytest

from tests.utils import run_server
from uvicorn.config import Config
from uvicorn.main import run


async def app(scope, receive, send):
Expand Down Expand Up @@ -59,3 +62,15 @@ async def test_run_reload():
async with httpx.AsyncClient() as client:
response = await client.get("http://127.0.0.1:8000")
assert response.status_code == 204


def test_run_invalid_app_config_combination(caplog: pytest.LogCaptureFixture) -> None:
with pytest.raises(SystemExit) as exit_exception:
run(app, reload=True)
assert exit_exception.value.code == 1
assert caplog.records[-1].name == "uvicorn.error"
assert caplog.records[-1].levelno == WARNING
assert caplog.records[-1].message == (
"You must pass the application as an import string to enable "
"'reload' or 'workers'."
)
2 changes: 1 addition & 1 deletion uvicorn/main.py
Original file line number Diff line number Diff line change
Expand Up @@ -450,4 +450,4 @@ def run(app: typing.Union[ASGIApplication, str], **kwargs: typing.Any) -> None:


if __name__ == "__main__":
main()
main() # pragma: no cover

0 comments on commit 21deebb

Please sign in to comment.