Skip to content

Commit 8ca0421

Browse files
authored
Merge branch 'develop' into maintenance/reuse-scm-tuple
2 parents b928c91 + 5de3458 commit 8ca0421

15 files changed

+144
-67
lines changed

.flake8

+2
Original file line numberDiff line numberDiff line change
@@ -226,6 +226,8 @@ pytest-parametrize-names-type = tuple
226226
# PT007:
227227
pytest-parametrize-values-type = tuple
228228
pytest-parametrize-values-row-type = tuple
229+
# PT023:
230+
pytest-mark-no-parentheses = true
229231

230232
# flake8-rst-docstrings
231233
rst-roles =

.git_archival.txt

+1
Original file line numberDiff line numberDiff line change
@@ -0,0 +1 @@
1+
ref-names: $Format:%D$

.gitattributes

+5
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,5 @@
1+
# Force LF line endings for text files
2+
* text=auto eol=lf
3+
4+
# Needed for setuptools-scm-git-archive
5+
.git_archival.txt export-subst

.github/workflows/test-library.yml

-44
Original file line numberDiff line numberDiff line change
@@ -210,49 +210,6 @@ jobs:
210210
|| steps.scm-version.outputs.dist-version
211211
}}-py3-none-any.whl')
212212
213-
integration:
214-
runs-on: ${{ matrix.os }}-latest
215-
name: >-
216-
e2e: 🐍${{ matrix.python }} @ ${{ matrix.os }}
217-
needs:
218-
- build
219-
- pre-setup # transitive, for accessing settings
220-
strategy:
221-
matrix:
222-
os: [Ubuntu, macOS]
223-
python: ['3.6', '3.7', '3.8', '3.9', '3.10']
224-
max-parallel: 4
225-
fail-fast: false
226-
steps:
227-
- uses: actions/checkout@v2
228-
with:
229-
ref: ${{ github.event.inputs.release-commitish }}
230-
- name: Make the env clean of non-test files
231-
run: |
232-
shopt -s extglob
233-
mv -v tests/integration/main.sh integration-test.sh
234-
rm -rf !integration-test.sh
235-
shell: bash
236-
- name: Setup Python
237-
uses: actions/setup-python@v2
238-
with:
239-
python-version: ${{ matrix.python }}
240-
- name: Download all the dists
241-
uses: actions/download-artifact@v2
242-
with:
243-
name: python-package-distributions
244-
path: dist/
245-
- name: Integration testing
246-
run: |
247-
pip install 'dist/${{ needs.pre-setup.outputs.wheel-artifact-name }}'
248-
proxy \
249-
--hostname 127.0.0.1 \
250-
--enable-web-server \
251-
--pid-file proxy.pid \
252-
--log-file proxy.log \
253-
&
254-
./integration-test.sh
255-
256213
build:
257214
name: build dists ${{ needs.pre-setup.outputs.git-tag }}
258215
needs:
@@ -577,7 +534,6 @@ jobs:
577534

578535
check: # This job does nothing and is only used for the branch protection
579536
needs:
580-
- integration
581537
- lint
582538
- test
583539

README.md

+31
Original file line numberDiff line numberDiff line change
@@ -1722,6 +1722,37 @@ OR, simply pass fully-qualified path as parameter, e.g.
17221722

17231723
`proxy --plugins /path/to/my/app/my_app.proxyPlugin`
17241724

1725+
Here is a quick working example:
1726+
1727+
- Contents of `/tmp/plug` folder
1728+
1729+
```console
1730+
╰─ ls -1 /tmp/plug ─╯
1731+
my_plugin.py
1732+
```
1733+
1734+
- Custom `MyPlugin` class
1735+
1736+
```console
1737+
╰─ cat /tmp/plug/my_plugin.py ─╯
1738+
from proxy.http.proxy import HttpProxyBasePlugin
1739+
1740+
1741+
class MyPlugin(HttpProxyBasePlugin):
1742+
pass
1743+
```
1744+
1745+
This is an empty plugin for demonstrating external plugin usage. You must implement necessary methods to make your plugins work for real traffic
1746+
1747+
- Start `proxy.py` with `MyPlugin`
1748+
1749+
```console
1750+
╰─ PYTHONPATH=/tmp/plug proxy --plugin my_plugin.MyPlugin ─╯
1751+
...[redacted]... - Loaded plugin proxy.http.proxy.HttpProxyPlugin
1752+
...[redacted]... - Loaded plugin my_plugin.MyPlugin
1753+
...[redacted]... - Listening on ::1:8899
1754+
```
1755+
17251756
## Unable to connect with proxy.py from remote host
17261757

17271758
Make sure `proxy.py` is listening on correct network interface.

proxy/common/_compat.py

+7
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,7 @@
1+
"""Compatibility code for using Proxy.py across various versions of Python."""
2+
3+
import platform
4+
5+
6+
SYS_PLATFORM = platform.system()
7+
IS_WINDOWS = SYS_PLATFORM == 'Windows'

