Skip to content

Commit

Permalink
Merge branch 'main' into miketheman/test-py311
Browse files Browse the repository at this point in the history
  • Loading branch information
miketheman committed Mar 3, 2023
2 parents 967e8d6 + d9ded31 commit d92972f
Show file tree
Hide file tree
Showing 11 changed files with 117 additions and 16 deletions.
2 changes: 1 addition & 1 deletion .github/workflows/codeql-analysis.yml
Original file line number Diff line number Diff line change
Expand Up @@ -25,7 +25,7 @@ jobs:

steps:
- name: Checkout repository
uses: actions/checkout@v3.2.0
uses: actions/checkout@v3.3.0
with:
# We must fetch at least the immediate parents so that if this is
# a pull request then we can checkout the head.
Expand Down
4 changes: 2 additions & 2 deletions .github/workflows/python.yml
Original file line number Diff line number Diff line change
Expand Up @@ -22,7 +22,7 @@ jobs:
python-version: ["3.7", "3.8", "3.9", "3.10", "3.11"]

steps:
- uses: actions/checkout@v3.2.0
- uses: actions/checkout@v3.3.0
- name: Set up Python ${{ matrix.python-version }}
uses: actions/setup-python@v4
with:
Expand All @@ -34,4 +34,4 @@ jobs:
- name: Prepare project for development
run: poetry install
- name: Test with pytest
run: poetry run coverage run -m pytest ; coverage report --show-missing
run: poetry run coverage run -m pytest ; poetry run coverage report --show-missing
2 changes: 1 addition & 1 deletion .github/workflows/stale-issues.yml
Original file line number Diff line number Diff line change
Expand Up @@ -10,7 +10,7 @@ jobs:
issues: write
pull-requests: write
steps:
- uses: actions/stale@v6
- uses: actions/stale@v7
with:
days-before-issue-stale: 90
days-before-issue-close: 30
Expand Down
8 changes: 4 additions & 4 deletions .pre-commit-config.yaml
Original file line number Diff line number Diff line change
Expand Up @@ -7,7 +7,7 @@ repos:
- id: check-yaml
- id: check-added-large-files
- repo: https://github.com/editorconfig-checker/editorconfig-checker.python
rev: '2.6.2'
rev: '2.7.1'
hooks:
- id: editorconfig-checker
alias: ec
Expand All @@ -16,12 +16,12 @@ repos:
hooks:
- id: flake8
- repo: https://github.com/pycqa/isort
rev: 5.10.1
rev: 5.12.0
hooks:
- id: isort
name: isort (python)
- repo: https://github.com/psf/black
rev: 22.12.0
rev: 23.1.0
hooks:
- id: black
- repo: https://github.com/asottile/pyupgrade
Expand All @@ -30,6 +30,6 @@ repos:
- id: pyupgrade
args: [--py37-plus]
- repo: https://github.com/jendrikseipp/vulture
rev: 'v2.6'
rev: 'v2.7'
hooks:
- id: vulture
14 changes: 13 additions & 1 deletion CHANGELOG.md
Original file line number Diff line number Diff line change
Expand Up @@ -4,7 +4,18 @@ This document records all notable changes to
[pytest-socket](https://pypi.python.org/pypi/pytest-socket). This
project attempts to adhere to [Semantic Versioning](http://semver.org/).

## [0.5.1][] (2020-01-23)
## [0.6.0][] (2023-02-03)

Enhancements:

- Support hostname in `--allow-hosts` #189

Changes:

- Dependency updates
- Development updates

## [0.5.1][] (2022-01-23)

### Fixes

Expand Down Expand Up @@ -119,3 +130,4 @@ Maintenance release.
[0.4.1]: https://github.com/miketheman/pytest-socket/compare/0.4.0...0.4.1
[0.5.0]: https://github.com/miketheman/pytest-socket/compare/0.4.1...0.5.0
[0.5.1]: https://github.com/miketheman/pytest-socket/compare/0.5.0...0.5.1
[0.6.0]: https://github.com/miketheman/pytest-socket/compare/0.5.1...0.6.0
2 changes: 1 addition & 1 deletion Makefile
Original file line number Diff line number Diff line change
Expand Up @@ -21,7 +21,7 @@ endif
@touch $(INSTALL_STAMP)

test: $(INSTALL_STAMP)
@poetry run coverage run -m pytest $(PYTEST_FLAGS) ; coverage report --show-missing
@poetry run coverage run -m pytest $(PYTEST_FLAGS) ; poetry run coverage report --show-missing

dist: clean $(INSTALL_STAMP)
@poetry build
Expand Down
4 changes: 4 additions & 0 deletions README.md
Original file line number Diff line number Diff line change
Expand Up @@ -66,6 +66,9 @@ def pytest_runtest_setup():
disable_socket()
```

If you exceptionally want to enable socket for one particular execution
pass `--force-enable-socket`. It takes precedence over `--disable-socket`.

To enable Unix sockets during the test run (e.g. for async), add this option:

```ini
Expand Down Expand Up @@ -101,6 +104,7 @@ or for whole test run
addopts = --allow-hosts=127.0.0.1,127.0.1.1
```


### Frequently Asked Questions

Q: Why is network access disabled in some of my tests but not others?
Expand Down
4 changes: 2 additions & 2 deletions pyproject.toml
Original file line number Diff line number Diff line change
@@ -1,6 +1,6 @@
[tool.poetry]
name = "pytest-socket"
version = "0.5.1"
version = "0.6.0"
description = "Pytest Plugin to disable socket calls during tests"
authors = ["Mike Fiedler <miketheman@gmail.com>"]
license = "MIT"
Expand Down Expand Up @@ -41,7 +41,7 @@ pytest-httpbin = "^1.0"
pytest-randomly = "^3.5.0"
asynctest = "^0.13.0"
requests = "^2.26.0"
starlette = "^0.23.0"
starlette = "^0.25.0"
httpx = "^0.23.0"
# See https://github.com/postmanlabs/httpbin/pull/674
httpbin = { git = "https://github.com/maximino-dev/httpbin.git", rev = "651c03a73" }
Expand Down
57 changes: 53 additions & 4 deletions pytest_socket.py
Original file line number Diff line number Diff line change
@@ -1,3 +1,4 @@
import ipaddress
import socket

import pytest
Expand Down Expand Up @@ -29,6 +30,12 @@ def pytest_addoption(parser):
dest="disable_socket",
help="Disable socket.socket by default to block network calls.",
)
group.addoption(
"--force-enable-socket",
action="store_true",
dest="force_enable_socket",
help="Force enable socket.socket network calls (override --disable-socket).",
)
group.addoption(
"--allow-hosts",
dest="allow_hosts",
Expand Down Expand Up @@ -99,6 +106,7 @@ def pytest_configure(config):
)

# Store the global configs in the `pytest.Config` object.
config.__socket_force_enabled = config.getoption("--force-enable-socket")
config.__socket_disabled = config.getoption("--disable-socket")
config.__socket_allow_unix_socket = config.getoption("--allow-unix-socket")
config.__socket_allow_hosts = config.getoption("--allow-hosts")
Expand All @@ -118,9 +126,12 @@ def pytest_runtest_setup(item) -> None:
if not hasattr(item, "fixturenames"):
return

# If test has the `enable_socket` marker, we accept this as most explicit.
if "socket_enabled" in item.fixturenames or item.get_closest_marker(
"enable_socket"
# If test has the `enable_socket` marker, fixture or
# it's forced from the CLI, we accept this as most explicit.
if (
"socket_enabled" in item.fixturenames
or item.get_closest_marker("enable_socket")
or item.config.__socket_force_enabled
):
enable_socket()
return
Expand Down Expand Up @@ -170,16 +181,54 @@ def host_from_connect_args(args):
return host_from_address(address)


def is_ipaddress(address: str) -> bool:
"""
Determine if the address is a valid IPv4 or IPv6 address.
"""
try:
ipaddress.ip_address(address)
return True
except ValueError:
return False


def resolve_hostname(hostname):
try:
return socket.gethostbyname(hostname)
except socket.gaierror:
return None


def normalize_allowed_hosts(allowed_hosts):
"""Convert all items in `allowed_hosts` to an IP address."""
ip_hosts = []
for host in allowed_hosts:
host = host.strip()
if is_ipaddress(host):
ip_hosts.append(host)
else:
resolved = resolve_hostname(host)
if resolved:
ip_hosts.append(resolved)

return ip_hosts


def socket_allow_hosts(allowed=None, allow_unix_socket=False):
"""disable socket.socket.connect() to disable the Internet. useful in testing."""
if isinstance(allowed, str):
allowed = allowed.split(",")

if not isinstance(allowed, list):
return

allowed_hosts = normalize_allowed_hosts(allowed)

def guarded_connect(inst, *args):
host = host_from_connect_args(args)
if host in allowed or (_is_unix_socket(inst.family) and allow_unix_socket):
if host in allowed_hosts or (
_is_unix_socket(inst.family) and allow_unix_socket
):
return _true_connect(inst, *args)

raise SocketConnectBlockedError(allowed, host)
Expand Down
28 changes: 28 additions & 0 deletions tests/test_precedence.py
Original file line number Diff line number Diff line change
Expand Up @@ -54,6 +54,34 @@ def test_socket():
assert_socket_blocked(result)


def test_force_enable_socket_via_cli_flag(testdir):
testdir.makepyfile(
"""
import socket
import pytest
@pytest.mark.disable_socket
def test_socket():
socket.socket(socket.AF_INET, socket.SOCK_STREAM)
"""
)
result = testdir.runpytest("--force-enable-socket")
result.assert_outcomes(passed=1)


def test_force_enable_cli_flag_precedence(testdir):
testdir.makepyfile(
"""
import socket
def test_socket():
socket.socket(socket.AF_INET, socket.SOCK_STREAM)
"""
)
result = testdir.runpytest("--disable-socket", "--force-enable-socket")
result.assert_outcomes(passed=1)


def test_global_disable_via_config(testdir):
testdir.makepyfile(
"""
Expand Down
8 changes: 8 additions & 0 deletions tests/test_restrict_hosts.py
Original file line number Diff line number Diff line change
Expand Up @@ -106,6 +106,14 @@ def test_single_cli_arg_connect_enabled(assert_connect):
assert_connect(True, cli_arg=localhost)


def test_single_cli_arg_connect_enabled_hostname_resolved(assert_connect):
assert_connect(True, cli_arg="localhost")


def test_single_cli_arg_connect_enabled_hostname_unresolvable(assert_connect):
assert_connect(False, cli_arg="unresolvable")


def test_single_cli_arg_connect_unicode_enabled(assert_connect):
assert_connect(True, cli_arg=localhost, code_template=connect_unicode_code_template)

Expand Down

0 comments on commit d92972f

Please sign in to comment.