From 657fb0007f39f07cc0401e0c5d03e25df6234aa4 Mon Sep 17 00:00:00 2001 From: Alexander M Pickering Date: Tue, 21 Jul 2026 20:19:46 -0500 Subject: Inital commit. --- tests/fixtures/c/directives.c | 7 + tests/fixtures/c/inline_ref.c | 5 + tests/fixtures/c/multi_chunk.c | 11 + tests/fixtures/c/no_comments.c | 3 + tests/fixtures/c/ref_string.c | 6 + tests/fixtures/c/rst_exec.c | 8 + tests/fixtures/c/shared_namespace.c | 11 + tests/fixtures/c/simple.c | 5 + tests/fixtures/c/src_repo.c | 5 + tests/fixtures/c/toc.c | 5 + tests/fixtures/lua/simple.lua | 5 + tests/fixtures/lua/single_line.lua | 2 + tests/fixtures/md/readme.md | 2 + tests/fixtures/md/simple.md | 3 + tests/fixtures/py/simple.py | 6 + tests/fixtures/py/single_line.py | 2 + tests/helpers/common.bash | 263 +++++++++++ tests/helpers/profiler.bash | 53 +++ tests/integration/cli.bats | 215 +++++++++ tests/integration/pipeline.bats | 842 ++++++++++++++++++++++++++++++++++++ tests/run_tests.sh | 57 +++ tests/unit/directives.bats | 235 ++++++++++ tests/unit/extraction.bats | 171 ++++++++ tests/unit/template_vars.bats | 160 +++++++ 24 files changed, 2082 insertions(+) create mode 100644 tests/fixtures/c/directives.c create mode 100644 tests/fixtures/c/inline_ref.c create mode 100644 tests/fixtures/c/multi_chunk.c create mode 100644 tests/fixtures/c/no_comments.c create mode 100644 tests/fixtures/c/ref_string.c create mode 100644 tests/fixtures/c/rst_exec.c create mode 100644 tests/fixtures/c/shared_namespace.c create mode 100644 tests/fixtures/c/simple.c create mode 100644 tests/fixtures/c/src_repo.c create mode 100644 tests/fixtures/c/toc.c create mode 100644 tests/fixtures/lua/simple.lua create mode 100644 tests/fixtures/lua/single_line.lua create mode 100644 tests/fixtures/md/readme.md create mode 100644 tests/fixtures/md/simple.md create mode 100644 tests/fixtures/py/simple.py create mode 100644 tests/fixtures/py/single_line.py create mode 100644 tests/helpers/common.bash create mode 100644 tests/helpers/profiler.bash create mode 100644 tests/integration/cli.bats create mode 100644 tests/integration/pipeline.bats create mode 100644 tests/run_tests.sh create mode 100644 tests/unit/directives.bats create mode 100644 tests/unit/extraction.bats create mode 100644 tests/unit/template_vars.bats (limited to 'tests') diff --git a/tests/fixtures/c/directives.c b/tests/fixtures/c/directives.c new file mode 100644 index 0000000..4becc88 --- /dev/null +++ b/tests/fixtures/c/directives.c @@ -0,0 +1,7 @@ +/* md +@name test/directives +@file generated/source.c +@ref test/directives-ref +Directive test body content. +*/ +void directives_demo() {} diff --git a/tests/fixtures/c/inline_ref.c b/tests/fixtures/c/inline_ref.c new file mode 100644 index 0000000..72ef395 --- /dev/null +++ b/tests/fixtures/c/inline_ref.c @@ -0,0 +1,5 @@ +/* md +@name test/inline-ref +See @lua/Coroutines for details on cooperative multitasking. +*/ +void inline_ref_func() {} diff --git a/tests/fixtures/c/multi_chunk.c b/tests/fixtures/c/multi_chunk.c new file mode 100644 index 0000000..57fd463 --- /dev/null +++ b/tests/fixtures/c/multi_chunk.c @@ -0,0 +1,11 @@ +/* md +@name test/alpha +First chunk content. +*/ +void foo() {} + +/* md +@name test/beta +Second chunk content. +*/ +void bar() {} diff --git a/tests/fixtures/c/no_comments.c b/tests/fixtures/c/no_comments.c new file mode 100644 index 0000000..a667cef --- /dev/null +++ b/tests/fixtures/c/no_comments.c @@ -0,0 +1,3 @@ +/* This is a regular comment, not a doc comment */ +// Another non-doc comment +int noop() { return 42; } diff --git a/tests/fixtures/c/ref_string.c b/tests/fixtures/c/ref_string.c new file mode 100644 index 0000000..711ac20 --- /dev/null +++ b/tests/fixtures/c/ref_string.c @@ -0,0 +1,6 @@ +/* md +@name test/ref-string +@ref lua/Coroutines +Return type: {{ ref_string }} +*/ +void ref_string_func() {} diff --git a/tests/fixtures/c/rst_exec.c b/tests/fixtures/c/rst_exec.c new file mode 100644 index 0000000..3aabf9f --- /dev/null +++ b/tests/fixtures/c/rst_exec.c @@ -0,0 +1,8 @@ +/* rst +@name test/rst-exec +RST Heading +=========== + +This chunk should be rendered by the rst renderer. +*/ +int noop() { return 0; } diff --git a/tests/fixtures/c/shared_namespace.c b/tests/fixtures/c/shared_namespace.c new file mode 100644 index 0000000..25f064a --- /dev/null +++ b/tests/fixtures/c/shared_namespace.c @@ -0,0 +1,11 @@ +/* md +@name test/shared +Part one of the shared page. +*/ +void part_one() {} + +/* md +@name test/shared +Part two of the shared page. +*/ +void part_two() {} diff --git a/tests/fixtures/c/simple.c b/tests/fixtures/c/simple.c new file mode 100644 index 0000000..2595929 --- /dev/null +++ b/tests/fixtures/c/simple.c @@ -0,0 +1,5 @@ +/* md +@name test/simple +Hello from a C comment. +*/ +int main() { return 0; } diff --git a/tests/fixtures/c/src_repo.c b/tests/fixtures/c/src_repo.c new file mode 100644 index 0000000..fb0d8b6 --- /dev/null +++ b/tests/fixtures/c/src_repo.c @@ -0,0 +1,5 @@ +/* md +@name test/src-repo +Generated from {{ src_repo }}/src.c +*/ +void src_repo_func() {} diff --git a/tests/fixtures/c/toc.c b/tests/fixtures/c/toc.c new file mode 100644 index 0000000..bb0782b --- /dev/null +++ b/tests/fixtures/c/toc.c @@ -0,0 +1,5 @@ +/* md +@name test/toc-chunk +Navigation: {{ toc }} +*/ +void toc_func() {} diff --git a/tests/fixtures/lua/simple.lua b/tests/fixtures/lua/simple.lua new file mode 100644 index 0000000..de527a9 --- /dev/null +++ b/tests/fixtures/lua/simple.lua @@ -0,0 +1,5 @@ +--[[ md +@name test/lua-simple +Hello from a Lua comment. +]] +local x = 1 diff --git a/tests/fixtures/lua/single_line.lua b/tests/fixtures/lua/single_line.lua new file mode 100644 index 0000000..f541825 --- /dev/null +++ b/tests/fixtures/lua/single_line.lua @@ -0,0 +1,2 @@ +--[[ md This comment opens and closes on one line — no chunk. ]] +local x = 1 diff --git a/tests/fixtures/md/readme.md b/tests/fixtures/md/readme.md new file mode 100644 index 0000000..5833cab --- /dev/null +++ b/tests/fixtures/md/readme.md @@ -0,0 +1,2 @@ +# Project Overview +Hello from the readme. diff --git a/tests/fixtures/md/simple.md b/tests/fixtures/md/simple.md new file mode 100644 index 0000000..494ade7 --- /dev/null +++ b/tests/fixtures/md/simple.md @@ -0,0 +1,3 @@ +@name test/md-simple +# Markdown Page +Hello from a markdown file. diff --git a/tests/fixtures/py/simple.py b/tests/fixtures/py/simple.py new file mode 100644 index 0000000..b8e2ce9 --- /dev/null +++ b/tests/fixtures/py/simple.py @@ -0,0 +1,6 @@ +'''md +@name test/py-simple +Hello from a Python doc comment. +''' +def noop(): + pass diff --git a/tests/fixtures/py/single_line.py b/tests/fixtures/py/single_line.py new file mode 100644 index 0000000..baafa10 --- /dev/null +++ b/tests/fixtures/py/single_line.py @@ -0,0 +1,2 @@ +'''This comment opens and closes on one line — no chunk.''' +x = 1 diff --git a/tests/helpers/common.bash b/tests/helpers/common.bash new file mode 100644 index 0000000..779cc96 --- /dev/null +++ b/tests/helpers/common.bash @@ -0,0 +1,263 @@ +# tests/helpers/common.bash +# Sourced by every bats test file via `load`. +# Provides: workspace isolation, PATH stubs, assertion helpers. + +# ── workspace setup/teardown ──────────────────────────────────────────────── + +# Call in setup(): creates an isolated temp directory and cds into it. +# A stub for md2html is pre-installed in a fake bin/ on PATH. +setup_workspace() { + TEST_TMPDIR="$(mktemp -d)" + # trap 'rm -rf $TEST_TMPDIR' EXIT + # Fake bin injected before the real PATH so stubs take priority + STUB_BIN="$TEST_TMPDIR/bin" + mkdir -p "$STUB_BIN" + install_stub_md2html "$STUB_BIN" + ORIG_DIR="$(pwd)" + export PATH="$STUB_BIN:$PATH" + # Work inside the temp dir so .trblcache is isolated + pushd "$TEST_TMPDIR" >/dev/null +} + +# Call in teardown(): removes the temp directory. +teardown_workspace() { + popd >/dev/null 2>&1 || true + rm -rf "$TEST_TMPDIR" +} + +# ── stubs ─────────────────────────────────────────────────────────────────── + +# md2html stub: echoes a predictable HTML marker for easy assertion. +# Accepts any flags; always succeeds. +install_stub_md2html() { + local bin_dir="$1" + cat > "$bin_dir/md2html" <<'EOF' +#!/usr/bin/env bash +# Stub: find the last non-flag argument as the input file, or read stdin. +# Flags (args starting with -) from the md alias are ignored. +input="" +for arg in "$@"; do + [[ "$arg" == -* ]] || input="$arg" +done +if [[ -n "$input" ]]; then + content="$(cat "$input")" +else + content="$(cat)" +fi +echo "

