Change back to codecov (#2363)

This commit is contained in:
Adam Hopkins 2022-01-09 12:22:09 +02:00 committed by GitHub
parent 101151b419
commit 8b0eaa097c
No known key found for this signature in database
GPG Key ID: 4AEE18F83AFDEB23
12 changed files with 311 additions and 69 deletions

View File

@ -1,28 +0,0 @@
exclude_patterns:
- "sanic/__main__.py"
- "sanic/application/logo.py"
- "sanic/application/motd.py"
- "sanic/reloader_helpers.py"
- "sanic/simple.py"
- "sanic/utils.py"
- ".github/"
- "changelogs/"
- "docker/"
- "docs/"
- "examples/"
- "scripts/"
- "tests/"
checks:
argument-count:
enabled: false
file-lines:
config:
threshold: 1000
method-count:
config:
threshold: 40
complex-logic:
enabled: false
method-complexity:
config:
threshold: 10

View File

@ -3,13 +3,12 @@ branch = True
source = sanic
omit =
site-packages
sanic/application/logo.py
sanic/application/motd.py
sanic/cli
sanic/__main__.py
sanic/compat.py
sanic/reloader_helpers.py
sanic/simple.py
sanic/utils.py
sanic/cli
[html]
directory = coverage
@ -21,3 +20,12 @@ exclude_lines =
noqa
NOQA
pragma: no cover
omit =
site-packages
sanic/__main__.py
sanic/compat.py
sanic/reloader_helpers.py
sanic/simple.py
sanic/utils.py
sanic/cli
skip_empty = True

View File

@ -20,7 +20,6 @@ jobs:
steps:
- uses: actions/checkout@v2
- uses: actions/setup-python@v1
with:
python-version: ${{ matrix.python-version }}
@ -29,9 +28,9 @@ jobs:
run: |
python -m pip install --upgrade pip
pip install tox
- uses: paambaati/codeclimate-action@v2.5.3
if: always()
env:
CC_TEST_REPORTER_ID: ${{ secrets.CODECLIMATE }}
- name: Run coverage
run: tox -e coverage
- uses: codecov/codecov-action@v2
with:
coverageCommand: tox -e coverage
files: ./coverage.xml
fail_ci_if_error: false

27
codecov.yml Normal file
View File

@ -0,0 +1,27 @@
coverage:
status:
patch:
default:
target: auto
threshold: 0.75
project:
default:
target: auto
threshold: 0.5
precision: 3
codecov:
require_ci_to_pass: false
ignore:
- "sanic/__main__.py"
- "sanic/compat.py"
- "sanic/reloader_helpers.py"
- "sanic/simple.py"
- "sanic/utils.py"
- "sanic/cli"
- ".github/"
- "changelogs/"
- "docker/"
- "docs/"
- "examples/"
- "scripts/"
- "tests/"

View File

@ -114,7 +114,7 @@ if TYPE_CHECKING: # no cov
Extend = TypeVar("Extend") # type: ignore
if OS_IS_WINDOWS:
if OS_IS_WINDOWS: # no cov
enable_windows_color_support()
filterwarnings("once", category=DeprecationWarning)
@ -1554,7 +1554,7 @@ class Sanic(BaseSanic, metaclass=TouchUpMeta):
) -> Task:
if not isinstance(task, Future):
prepped = cls._prep_task(task, app, loop)
if sys.version_info < (3, 8):
if sys.version_info < (3, 8): # no cov
if name:
error_logger.warning(
"Cannot set a name for a task when using Python 3.7. "
@ -1598,7 +1598,7 @@ class Sanic(BaseSanic, metaclass=TouchUpMeta):
:param task: future, couroutine or awaitable
"""
if name and sys.version_info == (3, 7):
if name and sys.version_info < (3, 8): # no cov
name = None
error_logger.warning(
"Cannot set a name for a task when using Python 3.7. Your "
@ -1626,7 +1626,7 @@ class Sanic(BaseSanic, metaclass=TouchUpMeta):
def get_task(
self, name: str, *, raise_exception: bool = True
) -> Optional[Task]:
if sys.version_info < (3, 8):
if sys.version_info < (3, 8): # no cov
error_logger.warning(
"This feature (get_task) is only supported on using "
"Python 3.8+."
@ -1648,7 +1648,7 @@ class Sanic(BaseSanic, metaclass=TouchUpMeta):
*,
raise_exception: bool = True,
) -> None:
if sys.version_info < (3, 8):
if sys.version_info < (3, 8): # no cov
error_logger.warning(
"This feature (cancel_task) is only supported on using "
"Python 3.8+."
@ -1660,7 +1660,7 @@ class Sanic(BaseSanic, metaclass=TouchUpMeta):
if msg:
if sys.version_info >= (3, 9):
args = (msg,)
else:
else: # no cov
raise RuntimeError(
"Cancelling a task with a message is only supported "
"on Python 3.9+."
@ -1672,7 +1672,7 @@ class Sanic(BaseSanic, metaclass=TouchUpMeta):
...
def purge_tasks(self):
if sys.version_info < (3, 8):
if sys.version_info < (3, 8): # no cov
error_logger.warning(
"This feature (purge_tasks) is only supported on using "
"Python 3.8+."
@ -1709,7 +1709,7 @@ class Sanic(BaseSanic, metaclass=TouchUpMeta):
@property
def tasks(self):
if sys.version_info < (3, 8):
if sys.version_info < (3, 8): # no cov
error_logger.warning(
"This feature (tasks) is only supported on using "
"Python 3.8+."

View File

@ -53,14 +53,14 @@ class ErrorHandler:
self._warn_fallback_deprecation()
@property
def fallback(self):
def fallback(self): # no cov
# This is for backwards compat and can be removed in v22.6
if self._fallback is _default:
return DEFAULT_FORMAT
return self._fallback
@fallback.setter
def fallback(self, value: str):
def fallback(self, value: str): # no cov
self._warn_fallback_deprecation()
if not isinstance(value, str):
raise SanicException(
@ -236,7 +236,7 @@ class ErrorHandler:
except Exception:
try:
url = repr(request.url)
except AttributeError:
except AttributeError: # no cov
url = "unknown"
response_message = (
"Exception raised in exception handler " '"%s" for uri: %s'
@ -281,7 +281,7 @@ class ErrorHandler:
if quiet is False or noisy is True:
try:
url = repr(request.url)
except AttributeError:
except AttributeError: # no cov
url = "unknown"
error_logger.exception(

View File

@ -6,7 +6,7 @@ from typing import Any, Dict
from warnings import warn
LOGGING_CONFIG_DEFAULTS: Dict[str, Any] = dict(
LOGGING_CONFIG_DEFAULTS: Dict[str, Any] = dict( # no cov
version=1,
disable_existing_loggers=False,
loggers={
@ -57,7 +57,7 @@ LOGGING_CONFIG_DEFAULTS: Dict[str, Any] = dict(
)
class Colors(str, Enum):
class Colors(str, Enum): # no cov
END = "\033[0m"
BLUE = "\033[01;34m"
GREEN = "\033[01;32m"
@ -65,23 +65,23 @@ class Colors(str, Enum):
RED = "\033[01;31m"
logger = logging.getLogger("sanic.root")
logger = logging.getLogger("sanic.root") # no cov
"""
General Sanic logger
"""
error_logger = logging.getLogger("sanic.error")
error_logger = logging.getLogger("sanic.error") # no cov
"""
Logger used by Sanic for error logging
"""
access_logger = logging.getLogger("sanic.access")
access_logger = logging.getLogger("sanic.access") # no cov
"""
Logger used by Sanic for access logging
"""
def deprecation(message: str, version: float):
def deprecation(message: str, version: float): # no cov
version_info = f"[DEPRECATION v{version}] "
if sys.stdout.isatty():
version_info = f"{Colors.RED}{version_info}"

View File

@ -13,7 +13,7 @@ ASGISend = Callable[[ASGIMessage], Awaitable[None]]
ASGIReceive = Callable[[], Awaitable[ASGIMessage]]
class MockProtocol:
class MockProtocol: # no cov
def __init__(self, transport: "MockTransport", loop):
# This should be refactored when < 3.8 support is dropped
self.transport = transport
@ -56,7 +56,7 @@ class MockProtocol:
await self._not_paused.wait()
class MockTransport:
class MockTransport: # no cov
_protocol: Optional[MockProtocol]
def __init__(

View File

@ -9,7 +9,7 @@ from websockets.typing import Data
from sanic.exceptions import ServerError
if TYPE_CHECKING:
if TYPE_CHECKING: # no cov
from .impl import WebsocketImplProtocol
UTF8Decoder = codecs.getincrementaldecoder("utf-8")
@ -37,7 +37,7 @@ class WebsocketFrameAssembler:
"get_id",
"put_id",
)
if TYPE_CHECKING:
if TYPE_CHECKING: # no cov
protocol: "WebsocketImplProtocol"
read_mutex: asyncio.Lock
write_mutex: asyncio.Lock
@ -131,7 +131,7 @@ class WebsocketFrameAssembler:
if self.paused:
self.protocol.resume_frames()
self.paused = False
if not self.get_in_progress:
if not self.get_in_progress: # no cov
# This should be guarded against with the read_mutex,
# exception is here as a failsafe
raise ServerError(
@ -204,7 +204,7 @@ class WebsocketFrameAssembler:
if self.paused:
self.protocol.resume_frames()
self.paused = False
if not self.get_in_progress:
if not self.get_in_progress: # no cov
# This should be guarded against with the read_mutex,
# exception is here as a failsafe
raise ServerError(
@ -212,7 +212,7 @@ class WebsocketFrameAssembler:
"asynchronous get was in progress."
)
self.get_in_progress = False
if not self.message_complete.is_set():
if not self.message_complete.is_set(): # no cov
# This should be guarded against with the read_mutex,
# exception is here as a failsafe
raise ServerError(
@ -220,7 +220,7 @@ class WebsocketFrameAssembler:
"message was complete."
)
self.message_complete.clear()
if self.message_fetched.is_set():
if self.message_fetched.is_set(): # no cov
# This should be guarded against with the read_mutex,
# and get_in_progress check, this exception is
# here as a failsafe

View File

@ -1,5 +1,4 @@
import asyncio
import base64
import logging
import random
import re
@ -205,7 +204,3 @@ def sanic_ext(ext_instance): # noqa
yield sanic_ext
with suppress(KeyError):
del sys.modules["sanic_ext"]
def encode_basic_auth_credentials(username, password):
return base64.b64encode(f"{username}:{password}".encode()).decode("ascii")

View File

@ -1,3 +1,4 @@
import base64
import logging
from json import dumps as json_dumps
@ -18,7 +19,10 @@ from sanic import Blueprint, Sanic
from sanic.exceptions import ServerError
from sanic.request import DEFAULT_HTTP_CONTENT_TYPE, RequestParameters
from sanic.response import html, json, text
from tests.conftest import encode_basic_auth_credentials
def encode_basic_auth_credentials(username, password):
return base64.b64encode(f"{username}:{password}".encode()).decode("ascii")
# ------------------------------------------------------------ #

237
tests/test_websockets.py Normal file
View File

@ -0,0 +1,237 @@
import re
from asyncio import Event, Queue, TimeoutError
from unittest.mock import AsyncMock, Mock, call
import pytest
from websockets.frames import CTRL_OPCODES, DATA_OPCODES, Frame
from sanic.exceptions import ServerError
from sanic.server.websockets.frame import WebsocketFrameAssembler
@pytest.mark.asyncio
async def test_ws_frame_get_message_incomplete_timeout_0():
assembler = WebsocketFrameAssembler(Mock())
assembler.message_complete = AsyncMock(spec=Event)
assembler.message_complete.is_set = Mock(return_value=False)
data = await assembler.get(0)
assert data is None
assembler.message_complete.is_set.assert_called_once()
@pytest.mark.asyncio
async def test_ws_frame_get_message_in_progress():
assembler = WebsocketFrameAssembler(Mock())
assembler.get_in_progress = True
message = re.escape(
"Called get() on Websocket frame assembler "
"while asynchronous get is already in progress."
)
with pytest.raises(ServerError, match=message):
await assembler.get()
@pytest.mark.asyncio
async def test_ws_frame_get_message_incomplete():
assembler = WebsocketFrameAssembler(Mock())
assembler.message_complete.wait = AsyncMock(return_value=True)
assembler.message_complete.is_set = Mock(return_value=False)
data = await assembler.get()
assert data is None
assembler.message_complete.wait.assert_awaited_once()
@pytest.mark.asyncio
async def test_ws_frame_get_message():
assembler = WebsocketFrameAssembler(Mock())
assembler.message_complete.wait = AsyncMock(return_value=True)
assembler.message_complete.is_set = Mock(return_value=True)
data = await assembler.get()
assert data == b""
assembler.message_complete.wait.assert_awaited_once()
@pytest.mark.asyncio
async def test_ws_frame_get_message_with_timeout():
assembler = WebsocketFrameAssembler(Mock())
assembler.message_complete.wait = AsyncMock(return_value=True)
assembler.message_complete.is_set = Mock(return_value=True)
data = await assembler.get(0.1)
assert data == b""
assembler.message_complete.wait.assert_awaited_once()
assert assembler.message_complete.is_set.call_count == 2
@pytest.mark.asyncio
async def test_ws_frame_get_message_with_timeouterror():
assembler = WebsocketFrameAssembler(Mock())
assembler.message_complete.wait = AsyncMock(return_value=True)
assembler.message_complete.is_set = Mock(return_value=True)
assembler.message_complete.wait.side_effect = TimeoutError("...")
data = await assembler.get(0.1)
assert data == b""
assembler.message_complete.wait.assert_awaited_once()
assert assembler.message_complete.is_set.call_count == 2
@pytest.mark.asyncio
async def test_ws_frame_get_not_completed():
assembler = WebsocketFrameAssembler(Mock())
assembler.message_complete = AsyncMock(spec=Event)
assembler.message_complete.is_set = Mock(return_value=False)
data = await assembler.get()
assert data is None
@pytest.mark.asyncio
async def test_ws_frame_get_not_completed_start():
assembler = WebsocketFrameAssembler(Mock())
assembler.message_complete = AsyncMock(spec=Event)
assembler.message_complete.is_set = Mock(side_effect=[False, True])
data = await assembler.get(0.1)
assert data is None
@pytest.mark.asyncio
async def test_ws_frame_get_paused():
assembler = WebsocketFrameAssembler(Mock())
assembler.message_complete = AsyncMock(spec=Event)
assembler.message_complete.is_set = Mock(side_effect=[False, True])
assembler.paused = True
data = await assembler.get()
assert data is None
assembler.protocol.resume_frames.assert_called_once()
@pytest.mark.asyncio
async def test_ws_frame_get_data():
assembler = WebsocketFrameAssembler(Mock())
assembler.message_complete = AsyncMock(spec=Event)
assembler.message_complete.is_set = Mock(return_value=True)
assembler.chunks = [b"foo", b"bar"]
data = await assembler.get()
assert data == b"foobar"
@pytest.mark.asyncio
async def test_ws_frame_get_iter_in_progress():
assembler = WebsocketFrameAssembler(Mock())
assembler.get_in_progress = True
message = re.escape(
"Called get_iter on Websocket frame assembler "
"while asynchronous get is already in progress."
)
with pytest.raises(ServerError, match=message):
[x async for x in assembler.get_iter()]
@pytest.mark.asyncio
async def test_ws_frame_get_iter_none_in_queue():
assembler = WebsocketFrameAssembler(Mock())
assembler.message_complete.set()
assembler.chunks = [b"foo", b"bar"]
chunks = [x async for x in assembler.get_iter()]
assert chunks == [b"foo", b"bar"]
@pytest.mark.asyncio
async def test_ws_frame_get_iter_paused():
assembler = WebsocketFrameAssembler(Mock())
assembler.message_complete.set()
assembler.paused = True
[x async for x in assembler.get_iter()]
assembler.protocol.resume_frames.assert_called_once()
@pytest.mark.asyncio
@pytest.mark.parametrize("opcode", DATA_OPCODES)
async def test_ws_frame_put_not_fetched(opcode):
assembler = WebsocketFrameAssembler(Mock())
assembler.message_fetched.set()
message = re.escape(
"Websocket put() got a new message when the previous message was "
"not yet fetched."
)
with pytest.raises(ServerError, match=message):
await assembler.put(Frame(opcode, b""))
@pytest.mark.asyncio
@pytest.mark.parametrize("opcode", DATA_OPCODES)
async def test_ws_frame_put_fetched(opcode):
assembler = WebsocketFrameAssembler(Mock())
assembler.message_fetched = AsyncMock()
assembler.message_fetched.is_set = Mock(return_value=False)
await assembler.put(Frame(opcode, b""))
assembler.message_fetched.wait.assert_awaited_once()
assembler.message_fetched.clear.assert_called_once()
@pytest.mark.asyncio
@pytest.mark.parametrize("opcode", DATA_OPCODES)
async def test_ws_frame_put_message_complete(opcode):
assembler = WebsocketFrameAssembler(Mock())
assembler.message_complete.set()
message = re.escape(
"Websocket put() got a new message when a message was "
"already in its chamber."
)
with pytest.raises(ServerError, match=message):
await assembler.put(Frame(opcode, b""))
@pytest.mark.asyncio
@pytest.mark.parametrize("opcode", DATA_OPCODES)
async def test_ws_frame_put_message_into_queue(opcode):
assembler = WebsocketFrameAssembler(Mock())
assembler.chunks_queue = AsyncMock(spec=Queue)
assembler.message_fetched = AsyncMock()
assembler.message_fetched.is_set = Mock(return_value=False)
await assembler.put(Frame(opcode, b"foo"))
assembler.chunks_queue.put.has_calls(
call(b"foo"),
call(None),
)
@pytest.mark.asyncio
@pytest.mark.parametrize("opcode", DATA_OPCODES)
async def test_ws_frame_put_not_fin(opcode):
assembler = WebsocketFrameAssembler(Mock())
retval = await assembler.put(Frame(opcode, b"foo", fin=False))
assert retval is None
@pytest.mark.asyncio
@pytest.mark.parametrize("opcode", CTRL_OPCODES)
async def test_ws_frame_put_skip_ctrl(opcode):
assembler = WebsocketFrameAssembler(Mock())
retval = await assembler.put(Frame(opcode, b""))
assert retval is None