Files
capy-bearer/scripts/test_capy_guide_examples.sh
udoandClaude Fable 5.1 ca8edb6bd1 Delete the queued task API, the task pool, and the job API
Tasks are fire-and-forget only: function TASK:NAME(props : dval) and
task_execute(target, timeout_seconds, props). The durable queue, the
TASK_WORKERS pool with its leases, cancellation, admission health and recovery,
task, task_status, task_await, task_cancel, the background form of shell_exec,
job_status, job_result, job_await, job_cancel, http_request_async, the job
directory and its --job-worker helper, their pages, fixtures, guide sections,
settings documentation and parity rows are gone. A bare TASK handler is a
compile error.

shell_exec(command, {on_done: "/unit.capy:NAME"}) keeps the one job use that
mattered: the command runs in the detached task runner, and TASK:NAME of the
unit receives command, exit_code, stdout, stderr, timed_out and
output_truncated when it finishes. Unit path resolution is shared by tasks and
services. The tasks guide is "Tasks and commands" with a stable slug change.

Co-Authored-By: Claude Fable 5.1 <noreply@anthropic.com>
2026-10-14 01:14:19 +00:00

199 lines
8.6 KiB
Bash
Executable File

#!/usr/bin/env bash
set -euo pipefail
cd "$(dirname "$0")/.."
umask 077
http_host="${BEARER_TEST_HTTP_HOST:-bearer.openfu.com}"
http_base="${BEARER_TEST_HTTP_BASE:-http://127.0.0.1}"
http_timeout="${BEARER_TEST_HTTP_TIMEOUT:-30}"
abi_version=$(awk '/^#define BEARER_WASM_CORE_ABI_VERSION / {print $3; exit}' src/wasm/abi.h)
site_directory="${BEARER_TEST_SITE_DIRECTORY:-site}"
bin_directory="${BEARER_TEST_BIN_DIRECTORY:-/tmp/bearer/work}"
if [[ -r /etc/bearer/settings.cfg ]]; then
configured_site=$(awk -F= '/^[[:space:]]*HTTP_DOCUMENT_ROOT[[:space:]]*=/ {sub(/^[^=]*=/, ""); print; exit}' /etc/bearer/settings.cfg)
configured_bin=$(awk -F= '/^[[:space:]]*BIN_DIRECTORY[[:space:]]*=/ {sub(/^[^=]*=/, ""); print; exit}' /etc/bearer/settings.cfg)
[[ -n "${BEARER_TEST_SITE_DIRECTORY:-}" ]] || site_directory="${configured_site:-$site_directory}"
[[ -n "${BEARER_TEST_BIN_DIRECTORY:-}" ]] || bin_directory="${configured_bin:-$bin_directory}"
fi
[[ "$http_timeout" =~ ^[1-9][0-9]*$ ]] || {
echo "Capy guide test timeout must be a positive integer" >&2
exit 2
}
python3 - "$http_base" <<'PY'
import sys
from urllib.parse import urlsplit
parsed = urlsplit(sys.argv[1])
if parsed.scheme != "http" or parsed.username or parsed.password or parsed.hostname not in {"127.0.0.1", "::1", "localhost"}:
raise SystemExit("Capy guide test HTTP base must use localhost over HTTP")
PY
site_directory=$(realpath "$site_directory")
[[ -d "$site_directory/tests" ]] || {
echo "Capy guide test directory is missing: $site_directory/tests" >&2
exit 1
}
service_status=$(curl -sS --max-time "$http_timeout" -o /dev/null -w '%{http_code}' -H "Host: $http_host" "$http_base/") || {
echo "Capy guide test service is unavailable at the local HTTP base" >&2
exit 1
}
[[ "$service_status" =~ ^[23][0-9][0-9]$ ]] || {
echo "Capy guide test service returned HTTP $service_status at the local HTTP base" >&2
exit 1
}
test_directory=$(mktemp -d "$site_directory/tests/capy-guide-examples.XXXXXX")
chmod 711 "$test_directory"
cache_directory=""
cleanup() {
rm -rf -- "$test_directory"
[[ -z "$cache_directory" ]] || rm -rf -- "$cache_directory"
}
trap cleanup EXIT HUP INT TERM
python3 - "$test_directory" <<'PY'
import importlib.util
import re
import sys
from pathlib import Path
root = Path.cwd()
page = root / "site/doc/content/guide/install-and-first-program.capy"
renderer = page.read_text()
if "function COMPONENT(props : dval)\n{\n\tprint(" not in renderer or "var page :=" in renderer:
raise SystemExit("Capy documentation routes must print static detail HTML directly")
checker_path = root / "scripts/check_capy_doc_examples.py"
spec = importlib.util.spec_from_file_location("capy_doc_examples", checker_path)
if spec is None or spec.loader is None:
raise SystemExit("Capy guide test cannot import the documentation checker")
checker = importlib.util.module_from_spec(spec)
spec.loader.exec_module(checker)
guides = root / "site/doc/source/guide"
errors = checker.check_language_guides(guides, root / "site/doc/source")
if errors:
raise SystemExit("Capy guide test rejected guide sources:\n- " + "\n- ".join(errors))
articles = {path.stem: path for path in guides.glob("*.txt")}
if set(articles) != checker.CANONICAL_GUIDES:
raise SystemExit("Capy guide test found a guide set that differs from the checker canonical set")
snippet_directory = Path(sys.argv[1]) / "snippets"
snippet_directory.mkdir()
unsafe = re.compile(r"\b(?:component_(?:capture|render)|file_[a-z_]+|mysql_[a-z_]+|server_[a-z_]+|shell_(?:exec|spawn)|sqlite_[a-z_]+|task(?:_[a-z_]+)?|unit_(?:call|compile|load)|ws_[a-z_]+)\s*\(")
for slug in sorted(checker.CANONICAL_GUIDES):
page = articles[slug]
sections = checker.parse_sections(page)
chosen = None
for index, (line, header, body) in enumerate(sections):
if header.startswith("example capy render") and index + 1 < len(sections) and sections[index + 1][1] == "output" and sections[index + 1][2]:
chosen = (line, body, sections[index + 1][2])
break
if chosen is None:
raise SystemExit(f"{page.name}: missing a runnable RENDER example with exact output")
line, source, expected = chosen
source_without_strings = re.sub(r'"(?:\\.|[^"\\])*"', '""', source)
source_without_strings = re.sub(r"//[^\n]*", "", source_without_strings)
match = unsafe.search(source_without_strings)
if match:
raise SystemExit(f"{page.name}:{line}: example requires undeclared external setup or has unsafe side effects: {match.group(0).strip()}")
source_path = Path(sys.argv[1]) / f"{slug}.capy"
source_path.write_text(source + "\n")
source_path.chmod(0o644)
(Path(sys.argv[1]) / f"{slug}.expected").write_text(expected + "\n")
snippet_index = 1
for index, (example_line, header, snippet) in enumerate(sections):
if not header.startswith("example capy") or snippet == source:
continue
snippet_path = snippet_directory / f"{slug}-{snippet_index}.capy"
snippet_path.write_text(snippet.rstrip() + "\n")
snippet_index += 1
PY
snippet_build="$test_directory/snippet-build"
mkdir -p "$snippet_build"
snippet_count=0
for snippet in "$test_directory"/snippets/*.capy; do
snippet_name=${snippet##*/}
# --source-root lets a snippet #import a web path such as
# "/doc/examples/call_target.capy", the same form the server resolves.
if ! bin/capyc "$snippet" --bearer-unit --abi-version "$abi_version" --source-root "$site_directory" -o "$snippet_build/$snippet_name.wasm" --source-map "$snippet_build/$snippet_name.wasm.source-map"; then
echo "Capy guide snippet failed to compile: $snippet_name" >&2
exit 1
fi
((snippet_count += 1))
done
cache_directory="$(scripts/unit_cache_directory "$bin_directory")$test_directory"
test_name=${test_directory##*/}
count=0
page_09_checked=false
for source in "$test_directory"/*.capy; do
slug=${source##*/}
slug=${slug%.capy}
expected="$test_directory/$slug.expected"
actual="$test_directory/$slug.actual"
if ! curl -fsS --max-time "$http_timeout" -H "Host: $http_host" "$http_base/tests/$test_name/$slug.capy" -o "$actual"; then
echo "Capy guide example failed for $slug: the local HTTP request failed" >&2
exit 1
fi
if ! cmp -s "$expected" "$actual"; then
expected_size=$(wc -c <"$expected")
actual_size=$(wc -c <"$actual")
expected_sha=$(sha256sum "$expected" | awk '{print $1}')
actual_sha=$(sha256sum "$actual" | awk '{print $1}')
printf 'Capy guide example mismatch for %s: expected %s bytes sha256=%s, got %s bytes sha256=%s\n' \
"$slug" "$expected_size" "$expected_sha" "$actual_size" "$actual_sha" >&2
exit 1
fi
page="$test_directory/$slug.page"
page_headers="$test_directory/$slug.headers"
if ! page_status=$(curl -sS --max-time "$http_timeout" -D "$page_headers" -o "$page" -w '%{http_code}' -H "Host: $http_host" "$http_base/doc/guide/${slug#[0-9][0-9]-}/"); then
echo "Capy documentation application failed to request $slug" >&2
exit 1
fi
if [[ ! "$page_status" =~ ^2[0-9][0-9]$ ]]; then
echo "Capy documentation application returned HTTP $page_status for $slug" >&2
exit 1
fi
if ! tr -d '\r' <"$page_headers" | grep -qiE '^Content-Type:[[:space:]]*text/html([[:space:]]*;|[[:space:]]*$)'; then
if [[ "$slug" == "09-web-handlers-and-requests" ]]; then
echo "Capy guide page 09 leaked an example response Content-Type" >&2
else
echo "Capy documentation application did not return text/html for $slug" >&2
fi
exit 1
fi
if [[ "$slug" == "09-web-handlers-and-requests" ]]; then
page_09_checked=true
fi
if grep -qE 'DOC EXAMPLE ERROR|Compile-only: WS|guide-example|Minimal executable example|Common variants' "$page" ||
! grep -q '<pre><code class="language-capy">' "$page" ||
! grep -q '<strong>Output</strong>' "$page" ||
! grep -q '<pre><code class="language-text">' "$page"; then
echo "Capy documentation application misplaced the source or output for $slug" >&2
exit 1
fi
python3 - "$page" <<'PY'
import re
import sys
from pathlib import Path
html = Path(sys.argv[1]).read_text()
pattern = r'<pre><code class="language-capy">.*?</code></pre><p><strong>Output</strong></p><pre><code class="language-text">.*?</code></pre>'
if re.search(pattern, html, re.S) is None:
raise SystemExit("Capy documentation source and output are not adjacent")
PY
((count += 1))
done
expected_guides=$(find site/doc/source/guide -maxdepth 1 -type f -name '*.txt' | wc -l)
[[ "$count" -eq "$expected_guides" ]] || {
echo "Capy guide test expected $expected_guides canonical examples, ran $count" >&2
exit 1
}
[[ "$page_09_checked" == true ]] || {
echo "Capy guide test did not request page 09" >&2
exit 1
}
echo "Capy guide HTTP examples passed for $count pages; $snippet_count additional snippets compiled"