$content

" +EOF + chmod +x "$bin_dir/md2html" +} + + +# ── AWK program builders ───────────────────────────────────────────────────── +# These mirror the comment_form patterns from trbldoc.sh so extraction +# logic can be unit-tested without sourcing the whole script. + +CACHE_DIR=".trblcache" + +# Returns the AWK program string for C-style /* */ comments. +awk_prog_c() { + local cache="$1" + cat < \$chunkfile << \"EOF\"\\n@file " name ":" NR ; + if(\$2) {\$1=""; print "@exec " \$0} + in_comment=1; +} +EOF +} + +# Returns the AWK program string for Lua +awk_prog_lua() { + local cache="$1" + cat < \$chunkfile << \"EOF\"\\n@file " name ":" NR ; + if(\$2) {\$1=""; print "@exec " \$0} + in_comment=1; +} +EOF +} + +# Returns the AWK program string for Python. +# Handles '''renderer format (renderer glued directly to ''', no space required). +awk_prog_py() { + local cache="$1" + sed "s|__PY_CACHE__|${cache}|g" << 'PYEOF' +BEGIN{} +/'''/ { + if(in_comment) {print "EOF"} + in_comment=0 +} +in_comment {print} +/'''.+/ && !/'''$/ { + print "chunkfile=$(mktemp -p __PY_CACHE__/chunks)" + print "cat > $chunkfile << \"EOF\"\n@file " name ":" NR ; + renderer = $0 + sub(/'''/, "", renderer) + sub(/^[[:space:]]+/, "", renderer) + sub(/[[:space:]]+$/, "", renderer) + if (renderer != "") {print "@exec " renderer} + in_comment=1; +} +PYEOF +} + +# Returns the AWK program string for .md files +awk_prog_md() { + local cache="$1" + cat < \$chunkfile << \"EOF\"\\n@file " name ":1\\n@exec md" ; +} +{print \$0} +END{print "EOF"} +EOF +} + +# ── extraction helper ──────────────────────────────────────────────────────── + +# run_extraction FILE AWK_PROG CACHE_DIR +# Runs AWK against FILE, evals the output shell script, and populates CACHE_DIR/chunks. +run_extraction() { + local file="$1" + local awk_prog="$2" + local cache="$3" + mkdir -p "$cache/chunks" + local chunk_script + chunk_script=$(awk -v name="$file" "$awk_prog" < "$file") + eval "$chunk_script" +} + +# chunk_count CACHE_DIR +# Prints the number of chunk files produced. +chunk_count() { + find "$1/chunks" -type f 2>/dev/null | wc -l | tr -d ' ' +} + +# chunk_contains CACHE_DIR PATTERN +# Succeeds if any chunk file contains a line matching PATTERN (grep -q). +chunk_contains() { + grep -rl "$2" "$1/chunks" >/dev/null 2>&1 +} + +# ── assertion helpers ──────────────────────────────────────────────────────── + +# assert_file_exists PATH +assert_file_exists() { + if [[ ! -f "$1" ]]; then + echo "ASSERTION FAILED: expected file to exist: $1" >&2 + return 1 + fi +} + +# assert_dir_exists PATH +assert_dir_exists() { + if [[ ! -d "$1" ]]; then + echo "ASSERTION FAILED: expected directory to exist: $1" >&2 + return 1 + fi +} + +# assert_contains FILE PATTERN +assert_contains() { + if ! grep -q "$2" "$1" 2>/dev/null; then + echo "ASSERTION FAILED: '$2' not found in $1" >&2 + echo "--- file contents ---" >&2 + cat "$1" >&2 + return 1 + fi +} + +# assert_not_contains FILE PATTERN +assert_not_contains() { + if grep -q "$2" "$1" 2>/dev/null; then + echo "ASSERTION FAILED: '$2' unexpectedly found in $1" >&2 + return 1 + fi +} + +# assert_equal ACTUAL EXPECTED +assert_equal() { + if [[ "$1" != "$2" ]]; then + echo "ASSERTION FAILED: expected '$2', got '$1'" >&2 + return 1 + fi +} + +# ── HTML validation helpers ────────────────────────────────────────────────── + +# assert_valid_html FILE +# Validates FILE as a complete HTML5 document using tidy. +# Mustache template markers ({{...}}, {{{...}}}) are substituted with +# structurally valid HTML before linting to avoid false positives from +# template syntax that tidy cannot parse. +# Skips automatically if tidy is not installed. +# tidy exit codes: 0 = clean, 1 = warnings only, 2 = errors. +# This helper fails only on exit code 2 (actual errors). +assert_valid_html() { + local file="$1" + if ! command -v tidy >/dev/null 2>&1; then + skip "tidy not installed (apt install tidy)" + fi + local tmp + tmp=$(mktemp --suffix=.html) + # Replace {{ var }} template markers with valid HTML equivalents so tidy + # sees a structurally correct document. + sed \ + -e 's/{{[^}]*}}/placeholder/g' \ + "$file" > "$tmp" + local tidy_out rc + tidy_out=$(tidy -errors -quiet --show-warnings no -utf8 "$tmp" 2>&1) + rc=$? + rm -f "$tmp" + if [ "$rc" -ge 2 ]; then + echo "HTML VALIDATION FAILED: $file" >&2 + echo "$tidy_out" >&2 + return 1 + fi +} + +# assert_valid_html_fragment FILE +# Wraps FILE in a minimal HTML5 document shell, then validates with tidy. +# Use for HTML fragment files (e.g. rendered chunk index.html output) that +# are not complete documents on their own. +# Skips automatically if tidy is not installed. +assert_valid_html_fragment() { + local file="$1" + if ! command -v tidy >/dev/null 2>&1; then + skip "tidy not installed (apt install tidy)" + fi + local tmp + tmp=$(mktemp --suffix=.html) + { + echo 'fragment' + cat "$file" + echo '' + } > "$tmp" + local tidy_out rc + tidy_out=$(tidy -errors -quiet --show-warnings no -utf8 "$tmp" 2>&1) + rc=$? + rm -f "$tmp" + if [ "$rc" -ge 2 ]; then + echo "HTML VALIDATION FAILED: $file" >&2 + echo "$tidy_out" >&2 + return 1 + fi +} diff --git a/tests/helpers/profiler.bash b/tests/helpers/profiler.bash new file mode 100644 index 0000000..c3d5207 --- /dev/null +++ b/tests/helpers/profiler.bash @@ -0,0 +1,53 @@ +# Profiler implementation +profile() { + # Set default options + local file=profiler.log + + # Open a file descriptor for writing to $file and save it in $tracefd + exec {tracefd}>"$file" + # Send trace output to $tracefd + export BASH_XTRACEFD="$tracefd" + # Print microsecond time in trace output + export PS4='+ $EPOCHREALTIME ' + # Enable tracing, run script, and disable tracing + set -x + bash -x -- "$@" + set +x + # Un-redirect the trace output. This also closes the file descriptor. + unset BASH_XTRACEFD + export -n BASH_XTRACEFD PS4 + + # Remove "source" line from output and change last line to only include the + # timestamp with no name. + sed -i -e 1d -e '$s/\(+\+ [0-9\.]\+\) .*$/\1/' "$file" +} + +analyze() { + # Set defaults + local file=profiler.log + local -a sortcmd=(cat) tablecmd=(cat) + + # Declare variables + local timestamp nestlvl cmd next_timestamp next_nestlvl next_cmd duration + # Open file as a file descriptor so we can re-use the same stream + exec {fd}<"$file"; + # Read first line + read -r nestlvl timestamp cmd <&"$fd" + # Process each line + while read -r next_nestlvl next_timestamp next_cmd + do + duration="$(echo "scale=6; $next_timestamp" - "$timestamp" | bc)" + # Prepend leading zero + if [ "${duration:0:1}" = . ] + then + duration="0$duration" + fi + echo "$duration" "$nestlvl" "$cmd" + + timestamp="$next_timestamp" + nestlvl="$next_nestlvl" + cmd="$next_cmd" + done <&"$fd" | "${sortcmd[@]}" | "${tablecmd[@]}" + # Close file descriptor + exec {fd}<&- +} diff --git a/tests/integration/cli.bats b/tests/integration/cli.bats new file mode 100644 index 0000000..5ffc55c --- /dev/null +++ b/tests/integration/cli.bats @@ -0,0 +1,215 @@ +#!/usr/bin/env bats +# tests/integration/cli.bats +# Tests for trbldoc.sh command-line option parsing. +# +# Each test runs the full script (with md2html stubs) and asserts that +# the correct sources are scanned and outputs produced, based on the options +# passed. + +load "../helpers/common.bash" +load "../helpers/profiler.bash" + +FIXTURES="$BATS_TEST_DIRNAME/../fixtures" +_REPO_ROOT="${REPO_ROOT:-$(cd "$BATS_TEST_DIRNAME/../.." && pwd)}" +TRBLDOC="$_REPO_ROOT/trbldoc.sh" + +setup() { + setup_workspace + CACHE=".trblcache" + NS="$CACHE/namespace" +} + +teardown() { + teardown_workspace +} + +# ── helpers ────────────────────────────────────────────────────────────────── + +run_pipeline() { + bash "$TRBLDOC" "$@" # 2>/dev/null + #profile "$TRBLDOC" "$@" 2>/dev/null + #analyze >> profile.txt +} + +stage_fixture() { + local src="$1" + local dest="$2" + mkdir -p "$(dirname "$dest")" + cp -r "$src" "$dest" +} + +# ── error cases ────────────────────────────────────────────────────────────── + +@test "cli: no arguments exits non-zero with an error message" { + run bash "$TRBLDOC" 2>&1 + [ "$status" -ne 0 ] + [[ "$output" == *"trbldoc:"* ]] +} + +@test "cli: unknown option exits non-zero" { + run bash "$TRBLDOC" -z 2>&1 + [ "$status" -ne 0 ] +} + +@test "cli: option missing its required argument exits non-zero" { + run bash "$TRBLDOC" -s 2>&1 + [ "$status" -ne 0 ] +} + +# ── -s (source folder) ─────────────────────────────────────────────────────── + +@test "cli: -s flag specifies the source folder to scan" { + stage_fixture "$FIXTURES/c/simple.c" "src/simple.c" + run_pipeline -s src + assert_dir_exists "$NS/test/simple" +} + +@test "cli: multiple -s flags scan all specified folders" { + stage_fixture "$FIXTURES/c/simple.c" "src1/simple.c" + stage_fixture "$FIXTURES/c/multi_chunk.c" "src2/multi_chunk.c" + run_pipeline -s src1 -s src2 + assert_dir_exists "$NS/test/simple" + assert_dir_exists "$NS/test/alpha" +} + +# ── -o (output directory) ──────────────────────────────────────────────────── + +@test "cli: -o flag creates the specified output directory" { + stage_fixture "$FIXTURES/c/simple.c" "src/simple.c" + run_pipeline -s src -o my_output + assert_dir_exists "my_output" + assert_file_exists "my_output/test/simple/index.html" +} + +@test "cli: without -o the default output directory .trblcache/built is created" { + stage_fixture "$FIXTURES/c/simple.c" "src/simple.c" + run_pipeline -s src + assert_dir_exists "$CACHE/built" +} + +# ── -f (custom file extension) ─────────────────────────────────────────────── + +@test "cli: -f flag registers a custom file extension for scanning" { + mkdir -p src + # Create a .myext file with C-style doc comments. + # The -f option takes ext;start_regex;end_regex (AWK patterns without slashes). + cat > src/custom.myext <<'SRCEOF' +/* md +@name test/custom-ext +Hello from a custom extension. +*/ +int noop() {} +SRCEOF + run_pipeline -s src -f 'myext;\/\*;\*\/' + assert_dir_exists "$NS/test/custom-ext" +} + +@test "cli: -f registered extension produces correct chunk content" { + mkdir -p src + cat > src/custom.myext <<'SRCEOF' +/* md +@name test/custom-content +Content from custom ext. +*/ +int noop() {} +SRCEOF + run_pipeline -s src -f 'myext;\/\*;\*\/' + assert_contains "$NS/test/custom-content/index.html" "Content from custom ext" +} + +# ── TRBLDOC_MD (markdown renderer override) ────────────────────────────────── + +@test "cli: TRBLDOC_MD overrides the markdown renderer for .md files" { + mkdir -p src + cat > src/doc.md <<'EOF' +@name test/md-override +# Heading +Some content. +EOF + cat > custom_md.sh <<'EOF' +#!/usr/bin/env bash +# Stub: emit a recognisable marker instead of real HTML. +cat "$@" > /dev/null +echo "

