Skip to content

Commit 69321a6

Browse files
authored
Merge branch 'main' into block-getaddr-and-host
2 parents a30ef55 + 83b596a commit 69321a6

14 files changed

+422
-69
lines changed

.codeclimate.yml

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -9,6 +9,8 @@ plugins:
99
enabled: true
1010
radon:
1111
enabled: true
12+
config:
13+
threshold: "C"
1214
exclude_patterns:
1315
- "*.egg-info/"
1416
- .cache/

.gitignore

Lines changed: 3 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -75,3 +75,6 @@ target/
7575

7676
# poetry lock file - for libraries only
7777
poetry.lock
78+
79+
# Installation tracking file
80+
.install.stamp

.pre-commit-config.yaml

Lines changed: 17 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,17 @@
1+
# See https://pre-commit.com for more information
2+
# See https://pre-commit.com/hooks.html for more hooks
3+
repos:
4+
- repo: https://github.com/pre-commit/pre-commit-hooks
5+
rev: v4.0.1
6+
hooks:
7+
- id: check-yaml
8+
- id: check-added-large-files
9+
- repo: https://github.com/editorconfig-checker/editorconfig-checker.python
10+
rev: '2.4.0'
11+
hooks:
12+
- id: editorconfig-checker
13+
alias: ec
14+
- repo: https://github.com/pycqa/flake8
15+
rev: '4.0.1'
16+
hooks:
17+
- id: flake8

CHANGELOG.md

