Poorconn: Simulating Poor Network Conditions

PyPI PyPI - Python Versions PyPI - Implementation Documentation PyPI - License Pipeline Status Coverage

Poorconn is a Python package that simulates poor network conditions. It is suitable for testing purposes, for both Python and non-Python projects.

It is capable of simulating the following poor network conditions:

Quickstart

Install this package via

$ pip install 'poorconn[full]'  # or "pip install poorconn" if you don't need pytest support

Command Line Usage

The following example starts a local HTTP server at port 8000 that hosts static files at the current working directory. It always closes connections upon accepting them:

$ python -m poorconn -H localhost -p 8000 close_upon_acceptance

In this command, python -m poorconn invokes Poorconn’s command line entrypoint, -H localhost specifies the hostname, -p 8000 specifies the port number, and close_upon_acceptance is a simulation command that simulates a specific poor network conditions, which in this case is closing connections upon accepting them.

After running the command above, connections from a client would establish but fail to communicate subsequently:

$ wget -t 1 http://127.0.0.1:8000
Connecting to 127.0.0.1:8000... connected.
HTTP request sent, awaiting response... No data received.
Giving up.

For another example, to start a local HTTP server that always throttle connections upon accepting them, simply replace close_upon_acceptance above with delay_before_sending_upon_acceptance --t=1 --length=1024:

$ python -m poorconn delay_before_sending_upon_acceptance --t=1 --length=1024

Here, -H localhost -p 8000 is omitted because it’s Poorconn’s default host and port settings. In this command, poorconn.delay_before_sending_upon_acceptance() delays roughly 1 seconds for every 1024 bytes sent. The connection is now throttled:

$ wget http://127.0.0.1:8000
Connecting to 127.0.0.1:8000... connected.
HTTP request sent, awaiting response... 200 OK
Length: 1609 (1.6K) [text/html]
Saving to: 'index.html'

index.html    1.57K   804 B/s    in 2.0s  <====== NOTE the time

'index.html' saved [1609/1609]

(Output above is abridged.)

Run python -m poorconn -h to view the help message and see the poorconn module API references for a list of simulation functions (which share the same names with simulation commands).

Docker Image

You can also run Poorconn as a command line tool using our Docker image by replacing python -m poorconn with docker run registry.gitlab.com/xuhdev/poorconn. For instance, the following achieves the same effect as the second command line example above:

$ docker run -p 8000:8000 registry.gitlab.com/xuhdev/poorconn delay_before_sending_upon_acceptance --t=1 --length=1024

Usage in Python

Running the following Python script achieves the same effects as the first command line example above:

from http.server import HTTPServer, SimpleHTTPRequestHandler
from poorconn import close_upon_acceptance, make_socket_patchable

# Start a local HTTP server that always closes connections upon established
with HTTPServer(("localhost", 8000), SimpleHTTPRequestHandler) as httpd:
    httpd.socket = make_socket_patchable(httpd.socket)
    close_upon_acceptance(httpd.socket)
    httpd.serve_forever()

The code snippet above is very similar to that runs a basic http server in Python, except that the socket object httpd.socket is patched by poorconn.close_upon_acceptance() before http server is running.

For the second command line example above, simply replace close_upon_acceptance(s) above with delay_before_sending_upon_acceptance(s, t=1, length=1024) and adjust imports.

Integration with Pytest

If you use pytest, you can also take advantage of poorconn fixtures in poorconn.pytest_plugin. The following example gets you started with testing against a slow HTTP server:

pytest_plugins = ('poorconn',)

from pathlib import Path
import time
import requests
import pytest

@pytest.mark.poorconn_http_server_config(t=2, length=1024)
def test_slow_http_server(poorconn_http_server, tmp_path):
    "Test GETing from a slow local http server that delays 2 seconds to send every 1024 bytes."
    (tmp_path / 'index.txt').write_bytes(b'h' * 1024)
    starting_time = time.time()
    # Replace the following line with the program you want to test
    content = requests.get(f'{poorconn_http_server.url}/index.txt').content
    ending_time = time.time()
    assert ending_time - starting_time > 2

Bug Reports and Feature Requests

Please open a ticket on the GitLab Issue Tracker.

Contributing

Contributions are welcome! To get started, check out Contributing Guide.

Indices and Tables