#!/usr/bin/env bash
# Every check that needs no PostgreSQL, in one list: the lint job of the workflow, `make lint` and a local run before a push all read it. The checks run side by
# side; the output of each is shown in the order below, a failed one with its log, the exit status is 1 when any failed.
#
#   scripts/lint.sh            all of them
#   scripts/lint.sh NAME...    only those (the names are the first column below)
((BASH_VERSINFO[0] >= 4)) || { echo 'needs bash 4+ (brew install bash)' >&2; exit 1; }
cd "$(dirname "$0")/.." || exit 2

# bench-fresh, docs, plan, portable, testing-regress and the selftests of the tools go through scripts/lint-cache.sh: a pass is kept per box by the content of the check's inputs, and the same check on the same inputs in another run
# waits for the one that is running and takes its result; CI always runs them.

# bench-cases compares with the base branch: a CI checkout is shallow. Only a shallow one is given the base: in a full clone (fetch-depth 0, the macOS job) a depth-1 fetch makes
# origin/main a shallow commit, its history is cut, and the neutral check then judges every commit of HEAD that main reached before (3 600 instead of 271 on the merge of main)
[[ -z ${CI:-} || $(git rev-parse --is-shallow-repository) != true ]] || git fetch -q --depth=1 origin "${GITHUB_BASE_REF:-main}:refs/remotes/origin/${GITHUB_BASE_REF:-main}" 2>/dev/null || true

