3proxy/tests
2026-08-25 19:07:20 +03:00
..
cases fix port range test 2026-08-25 18:37:40 +03:00
.gitignore Add tests 2026-08-25 18:03:50 +03:00
harness.py Fix: insufficient default stack for wolfSSL with glibc 2026-08-25 19:07:20 +03:00
README.md Add tests 2026-08-25 18:03:50 +03:00
run.py Add tests 2026-08-25 18:03:50 +03:00

Regression tests

python3 tests/run.py                     # every case
python3 tests/run.py httpsrv             # cases whose name matches
python3 tests/run.py --bin build/bin/3proxy
python3 tests/run.py -v                  # print every check
python3 tests/run.py --keep              # keep the configurations and logs

Python 3.6 or later and a built 3proxy are the only requirements: the suite is standard library throughout, so it runs wherever 3proxy builds. With no --bin it looks in bin/, then build/bin/, then the per-configuration directories a multi-configuration CMake generator uses.

The proxy under test is also the origin server the tests talk to: the http command's echo operation reports back how a request arrived - method, path, query, host, and the source port it came from - and data generates a body of a requested size, framing, status and pace. So a case can state what a proxy should do to a request and then read off what actually reached the other side.

Adding a case

A case is a module under cases/ exporting run(t). It writes the configurations it needs, starts them, and says what it expects:

def run(t):
    srv = t.free_port()
    t.start("my_case", f"""
        log
        auth iponly
        allow *
        http * /echo echo
        httpsrv -p{srv}
    """, ports=[srv])

    r = t.http(f"http://127.0.0.1:{srv}/echo")
    t.eq(200, r.status, "the server answers")
    t.contains(r, "method=GET", "the method is reported")

Servers are stopped for you when the case ends, whether or not it passed.

t offers http() (direct, through an HTTP proxy, or over a CONNECT tunnel), socks_http() and socks_connect() for SOCKS4 and SOCKS5, socks_udp_associate(), raw() for bytes a real client would never send, and run_config() for configurations that are meant to be rejected. Assertions are eq, ne, contains, not_contains, in_range, not_in_range, plus ok, fail and skip. harness.field() and int_field() pull a single line out of an echo reply.

Note that access rules accumulate until flush, so a service section that means to stand on its own should start with one - otherwise an earlier allow * matches first and the rule under test is never reached.