#!/bin/bash # Initialize default values and export variables for later use declare -a UNITS_DIRS # Changed to an array to allow multiple directories export passed_along_to_mettalog export METTALOG_MAX_TIME export all_test_args="${@}" run_tests_auto_reply="" generate_report_auto_reply="" #METTALOG_OUTPUT="examples" METTALOG_OUTPUT="tests_output/testrun_$(date +%Y%m%d_%H%M%S)" passed_along_to_mettalog=() METTALOG_MAX_TIME=75 clean=0 # 0 means don't clean, 1 means do clean fresh=0 if_failures=0 if_regressions=0 show_help=0 export RUST_METTA_MAX_TIME=60 EXTRA_FIND_ARGS=" ! -path '*/.*' ! -path '*~*' " EXTRA_GREP_ARGS="" dry_run=0 debug_this_script=1 # Filters to exclude names starting with a dot, containing a tilde, # starting with an underscore, and for files, excluding those not ending with 'metta' DIR_FILTER=("^\." ".*~.*" "^_") FILE_FILTER=("^\." ".*~.*" "^_" "!.*\.metta$") # ANSI escape codes for colors YELLOW='\033[1;33m' BLUE='\033[0;34m' RED='\033[0;31m' GREEN='\033[0;32m' BOLD='\033[1m' # ANSI escape code to reset color NC='\033[0m' # No Color do_DEBUG() { # Calculate the screen width and 74% of it local screen_width=$(tput cols) local threshold=$((screen_width * 74 / 100)) # Construct the debug message # Construct the debug message local msg="; ${YELLOW}DEBUG${NC} $*" # Calculate the length of the debug message local msg_length=${#msg} if [ "$msg_length" -gt "$threshold" ]; then # If the message is longer than 74% of the screen width, # print a newline before and after the message echo >&2 echo -e "$msg" >&2 echo >&2 else # If the message is not longer than 74% of the screen width, print it as usual echo -e "$msg" >&2 fi } DEBUG() { if [ "$debug_this_script" == "true" ]; then do_DEBUG "$@" else if [ "$dry_run" -eq 1 ]; then do_DEBUG "$@" fi fi } IF_REALLY_DO() { if [ "$dry_run" -eq 1 ]; then do_DEBUG "${BLUE}Dry Run:${NC} $*" else DEBUG "${GREEN}Doing:${NC} $*" eval "$@" return $? #|| { DEBUG "${RED}Error executing command:${NC} $*"; return 1; } fi } # Initialize arrays to hold the files to be tested, excluded, and containing '!' declare -a files_to_test declare -a excluded_files declare -a excluded_dirs declare -a files_with_exclamation # Function to check if the filename matches the filter matches_filter() { local filename="$(basename "$1")" local -n filter=$2 for pattern in "${filter[@]}"; do if [[ "$pattern" == "!"* ]]; then pattern="${pattern#!}" # Remove the '!' from the start if [[ ! "$filename" =~ $pattern ]]; then return 0 fi elif [[ "$filename" =~ $pattern ]]; then return 0 fi done return 1 } # Main function to handle files and directories based on patterns find_test_masks() { shopt -s globstar nullglob # Enable recursive glob patterns for pattern_or_path in "$@"; do # Iterate over all arguments for file in $pattern_or_path; do if [ -f "$file" ]; then if matches_filter "$file" FILE_FILTER; then excluded_files+=("$file") else files_to_test+=("$file") # Check if file contains lines starting with "!" if grep -q '^!' "$file"; then files_with_exclamation+=("$file") fi fi elif [ -d "$file" ]; then if matches_filter "$(basename "$file")" DIR_FILTER; then excluded_dirs+=("$file") else echo "Directory: $file" while IFS= read -r -d $'\0' subfile; do if [ -f "$subfile" ] && matches_filter "$(basename "$subfile")" FILE_FILTER; then excluded_files+=("$subfile") else files_to_test+=("$subfile") # Check if file contains lines starting with "!" if grep -q '^!' "$subfile"; then files_with_exclamation+=("$subfile") fi fi done < <(find "$file" -type f -print0) fi fi done done shopt -u globstar nullglob # Disable recursive glob patterns after use } # Execute the main function with all provided arguments find_test_masks "$@" # Report files containing lines starting with '!' DEBUG "Files containing lines starting with '!':" printf '%s\n' "${files_with_exclamation[@]}" # Report excluded files and directories #DEBUG "Excluded files:" #printf '%s\n' "${excluded_files[@]}" DEBUG "Excluded directories:" printf '%s\n' "${excluded_dirs[@]}" # Function to extract directories and their parents from the files_to_test array # Function to extract all parent directories for each file extract_all_parent_directories() { declare -A directory_map # Use an associative array to avoid duplicate entries for filepath in "${files_to_test[@]}"; do # Extract the directory path of the current file current_dir=$(dirname "$filepath") # Traverse up the directory path until the root or predefined level while [[ "$current_dir" != "." && "$current_dir" != "/" ]]; do # Change "/" to a different level if needed # Add the directory to the map # DEBUG "CD $current_dir" directory_map["$current_dir"]=1 # Move up to the parent directory current_dir=$(dirname "$current_dir") done done # Convert the associative array keys to an indexed array unique_directories=("${!directory_map[@]}") } extract_all_parent_directories # Function to sort directories by their depth sort_directories_by_depth() { # Use array to store sorted directories IFS=$'\n' read -d '' -r -a sorted_directories1 < <(for dir in "${unique_directories[@]}"; do echo "$dir" done | sort ) # Assign sorted directories back to unique_directories unique_directories=("${sorted_directories1[@]}") # Use array to store sorted directories IFS=$'\n' read -d '' -r -a sorted_directories2 < <(for dir in "${unique_directories[@]}"; do echo "$dir" done | awk -F'/' '{print NF-1, $0}' | sort -nr | cut -d' ' -f2-) # Assign sorted directories back to unique_directories unique_directories=("${sorted_directories2[@]}") } sort_directories_by_depth # Check if unique_directories is empty and set UNITS_DIR accordingly if [ ${#unique_directories[@]} -eq 0 ]; then UNITS_DIR="test" else UNITS_DIR="${unique_directories[-1]}" fi DEBUG "UNITS_DIR is set to: $UNITS_DIR" # This script performs various testing operations for MeTTaLog. # It handles command-line arguments to customize its behavior. # Check if the script is being sourced or run directly export IS_SOURCED=$( [[ "${BASH_SOURCE[0]}" != "${0}" ]] && echo 1 || echo 0) export RUST_BACKTRACE=full # Enable full Rust backtrace for debugging # Save the directory one level above where this script resides export SCRIPT_DIR="$( cd "$( dirname "${BASH_SOURCE[0]}" )" && cd .. && pwd )" # Function to add an item to a list if it's not already present add_to_list() { local item="$1" local -n list_ref="$2" if [[ ! " ${list_ref[*]} " =~ " $item " ]]; then list_ref+=("$item") fi } # command-line argument parsing while [ "$#" -gt 0 ]; do case "$1" in -y|--yes) run_tests_auto_reply="y" ;; -n|--no) run_tests_auto_reply="n" ;; --timeout=*) METTALOG_MAX_TIME="${1#*=}" ;; --output=*) METTALOG_OUTPUT="${1#*=}" ;; --report=*) generate_report_auto_reply="${1#*=}" ;; --clean) clean=1; if_failures=0 ;; --regres*) clean=0; if_failures=0; if_regressions=1 ;; --conti*) clean=0; if_failures=0 ;; --failu*) clean=0; if_failures=1 ;; --dry-run) dry_run=1 ;; --test) dry_run=0 ; add_to_list "$1" passed_along_to_mettalog ;; --fresh*) fresh=1 ;; --exclude=*) EXTRA_FIND_ARGS+=" ! -path ${1#*=}"; CANT_HAVE="${1#*=}" ;; --include=*) EXTRA_FIND_ARGS+=" -path ${1#*=}"; MUST_HAVE="${1#*=}" ;; -h|--help) DEBUG "Usage: $0 [options] [directory] [extra args]"; show_help=1; dry_run=1 ;; -*) add_to_list "$1" passed_along_to_mettalog ;; *) if [ -d "$1" ] || [ -f "$1" ]; then UNITS_DIR="$1" else : fi ;; esac shift done if [ "$show_help" -eq 1 ]; then # Help section with detailed usage instructions echo "Options:" echo " -y|--yes Automatically choose 'y' for rerunning all tests" echo " -n|--no Automatically choose 'n'" echo " --fresh Clean up by deleting any .answers files under directory" echo " --clean Clean up by deleting all .html files under directory" echo " --continue Continue running tests (Generating any missing html files)" echo " --failures Rerun unsuccessful tests only" echo " --timeout=SECONDS Specify a timeout value in seconds (current: $METTALOG_MAX_TIME)" echo " --report=(Y/N) Generate a report (if not supplied, will be asked at the end)" echo " --output=DIR Specify the output directory (current: $METTALOG_OUTPUT)" echo " --[in|ex]clud*=PATTERN Include or exclude tests based on pattern" echo " --dry-run Simulate the execution without actually running the tests" echo " -h|--help Display this help message" echo "" echo "Arguments:" echo " directory Directory to find .metta file tests (current: ${SCRIPT_DIR}/${UNITS_DIR})" echo " extra args Optional command-line arguments passed to MeTTaLog" echo "" echo "Examples:" echo " # Run under '${SCRIPT_DIR}/$METTALOG_OUTPUT/baseline_compat/hyperon-pln_metta/sumo' with a 60 second timeout per test" echo " $0 $METTALOG_OUTPUT/ --include \"*sumo/\" --timeout=60" echo "" echo " # Automatically (chooses 'y') cleans up and runs all tests in default '${SCRIPT_DIR}/examples' directory with a 180 second timeout per test" echo " $0 -y --clean --timeout=180" echo "" echo "Note: Arguments can be in any order." fi if [[ "$METTALOG_OUTPUT" != "" ]]; then # Create the directory if it doesn't exist NEW_UNITSDIR="${METTALOG_OUTPUT}/${UNITS_DIR#*/}" IF_REALLY_DO mkdir -p "$NEW_UNITSDIR" DEBUG "$UNITS_DIR -> $NEW_UNITSDIR" #UNITS_DIR="$NEW_UNITSDIR" else METTALOG_OUTPUT="examples" fi # Function to print the list of unique directories # DEBUG "All unique parent directories:" for dir in "${unique_directories[@]}"; do IF_REALLY_DO mkdir -p "${METTALOG_OUTPUT}/$dir" done # Function to delete .html files from the included_files list delete_html_files() { for file in "${files_to_test[@]}"; do if [ -f "${METTALOG_OUTPUT}/${file}.html" ]; then IF_REALLY_DO rm -f "${METTALOG_OUTPUT}/${file}.html" fi done } # Delete HTML files if the clean flag is set if [ $clean -eq 1 ]; then delete_html_files fi # Function to check if a file is in an array file_in_array() { local file="$1" shift local arr=("$@") for elem in "${arr[@]}"; do [[ "$elem" == "$file" ]] && return 0 done return 1 } # Function to check if a file is in an array run_tests() { for file in "${files_to_test[@]}"; do if [ -f "${file}" ]; then process_file "$file" fi done } # Function to run tests function run_tests_units_dir() { # Process test files BASE_DIR="${UNITS_DIR}" DEBUG "Running tests in $BASE_DIR" set +v DEBUG "Finding files with 'test' in their name and apply $EXTRA_FIND_ARGS ..." mapfile -t test_files < <(find "${UNITS_DIR}" $EXTRA_FIND_ARGS -type f -iname "*test*.metta") DEBUG "'Test' files found: ${#test_files[@]}" DEBUG "Finding files containing 'assert' keyword and apply $MUST_HAVE ..." mapfile -t assert_files < <(find "${UNITS_DIR}" $EXTRA_FIND_ARGS -type f -name '*.metta' -print0 | xargs -0 grep -rl 'assert' -- $GREP_ARGS) DEBUG "Assert<*> files found: ${#assert_files[@]} " DEBUG "Finding files containing execution directive (lines starting with '!') and apply $MUST_HAVE ..." mapfile -t has_tests < <(find "${UNITS_DIR}" $EXTRA_FIND_ARGS -type f -name '*.metta' -print0 | xargs -0 grep -rl '^!\([^!]*\)$' -- $GREP_ARGS) DEBUG "Test directive files found: ${#has_tests[@]}" # Remove empty elements from arrays assert_files=("${assert_files[@]}" ) test_files=("${test_files[@]}" ) has_tests=("${has_tests[@]}" ) # Function to filter out elements of array1 that are present in array2 filter_arrays() { local -n array1=$1 local -n array2=$2 local temp_array=() local skip for item1 in "${array1[@]}"; do skip=0 for item2 in "${array2[@]}"; do if [[ "$item1" == "$item2" ]]; then skip=1 break fi done if [[ skip -eq 0 ]]; then temp_array+=("$item1") fi done array1=("${temp_array[@]}") } # Filter out assert_files from has_tests filter_arrays has_tests assert_files # Filter out assert_files from test_files filter_arrays test_files assert_files # Filter out has_tests from test_files filter_arrays test_files has_tests # Remove empty elements from arrays assert_files=("${assert_files[@]}" ) test_files=("${test_files[@]}" ) has_tests=("${has_tests[@]}" ) # for file in "${assert_files[@]}"; do [ -f "${file}" ] && echo assert_files "$file"; done # for file in "${test_files[@]}"; do [ -f "${file}" ] && echo test_files "$file"; done # for file in "${has_tests[@]}"; do [ -f "${file}" ] && echo has_tests "$file"; done # Combine all files and make the collection unique all_files=( "${assert_files[@]}" "${test_files[@]}" "${has_tests[@]}" ) readarray -t unique_files < <(printf "%s\n" "${all_files[@]}" | sort -u) # Process each unique file for file in "${unique_files[@]}"; do if [ -f "${file}" ]; then process_file "$file" fi done } # Main execution block function main() { cd "$SCRIPT_DIR" # Prompt user to rerun all tests if run_tests_auto_reply is not set if [ -z "$run_tests_auto_reply" ]; then read -p "Rerun all tests? (y/N): " -n 1 -r DEBUG "" else REPLY=$run_tests_auto_reply fi # Run tests and generate MeTTaLog report if [[ $REPLY =~ ^[Yy]$ ]]; then # run our selected tests run_tests IF_REALLY_DO generate_final_MeTTaLog else DEBUG "Skipping test run." fi IF_REALLY_DO scripts/pass_fail_totals.sh $METTALOG_OUTPUT # Prompt for code commit and unit report generation if [ -z "$generate_report_auto_reply" ]; then read -p "Commit code and generate unit reports? (y/N): " -n 1 -r DEBUG "" else REPLY=$generate_report_auto_reply fi if [[ $REPLY =~ ^[Yy]$ ]]; then IF_REALLY_DO PreCommitReports else DEBUG "Skipping report generation." fi } # Capture original auto margins setting and terminal size original_automargins=$(stty -a | grep -o 'onlcr' || echo 'off') original_size=$(stty size) original_rows=$(echo $original_size | cut -d ' ' -f1) original_cols=$(echo $original_size | cut -d ' ' -f2) # Function to reset auto margins and terminal size to their original state reset_settings() { # Reset auto margins to original state if [ "$original_automargins" == "on" ]; then stty onlcr else stty -onlcr fi # Reset terminal size to original echo -ne "\e[8;${original_rows};${original_cols}t" stty cols "$original_cols" DEBUG "Settings reset to original." } # Function to disable auto margins and set terminal width to 999 columns disable_automargins() { stty -onlcr # Disable auto margins stty cols 999 # Set columns to a large number to prevent wrapping DEBUG "Auto margins disabled, terminal width set to 999 columns." } # Function to set traps for clean exit and interruption set_exit_traps() { trap reset_settings EXIT trap 'reset_settings; kill -SIGINT $$' INT } # Initial trap setup for safety #set_exit_traps process_file() { #local file=$(find_override_file "$1") local file="$1" local absfile=$(readlink -f "$file") local extra_args="${@:2}" shift export file_html="${METTALOG_OUTPUT}/${file}.html" DEBUG "" DEBUG "Testing: $file" cd "$SCRIPT_DIR" DEBUG "" # Add unique absolute paths to PYTHONPATH pp1=$(realpath "$(dirname "${file}")") pp2=$(realpath "$(dirname "${pp1}")") for pp in $pp1 $pp2; do if [[ ":$PYTHONPATH:" != *":$pp:"* ]]; then export PYTHONPATH="${PYTHONPATH:+"$PYTHONPATH:"}$pp" fi done # Set OPENAI_API_KEY only if it's not already set if [ -z "$OPENAI_API_KEY" ]; then export OPENAI_API_KEY=freeve fi local take_test=0 # Combined condition check if [[ "$fresh" -eq 1 ]] || [ ! -f "${file}.answers" ] || ([ "${file}" -nt "${file}.answers" ] && [ -s "${file}.answers" ]); then DEBUG "Regenerating answers: $file.answers" IF_REALLY_DO cat /dev/null > "${file}.answers" IF_REALLY_DO rm -f "${file}.answers" set +e # Function to handle SIGINT handle_sigint() { DEBUG "SIGINT received, stopping metta but continuing script..." stty sane } # Trap SIGINT trap 'handle_sigint' SIGINT ( cd "$(dirname "${file}")" || true set +x IF_REALLY_DO timeout --foreground --kill-after=5 --signal=SIGINT $(($RUST_METTA_MAX_TIME + 1)) time metta "$absfile" 2>&1 | tee "${absfile}.answers" TEST_EXIT_CODE=$? take_test=1 #set +x if [ $TEST_EXIT_CODE -eq 124 ]; then DEBUG "Rust MeTTa Killed (definitely due to timeout) after $RUST_METTA_MAX_TIME seconds: ${TEST_CMD}" [ "$if_failures" -eq 1 ] && rm -f "$file_html" elif [ $TEST_EXIT_CODE -ne 0 ]; then DEBUG "Rust MeTTa Completed with error (EXITCODE=$TEST_EXIT_CODE) under $RUST_METTA_MAX_TIME seconds" else DEBUG "Rust MeTTa Completed successfully (EXITCODE=0) under $RUST_METTA_MAX_TIME seconds" fi ) || true stty sane trap - SIGINT set -e else DEBUG "Using for answers: $file.answers" fi if [ "$if_regressions" -eq 1 ]; then if [[ ! -f "$file_html" ]]; then DEBUG "Not taking test since HTML file does not exist." return fi failures_zero=$(grep -h -c "Failures: 0" "$file_html") if [ "$failures_zero" -eq 0 ]; then DEBUG "Not taking test since Failures not 0." return fi take_test=1 DEBUG "Taking test since Failures: 0 and looking for regressions." elif [[ ! -f "$file_html" ]]; then take_test=1 DEBUG "Taking test since HTML file does not exist." else if [ "$clean" -eq 1 ]; then take_test=1 DEBUG "Taking test since --clean." elif [ "$if_failures" -eq 1 ]; then failures_not_zero=$(grep -h -c "Failures: [^0]" "$file_html") if [ "$failures_not_zero" -eq 0 ]; then success_missing=0 if ! grep -q "Successes: " "$file_html"; then success_missing=1 fi if [ "$success_missing" -eq 1 ]; then DEBUG "Retaking Test since the word 'Success' is missing from $file_html." take_test=1 else DEBUG "The word 'Success' is present in $file_html." fi fi if [ "$failures_not_zero" -gt 0 ]; then take_test=1 DEBUG "Retaking test since failures are present." IF_REALLY_DO rm -f "$file_html" else if [ "$take_test" -eq 0 ]; then DEBUG "Not retaking since Failures: 0." fi fi else DEBUG "Results present, not taking test." fi fi if [ "$take_test" -eq 1 ]; then sleep 0.1 IF_REALLY_DO touch "$file_html" TEST_CMD="./MeTTa '--output=$METTALOG_OUTPUT' --timeout=$METTALOG_MAX_TIME --html --repl=false ${extra_args[@]} ${passed_along_to_mettalog[@]} \"$file\" --halt=true" DEBUG "Running command with timeout: $TEST_CMD" set +e IF_REALLY_DO time $TEST_CMD TEST_EXIT_CODE=$? #reset_settings # set -e DEBUG "" if [ $TEST_EXIT_CODE -eq 124 ]; then DEBUG "Killed (definitely due to timeout) after $METTALOG_MAX_TIME seconds: ${TEST_CMD}" IF_REALLY_DO [ "$if_failures" -eq 1 ] && rm -f "$file_html" elif [ $TEST_EXIT_CODE -ne 7 ]; then DEBUG "Completed with error (EXITCODE=$TEST_EXIT_CODE) under $METTALOG_MAX_TIME seconds: ${TEST_CMD}" else DEBUG "Completed successfully (EXITCODE=0) under $METTALOG_MAX_TIME seconds: ${TEST_CMD}" fi #/scripts/total_loonits.sh fi } function generate_final_MeTTaLog() { # Change to the script directory cd "$SCRIPT_DIR" || exit 1 # Calculate the number of passed and failed tests passed=$(grep -c "| PASS |" /tmp/SHARED.UNITS) failed=$(grep -c "| FAIL |" /tmp/SHARED.UNITS) total=$((passed + failed)) percent_passed=$(awk -v passed="$passed" -v total="$total" 'BEGIN { printf "%.2f", (passed/total)*100 }') # Create a markdown file with test links and headers { DEBUG "| STATUS | TEST NAME | TEST CONDITION | ACTUAL RESULT | EXPECTED RESULT |" DEBUG "|--------|-----------|----------------|---------------|-----------------|" cat /tmp/SHARED.UNITS | awk -F'\\(|\\) \\| \\(' '{ print $2 " " $0 }' | sort | cut -d' ' -f2- | tac | awk '!seen[$0]++' | tac } > ./$METTALOG_OUTPUT/PASS_FAIL.md ./scripts/pass_fail_totals.sh $METTALOG_OUTPUT/ > $METTALOG_OUTPUT/TEST_LINKS.md } function PreCommitReports() { cd "$SCRIPT_DIR" DEBUG "Executing Tasks..." rsync -avm --include='*.metta.html' -f 'hide,! */' $METTALOG_OUTPUT/ reports/ \ && DEBUG "1) Synced HTML files from $METTALOG_OUTPUT/ to reports/ and deleted the original HTML files in $METTALOG_OUTPUT/" \cp -f $METTALOG_OUTPUT/PASS_FAIL.md reports/PASS_FAIL.md \cp -f $METTALOG_OUTPUT/TEST_LINKS.md reports/TEST_LINKS.md #mv final_MeTTaLog.md MeTTaLog.md \ #&& DEBUG "2) Renamed final_MeTTaLog.md to MeTTaLog.md" # Get current branch name branch_name=$(git rev-parse --abbrev-ref HEAD) # Get the latest commit SHA latest_commit=$(git rev-parse HEAD) # Get the current timestamp timestamp=$(date +%Y%m%d%H%M%S) version_info="${branch_name}_${latest_commit}_${timestamp}" # Check if there are uncommitted changes if [[ -n $(git status --porcelain) ]]; then changes="_with_uncommitted_changes" fi # Construct the reference string version_info="${branch_name}_${latest_commit}_${timestamp}${changes}" DEBUG " " >> NewResults.md echo $(date) >> NewResults.md DEBUG "version_info=$version_info" >> NewResults.md DEBUG " " >> NewResults.md cat Results.md >> NewResults.md DEBUG "Tasks Completed Successfully." } function compare_test_files() { if [ "$#" -ne 2 ]; then DEBUG "Usage: compare_test_files " return 1 fi file1="$1" file2="$2" cd "$SCRIPT_DIR" if [ ! -f "$file1" ] || [ ! -f "$file2" ]; then DEBUG "Error: One or both of the files do not exist." return 1 fi sorted1=$(mktemp) sorted2=$(mktemp) grep -E '\| PASS \||\| FAIL \|' "$file1" | awk -F'|' '{ gsub(/.*#/, "", $3); print $2, $3 }' | sort > "$sorted1" grep -E '\| PASS \||\| FAIL \|' "$file2" | awk -F'|' '{ gsub(/.*#/, "", $3); print $2, $3 }' | sort > "$sorted2" cat "$sorted1" [ -e Results.md ] && rm Results.md touch Results.md # Detect new and missing tests comm -13 "$sorted1" "$sorted2" > new_tests.md comm -23 "$sorted1" "$sorted2" > missing_tests.md comm -3 "$sorted1" "$sorted2" | while read -r line; do read -r status test <<< "$line" status1=$(grep "$test" "$sorted1" | awk '{print $1}' | tr -d ' ') status2=$(grep "$test" "$sorted2" | awk '{print $1}' | tr -d ' ') if [[ -n "$status1" && -n "$status2" && "$status1" != "$status2" ]]; then DEBUG "Now ${status2}ING $test" >> Results.md fi done sort -u Results.md -o Results.md DEBUG "New tests:" cat new_tests.md DEBUG "-----------------------------------------" DEBUG "Missing tests:" cat missing_tests.md DEBUG "-----------------------------------------" DEBUG "-----------------------------------------" grep 'PASSING' Results.md DEBUG "-----------------------------------------" grep 'FAILING' Results.md DEBUG "-----------------------------------------" new_passing=$(grep -c 'PASSING' Results.md) new_failing=$(grep -c 'FAILING' Results.md) DEBUG "Number of newly PASSING tests: $new_passing" DEBUG "Number of newly FAILING tests: $new_failing" rm -f "$sorted1" "$sorted2" new_tests.md missing_tests.md } ( main )