#!/bin/bash # run static code checks like eslint, flake8, mypy, ruff, vulture. set -eu # requires: .flake8 # requires: pyproject.toml # requires: containers/flatpak/test/ruff.toml # requires: pkg/ruff.toml # requires: test/common/ruff.toml # requires: test/example/ruff.toml # requires: test/verify/ruff.toml # requires: tools/vulture-suppressions/ruff.toml # we consider any function named test_* to be a test case # each test is considered to succeed if it exits with no output # exit with status 77 is a skip, with the message in the output # otherwise, any output is a failure, even if exit status is 0 # note: `set -e` is not active during the tests. find_scripts() { # Helper to find all scripts in the tree ( # Any non-binary file which contains a given shebang git grep --cached -lIz '^#!.*'"$1" shift # Any file matching the provided globs git ls-files -z "$@" ) | sort -z | uniq -z } find_python_files() { find_scripts 'python3' '*.py' } test_flake8() { command -v flake8 >/dev/null || skip 'no flake8' find_python_files | xargs -r -0 flake8 } test_ruff() { command -v ruff >/dev/null || skip 'no ruff' find_python_files | xargs -r -0 ruff check --no-cache } if [ "${WITH_PARTIAL_TREE:-0}" = 0 ]; then mypy_strict_files=' src/cockpit/__init__.py src/cockpit/_version.py src/cockpit/jsonutil.py src/cockpit/protocol.py src/cockpit/transports.py ' test_mypy() { command -v mypy >/dev/null || skip 'no mypy' for pkg in systemd_ctypes ferny bei; do test -e "src/cockpit/_vendor/${pkg}/__init__.py" || skip "no ${pkg}" done mypy --no-error-summary src/cockpit test/pytest # test scripts individually, to avoid clashing on `__main__` # also skip integration tests, they are too big and not annotated find_scripts 'python3' "*.none" | grep -zv 'test/' | xargs -r -0 -n1 mypy --no-error-summary mypy --no-error-summary --strict $mypy_strict_files } test_vulture() { # vulture to find unused variables/functions command -v vulture >/dev/null || skip 'no vulture' find_python_files | xargs -r -0 vulture } fi test_js_translatable_strings() { # Translatable strings must be marked with _(""), not _('') ! git grep -n -E "(gettext|_)\(['\`]" -- {src,pkg}/'*'.{js,jsx} } if [ "${WITH_PARTIAL_TREE:-0}" = 0 ]; then test_eslint() { test -x node_modules/.bin/eslint -a -x /usr/bin/node || skip 'no eslint' find_scripts 'node' '*.js' '*.jsx' | xargs -0 node_modules/.bin/eslint } fi test_stylelint() { test -x node_modules/.bin/stylelint -a -x /usr/bin/node || skip 'no stylelint' git ls-files -z '*.css' '*.scss' | xargs -r -0 node_modules/.bin/stylelint } test_no_translatable_attr() { # Use of translatable attribute in HTML: should be 'translate' instead ! git grep -n 'translatable=["'\'']yes' -- pkg doc } test_unsafe_security_policy() { # It's dangerous to have 'unsafe-inline' or 'unsafe-eval' in our # content-security-policy entries. git grep -lIz -E 'content-security-policy.*(\*|unsafe)' 'pkg/*/manifest.json' | while read -d '' filename; do if test ! -f "$(dirname ${filename})/content-security-policy.override"; then echo "${filename} contains unsafe content security policy" fi done } test_json_verify() { # Check all JSON files for validity git ls-files -z '*.json' | while read -d '' filename; do python3 -m json.tool "${filename}" /dev/null 2>&1 | sed "s@^@${filename}: @" done } test_html_verify() { # Check all HTML files for syntactic validity git ls-files -z 'pkg/*.html' | while read -d '' filename; do if ! python3 -c "import xml.etree.ElementTree as ET; ET.parse('${filename}')"; then echo "${filename} contains invalid XML" fi done } test_include_config_h() { # Every C file should #include "config.h" at the top git ls-files -cz '*.c' | while read -d '' filename; do if sed -n '/^#include "config.h"$/q1; /^\s*#/q;' "${filename}"; then printf '%s: #include "config.h" is not the first line\n' "${filename}" fi done } ### end of tests. start of machinery. skip() { printf "%s\n" "$*" exit 77 } main() { if [ $# = 0 ]; then tap='' elif [ $# = 1 -a "$1" = "--tap" ]; then tap='1' else printf "usage: %s [--tap]\n" "$0" >&2 exit 1 fi cd "${0%/*}/.." if [ ! -e .git ]; then echo '1..0 # SKIP not in a git checkout' exit 0 fi exit_status=0 counter=0 tests=($(compgen -A function 'test_')) [ -n "${tap}" ] && printf "1..%d\n" "${#tests[@]}" for test_function in "${tests[@]}"; do path="/static-code/$(echo ${test_function} | tr '_' '-')" counter=$((counter + 1)) fail='' skip='' # run the test, capturing its output and exit status output="$(${test_function} 2>&1)" && test_status=0 || test_status=$? if [ "${test_status}" = 77 ]; then if [ -z "${tap}" ]; then printf >&2 "WARNING: skipping %s: %s\n" "${path}" "${output}" fi skip=" # SKIP ${output}" output='' elif [ "${test_status}" != 0 -o -n "${output}" ]; then exit_status=1 fail=1 fi # Only print output on failures or --tap mode [ -n "${tap}" -o -n "${fail}" ] || continue # excluding the plan, this is the only output that we ever generate printf "%s %d %s%s\n" "${fail:+not }ok" "${counter}" "${path}" "${skip}" if [ -n "${output}" ]; then printf "%s\n" "${output}" | sed -e 's/^/# /' fi done exit "${exit_status}" } main "$@"