custom renderer was here

" +EOF + chmod +x custom_md.sh + TRBLDOC_MD="./custom_md.sh" run_pipeline -s src + assert_contains "$NS/test/md-override/index.html" "custom-md-renderer" +} + +@test "cli: TRBLDOC_MD override is not used when unset (default md2html stub runs)" { + stage_fixture "$FIXTURES/md/simple.md" "src/simple.md" + run_pipeline -s src + # The default stub wraps content in class=stub-md; confirm it ran. + assert_contains "$NS/test/md-simple/index.html" "stub-md" +} + +# ── built-in site assembly ─────────────────────────────────────────────────── + +@test "cli: built output is assembled by default without external zod" { + mkdir -p src + cat > src/simple.c <<'EOF' +/* md +@name test/simple +Hello from a C comment. +*/ +int main() { return 0; } +EOF + run_pipeline -s src + assert_file_exists "$CACHE/built/test/simple/index.html" + assert_contains "$CACHE/built/test/simple/index.html" "" + assert_contains "$CACHE/built/test/simple/index.html" "Hello from a C comment" + assert_not_contains "$CACHE/built/test/simple/index.html" "{{{yield}}}" +} + +@test "cli: default main.layout includes stylesheet and excludes highlightjs mermaid and mathjax" { + stage_fixture "$FIXTURES/c/simple.c" "src/simple.c" + run_pipeline -s src + assert_contains "$NS/main.layout" '' + assert_not_contains "$NS/main.layout" "highlight.min.js" + assert_not_contains "$NS/main.layout" "highlightAll()" + assert_not_contains "$NS/main.layout" "mermaid.min.js" + assert_not_contains "$NS/main.layout" "mermaid.initialize" + assert_not_contains "$NS/main.layout" "mathjax" +} + +@test "cli: -l uses the provided main.layout template" { + stage_fixture "$FIXTURES/c/simple.c" "src/simple.c" + cat > alt.layout <<'EOF' + + +{{ title }} + +
ALT {{ breadcrumb }}
+ +
{{ yield }}
+ + +EOF + run_pipeline -s src -l alt.layout + assert_contains "$NS/main.layout" "ALT {{ breadcrumb }}" + assert_not_contains "$NS/main.layout" "Generated by trbldoc" +} + +@test "cli: -l fails when layout template file does not exist" { + stage_fixture "$FIXTURES/c/simple.c" "src/simple.c" + run bash "$TRBLDOC" -s src -l does-not-exist.layout 2>&1 + [ "$status" -ne 0 ] + [[ "$output" == *"layout template not found"* ]] +} + +@test "cli: files without a registered extension are not scanned" { + mkdir -p src + # .xyz has no registered comment form; no output namespace should appear. + cat > src/ignored.xyz <<'SRCEOF' +/* md +@name test/should-not-exist +This should not be processed. +*/ +SRCEOF + run_pipeline -s src + if [[ -d "$NS/test/should-not-exist" ]]; then + echo "FAILED: .xyz file was unexpectedly processed" >&2 + return 1 + fi +} diff --git a/tests/integration/pipeline.bats b/tests/integration/pipeline.bats new file mode 100644 index 0000000..2f43304 --- /dev/null +++ b/tests/integration/pipeline.bats @@ -0,0 +1,842 @@ +#!/usr/bin/env bats +# tests/integration/pipeline.bats +# End-to-end tests for trbldoc.sh. +# +# External markdown rendering is stubbed via helpers/common.bash. +# Tests inspect both .trblcache/namespace intermediate output and the assembled +# .trblcache/built pages produced by trbldoc itself. +# +# Tests marked `skip` document known bugs from todo.md. Remove `skip` after +# the corresponding bug is fixed. + +load "../helpers/common.bash" + +FIXTURES="$BATS_TEST_DIRNAME/../fixtures" +# REPO_ROOT is exported by run_tests.sh; fall back to relative path when bats +# is invoked directly on this file. +_REPO_ROOT="${REPO_ROOT:-$(cd "$BATS_TEST_DIRNAME/../.." && pwd)}" +TRBLDOC="$_REPO_ROOT/trbldoc.sh" + +setup() { + setup_workspace + CACHE=".trblcache" + NS="$CACHE/namespace" +} + +teardown() { + teardown_workspace +} + +# ── helpers ───────────────────────────────────────────────────────────────── + +# Copy a fixture file into a temporary source tree and run the pipeline. +# Usage: run_pipeline SRC_DIR [additional source dirs...] +run_pipeline() { + bash "$TRBLDOC" "$@" +} + +# Copy a fixture into a local source dir for a clean run. +stage_fixture() { + local src="$1" # path to fixture file or dir + local dest="$2" # destination relative path in tmp workspace + mkdir -p "$(dirname "$dest")" + cp -r "$src" "$dest" +} + +install_busybox_tool_wrapper() { + local tool="$1" + local busybox_bin="$2" + cat > "$STUB_BIN/$tool" <> "\$TRBLDOC_TEST_TOOL_LOG" +fi +exec "$busybox_bin" $tool "\$@" +EOF + chmod +x "$STUB_BIN/$tool" +} + +# ── cache structure ────────────────────────────────────────────────────────── + +@test "pipeline: .trblcache directory is created on run" { + stage_fixture "$FIXTURES/c/simple.c" "src/simple.c" +run_pipeline -s src + assert_dir_exists ".trblcache" +} + +@test "pipeline: namespace subdirectory is created on run" { + stage_fixture "$FIXTURES/c/simple.c" "src/simple.c" + run_pipeline -s src + assert_dir_exists ".trblcache/namespace" +} + +@test "pipeline: chunks subdirectory is created on run" { + stage_fixture "$FIXTURES/c/simple.c" "src/simple.c" + run_pipeline -s src + assert_dir_exists ".trblcache/chunks" +} + +@test "pipeline: global.meta is created" { + stage_fixture "$FIXTURES/c/simple.c" "src/simple.c" + run_pipeline -s src + assert_file_exists "$NS/global.meta" +} + +@test "pipeline: main.layout is created" { + stage_fixture "$FIXTURES/c/simple.c" "src/simple.c" + run_pipeline -s src + assert_file_exists "$NS/main.layout" +} + +@test "pipeline: main.layout passes HTML validation" { + stage_fixture "$FIXTURES/c/simple.c" "src/simple.c" + run_pipeline -s src + assert_valid_html "$NS/main.layout" +} + +@test "pipeline: main.layout has well-formed HTML structure" { + stage_fixture "$FIXTURES/c/simple.c" "src/simple.c" + run_pipeline -s src + local layout="$NS/main.layout" + # must appear before + local html_line head_line body_line footer_line close_body_line + html_line=$(grep -n '' "$layout" | head -1 | cut -d: -f1) + # before + [ "$html_line" -lt "$head_line" ] + # exactly one + local head_count + head_count=$(grep -c '' "$layout") + assert_equal "$head_count" "1" + #