Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We鈥檒l occasionally send you account related emails.

Already on GitHub? Sign in to your account

Block tests from opening sockets #55516

Merged
merged 10 commits into from
Oct 6, 2021
1 change: 1 addition & 0 deletions requirements_test.txt
Original file line number Diff line number Diff line change
Expand Up @@ -18,6 +18,7 @@ pipdeptree==2.1.0
pylint-strict-informational==0.1
pytest-aiohttp==0.3.0
pytest-cov==2.12.1
pytest-socket==0.4.1
pytest-test-groups==1.0.3
pytest-sugar==0.9.4
pytest-timeout==1.4.2
Expand Down
8 changes: 8 additions & 0 deletions tests/components/auth/conftest.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,8 @@
"""Test configuration for auth."""
import pytest


@pytest.fixture
def aiohttp_client(loop, aiohttp_client, socket_enabled):
"""Return aiohttp_client and allow opening sockets."""
return aiohttp_client
6 changes: 6 additions & 0 deletions tests/components/emulated_hue/test_upnp.py
Original file line number Diff line number Diff line change
Expand Up @@ -28,6 +28,12 @@ def sendto(self, response, addr):
self.sends.append((response, addr))


@pytest.fixture
def aiohttp_client(loop, aiohttp_client, socket_enabled):
"""Return aiohttp_client and allow opening sockets."""
return aiohttp_client


@pytest.fixture
def hue_client(aiohttp_client):
"""Return a hue API client."""
Expand Down
6 changes: 6 additions & 0 deletions tests/components/frontend/test_init.py
Original file line number Diff line number Diff line change
Expand Up @@ -79,6 +79,12 @@ async def frontend_themes(hass):
)


@pytest.fixture
def aiohttp_client(loop, aiohttp_client, socket_enabled):
"""Return aiohttp_client and allow opening sockets."""
return aiohttp_client


@pytest.fixture
async def mock_http_client(hass, aiohttp_client, frontend):
"""Start the Home Assistant HTTP component."""
Expand Down
8 changes: 8 additions & 0 deletions tests/components/http/conftest.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,8 @@
"""Test configuration for http."""
import pytest


@pytest.fixture
def aiohttp_client(loop, aiohttp_client, socket_enabled):
"""Return aiohttp_client and allow opening sockets."""
return aiohttp_client
8 changes: 8 additions & 0 deletions tests/components/image_processing/test_init.py
Original file line number Diff line number Diff line change
@@ -1,6 +1,8 @@
"""The tests for the image_processing component."""
from unittest.mock import PropertyMock, patch

import pytest

import homeassistant.components.http as http
import homeassistant.components.image_processing as ip
from homeassistant.const import ATTR_ENTITY_PICTURE
Expand All @@ -11,6 +13,12 @@
from tests.components.image_processing import common


@pytest.fixture
def aiohttp_unused_port(loop, aiohttp_unused_port, socket_enabled):
"""Return aiohttp_unused_port and allow opening sockets."""
return aiohttp_unused_port


def get_url(hass):
"""Return camera url."""
state = hass.states.get("camera.demo_camera")
Expand Down
7 changes: 5 additions & 2 deletions tests/components/motioneye/test_camera.py
Original file line number Diff line number Diff line change
@@ -1,6 +1,5 @@
"""Test the motionEye camera."""
import copy
import logging
from typing import Any, cast
from unittest.mock import AsyncMock, Mock

Expand Down Expand Up @@ -48,7 +47,11 @@

from tests.common import async_fire_time_changed

_LOGGER = logging.getLogger(__name__)

@pytest.fixture
def aiohttp_server(loop, aiohttp_server, socket_enabled):
"""Return aiohttp_server and allow opening sockets."""
return aiohttp_server


async def test_setup_camera(hass: HomeAssistant) -> None:
Expand Down
6 changes: 6 additions & 0 deletions tests/components/nest/conftest.py
Original file line number Diff line number Diff line change
Expand Up @@ -48,6 +48,12 @@ async def response_handler(self, request):
return aiohttp.web.json_response()


@pytest.fixture
def aiohttp_client(loop, aiohttp_client, socket_enabled):
"""Return aiohttp_client and allow opening sockets."""
return aiohttp_client


