From 4965d83697962353a14738ddb4e97515c919c193 Mon Sep 17 00:00:00 2001 From: Chemaclass Date: Sat, 2 Aug 2025 16:06:39 +0200 Subject: [PATCH 1/5] feat: lower min support bash 3.0 --- .github/ISSUE_TEMPLATE/BUG.md | 2 +- CHANGELOG.md | 2 +- README.md | 2 +- bashunit | 11 +++++++---- docs/installation.md | 2 +- src/benchmark.sh | 12 ++++++------ src/clock.sh | 12 ++++++------ src/console_results.sh | 15 ++++++++------- src/globals.sh | 2 +- src/helpers.sh | 8 ++++---- src/main.sh | 6 +++--- src/reports.sh | 10 +++++----- src/runner.sh | 12 ++++++------ src/state.sh | 2 +- src/str.sh | 10 +++++----- src/test_doubles.sh | 4 ++-- tests/unit/bash_version_test.sh | 4 ++-- 17 files changed, 60 insertions(+), 56 deletions(-) diff --git a/.github/ISSUE_TEMPLATE/BUG.md b/.github/ISSUE_TEMPLATE/BUG.md index 0e632865..2f81cb7e 100644 --- a/.github/ISSUE_TEMPLATE/BUG.md +++ b/.github/ISSUE_TEMPLATE/BUG.md @@ -14,7 +14,7 @@ labels: bug | Q | A | |------------------|--------------------------| | OS | macOS / Linux / Windows | -| Shell & version | sh 2.0 / bash 3.2 / ... | +| Shell & version | sh 2.0 / bash 3.0 / ... | | bashunit version | x.y.z | #### Summary diff --git a/CHANGELOG.md b/CHANGELOG.md index 64ddd6de..f16f3a76 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -4,7 +4,7 @@ - Update docs mocks usage - Add support for `.bash` test files -- Add runtime check for Bash >= 3.2 +- Lower minimum supported Bash version to 3.0 ## [0.22.3](https://github.com/TypedDevs/bashunit/compare/0.22.2...0.22.3) - 2025-07-27 diff --git a/README.md b/README.md index 225950ec..50306e98 100644 --- a/README.md +++ b/README.md @@ -40,7 +40,7 @@ You can find the complete documentation for **bashunit** online, including insta ## Requirements -bashunit requires **Bash 3.2** or newer. +bashunit requires **Bash 3.0** or newer. ## Contribute diff --git a/bashunit b/bashunit index 38e22b94..185e316b 100755 --- a/bashunit +++ b/bashunit @@ -1,7 +1,7 @@ #!/usr/bin/env bash set -euo pipefail -declare -r BASHUNIT_MIN_BASH_VERSION="3.2" +declare -r BASHUNIT_MIN_BASH_VERSION="3.0" function _check_bash_version() { local current_version @@ -19,7 +19,10 @@ function _check_bash_version() { local major minor IFS=. read -r major minor _ <<< "$current_version" - if (( major < 3 )) || { (( major == 3 )) && (( minor < 2 )); }; then + local min_major min_minor + IFS=. read -r min_major min_minor _ <<< "$BASHUNIT_MIN_BASH_VERSION" + + if (( major < min_major )) || { (( major == min_major )) && (( minor < min_minor )); }; then printf 'Bashunit requires Bash >= %s. Current version: %s\n' "$BASHUNIT_MIN_BASH_VERSION" "$current_version" >&2 exit 1 fi @@ -145,7 +148,7 @@ while [[ $# -gt 0 ]]; do trap '' EXIT && exit 0 ;; *) - _RAW_ARGS+=("$1") + _RAW_ARGS[${#_RAW_ARGS[@]}]="$1" ;; esac shift @@ -157,7 +160,7 @@ if [[ ${#_RAW_ARGS[@]} -gt 0 ]]; then [[ "$_BENCH_MODE" == true ]] && pattern='*[bB]ench.sh' for arg in "${_RAW_ARGS[@]}"; do while IFS= read -r file; do - _ARGS+=("$file") + _ARGS[${#_ARGS[@]}]="$file" done < <(helper::find_files_recursive "$arg" "$pattern") done fi diff --git a/docs/installation.md b/docs/installation.md index c7544fa7..0b956cb0 100644 --- a/docs/installation.md +++ b/docs/installation.md @@ -7,7 +7,7 @@ Here, we provide different options that you can use to install **bashunit** in y ## Requirements -bashunit requires **Bash 3.2** or newer. +bashunit requires **Bash 3.0** or newer. ## install.sh diff --git a/src/benchmark.sh b/src/benchmark.sh index 32b9eb91..75eae808 100644 --- a/src/benchmark.sh +++ b/src/benchmark.sh @@ -42,11 +42,11 @@ function benchmark::parse_annotations() { } function benchmark::add_result() { - _BENCH_NAMES+=("$1") - _BENCH_REVS+=("$2") - _BENCH_ITS+=("$3") - _BENCH_AVERAGES+=("$4") - _BENCH_MAX_MILLIS+=("$5") + _BENCH_NAMES[${#_BENCH_NAMES[@]}]="$1" + _BENCH_REVS[${#_BENCH_REVS[@]}]="$2" + _BENCH_ITS[${#_BENCH_ITS[@]}]="$3" + _BENCH_AVERAGES[${#_BENCH_AVERAGES[@]}]="$4" + _BENCH_MAX_MILLIS[${#_BENCH_MAX_MILLIS[@]}]="$5" } # shellcheck disable=SC2155 @@ -67,7 +67,7 @@ function benchmark::run_function() { local end_time=$(clock::now) local dur_ns=$(math::calculate "($end_time - $start_time)") local dur_ms=$(math::calculate "$dur_ns / 1000000") - durations+=("$dur_ms") + durations[${#durations[@]}]="$dur_ms" if env::is_bench_mode_enabled; then local label="$(helper::normalize_test_function_name "$fn_name")" diff --git a/src/clock.sh b/src/clock.sh index 1cd4c312..b5575578 100644 --- a/src/clock.sh +++ b/src/clock.sh @@ -7,34 +7,34 @@ function clock::_choose_impl() { local attempts=() # 1. Try Perl with Time::HiRes - attempts+=("Perl") + attempts[${#attempts[@]}]="Perl" if dependencies::has_perl && perl -MTime::HiRes -e "" &>/dev/null; then _CLOCK_NOW_IMPL="perl" return 0 fi # 2. Try Python 3 with time module - attempts+=("Python") + attempts[${#attempts[@]}]="Python" if dependencies::has_python; then _CLOCK_NOW_IMPL="python" return 0 fi # 3. Try Node.js - attempts+=("Node") + attempts[${#attempts[@]}]="Node" if dependencies::has_node; then _CLOCK_NOW_IMPL="node" return 0 fi # 4. Windows fallback with PowerShell - attempts+=("PowerShell") + attempts[${#attempts[@]}]="PowerShell" if check_os::is_windows && dependencies::has_powershell; then _CLOCK_NOW_IMPL="powershell" return 0 fi # 5. Unix fallback using `date +%s%N` (if not macOS or Alpine) - attempts+=("date") + attempts[${#attempts[@]}]="date" if ! check_os::is_macos && ! check_os::is_alpine; then local result result=$(date +%s%N 2>/dev/null) @@ -45,7 +45,7 @@ function clock::_choose_impl() { fi # 6. Try using native shell EPOCHREALTIME (if available) - attempts+=("EPOCHREALTIME") + attempts[${#attempts[@]}]="EPOCHREALTIME" if shell_time="$(clock::shell_time)"; then _CLOCK_NOW_IMPL="shell" return 0 diff --git a/src/console_results.sh b/src/console_results.sh index 92d18723..bc2b2e54 100644 --- a/src/console_results.sh +++ b/src/console_results.sh @@ -172,10 +172,11 @@ ${_COLOR_FAILED}✗ Failed${_COLOR_DEFAULT}: %s "${function_name}" "${expected}" "${failure_condition_message}" "${actual}")" if [ -n "$extra_key" ]; then - line+="$(printf "\ - - ${_COLOR_FAINT}%s${_COLOR_DEFAULT} ${_COLOR_BOLD}'%s'${_COLOR_DEFAULT}\n" \ - "${extra_key}" "${extra_value}")" + line="${line}$( + printf "%s%s %s'%s'%s\n" \ + "${_COLOR_FAINT}" "${extra_key}" \ + "${_COLOR_BOLD}" "${extra_value}" "${_COLOR_DEFAULT}" + )" fi state::print_line "failed" "$line" @@ -200,7 +201,7 @@ function console_results::print_failed_snapshot_test() { "$snapshot_file" "$actual_file" 2>/dev/null \ | tail -n +6 | sed "s/^/ /")" - line+="$git_diff_output" + line="${line}$git_diff_output" rm "$actual_file" fi @@ -215,7 +216,7 @@ function console_results::print_skipped_test() { line="$(printf "${_COLOR_SKIPPED}↷ Skipped${_COLOR_DEFAULT}: %s\n" "${function_name}")" if [[ -n "$reason" ]]; then - line+="$(printf "${_COLOR_FAINT} %s${_COLOR_DEFAULT}\n" "${reason}")" + line="${line}$(printf "${_COLOR_FAINT} %s${_COLOR_DEFAULT}\n" "${reason}")" fi state::print_line "skipped" "$line" @@ -229,7 +230,7 @@ function console_results::print_incomplete_test() { line="$(printf "${_COLOR_INCOMPLETE}✒ Incomplete${_COLOR_DEFAULT}: %s\n" "${function_name}")" if [[ -n "$pending" ]]; then - line+="$(printf "${_COLOR_FAINT} %s${_COLOR_DEFAULT}\n" "${pending}")" + line="${line}$(printf "${_COLOR_FAINT} %s${_COLOR_DEFAULT}\n" "${pending}")" fi state::print_line "incomplete" "$line" diff --git a/src/globals.sh b/src/globals.sh index e11705fc..5eb62998 100644 --- a/src/globals.sh +++ b/src/globals.sh @@ -32,7 +32,7 @@ function random_str() { local chars='abcdefghijklmnopqrstuvwxyzABCDEFGHIJKLMNOPQRSTUVWXYZ0123456789' local str='' for (( i=0; i&1) + output=$(BASHUNIT_TEST_BASH_VERSION=2.9 ./bashunit --version 2>&1) exit_code=$? - assert_contains "Bashunit requires Bash >= 3.2. Current version: 3.1" "$output" + assert_contains "Bashunit requires Bash >= 3.0. Current version: 2.9" "$output" assert_general_error "$output" "" "$exit_code" } From 2f545f36eacc09f49ba1b934bb568e787cd2100c Mon Sep 17 00:00:00 2001 From: Chemaclass Date: Sun, 3 Aug 2025 00:49:27 +0200 Subject: [PATCH 2/5] ci: run bash 3.0 with alpine --- .github/docker/alpine-bash3.Dockerfile | 16 ++++++++++++++++ .github/workflows/tests.yml | 16 ++++++++++++++++ 2 files changed, 32 insertions(+) create mode 100644 .github/docker/alpine-bash3.Dockerfile diff --git a/.github/docker/alpine-bash3.Dockerfile b/.github/docker/alpine-bash3.Dockerfile new file mode 100644 index 00000000..cddb1699 --- /dev/null +++ b/.github/docker/alpine-bash3.Dockerfile @@ -0,0 +1,16 @@ +FROM alpine:3.19 + +# Install build tools and dependencies +RUN apk add --no-cache build-base wget git make \ + && wget https://ftp.gnu.org/gnu/bash/bash-3.0.tar.gz \ + && tar -xzf bash-3.0.tar.gz \ + && cd bash-3.0 \ + && ./configure --prefix=/usr \ + && make \ + && make install \ + && cd .. \ + && rm -rf bash-3.0 bash-3.0.tar.gz \ + && apk del build-base wget + +WORKDIR /project +CMD ["/bin/bash"] diff --git a/.github/workflows/tests.yml b/.github/workflows/tests.yml index 33648025..dbf8d676 100644 --- a/.github/workflows/tests.yml +++ b/.github/workflows/tests.yml @@ -80,6 +80,22 @@ jobs: chown -R builder /project && \ su - builder -c 'cd /project; make test';" + alpine-bash30: + name: "Alpine Bash 3.0" + runs-on: ubuntu-22.04 + steps: + - name: Checkout + uses: actions/checkout@v4 + with: + fetch-depth: 1 + + - name: Build test image + run: docker build -t bashunit-bash3 -f .github/docker/alpine-bash3.Dockerfile . + + - name: Run Tests + run: | + docker run --rm -v "$(pwd)":/project -w /project bashunit-bash3 make test + simple-output: name: "Simple output" runs-on: ubuntu-latest From fa35a56f5de982e33c6788040d9a17b71737169d Mon Sep 17 00:00:00 2001 From: Chemaclass Date: Sun, 3 Aug 2025 01:01:36 +0200 Subject: [PATCH 3/5] chore: update docker alpine-bash3 with frolvlad/alpine-glibc --- .github/docker/alpine-bash3.Dockerfile | 5 +++-- 1 file changed, 3 insertions(+), 2 deletions(-) diff --git a/.github/docker/alpine-bash3.Dockerfile b/.github/docker/alpine-bash3.Dockerfile index cddb1699..cbe7b16a 100644 --- a/.github/docker/alpine-bash3.Dockerfile +++ b/.github/docker/alpine-bash3.Dockerfile @@ -1,4 +1,4 @@ -FROM alpine:3.19 +FROM frolvlad/alpine-glibc # Install build tools and dependencies RUN apk add --no-cache build-base wget git make \ @@ -8,9 +8,10 @@ RUN apk add --no-cache build-base wget git make \ && ./configure --prefix=/usr \ && make \ && make install \ + && /usr/bin/bash --version \ && cd .. \ && rm -rf bash-3.0 bash-3.0.tar.gz \ && apk del build-base wget WORKDIR /project -CMD ["/bin/bash"] +CMD ["/usr/bin/bash"] From b2aac9a803fe6e3e3759a94d89ddad91b10c3c1b Mon Sep 17 00:00:00 2001 From: Chemaclass Date: Sun, 3 Aug 2025 01:27:42 +0200 Subject: [PATCH 4/5] chore: use ubuntu-20.04 with bash 3.0 --- .github/docker/alpine-bash3.Dockerfile | 17 ----------------- .github/docker/bash3.Dockerfile | 16 ++++++++++++++++ .github/workflows/tests.yml | 8 ++++---- 3 files changed, 20 insertions(+), 21 deletions(-) delete mode 100644 .github/docker/alpine-bash3.Dockerfile create mode 100644 .github/docker/bash3.Dockerfile diff --git a/.github/docker/alpine-bash3.Dockerfile b/.github/docker/alpine-bash3.Dockerfile deleted file mode 100644 index cbe7b16a..00000000 --- a/.github/docker/alpine-bash3.Dockerfile +++ /dev/null @@ -1,17 +0,0 @@ -FROM frolvlad/alpine-glibc - -# Install build tools and dependencies -RUN apk add --no-cache build-base wget git make \ - && wget https://ftp.gnu.org/gnu/bash/bash-3.0.tar.gz \ - && tar -xzf bash-3.0.tar.gz \ - && cd bash-3.0 \ - && ./configure --prefix=/usr \ - && make \ - && make install \ - && /usr/bin/bash --version \ - && cd .. \ - && rm -rf bash-3.0 bash-3.0.tar.gz \ - && apk del build-base wget - -WORKDIR /project -CMD ["/usr/bin/bash"] diff --git a/.github/docker/bash3.Dockerfile b/.github/docker/bash3.Dockerfile new file mode 100644 index 00000000..157200ed --- /dev/null +++ b/.github/docker/bash3.Dockerfile @@ -0,0 +1,16 @@ +FROM ubuntu:20.04 + +# Install build tools +RUN apt-get update && \ + apt-get install -y build-essential wget && \ + wget https://ftp.gnu.org/gnu/bash/bash-3.0.tar.gz && \ + tar -xzf bash-3.0.tar.gz && \ + cd bash-3.0 && \ + ./configure --prefix=/opt/bash-3.0 && \ + make && make install && \ + ln -s /opt/bash-3.0/bin/bash /usr/bin/bash3 && \ + cd .. && rm -rf bash-3.0* + +CMD ["/usr/bin/bash3"] + +# docker build -f alpine-bash3.Dockerfile -t chemaclass/bash-3.0:alpine-glibc . diff --git a/.github/workflows/tests.yml b/.github/workflows/tests.yml index dbf8d676..6125cafe 100644 --- a/.github/workflows/tests.yml +++ b/.github/workflows/tests.yml @@ -80,9 +80,9 @@ jobs: chown -R builder /project && \ su - builder -c 'cd /project; make test';" - alpine-bash30: - name: "Alpine Bash 3.0" - runs-on: ubuntu-22.04 + bash30: + name: "Bash 3.0" + runs-on: ubuntu-20.04 steps: - name: Checkout uses: actions/checkout@v4 @@ -90,7 +90,7 @@ jobs: fetch-depth: 1 - name: Build test image - run: docker build -t bashunit-bash3 -f .github/docker/alpine-bash3.Dockerfile . + run: docker build -t bashunit-bash3 -f .github/docker/bash3.Dockerfile . - name: Run Tests run: | From ae24589e7572fbbe97d3287a79163b6479851e5d Mon Sep 17 00:00:00 2001 From: Chemaclass Date: Sun, 3 Aug 2025 02:06:51 +0200 Subject: [PATCH 5/5] chore: improve docker/Dockerfile --- .github/docker/Dockerfile | 32 ++++++++++++++++++++++++++++++++ .github/docker/bash3.Dockerfile | 16 ---------------- .github/workflows/tests.yml | 4 ++-- 3 files changed, 34 insertions(+), 18 deletions(-) create mode 100644 .github/docker/Dockerfile delete mode 100644 .github/docker/bash3.Dockerfile diff --git a/.github/docker/Dockerfile b/.github/docker/Dockerfile new file mode 100644 index 00000000..15ac655e --- /dev/null +++ b/.github/docker/Dockerfile @@ -0,0 +1,32 @@ +# Stage 1: Build Bash 3.0 on Ubuntu +FROM ubuntu:20.04 AS builder + +ENV DEBIAN_FRONTEND=noninteractive + +RUN apt-get update && \ + apt-get install -y --no-install-recommends \ + build-essential \ + wget \ + ca-certificates && \ + wget https://ftp.gnu.org/gnu/bash/bash-3.0.tar.gz && \ + tar -xzf bash-3.0.tar.gz && \ + cd bash-3.0 && \ + ./configure --prefix=/bash3 --build=x86_64-unknown-linux-gnu && \ + make && make install && \ + cd .. && \ + rm -rf bash-3.0* + +# Stage 2: Minimal Alpine with Bash 3.0 +FROM alpine:3.18 + +# Copy compiled Bash 3.0 from the builder +COPY --from=builder /bash3 /bash3 + +# Create the canonical /usr/bin/bash symlink +RUN ln -sf /bash3/bin/bash /usr/bin/bash + +# Optional: Test version +RUN /usr/bin/bash --version + +WORKDIR /project +CMD ["/usr/bin/bash"] diff --git a/.github/docker/bash3.Dockerfile b/.github/docker/bash3.Dockerfile deleted file mode 100644 index 157200ed..00000000 --- a/.github/docker/bash3.Dockerfile +++ /dev/null @@ -1,16 +0,0 @@ -FROM ubuntu:20.04 - -# Install build tools -RUN apt-get update && \ - apt-get install -y build-essential wget && \ - wget https://ftp.gnu.org/gnu/bash/bash-3.0.tar.gz && \ - tar -xzf bash-3.0.tar.gz && \ - cd bash-3.0 && \ - ./configure --prefix=/opt/bash-3.0 && \ - make && make install && \ - ln -s /opt/bash-3.0/bin/bash /usr/bin/bash3 && \ - cd .. && rm -rf bash-3.0* - -CMD ["/usr/bin/bash3"] - -# docker build -f alpine-bash3.Dockerfile -t chemaclass/bash-3.0:alpine-glibc . diff --git a/.github/workflows/tests.yml b/.github/workflows/tests.yml index 6125cafe..10def780 100644 --- a/.github/workflows/tests.yml +++ b/.github/workflows/tests.yml @@ -90,11 +90,11 @@ jobs: fetch-depth: 1 - name: Build test image - run: docker build -t bashunit-bash3 -f .github/docker/bash3.Dockerfile . + run: docker build -t bash3 -f .github/docker/Dockerfile . - name: Run Tests run: | - docker run --rm -v "$(pwd)":/project -w /project bashunit-bash3 make test + docker run --rm -v "$(pwd)":/project -w /project bash3 make test simple-output: name: "Simple output"