proxy.py/tests/test_acceptor.py

148 lines
4.8 KiB
Python
Raw Normal View History

Proxy.py Dashboard (#141) * Remove redundant variables * Initialize frontend dashboard app (written in typescript) * Add a WebsocketFrame.text method to quickly build a text frame raw packet, also close connection for static file serving, atleast Google Chrome seems to hang up instead of closing the connection * Add read_and_build_static_file_response method for reusability in plugins * teardown websocket connection when opcode CONNECTION_CLOSE is received * First draft of proxy.py dashboard * Remove uglify, obfuscator is superb enough * Correct generic V * First draft of dashboard * ProtocolConfig is now Flags * First big refactor toward no-single-file-module * Working tests * Update dashboard for refactored imports * Remove proxy.py as now we can just call python -m proxy -h * Fix setup.py for refactored code * Banner update * Lint check * Fix dashboard static serving and no UNDER_TEST constant necessary * Add support for plugin imports when specified in path/to/module.MyPlugin * Update README with instructions to run proxy.py after refactor * Move dashboard under /dashboard path * Rename to devtools.ts * remove unused * Update github workflow for new directory structure * Update test command too * Fix coverage generation * *.py is an invalid syntax on windows * No * on windows * Enable execution via github zip downloads * Github Zip downloads cannot be executed as Github puts project under a folder named after Github project, this breaks python interpreter expectation of finding a __main__.py in the root directory * Forget zip runs for now * Initialize ProxyDashboard on page load rather than within typescript i.e. on script load * Enforce eslint with standard style * Add .editorconfig to make editor compatible with various style requirements (Makefile, Typescript, Python) * Remove extra empty line * Add ability to pass headers with HttpRequestRejected exception, also remove proxy agent header for HttpRequestRejected * Add ability to pass headers with HttpRequestRejected exception, also remove proxy agent header for HttpRequestRejected * Fix tests * Move common code under common sub-module * Move flags under common module * Move acceptor under core * Move connection under core submodule * Move chunk_parser under http * Move http_parser as http/parser * Move http_methods as http/methods * Move http_proxy as http/proxy * Move web_server as http/server * Move status_codes as http/codes * move websocket as http/websocket * Move exception under http/exception, also move http/proxy exceptions under http/exceptions * move protocol_handler as http/handler * move devtools as http/devtools * Move version under common/version * Lifecycle if now core Event * autopep8 * Add core event queue * Register / unregister handler * Enable inspection support for frontend dashboard * Dont give an illusion of exception for HttpProtocolExceptions * Update readme for refactored codebase * DictQueueType everywhere * Move all websocket API related code under WebsocketApi class * Inspection enabled on tab switch. 1. Additionally now acceptors are assigned an int id. 2. Fix tests to match change in constructor. * Corresponding ends of the work queues can be closed immediately. Since work queues between AcceptorPool and Acceptor process is used only once, close corresponding ends asap instead of at shutdown. * No need of a manager for shared multiprocess Lock. This unnecessarily creates additional manager process. * Move threadless into its own module * Merge acceptor and acceptor_pool tests * Defer os.close * Change content display with tab clicks. Also ensure relay manager shutdown. * Remove --cov flags * Use right type for SyncManager * Ensure coverage again * Print help to discover flags, --cov certainly not available on Travis for some reason * Add pytest-cov to requirements-testing * Re-add windows on .travis also add changelog to readme * Use 3.7 and no pip upgrade since it fails on travis windows * Attempt to fix pip install on windows * Disable windows on travis, it fails and uses 3.8. Try reporting coverage from github actions * Move away from coveralls, use codecov * Codecov app installation either didnt work or token still needs to be passed * Remove travis CI * Use https://github.com/codecov/codecov-action for coverage uploads * Remove run codecov * Ha, codecov action only works on linux, what a mess * Add cookie.js though unable to use it with es5/es6 modules yet * Enable testing for python 3.8 also Build dashboard during testing * No python 3.8 on github actions yet * Autopep8 * Add separate workflows for library (python) and dashboard (node) app * Type jobs not job * Add checkout * Fix parsing node version * Fix dashboard build on windows * Show codecov instead of coveralls
2019-10-28 21:57:33 +00:00
# -*- coding: utf-8 -*-
"""
proxy.py
~~~~~~~~
Fast, Lightweight, Programmable Proxy Server in a single Python file.
:copyright: (c) 2013-present by Abhinav Singh and contributors.
:license: BSD, see LICENSE for more details.
"""
import unittest
import socket
import selectors
import multiprocessing
from unittest import mock
from proxy.common.flags import Flags
from proxy.core.acceptor import Acceptor, AcceptorPool
class TestAcceptor(unittest.TestCase):
def setUp(self) -> None:
self.acceptor_id = 1
self.mock_protocol_handler = mock.MagicMock()
self.pipe = multiprocessing.Pipe()
self.flags = Flags()
self.acceptor = Acceptor(
idd=self.acceptor_id,
work_queue=self.pipe[1],
flags=self.flags,
work_klass=self.mock_protocol_handler)
@mock.patch('selectors.DefaultSelector')
@mock.patch('socket.fromfd')
@mock.patch('proxy.core.acceptor.recv_handle')
def test_continues_when_no_events(
self,
mock_recv_handle: mock.Mock,
mock_fromfd: mock.Mock,
mock_selector: mock.Mock) -> None:
fileno = 10
conn = mock.MagicMock()
addr = mock.MagicMock()
sock = mock_fromfd.return_value
mock_fromfd.return_value.accept.return_value = (conn, addr)
mock_recv_handle.return_value = fileno
selector = mock_selector.return_value
selector.select.side_effect = [[], KeyboardInterrupt()]
self.acceptor.run()
sock.accept.assert_not_called()
self.mock_protocol_handler.assert_not_called()
@mock.patch('threading.Thread')
@mock.patch('selectors.DefaultSelector')
@mock.patch('socket.fromfd')
@mock.patch('proxy.core.acceptor.recv_handle')
def test_accepts_client_from_server_socket(
self,
mock_recv_handle: mock.Mock,
mock_fromfd: mock.Mock,
mock_selector: mock.Mock,
mock_thread: mock.Mock) -> None:
fileno = 10
conn = mock.MagicMock()
addr = mock.MagicMock()
sock = mock_fromfd.return_value
mock_fromfd.return_value.accept.return_value = (conn, addr)
mock_recv_handle.return_value = fileno
mock_thread.return_value.start.side_effect = KeyboardInterrupt()
selector = mock_selector.return_value
selector.select.return_value = [(None, None)]
self.acceptor.run()
selector.register.assert_called_with(sock, selectors.EVENT_READ)
selector.unregister.assert_called_with(sock)
mock_recv_handle.assert_called_with(self.pipe[1])
mock_fromfd.assert_called_with(
fileno,
family=socket.AF_INET6,
type=socket.SOCK_STREAM
)
self.mock_protocol_handler.assert_called_with(
fileno=conn.fileno(),
addr=addr,
flags=self.flags,
event_queue=None,
)
mock_thread.assert_called_with(
target=self.mock_protocol_handler.return_value.run)
mock_thread.return_value.start.assert_called()
sock.close.assert_called()
class TestAcceptorPool(unittest.TestCase):
@mock.patch('proxy.core.acceptor.send_handle')
@mock.patch('multiprocessing.Pipe')
@mock.patch('socket.socket')
@mock.patch('proxy.core.acceptor.Acceptor')
def test_setup_and_shutdown(
self,
mock_worker: mock.Mock,
mock_socket: mock.Mock,
mock_pipe: mock.Mock,
_mock_send_handle: mock.Mock) -> None:
mock_worker1 = mock.MagicMock()
mock_worker2 = mock.MagicMock()
mock_worker.side_effect = [mock_worker1, mock_worker2]
num_workers = 2
sock = mock_socket.return_value
work_klass = mock.MagicMock()
flags = Flags(num_workers=2)
acceptor = AcceptorPool(flags=flags, work_klass=work_klass)
acceptor.setup()
work_klass.assert_not_called()
mock_socket.assert_called_with(
socket.AF_INET6 if acceptor.flags.hostname.version == 6 else socket.AF_INET,
socket.SOCK_STREAM
)
sock.setsockopt.assert_called_with(
socket.SOL_SOCKET, socket.SO_REUSEADDR, 1)
sock.bind.assert_called_with(
(str(acceptor.flags.hostname), acceptor.flags.port))
sock.listen.assert_called_with(acceptor.flags.backlog)
sock.setblocking.assert_called_with(False)
self.assertTrue(mock_pipe.call_count, num_workers)
self.assertTrue(mock_worker.call_count, num_workers)
mock_worker1.start.assert_called()
mock_worker1.join.assert_not_called()
mock_worker2.start.assert_called()
mock_worker2.join.assert_not_called()
sock.close.assert_called()
acceptor.shutdown()
mock_worker1.join.assert_called()
mock_worker2.join.assert_called()