CHECKS=(
	'syntax		for f in scripts/*.sh scripts/ci/*.sh test/t/*.sh bench/bench bench/*.sh; do bash -n "$f" || exit 1; done'
	'layout		scripts/restructure-src.sh --check'
	'tracked	! git ls-files | grep -E "(^|/)__pycache__/|[.]pyc$"'
	'portable	scripts/lint-cache.sh portable src/portable.list src/portable-col.list $(grep -hvE '\''^[[:space:]]*(#|$)'\'' src/portable.list src/portable-col.list | sed '\''s#^#src/#'\'') src/core/host.h sqlite scripts/check-portable.sh -- scripts/check-portable.sh'
	'update-drops	m=$(sed -n "s/^default_version *= *.\([0-9]*[.][0-9]*\).*/\1/p" pg_sift.control) && ! grep -nE "^(DROP|ALTER [A-Z ]* (DROP|RENAME))" sql/pg_sift--[0-9.]*--$m.sql'
	'changelog-summary	[ "$(sed -n "/^## Unreleased/,/^## [^U]/p" CHANGELOG.md | grep -c "things worth reading")" -le 1 ] || { echo "CHANGELOG.md: Unreleased has two summary entries (\"things worth reading\"); a merge left the old wording beside the new one"; exit 1; }'
	'neon-includes	for f in $(grep -l SIFT_HAVE_NEON src/*/*_neon.c src/core/visibility_popcnt.c); do grep -q -E "^#include \"core/(cpu|neon|simd_isa)[.]h\"" $f || { echo "$f: SIFT_HAVE_NEON is defined by core/cpu.h; without it the object is empty on AArch64 and the library does not link there"; exit 1; }; done'
	'testing-regress	scripts/lint-cache.sh testing-regress Makefile test/sql scripts/check-testing-regress.sh -- scripts/check-testing-regress.sh'
	'host-stubs	a=$(mktemp -d) b=$(mktemp -d) && trap "rm -rf $a $b" EXIT && make -s -C sqlite B=$a $a/host_stubs.o && make -s -C mariadb OUT=$b $b/host_stubs.o && echo "host_stubs.c compiles for the SQLite and MariaDB hosts"'
	'neutral	scripts/check-neutral.sh'
	'pgtest-auto-conf	d=$(mktemp -d) && trap "rm -rf $d" EXIT && echo "work_mem = 1GB" >$d/postgresql.auto.conf && scripts/pgtest.sh reset-auto-conf $d 2>/dev/null && [ ! -s $d/postgresql.auto.conf ]'
	'limits		scripts/gen-limits.sh --check'
	'options		scripts/gen-options.sh --check && scripts/gen-options.sh --selftest'
	'docs		scripts/lint-cache.sh docs . -- python3 scripts/gen-docs.py --check'
	'messages	scripts/gen-messages.sh --check && scripts/gen-messages.sh --selftest'
	'errors		scripts/check-errors.sh'
	'guards		scripts/check-guards.sh && scripts/check-guards.sh --selftest'
	'refusals	scripts/gen-refusals.sh --check'
	'approx		scripts/check-approx.sh'
	'kernels	python3 scripts/check-kernels.py'
	'memory		scripts/check-memory.sh && scripts/check-memory.sh --selftest'
	'pg-includes	scripts/check-pg-includes.sh && scripts/check-pg-includes.sh --selftest'
	'alternates	scripts/check-alternates.sh --selftest && scripts/check-alternates.sh'
	'quick-skip	scripts/check-quick-skip.sh --selftest && scripts/check-quick-skip.sh'
	'undefined-symbols	scripts/check-undefined.sh --selftest'
	'bench-tests	for f in bench/test/test_*.py; do python3 -I "$f" || exit 1; done'
	'bench-resume	bash bench/test/test_resume.sh'
	'bench-coverage	bench/bench cases --check'
	'slugs		scripts/check-slugs.sh'
	'file-size	scripts/check-file-size.sh'
	'leakcheck	scripts/leakcheck.sh selftest'
	'plan		scripts/lint-cache.sh plan --names scripts test Makefile sql pg_sift.control src/agg/par '\'':(exclude)test/sql'\'' '\'':(exclude)test/expected'\'' '\''*.py'\'' -- scripts/ci.sh --selftest'
	'selftest-tools	scripts/lint-cache.sh selftest-tools scripts packaging bench/steps/cases.py bench/steps/cases -- bash -c '\''scripts/check-neutral.sh --selftest && scripts/package.sh --selftest && scripts/apt-install.sh --selftest && scripts/release.sh --selftest && scripts/plangate.sh --selftest && python3 -I bench/steps/cases.py cpus --selftest'\'
	'tap-versions	v=$(sed -n "s/^default_version *= *.\(.*\).$/\1/p" pg_sift.control) && ! grep -nE "(extversion|version\(\)).*== *${v//./[.]}( |\"|$)" test/t/compat_*.sh'
	'tap-timeouts	scripts/check-tap-timeouts.sh --selftest && scripts/check-tap-timeouts.sh'
	'port-isa-check	sh scripts/port-isa-check.sh --selftest'
	'ci-claim	scripts/test-ci-claim.sh'
	'ci-stop	scripts/test-ci-stop.sh'
	'ci-ready	scripts/test-ci-ready.sh'
	'scaling-lock	scripts/test-scaling-gate.sh'
	'relcache-slots	scripts/check-relcache-slots.sh --selftest && scripts/check-relcache-slots.sh'
	'pg-bare	scripts/check-pg-bare.sh --selftest && scripts/check-pg-bare.sh'
	'scaling-triggers	scripts/check-scaling-triggers.sh'
	'scratch	scripts/check-scratch.sh --selftest && scripts/check-scratch.sh'
	'no-pattern-kill	! git grep -nE "\\b(pkil[l]|killal[l])\\b" -- scripts test bench packaging .github Makefile ":!scripts/land-selftest.sh"'
	'compat-calls	scripts/check-compat-calls.sh && scripts/check-compat-calls.sh --selftest'
	'package-text	scripts/check-package-text.sh --selftest && scripts/check-package-text.sh'
	'version-shape	! git grep -nE "version\(\).*cut -d[.] -f1-2|version\(\) ~ .\^\[0-9\]\+\[[.]\]\[0-9\]\+[$]." -- test'
	'format-literals	scripts/check-format-literals.sh && scripts/check-format-literals.sh --selftest'
	'expected-oracle	scripts/check-expected-oracle.py --selftest && scripts/check-expected-oracle.py'
	'affected-untracked	scripts/test-affected-untracked.sh'
	'affected-wide	scripts/test-affected-wide.sh'
	'gen-docs-skip	scripts/lint-cache.sh gen-docs-skip scripts bench/steps README.md -- scripts/test-gen-docs-skip.sh'
	'land		scripts/lint-cache.sh land scripts -- scripts/land.sh --selftest'
	'diff-fuzz	test/fuzz/diff_fuzz.py --selftest'
	'txn-fuzz	test/fuzz/txn_fuzz.py --selftest'
	'bm25-fuzz	test/fuzz/bm25_fuzz.py --selftest'
)

want=" $* "
tmp=$(mktemp -d)
trap 'rm -rf "$tmp"' EXIT
pids=() names=()
for c in "${CHECKS[@]}"; do
	name=${c%%$'\t'*} cmd=${c##*$'\t'}
	[[ $# == 0 || $want == *" $name "* ]] || continue
	bash -c "$cmd" >"$tmp/$name" 2>&1 &
	pids+=($!) names+=("$name")
done
bad=0
for i in "${!pids[@]}"; do
	if wait "${pids[i]}"; then echo "ok    ${names[i]}: $(tail -n 1 "$tmp/${names[i]}" | cut -c1-150)"
	else rc=$?; bad=1; echo "FAIL  ${names[i]} (exit $rc)"; [[ -s $tmp/${names[i]} ]] && tail -n 25 "$tmp/${names[i]}" || echo "(the check printed nothing: a defect of the check)"; fi
done
(( ! bad )) || exit 1
