Ctrl K
ComunidadGuías…

run_tests.sh

Rama main · réplica de lectura del repositorio original.

Archivo original ↗Preguntar a KiBot
#!/usr/bin/env bash
# The full test run. Installs nothing into your interpreter: the standard library only.
# Three steps borrow a tool through `uv`, into uv's own cache, and say so when they
# cannot: the oldest promised Python, the linter, and the count of type errors.
# The tests work on a synthetic fixture (tests/fixtures/profile) — they neither
# read nor change anyone's real profile.
set -euo pipefail
ROOT="$(cd "$(dirname "$0")" && pwd)"
cd "$ROOT"
export PYTHONPATH="src:tests${PYTHONPATH:+:$PYTHONPATH}"
export SCHOLION_PROFILE_DIR="$ROOT/tests/fixtures/profile"
export SCHOLION_OFFLINE=1
# The output language is pinned rather than inherited: with SCHOLION_LANG=ru in
# the shell a developer would otherwise get a different run from CI, and a test
# about language would pass or fail by accident of the terminal.
export SCHOLION_LANG=en
# The genome is switched off explicitly: otherwise, on the owner's machine, the
# run would reach for a real VCF of tens of gigabytes (in iCloud that also means
# downloading the file), and the result would depend on whose genome happens to
# lie next to it. The tests are obliged to check the BEHAVIOUR of "the database
# is not connected", which is the same for everybody.
export SCHOLION_GENOME_VCF="$ROOT/tests/fixtures/no-such-file.vcf.gz"
export SCHOLION_GENOME_DIR="$ROOT/tests/fixtures/no-genome"
export SCHOLION_GENE_GFF3="$ROOT/tests/fixtures/no-annotation.gff3"
# The workstation is switched off the same way (task 192). On a clinician's
# machine the real one names patients' containers, and a test that dropped the
# profile pin would otherwise be reading whichever patient is active.
export SCHOLION_WORKSTATION="$ROOT/tests/fixtures/no-workstation.json"
# Guard against the usual "command + comment on one line" paste: in interactive
# zsh a `#` does NOT start a comment, and the words after the hash arrive here as
# arguments. unittest answers with its usage text, which does not look like "the
# tests never ran at all" and is easily mistaken for a successful run. So the
# refusal is spelled out in words.
# Both entry points use the same selector validation in run_tests.py.

# Cheap metadata failures precede a costly suite. The Python runner owns the
# same preflight on platforms without bash.
if [ "$#" -eq 0 ]; then
  python3 src/tools/run_tests.py --preflight-only < /dev/null
fi
REACH_RAN=0
if [ "$#" -eq 0 ] && [ -d share ] && [ -f src/tools/check_test_reach.py ] \
   && [ "${SCHOLION_SKIP_REACH:-}" != "1" ] && [ "${SCHOLION_SKIP_REACH:-}" != "true" ] \
   && [ "${SCHOLION_SKIP_REACH:-}" != "yes" ]; then
  echo "▶ full tests and statement reach in one run (synthetic profile, offline)"
  python3 src/tools/check_test_reach.py --strict < /dev/null
  REACH_RAN=1
else
  echo "▶ tests (SCHOLION_OFFLINE=1 — synthetic profile only)"
  python3 src/tools/run_tests.py --suite-only "$@" < /dev/null
fi
echo "▶ backward compatibility of the public contract"
python3 src/tools/check_compat.py
# These steps run only if the tool is present in this build: the anonymised
# package does not carry every internal tool, and a run at the recipient's end
# must not fail over the absence of something they were never given.
if [ -f src/tools/sync_docs.py ]; then
  echo "▶ the documents inside the package match their sources"
  python3 src/tools/sync_docs.py || exit 1
fi

if [ -f src/tools/sync_manifest.py ]; then
  echo "▶ the host manifest matches the build"
  python3 src/tools/sync_manifest.py || exit 1
fi

# The plugin folder as Claude's own validator reads it — the check the catalogue
# runs on submission, run here first. It needs the Claude command-line tool,
# which this project neither installs nor requires; where it is absent the step
# says so rather than passing in silence.
if [ -d agent-plugin/.claude-plugin ]; then
  if [ "${SCHOLION_SKIP_PLUGIN_VALIDATE:-}" = "1" ] || [ "${SCHOLION_SKIP_PLUGIN_VALIDATE:-}" = "true" ]; then
    echo "▶ the Claude plugin validates — skipped by SCHOLION_SKIP_PLUGIN_VALIDATE"
  elif command -v claude > /dev/null 2>&1; then
    echo "▶ the Claude plugin validates (claude plugin validate --strict; SCHOLION_SKIP_PLUGIN_VALIDATE=1 skips)"
    claude plugin validate --strict agent-plugin || exit 1
  else
    echo "▶ the Claude plugin validates — not checked: the claude command is not installed here"
  fi
fi

if [ -f src/tools/sync_rules.py ]; then
  echo "▶ the assistant rules are in sync with ASSISTANT-RULES.md"
  python3 src/tools/sync_rules.py