proxy/common/constants.py

+2-3
Original file line numberDiff line numberDiff line change
@@ -18,15 +18,14 @@
1818

1919
from typing import Any, List
2020

21+
from ._compat import IS_WINDOWS # noqa: WPS436
2122
from .version import __version__
2223

2324

2425
def _env_threadless_compliant() -> bool:
2526
"""Returns true for Python 3.8+ across all platforms
2627
except Windows."""
27-
if os.name == 'nt':
28-
return False
29-
return sys.version_info >= (3, 8)
28+
return not IS_WINDOWS and sys.version_info >= (3, 8)
3029

3130

3231
PROXY_PY_START_TIME = time.time()

proxy/common/flag.py

+2-1
Original file line numberDiff line numberDiff line change
@@ -19,6 +19,7 @@
1919

2020
from typing import Optional, List, Any, cast
2121

22+
from ._compat import IS_WINDOWS # noqa: WPS436
2223
from .plugins import Plugins
2324
from .types import IpAddress
2425
from .utils import bytes_, is_py2, set_open_file_limit
@@ -255,7 +256,7 @@ def initialize(
255256
)
256257
# AF_UNIX is not available on Windows
257258
# See https://bugs.python.org/issue33408
258-
if os.name != 'nt':
259+
if not IS_WINDOWS:
259260
args.family = socket.AF_UNIX if args.unix_socket_path else (
260261
socket.AF_INET6 if args.hostname.version == 6 else socket.AF_INET
261262
)

proxy/common/utils.py

+14-12
Original file line numberDiff line numberDiff line change
@@ -8,7 +8,6 @@
88
:copyright: (c) 2013-present by Abhinav Singh and contributors.
99
:license: BSD, see LICENSE for more details.
1010
"""
11-
import os
1211
import sys
1312
import ssl
1413
import socket
@@ -20,9 +19,10 @@
2019
from types import TracebackType
2120
from typing import Optional, Dict, Any, List, Tuple, Type, Callable
2221

22+
from ._compat import IS_WINDOWS # noqa: WPS436
2323
from .constants import HTTP_1_1, COLON, WHITESPACE, CRLF, DEFAULT_TIMEOUT, DEFAULT_THREADLESS
2424

25-
if os.name != 'nt':
25+
if not IS_WINDOWS:
2626
import resource
2727

2828
logger = logging.getLogger(__name__)
@@ -272,14 +272,16 @@ def get_available_port() -> int:
272272

273273
def set_open_file_limit(soft_limit: int) -> None:
274274
"""Configure open file description soft limit on supported OS."""
275-
if os.name != 'nt': # resource module not available on Windows OS
276-
curr_soft_limit, curr_hard_limit = resource.getrlimit(
277-
resource.RLIMIT_NOFILE,
275+
if IS_WINDOWS: # resource module not available on Windows OS
276+
return
277+
278+
curr_soft_limit, curr_hard_limit = resource.getrlimit(
279+
resource.RLIMIT_NOFILE,
280+
)
281+
if curr_soft_limit < soft_limit < curr_hard_limit:
282+
resource.setrlimit(
283+
resource.RLIMIT_NOFILE, (soft_limit, curr_hard_limit),
284+
)
285+
logger.debug(
286+
'Open file soft limit set to %d', soft_limit,
278287
)
279-
if curr_soft_limit < soft_limit < curr_hard_limit:
280-
resource.setrlimit(
281-
resource.RLIMIT_NOFILE, (soft_limit, curr_hard_limit),
282-
)
283-
logger.debug(
284-
'Open file soft limit set to %d', soft_limit,
285-
)

pytest.ini

+1
Original file line numberDiff line numberDiff line change
@@ -65,6 +65,7 @@ junit_suite_name = proxy_py_test_suite
6565

6666
# A mapping of markers to their descriptions allowed in strict mode:
6767
markers =
68+
smoke: Quick self-check smoke tests
6869

6970
minversion = 6.2.0
7071

tests/core/test_listener.py

+12-1
Original file line numberDiff line numberDiff line change
@@ -15,7 +15,10 @@
1515

1616
from unittest import mock
1717

18+
import pytest
19+
1820
from proxy.core.acceptor import Listener
21+
from proxy.common._compat import IS_WINDOWS # noqa: WPS436
1922
from proxy.common.flag import FlagParser
2023

2124

@@ -43,7 +46,15 @@ def test_setup_and_teardown(self, mock_socket: mock.Mock) -> None:
4346
listener.shutdown()
4447
sock.close.assert_called_once()
4548

46-
@unittest.skipIf(os.name == 'nt', 'AF_UNIX not available on windows')
49+
# FIXME: Ignore is necessary for as long as pytest hasn't figured out
50+
# FIXME: typing for their fixtures.
51+
# Refs:
52+
# * https://github.com/pytest-dev/pytest/issues/7469#issuecomment-918345196
53+
# * https://github.com/pytest-dev/pytest/issues/3342
54+
@pytest.mark.skipif(
55+
IS_WINDOWS,
56+
reason='AF_UNIX not available on Windows',
57+
) # type: ignore[misc]
4758
@mock.patch('os.remove')
4859
@mock.patch('socket.socket')
4960
def test_unix_path_listener(self, mock_socket: mock.Mock, mock_remove: mock.Mock) -> None:

tests/integration/test_integration.py

+52
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,52 @@
1+
"""Test the simplest proxy use scenario for smoke."""
2+
from pathlib import Path
3+
from subprocess import check_output, Popen
4+
from typing import Generator
5+
6+
import pytest
7+
8+
from proxy.common._compat import IS_WINDOWS # noqa: WPS436
9+
10+
11+
# FIXME: Ignore is necessary for as long as pytest hasn't figured out
12+
# FIXME: typing for their fixtures.
13+
# Refs:
14+
# * https://github.com/pytest-dev/pytest/issues/7469#issuecomment-918345196
15+
# * https://github.com/pytest-dev/pytest/issues/3342
16+
@pytest.fixture # type: ignore[misc]
17+
def _proxy_py_instance() -> Generator[None, None, None]:
18+
"""Instantiate proxy.py in a subprocess for testing.
19+
20+
After the testing is over, tear it down.
21+
"""
22+
proxy_cmd = (
23+
'proxy',
24+
'--hostname', '127.0.0.1',
25+
'--enable-web-server',
26+
)
27+
proxy_proc = Popen(proxy_cmd)
28+
try:
29+
yield
30+
finally:
31+
proxy_proc.terminate()
32+
proxy_proc.wait(1)
33+
34+
35+
# FIXME: Ignore is necessary for as long as pytest hasn't figured out
36+
# FIXME: typing for their fixtures.
37+
# Refs:
38+
# * https://github.com/pytest-dev/pytest/issues/7469#issuecomment-918345196
39+
# * https://github.com/pytest-dev/pytest/issues/3342
40+
@pytest.mark.smoke # type: ignore[misc]
41+
@pytest.mark.usefixtures('_proxy_py_instance') # type: ignore[misc]
42+
@pytest.mark.xfail(
43+
IS_WINDOWS,
44+
reason='OSError: [WinError 193] %1 is not a valid Win32 application',
45+
raises=OSError,
46+
) # type: ignore[misc]
47+
def test_curl() -> None:
48+
"""An acceptance test with using ``curl`` through proxy.py."""
49+
this_test_module = Path(__file__)
50+
shell_script_test = this_test_module.with_suffix('.sh')
51+
52+
check_output(str(shell_script_test))
File renamed without changes.

tests/test_set_open_file_limit.py

+8-3
Original file line numberDiff line numberDiff line change
@@ -8,17 +8,22 @@
88
:copyright: (c) 2013-present by Abhinav Singh and contributors.
99
:license: BSD, see LICENSE for more details.
1010
"""
11-
import os
1211
import unittest
1312
from unittest import mock
1413