@pytest.fixture
async def auth(aiohttp_client):
"""Fixture for an AbstractAuth."""
Expand Down
72 changes: 69 additions & 3 deletions tests/conftest.py
Original file line number Diff line number Diff line change
Expand Up @@ -3,13 +3,15 @@
import datetime
import functools
import logging
import socket
import ssl
import threading
from unittest.mock import MagicMock, patch

from aiohttp.test_utils import make_mocked_request
import multidict
import pytest
import pytest_socket
import requests_mock as _requests_mock

from homeassistant import core as ha, loader, runner, util
Expand Down Expand Up @@ -61,6 +63,70 @@ def pytest_configure(config):
)


def pytest_runtest_setup():
"""Throw if tests attempt to open sockets.

allow_unix_socket is set to True because it's needed by asyncio.
Important: socket_allow_hosts must be called before disable_socket, otherwise all
destinations will be allowed.
"""
pytest_socket.socket_allow_hosts(["127.0.0.1"])
disable_socket(allow_unix_socket=True)


@pytest.fixture
def socket_disabled(pytestconfig):
"""Disable socket.socket for duration of this test function.

This incorporates changes from https://github.com/miketheman/pytest-socket/pull/76
and hardcodes allow_unix_socket to True because it's not passed on the command line.
"""
socket_was_enabled = socket.socket == pytest_socket._true_socket
disable_socket(allow_unix_socket=True)
yield
if socket_was_enabled:
pytest_socket.enable_socket()


@pytest.fixture
def socket_enabled(pytestconfig):
"""Enable socket.socket for duration of this test function.

This incorporates changes from https://github.com/miketheman/pytest-socket/pull/76
and hardcodes allow_unix_socket to True because it's not passed on the command line.
"""
socket_was_disabled = socket.socket != pytest_socket._true_socket
pytest_socket.enable_socket()
yield
if socket_was_disabled:
disable_socket(allow_unix_socket=True)


def disable_socket(allow_unix_socket=False):
"""Disable socket.socket to disable the Internet. useful in testing.

This incorporates changes from https://github.com/miketheman/pytest-socket/pull/75
"""

class GuardedSocket(socket.socket):
"""socket guard to disable socket creation (from pytest-socket)."""

def __new__(cls, *args, **kwargs):
try:
if len(args) > 0:
is_unix_socket = args[0] == socket.AF_UNIX
else:
is_unix_socket = kwargs.get("family") == socket.AF_UNIX
except AttributeError:
# AF_UNIX not supported on Windows https://bugs.python.org/issue33408
is_unix_socket = False
if is_unix_socket and allow_unix_socket:
return super().__new__(cls, *args, **kwargs)
raise pytest_socket.SocketBlockedError()

socket.socket = GuardedSocket


def check_real(func):
"""Force a function to require a keyword _test_real to be passed in."""

Expand Down Expand Up @@ -319,7 +385,7 @@ def local_auth(hass):


@pytest.fixture
def hass_client(hass, aiohttp_client, hass_access_token):
def hass_client(hass, aiohttp_client, hass_access_token, socket_enabled):
"""Return an authenticated HTTP client."""

async def auth_client():
Expand All @@ -332,7 +398,7 @@ async def auth_client():


@pytest.fixture
def hass_client_no_auth(hass, aiohttp_client):
def hass_client_no_auth(hass, aiohttp_client, socket_enabled):
"""Return an unauthenticated HTTP client."""

async def client():
Expand Down Expand Up @@ -367,7 +433,7 @@ def current_request_with_host(current_request):


@pytest.fixture
def hass_ws_client(aiohttp_client, hass_access_token, hass):
def hass_ws_client(aiohttp_client, hass_access_token, hass, socket_enabled):
"""Websocket client fixture connected to websocket server."""

async def create_client(hass=hass, access_token=hass_access_token):
Expand Down
18 changes: 18 additions & 0 deletions tests/test_test_fixtures.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,18 @@
"""Test test fixture configuration."""
import socket

import pytest
import pytest_socket


def test_sockets_disabled():
"""Test we can't open sockets."""
with pytest.raises(pytest_socket.SocketBlockedError):
socket.socket()


def test_sockets_enabled(socket_enabled):
"""Test we can't connect to an address different from 127.0.0.1."""
mysocket = socket.socket()
with pytest.raises(pytest_socket.SocketConnectBlockedError):
mysocket.connect(("127.0.0.2", 1234))