fi
# The suite on the OLDEST Python the package promises, run BEFORE a tag instead
# of after one.
#
# `requires-python = ">=3.10"` is a promise to everyone who installs this, and
# until now nothing on this machine checked it: the matrix that does lives in the
# public repository and is reached only through publication. It answered twice in
# two days, both times about a tag that was already out — once with a failed
# release build, once with a red matrix on a published version. Both were the
# same shape: verified on one interpreter, promised about four.
#
# The floor is read from pyproject.toml rather than written here. Three places
# already name it — the promise, the matrix, and this — and a number typed into
# each is three numbers.
#
# `uv` fetches the interpreter and caches it; a second run costs half a second
# plus the suite. Where uv is absent the step says so and does not pretend: a
# check that cannot run is not a check that passed.
if [ "$#" -gt 0 ]; then
  :
elif [ "${SCHOLION_SKIP_OLDEST:-}" = "1" ] || [ "${SCHOLION_SKIP_OLDEST:-}" = "true" ]; then
  echo "▶ the oldest supported Python — skipped by SCHOLION_SKIP_OLDEST"
elif [ "${CI:-}" = "true" ]; then
  echo "▶ the oldest supported Python — not here: the matrix runs every version it promises"
elif [ ! -f pyproject.toml ]; then
  echo "▶ the oldest supported Python — not asked: no pyproject.toml here to read the floor from"
else
  FLOOR="$(sed -n 's/^requires-python[^0-9]*\([0-9][0-9]*\.[0-9][0-9]*\).*/\1/p' pyproject.toml | head -1)"
  HERE="$(python3 -c 'import sys; print("%d.%d" % sys.version_info[:2])')"
  case "$FLOOR" in [0-9]*.[0-9]*) ;; *) FLOOR="" ;; esac
  if [ -z "$FLOOR" ]; then
    echo "▶ the oldest supported Python — not asked: pyproject.toml names no floor"
  elif [ "$FLOOR" = "$HERE" ]; then
    echo "▶ the oldest supported Python — this IS $FLOOR, already covered by the run above"
  elif ! command -v uv >/dev/null 2>&1; then
    echo "⚠ the oldest supported Python ($FLOOR) was NOT run: uv is not installed, and there is"
    echo "  nothing else here to fetch that interpreter with. This machine runs $HERE, so"
    echo '  requires-python is unverified until the matrix answers — after a tag.'
  else
    echo "▶ the suite on Python $FLOOR, the oldest this package promises (uv fetches it; SCHOLION_SKIP_OLDEST=1 skips)"
    uv run --python "$FLOOR" --no-project -- python -m unittest discover -s tests -t . < /dev/null || exit 1
  fi
fi

# The linter of the public matrix, run before a publication rather than after it.
#
# The same arrangement as the count of type errors below, for the same reason:
# the job that runs `ruff` lives in the public repository and answers after a
# tag, and 0.5.9 went out with two findings it would have named. Two tests hold
# the classes that cost that release on the standard library alone; this step
# holds the rest of the rule set, where `uv` can fetch the tool.
#
# Both the version and the command are read from the workflow, so that what is
# checked here is what will be checked there.
if [ -f .github/workflows/tests.yml ]; then
  if [ "$#" -gt 0 ]; then
    echo "▶ the linter — not run: this run was narrowed to $*"
  elif [ "${SCHOLION_SKIP_LINT:-}" = "1" ] || [ "${SCHOLION_SKIP_LINT:-}" = "true" ]; then
    echo "▶ the linter — skipped by SCHOLION_SKIP_LINT"
  elif [ "${CI:-}" = "true" ]; then
    echo "▶ the linter — not here: the matrix has a job of its own for it"
  else
    LINT_PIN="$(sed -n 's/.*"\(ruff==[0-9][0-9.]*\)".*/\1/p' .github/workflows/tests.yml | head -1)"
    LINT_ARGS="$(sed -n 's/^ *run: ruff \(check [A-Za-z0-9_ ./-]*\)$/\1/p' .github/workflows/tests.yml | head -1)"
    if [ -z "$LINT_PIN" ] || [ -z "$LINT_ARGS" ]; then
      echo "⚠ the linter was NOT run: the workflow here pins no ruff version, or names no command for it."
    elif ! command -v uv >/dev/null 2>&1; then
      echo "⚠ the linter was NOT run: uv is not installed, and there is nothing else here to fetch"
      echo "  $LINT_PIN with. Its findings are unknown until the matrix answers — after a tag."
    elif ! uv run --no-project --with "$LINT_PIN" -- ruff --version >/dev/null 2>&1; then
      echo "⚠ the linter was NOT run: uv could not provide $LINT_PIN — no network, and nothing"
      echo "  cached. Its findings are unknown until the matrix answers — after a tag."
    else
      echo "▶ the linter finds nothing ($LINT_PIN $LINT_ARGS, fetched by uv; SCHOLION_SKIP_LINT=1 skips)"
      # shellcheck disable=SC2086  # the arguments are words, read from the workflow
      uv run --no-project --with "$LINT_PIN" -- ruff $LINT_ARGS --quiet || exit 1
      echo "  ✓ zero findings"
    fi
  fi
