diff --git a/CMakeLists.txt b/CMakeLists.txt index 7de9776..bf82d92 100644 --- a/CMakeLists.txt +++ b/CMakeLists.txt @@ -92,4 +92,7 @@ if(BUILD_TESTING) target_include_directories(tux-dock-stop-sequence-tests PRIVATE src) target_compile_options(tux-dock-stop-sequence-tests PRIVATE -Wall -Wextra -Wpedantic) add_test(NAME tux-dock-stop-sequence-tests COMMAND tux-dock-stop-sequence-tests) + + add_test(NAME tux-dock-docker-integration-tests + COMMAND sh ${CMAKE_CURRENT_SOURCE_DIR}/tests/test_docker_integration.sh) endif() diff --git a/README.md b/README.md index 1aba8a1..051c28e 100644 --- a/README.md +++ b/README.md @@ -47,10 +47,15 @@ sudo ./build/tux-dock # Build without running tests ./compile.sh --no-test + +# Run tests and serve an HTML 3.2 report on port 8095 +./compile.sh --web-test-view + +# Override the report server port +./compile.sh --web-test-view 9000 ``` -Prefer a prebuilt binary? CI artifacts are published at: -https://mentalnet.xyz/forgejo/markmental/tuxdock/actions +The web report is generated under `/tmp` and includes exact CTest output, test summaries, Docker integration output, and a text rendition of the TUI flow. It requires `nc` or `netcat`; press `Ctrl-C` to stop the server. The default port is `8095`; pass a port after `--web-test-view` to override it. `--web-test-view --no-test` is invalid. Normal test runs include a Docker integration test using `debian:forky`, so Docker must be available. --- @@ -161,7 +166,7 @@ ctest --test-dir build --output-on-failure - Version: `0.1-beta` - Created by: `markmental` - GitHub: https://github.com/MARKMENTAL/tuxdock -- Forgejo: https://mentalnet.xyz/forgejo/markmental/tuxdock +- Forgejo: https://mentalnet.xyz/forgejo-v2/markmental/tuxdock --- diff --git a/compile.sh b/compile.sh index ae041fa..29ab3a7 100755 --- a/compile.sh +++ b/compile.sh @@ -2,14 +2,37 @@ set -eu -if [ "$#" -gt 1 ] || { [ "$#" -eq 1 ] && [ "$1" != "--no-test" ]; }; then - printf '%s\n' "Usage: $0 [--no-test]" >&2 +run_tests=1 +web_view=0 +web_port=8095 + +while [ "$#" -gt 0 ]; do + argument=$1 + shift + case "$argument" in + --no-test) run_tests=0 ;; + --web-test-view) + web_view=1 + if [ "$#" -gt 0 ] && printf '%s' "$1" | grep -Eq '^[0-9]+$'; then + web_port=$1 + shift + fi + ;; + *) + printf '%s\n' "Usage: $0 [--no-test] [--web-test-view [port]]" >&2 + exit 2 + ;; + esac +done + +if [ "$web_port" -lt 1 ] || [ "$web_port" -gt 65535 ]; then + printf '%s\n' "Web test view port must be between 1 and 65535" >&2 exit 2 fi -run_tests=1 -if [ "$#" -eq 1 ]; then - run_tests=0 +if [ "$web_view" -eq 1 ] && [ "$run_tests" -eq 0 ]; then + printf '%s\n' "--web-test-view cannot be combined with --no-test" >&2 + exit 2 fi if [ "$run_tests" -eq 1 ]; then @@ -21,7 +44,106 @@ fi cmake --build build -j if [ "$run_tests" -eq 1 ]; then - ctest --test-dir build --output-on-failure + report_dir=$(mktemp -d /tmp/tux-dock-test.XXXXXX) + test_output="$report_dir/ctest-output.txt" + test_status=0 + ctest --test-dir build --output-on-failure >"$test_output" 2>&1 || test_status=$? + + if [ "$web_view" -eq 1 ]; then + report="$report_dir/report.html" + escaped_output=$(mktemp "$report_dir/output.XXXXXX") + sed 's/&/\&/g; s//\>/g' "$test_output" >"$escaped_output" + total=$(awk '/tests passed, [0-9]+ tests failed out of [0-9]+/ { print $(NF); exit }' "$test_output" 2>/dev/null || true) + failed=$(awk '/tests passed, [0-9]+ tests failed out of [0-9]+/ { print $4; exit }' "$test_output" 2>/dev/null || true) + total=${total:-unknown} + passed=${passed:-0} + failed=${failed:-0} + if [ "$total" != unknown ]; then + passed=$((total - failed)) + fi + duration=$(awk '/Total Test time/ { print $(NF - 1); exit }' "$test_output" 2>/dev/null || true) + duration=${duration:-unknown} + command_line='ctest --test-dir build --output-on-failure' + test_rows=$(mktemp "$report_dir/test-rows.XXXXXX") + awk ' + match($0, /^[[:space:]]*([0-9]+)\/([0-9]+) Test #[0-9]+: ([^ ]+)[[:space:]]+\.+[[:space:]]+(Passed|Failed|Skipped)[[:space:]]+([0-9.]+) sec/, m) { + printf "%s%s%s%s sec\n", m[1], m[3], m[4], m[5] + } + ' "$test_output" >"$test_rows" + integration_output=$(mktemp "$report_dir/integration-output.XXXXXX") + test_log="build/Testing/Temporary/LastTest.log" + if [ -f "$test_log" ]; then + awk ' + /^[0-9]+\/[0-9]+ Testing: tux-dock-docker-integration-tests$/ { capture=1 } + capture { print } + /^Test Passed\.$/ && capture { end=1 } + end && /time elapsed:/ { print; exit } + ' "$test_log" >"$integration_output" + else + printf '%s\n' 'Docker integration transcript unavailable: LastTest.log was not found.' >"$integration_output" + fi + escaped_integration=$(mktemp "$report_dir/integration-escaped.XXXXXX") + sed 's/&/\&/g; s//\>/g' "$integration_output" >"$escaped_integration" + raw_output=$(mktemp "$report_dir/raw-output.XXXXXX") + awk '/\[TEST OUTPUT BEGIN\]/{capture=1} capture{print} /\[TEST RESULT\]/{capture=0}' "$integration_output" >"$raw_output" + escaped_raw=$(mktemp "$report_dir/raw-escaped.XXXXXX") + sed 's/&/\&/g; s//\>/g' "$raw_output" >"$escaped_raw" + tui_output=$(mktemp "$report_dir/tui-output.XXXXXX") + awk '!/\[TEST OUTPUT BEGIN\]/ && !/\[TEST OUTPUT END\]/ && !/\[TEST RESULT\]/ { print }' "$integration_output" >"$tui_output" + escaped_tui=$(mktemp "$report_dir/tui-escaped.XXXXXX") + sed 's/&/\&/g; s//\>/g' "$tui_output" >"$escaped_tui" + { + printf '%s\n' '' + printf '%s\n' 'Tux-Dock Test Report' + printf '%s\n' '

