diff --git a/.gitignore b/.gitignore index e2e7327..78c1042 100644 --- a/.gitignore +++ b/.gitignore @@ -1 +1,2 @@ /out +/iso_out diff --git a/Dockerfile b/Dockerfile index fd7757e..9e1d6c1 100644 --- a/Dockerfile +++ b/Dockerfile @@ -3,11 +3,11 @@ FROM archlinux:base ARG ALEZ_BUILD_DIR='/opt/alez' ARG ARCHZFS_KEY='F75D9D76' -RUN pacman -Syu --noconfirm --needed base base-devel git archiso reflector curl wget +RUN pacman -Syu --noconfirm --needed base base-devel git archiso reflector curl wget dialog RUN mkdir ~/.gnupg && echo "disable-ipv6" >> ~/.gnupg/dirmngr.conf RUN pacman-key --init && pacman-key --populate archlinux && \ - if ! pacman-key -r "${ARCHZFS_KEY}"; then pacman-key -r "${ARCHZFS_KEY}" --keyserver hkp://pool.sks-keyservers.net:80; fi && pacman-key --lsign-key "${ARCHZFS_KEY}" + if ! pacman-key -r "${ARCHZFS_KEY}"; then pacman-key -r "${ARCHZFS_KEY}" --keyserver hkps://keyserver.ubuntu.com; fi && pacman-key --lsign-key "${ARCHZFS_KEY}" RUN mkdir -p "${ALEZ_BUILD_DIR}" && \ cp -r /usr/share/archiso/configs/releng "${ALEZ_BUILD_DIR}/iso" && \ @@ -23,7 +23,7 @@ RUN sed -i '/^\[core\]/i [archzfs]\n\ Server = http://archzfs.com/$repo/x86_64\n' \ "${ALEZ_BUILD_DIR}/iso/pacman.conf" -RUN printf 'git\narchzfs-linux\nreflector\nwget\nlinux\nlinux-firmware\ndhcpcd\nless\nmdadm' >> \ +RUN printf 'git\narchzfs-linux\nreflector\nwget\nlinux\nlinux-firmware\ndhcpcd\nless\nmdadm\ndialog' >> \ "${ALEZ_BUILD_DIR}/iso/packages.x86_64" COPY motd "${ALEZ_BUILD_DIR}/iso/airootfs/etc/" @@ -31,9 +31,11 @@ COPY motd "${ALEZ_BUILD_DIR}/iso/airootfs/etc/" # Copy in current directory to allow git tag checking COPY . "${ALEZ_BUILD_DIR}/iso/airootfs/usr/local/share/ALEZ" +COPY alez.sh "/usr/local/share/ALEZ/alez.sh" +RUN chmod u+x "/usr/local/share/ALEZ/alez.sh" RUN ln -s "/usr/local/share/ALEZ/alez.sh" "${ALEZ_BUILD_DIR}/iso/airootfs/usr/local/bin/alez" VOLUME "${ALEZ_BUILD_DIR}/iso/out" WORKDIR "${ALEZ_BUILD_DIR}/iso" -CMD ["/usr/bin/mkarchiso"] +CMD ["/usr/bin/mkarchiso", "./"] diff --git a/alez.sh b/alez.sh index 718c358..d974612 100755 --- a/alez.sh +++ b/alez.sh @@ -1,11 +1,14 @@ -#!/bin/bash +#!/usr/bin/env bash # shellcheck disable=SC2015 # Arch Linux Easy ZFS (ALEZ) installer 1.2 # by Dan MacDonald with contributions from John Ramsden # Exit on error -set -o errexit -o errtrace +set -o errexit -o errtrace -o pipefail -o nounset +# Restrict internal field separator +IFS=$'\n\t' + # Set a default locale during install to avoid mandb error when indexing man pages export LANG=C @@ -19,6 +22,20 @@ version=1.2 RED='\033[0;31m' NC='\033[0m' # No Color +# To make bash booleans "saner" +# Anything set to these values will be expected to be used like so: +# value=$true +# if [ $value ]; then ... +# if [ ! $value ]; then ... +# or +# if [ $value1 ] && [ $value2 ]; then ... +# or +# [ $value ] && code_that_only_runs_if_value_is_true +# or +# [ $value ] || code_that_only_runs_if_value_is_false +false= +true=0 + installdir="/mnt" archzfs_pgp_key="F75D9D76" zroot="zroot" @@ -29,11 +46,11 @@ repo_remote="danboid/ALEZ" HEIGHT=0 WIDTH=0 -show_path=false +show_path=$false declare -a base_packages base_packages=( - 'base' 'nano' 'linux-firmware' 'man-db' 'man-pages' 'vi' 'less' + 'base' 'nano' 'linux-firmware' 'man-db' 'man-pages' 'vi' 'less' 'dialog' ) declare -a zpool_bios_features @@ -47,6 +64,30 @@ zpool_bios_features=( 'feature@userobj_accounting=disabled' ) +# debug/test IO mode switches +# The idea is that output normally dumped to /dev/null in prod +# might want to be visibled somehow in a dev or test env. +STDOUT=/dev/stdout +STDERR=/dev/stderr +DEVNULL=/dev/null +IO_OUT=$STDOUT +IO_ERR=$STDERR +IO_NULL=$DEVNULL +# redirect all IO output to stdout in debug or test env mode +PROD=$true +if [ ${DEBUG:-} ] || [ ${TEST:-} ]; then + PROD=$false +fi + +if [ ! $PROD ]; then + IO_OUT=$STDOUT + IO_ERR=$STDERR + IO_NULL=$STDERR +fi + +# Echo to standard error if not in prod, otherwise do nothing (useful for debugging) +errcho() { [ ! $PROD ] && cat <<< "$@" 1>&2; } + check_latest() { # If repo doesn't exist, skip check [ -d "${repo_directory}" ] || return 0 @@ -72,7 +113,7 @@ check_latest() { } check_internet() { - ping -c 1 archlinux.org &> /dev/null || return 1 + ping -c 1 archlinux.org &>"${IO_NULL}" || return 1 return 0 } @@ -85,7 +126,7 @@ unmount_cleanup() { { umount -R "${installdir}" || : ; zfs umount -a && zpool export "${zroot}" || : ; - } &> /dev/null + } &>"${IO_NULL}" } bootloader_message() { @@ -123,7 +164,7 @@ lsdsks() { update_parts() { # shellcheck disable=SC2046 - mapfile -t partids < <(ls /dev/disk/by-id/* $(${show_path} && ls /dev/vd* || : ;)) + mapfile -t partids < <(ls /dev/disk/by-id/* $( [ $show_path ] && ls /dev/vd* || : ;)) ptcount=${#partids[@]} } @@ -152,9 +193,9 @@ lsparts() { } zap_partition(){ - vgchange -an &> /dev/null - mdadm --zero-superblock --force "${1}" &> /dev/null - sgdisk --zap-all "${1}" &> /dev/null + vgchange -an &>"${IO_NULL}" + mdadm --zero-superblock --force "${1}" &>"${IO_NULL}" + sgdisk --zap-all "${1}" &>"${IO_NULL}" } bios_partitioning(){ @@ -225,7 +266,7 @@ get_matching_kernel() { mkdir -p "${pkgdir}" { pushd "${pkgdir}" && wget "${url}" && popd - } &> /dev/null + } &>"${IO_NULL}" chrun "pacman -U --noconfirm /${pkg}" && rm "${pkgdir}/${pkg}" @@ -235,14 +276,14 @@ get_matching_kernel() { } refresh_mirrors() { - pacman -Sy --noconfirm &> /dev/null + pacman -Sy --noconfirm &>"${IO_NULL}" - if hash reflector 2> /dev/null; then + if hash reflector 2>"${IO_NULL}"; then { echo "Refreshing mirrorlist" reflector --verbose --latest 25 \ --sort rate --save /etc/pacman.d/mirrorlist || : - } 2> /dev/null | dialog --progressbox ${HEIGHT} ${WIDTH} + } 2>"${IO_NULL}" | dialog --progressbox ${HEIGHT} ${WIDTH} fi } @@ -255,12 +296,12 @@ install_arch(){ else pacstrap "${installdir}" linux-headers linux "${base_packages[@]}" fi - } 2> /dev/null + } 2>"${IO_NULL}" chrun "if ! pacman-key -r F75D9D76; then - pacman-key -r F75D9D76 --keyserver hkp://pool.sks-keyservers.net:80; + pacman-key -r F75D9D76 --keyserver hkps://keyserver.ubuntu.com; fi && pacman-key --lsign-key F75D9D76" \ - "Adding Arch ZFS repo key in chroot..." 2> /dev/null + "Adding Arch ZFS repo key in chroot..." 2>"${IO_NULL}" echo "Add fstab entries..." fstab_output="$(genfstab -U "${installdir}")" @@ -290,7 +331,7 @@ install_arch(){ else chrun "pacman -Sy; pacman -S --noconfirm zfs-linux" "Installing ZFS in chroot..." fi - } 2> /dev/null + } 2>"${IO_NULL}" echo -e "Enable systemd ZFS service...\n" chrun "systemctl enable zfs.target" @@ -313,7 +354,7 @@ menuentry \"Arch Linux ZFS\" {\n\ } install_grub(){ - chrun "grub-install /dev/${disks[${1}]}" "Installing GRUB to /dev/${disks[${1}]}..." 2> /dev/null + chrun "grub-install /dev/${disks[${1}]}" "Installing GRUB to /dev/${disks[${1}]}..." 2>"${IO_NULL}" } install_grub_efi(){ @@ -325,7 +366,7 @@ install_grub_efi(){ # Install GRUB EFI chrun "grub-install --target=x86_64-efi --efi-directory=${1} --bootloader-id=GRUB" \ "Installing grub-efi to ${1}" - } 2> /dev/null + } 2>"${IO_NULL}" } gen_sdboot_entry(){ @@ -338,7 +379,7 @@ EOF } install_sdboot(){ - chrun "bootctl --path=${1} install" "Installing systemd-boot to ${1}" 2> /dev/null + chrun "bootctl --path=${1} install" "Installing systemd-boot to ${1}" 2>"${IO_NULL}" mkdir -p "${installdir}/${1}/loader/entries" if [[ "${kernel_type}" =~ ^(l|L)$ ]]; then gen_sdboot_entry "${1}" "linux-lts" @@ -414,8 +455,11 @@ define() { fetch_archzfs_key() { declare -a keyservers=( - 'hkp://pool.sks-keyservers.net:80' - # 'hkp://pgp.mit.edu:80' # Replace with working keyservers + 'hkps://keyserver.ubuntu.com' + 'hkps://pgp.mit.edu' + 'hkp://pgp.mit.edu:80' + # SKS keyservers are permanently down due to GDPR filings + # 'hkp://pool.sks-keyservers.net:80' # 'hkp://ipv4.pool.sks-keyservers.net:80' ) @@ -429,18 +473,23 @@ fetch_archzfs_key() { pacman-key --lsign-key "${archzfs_pgp_key}" && return 0 fi done - } &> /dev/null + } &>"${IO_NULL}" return 1 } init_keyring() { declare -a keyservers=( - 'hkp://pool.sks-keyservers.net:80' - # 'hkp://pgp.mit.edu:80' # Replace with working keyservers + 'hkps://keyserver.ubuntu.com' + 'hkps://pgp.mit.edu' + 'hkp://pgp.mit.edu:80' + # SKS keyservers are permanently down due to GDPR filings + # 'hkp://pool.sks-keyservers.net:80' # 'hkp://ipv4.pool.sks-keyservers.net:80' ) - pacman-key --init &> /dev/null + pacman -S --noconfirm archlinux-keyring + pacman-key --init &>"${IO_NULL}" + pacman-key --populate archlinux { # Try default keyserver first @@ -448,7 +497,7 @@ init_keyring() { for ks in "${keyservers[@]}"; do pacman-key --refresh-keys --keyserver "${ks}" && return 0 done - } &>/dev/null + } &>"${IO_NULL}" return 1 } @@ -521,7 +570,7 @@ fi # Check if any VirtIO devices exist if lsblk | grep -E 'vd.*disk'; then - [ -d /dev/disk/by-path/ ] && show_path=true + [ -d /dev/disk/by-path/ ] && show_path=$true fi # No frills GPT partitioning @@ -585,20 +634,36 @@ while dialog --clear --title "New zpool?" --yesno "${msg}" $HEIGHT $WIDTH; do partinfo="$(get_parts)" if [ "$zpconf" == "s" ]; then + errcho "zpconf=s" msg="Select a partition.\n\nIf you used alez to create your partitions,\nyou likely want the one ending with -part2" # shellcheck disable=SC2086 zps=$(dialog --stdout --clear --title "Choose partition" \ --menu "${msg}" $HEIGHT $WIDTH "$(( 2 + ptcount))" ${partinfo}) if [[ "${install_type}" =~ ^(b|B)$ ]]; then # shellcheck disable=SC2046 + errcho "zpconf=s" + errcho "features=$(print_features)" + errcho "zroot=${zroot}" + errcho "zps=$zps" + errcho "partids=${partids[$zps]}" + errcho "install_type=${install_type}" + errcho "zpool create -f -d -m none -o ashift=12 $(print_features) ${zroot} ${partids[$zps]}" zpool create -f -d -m none -o ashift=12 $(print_features) "${zroot}" "${partids[$zps]}" else + errcho "zpconf=s" + errcho "features=$(print_features)" + errcho "zroot=${zroot}" + errcho "zps=$zps" + errcho "partids=${partids[$zps]}" + errcho "install_type=${install_type}" + errcho "zpool create -f -d -m none -o ashift=12 ${zroot} ${partids[$zps]}" zpool create -f -d -m none -o ashift=12 "${zroot}" "${partids[$zps]}" fi dialog --title "Success" --msgbox "Created a single disk zpool with ${partids[$zps]}...." ${HEIGHT} ${WIDTH} break elif [ "$zpconf" == "m" ]; then # shellcheck disable=SC2086 + errcho "zpconf=m" zp1=$(dialog --stdout --clear --title "First zpool partition" \ --menu "Select the number of the first partition" $HEIGHT $WIDTH "$(( 2 + ptcount ))" ${partinfo}) # shellcheck disable=SC2086 @@ -608,10 +673,26 @@ while dialog --clear --title "New zpool?" --yesno "${msg}" $HEIGHT $WIDTH; do echo "Creating a mirrored zpool..." if [[ "${install_type}" =~ ^(b|B)$ ]]; then # shellcheck disable=SC2046 + errcho "zpconf=m" + errcho "features=$(print_features)" + errcho "zroot=${zroot}" + errcho "zp1=$zp1" + errcho "zp2=$zp2" + errcho "partids=${partids[$zp1]},${partids[$zp2]}" + errcho "install_type=${install_type}" + errcho "zpool create ${zroot} mirror -f -d -m none -o ashift=12 $(print_features) ${partids[$zp1]} ${partids[$zp2]}" zpool create "${zroot}" mirror -f -d -m none \ -o ashift=12 \ $(print_features) "${partids[$zp1]}" "${partids[$zp2]}" else + errcho "zpconf=m" + errcho "features=$(print_features)" + errcho "zroot=${zroot}" + errcho "zp1=$zp1" + errcho "zp2=$zp2" + errcho "partids=${partids[$zp1]},${partids[$zp2]}" + errcho "install_type=${install_type}" + errcho "zpool create ${zroot} mirror -f -d -m none -o ashift=12 ${partids[$zp1]} ${partids[$zp2]}" zpool create "${zroot}" mirror -f -d -m none -o ashift=12 "${partids[$zp1]}" "${partids[$zp2]}" fi dialog --title "Success" --msgbox "Created a mirrored zpool with ${partids[$zp1]} ${partids[$zp2]}...." ${HEIGHT} ${WIDTH} @@ -625,7 +706,7 @@ done zfs create -o mountpoint=none "${zroot}"/data zfs create -o mountpoint=legacy "${zroot}"/data/home - { zfs create -o mountpoint=/ "${zroot}"/ROOT/default || : ; } &> /dev/null + { zfs create -o mountpoint=/ "${zroot}"/ROOT/default || : ; } &>"${IO_NULL}" # GRUB only datasets if [[ "${bootloader}" =~ ^(g|G)$ ]]; then @@ -678,6 +759,7 @@ if [[ "${install_type}" =~ ^(u|U)$ ]]; then $HEIGHT $WIDTH "$(( 2 + ptcount))" ${partinfo}) efi_partition="${partids[$esp]}" + errcho "mkfs.fat -F 32 ${efi_partition}" mkfs.fat -F 32 "${efi_partition}"| dialog --progressbox 10 70 mkdir -p "${installdir}${esp_mountpoint}" "${installdir}/boot" @@ -691,26 +773,30 @@ fi dialog --title "Begin install?" --msgbox "Setup complete, begin install?" ${HEIGHT} ${WIDTH} +errcho "Initializing keyring..." if ! init_keyring; then dialog --title "Installation error" \ --msgbox "ERROR: Failed to initialize keyring" ${HEIGHT} ${WIDTH} exit 1 fi +errcho "Fetching archzfs key..." if ! fetch_archzfs_key; then dialog --title "Installation error" \ --msgbox "ERROR: Failed to fetch archzfs key" ${HEIGHT} ${WIDTH} exit 1 fi +errcho "Refreshing mirrors..." refresh_mirrors +errcho "Installing arch..." install_arch | dialog --progressbox 30 70 # Install GRUB BIOS if [[ "${install_type}" =~ ^(b|B)$ ]]; then - chrun "pacman -S --noconfirm grub os-prober" "Installing GRUB in chroot..." 2> /dev/null | dialog --progressbox 30 70 + chrun "pacman -S --noconfirm grub os-prober" "Installing GRUB in chroot..." 2>"${IO_NULL}" | dialog --progressbox 30 70 add_grub_entry @@ -748,7 +834,7 @@ else else install_grub_efi "${esp_mountpoint}" fi - } 2> /dev/null | dialog --progressbox 30 70 + } 2>"${IO_NULL}" | dialog --progressbox 30 70 fi { @@ -758,7 +844,7 @@ fi else chrun "mkinitcpio -p linux" fi -} 2> /dev/null | dialog --progressbox 30 70 +} 2>"${IO_NULL}" | dialog --progressbox 30 70 unmount_cleanup diff --git a/profiledef.sh b/profiledef.sh new file mode 100644 index 0000000..4ef923e --- /dev/null +++ b/profiledef.sh @@ -0,0 +1,10 @@ +#!/usr/bin/env bash +# shellcheck disable=SC2034 + +iso_name="archlinux" +iso_label="ARCH_$(date +%Y%m)" +iso_publisher="Arch Linux " +iso_application="Arch Linux Live/Rescue CD" +iso_version="$(date +%Y.%m.%d)" +install_dir="arch" +bootmodes=() diff --git a/run_archiso b/run_archiso new file mode 100755 index 0000000..662d42a --- /dev/null +++ b/run_archiso @@ -0,0 +1,180 @@ +#!/usr/bin/env bash +# +# Copyright (C) 2020 David Runge +# +# SPDX-License-Identifier: GPL-3.0-or-later +# +# A simple script to run an archiso image using qemu. The image can be booted +# using BIOS or UEFI. +# +# Requirements: +# - qemu +# - edk2-ovmf (when UEFI booting) + + +# set -eu +set -o errexit -o errtrace -o pipefail -o nounset + +print_help() { + local usagetext + IFS='' read -r -d '' usagetext < 0 )); then + while getopts 'abc:dhi:suvx:' flag; do + case "$flag" in + a) + accessibility='on' + ;; + b) + boot_type='bios' + ;; + c) + oddimage="$OPTARG" + ;; + d) + mediatype='hd' + ;; + h) + print_help + exit 0 + ;; + i) + image="$OPTARG" + ;; + u) + boot_type='uefi' + ;; + s) + secure_boot='on' + ;; + v) + display='none' + qemu_options+=(-vnc 'vnc=0.0.0.0:0,vnc=[::]:0') + ;; + x) + extradisk="$OPTARG" + qemu_options+=('-drive' "file=$extradisk,if=virtio,aio=native,media=disk,cache.direct=on") + ;; + *) + printf '%s\n' "Error: Wrong option. Try 'run_archiso -h'." + exit 1 + ;; + esac + done +else + print_help + exit 1 +fi + +check_image +run_image diff --git a/test b/test new file mode 100755 index 0000000..3962891 --- /dev/null +++ b/test @@ -0,0 +1,50 @@ +#!/usr/bin/env bash + +# this script follows unofficial bash strict mode: http://redsymbol.net/articles/unofficial-bash-strict-mode/ +# prereqs: qemu edk2-ovmf docker +# also make sure you have VT-x emulation on if you're running this inside a VM + +# Exit on error +set -o errexit -o errtrace -o pipefail -o nounset +# Restrict internal field separator +IFS=$'\n\t' + +testimagepath="./testdata/test.img" + +# remove all built iso files unless PRESERVE is set +[ ${PRESERVE:-} ] || { + echo "Removing existing ISO's..." + [ -f ./iso_out/*.iso ] && rm -v ./iso_out/*.iso +} + +[ ${SKIP_REBUILD:-} ] || { + echo "Building ALEZ docker image..." + docker build -t aleztest . +} + +# rebuild a new iso unless PRESERVE is set +[ ${PRESERVE:-} ] || { + echo "Building ISO..." + docker run -it --rm --privileged -v $(realpath ./iso_out):/opt/alez/iso/out aleztest +} + +# create a virtual drive to attach to qemu to test/dev if it doesn't already exist +[ -d "./testdata" ] || mkdir -p ./testdata +[ -f "$testimagepath" ] || { + echo "Creating virtual drive image for qemu VM at ${testimagepath}..." + qemu-img create -f qcow2 $testimagepath 10G +} + +echo "Firing up qemu VM using ISO as boot..." +./run_archiso -u -x $testimagepath -i ./iso_out/archlinux-*.iso + +# cleanup; remove the virtual drive +# only runs if the command above was successful +[ $? ] && { + echo "VM exited successfully; removing virtual drive at ${testimagepath}..." + rm $testimagepath +} + +# if there wasn't a failure before now, then exit 0, or else the exit code of the above command would be the exit code +# (see the end of the unofficial bash strict mode article for explanation) +exit 0 diff --git a/tinytestlib b/tinytestlib new file mode 100755 index 0000000..68d07c1 --- /dev/null +++ b/tinytestlib @@ -0,0 +1,160 @@ +#!/usr/bin/env bash + +# Tinytestlib: +# Because all the other solutions I found to do basic bash shell script testing were too large. + +# Exit on error +set -o errexit -o errtrace -o pipefail -o nounset + +# Captures stdout, stderr and return code of any command into the named variables. +capture () { + local _out_var _out _err_var _err _ret_var _ret debugflag + debugflag= + if [ "$1" = "--debug" ]; then + debugflag=1 + shift + fi + if [ "$#" -lt 4 ]; then + echo "Usage: capture [--debug] command [arg ...]" + return 1 + fi + _out_var="$1"; shift + _err_var="$1"; shift + _ret_var="$1"; shift + # just a modification of some nutso magic I found online that captures stdout into $_out, stderr into $_err and return/status code into $_ret + # WITHOUT opening files, because touching the filesystem unnecessarily is sad (slows down tests, etc) + . <({ _err=$({ _out=$("$@"); _ret=$?; } 2>&1; declare -p _out _ret >&2); declare -p _err; } 2>&1) + if [ $debugflag ]; then + echo "cmd: $@" + echo "out: $_out" + echo "err: $_err" + echo "ret: $_ret" + fi + read $_out_var <<<$(printf "%b" "$_out") + read $_err_var <<<$(printf "%b" "$_err") + read $_ret_var <<<$(printf "%b" "$_ret") +} + +red_text () { + echo -en "\e[31m${1}\e[0m" +} + +green_text () { + echo -en "\e[32m${1}\e[0m" +} + +hex_encode () { + printf "%b" "$*" | hexdump -ve '/1 "%02x"' +} + +hex_decode () { + printf "%b" "$*" | xxd -r -p +} + +_assert_equality_or_lack_thereof () { + local comp_op arg1_enc arg2_enc + comp_op="$1" + shift + # to tapdance around escaping issues, I just encode to hex and compare those. + # This may or may not be necessary anymore, but it fixed issues in the past + # Wish it didn't have to fire up a subshell though; future tweak? + arg1_enc=$(hex_encode $1) + arg2_enc=$(hex_encode $2) + if [ "$arg1_enc" $comp_op "$arg2_enc" ]; then + green_text "." + return 0 + else + red_text "F" + red_text "assert_equal failure: values <$1> and <$2> do not match" >&2 + echo "" >&2 + return 1 + fi +} +assert_equal () { + _assert_equality_or_lack_thereof "==" "$1" "$2" + return $? +} +assert_not_equal () { + _assert_equality_or_lack_thereof "!=" "$1" "$2" + return $? +} +assert_success () { + local stdout stderr ret + capture stdout stderr ret "$*" + if [ $ret = 0 ]; then + green_text "." + return 0 + else + red_text "F" + red_text "assert_success failure: <$*>" >&2 + echo "" >&2 + return 1 + fi +} +assert_failure () { + local stdout stderr ret + capture stdout stderr ret "$*" + if [ $ret != 0 ]; then + green_text "." + return 0 + else + red_text "F" + red_text "asserted failure, but it actually succeeded: <$*>" >&2 + echo "" >&2 + return 1 + fi +} + +# Who tests the testers?? Obviously, the testers themselves test the testers!! +# Run this by passing env TEST=true +_test () { + local stdout stderr ret + # test basic assert_equal success + assert_equal "test with spaces" "test with spaces" + assert_equal "\"quoted 'stuff'\"" "\"quoted 'stuff'\"" + # test basic assert_not_equal success + assert_not_equal "a a" "b b" + # test basic assert_equal success by capturing the stdout, stderr and returncode + capture stdout stderr ret assert_equal "test with spaces" "test with spaces" + assert_equal "$stdout" "\e[32m.\e[0m" # ansi green . + assert_equal "$stderr" "" + assert_equal $ret 0 + # test basic assert_equal failure by capturing the stdout, stderr and returncode + capture stdout stderr ret assert_equal "good" "bad" + assert_equal "$stdout" "\e[31mF\e[0m" # ansi red F + assert_equal "$stderr" "\e[31massert_equal failure: values and do not match\e[0m" + assert_equal $ret 1 + # test the hex encoder/decoder + local testencode testdecode teststring + teststring="hello" + testencode=$(hex_encode $teststring) + testdecode=$(hex_decode $testencode) + assert_equal "$teststring" "$testdecode" + # test success and failure assertions + assert_success ls + assert_failure ls thisfilebetternotexist +} + +testsuite () { + local stdout stderr ret + capture stdout stderr ret _test + echo -e $stdout + echo -en $stderr + # for some reason I couldn't propagate the return code without adding "|| return 1" to every assert line in _test, which is lame. + # I thought -o errexit did this? Anyway, I just look at the stderr of the test suite itself to determine, for now. + if [[ $stderr == "" ]]; then + echo + green_text "Success" + echo + return 0 + else + echo + red_text "Failure" + echo + return 1 + fi +} + +if [ $TEST ]; then + testsuite +fi