fi

# Type errors per module, counted before publication rather than after it.
#
# The matrix has a job for this and it answers after a tag: it was red on two
# published versions in a row, the second time for a count that had grown in
# work already out. `mypy` is a third-party tool and the package depends on
# nothing, so nothing is installed into anybody's interpreter: `uv` fetches the
# tool into its own cache, the same way it fetches the oldest Python above.
#
# The version is read from the workflow that pins it. The count depends on the
# version, and a number typed here as well would be two numbers.
#
# Where it cannot run it says so and the run goes on: a check that did not run
# is not a check that passed, and it is not a failure of the tree either.
if [ -f src/tools/check_types.py ]; then
  if [ "$#" -gt 0 ]; then
    echo "▶ type errors per module — not measured: this run was narrowed to $*"
  elif [ "${SCHOLION_SKIP_TYPES:-}" = "1" ] || [ "${SCHOLION_SKIP_TYPES:-}" = "true" ]; then
    echo "▶ type errors per module — skipped by SCHOLION_SKIP_TYPES"
  elif [ "${CI:-}" = "true" ]; then
    echo "▶ type errors per module — not here: the matrix has a job of its own for them"
  else
    TYPES_PIN=""
    if [ -f .github/workflows/tests.yml ]; then
      TYPES_PIN="$(sed -n 's/.*"\(mypy==[0-9][0-9.]*\)".*/\1/p' .github/workflows/tests.yml | head -1)"
    fi
    if [ -z "$TYPES_PIN" ]; then
      echo "⚠ type errors were NOT measured: no workflow here pins a mypy version to measure with."
    elif ! command -v uv >/dev/null 2>&1; then
      echo "⚠ type errors were NOT measured: uv is not installed, and there is nothing else here"
      echo "  to fetch $TYPES_PIN with. The count is unverified until the matrix answers — after a tag."
    elif ! uv run --no-project --with "$TYPES_PIN" -- python -c "import mypy" >/dev/null 2>&1; then
      echo "⚠ type errors were NOT measured: uv could not provide $TYPES_PIN — no network, and"
      echo "  nothing cached. The count is unverified until the matrix answers — after a tag."
    else
      echo "▶ no module gained a type error ($TYPES_PIN, fetched by uv; SCHOLION_SKIP_TYPES=1 skips)"
      uv run --no-project --with "$TYPES_PIN" -- python src/tools/check_types.py --strict > /dev/null 2>&1 || {
        uv run --no-project --with "$TYPES_PIN" -- python src/tools/check_types.py --strict | tail -4
        exit 1
      }
      echo "  ✓ every module is at or below its recorded count"
    fi
  fi
fi

# The reach of the suite over the code, measured rather than assumed. The full
# source suite above already records it. Never execute that same suite again
# merely to obtain coverage. Narrowed runs cannot replace full-suite evidence.
#
# Why it is here at all and not in CI: the matrix in .github/workflows/tests.yml
# runs in the PUBLIC repository, which this tree reaches only through
# publish_share.sh. A gate there answers after publication. This is the only
# place that answers before it.
#
# A narrowed run is skipped rather than measured: `./run_tests.sh tests.test_x`
# executes a fraction of the suite, and comparing that fraction's reach against a
# baseline taken from the whole would fail for the one reason that is not a
# defect.
if [ -f src/tools/check_test_reach.py ]; then
  if [ "$REACH_RAN" = "1" ]; then
    echo "▶ reach of the suite — passed with the primary full run"
  elif [ "$#" -gt 0 ]; then
    echo "▶ reach of the suite — not measured: this run was narrowed to $*"
  elif [ "${SCHOLION_SKIP_REACH:-}" = "1" ] || [ "${SCHOLION_SKIP_REACH:-}" = "true" ] || [ "${SCHOLION_SKIP_REACH:-}" = "yes" ]; then
    echo "▶ reach of the suite — skipped by SCHOLION_SKIP_REACH"
  else
    echo "▶ no module lost reach over the code (full suite; SCHOLION_SKIP_REACH=1 skips it)"
    python3 src/tools/check_test_reach.py --strict || exit 1
  fi
fi

# A broad `except` that swallows an error must say why that is safe (task 209):
# the class behind «a failed read of the genome answered reference». Instant —
# an AST pass over src/ — so it runs on every invocation, narrowed or not.
if [ -f src/tools/check_quiet_excepts.py ]; then
  echo "▶ every silent broad except says why its silence is safe"
  python3 src/tools/check_quiet_excepts.py --strict > /dev/null || {
    python3 src/tools/check_quiet_excepts.py --strict
    exit 1
  }
  echo "  ✓ none unexplained"
fi

if [ -f src/tools/check_language.py ]; then
  echo "▶ Russian has not been added to what ships"
  python3 src/tools/check_language.py --strict > /dev/null || {
    python3 src/tools/check_language.py --strict
    exit 1
  }
  echo "  ✓ the remainder did not grow"
fi