Lines changed: 15 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -4,6 +4,19 @@ This document records all notable changes to
44
[pytest-socket](https://pypi.python.org/pypi/pytest-socket). This
55
project attempts to adhere to [Semantic Versioning](http://semver.org/).
66

7+
## [0.4.2][] (Unreleased)
8+
9+
Fixes:
10+
11+
- Prevent `IndexError` with `httpx.AsyncClient` #85
12+
13+
- Development updates
14+
- Doc updates
15+
16+
## [0.4.1][] (2021-08-29)
17+
18+
- Include tests and configs in source distribution archive #69
19+
720
## [0.4.0][] (2021-03-30)
821

922
Enhancements:
@@ -74,3 +87,5 @@ Maintenance release.
7487
[0.3.4]: https://github.com/miketheman/pytest-socket/compare/0.3.3...0.3.4
7588
[0.3.5]: https://github.com/miketheman/pytest-socket/compare/0.3.4...0.3.5
7689
[0.4.0]: https://github.com/miketheman/pytest-socket/compare/0.3.5...0.4.0
90+
[0.4.1]: https://github.com/miketheman/pytest-socket/compare/0.4.0...0.4.1
91+
[0.4.2]: https://github.com/miketheman/pytest-socket/compare/0.4.1...0.4.2

Makefile

Lines changed: 9 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -1,24 +1,28 @@
11
.PHONY: all clean poetry test dist testrelease release
22

3+
INSTALL_STAMP := .install.stamp
34
POETRY := $(shell command -v poetry 2> /dev/null)
5+
PYTEST_FLAGS :=
46

57
all: test
68

79
clean:
810
@find . -name \*.pyc -delete
911
@find . -name __pycache__ -delete
10-
@rm -fr .cache/ .coverage .pytest_cache/ *.egg-info/ dist/ htmlcov/
12+
@rm -fr $(INSTALL_STAMP) .cache/ .coverage .pytest_cache/ *.egg-info/ dist/ htmlcov/
1113

12-
poetry.lock pytest_socket.egg-info/ :
14+
install: $(INSTALL_STAMP)
15+
$(INSTALL_STAMP): pyproject.toml poetry.lock
1316
ifndef POETRY
1417
$(error "poetry is not available, please install it first.")
1518
endif
1619
@poetry install
20+
@touch $(INSTALL_STAMP)
1721

18-
test: pytest_socket.egg-info/
19-
@poetry run pytest
22+
test: $(INSTALL_STAMP)
23+
@poetry run pytest $(PYTEST_FLAGS)
2024

21-
dist: clean poetry.lock
25+
dist: clean $(INSTALL_STAMP)
2226
@poetry build
2327

2428
testrelease: dist

README.md

Lines changed: 10 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -3,6 +3,7 @@
33
[![PyPI current version](https://img.shields.io/pypi/v/pytest-socket.svg)](https://pypi.python.org/pypi/pytest-socket)
44
[![Python Support](https://img.shields.io/pypi/pyversions/pytest-socket.svg)](https://pypi.python.org/pypi/pytest-socket)
55
[![Tests](https://github.com/miketheman/pytest-socket/workflows/Python%20Tests/badge.svg)](https://github.com/miketheman/pytest-socket/actions?query=workflow%3A%22Python+Tests%22)
6+
[![pre-commit.ci status](https://results.pre-commit.ci/badge/github/miketheman/pytest-socket/main.svg)](https://results.pre-commit.ci/latest/github/miketheman/pytest-socket/main)
67
[![Maintainability](https://api.codeclimate.com/v1/badges/1608a75b1c3a20211992/maintainability)](https://codeclimate.com/github/miketheman/pytest-socket/maintainability)
78
[![FOSSA Status](https://app.fossa.io/api/projects/git%2Bgithub.com%2Fmiketheman%2Fpytest-socket.svg?type=shield)](https://app.fossa.io/projects/git%2Bgithub.com%2Fmiketheman%2Fpytest-socket?ref=badge_shield)
89

@@ -41,14 +42,21 @@ Run `pytest --disable-socket`, tests should fail on any access to `socket` or
4142
libraries using socket with a `SocketBlockedError`.
4243

4344
To add this flag as the default behavior, add this section to your
44-
`pytest.ini` or `setup.cfg`:
45+
[`pytest.ini`](https://docs.pytest.org/en/6.2.x/customize.html#pytest-ini):
4546

4647
```ini
4748
[pytest]
4849
addopts = --disable-socket
4950
```
5051

51-
or update your `conftest.py` to include:
52+
or add this to your [`setup.cfg`](https://docs.pytest.org/en/6.2.x/customize.html#setup-cfg):
53+
54+
```ini
55+
[tool:pytest]
56+
addopts = --disable-socket
57+
```
58+
59+
or update your [`conftest.py`](https://docs.pytest.org/en/6.2.x/writing_plugins.html#conftest-py-plugins) to include:
5260

5361
```python
5462
from pytest_socket import disable_socket

pyproject.toml

Lines changed: 10 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -1,6 +1,6 @@
11
[tool.poetry]
22
name = "pytest-socket"
3-
version = "0.4.0"
3+
version = "0.4.1"
44
description = "Pytest Plugin to disable socket calls during tests"
55
authors = ["Mike Fiedler <miketheman@gmail.com>"]
66
license = "MIT"
@@ -10,6 +10,10 @@ repository = "https://github.com/miketheman/pytest-socket"
1010
include = [
1111
"LICENSE",
1212
"README.md",
13+
{ path = "tests", format = "sdist" },
14+
{ path = "pytest.ini", format = "sdist" },
15+
{ path = ".coveragerc", format = "sdist" },
16+
{ path = ".flake8", format = "sdist" },
1317
]
1418
classifiers = [
1519
"Development Status :: 4 - Beta",
@@ -27,16 +31,17 @@ classifiers = [
2731
]
2832

2933
[tool.poetry.dependencies]
30-
python = "^3.6"
34+
python = "^3.6.2"
3135
pytest = ">=3.6.3"
3236

3337
[tool.poetry.dev-dependencies]
3438
pytest = "^6.0"
3539
pytest-cov = "^2.8.1"
3640
pytest-httpbin = "^1.0"
37-
pytest-flake8 = "^1.0.4"
3841
asynctest = "^0.13.0"
42+
requests = "^2.26.0"
3943
starlette = "^0.14.2"
44+
httpx = "^0.21.1"
4045

4146
[tool.poetry.plugins.pytest11]
4247
socket = 'pytest_socket'
@@ -45,5 +50,5 @@ socket = 'pytest_socket'
4550
"Bug Tracker" = "https://github.com/miketheman/pytest-socket/issues"
4651

4752
[build-system]
48-
requires = ["poetry>=0.12"]
49-
build-backend = "poetry.masonry.api"
53+
requires = ["poetry-core>=1.0.0"]
54+
build-backend = "poetry.core.masonry.api"

pytest.ini

Lines changed: 0 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -2,4 +2,3 @@
22
addopts =
33
--cov
44
--cov-report=term
5-
--flake8

pytest_socket.py

Lines changed: 57 additions & 40 deletions
Original file line numberDiff line numberDiff line change
@@ -44,40 +44,27 @@ def pytest_addoption(parser):
4444
)
4545

4646

47-
@pytest.fixture(autouse=True)
48-
def _socket_marker(request):
49-
"""
50-
Create an automatically-used fixture that every test function will load.
51-
52-
The last option to set the fixture wins priority.
53-
The expected behavior is that higher granularity options should override
54-
lower granularity options.
55-
"""
56-
if request.config.getoption('--disable-socket'):
57-
request.getfixturevalue('socket_disabled')
58-
59-
if request.node.get_closest_marker('disable_socket'):
60-
request.getfixturevalue('socket_disabled')
61-
if request.node.get_closest_marker('enable_socket'):
62-
request.getfixturevalue('socket_enabled')
63-
64-
6547
@pytest.fixture
6648
def socket_disabled(pytestconfig):
6749
""" disable socket.socket for duration of this test function """
68-
allow_unix_socket = pytestconfig.getoption('--allow-unix-socket')
69-
disable_socket(allow_unix_socket)
50+
disable_socket(allow_unix_socket=pytestconfig.__socket_allow_unix_socket)
7051
yield
71-
enable_socket()
7252

7353

7454
@pytest.fixture
7555
def socket_enabled(pytestconfig):
7656
""" enable socket.socket for duration of this test function """
7757
enable_socket()
7858
yield
79-
allow_unix_socket = pytestconfig.getoption('--allow-unix-socket')
80-
disable_socket(allow_unix_socket)
59+
60+
61+
def _is_unix_socket(family) -> bool:
62+
try:
63+
is_unix_socket = family == socket.AF_UNIX
64+
except AttributeError:
65+
# AF_UNIX not supported on Windows https://bugs.python.org/issue33408
66+
is_unix_socket = False
67+
return is_unix_socket
8168

8269

8370
def disable_socket(allow_unix_socket=False):
@@ -86,15 +73,9 @@ def disable_socket(allow_unix_socket=False):
8673

8774
class GuardedSocket(socket.socket):
8875
""" socket guard to disable socket creation (from pytest-socket) """
89-
def __new__(cls, *args, **kwargs):
90-
try:
91-
is_unix_socket = args[0] == socket.AF_UNIX
92-
except AttributeError:
93-
# AF_UNIX not supported on Windows https://bugs.python.org/issue33408
94-
is_unix_socket = False
95-
96-
if is_unix_socket and allow_unix_socket:
97-
return super().__new__(cls, *args, **kwargs)
76+
def __new__(cls, family=-1, type=-1, proto=-1, fileno=None):
77+
if _is_unix_socket(family) and allow_unix_socket:
78+
return super().__new__(cls, family, type, proto, fileno)
9879

9980
raise SocketBlockedError()
10081

@@ -116,20 +97,54 @@ def pytest_configure(config):
11697
config.addinivalue_line("markers", "enable_socket(): Enable socket connections for a specific test")
11798
config.addinivalue_line("markers", "allow_hosts([hosts]): Restrict socket connection to defined list of hosts")
11899

100+
# Store the global configs in the `pytest.Config` object.
101+
config.__socket_disabled = config.getoption('--disable-socket')
102+
config.__socket_allow_unix_socket = config.getoption('--allow-unix-socket')
103+
config.__socket_allow_hosts = config.getoption('--allow-hosts')
104+
105+
106+
def pytest_runtest_setup(item) -> None:
107+
"""During each test item's setup phase,
108+
choose the behavior based on the configurations supplied.
119109
120-
def pytest_runtest_setup(item):
110+
This is the bulk of the logic for the plugin.
111+
As the logic can be extensive, this method is allowed complexity.
112+
It may be refactored in the future to be more readable.
113+
"""
114+
115+
# If test has the `enable_socket` marker, we accept this as most explicit.
116+
if 'socket_enabled' in item.fixturenames or item.get_closest_marker('enable_socket'):
117+
enable_socket()
118+
return
119+
120+
# If the test has the `disable_socket` marker, it's explicitly disabled.
121+
if 'socket_disabled' in item.fixturenames or item.get_closest_marker('disable_socket'):
122+
disable_socket(item.config.__socket_allow_unix_socket)
123+
return
124+
125+
# Resolve `allow_hosts` behaviors.
126+
hosts = _resolve_allow_hosts(item)
127+
128+
# Finally, check the global config and disable socket if needed.
129+
if item.config.__socket_disabled and not hosts:
130+
disable_socket(item.config.__socket_allow_unix_socket)
131+
132+
133+
def _resolve_allow_hosts(item):
134+
"""Resolve `allow_hosts` behaviors."""
121135
mark_restrictions = item.get_closest_marker('allow_hosts')
122-
cli_restrictions = item.config.getoption('--allow-hosts')
136+
cli_restrictions = item.config.__socket_allow_hosts
123137
hosts = None
124138
if mark_restrictions:
125139
hosts = mark_restrictions.args[0]
126140
elif cli_restrictions:
127141
hosts = cli_restrictions
128-
socket_allow_hosts(hosts)
142+
socket_allow_hosts(hosts, allow_unix_socket=item.config.__socket_allow_unix_socket)
143+
return hosts
129144

130145

131146
def pytest_runtest_teardown():
132-
remove_host_restrictions()
147+
_remove_restrictions()
133148

134149

135150
def host_from_address(address):
@@ -145,7 +160,7 @@ def host_from_connect_args(args):
145160
return host_from_address(address)
146161

147162

148-
def socket_allow_hosts(allowed=None):
163+
def socket_allow_hosts(allowed=None, allow_unix_socket=False):
149164
""" disable socket.socket.connect() to disable the Internet. useful in testing.
150165
"""
151166
if isinstance(allowed, str):
@@ -155,14 +170,16 @@ def socket_allow_hosts(allowed=None):
155170

156171
def guarded_connect(inst, *args):
157172
host = host_from_connect_args(args)
158-
if host and host in allowed:
173+
if host in allowed or (_is_unix_socket(inst.family) and allow_unix_socket):
159174
return _true_connect(inst, *args)
175+
160176
raise SocketConnectBlockedError(allowed, host)
161177

162178
socket.socket.connect = guarded_connect
163179

164180

165-
def remove_host_restrictions():
166-
""" restore socket.socket.connect() to allow access to the Internet. useful in testing.
181+
def _remove_restrictions():
182+
""" restore socket.socket.* to allow access to the Internet. useful in testing.
167183
"""
184+
socket.socket = _true_socket
168185
socket.socket.connect = _true_connect

tests/conftest.py

Lines changed: 13 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1 +1,14 @@
1+
import pytest
2+
from pytest_socket import enable_socket
3+
14
pytest_plugins = 'pytester'
5+
6+
7+
@pytest.fixture(autouse=True)
8+
def reenable_socket():
9+
"""
10+
The tests can leave the socket disabled in the global scope.
11+
Fix by automatically re-enabling it after each test.
12+
"""
13+
yield
14+
enable_socket()

0 commit comments

Comments
 (0)