| #!/usr/bin/env bash
#
# Autobahn|Testsuite against a ws echo server or client, both ways, with
# upstream's Docker image: wstest is Python 2 only, and the image is how
# upstream runs it. docs/sai.md says how to set up the builder it runs
# on; docs/port-plan.md (phase 1g) why it is a gate.
#
# scripts/autobahn.sh server <command starting an echo server on 9001>
# scripts/autobahn.sh client <command running one echo client connection>
#
# In "server" mode, the command is started in the background, and wstest's
# fuzzingclient runs every case against ws://127.0.0.1:9001. In "client"
# mode, wstest's fuzzingserver listens on 9001, and the command is run
# once a case, with the path to ask for appended as its last argument
# (/runCase?case=N&agent=AGENT), until it fails, the cases being over; then
# once with /updateReports?agent=AGENT. The echo examples of C, for a
# baseline, built with -DLWS_WITH_MINIMAL_EXAMPLES=1:
#
# scripts/autobahn.sh server bin/lws-minimal-ws-server-echo -p 9001
# scripts/autobahn.sh client bin/lws-minimal-ws-client-echo \
# -s 127.0.0.1 -p 9001 -u
#
# Reports go to ./autobahn/reports/{servers,clients}, index.html among
# them. The exit code is 0 only if every case not excluded below is OK,
# NON-STRICT or INFORMATIONAL, as C's scripts judge it.
#
# AUTOBAHN_IMAGE overrides the image; AUTOBAHN_AGENT the agent name.
set -euo pipefail
# Upstream's image, pinned: a digest, not a tag, so what runs is what was
# checked. Pin it on the builder's first run: docker pull
# crossbario/autobahn-testsuite, then docker inspect --format
# '{{index .RepoDigests 0}}' crossbario/autobahn-testsuite, and put that
# here.
image="${AUTOBAHN_IMAGE:-crossbario/autobahn-testsuite@sha256:PIN-ME}"
agent="${AUTOBAHN_AGENT:-npro}"
port=9001
# Cases excluded, and why. C's, until npro's own reasons replace them:
# 2.10, 2.11: several pings in flight; RFC 6455 does not require it, and
# C and npro keep one pending pong.
# 12.3.1, 12.3.2, 12.4.*, 12.5.*: excluded by C's client script for
# permessage-deflate with its client; the reason is not
# written down there, and is to be found before npro keeps
# them out.
exclude_server='"2.10", "2.11"'
exclude_client='"2.10", "2.11", "12.3.1", "12.3.2", "12.4.*", "12.5.*"'
mode="${1:?usage: $0 server|client <command...>}"
shift
[ $# -gt 0 ] || { echo "$0: no command given" >&2; exit 2; }
case "$image" in
*PIN-ME*)
echo "$0: pin the image's digest first: see the top of $0" >&2
exit 2
;;
esac
work="$PWD/autobahn"
mkdir -p "$work/config" "$work/reports"
# wstest in the container, on the host's network so 127.0.0.1 is shared,
# as this user so the reports are ours
name="npro-autobahn-$$"
wstest() {
docker run --rm --name "$name" --network host --user "$(id -u):$(id -g)" \
-v "$work/config:/config:ro" -v "$work/reports:/reports" \
"$image" wstest "$@"
}
# the cases that are not OK, NON-STRICT or INFORMATIONAL, from an index;
# "behavior": exactly, not "behaviorClose":
failures() {
grep '"behavior":' "$1" | grep -v -e '"OK"' -e '"NON-STRICT"' \
-e '"INFORMATIONAL"' || true
}
judge() {
local index="$1" what="$2" total bad
[ -s "$index" ] || { echo "$what: no report" >&2; return 1; }
total=$(grep -c '"behavior":' "$index" || true)
bad=$(failures "$index" | wc -l)
echo "$what: $total cases, $bad failed"
[ "$bad" -eq 0 ]
}
case "$mode" in
server)
cat >"$work/config/fuzzingclient.json" <<EOF
{
"outdir": "/reports/servers",
"servers": [ { "agent": "$agent", "url": "ws://127.0.0.1:$port" } ],
"cases": [ "*" ],
"exclude-cases": [ $exclude_server ],
"exclude-agent-cases": {}
}
EOF
"$@" &
server=$!
trap 'kill $server 2>/dev/null || true' EXIT
sleep 1
kill -0 "$server" || { echo "$0: the server did not start" >&2; exit 3; }
wstest -m fuzzingclient -s /config/fuzzingclient.json
judge "$work/reports/servers/index.json" "autobahn's client, $agent's server"
;;
client)
cat >"$work/config/fuzzingserver.json" <<EOF
{
"url": "ws://127.0.0.1:$port",
"outdir": "/reports/clients",
"cases": [ "*" ],
"exclude-cases": [ $exclude_client ],
"exclude-agent-cases": {}
}
EOF
wstest -m fuzzingserver -s /config/fuzzingserver.json &
fuzzer=$!
# stopping the docker client need not stop its container
trap 'docker rm -f "$name" >/dev/null 2>&1 || true' EXIT
sleep 3
kill -0 "$fuzzer" || { echo "$0: wstest did not start" >&2; exit 3; }
n=1
while "$@" "/runCase?case=$n&agent=$agent"; do
n=$((n + 1))
done
"$@" "/updateReports?agent=$agent" || true
sleep 2
docker rm -f "$name" >/dev/null 2>&1 || true
judge "$work/reports/clients/index.json" "autobahn's server, $agent's client"
;;
*)
echo "usage: $0 server|client <command...>" >&2
exit 2
;;
esac
|