Tux-Dock Test Report

' + printf '

Generated: %s

\n' "$(date)" + printf '%s\n' '

Summary

' + printf '\n' "$total" + printf '\n' "$passed" + printf '\n' "$failed" "$([ "$failed" -gt 0 ] 2>/dev/null && printf 160 || printf 1)" + printf '
MetricValueGraph
Total tests%s
 
Passed%s
 
Failed%s
 
Duration%s seconds 
\n' "$duration" + printf '%s\n' '

Individual Tests

' + cat "$test_rows" + printf '%s\n' '
#TestStatusDuration
' + printf '%s\n' '

Exact Test Run

'
+            printf '%s\n' "$command_line"
+            printf '%s\n' '

Test Output

'
+            cat "$escaped_output"
+            printf '%s\n' '

Docker Integration Raw Output

'
+            cat "$escaped_raw"
+            printf '%s\n' '

TUI Transcript

'
+            cat "$escaped_tui"
+            printf '%s\n' '
' + } >"$report" + + response="$report_dir/http-response.txt" + report_size=$(wc -c <"$report" | tr -d ' ') + { + printf 'HTTP/1.1 200 OK\r\n' + printf 'Content-Type: text/html; charset=utf-8\r\n' + printf 'Content-Length: %s\r\n' "$report_size" + printf 'Connection: close\r\n' + printf '\r\n' + cat "$report" + } >"$response" + + if command -v nc >/dev/null 2>&1; then + nc_command=nc + elif command -v netcat >/dev/null 2>&1; then + nc_command=netcat + else + printf '%s\n' "Web report generated at $report, but nc/netcat is unavailable." >&2 + exit "$test_status" + fi + + printf '%s\n' "Test report: http://0.0.0.0:$web_port/" + printf '%s\n' "Report file: $report" + printf '%s\n' "Press Ctrl-C to stop the server." + while :; do + "$nc_command" -l -N -s 0.0.0.0 -p "$web_port" <"$response" + done + fi + exit "$test_status" fi printf '%s\n' "tux-dock successfully compiled!" diff --git a/tests/test_docker_integration.sh b/tests/test_docker_integration.sh new file mode 100755 index 0000000..60a2aba --- /dev/null +++ b/tests/test_docker_integration.sh @@ -0,0 +1,76 @@ +#!/bin/sh + +set -eu + +image=${TUX_DOCK_TEST_IMAGE:-debian:forky} +name="tux-dock-test-$$-$(date +%s)" +cleanup_status=0 + +run_raw() { + command_name=$1 + shift + started=$(date +%s) + printf '%s\n' "[TEST OUTPUT BEGIN] $command_name" + set +e + "$@" 2>&1 + command_status=$? + set -e + elapsed=$(( $(date +%s) - started )) + printf '%s\n' "[TEST OUTPUT END] $command_name" + printf '%s\n' "[TEST RESULT] command=$command_name status=$command_status elapsed=${elapsed}s" + return "$command_status" +} + +cleanup() { + if docker container inspect "$name" >/dev/null 2>&1; then + printf '%s\n' "[TUI] Cleanup: Remove Container" + printf '%s\n' "[TUI] Busy |" + if ! docker rm -f "$name"; then + cleanup_status=1 + fi + printf '%s\n' "[TUI] Complete: Container removed" + fi +} +trap cleanup EXIT INT TERM + +printf '%s\n' "[TUI] Actions > Run/Create Interactive Container" +printf '%s\n' "[TUI] Busy |" +printf '%s\n' "[TUI] docker run --name $name $image sh -c ''" + +if ! docker image inspect "$image" >/dev/null 2>&1; then + printf '%s\n' "[TUI] Pull Docker Image: $image" + run_raw "docker pull $image" docker pull "$image" +else + printf '%s\n' "[TUI] Image available: $image" +fi + +run_in_container() { + command_name=$1 + shift + printf '%s\n' "[TUI] Run command: $command_name" + printf '%s\n' "[TUI] Busy |" + run_raw "$command_name" docker exec "$name" "$@" + printf '%s\n' "[TUI] Complete: $command_name succeeded" +} + +run_raw "docker run -d --name $name $image sleep 300" docker run -d --name "$name" "$image" sleep 300 +printf '%s\n' "[TUI] Complete: Container created" +run_in_container "ls /" ls / +run_in_container "cat /etc/os-release" cat /etc/os-release +run_in_container "apt-get update" apt-get update +run_in_container "bash --version" bash --version + +printf '%s\n' "[TUI] Remove Container: $name" +run_raw "docker rm -f $name" docker rm -f "$name" +printf '%s\n' "[TUI] Complete: Container removed" + +printf '%s\n' "[TUI] Verify removal: docker container inspect $name" +if run_raw "docker container inspect $name" docker container inspect "$name"; then + printf '%s\n' "Container still exists after removal" >&2 + exit 1 +else + printf '%s\n' "[TUI] Complete: Removal verified" +fi + +trap - EXIT INT TERM +exit "$cleanup_status"