/ devlog / devscripts / check_audit.sh
#!/bin/sh
# Runs the audit the process file lists and prints one line per check,
# by the name the process file gives it, with PASS or FAIL.
#
#   devlog/devscripts/check_audit.sh                 every file the patch touches
#   devlog/devscripts/check_audit.sh file.php ...    the files named
#
# WHY THIS EXISTS. The process file lists eleven checks by name. What I
# reported each round was the scripts I had run, which cover them but do
# not answer them one at a time: a reader could not tell whether "no
# banned identifier suffixes" had been looked at or had merely fallen
# inside check_style. Four of the eleven were reported under other names
# for twenty rounds. This prints the eleven, in the order the process
# file gives them, so a missing one shows as a missing line.
#
# The suite step reads a run's timings as well as its counts, since the
# process file asks that a case over about a hundredth of a second be
# flagged: that usually means it reached the disk or the network without
# meaning to.
#
# Exits non-zero when any check fails, so cut_patch.sh can gate on it.
set -u
WORK=${WORK:-/home/claude/work}
cd "$WORK" || exit 2
if [ $# -gt 0 ]; then
    files="$*"
else
    files=`git diff confirmed-head..HEAD --name-only | grep '\.php$'`
fi
php_files=""
for one in $files; do
    if [ -f "$one" ]; then
        php_files="$php_files $one"
    fi
done
failed=0
say()
{
    if [ "$2" = "PASS" ]; then
        echo "  PASS  $1"
    else
        echo "  FAIL  $1"
        failed=1
    fi
}
# 1. php -l on every touched file.
bad=0
for one in $php_files; do
    if ! php -l "$one" > /dev/null 2>&1; then
        bad=`expr $bad + 1`
        echo "        will not parse: $one"
    fi
done
if [ $bad -eq 0 ]; then
    say "php -l on every touched file" PASS
else
    say "php -l on every touched file" FAIL
fi
# 2. needsdocs returns 0.
gaps=0
for one in $php_files; do
    found=`php src/executables/CodeTool.php needsdocs "$one" 2>/dev/null |
        grep 'Total issues' | grep -o '[0-9]*'`
    gaps=`expr $gaps + ${found:-0}`
done
if [ "$gaps" -eq 0 ]; then
    say "every class and method has a docblock" PASS
else
    say "every class and method has a docblock" FAIL
fi
# 3 to 6, and 9: the shapes check_style.sh looks for, asked one at a
# time so each of the process file's names gets its own line.
style=`sh devlog/devscripts/check_style.sh $php_files 2>&1`
for pair in "a variable named for one letter=no new single-letter PHP \
variables except i, j, k" \
    "a name saying what kind of thing it is=no banned identifier \
suffixes" \
    "a comment over more than one line written with two slashes=no \
multi-line comment written with two slashes" \
    "with no braces=every if, else and loop body is braced"
do
    look=`echo "$pair" | cut -d= -f1`
    name=`echo "$pair" | cut -d= -f2`
    if echo "$style" | grep -q "$look"; then
        say "$name" FAIL
        echo "$style" | grep "$look" | head -3 | sed 's/^/        /'
    else
        say "$name" PASS
    fi
done
# 7. longlines clean.
long=0
for one in $php_files; do
    if php src/executables/CodeTool.php longlines "$one" 2>/dev/null |
        grep -q 'Line '; then
        long=`expr $long + 1`
        echo "        a line past 80 columns: $one"
    fi
done
if [ "$long" -eq 0 ]; then
    say "no line over 80 columns in a touched file" PASS
else
    say "no line over 80 columns in a touched file" FAIL
fi
# 8. check_deprecated clean.
if sh devlog/devscripts/check_deprecated.sh $php_files > /tmp/audit_dep.txt 2>&1; then
    say "nothing this PHP passes in silence that Chris's PHP reports" \
        PASS
else
    say "nothing this PHP passes in silence that Chris's PHP reports" \
        FAIL
    head -3 /tmp/audit_dep.txt | sed 's/^/        /'
fi
# 10. No banned words. The response is checked separately; this looks at
# the patch itself.
if git diff confirmed-head..HEAD > /tmp/audit_patch.txt 2>/dev/null &&
    sh devlog/devscripts/check_words.sh /tmp/audit_patch.txt > /dev/null 2>&1; then
    say "no banned words in the patch" PASS
else
    say "no banned words in the patch" FAIL
fi
# 11. Createdb, and the suite with its timings.
if php src/configs/Createdb.php 2>&1 | grep -q 'Create DB succeeded'; then
    rm -rf /home/claude/work-dir/app/locale
    say "Createdb prints Create DB succeeded" PASS
else
    say "Createdb prints Create DB succeeded" FAIL
fi
if [ "${SKIP_SUITE:-}" = "1" ]; then
    echo "  ----  the suite was not run this time"
else
    php src/executables/CodeTool.php unit > /tmp/audit_suite.txt 2>&1
    python3 - <<'PYEOF'
import re
text = open("/tmp/audit_suite.txt", encoding="utf-8").read()
found = re.findall(
    r'^\s*(\w+): \[(\d+) / (\d+)\] passed\. Time: ([\d.e+-]+)s', text,
    re.M)
cases = len(found)
passed = sum(int(one[1]) for one in found)
of = sum(int(one[2]) for one in found)
failures = text.count("FAILED")
warnings = len(re.findall(r'WARNING|Deprecated|Notice:', text))
# Everything else the run says went wrong: a statement a database
# refused, a name PHP could not find, a file that would not open.
wrong = len(re.findall(r'Exec failed|no such table|no such column'
    r'|SQLSTATE|Fatal error|Uncaught|Undefined variable'
    r'|Undefined array key|Undefined index|Undefined property'
    r'|Call to a member|failed to open stream', text))
slow = sorted(((float(one[3]), one[0]) for one in found), reverse=True)
over = [one for one in slow if one[0] > 0.01]
mark = ("PASS" if failures == 0 and warnings == 0 and wrong == 0
    and cases > 0 else "FAIL")
print("  %s  the suite: %d cases, %d of %d assertions, %d failures, "
    "%d warnings, %d other line(s) saying something went wrong" %
    (mark, cases, passed, of, failures, warnings, wrong))
print("  ----  %d case(s) over 0.01 seconds, which usually means "
    "unintended input or output" % len(over))
for seconds, name in over[:5]:
    print("        %6.3f s  %s" % (seconds, name))
PYEOF
    if grep -q FAILED /tmp/audit_suite.txt; then
        failed=1
    fi
fi
if [ "$failed" -eq 0 ]; then
    echo "check_audit: every check above passed"
    exit 0
fi
echo "check_audit: a check above failed"
exit 1
X