From b9dfe7c365a530ec38ed3a7b45f509e3402bb5ce Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Pablo=20Correa=20G=C3=B3mez?= Date: Sat, 8 Aug 2026 20:00:15 +0200 Subject: [PATCH] main/postmarketos-testlib: new aport MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Package the script currently in .ci/lib/testlib.sh in pmaports so that it can be used by packages during abuild check step Signed-off-by: Pablo Correa Gómez Part-of: --- main/postmarketos-testlib/APKBUILD | 20 ++ main/postmarketos-testlib/testlib.sh | 283 +++++++++++++++++++++++++++ 2 files changed, 303 insertions(+) create mode 100644 main/postmarketos-testlib/APKBUILD create mode 100755 main/postmarketos-testlib/testlib.sh diff --git a/main/postmarketos-testlib/APKBUILD b/main/postmarketos-testlib/APKBUILD new file mode 100644 index 000000000..ebe7f33c3 --- /dev/null +++ b/main/postmarketos-testlib/APKBUILD @@ -0,0 +1,20 @@ +maintainer="Clayton Craft " +pkgname=postmarketos-testlib +pkgver=1.0.0 +pkgrel=0 +pkgdesc="Helper scripts for shell testing" +url="https://postmarketos.org" +arch="noarch" +license="MIT" +depends="busybox-binsh" +source="testlib.sh" +options="!check" # no tests + +package() { + install -Dm755 "$srcdir/testlib.sh" \ + "$pkgdir/usr/bin/postmarketos-testlib" +} + +sha512sums=" +6f468d4b2075dc25b3a48f941ae45fe358700cb3b8495aeb5bbebf7437c678290cb790e9832436eee74d78bf0bfcfba1962a339d2fa564ee4a07ac879718bf86 testlib.sh +" diff --git a/main/postmarketos-testlib/testlib.sh b/main/postmarketos-testlib/testlib.sh new file mode 100755 index 000000000..50999f1e1 --- /dev/null +++ b/main/postmarketos-testlib/testlib.sh @@ -0,0 +1,283 @@ +#!/bin/sh +# Unit testing framework for shell scripts +# Invoke with the path to a tests/ subdir of a package and the +# package name as arguments. Or source it with the following +# variables set: +# * results_dir - second argument to the test script + +set -e + +# Colors :D +RED="\e[31m" +GREEN="\e[32m" +YELLOW="\e[33m" +BLUE="\e[34m" +BOLDBLUE="\e[1;34m" +RESET="\e[0m" + +testlib_path="$(realpath "$0")" + +_test_assert_failed="" +_test_testn=1 +_test_passedn=0 +_test_failedn=0 +_test_current_test="" +_test_statefile="$(mktemp)" +_test_original_pwd="" +_test_temp_root="" +# Set by each test +results_dir="${results_dir:-}" + +test_log() { + # ash supports echo -e + # shellcheck disable=SC3037 + echo -e "$*" >&2 +} + +test_info() { + # shellcheck disable=SC3037 + echo -e "${BLUE}$*${RESET}" >&2 +} + +test_debug() { + # shellcheck disable=SC3037 + echo -e "${YELLOW}$*${RESET}" >&2 +} + +create_test_root() { + _test_original_pwd="$(pwd)" + _test_temp_root="$(mktemp -d)" + cd "$_test_temp_root" +} + +cleanup_test_root() { + if [ -n "$_test_original_pwd" ]; then + cd "$_test_original_pwd" + fi + if [ -n "$_test_temp_root" ] && [ -d "$_test_temp_root" ]; then + rm -rf "$_test_temp_root" + fi + _test_original_pwd="" + _test_temp_root="" +} + +assert_strequal() { + # $1: first string + # $2: second string + if [ -z "$1" ] || [ -z "$2" ]; then + test_log "ERROR: assert_equal: not enough arguments given" + exit 1 + fi + if [ "$1" != "$2" ]; then + _test_assert_failed="$1 != $2" + test_log " ❌ $_test_assert_failed" + return + fi + test_log " ✅ $1 == $2" +} + +assert_equal() { + # $1: name of variable + # $2: value to compare + if [ -z "$1" ] || [ -z "$2" ]; then + test_log "ERROR: assert_equal: not enough arguments given" + exit 1 + fi + val="$(eval echo "\$$1")" + if [ "$val" != "$2" ]; then + _test_assert_failed="\$$1 != $2 (got $val)" + test_log " ❌ $_test_assert_failed" + return + fi + test_log " ✅ \$$1 == $2" +} + +assert_unset() { + if [ -z "$1" ]; then + test_log "ERROR: assert_unset: not enough arguments given" + exit 1 + fi + val="$(eval echo \$"$1")" + if [ -n "$val" ]; then + _test_assert_failed="\$$1 should be unset (has value '$val')" + test_log " ❌ $_test_assert_failed" + return + fi + test_log " ✅ \$$1 is unset" +} + +assert_exists() { + # $1: filename to test + if [ -z "$1" ]; then + test_log "ERROR: assert_exists: not enough arguments given" + exit 1 + fi + if ! [ -e "$1" ]; then + _test_assert_failed="\$$1 should exist but does not" + test_log " ❌ $_test_assert_failed" + return + fi + test_log " ✅ \$$1 exists" +} + +assert_not_exists() { + if [ -z "$1" ]; then + test_log "ERROR: assert_not_exists: not enough arguments given" + exit 1 + fi + if [ -e "$1" ]; then + _test_assert_failed="\$$1 should NOT exist but does" + test_log " ❌ $_test_assert_failed" + return + fi + test_log " ✅ \$$1 does not exist" +} + +assert_contains() { + # $1: string to search in + # $2: substring to find + if [ -z "$1" ] || [ -z "$2" ]; then + test_log "ERROR: assert_contains: not enough arguments given" + exit 1 + fi + case "$1" in + *"$2"*) + test_log " ✅ '$2' found in '$1'" + ;; + *) + _test_assert_failed="'$2' not found in '$1'" + test_log " ❌ $_test_assert_failed" + return + ;; + esac +} + +assert_not_contains() { + # $1: string to search in + # $2: substring that should not be found + if [ -z "$1" ] || [ -z "$2" ]; then + test_log "ERROR: assert_not_contains: not enough arguments given" + exit 1 + fi + case "$1" in + *"$2"*) + _test_assert_failed="'$2' found in '$1' but should not be" + test_log " ❌ $_test_assert_failed" + return + ;; + *) + test_log " ✅ '$2' not found in '$1'" + ;; + esac +} + +# Log some expression and evaluate it +logeval() { + eval "$*" + test_log "${YELLOW}+ $*${RESET}" +} + +# Start a test. Shell scripts involved in the test should be sourced AFTER calling this function! +start_test() { + if [ -n "$_test_current_test" ]; then + test_log "ERROR: multiple calls to start_test without calling end_test first!" + test_log "Current test: $_test_current_test" + exit 1 + fi + test_log "==> Test $_test_testn: $1" + _test_current_test="$1" + set > "$_test_statefile" +} + +# End a test and reset the environment. +end_test() { + local msg + local save_assert_failed + + # Restore environment + save_assert_failed="$_test_assert_failed" + + # Splitting is intentional + # shellcheck disable=SC2046 + unset $(set | grep "='" | cut -d= -f1 | grep -vE "(PATH|IFS|TERM|LANG|HOME|PWD|SHELL|USER|save_assert_failed|_test_statefile)" ) + + # shellcheck disable=SC1090 + . "$_test_statefile" + _test_assert_failed="$save_assert_failed" + + if [ -n "$_test_assert_failed" ]; then + msg="${RED}Failed! ❌ ${_test_assert_failed}${RESET}" + echo "Test $_test_testn: $_test_assert_failed" >> "$results_dir/failed" + _test_failedn=$((_test_failedn+1)) + echo "$_test_failedn" > "$results_dir/failedn" + unset _test_assert_failed + else + msg="${GREEN}Passed!${RESET}" + _test_passedn=$((_test_passedn+1)) + echo "$_test_passedn" > "$results_dir/passedn" + fi + test_log "<== Test $_test_testn: $msg $_test_current_test\n" + _test_testn=$((_test_testn+1)) + _test_current_test="" +} + +# Call at the end of each testsuite shell script +end_testsuite() { + if [ -n "$_test_current_test" ]; then + test_log "${RED}ERROR: end_testsuite called without calling end_test first!${RESET}" + test_log "${RED}Current test: $_test_current_test${RESET}" + exit 1 + fi + if [ -f "$results_dir/failed" ]; then + exit 1 + fi + + exit 0 +} + +run_tests() { + local failed test_results_base + local test name total passedn failedn t_passed t_failed + + test_results_base="$(mktemp -d)" + test_log "Running tests for $2" + + # FIXME: yeah this isn't quite the best way to iterate the files + # shellcheck disable=SC2010 + for name in $(ls "$1" | grep "\.sh$"); do + test="$1/$name" + mkdir "$test_results_base/$name" + echo 0 > "$test_results_base/$name/passedn" + echo 0 > "$test_results_base/$name/failedn" + test_log + test_log "${BOLDBLUE}# Test suite ${name}${RESET}" + $test "$testlib_path" "$test_results_base/$name" || failed="$failed $name" + t_passed=$(cat "$test_results_base/$name/passedn") + t_failed=$(cat "$test_results_base/$name/failedn") + passedn=$((passedn+t_passed)) + failedn=$((failedn+t_failed)) + total=$((total+t_passed+t_failed)) + done + if [ -n "$failed" ]; then + test_log + test_log "Some tests failed:" + for test in $failed; do + test_log "❌ $test" + test_log " ${RED}$(cat "$test_results_base/$test/failed")${RESET}" + done + fi + + if [ $passedn -lt $total ]; then + test_log "[${RED}$passedn/$total tests passed${RESET}]" + else + test_log "[${GREEN}$passedn/$total tests passed!${RESET}]" + fi + test_log + + if [ -n "$failed" ]; then exit 1; else exit 0; fi +} + +# FIXME: probably not the best way to determine if this was sourced +if [ $# -gt 0 ]; then + run_tests "$1" "$2" +fi