#!/usr/bin/env bash set -euo pipefail ROOT_DIR="$(cd "$(dirname "$0")/../.." && pwd)" WORKSPACE_ROOT="$(cd "$ROOT_DIR/.." && pwd)" LINUXCNC_DIR="${LINUXCNC_DIR:-$WORKSPACE_ROOT/linuxcnc}" NC_DIR="$LINUXCNC_DIR/nc_files" BUILD_DIR="${NC_FILES_BUILD_DIR:-$ROOT_DIR/build/native/nc-files}" RS274="${RS274:-$LINUXCNC_DIR/bin/rs274}" TIMEOUT_SECONDS="${NC_FILES_TIMEOUT_SECONDS:-30}" ONLY_FILTER="" SCAN_ALL=0 while [[ $# -gt 0 ]]; do case "$1" in --all) SCAN_ALL=1 shift ;; --only) [[ $# -ge 2 ]] || { echo "missing value for --only" >&2; exit 2; } ONLY_FILTER="$2" shift 2 ;; --help|-h) cat <&2 exit 2 ;; esac done if [[ ! -x "$RS274" ]]; then echo "missing executable rs274: $RS274" >&2 exit 1 fi if [[ ! -d "$NC_DIR" ]]; then echo "missing LinuxCNC nc_files directory: $NC_DIR" >&2 exit 1 fi export LD_LIBRARY_PATH="$LINUXCNC_DIR/lib${LD_LIBRARY_PATH:+:$LD_LIBRARY_PATH}" export PYTHONPATH="$LINUXCNC_DIR/lib/python${PYTHONPATH:+:$PYTHONPATH}" mkdir -p "$BUILD_DIR"/{logs,out,wrapped,params} SUMMARY_FILE="$BUILD_DIR/summary.tsv" INI_FILE="$BUILD_DIR/test-rs274.ini" TOOL_TABLE="$BUILD_DIR/test-tools.tbl" PARAMETER_TEMPLATE="$BUILD_DIR/test.var.template" printf 'path\tclass\tstatus\texpected_failure\texit_code\tseconds\toutput_lines\tstderr_summary\n' > "$SUMMARY_FILE" relpath() { local path="$1" printf '%s\n' "${path#"$NC_DIR"/}" } safe_name() { local rel="$1" rel="${rel//\//_}" rel="${rel// /_}" rel="${rel//#/_}" rel="${rel//=/}" printf '%s\n' "$rel" } write_fixture_config() { { printf '[RS274NGC]\n' printf 'PARAMETER_FILE = test.var\n' printf 'SUBROUTINE_PATH = %s\n' "$( find "$NC_DIR" -type d | sort | paste -sd ':' - )" printf 'OWORD_NARGS = 1\n' printf 'FEATURES = 30\n' printf '\n[DISPLAY]\n' printf 'PROGRAM_PREFIX = %s\n' "$NC_DIR" printf '\n[TRAJ]\n' printf 'COORDINATES = X Y Z A B C U V W\n' printf '\n[EMCIO]\n' printf 'TOOL_TABLE = %s\n' "$TOOL_TABLE" } > "$INI_FILE" : > "$TOOL_TABLE" local tool for tool in $(seq 1 200); do printf 'T%s P%s D0.125000 Z+0.000000 ; generated nc_files test tool\n' "$tool" "$tool" >> "$TOOL_TABLE" done { printf '5161 0.0\n' printf '5162 0.0\n' printf '5163 0.0\n' printf '5220 1\n' printf '5221 0.0\n' printf '5222 0.0\n' printf '5223 0.0\n' printf '5399 0.0\n' } > "$PARAMETER_TEMPLATE" } has_program_end() { local ngc="$1" awk ' BEGIN { found = 0 } /^[[:space:]]*%[[:space:]]*$/ { found = 1 } { line = tolower($0) gsub(/\([^)]*\)/, "", line) if (line ~ /(^|[[:space:]])m0*(2|30)([[:space:]]|$)/) { found = 1 } } END { exit(found ? 0 : 1) } ' "$ngc" } is_default_basic_path() { local rel="$1" case "$rel" in */*) case "$rel" in gladevcp_lib/*.ngc|macros/lathe/*.ngc|macros/mill/*.ngc|ngcgui_lib/*.ngc) return 0 ;; esac return 1 ;; *.ngc) return 0 ;; esac return 1 } classify_program() { local rel="$1" local ngc="$2" case "$rel" in macros/*|ngcgui_lib/*|gladevcp_lib/*|remap-subroutines/*|remap_lib/*|probe/*) printf 'macro_load\n' return 0 ;; esac if has_program_end "$ngc"; then printf 'main\n' else printf 'macro_load\n' fi } prepare_input() { local class="$1" local ngc="$2" local wrapped="$3" if [[ "$class" == "main" ]]; then printf '%s\n' "$ngc" return 0 fi cp "$ngc" "$wrapped" printf '\nM2\n' >> "$wrapped" printf '%s\n' "$wrapped" } known_expected_failure() { local rel="$1" local combined="$2" case "$rel" in cone.ngc) [[ "$combined" == *"Bad character 'w' used"* ]] && { printf 'five-axis-W-machine-context\n'; return 0; } ;; g76.ngc|lathe-g76.ngc) [[ "$combined" == *"Length of cutter compensation entry move is not greater than the tool radius"* ]] && { printf 'lathe-cutter-comp-tool-context\n'; return 0; } ;; lathe_g70_71_demo.ngc) [[ "$combined" == *"Arc move in concave corner cannot be reached by the tool without gouging"* ]] && { printf 'lathe-profile-tool-context\n'; return 0; } ;; nestedcall.ngc) [[ "$combined" == *"Unexpected character after O-word"* ]] && { printf 'upstream-oword-callsub-syntax-edge\n'; return 0; } ;; nurbs/G5/Curva_Stella\ #1=1_G5.ngc) [[ "$combined" == *"Unknown g code used"* && "$combined" == *"G2.2"* ]] && { printf 'nurbs-g22-entrypoint-edge\n'; return 0; } ;; probe*.ngc|probe/*|smartprobe.ngc|tool-length-probe.ngc|touchoff.ngc) [[ "$combined" == *"probe"* || "$combined" == *"G38"* ]] && { printf 'probe-runtime-context\n'; return 0; } ;; plasmac/*|plasmatest.ngc) [[ "$combined" == *"Unknown m code"* || "$combined" == *"Named parameter"* ]] && { printf 'plasmac-runtime-context\n'; return 0; } ;; esac return 1 } summarize_text() { local file="$1" if [[ ! -f "$file" ]]; then return 0 fi tr '\n\t' ' ' < "$file" | sed -E 's/[[:space:]]+/ /g; s/^ //; s/ $//' | cut -c 1-260 } write_fixture_config total=0 pass=0 fail=0 timeout_count=0 expected_fail=0 unexpected_fail=0 while IFS= read -r ngc; do rel="$(relpath "$ngc")" if [[ "$SCAN_ALL" -eq 0 ]] && ! is_default_basic_path "$rel"; then continue fi if [[ -n "$ONLY_FILTER" && "$rel" != *"$ONLY_FILTER"* ]]; then continue fi total=$((total + 1)) class="$(classify_program "$rel" "$ngc")" safe="$(safe_name "$rel")" wrapped="$BUILD_DIR/wrapped/$safe" input="$(prepare_input "$class" "$ngc" "$wrapped")" out_file="$BUILD_DIR/out/$safe.out" stdout_file="$BUILD_DIR/logs/$safe.stdout" stderr_file="$BUILD_DIR/logs/$safe.stderr" param_file="$BUILD_DIR/params/$safe.var" cp "$PARAMETER_TEMPLATE" "$param_file" start_time="$(date +%s)" set +e ( cd "$WORKSPACE_ROOT" INI_FILE_NAME="$INI_FILE" timeout "$TIMEOUT_SECONDS" "$RS274" \ -g \ -i "$INI_FILE" \ -t "$TOOL_TABLE" \ -v "$param_file" \ "$input" \ "$out_file" ) >"$stdout_file" 2>"$stderr_file" rc=$? set -e end_time="$(date +%s)" seconds=$((end_time - start_time)) status="FAIL" if [[ "$rc" -eq 0 ]]; then status="PASS" pass=$((pass + 1)) elif [[ "$rc" -eq 124 || "$rc" -eq 137 ]]; then status="TIMEOUT" timeout_count=$((timeout_count + 1)) fail=$((fail + 1)) else fail=$((fail + 1)) fi combined="$(cat "$stdout_file" "$stderr_file" 2>/dev/null || true)" expected="" if [[ "$status" != "PASS" ]]; then expected="$(known_expected_failure "$rel" "$combined" || true)" if [[ -n "$expected" ]]; then expected_fail=$((expected_fail + 1)) else unexpected_fail=$((unexpected_fail + 1)) fi fi output_lines=0 [[ -f "$out_file" ]] && output_lines="$(wc -l < "$out_file" | tr -d '[:space:]')" stderr_summary="$(summarize_text "$stderr_file")" printf '%s\t%s\t%s\t%s\t%s\t%s\t%s\t%s\n' \ "$rel" \ "$class" \ "$status" \ "$expected" \ "$rc" \ "$seconds" \ "$output_lines" \ "$stderr_summary" >> "$SUMMARY_FILE" done < <(find "$NC_DIR" -type f -name '*.ngc' | sort) { printf 'nc_files harness summary\n' printf 'summary: %s\n' "$SUMMARY_FILE" printf 'suite: %s\n' "$([[ "$SCAN_ALL" -eq 1 ]] && printf all || printf basic)" printf 'total: %s\n' "$total" printf 'pass: %s\n' "$pass" printf 'fail: %s\n' "$fail" printf 'timeout: %s\n' "$timeout_count" printf 'expected_fail: %s\n' "$expected_fail" printf 'unexpected_fail: %s\n' "$unexpected_fail" } | tee "$BUILD_DIR/summary.txt" if [[ "$unexpected_fail" -ne 0 ]]; then echo "unexpected nc_files failures:" >&2 awk -F '\t' 'NR > 1 && $3 != "PASS" && $4 == "" { print $1 "\t" $3 "\t" $8 }' "$SUMMARY_FILE" >&2 exit 1 fi