From 00005f16e05c1bbaf2050d53061f239a869ffb51 Mon Sep 17 00:00:00 2001 From: Jim Huang Date: Mon, 30 Mar 2026 22:14:17 +0800 Subject: [PATCH] Consolidate CI workflows and extract shared script Refine GitHub Actions workflows: - Merge coding-style and static-analysis into a single lint job - Add actions/cache for apt packages with workflow-hash keys - Drop redundant build-essential install from unit-tests - Add missing checkout step to build-lkl.yml build job - Remove scripts/ from build artifact (integration-tests checks out) Extract duplicated shell helpers into scripts/common.sh: - die(), detect_arch(), download_file(), verify_sha256(), find_timeout_cmd() - Make set_colors() portable (replace [[ ]] with [ ]) - Use absolute-path sourcing pattern across all scripts Change-Id: Ib44af8ca4fe14982b45464476386efbb9afe3423 --- .github/workflows/build-kbox.yml | 59 ++++++++---------- .github/workflows/build-lkl.yml | 12 +++- mk/tests.mk | 4 +- scripts/build-lkl.sh | 67 ++++++++++++++------ scripts/common.sh | 102 ++++++++++++++++++++++++++++--- scripts/fetch-busybox.sh | 17 +----- scripts/fetch-lkl.sh | 33 ++-------- scripts/mkrootfs.sh | 36 ++--------- scripts/run-stress.sh | 30 +-------- scripts/run-tests.sh | 30 +-------- 10 files changed, 205 insertions(+), 185 deletions(-) mode change 100755 => 100644 scripts/common.sh diff --git a/.github/workflows/build-kbox.yml b/.github/workflows/build-kbox.yml index 6b7b59a..5c1fc24 100644 --- a/.github/workflows/build-kbox.yml +++ b/.github/workflows/build-kbox.yml @@ -1,14 +1,14 @@ # Build kbox and run full test suite. # Zero root required -- everything runs as an unprivileged user. # -# Parallelism (5 independent jobs, 1 sequential): -# coding-style -- clang-format + newline checks (fast) -# static-analysis -- security patterns + cppcheck -# unit-tests -- no LKL dependency, ASAN/UBSAN -# build-kbox -- fetches LKL, compiles kbox + guest/stress bins, builds rootfs -# integration -- needs build-kbox artifacts, runs integration + stress tests +# Parallelism (4 independent jobs, 1 sequential): +# commit-hygiene -- Change-Id + subject format (needs full history) +# lint -- clang-format, newline, security, cppcheck (one apt install) +# unit-tests -- no LKL dependency, ASAN/UBSAN +# build-kbox -- fetches LKL, compiles kbox + guest/stress bins, builds rootfs +# integration -- needs build-kbox artifacts, runs integration + stress tests # -# coding-style, static-analysis, unit-tests, and build-kbox run in parallel. +# commit-hygiene, lint, unit-tests, and build-kbox run in parallel. # integration-tests waits for build-kbox only. name: Build and Test @@ -49,17 +49,24 @@ jobs: scripts/check-commitlog.sh fi - # ---- Coding style: formatting checks (fast, ~10s) ---- - coding-style: + # ---- Lint: formatting + static analysis (consolidated, one apt install) ---- + lint: runs-on: ubuntu-24.04 steps: - name: Checkout uses: actions/checkout@v6 - - name: Install clang-format + - name: Cache apt packages + uses: actions/cache@v5 + with: + path: ~/apt-cache + key: apt-lint-${{ runner.os }}-${{ hashFiles('.github/workflows/build-kbox.yml') }} + - name: Install tools run: | + mkdir -p ~/apt-cache sudo apt-get update - sudo apt-get install -y clang-format-20 + sudo apt-get install -y -o Dir::Cache::Archives=$HOME/apt-cache \ + clang-format-20 cppcheck - name: Check trailing newline run: .ci/check-newline.sh @@ -67,18 +74,6 @@ jobs: - name: Check clang-format run: .ci/check-format.sh - # ---- Static analysis: security patterns + cppcheck ---- - static-analysis: - runs-on: ubuntu-24.04 - steps: - - name: Checkout - uses: actions/checkout@v6 - - - name: Install cppcheck - run: | - sudo apt-get update - sudo apt-get install -y cppcheck - - name: Security checks run: .ci/check-security.sh @@ -92,11 +87,6 @@ jobs: - name: Checkout uses: actions/checkout@v6 - - name: Install compiler - run: | - sudo apt-get update - sudo apt-get install -y build-essential - - name: Run unit tests (ASAN/UBSAN) run: make check-unit @@ -107,10 +97,17 @@ jobs: - name: Checkout uses: actions/checkout@v6 + - name: Cache apt packages + uses: actions/cache@v5 + with: + path: ~/apt-cache + key: apt-build-${{ runner.os }}-${{ hashFiles('.github/workflows/build-kbox.yml') }} - name: Install dependencies run: | + mkdir -p ~/apt-cache sudo apt-get update - sudo apt-get install -y build-essential e2fsprogs + sudo apt-get install -y -o Dir::Cache::Archives=$HOME/apt-cache \ + e2fsprogs - name: Fetch prebuilt LKL run: ./scripts/fetch-lkl.sh @@ -122,7 +119,7 @@ jobs: path: | alpine.ext4 deps/ - key: rootfs-${{ hashFiles('scripts/alpine-sha256.txt', 'scripts/mkrootfs.sh', 'tests/guest/*.c', 'tests/stress/*.c', 'Makefile') }} + key: rootfs-${{ hashFiles('scripts/alpine-sha256.txt', 'scripts/common.sh', 'scripts/mkrootfs.sh', 'tests/guest/*.c', 'tests/stress/*.c', 'Makefile') }} - name: Configure (defconfig) run: make defconfig @@ -148,7 +145,6 @@ jobs: tests/guest/*-test tests/stress/* !tests/stress/*.c - scripts/ # ---- Integration + stress tests: needs kbox binary + rootfs ---- integration-tests: @@ -169,7 +165,6 @@ jobs: chmod +x kbox chmod +x tests/guest/*-test 2>/dev/null || true chmod +x tests/stress/* 2>/dev/null || true - chmod +x scripts/*.sh - name: Integration tests run: ./scripts/run-tests.sh ./kbox alpine.ext4 diff --git a/.github/workflows/build-lkl.yml b/.github/workflows/build-lkl.yml index b2724d7..1683fe9 100644 --- a/.github/workflows/build-lkl.yml +++ b/.github/workflows/build-lkl.yml @@ -92,10 +92,20 @@ jobs: runs-on: ${{ matrix.runner }} timeout-minutes: 30 steps: + - name: Checkout + uses: actions/checkout@v6 + + - name: Cache apt packages + uses: actions/cache@v5 + with: + path: ~/apt-cache + key: apt-lkl-${{ runner.os }}-${{ matrix.arch }}-${{ hashFiles('.github/workflows/build-lkl.yml') }} - name: Install build dependencies run: | + mkdir -p ~/apt-cache sudo apt-get update - sudo apt-get install -y build-essential flex bison bc libelf-dev + sudo apt-get install -y -o Dir::Cache::Archives=$HOME/apt-cache \ + build-essential flex bison bc libelf-dev - name: Build LKL from source env: diff --git a/mk/tests.mk b/mk/tests.mk index 11bb0f2..f53f1cc 100644 --- a/mk/tests.mk +++ b/mk/tests.mk @@ -76,9 +76,11 @@ check-unit: $(TEST_TARGET) # Unit tests are built WITHOUT linking LKL. # We define LKL stubs for functions referenced by test support code. +TEST_LDFLAGS = $(filter-out -L$(LKL_DIR) -L$(LKL_DIR)/lib,$(LDFLAGS)) + $(TEST_TARGET): $(TEST_SRCS) $(TEST_SUPPORT_SRCS) $(wildcard .config) @echo " LD $@" - $(Q)$(CC) $(CFLAGS) -DKBOX_UNIT_TEST -o $@ $(TEST_SRCS) $(TEST_SUPPORT_SRCS) $(LDFLAGS) -lpthread + $(Q)$(CC) $(CFLAGS) -DKBOX_UNIT_TEST -o $@ $(TEST_SRCS) $(TEST_SUPPORT_SRCS) $(TEST_LDFLAGS) -lpthread check-integration: $(TARGET) guest-bins stress-bins $(ROOTFS) @echo " RUN check-integration" diff --git a/scripts/build-lkl.sh b/scripts/build-lkl.sh index 6e773d8..851b302 100755 --- a/scripts/build-lkl.sh +++ b/scripts/build-lkl.sh @@ -11,23 +11,49 @@ set -eu -case "${1:-$(uname -m)}" in - x86_64 | amd64) ARCH="x86_64" ;; - aarch64 | arm64) ARCH="aarch64" ;; - *) - echo "error: unsupported architecture: ${1:-$(uname -m)}" >&2 - exit 1 - ;; -esac +. "$(cd "$(dirname "$0")" && pwd)/common.sh" + +detect_arch "${1:-}" LKL_DIR="${LKL_DIR:-lkl-${ARCH}}" LKL_SRC="${LKL_SRC:-build/lkl-src}" LKL_UPSTREAM="https://github.com/lkl/linux" -die() +is_commit_sha() { - echo "error: $*" >&2 - exit 1 + case "$1" in + [0-9a-fA-F][0-9a-fA-F][0-9a-fA-F][0-9a-fA-F][0-9a-fA-F][0-9a-fA-F][0-9a-fA-F]*) + case "$1" in + *[!0-9a-fA-F]*) + return 1 + ;; + esac + return 0 + ;; + esac + + return 1 +} + +checkout_lkl_ref() +{ + ref="$1" + + echo " CHECKOUT ${ref}" + if is_commit_sha "$ref"; then + # Bare commit SHAs are not advertised as remote refs, so a shallow + # ref fetch is not reliable. Ensure full history before checkout. + if git -C "${LKL_SRC}" rev-parse --is-shallow-repository > /dev/null 2>&1 \ + && [ "$(git -C "${LKL_SRC}" rev-parse --is-shallow-repository)" = "true" ]; then + git -C "${LKL_SRC}" fetch --unshallow origin + else + git -C "${LKL_SRC}" fetch origin + fi + git -C "${LKL_SRC}" checkout "${ref}" + else + git -C "${LKL_SRC}" fetch --depth=1 origin "${ref}" + git -C "${LKL_SRC}" checkout FETCH_HEAD + fi } # ---- Clone or update source tree ---------------------------------------- @@ -35,21 +61,24 @@ die() if [ -d "${LKL_SRC}/.git" ]; then echo " SRC ${LKL_SRC} (exists, skipping clone)" if [ -n "${LKL_REF:-}" ]; then - echo " CHECKOUT ${LKL_REF}" - git -C "${LKL_SRC}" fetch --depth=1 origin "${LKL_REF}" - git -C "${LKL_SRC}" checkout FETCH_HEAD + checkout_lkl_ref "${LKL_REF}" fi else echo " CLONE ${LKL_UPSTREAM} -> ${LKL_SRC}" mkdir -p "$(dirname "${LKL_SRC}")" - if [ -n "${LKL_REF:-}" ]; then - # Shallow clone of a specific ref. - git clone --depth=1 --branch "${LKL_REF}" \ - "${LKL_UPSTREAM}" "${LKL_SRC}" + if [ -n "${LKL_REF:-}" ] && is_commit_sha "${LKL_REF}"; then + # Commit SHAs require full history because remotes typically do not + # advertise arbitrary object IDs as shallow-fetchable refs. + git clone "${LKL_UPSTREAM}" "${LKL_SRC}" + checkout_lkl_ref "${LKL_REF}" else - # Shallow clone of default branch. + # Shallow clone of default branch, then checkout specific branch/tag if + # requested. git clone --branch does not accept bare commit SHAs. git clone --depth=1 "${LKL_UPSTREAM}" "${LKL_SRC}" + if [ -n "${LKL_REF:-}" ]; then + checkout_lkl_ref "${LKL_REF}" + fi fi fi diff --git a/scripts/common.sh b/scripts/common.sh old mode 100755 new mode 100644 index 79e2067..290ff04 --- a/scripts/common.sh +++ b/scripts/common.sh @@ -1,11 +1,17 @@ #!/usr/bin/env bash -# common.sh -- shared helpers for kbox scripts and git hooks. + +# shared helpers for kbox scripts and git hooks. +# +# All functions use portable shell (dash/ash/bash). `local` is not +# strictly POSIX but is supported by every sh implementation we target. +# Source via: . "$(cd "$(dirname "$0")" && pwd)/common.sh" + +# --- Terminal colors --- +# Call set_colors before using RED/GREEN/YELLOW/NC etc. set_colors() { - local default_color - default_color=$(git config --get color.ui 2> /dev/null || echo 'auto') - if [[ "$default_color" == "always" ]] || [[ "$default_color" == "auto" && -t 1 ]]; then + if [ -t 1 ]; then RED='\033[1;31m' GREEN='\033[1;32m' YELLOW='\033[1;33m' @@ -24,19 +30,101 @@ set_colors() fi } -# Print a fatal error message and exit. +# --- Error handling --- + +# Simple fatal error (no colors, works before set_colors). +die() +{ + echo "error: $*" >&2 + exit 1 +} + +# Formatted fatal error (uses RED/NC from set_colors). throw() { local fmt="$1" shift - printf "\n${RED}[!] $fmt${NC}\n" "$@" >&2 + # shellcheck disable=SC2059 + printf "\n${RED}[!] ${fmt}${NC}\n" "$@" >&2 exit 1 } -# Skip hooks entirely when running in CI (GitHub Actions, etc.). +# --- CI detection --- + check_ci() { if [ -n "${CI:-}" ] || [ -d "/home/runner/work" ]; then exit 0 fi } + +# --- Architecture detection --- +# Sets ARCH to x86_64 or aarch64. Accepts an optional override argument; +# defaults to the host architecture via uname -m. + +detect_arch() +{ + case "${1:-$(uname -m)}" in + x86_64 | amd64) ARCH="x86_64" ;; + aarch64 | arm64) ARCH="aarch64" ;; + *) die "unsupported architecture: ${1:-$(uname -m)}" ;; + esac +} + +# --- Download helpers --- +# download_file URL OUTPUT -- fetch URL to OUTPUT via curl or wget. + +download_file() +{ + local url="$1" + local output="$2" + + if command -v curl > /dev/null 2>&1; then + curl -fSL -o "$output" "$url" || die "download failed: $url" + elif command -v wget > /dev/null 2>&1; then + wget -q -O "$output" "$url" || die "download failed: $url" + else + die "neither curl nor wget found" + fi +} + +# --- SHA256 verification --- +# verify_sha256 FILE SHA256_FILE [PATTERN] +# Looks up PATTERN (default: basename of FILE) in SHA256_FILE and +# verifies the hash. Removes FILE on mismatch. No-op if SHA256_FILE +# is missing or has no matching entry. + +verify_sha256() +{ + local file="$1" + local sha256_file="$2" + local pattern="${3:-$(basename "$file")}" + + [ -f "$sha256_file" ] || return 0 + + local expected + expected=$(awk -v f="$pattern" '$2 == f { print $1 }' "$sha256_file") + [ -n "$expected" ] || return 0 + + local actual + actual=$(sha256sum "$file" | awk '{print $1}') + if [ "$actual" != "$expected" ]; then + rm -f "$file" + die "SHA256 mismatch for ${pattern}" + fi + echo "SHA256 verified." +} + +# --- Timeout command detection --- +# Sets TIMEOUT_CMD to "timeout", "gtimeout", or "" (not available). + +find_timeout_cmd() +{ + if command -v timeout > /dev/null 2>&1; then + TIMEOUT_CMD="timeout" + elif command -v gtimeout > /dev/null 2>&1; then + TIMEOUT_CMD="gtimeout" + else + TIMEOUT_CMD="" + fi +} diff --git a/scripts/fetch-busybox.sh b/scripts/fetch-busybox.sh index c758802..b6f863a 100755 --- a/scripts/fetch-busybox.sh +++ b/scripts/fetch-busybox.sh @@ -7,17 +7,13 @@ set -eu +. "$(cd "$(dirname "$0")" && pwd)/common.sh" + ARCH="${1:-x86_64}" BUSYBOX_VERSION="${BUSYBOX_VERSION:-1.36.1}" OUTDIR="deps" OUTFILE="${OUTDIR}/busybox" -die() -{ - echo "error: $*" >&2 - exit 1 -} - case "$ARCH" in x86_64) URL="https://busybox.net/downloads/binaries/${BUSYBOX_VERSION}-defconfig-multiarch-musl/busybox-x86_64" @@ -38,14 +34,7 @@ if [ -x "$OUTFILE" ]; then fi echo "Downloading busybox ${BUSYBOX_VERSION} (${ARCH})..." - -if command -v curl > /dev/null 2>&1; then - curl -fL -o "$OUTFILE" "$URL" || die "curl download failed" -elif command -v wget > /dev/null 2>&1; then - wget -q -O "$OUTFILE" "$URL" || die "wget download failed" -else - die "Neither curl nor wget found." -fi +download_file "$URL" "$OUTFILE" chmod +x "$OUTFILE" echo "OK: ${OUTFILE}" diff --git a/scripts/fetch-lkl.sh b/scripts/fetch-lkl.sh index f6e9b3b..77dd241 100755 --- a/scripts/fetch-lkl.sh +++ b/scripts/fetch-lkl.sh @@ -13,15 +13,9 @@ set -eu -# Auto-detect architecture. -case "${1:-$(uname -m)}" in - x86_64 | amd64) ARCH="x86_64" ;; - aarch64 | arm64) ARCH="aarch64" ;; - *) - echo "error: unsupported architecture: ${1:-$(uname -m)}" >&2 - exit 1 - ;; -esac +. "$(cd "$(dirname "$0")" && pwd)/common.sh" + +detect_arch "${1:-}" LKL_DIR="${LKL_DIR:-lkl-${ARCH}}" REPO="${KBOX_REPO:-sysprog21/kbox}" @@ -29,12 +23,6 @@ NIGHTLY_TAG="${KBOX_LKL_TAG:-lkl-nightly}" ASSET="liblkl-${ARCH}.tar.gz" SHA256_FILE="scripts/lkl-sha256.txt" -die() -{ - echo "error: $*" >&2 - exit 1 -} - mkdir -p "$LKL_DIR" # Already present? @@ -54,18 +42,7 @@ try_release() echo "Downloading ${URL}..." curl -fSL -o "${LKL_DIR}/${ASSET}" "$URL" || return 1 - # Verify SHA256 if pinfile has an entry for this asset. - if [ -f "$SHA256_FILE" ]; then - EXPECTED=$(grep "$ASSET" "$SHA256_FILE" | awk '{print $1}') - if [ -n "$EXPECTED" ]; then - ACTUAL=$(sha256sum "${LKL_DIR}/${ASSET}" | awk '{print $1}') - if [ "$ACTUAL" != "$EXPECTED" ]; then - rm -f "${LKL_DIR}/${ASSET}" - die "SHA256 mismatch for ${ASSET}" - fi - echo "SHA256 verified." - fi - fi + verify_sha256 "${LKL_DIR}/${ASSET}" "$SHA256_FILE" "$ASSET" tar xzf "${LKL_DIR}/${ASSET}" -C "$LKL_DIR" rm -f "${LKL_DIR}/${ASSET}" @@ -89,6 +66,8 @@ try_gh() return 1 } + verify_sha256 "${TMPDIR}/${ASSET}" "$SHA256_FILE" "$ASSET" + tar xzf "${TMPDIR}/${ASSET}" -C "$LKL_DIR" rm -rf "$TMPDIR" return 0 diff --git a/scripts/mkrootfs.sh b/scripts/mkrootfs.sh index 13f8028..3b6523d 100755 --- a/scripts/mkrootfs.sh +++ b/scripts/mkrootfs.sh @@ -15,6 +15,8 @@ set -eu +. "$(cd "$(dirname "$0")" && pwd)/common.sh" + SIZE_MB="${1:-128}" OUTFILE="${ROOTFS:-alpine.ext4}" GUEST_DIR="tests/guest" @@ -24,22 +26,13 @@ STAGING="" ALPINE_VERSION="3.21" if [ -z "${ALPINE_ARCH:-}" ]; then - case "$(uname -m)" in - aarch64 | arm64) ALPINE_ARCH="aarch64" ;; - x86_64 | amd64) ALPINE_ARCH="x86_64" ;; - *) die "Unsupported host architecture: $(uname -m). Set ALPINE_ARCH explicitly." ;; - esac + detect_arch + ALPINE_ARCH="$ARCH" fi ALPINE_TARBALL="alpine-minirootfs-${ALPINE_VERSION}.0-${ALPINE_ARCH}.tar.gz" ALPINE_URL="https://dl-cdn.alpinelinux.org/alpine/v${ALPINE_VERSION}/releases/${ALPINE_ARCH}/${ALPINE_TARBALL}" ALPINE_SHA256_FILE="scripts/alpine-sha256.txt" -die() -{ - echo "error: $*" >&2 - exit 1 -} - cleanup() { if [ -n "$STAGING" ] && [ -d "$STAGING" ]; then @@ -57,27 +50,10 @@ TARBALL_PATH="${CACHE_DIR}/${ALPINE_TARBALL}" if [ ! -f "$TARBALL_PATH" ]; then echo "Downloading Alpine minirootfs ${ALPINE_VERSION} (${ALPINE_ARCH})..." - if command -v curl > /dev/null 2>&1; then - curl -fSL -o "$TARBALL_PATH" "$ALPINE_URL" || die "Download failed." - elif command -v wget > /dev/null 2>&1; then - wget -q -O "$TARBALL_PATH" "$ALPINE_URL" || die "Download failed." - else - die "Neither curl nor wget found." - fi + download_file "$ALPINE_URL" "$TARBALL_PATH" fi -# Verify SHA256. -if [ -f "$ALPINE_SHA256_FILE" ]; then - EXPECTED=$(awk -v f="$ALPINE_TARBALL" '$2 == f { print $1 }' "$ALPINE_SHA256_FILE") - if [ -n "$EXPECTED" ]; then - ACTUAL=$(sha256sum "$TARBALL_PATH" | awk '{print $1}') - if [ "$ACTUAL" != "$EXPECTED" ]; then - rm -f "$TARBALL_PATH" - die "SHA256 mismatch: expected ${EXPECTED}, got ${ACTUAL}" - fi - echo "SHA256 verified." - fi -fi +verify_sha256 "$TARBALL_PATH" "$ALPINE_SHA256_FILE" "$ALPINE_TARBALL" # Create staging directory and extract Alpine rootfs. STAGING=$(mktemp -d) diff --git a/scripts/run-stress.sh b/scripts/run-stress.sh index fefdb4d..1948c72 100755 --- a/scripts/run-stress.sh +++ b/scripts/run-stress.sh @@ -12,6 +12,7 @@ set -eu # Suppress via LSAN_OPTIONS suppression file (see scripts/lsan-suppressions.txt) # rather than blanket detect_leaks=0, so kbox's own leaks are still caught. SCRIPT_DIR="$(cd "$(dirname "$0")" && pwd)" +. "${SCRIPT_DIR}/common.sh" SUPP="suppressions=${SCRIPT_DIR}/lsan-suppressions.txt" export LSAN_OPTIONS="${LSAN_OPTIONS:+${LSAN_OPTIONS}:}${SUPP}" @@ -26,37 +27,12 @@ SKIP=0 # safety net for hangs. TIMEOUT="${STRESS_TIMEOUT:-60}" -# Colors (if terminal supports them). -if [ -t 1 ]; then - RED='\033[0;31m' - GREEN='\033[0;32m' - YELLOW='\033[0;33m' - NC='\033[0m' -else - RED='' - GREEN='' - YELLOW='' - NC='' -fi - -die() -{ - echo "error: $*" >&2 - exit 1 -} +set_colors +find_timeout_cmd [ -x "$KBOX" ] || die "kbox binary not found at ${KBOX}" [ -f "$ROOTFS" ] || die "rootfs image not found at ${ROOTFS}" -# Check if timeout command is available. -if command -v timeout > /dev/null 2>&1; then - TIMEOUT_CMD="timeout" -elif command -v gtimeout > /dev/null 2>&1; then - TIMEOUT_CMD="gtimeout" -else - TIMEOUT_CMD="" -fi - run_stress_test() { name="$1" diff --git a/scripts/run-tests.sh b/scripts/run-tests.sh index 8481eb1..de60984 100755 --- a/scripts/run-tests.sh +++ b/scripts/run-tests.sh @@ -13,6 +13,7 @@ set -eu # Suppress via LSAN_OPTIONS suppression file (see scripts/lsan-suppressions.txt) # rather than blanket detect_leaks=0, so kbox's own leaks are still caught. SCRIPT_DIR="$(cd "$(dirname "$0")" && pwd)" +. "${SCRIPT_DIR}/common.sh" SUPP="suppressions=${SCRIPT_DIR}/lsan-suppressions.txt" export LSAN_OPTIONS="${LSAN_OPTIONS:+${LSAN_OPTIONS}:}${SUPP}" @@ -22,38 +23,13 @@ PASS=0 FAIL=0 SKIP=0 -# Colors (if terminal supports them). -if [ -t 1 ]; then - RED='\033[0;31m' - GREEN='\033[0;32m' - YELLOW='\033[0;33m' - NC='\033[0m' -else - RED='' - GREEN='' - YELLOW='' - NC='' -fi - -die() -{ - echo "error: $*" >&2 - exit 1 -} +set_colors [ -x "$KBOX" ] || die "kbox binary not found at ${KBOX}" [ -f "$ROOTFS" ] || die "rootfs image not found at ${ROOTFS}" KBOX_TEST_TIMEOUT="${KBOX_TEST_TIMEOUT:-30}" - -# Detect timeout command (GNU coreutils on Linux, gtimeout on macOS). -if command -v timeout > /dev/null 2>&1; then - TIMEOUT_CMD="timeout" -elif command -v gtimeout > /dev/null 2>&1; then - TIMEOUT_CMD="gtimeout" -else - TIMEOUT_CMD="" -fi +find_timeout_cmd run_with_timeout() {