Building and testing
How to work on the server itself: building it, running the tests, and what the tests check. To use the server in a project, Installing is enough.
Build
git clone --recursive https://github.com/Kiyomi-Computation-Systems/chez-http-server.git
cd chez-http-server
make build
make help lists every target. The ones you will use most:
| Target | Does |
|---|---|
make build | builds the chez-libuv shim and assembles the library path |
make test | runs every suite, tests/test-*.sps |
make run PROGRAM=... [ARGS="..."] | runs a program on the library path |
make dev | starts a Chez REPL with everything on the library path |
make check-pins | checks every submodule sits at its pinned commit, and Akku.manifest at the same versions |
make prod | rebuilds the shim optimized and runs the suite on it |
make clean | removes build artifacts |
Test
make test
For the test run you also need the clients the suites drive the server
with: curl, ab (apache2-utils on Linux), python3 and awk,
plus pcre2 and zlib where pkg-config sees them (brew install pcre2, or apt install libpcre2-dev zlib1g-dev). make test builds
vtest2 from its pinned source and downloads oha, checked by its
sha256, the first time.
To run one suite:
make test TESTS=tests/test-examples.sps
Every suite exits with one of four statuses: 0 all passed, 1 a check
failed, 2 the suite itself broke or ran no check, 3 a vendored file no
longer matches its recorded checksum. make test runs them all, keeps going
past a failure, and reports ALL SUITES PASSED only when every one passed.
What the suites check
| Kind | Checks | Against |
|---|---|---|
| the pure core | every row of the connection's transition table, the limits, the serializer, targets, Host | the table written out as data, RFC 9110 and 9112 |
| the parser corpora | how the server answers each request, however its bytes are split into reads | llhttp's fixtures, libhtp's streams |
| strictness and robustness | which requests are refused, and with what | Kestrel's, Apache httpd's and lighttpd's tests; AWS http-desync-guardian; request-smuggling vectors |
| the shell | sockets, timeouts, backpressure, drain, upgrades | a real libuv loop on loopback |
| real clients | that what they see is right | curl, ApacheBench, vtest2 scripts, an oha load test |
| allocation | the bytes a request allocates | a ceiling the suite fails above |
| the examples | that each example answers as its page says | each started as its own process |
The corpus suites try a sample of the ways to split each input by default;
CORPUS=full make test tries every split.
The vendored corpora live in test/vendor/, each with a PROVENANCE file
giving its source and the sha256 of every file. They are never edited by
hand; make corpus-refresh re-vendors one from an upstream checkout.
In Docker
With Docker installed, one script gives you every tool the build, the tests and the docs site need, without installing any of them on your machine:
scripts/docker-env make test # run one command
scripts/docker-env # or open a shell
The command runs in a container built from docker/dev.Dockerfile: the
Ubuntu and packages CI uses, and Chez Scheme 10.4.1 built from source.
- The first run is slow. It builds the image first, mostly compiling
Chez, which takes a few minutes. Later runs start in about a second. The
image is rebuilt only when
docker/dev.Dockerfileor the Chez version changes. - A fresh clone needs nothing else. The container has the network, so
make testclones the submodules and fetches oha itself. - It mounts this checkout and nothing else of your machine (in a
linked worktree, also the main repository's
.git): none of your git config but the repository's own, no keys, no tokens, and no environment variables but the terminal's and your Docker proxy settings. Edit, commit and push outside it. It runs as you, so the files it writes are yours. - It is not a sandbox. It reaches the network, your machine's own
services included, and your machine later runs what it writes into the
checkout,
.gitincluded. - To reach a server it runs, give a port with
-p.scripts/docker-env -p 8080 make run PROGRAM=examples/hello.spsserves on your machine's 127.0.0.1:8080, andcurl http://127.0.0.1:8080/from outside answersHello.-psets the port inside too, so for another one give-p 3000, notCHEZ_HTTP_PORT. - Set a variable inside it with
env, after-p:scripts/docker-env -p 8080 env CHEZ_HTTP_DRAIN_TIMEOUT=5 make run PROGRAM=examples/hello.sps. - One toolchain at a time. The container shares
build/with your machine, and each toolchain records paths the others lack. Runmake cleanwhen you switch between it, your machine's own tools and the Guix container.
make check-docker, run outside the container, tests the script, then
checks the container from inside: the tools, Chez and libuv at their
floors, the isolation, then make test and make check-site. Last, it
serves an example through -p and asks it from your machine.
On a Guix host
With Guix installed, one script gives you every tool the build, the tests and the docs site need, without installing any of them into your system:
scripts/guix-env make test # run one command
scripts/guix-env # or open a shell
The command runs in a container built from the Guix commit pinned in
channels.scm and the packages in manifest.scm: Chez Scheme 10.4.1,
libuv 1.52.1, gcc, curl, ab, what vtest2 builds with, and cmark-gfm.
- The first run is slow. Guix fetches its pinned commit and the
packages, then builds Chez from source, which alone takes about two
minutes. Later runs start in a few seconds, so batch commands:
scripts/guix-env sh -c 'make test && make check-site'. - A fresh clone needs nothing else. The container has the network, so
make testclones the submodules and fetches oha itself. - It sees this checkout and nothing else of your home directory (in a
linked worktree, also the main repository's
.git): no git identity, no keys, no tokens. Edit, commit and push outside it. - It sets
CHEZandCC, so noCHEZ=is needed. - A server it runs answers on your machine. It shares the network, so
after
scripts/guix-env make run PROGRAM=examples/hello.sps,curl http://127.0.0.1:8080/from outside answersHello. - Your environment stays outside, except
TERM,COLORTERMandLANG. Set a variable inside instead:scripts/guix-env env CHEZ_HTTP_PORT=3000 make run PROGRAM=examples/hello.sps.
make check-guix, run outside the container, tests the script, then checks
the container from inside: the pinned tools, Chez and libuv at their floors,
the isolation, and finally make test and make check-site. To move the
pin, change the commit in channels.scm (guix describe -f channels prints
yours) and run make check-guix.
The docs site
These pages render to a small static site:
make site # writes build/site/; open build/site/index.html
make check-site # every page listed, every link and anchor resolving
The site is built by build-site.sps and the libraries in site/, through
chez-cmark-gfm,
which needs the system cmark-gfm library (brew install cmark-gfm, or
apt install cmark-gfm). make site fetches chez-cmark-gfm the first time,
as Akku.lock pins it, or uses .akku/ after akku install, which also
installs the server's dependencies and builds chez-libuv's shim;
make check-akku-lock checks Akku.lock is what akku lock makes of
Akku.manifest. A new page goes
in docs/ and in site/pages.sls, the nav's order; make check-site fails
until both name it.
Each release tag publishes the site to
GitHub Pages,
through .github/workflows/pages.yml, after make check-site passes; it also
runs by hand from the Actions tab. That job has only Chez and cmark-gfm:
make check-site clones chez-srfi alone and builds no shim, and CI's site
job builds the site the same way on every pull request.
The Akku package
Akku.manifest also makes the server an Akku
package: the libraries under src/, depending on chez-libuv, chez-httparse,
chez-uri (the uriparse repository) and byte-machine. Each range starts at
the version vendored under vendor/, the one the suites ran against; a later
patch release meets it too. make check-pins fails when a range, a vendored
copy and Akku.lock disagree, so a pin moves in all three at once: the
submodule, its range, then akku lock. It also fails when the package's
version is not the latest release in CHANGELOG.md, so bump it in
Akku.manifest with each release.
make check-akku needs akku. It checks Akku.lock with
make check-akku-lock, then installs this checkout into a scratch project as
someone using the server would, with the dependencies Akku.lock locks, and
checks that exactly src/'s libraries land, that
chez-libuv's install hook builds the shim, and that examples/hello.sps
answers on Akku's library path alone.
House rules
- Functional core, imperative shell. Rules go in the pure libraries
under
src/http-server/, which import no libuv; effects go insrc/http-server/private/.make check-importsenforces it. The facade,src/http-server.sls, and the testing library,src/http-server/testing.sls, sit over both. - A test is finished when it has been seen to fail. Break the code it guards, watch the check go red, then put the code back.
- Every exported procedure has a signature comment, Typed Racket style:
;; reason-status : Symbol -> Nat. - Comments describe the code as it is, not how it came to be.
CI runs make test and make check-site on Linux for every pull request.
CONTRIBUTING.md has the rest of what a pull request
needs.