14+
import pytest
15+
16+
from proxy.common._compat import IS_WINDOWS # noqa: WPS436
1517
from proxy.common.utils import set_open_file_limit
1618

17-
if os.name != 'nt':
19+
if not IS_WINDOWS:
1820
import resource
1921

2022

21-
@unittest.skipIf(os.name == 'nt', 'Open file limit tests disabled for Windows')
23+
@pytest.mark.skipif(
24+
IS_WINDOWS,
25+
reason='Open file limit tests disabled for Windows',
26+
)
2227
class TestSetOpenFileLimit(unittest.TestCase):
2328

2429
@mock.patch('resource.getrlimit', return_value=(128, 1024))

tests/testing/test_embed.py

+7-3
Original file line numberDiff line numberDiff line change
@@ -8,20 +8,24 @@
88
:copyright: (c) 2013-present by Abhinav Singh and contributors.
99
:license: BSD, see LICENSE for more details.
1010
"""
11-
import os
12-
import unittest
1311
import http.client
1412
import urllib.request
1513
import urllib.error
1614

15+
import pytest
16+
1717
from proxy import TestCase
18+
from proxy.common._compat import IS_WINDOWS # noqa: WPS436
1819
from proxy.common.constants import DEFAULT_CLIENT_RECVBUF_SIZE, PROXY_AGENT_HEADER_VALUE
1920
from proxy.common.utils import socket_connection, build_http_request
2021
from proxy.http import httpMethods
2122
from proxy.http.server import HttpWebServerPlugin
2223

2324

24-
@unittest.skipIf(os.name == 'nt', 'Disabled for Windows due to weird permission issues.')
25+
@pytest.mark.skipif(
26+
IS_WINDOWS,
27+
reason='Disabled for Windows due to weird permission issues.',
28+
)
2529
class TestProxyPyEmbedded(TestCase):
2630
"""This test case is a demonstration of proxy.TestCase and also serves as
2731
integration test suite for proxy.py."""

0 commit comments

Comments
 (0)