mirror of
https://github.com/Av0idD4rk/firegex.git
synced 2026-10-23 14:31:00 +00:00
Minor fixes for release
This commit is contained in:
parent
a6643490c1
commit
21e3027f0b
54
.github/workflows/release.yml
vendored
54
.github/workflows/release.yml
vendored
@ -248,6 +248,55 @@ jobs:
|
|||||||
env:
|
env:
|
||||||
GITHUB_TOKEN: ${{ secrets.GITHUB_TOKEN }}
|
GITHUB_TOKEN: ${{ secrets.GITHUB_TOKEN }}
|
||||||
|
|
||||||
|
# The README's picture of the interface, retaken for every release so it never shows a
|
||||||
|
# version that no longer exists. From the demo build served locally rather than from the
|
||||||
|
# published demo: that deploys on its own schedule, and could still be the old one.
|
||||||
|
update_readme_screenshot:
|
||||||
|
needs: [run_tests, engine_tests]
|
||||||
|
runs-on: ubuntu-latest
|
||||||
|
permissions:
|
||||||
|
contents: write
|
||||||
|
|
||||||
|
steps:
|
||||||
|
- name: Checkout repository
|
||||||
|
uses: actions/checkout@v4
|
||||||
|
with:
|
||||||
|
ref: main
|
||||||
|
|
||||||
|
- name: Set up bun
|
||||||
|
uses: oven-sh/setup-bun@v2
|
||||||
|
with:
|
||||||
|
bun-version: latest
|
||||||
|
|
||||||
|
- name: Set up Python
|
||||||
|
uses: actions/setup-python@v5
|
||||||
|
with:
|
||||||
|
python-version: '3.12'
|
||||||
|
|
||||||
|
- name: Install Playwright
|
||||||
|
run: |
|
||||||
|
python -m pip install --upgrade pip
|
||||||
|
pip install playwright
|
||||||
|
playwright install --with-deps chromium
|
||||||
|
|
||||||
|
- name: Build the demo
|
||||||
|
working-directory: frontend
|
||||||
|
run: bun install --frozen-lockfile && VITE_VERSION="${{ github.ref_name }}" bun run build:demo
|
||||||
|
|
||||||
|
- name: Screenshot it
|
||||||
|
working-directory: frontend
|
||||||
|
run: |
|
||||||
|
bunx vite preview --port 4173 --strictPort &
|
||||||
|
timeout 60 bash -c 'until curl -sf http://localhost:4173/ > /dev/null; do sleep 0.5; done'
|
||||||
|
python3 ../docs/screenshot_demo.py http://localhost:4173/
|
||||||
|
|
||||||
|
- name: Commit the new screenshot
|
||||||
|
uses: stefanzweifel/git-auto-commit-action@v5
|
||||||
|
with:
|
||||||
|
commit_message: "Update README screenshot for ${{ github.ref_name }}"
|
||||||
|
file_pattern: 'docs/Firegex_Screenshot.png'
|
||||||
|
branch: main
|
||||||
|
|
||||||
# `firegex` carries a C extension — the llhttp binding in `fgex-lib/llhttp/` — so it
|
# `firegex` carries a C extension — the llhttp binding in `fgex-lib/llhttp/` — so it
|
||||||
# is no longer one universal wheel. A wheel is built per platform and per interpreter
|
# is no longer one universal wheel. A wheel is built per platform and per interpreter
|
||||||
# here, and the sdist goes up beside them: where no wheel matches, pip compiles from
|
# here, and the sdist goes up beside them: where no wheel matches, pip compiles from
|
||||||
@ -351,8 +400,11 @@ jobs:
|
|||||||
user: __token__
|
user: __token__
|
||||||
password: ${{ secrets.PYPI_API_TOKEN }}
|
password: ${{ secrets.PYPI_API_TOKEN }}
|
||||||
|
|
||||||
|
# After `firegex`, never beside it: this package is nothing but `firegex==<tag>`, so
|
||||||
|
# published while a wheel job failed it is an alias for a version that does not exist,
|
||||||
|
# and `pip install fgex` fails for everybody until somebody notices.
|
||||||
pypi_publish_fgex_alias:
|
pypi_publish_fgex_alias:
|
||||||
needs: [run_tests, engine_tests]
|
needs: pypi_publish_firegex
|
||||||
runs-on: ubuntu-latest
|
runs-on: ubuntu-latest
|
||||||
permissions:
|
permissions:
|
||||||
contents: read
|
contents: read
|
||||||
|
|||||||
@ -66,7 +66,7 @@ there — a file only processed for *site* directories, so putting that path in
|
|||||||
imports pytest and still leaves `pyworker.py --check` unable to find the filter library.
|
imports pytest and still leaves `pyworker.py --check` unable to find the filter library.
|
||||||
The script installs into the environment it is running in, which resolves all of it. Two
|
The script installs into the environment it is running in, which resolves all of it. Two
|
||||||
things that trip a root run were found by trying it, and both are fixed rather than worked
|
things that trip a root run were found by trying it, and both are fixed rather than worked
|
||||||
around: `tests/requirements.txt` was missing `fastapi` (see the file for why one unit test
|
around: the `test` group in `tests/pyproject.toml` was missing `fastapi` (see the file for why one unit test
|
||||||
needs the backend's own dependency, and why its absence stops the *whole* run rather than
|
needs the backend's own dependency, and why its absence stops the *whole* run rather than
|
||||||
one file), and `./run_tests.sh unit` — advertised in the script's own help — took `unit` as
|
one file), and `./run_tests.sh unit` — advertised in the script's own help — took `unit` as
|
||||||
a **password** and ran everything against an instance it could not authenticate to.
|
a **password** and ran everything against an instance it could not authenticate to.
|
||||||
@ -108,7 +108,7 @@ Mount `backend/`, not `backend/proxysrc/`: `tests/pyworker.rs` launches `../modu
|
|||||||
|
|
||||||
The suite runs against an instance with authentication turned off as well: `login()` reads a 403 there as "there is nothing to log in to" rather than as a failure, since every request is already allowed. `integration/test_auth.py` is the exception and skips itself — it is about passwords, and those endpoints answer 403 in that mode by design. Note `BearerSession.post` copies the header dict rather than writing into it: the form `Content-Type` belongs to the login request alone, and it used to stick, which went unnoticed only because a successful login replaces the whole dict a moment later.
|
The suite runs against an instance with authentication turned off as well: `login()` reads a 403 there as "there is nothing to log in to" rather than as a failure, since every request is already allowed. `integration/test_auth.py` is the exception and skips itself — it is about passwords, and those endpoints answer 403 in that mode by design. Note `BearerSession.post` copies the header dict rather than writing into it: the form `Content-Type` belongs to the login request alone, and it used to stick, which went unnoticed only because a successful login replaces the whole dict a moment later.
|
||||||
|
|
||||||
CI (`.github/workflows/release.yml`) runs **only on `release: published`**, not on pushes or pull requests — nothing validates a PR automatically. Two jobs gate every publish job below them, both on `ubuntu-latest` and `ubuntu-24.04-arm`: `run_tests` does `python3 run.py start -P testpassword` then `cd tests && sudo ./run_tests.sh`, and `engine_tests` runs the Rust suite (`cargo test` in `proxysrc`) in the Dockerfile's `enginetest` stage — the compiler stage plus the filter library the pyworker tests start. That suite was not run before a release at all, though much of what it covers (HTTP/2, HTTP/3 and QUIC termination, the worker's framing, the UDP relay) no integration test reaches. `enginetest` sits **before** `final` on purpose: a build with no target, which is what compose does, takes the last stage. The tests' dependencies are pure python; `iperf3`, matplotlib and numpy belong to `bench/requirements.txt` and CI installs none of them.
|
CI (`.github/workflows/release.yml`) runs **only on `release: published`**, not on pushes or pull requests — nothing validates a PR automatically. Two jobs gate every publish job below them, both on `ubuntu-latest` and `ubuntu-24.04-arm`: `run_tests` does `python3 run.py start -P testpassword` then `cd tests && sudo ./run_tests.sh`, and `engine_tests` runs the Rust suite (`cargo test` in `proxysrc`) in the Dockerfile's `enginetest` stage — the compiler stage plus the filter library the pyworker tests start. That suite was not run before a release at all, though much of what it covers (HTTP/2, HTTP/3 and QUIC termination, the worker's framing, the UDP relay) no integration test reaches. `enginetest` sits **before** `final` on purpose: a build with no target, which is what compose does, takes the last stage. The tests' dependencies are pure python; `iperf3`, matplotlib and numpy belong to the `bench` group of `tests/pyproject.toml` and CI installs none of them.
|
||||||
|
|
||||||
## Architecture
|
## Architecture
|
||||||
|
|
||||||
|
|||||||
@ -57,12 +57,12 @@ RUN python3.14 -m pip install --no-cache-dir --break-system-packages /fgex-lib
|
|||||||
#Building main conteiner
|
#Building main conteiner
|
||||||
FROM --platform=$TARGETARCH base AS final
|
FROM --platform=$TARGETARCH base AS final
|
||||||
|
|
||||||
COPY ./backend/requirements.txt /execute/requirements.txt
|
COPY ./backend/pyproject.toml /execute/pyproject.toml
|
||||||
COPY ./fgex-lib /execute/fgex-lib
|
COPY ./fgex-lib /execute/fgex-lib
|
||||||
|
|
||||||
RUN dnf -y update && dnf install -y gcc-c++ python3.14-devel uv git &&\
|
RUN dnf -y update && dnf install -y gcc-c++ python3.14-devel uv git &&\
|
||||||
uv pip install --no-cache --system ./fgex-lib &&\
|
uv pip install --no-cache --system ./fgex-lib &&\
|
||||||
uv pip install --no-cache --system -r /execute/requirements.txt &&\
|
uv pip install --no-cache --system -r /execute/pyproject.toml &&\
|
||||||
uv cache clean && dnf remove -y gcc-c++ python3.14-devel uv git && dnf clean all
|
uv cache clean && dnf remove -y gcc-c++ python3.14-devel uv git && dnf clean all
|
||||||
|
|
||||||
COPY ./backend/ /execute/
|
COPY ./backend/ /execute/
|
||||||
|
|||||||
15
backend/pyproject.toml
Normal file
15
backend/pyproject.toml
Normal file
@ -0,0 +1,15 @@
|
|||||||
|
# Not a package: only read for its dependencies (`uv pip install -r pyproject.toml` in the
|
||||||
|
# Dockerfile). The version firegex reports is in utils/__init__.py.
|
||||||
|
[project]
|
||||||
|
name = "firegex-backend"
|
||||||
|
version = "0.0.0"
|
||||||
|
dependencies = [
|
||||||
|
"fastapi[all]",
|
||||||
|
"httpx",
|
||||||
|
"uvicorn[standard]",
|
||||||
|
"psutil",
|
||||||
|
"python-jose[cryptography]",
|
||||||
|
"python-socketio",
|
||||||
|
"brotli",
|
||||||
|
# nftables comes from the distribution (python3-nftables), not from PyPI.
|
||||||
|
]
|
||||||
@ -1,8 +0,0 @@
|
|||||||
fastapi[all]
|
|
||||||
httpx
|
|
||||||
uvicorn[standard]
|
|
||||||
psutil
|
|
||||||
python-jose[cryptography]
|
|
||||||
python-socketio
|
|
||||||
brotli
|
|
||||||
#git+https://salsa.debian.org/pkg-netfilter-team/pkg-nftables#egg=nftables&subdirectory=py
|
|
||||||
Binary file not shown.
|
Before Width: | Height: | Size: 298 KiB After Width: | Height: | Size: 246 KiB |
45
docs/screenshot_demo.py
Normal file
45
docs/screenshot_demo.py
Normal file
@ -0,0 +1,45 @@
|
|||||||
|
#!/usr/bin/env python3
|
||||||
|
"""
|
||||||
|
Take the README's screenshot of the interface, from the demo build.
|
||||||
|
|
||||||
|
Usage:
|
||||||
|
python3 docs/screenshot_demo.py [url] [output_path]
|
||||||
|
|
||||||
|
`url` defaults to the published demo; the release workflow builds the demo from the
|
||||||
|
release being published and serves it locally instead, so the picture is of that version
|
||||||
|
and not of whatever the Pages deploy last finished (built with `VITE_VERSION=<tag>`, the
|
||||||
|
header then names it instead of saying "demo"). Needs Playwright with chromium
|
||||||
|
(`pip install playwright && playwright install --with-deps chromium`).
|
||||||
|
|
||||||
|
The viewport is fixed, so the image keeps its size from one release to the next however
|
||||||
|
much the demo's seed data happens to render.
|
||||||
|
"""
|
||||||
|
import sys
|
||||||
|
from pathlib import Path
|
||||||
|
|
||||||
|
from playwright.sync_api import sync_playwright
|
||||||
|
|
||||||
|
URL = sys.argv[1] if len(sys.argv) > 1 else "https://demo.firegex.pwnzer0tt1.it/"
|
||||||
|
OUTPUT = Path(sys.argv[2]) if len(sys.argv) > 2 else Path(__file__).parent / "Firegex_Screenshot.png"
|
||||||
|
VIEWPORT = {"width": 1600, "height": 960}
|
||||||
|
|
||||||
|
|
||||||
|
def main():
|
||||||
|
with sync_playwright() as p:
|
||||||
|
browser = p.chromium.launch()
|
||||||
|
page = browser.new_page(viewport=VIEWPORT, device_scale_factor=2, color_scheme="dark")
|
||||||
|
page.goto(URL, wait_until="networkidle")
|
||||||
|
# The demo greets its visitor with a toast saying it is one, which is not part of
|
||||||
|
# the interface being pictured.
|
||||||
|
page.add_style_tag(content=".mantine-Notifications-root { display: none !important; }")
|
||||||
|
# The list fills in from the demo's in-memory backend after the page has loaded;
|
||||||
|
# a seeded service's name is what says it has.
|
||||||
|
page.wait_for_selector("text=shop-api", timeout=15000)
|
||||||
|
page.wait_for_timeout(1000)
|
||||||
|
page.screenshot(path=str(OUTPUT))
|
||||||
|
browser.close()
|
||||||
|
print(f"Saved {OUTPUT} ({VIEWPORT['width']}x{VIEWPORT['height']} @2x)")
|
||||||
|
|
||||||
|
|
||||||
|
if __name__ == "__main__":
|
||||||
|
main()
|
||||||
@ -1,4 +1,3 @@
|
|||||||
include requirements.txt
|
|
||||||
recursive-include llhttp *.c *.h
|
recursive-include llhttp *.c *.h
|
||||||
include llhttp/PROVENANCE.md
|
include llhttp/PROVENANCE.md
|
||||||
include llhttp/LICENSE.pyllhttp
|
include llhttp/LICENSE.pyllhttp
|
||||||
|
|||||||
42
fgex-lib/pyproject.toml
Normal file
42
fgex-lib/pyproject.toml
Normal file
@ -0,0 +1,42 @@
|
|||||||
|
[build-system]
|
||||||
|
requires = ["setuptools>=64"]
|
||||||
|
build-backend = "setuptools.build_meta"
|
||||||
|
|
||||||
|
[project]
|
||||||
|
name = "firegex"
|
||||||
|
# Set in setup.py, where the release workflow writes the tag.
|
||||||
|
dynamic = ["version"]
|
||||||
|
description = "Firegex client"
|
||||||
|
readme = "README.md"
|
||||||
|
authors = [{ name = "Pwnzer0tt1", email = "pwnzer0tt1@poliba.it" }]
|
||||||
|
requires-python = ">=3.10"
|
||||||
|
classifiers = [
|
||||||
|
"Programming Language :: Python :: 3",
|
||||||
|
"License :: OSI Approved :: GNU General Public License v3 or later (GPLv3+)",
|
||||||
|
]
|
||||||
|
dependencies = [
|
||||||
|
"typer",
|
||||||
|
# Imported directly by the CLI and both simulators for their output. `typer` happens to
|
||||||
|
# bring it today; `typer-slim` does not, and nothing here should rest on which one it is.
|
||||||
|
"rich",
|
||||||
|
"pydantic>=2",
|
||||||
|
"typing-extensions>=4.7.1",
|
||||||
|
"pycryptodome",
|
||||||
|
"watchfiles",
|
||||||
|
"websockets",
|
||||||
|
# 1.2 is the first with `output_buffer_limit`, which is what lets a body be decoded only
|
||||||
|
# as far as a filter may hold it. See `_unbrotli` in pyfilters/models/http.py.
|
||||||
|
"brotli>=1.2.0",
|
||||||
|
# Python 3.14 has `compression.zstd` in the standard library; this fills the gap
|
||||||
|
# below it. See firegex/pyfilters/internals/zstd_compat.py.
|
||||||
|
"zstandard; python_version < '3.14'",
|
||||||
|
]
|
||||||
|
|
||||||
|
[project.urls]
|
||||||
|
Homepage = "https://github.com/pwnzer0tt1/firegex"
|
||||||
|
|
||||||
|
[project.scripts]
|
||||||
|
fgex = "firegex.cli:run"
|
||||||
|
|
||||||
|
[tool.setuptools.packages.find]
|
||||||
|
include = ["firegex*"]
|
||||||
@ -1,15 +0,0 @@
|
|||||||
typer
|
|
||||||
# Imported directly by the CLI and both simulators for their output. `typer` happens to
|
|
||||||
# bring it today; `typer-slim` does not, and nothing here should rest on which one it is.
|
|
||||||
rich
|
|
||||||
pydantic>=2
|
|
||||||
typing-extensions>=4.7.1
|
|
||||||
pycryptodome
|
|
||||||
watchfiles
|
|
||||||
websockets
|
|
||||||
# 1.2 is the first with `output_buffer_limit`, which is what lets a body be decoded only
|
|
||||||
# as far as a filter may hold it. See `_unbrotli` in pyfilters/models/http.py.
|
|
||||||
brotli>=1.2.0
|
|
||||||
# Python 3.14 has `compression.zstd` in the standard library; this fills the gap
|
|
||||||
# below it. See firegex/pyfilters/internals/zstd_compat.py.
|
|
||||||
zstandard; python_version < '3.14'
|
|
||||||
@ -23,32 +23,12 @@ LLHTTP = setuptools.Extension(
|
|||||||
extra_compile_args=["-O3"],
|
extra_compile_args=["-O3"],
|
||||||
)
|
)
|
||||||
|
|
||||||
with open("README.md", "r", encoding="utf-8") as fh:
|
# Everything else about the package is in pyproject.toml. The version stays here because
|
||||||
long_description = fh.read()
|
# the release workflow writes the tag over the placeholder, which is not a valid version
|
||||||
|
# until it has.
|
||||||
with open('requirements.txt', 'r', encoding='utf-8') as f:
|
|
||||||
required = [ele.strip() for ele in f.read().splitlines() if not ele.strip().startswith("#") and ele.strip() != ""]
|
|
||||||
|
|
||||||
VERSION = "{{VERSION_PLACEHOLDER}}"
|
VERSION = "{{VERSION_PLACEHOLDER}}"
|
||||||
|
|
||||||
setuptools.setup(
|
setuptools.setup(
|
||||||
name="firegex",
|
version=VERSION if "{" not in VERSION else "0.0.0", #uv pip install -U . --no-cache-dir for testing
|
||||||
version= VERSION if "{" not in VERSION else "0.0.0", #uv pip install -U . --no-cache-dir for testing
|
|
||||||
author="Pwnzer0tt1",
|
|
||||||
author_email="pwnzer0tt1@poliba.it",
|
|
||||||
scripts=["fgex"],
|
|
||||||
py_modules=["firegex"],
|
|
||||||
install_requires=required,
|
|
||||||
include_package_data=True,
|
|
||||||
description="Firegex client",
|
|
||||||
long_description=long_description,
|
|
||||||
long_description_content_type="text/markdown",
|
|
||||||
url="https://github.com/pwnzer0tt1/firegex",
|
|
||||||
packages=setuptools.find_packages(),
|
|
||||||
ext_modules=[LLHTTP],
|
ext_modules=[LLHTTP],
|
||||||
classifiers=[
|
|
||||||
"Programming Language :: Python :: 3",
|
|
||||||
"License :: OSI Approved :: GNU General Public License v3 or later (GPLv3+)",
|
|
||||||
],
|
|
||||||
python_requires='>=3.10',
|
|
||||||
)
|
)
|
||||||
|
|||||||
@ -379,7 +379,7 @@ type Handler = (m: RegExpMatchArray, body: Json, query: Record<string, string>)
|
|||||||
const routes: [string, RegExp, Handler][] = [
|
const routes: [string, RegExp, Handler][] = [
|
||||||
|
|
||||||
// ---- global
|
// ---- global
|
||||||
["GET", /^status$/, () => ({ status: "run", loggined: true, version: "demo", auth_disabled: false })],
|
["GET", /^status$/, () => ({ status: "run", loggined: true, version: import.meta.env.VITE_VERSION || "demo", auth_disabled: false })],
|
||||||
["POST", /^login$/, () => ({ access_token: "demo-token", token_type: "bearer" })],
|
["POST", /^login$/, () => ({ access_token: "demo-token", token_type: "bearer" })],
|
||||||
["POST", /^set-password$/, () => ok],
|
["POST", /^set-password$/, () => ok],
|
||||||
["POST", /^change-password$/, () => ({ ...ok, access_token: "demo-token" })],
|
["POST", /^change-password$/, () => ({ ...ok, access_token: "demo-token" })],
|
||||||
|
|||||||
@ -12,9 +12,9 @@ most of this page is about. `pytest` does not collect this directory.
|
|||||||
| `stress_test_history.py` | a soak test for `HttpHistory` under load |
|
| `stress_test_history.py` | a soak test for `HttpHistory` under load |
|
||||||
| `results_plotter.py` | redraws the SVGs in `results/` from the CSVs beside them |
|
| `results_plotter.py` | redraws the SVGs in `results/` from the CSVs beside them |
|
||||||
|
|
||||||
These need more than the tests do — `pip3 install -r requirements.txt` here, on top of
|
These need more than the tests do — the `bench` group of `../pyproject.toml`
|
||||||
the one a directory up, and `iperf3` itself installed as a package because the python
|
(`pip3 install --group ../pyproject.toml:bench` from here, pip 25.1 or later), and `iperf3`
|
||||||
binding links against it.
|
itself installed as a package because the python binding links against it.
|
||||||
|
|
||||||
# Running a benchmark
|
# Running a benchmark
|
||||||
|
|
||||||
|
|||||||
@ -1,6 +0,0 @@
|
|||||||
# On top of ../requirements.txt.
|
|
||||||
# `iperf3` is a binding against libiperf, so the library has to be installed first
|
|
||||||
# (`apt-get install iperf3`, `dnf install iperf3`).
|
|
||||||
iperf3
|
|
||||||
matplotlib
|
|
||||||
numpy
|
|
||||||
@ -110,7 +110,10 @@ def test_stopping_a_proxied_service_does_not_cut_its_connections(api, protected,
|
|||||||
"a stopped service went on filtering the connections it had"
|
"a stopped service went on filtering the connections it had"
|
||||||
time.sleep(3)
|
time.sleep(3)
|
||||||
assert _held_open(server, b"a while later") == b"a while later"
|
assert _held_open(server, b"a while later") == b"a while later"
|
||||||
assert _log_says(api, service_id, "carry on unfiltered"), \
|
# The line waits for a STATS report taken after the stop, which the engine sends
|
||||||
|
# every two seconds and drops while the backend is behind — under a full suite on
|
||||||
|
# a busy machine, ten seconds was once not enough to see one.
|
||||||
|
assert _log_says(api, service_id, "carry on unfiltered", wait=30), \
|
||||||
"the log did not say connections were still being carried"
|
"the log did not say connections were still being carried"
|
||||||
finally:
|
finally:
|
||||||
server.close_client()
|
server.close_client()
|
||||||
|
|||||||
55
tests/pyproject.toml
Normal file
55
tests/pyproject.toml
Normal file
@ -0,0 +1,55 @@
|
|||||||
|
# Not a package: dependency groups only (PEP 735). `run_tests.sh` installs `test`;
|
||||||
|
# `bench` is what the benchmarks need on top of it.
|
||||||
|
[dependency-groups]
|
||||||
|
test = [
|
||||||
|
"pytest",
|
||||||
|
"requests",
|
||||||
|
"cryptography",
|
||||||
|
"psutil",
|
||||||
|
"pydantic",
|
||||||
|
"python-socketio[client]",
|
||||||
|
# The only way to speak QUIC from Python, and the integration tests for that layer have
|
||||||
|
# to speak it: what they check is the half the engine's own tests cannot reach — the
|
||||||
|
# rules that steer a QUIC address, the relay bound per address, and a filter attached
|
||||||
|
# through the API. Everything that needs it skips with a reason when it is absent, so a
|
||||||
|
# machine without it still runs the rest.
|
||||||
|
"aioquic",
|
||||||
|
# And the only way to speak gRPC from Python, which is what says HTTP/2 is filtered: a
|
||||||
|
# gRPC service is a real HTTP/2 service, its method name is an HPACK-compressed `:path`,
|
||||||
|
# and its status is a trailer section — the three things a firegex that only forwarded
|
||||||
|
# HTTP/2 could not show a filter. Everything that needs it skips with a reason when it is
|
||||||
|
# absent, so a machine without it still runs the rest.
|
||||||
|
"grpcio",
|
||||||
|
# `unit/test_nftables_addresses.py` imports the **backend** — the address-to-nftables
|
||||||
|
# translation is the backend's, and testing a copy of it would be testing a copy. That
|
||||||
|
# drags in `backend/utils`, which raises FastAPI's `HTTPException`, so FastAPI has to be
|
||||||
|
# here even though nothing in this suite serves a request. Without it the file cannot be
|
||||||
|
# collected, and a collection error is not one test failing: pytest stops the entire run
|
||||||
|
# before anything has executed. It went unnoticed for as long as it did because a machine
|
||||||
|
# that has ever run the backend outside the container already has it.
|
||||||
|
"fastapi",
|
||||||
|
# `unit/` imports the filter library straight out of `../fgex-lib`, so its runtime
|
||||||
|
# dependencies have to be here too. Not the published `firegex` itself, which would
|
||||||
|
# quietly shadow the source tree the suite is meant to be testing.
|
||||||
|
#
|
||||||
|
# The HTTP parser is deliberately NOT here. It is a C extension that ships inside
|
||||||
|
# firegex itself, so it has to be *built*: `run_tests.sh` does that with an editable
|
||||||
|
# install of `../fgex-lib`. Running `pytest` by hand without it gives
|
||||||
|
# `ImportError: cannot import name '_llhttp' from 'firegex'`.
|
||||||
|
"brotli",
|
||||||
|
"websockets",
|
||||||
|
# Unconditional here, unlike in the library, which only needs it below 3.14: the
|
||||||
|
# suite uses it to build the corpus *and* to exercise the fallback backend, so
|
||||||
|
# without it the cross-backend agreement test silently skips on the one Python
|
||||||
|
# that matters most.
|
||||||
|
"zstandard",
|
||||||
|
]
|
||||||
|
# The tests' dependencies stay pure python, so a CI run that only tests does not build a
|
||||||
|
# binding against a C library it will never call. `iperf3` is one: the library has to be
|
||||||
|
# installed first (`apt-get install iperf3`, `dnf install iperf3`).
|
||||||
|
bench = [
|
||||||
|
{ include-group = "test" },
|
||||||
|
"iperf3",
|
||||||
|
"matplotlib",
|
||||||
|
"numpy",
|
||||||
|
]
|
||||||
@ -1,48 +0,0 @@
|
|||||||
# What the tests need. The benchmarks need more — iperf3, matplotlib, numpy — and those
|
|
||||||
# are in bench/requirements.txt, so a CI run that only tests does not build a binding
|
|
||||||
# against a C library it will never call.
|
|
||||||
pytest
|
|
||||||
requests
|
|
||||||
cryptography
|
|
||||||
psutil
|
|
||||||
pydantic
|
|
||||||
python-socketio[client]
|
|
||||||
# The only way to speak QUIC from Python, and the integration tests for that layer have
|
|
||||||
# to speak it: what they check is the half the engine's own tests cannot reach — the
|
|
||||||
# rules that steer a QUIC address, the relay bound per address, and a filter attached
|
|
||||||
# through the API. Everything that needs it skips with a reason when it is absent, so a
|
|
||||||
# machine without it still runs the rest.
|
|
||||||
aioquic
|
|
||||||
# And the only way to speak gRPC from Python, which is what says HTTP/2 is filtered: a
|
|
||||||
# gRPC service is a real HTTP/2 service, its method name is an HPACK-compressed `:path`,
|
|
||||||
# and its status is a trailer section — the three things a firegex that only forwarded
|
|
||||||
# HTTP/2 could not show a filter. Everything that needs it skips with a reason when it is
|
|
||||||
# absent, so a machine without it still runs the rest.
|
|
||||||
grpcio
|
|
||||||
|
|
||||||
# `unit/test_nftables_addresses.py` imports the **backend** — the address-to-nftables
|
|
||||||
# translation is the backend's, and testing a copy of it would be testing a copy. That
|
|
||||||
# drags in `backend/utils`, which raises FastAPI's `HTTPException`, so FastAPI has to be
|
|
||||||
# here even though nothing in this suite serves a request. Without it the file cannot be
|
|
||||||
# collected, and a collection error is not one test failing: pytest stops the entire run
|
|
||||||
# before anything has executed. It went unnoticed for as long as it did because a machine
|
|
||||||
# that has ever run the backend outside the container already has it.
|
|
||||||
fastapi
|
|
||||||
|
|
||||||
# `unit/` imports the filter library straight out of `../fgex-lib`, so its runtime
|
|
||||||
# dependencies have to be here too. Listed one by one rather than with
|
|
||||||
# `-r ../fgex-lib/requirements.txt`, because that file lists `fgex` — the alias package,
|
|
||||||
# which depends on a *published* `firegex` and would quietly shadow the source tree the
|
|
||||||
# suite is meant to be testing.
|
|
||||||
#
|
|
||||||
# The HTTP parser is deliberately NOT here. It is a C extension that ships inside
|
|
||||||
# firegex itself, so it has to be *built*: `run_tests.sh` does that with an editable
|
|
||||||
# install of `../fgex-lib`. Running `pytest` by hand without it gives
|
|
||||||
# `ImportError: cannot import name '_llhttp' from 'firegex'`.
|
|
||||||
brotli
|
|
||||||
websockets
|
|
||||||
# Unconditional here, unlike in the library, which only needs it below 3.14: the
|
|
||||||
# suite uses it to build the corpus *and* to exercise the fallback backend, so
|
|
||||||
# without it the cross-backend agreement test silently skips on the one Python
|
|
||||||
# that matters most.
|
|
||||||
zstandard
|
|
||||||
@ -47,7 +47,10 @@ fi
|
|||||||
ADDRESS="${FIREGEX_ADDRESS:-http://127.0.0.1:4444/}"
|
ADDRESS="${FIREGEX_ADDRESS:-http://127.0.0.1:4444/}"
|
||||||
ADDRESS="${ADDRESS%/}/"
|
ADDRESS="${ADDRESS%/}/"
|
||||||
|
|
||||||
pip3 install -q -r requirements.txt
|
# The `test` group of pyproject.toml, read here rather than with `pip install --group`,
|
||||||
|
# which needs pip 25.1 — older than what CI's runners ship.
|
||||||
|
python3 -c 'import tomllib; print("\n".join(tomllib.load(open("pyproject.toml", "rb"))["dependency-groups"]["test"]))' \
|
||||||
|
| pip3 install -q -r /dev/stdin
|
||||||
# The filter library is imported straight out of the source tree, and it carries a C
|
# The filter library is imported straight out of the source tree, and it carries a C
|
||||||
# extension — the llhttp binding that parses HTTP — which therefore has to be built. An
|
# extension — the llhttp binding that parses HTTP — which therefore has to be built. An
|
||||||
# editable install builds it in place; without this `unit/` cannot even be collected.
|
# editable install builds it in place; without this `unit/` cannot even be collected.
|
||||||
|
|||||||
Loading…
Reference in New Issue
Block a user