SeekquarrySeekquarry - Yioop Repo - Seekquarry

/ devlog / devscripts / check_test_writes.sh⤓
#!/bin/sh
# Names every file a unit test writes, makes, renames or deletes outside
# the tests folder.
#
#   check_test_writes.sh                     every test file
#   check_test_writes.sh tests/FooTest.php   the named test files
#
# WHY THIS EXISTS. A test case writes only inside the tests folder. One that wrote the site's own app/css/search.css failed once
# in a full run, and tests had written the site's work directory, /tmp and
# /root. Reading the tests for such writes misses the ones the code under
# test makes, so this runs each test file under strace, which logs every
# file the process and its children open for writing, make, rename or
# delete. It prints the full path of each one outside the tests folder,
# with its test file and how many times it was touched.
#
# Every run opens the site's two databases, public_default.db and
# private_default.db, in the mode that would make them if missing. That is
# reading, not writing, and SQLite makes their -wal and -shm files when it
# opens them, so these are named only when the bytes of a database or its
# -wal file change.
#
# strace is a Linux program; python3 reads its log. Each test file runs
# under timeout 120. Exits non-zero when a test writes outside tests/.
cd "$(dirname "$0")/../.." || exit 2
for program in strace python3; do
    if ! command -v $program >/dev/null 2>&1; then
        echo "check_test_writes: $program is not installed"
        exit 2
    fi
done
if [ $# -eq 0 ]; then
    set -- tests/*Test.php
fi
root=$(pwd -P)
# the work directory is work_directory beside src, or the folder WORK names
work=$(cd "${WORK:-$root/work_directory}" && pwd -P) || exit 2
log="$work/temp/check_test_writes.$$"
found=0
for test_file in "$@"; do
    name=$(basename "$test_file" .php)
    before=$(cat "$work"/data/*_default.db "$work"/data/*_default.db-wal \
        2>/dev/null | cksum)
    timeout 120 strace -f -qq -y -o "$log" -e trace=openat,unlink,unlinkat,\
rename,renameat,renameat2,mkdir,mkdirat,rmdir,symlink,symlinkat,link,\
linkat php src/executables/CodeTool.php unit "$name" >/dev/null 2>&1
    after=$(cat "$work"/data/*_default.db "$work"/data/*_default.db-wal \
        2>/dev/null | cksum)
    changed=0
    [ "$before" = "$after" ] || changed=1
    out=$(python3 - "$log" "$root" "$work" "$changed" <<'EOF'
import os, re, sys, collections
log, root, work, changed = sys.argv[1], sys.argv[2], sys.argv[3], sys.argv[4]
allowed = [os.path.join(root, "tests") + os.sep]
databases = [os.path.join(work, "data", name + ending) for name in
    ("public_default.db", "private_default.db") for ending in
    ("", "-wal", "-shm")]
writes = re.compile(r'O_WRONLY|O_CREAT|O_TRUNC')
counts = collections.Counter()
for line in open(log, errors="replace"):
    found = re.match(r'\d+\s+(\w+)\((.*)\)\s+=\s+(-?\d+)', line)
    if not found or found.group(3).startswith("-"):
        continue
    call, args = found.group(1), found.group(2)
    paths = re.findall(r'"((?:[^"\\]|\\.)*)"', args)
    if call == "openat":
        if not writes.search(args):
            continue
        paths = paths[:1]
    elif call in ("symlink", "symlinkat"):
        paths = paths[-1:]
    # strace -y writes a folder given by number as 3</its/path>, and a
    # relative name in a call ending in at is read from that folder
    folder = re.match(r'\d+<([^>]*)>', args)
    base = folder.group(1) if folder else root
    for path in paths:
        full = os.path.realpath(os.path.join(base, path))
        if full.startswith(("/dev/", "/proc/")) or \
            any(full.startswith(place) for place in allowed):
            continue
        if full in databases and changed == "0":
            continue
        counts[full] += 1
for full, count in sorted(counts.items()):
    print("    %s (%d)" % (full, count))
EOF
)
    if [ -n "$out" ]; then
        found=1
        echo "$name:"
        echo "$out"
    fi
done
rm -f "$log"
if [ "$found" -ne 0 ]; then
    echo "check_test_writes: the tests above write outside $root/tests"
    exit 1
fi
echo "check_test_writes: every test writes only inside $root/tests"