From 672e6e994d2a4f8299ec4a032278d5302ecfcd92 Mon Sep 17 00:00:00 2001 From: Salvydas Lukosius Date: Sat, 16 May 2026 06:39:29 +0100 Subject: [PATCH 1/6] Modernize zunit org integration --- .github/workflows/test-matrix.yml | 47 ++++++++++++++ .github/workflows/test-native.yml | 32 ++++++++++ .github/workflows/zsh-n.yml | 27 ++++++++ README.md | 31 +++++++-- build.zsh | 2 + src/assertions.zsh | 2 + src/commands/init.zsh | 60 +++++++++++++++-- src/commands/run.zsh | 6 +- src/events.zsh | 2 + src/helpers.zsh | 2 + src/reports/html.zsh | 45 ++++++++++--- src/reports/tap.zsh | 2 + src/zunit.zsh | 22 ++++--- .../_support/script-with-global-variable.zsh | 2 + tests/_support/script-with-local-variable.zsh | 2 + tests/cli.zunit | 64 +++++++++++++++++++ tests/reports.zunit | 14 ++++ zunit.zsh-completion | 5 +- 18 files changed, 331 insertions(+), 36 deletions(-) create mode 100644 .github/workflows/test-matrix.yml create mode 100644 .github/workflows/test-native.yml create mode 100644 .github/workflows/zsh-n.yml create mode 100644 tests/cli.zunit create mode 100644 tests/reports.zunit diff --git a/.github/workflows/test-matrix.yml b/.github/workflows/test-matrix.yml new file mode 100644 index 0000000..2b87d02 --- /dev/null +++ b/.github/workflows/test-matrix.yml @@ -0,0 +1,47 @@ +--- +name: "ZUnit (Zsh matrix)" + +on: + schedule: + - cron: "0 3 * * 3" + workflow_dispatch: {} + +permissions: + contents: read + +jobs: + zunit-matrix: + runs-on: ubuntu-latest + strategy: + fail-fast: false + matrix: + zsh_version: ["5.5.1", "5.6.2", "5.7.1", "5.8", "5.8.1", "5.9"] + steps: + - uses: actions/checkout@v4 + - uses: docker/setup-buildx-action@v4 + - name: Build test image + run: | + cat > Dockerfile.ci <<'EOF' + FROM alpine:edge + ARG ZSH_VERSION + RUN apk add --no-cache autoconf gcc make musl-dev ncurses-dev curl git xz yodl + RUN curl -fsSL "https://sourceforge.net/projects/zsh/files/zsh/${ZSH_VERSION}/zsh-${ZSH_VERSION}.tar.xz/download" -o /tmp/zsh.tar.xz \ + && tar -C /tmp -xf /tmp/zsh.tar.xz \ + && cd "/tmp/zsh-${ZSH_VERSION}" \ + && ./configure --prefix=/usr/local \ + && make \ + && make install + RUN mkdir -p /opt/zunit/.bin \ + && curl -fsSL 'https://raw.githubusercontent.com/zdharma/revolver/master/revolver' > /opt/zunit/.bin/revolver \ + && curl -fsSL 'https://raw.githubusercontent.com/zdharma/color/master/color.zsh' > /opt/zunit/.bin/color \ + && chmod u+x /opt/zunit/.bin/{color,revolver} + WORKDIR /opt/zunit + EOF + docker build --build-arg ZSH_VERSION="${{ matrix.zsh_version }}" -f Dockerfile.ci -t "zunit-ci:${{ matrix.zsh_version }}" . + - name: Run tests + run: | + docker run --rm \ + --volume "$PWD:/opt/zunit" \ + --workdir /opt/zunit \ + "zunit-ci:${{ matrix.zsh_version }}" \ + sh -lc 'export PATH="/opt/zunit/.bin:$PATH"; ./build.zsh >/dev/null; ./zunit --tap tests' diff --git a/.github/workflows/test-native.yml b/.github/workflows/test-native.yml new file mode 100644 index 0000000..76107d1 --- /dev/null +++ b/.github/workflows/test-native.yml @@ -0,0 +1,32 @@ +--- +name: "ZUnit (native)" + +on: + push: + branches: [main] + pull_request: + branches: [main] + schedule: + - cron: "0 12 * * 1" + workflow_dispatch: {} + +permissions: + contents: read + +jobs: + zunit: + runs-on: ubuntu-latest + steps: + - uses: actions/checkout@v4 + - name: Install dependencies + run: | + sudo apt-get update + sudo apt-get install -yq zsh + mkdir -p .bin + curl -fsSL 'https://raw.githubusercontent.com/zdharma/revolver/master/revolver' > .bin/revolver + curl -fsSL 'https://raw.githubusercontent.com/zdharma/color/master/color.zsh' > .bin/color + chmod u+x .bin/{color,revolver} + - name: Build + run: ./build.zsh + - name: Run test suite + run: PATH="$PWD/.bin:$PATH" ./zunit --tap tests diff --git a/.github/workflows/zsh-n.yml b/.github/workflows/zsh-n.yml new file mode 100644 index 0000000..4fab7f9 --- /dev/null +++ b/.github/workflows/zsh-n.yml @@ -0,0 +1,27 @@ +--- +name: "✅ Zsh Check" + +on: + push: + branches: [main] + pull_request: + branches: [main] + workflow_dispatch: {} + +permissions: + contents: read + +jobs: + zsh-n: + runs-on: ubuntu-latest + steps: + - uses: actions/checkout@v4 + - name: Install zsh + run: sudo apt-get update && sudo apt-get install -yq zsh + - name: Check Zsh syntax + run: | + find src -type f -name '*.zsh' -print | sort | while read -r file; do + zsh -n "$file" + done + zsh -n build.zsh + zsh -n zunit.zsh-completion diff --git a/README.md b/README.md index be696c6..eacfa6e 100644 --- a/README.md +++ b/README.md @@ -1,8 +1,21 @@ ![ZUnit](https://zunit.xyz/img/logo.png) -[![GitHub release](https://img.shields.io/github/release/zunit-zsh/zunit.svg)](https://github.com/zunit-zsh/zunit/releases/latest) [![Build Status](https://travis-ci.org/zunit-zsh/zunit.svg?branch=master)](https://travis-ci.org/zunit-zsh/zunit) [![Gitter](https://badges.gitter.im/Join%20Chat.svg)](https://gitter.im/zunit-zsh/zunit?utm_source=badge&utm_medium=badge&utm_campaign=pr-badge&utm_content=badge) +[![GitHub release](https://img.shields.io/github/release/zunit-zsh/zunit.svg)](https://github.com/zunit-zsh/zunit/releases/latest) [![ZUnit (native)](https://github.com/z-shell/zunit/actions/workflows/test-native.yml/badge.svg)](https://github.com/z-shell/zunit/actions/workflows/test-native.yml) [![Gitter](https://badges.gitter.im/Join%20Chat.svg)](https://gitter.im/zunit-zsh/zunit?utm_source=badge&utm_medium=badge&utm_campaign=pr-badge&utm_content=badge) -ZUnit is a powerful unit testing framework for ZSH +ZUnit is a powerful unit testing framework for ZSH. + +This repository is maintained by the Z-Shell organization as the active +workspace mirror used by the wider Zi/Z-Shell ecosystem. Historical upstream +links are preserved where they still describe the original project, while +runtime integrations continue to follow the currently published package +coordinates used across the ecosystem. + +## Maintenance + +The Z-Shell mirror validates the project with GitHub Actions using native tests, +Zsh syntax checks, and a scheduled Zsh compatibility matrix. The matrix follows +the versions used by the surrounding Z-Shell test infrastructure so downstream +projects can rely on the same compatibility baseline. ## Installation @@ -27,10 +40,10 @@ zulu install zunit ZUnit and its dependencies can all be installed with zplug. ```sh -zplug 'molovo/revolver', \ +zplug 'zdharma/revolver', \ as:command, \ use:revolver -zplug 'zunit-zsh/zunit', \ +zplug 'zdharma/zunit', \ as:command, \ use:zunit, \ hook-build:'./build.zsh' @@ -46,14 +59,14 @@ brew install zunit-zsh/zunit/zunit ### Manual ```sh -git clone https://github.com/zunit-zsh/zunit +git clone https://github.com/zdharma/zunit cd ./zunit ./build.zsh chmod u+x ./zunit cp ./zunit /usr/local/bin ``` -> ZUnit requires [Revolver](https://github.com/molovo/revolver) to be installed, and in your `$PATH`. The zulu or homebrew installation methods will install this dependency for you. +> ZUnit requires [Revolver](https://github.com/zdharma/revolver) to be installed, and in your `$PATH`. The zulu or homebrew installation methods will install this dependency for you. ## Writing Tests @@ -71,6 +84,12 @@ Tests in ZUnit have a simple syntax, which is inspired by the [BATS](https://git The body of each test can contain any valid ZSH code. The zunit shebang `#!/usr/bin/env zunit` **MUST** appear at the top of each test file, or ZUnit will not run it. +To bootstrap a new project with a current CI example, run: + +```sh +zunit init --github-actions +``` + ## Documentation For a full breakdown of ZUnit's syntax and functionality, check out the [official documentation](https://zunit.xyz/docs/). diff --git a/build.zsh b/build.zsh index cf8a616..04140fc 100755 --- a/build.zsh +++ b/build.zsh @@ -1,4 +1,6 @@ #!/usr/bin/env zsh +# -*- mode: zsh; sh-indentation: 2; indent-tabs-mode: nil; sh-basic-offset: 2; -*- +# vim: ft=zsh sw=2 ts=2 et # Clear the file to start with cat /dev/null > zunit diff --git a/src/assertions.zsh b/src/assertions.zsh index 0a05270..44b0cd5 100644 --- a/src/assertions.zsh +++ b/src/assertions.zsh @@ -1,3 +1,5 @@ +# -*- mode: zsh; sh-indentation: 2; indent-tabs-mode: nil; sh-basic-offset: 2; -*- +# vim: ft=zsh sw=2 ts=2 et ################################ # Internal assertion functions # ################################ diff --git a/src/commands/init.zsh b/src/commands/init.zsh index f54b900..256b102 100644 --- a/src/commands/init.zsh +++ b/src/commands/init.zsh @@ -1,3 +1,5 @@ +# -*- mode: zsh; sh-indentation: 2; indent-tabs-mode: nil; sh-basic-offset: 2; -*- +# vim: ft=zsh sw=2 ts=2 et ############################ # The 'zunit init' command # ############################ @@ -10,9 +12,10 @@ function _zunit_init_usage() { echo " zunit init [options]" echo echo "$(color yellow 'Options:')" - echo " -h, --help Output help text and exit" - echo " -v, --version Output version information and exit" - echo " -t, --travis Generate .travis.yml in project" + echo " -h, --help Output help text and exit" + echo " -v, --version Output version information and exit" + echo " -g, --github-actions Generate .github/workflows/zunit.yml in project" + echo " -t, --travis Generate legacy .travis.yml in project" } ### @@ -38,9 +41,11 @@ function _zunit_parse_yaml() { } function _zunit_init() { - local with_travis + local with_github_actions with_travis - zparseopts -D t=with_travis -travis=with_travis + zparseopts -D \ + g=with_github_actions -github-actions=with_github_actions \ + t=with_travis -travis=with_travis # The contents of .zunit.yml local yaml="tap: false @@ -72,13 +77,43 @@ allow_risky: false" install: - mkdir .bin - curl -L https://github.com/zunit-zsh/zunit/releases/download/v$(_zunit_version)/zunit > .bin/zunit - - curl -L https://raw.githubusercontent.com/molovo/revolver/master/revolver > .bin/revolver - - curl -L https://raw.githubusercontent.com/molovo/color/master/color.zsh > .bin/color + - curl -L https://raw.githubusercontent.com/zdharma/revolver/master/revolver > .bin/revolver + - curl -L https://raw.githubusercontent.com/zdharma/color/master/color.zsh > .bin/color before_script: - chmod u+x .bin/{color,revolver,zunit} - export PATH=\"\$PWD/.bin:\$PATH\" script: zunit" + # An example GitHub Actions workflow + local github_actions_yml='--- +name: "ZUnit" + +on: + push: + pull_request: + workflow_dispatch: {} + +permissions: + contents: read + +jobs: + zunit: + runs-on: ubuntu-latest + steps: + - uses: actions/checkout@v4 + - name: Install dependencies + run: | + sudo apt-get update + sudo apt-get install -yq zsh + mkdir -p .bin + curl -fsSL '\''https://raw.githubusercontent.com/zdharma/revolver/master/revolver'\'' > .bin/revolver + curl -fsSL '\''https://raw.githubusercontent.com/zdharma/color/master/color.zsh'\'' > .bin/color + chmod u+x .bin/{color,revolver} + - name: Build + run: ./build.zsh + - name: Test + run: PATH="$PWD/.bin:$PATH" ./zunit --tap tests' + # Check that a config file doesn't already exist so that # we don't overwrite it if [[ -f "$PWD/.zunit.yml" ]]; then @@ -104,6 +139,17 @@ script: zunit" echo "$example" > "$PWD/tests/example.zunit" fi + # If GitHub Actions config has been requested + if [[ -n $with_github_actions ]]; then + if [[ -f "$PWD/.github/workflows/zunit.yml" ]]; then + echo $(color yellow "GitHub Actions workflow already exists at $PWD/.github/workflows/zunit.yml. Skipping...") + else + echo "Writing GitHub Actions workflow to $PWD/.github/workflows/zunit.yml" + mkdir -p "$PWD/.github/workflows" + echo "$github_actions_yml" > "$PWD/.github/workflows/zunit.yml" + fi + fi + # If travis config has been requested if [[ -n $with_travis ]]; then # Check that a travis config doesn't already exist so that diff --git a/src/commands/run.zsh b/src/commands/run.zsh index f2f8eac..7b65521 100644 --- a/src/commands/run.zsh +++ b/src/commands/run.zsh @@ -1,3 +1,5 @@ +# -*- mode: zsh; sh-indentation: 2; indent-tabs-mode: nil; sh-basic-offset: 2; -*- +# vim: ft=zsh sw=2 ts=2 et ########################### # The 'zunit run' command # ########################### @@ -17,7 +19,7 @@ function _zunit_run_usage() { echo " --verbose Prints full output from each test" echo " --output-text Print results to a text log, in TAP compatible format" echo " --output-html Print results to a HTML page" - echo " --allow-risky Supress warnings generated for risky tests" + echo " --allow-risky Suppress warnings generated for risky tests" echo " --time-limit Set a time limit of n seconds for each test" } @@ -475,7 +477,7 @@ function _zunit_run() { # Make sure we have a config file, otherwise we can't determine # which directory to write logs to if [[ $missing_config -eq 1 ]]; then - echo $(color red '.zunit.yml could not be found. Run `zulu init`') + echo $(color red '.zunit.yml could not be found. Run `zunit init`') exit 1 fi diff --git a/src/events.zsh b/src/events.zsh index 27c90f2..2f73a23 100644 --- a/src/events.zsh +++ b/src/events.zsh @@ -1,3 +1,5 @@ +# -*- mode: zsh; sh-indentation: 2; indent-tabs-mode: nil; sh-basic-offset: 2; -*- +# vim: ft=zsh sw=2 ts=2 et ########################################## # Functions for handling internal events # ########################################## diff --git a/src/helpers.zsh b/src/helpers.zsh index 1900618..b72dc0a 100644 --- a/src/helpers.zsh +++ b/src/helpers.zsh @@ -1,3 +1,5 @@ +# -*- mode: zsh; sh-indentation: 2; indent-tabs-mode: nil; sh-basic-offset: 2; -*- +# vim: ft=zsh sw=2 ts=2 et ################################ # Helpers for use within tests # ################################ diff --git a/src/reports/html.zsh b/src/reports/html.zsh index 8185019..bcd9050 100644 --- a/src/reports/html.zsh +++ b/src/reports/html.zsh @@ -1,7 +1,22 @@ +# -*- mode: zsh; sh-indentation: 2; indent-tabs-mode: nil; sh-basic-offset: 2; -*- +# vim: ft=zsh sw=2 ts=2 et ######################################### # Functions for handling HTML reporting # ######################################### +### +# Escape text before inserting it into generated HTML +### +function _zunit_html_escape() { + local value="$1" + value="${value//&/&}" + value="${value///>}" + value="${value//\"/"}" + value="${value//\'/'}" + echo "$value" +} + ### # The head of the HTML document used to display results ### @@ -22,8 +37,9 @@ function _zunit_html_footer() { # Output a HTML success message ### _zunit_html_success() { + local safe_name="$(_zunit_html_escape "$name")" echo "
  • -
    $name
    +
    $safe_name
  • " } @@ -31,9 +47,11 @@ _zunit_html_success() { # Output a HTML failure message ### _zunit_html_failure() { + local safe_name="$(_zunit_html_escape "$name")" + local safe_message="$(_zunit_html_escape "$message")" echo "
  • -
    $name
    -
    $message
    +
    $safe_name
    +
    $safe_message
  • " } @@ -41,9 +59,11 @@ _zunit_html_failure() { # Output a HTML error message ### _zunit_html_error() { + local safe_name="$(_zunit_html_escape "$name")" + local safe_message="$(_zunit_html_escape "$message")" echo "
  • -
    $name
    -
    $message
    +
    $safe_name
    +
    $safe_message
  • " } @@ -51,9 +71,11 @@ _zunit_html_error() { # Output a HTML warning message ### _zunit_html_warn() { + local safe_name="$(_zunit_html_escape "$name")" + local safe_message="$(_zunit_html_escape "$message")" echo "
  • -
    $name
    - $message +
    $safe_name
    + $safe_message
  • " } @@ -61,9 +83,11 @@ _zunit_html_warn() { # Output a HTML skipped test message ### _zunit_html_skip() { + local safe_name="$(_zunit_html_escape "$name")" + local safe_message="$(_zunit_html_escape "$message")" echo "
  • -
    $name
    - $message +
    $safe_name
    + $safe_message
  • " } @@ -72,7 +96,8 @@ _zunit_html_skip() { ### _zunit_html_fatal_error() { message="$@" + local safe_message="$(_zunit_html_escape "$message")" echo "
  • -
    $message
    +
    $safe_message
  • " } diff --git a/src/reports/tap.zsh b/src/reports/tap.zsh index 896842d..aae7a26 100644 --- a/src/reports/tap.zsh +++ b/src/reports/tap.zsh @@ -1,3 +1,5 @@ +# -*- mode: zsh; sh-indentation: 2; indent-tabs-mode: nil; sh-basic-offset: 2; -*- +# vim: ft=zsh sw=2 ts=2 et ######################################## # Functions for handling TAP reporting # ######################################## diff --git a/src/zunit.zsh b/src/zunit.zsh index 5229694..c19f507 100755 --- a/src/zunit.zsh +++ b/src/zunit.zsh @@ -1,4 +1,6 @@ #!/usr/bin/env zsh +# -*- mode: zsh; sh-indentation: 2; indent-tabs-mode: nil; sh-basic-offset: 2; -*- +# vim: ft=zsh sw=2 ts=2 et setopt extendedglob typesetsilent @@ -25,7 +27,7 @@ function _zunit_usage() { echo " --verbose Prints full output from each test" echo " --output-text Print results to a text log, in TAP compatible format" echo " --output-html Print results to a HTML page" - echo " --allow-risky Supress warnings generated for risky tests" + echo " --allow-risky Suppress warnings generated for risky tests" echo " --time-limit Set a time limit in seconds for each test" } @@ -59,14 +61,6 @@ function _zunit() { fi fi - # Check for the 'revolver' dependency - $(type revolver >/dev/null 2>&1) - if [[ $? -ne 0 ]]; then - # 'revolver' could not be found, so print an error message - echo "\033[0;31mMissing required dependency: Revolver - https://github.com/molovo/revolver\033[0;m" >&2 - exit 1 - fi - zparseopts -D -E \ h=help -help=help \ v=version -version=version @@ -78,6 +72,16 @@ function _zunit() { exit 0 fi + # Check for the 'revolver' dependency only when a command may actually run + # tests. Introspection commands such as --help and --version should remain + # usable before dependencies are installed. + if [[ -z $help ]]; then + if ! type revolver >/dev/null 2>&1; then + echo "\033[0;31mMissing required dependency: Revolver - https://github.com/zdharma/revolver\033[0;m" >&2 + exit 1 + fi + fi + # Check which command has been passed, and run it. If the command # is not recognised, then we'll assume it's a test file and pass # it to `zunit run`, since that will catch it if it's not a valid file diff --git a/tests/_support/script-with-global-variable.zsh b/tests/_support/script-with-global-variable.zsh index d03f6b3..332a77c 100644 --- a/tests/_support/script-with-global-variable.zsh +++ b/tests/_support/script-with-global-variable.zsh @@ -1,3 +1,5 @@ #!/usr/bin/env zsh +# -*- mode: zsh; sh-indentation: 2; indent-tabs-mode: nil; sh-basic-offset: 2; -*- +# vim: ft=zsh sw=2 ts=2 et lost_city='Atlantis' diff --git a/tests/_support/script-with-local-variable.zsh b/tests/_support/script-with-local-variable.zsh index 686c901..d998429 100644 --- a/tests/_support/script-with-local-variable.zsh +++ b/tests/_support/script-with-local-variable.zsh @@ -1,3 +1,5 @@ #!/usr/bin/env zsh +# -*- mode: zsh; sh-indentation: 2; indent-tabs-mode: nil; sh-basic-offset: 2; -*- +# vim: ft=zsh sw=2 ts=2 et local lost_city='Atlantis' diff --git a/tests/cli.zunit b/tests/cli.zunit new file mode 100644 index 0000000..43e4535 --- /dev/null +++ b/tests/cli.zunit @@ -0,0 +1,64 @@ +#!/usr/bin/env zunit + +@test '--help works without revolver installed' { + PATH='/usr/bin:/bin' + run "$PWD/zunit" --help + + assert "$state" equals 0 + assert "$output" contains 'Usage:' +} + +@test '--version works without revolver installed' { + PATH='/usr/bin:/bin' + run "$PWD/zunit" --version + + assert "$state" equals 0 + assert "$output" same_as '0.8.2' +} + +@test 'help text spells suppress correctly' { + PATH='/usr/bin:/bin' + run "$PWD/zunit" --help + + assert "$output" contains 'Suppress warnings generated for risky tests' +} + +@test 'run reports the active revolver repository when dependency is missing' { + PATH='/usr/bin:/bin' + run "$PWD/zunit" run + + assert "$state" equals 1 + assert "$output" contains 'https://github.com/zdharma/revolver' +} + +@test 'report output without config points users to zunit init' { + local sandbox + sandbox="$(mktemp -d)" + + cd "$sandbox" + run "$OLDPWD/zunit" --output-text + + assert "$state" equals 1 + assert "$output" contains '.zunit.yml could not be found. Run `zunit init`' +} + +@test 'init help documents GitHub Actions bootstrap support' { + PATH='/usr/bin:/bin' + run "$PWD/zunit" init --help + + assert "$state" equals 0 + assert "$output" contains '--github-actions' +} + +@test 'init can generate GitHub Actions workflow' { + local sandbox + sandbox="$(mktemp -d)" + + cd "$sandbox" + run "$OLDPWD/zunit" init --github-actions + + assert "$state" equals 0 + assert "$sandbox/.github/workflows/zunit.yml" exists + assert "$(< "$sandbox/.github/workflows/zunit.yml")" contains 'name: "ZUnit"' + assert "$(< "$sandbox/.github/workflows/zunit.yml")" contains './zunit --tap tests' +} diff --git a/tests/reports.zunit b/tests/reports.zunit new file mode 100644 index 0000000..58f5a8f --- /dev/null +++ b/tests/reports.zunit @@ -0,0 +1,14 @@ +#!/usr/bin/env zunit + +@test 'HTML reports escape test names and failure messages' { + source "$PWD/src/commands/run.zsh" + source "$PWD/src/reports/html.zsh" + + name='' + message='boom & burn' + + output="$(_zunit_html_failure)" + + assert "$output" contains '<script>alert("name")</script>' + assert "$output" contains '<b>boom & burn</b>' +} diff --git a/zunit.zsh-completion b/zunit.zsh-completion index 684d315..713b03c 100644 --- a/zunit.zsh-completion +++ b/zunit.zsh-completion @@ -24,7 +24,7 @@ _zunit() { '--verbose[prints full output from each test]' \ '--output-text[print results to a text log, in TAP compatible format]' \ '--output-html[print results to a HTML page]' \ - '--allow-risky[supress warnings generated for risky tests]' \ + '--allow-risky[suppress warnings generated for risky tests]' \ '--time-limit[set a time limit in seconds for each test]' case "$state" in @@ -33,6 +33,7 @@ _zunit() { init ) _arguments -A \ '(-h --help)'{-h,--help}'[show help text and exit]' \ + '(-g --github-actions)'{-g,--github-actions}'[generate a GitHub Actions workflow]' \ '(-t --travis)'{-t,--travis}'[generate a .travis.yml file]' _arguments \ @@ -46,7 +47,7 @@ _zunit() { '--verbose[prints full output from each test]' \ '--output-text[print results to a text log, in TAP compatible format]' \ '--output-html[print results to a HTML page]' \ - '--allow-risky[supress warnings generated for risky tests]' \ + '--allow-risky[suppress warnings generated for risky tests]' \ '--time-limit[set a time limit in seconds for each test]' _arguments \ From d5f038caf8f9be67440d97a288b958a0dd0ded5b Mon Sep 17 00:00:00 2001 From: Salvydas Lukosius Date: Sat, 16 May 2026 06:52:14 +0100 Subject: [PATCH 2/6] Fix zunit init bootstrap flow --- src/commands/init.zsh | 19 ++++++++++--------- src/zunit.zsh | 6 +++--- tests/cli.zunit | 28 +++++++++++++++++++++++++++- 3 files changed, 40 insertions(+), 13 deletions(-) diff --git a/src/commands/init.zsh b/src/commands/init.zsh index 256b102..b7555bb 100644 --- a/src/commands/init.zsh +++ b/src/commands/init.zsh @@ -41,7 +41,7 @@ function _zunit_parse_yaml() { } function _zunit_init() { - local with_github_actions with_travis + local with_github_actions with_travis zunit_version zparseopts -D \ g=with_github_actions -github-actions=with_github_actions \ @@ -84,9 +84,11 @@ before_script: - export PATH=\"\$PWD/.bin:\$PATH\" script: zunit" + zunit_version="$(_zunit_version)" + # An example GitHub Actions workflow - local github_actions_yml='--- -name: "ZUnit" + local github_actions_yml="--- +name: \"ZUnit\" on: push: @@ -106,13 +108,12 @@ jobs: sudo apt-get update sudo apt-get install -yq zsh mkdir -p .bin - curl -fsSL '\''https://raw.githubusercontent.com/zdharma/revolver/master/revolver'\'' > .bin/revolver - curl -fsSL '\''https://raw.githubusercontent.com/zdharma/color/master/color.zsh'\'' > .bin/color - chmod u+x .bin/{color,revolver} - - name: Build - run: ./build.zsh + curl -fsSL 'https://github.com/zunit-zsh/zunit/releases/download/v${zunit_version}/zunit' > .bin/zunit + curl -fsSL 'https://raw.githubusercontent.com/zdharma/revolver/master/revolver' > .bin/revolver + curl -fsSL 'https://raw.githubusercontent.com/zdharma/color/master/color.zsh' > .bin/color + chmod u+x .bin/{color,revolver,zunit} - name: Test - run: PATH="$PWD/.bin:$PATH" ./zunit --tap tests' + run: PATH=\"\$PWD/.bin:\$PATH\" zunit --tap tests" # Check that a config file doesn't already exist so that # we don't overwrite it diff --git a/src/zunit.zsh b/src/zunit.zsh index c19f507..d32c20d 100755 --- a/src/zunit.zsh +++ b/src/zunit.zsh @@ -73,9 +73,9 @@ function _zunit() { fi # Check for the 'revolver' dependency only when a command may actually run - # tests. Introspection commands such as --help and --version should remain - # usable before dependencies are installed. - if [[ -z $help ]]; then + # tests. Introspection and bootstrap commands should remain usable before + # dependencies are installed. + if [[ -z $help && $ctx != init ]]; then if ! type revolver >/dev/null 2>&1; then echo "\033[0;31mMissing required dependency: Revolver - https://github.com/zdharma/revolver\033[0;m" >&2 exit 1 diff --git a/tests/cli.zunit b/tests/cli.zunit index 43e4535..4ce1261 100644 --- a/tests/cli.zunit +++ b/tests/cli.zunit @@ -60,5 +60,31 @@ assert "$state" equals 0 assert "$sandbox/.github/workflows/zunit.yml" exists assert "$(< "$sandbox/.github/workflows/zunit.yml")" contains 'name: "ZUnit"' - assert "$(< "$sandbox/.github/workflows/zunit.yml")" contains './zunit --tap tests' + assert "$(< "$sandbox/.github/workflows/zunit.yml")" contains 'zunit --tap tests' +} + +@test 'init works without revolver installed' { + local sandbox + sandbox="$(mktemp -d)" + + cd "$sandbox" + PATH='/usr/bin:/bin' + run "$OLDPWD/zunit" init + + assert "$state" equals 0 + assert "$sandbox/.zunit.yml" exists +} + +@test 'generated GitHub Actions workflow installs and runs zunit for consumer projects' { + local sandbox workflow + sandbox="$(mktemp -d)" + + cd "$sandbox" + run "$OLDPWD/zunit" init --github-actions + workflow="$(< "$sandbox/.github/workflows/zunit.yml")" + + assert "$workflow" contains 'curl -fsSL '\''https://github.com/zunit-zsh/zunit/releases/download/v0.8.2/zunit'\'' > .bin/zunit' + assert "$workflow" contains 'PATH="$PWD/.bin:$PATH" zunit --tap tests' + assert "$workflow" does_not_contain './build.zsh' + assert "$workflow" does_not_contain './zunit --tap tests' } From dd9f804f655de021edd4930b1209136a6e8a7316 Mon Sep 17 00:00:00 2001 From: Sall <59910950+ss-o@users.noreply.github.com> Date: Sat, 16 May 2026 07:02:54 +0100 Subject: [PATCH 3/6] Potential fix for pull request finding Co-authored-by: Copilot Autofix powered by AI <175728472+Copilot@users.noreply.github.com> Signed-off-by: Sall <59910950+ss-o@users.noreply.github.com> --- tests/cli.zunit | 7 ++++++- 1 file changed, 6 insertions(+), 1 deletion(-) diff --git a/tests/cli.zunit b/tests/cli.zunit index 4ce1261..ce54a82 100644 --- a/tests/cli.zunit +++ b/tests/cli.zunit @@ -9,11 +9,16 @@ } @test '--version works without revolver installed' { + local expected_version PATH='/usr/bin:/bin' + expected_version="$( + source "$PWD/src/zunit.zsh" + _zunit_version + )" run "$PWD/zunit" --version assert "$state" equals 0 - assert "$output" same_as '0.8.2' + assert "$output" same_as "$expected_version" } @test 'help text spells suppress correctly' { From 6bcb29f99f8968741e4d3e9c37877d4c1d537176 Mon Sep 17 00:00:00 2001 From: Sall <59910950+ss-o@users.noreply.github.com> Date: Sat, 16 May 2026 07:03:20 +0100 Subject: [PATCH 4/6] Potential fix for pull request finding Co-authored-by: Copilot Autofix powered by AI <175728472+Copilot@users.noreply.github.com> Signed-off-by: Sall <59910950+ss-o@users.noreply.github.com> --- tests/reports.zunit | 1 - 1 file changed, 1 deletion(-) diff --git a/tests/reports.zunit b/tests/reports.zunit index 58f5a8f..66a5af5 100644 --- a/tests/reports.zunit +++ b/tests/reports.zunit @@ -1,7 +1,6 @@ #!/usr/bin/env zunit @test 'HTML reports escape test names and failure messages' { - source "$PWD/src/commands/run.zsh" source "$PWD/src/reports/html.zsh" name='' From 92b346138f82b0d2143042cd36281126f5e3f958 Mon Sep 17 00:00:00 2001 From: "copilot-swe-agent[bot]" <198982749+Copilot@users.noreply.github.com> Date: Sat, 16 May 2026 06:06:10 +0000 Subject: [PATCH 5/6] Address review comments: print -r -- in html_escape, pin Alpine, clarify README Agent-Logs-Url: https://github.com/z-shell/zunit/sessions/669d9ca6-d99f-4d97-9626-5bb99b8b6196 Co-authored-by: ss-o <59910950+ss-o@users.noreply.github.com> --- .github/workflows/test-matrix.yml | 4 ++-- README.md | 7 +++++++ src/reports/html.zsh | 2 +- 3 files changed, 10 insertions(+), 3 deletions(-) diff --git a/.github/workflows/test-matrix.yml b/.github/workflows/test-matrix.yml index 2b87d02..d4e78ee 100644 --- a/.github/workflows/test-matrix.yml +++ b/.github/workflows/test-matrix.yml @@ -22,10 +22,10 @@ jobs: - name: Build test image run: | cat > Dockerfile.ci <<'EOF' - FROM alpine:edge + FROM alpine:3.20 ARG ZSH_VERSION RUN apk add --no-cache autoconf gcc make musl-dev ncurses-dev curl git xz yodl - RUN curl -fsSL "https://sourceforge.net/projects/zsh/files/zsh/${ZSH_VERSION}/zsh-${ZSH_VERSION}.tar.xz/download" -o /tmp/zsh.tar.xz \ + RUN curl -fsSL --retry 3 --retry-delay 5 "https://sourceforge.net/projects/zsh/files/zsh/${ZSH_VERSION}/zsh-${ZSH_VERSION}.tar.xz/download" -o /tmp/zsh.tar.xz \ && tar -C /tmp -xf /tmp/zsh.tar.xz \ && cd "/tmp/zsh-${ZSH_VERSION}" \ && ./configure --prefix=/usr/local \ diff --git a/README.md b/README.md index eacfa6e..8d6ea87 100644 --- a/README.md +++ b/README.md @@ -10,6 +10,13 @@ links are preserved where they still describe the original project, while runtime integrations continue to follow the currently published package coordinates used across the ecosystem. +> **Note:** The install snippets below intentionally reference `zdharma/zunit` +> and `zdharma/revolver` — those are the package coordinates that zplug, +> Homebrew, and related tooling still resolve at runtime. The `z-shell/zunit` +> repository (where this README and the CI badges live) is the active +> development mirror; the `zdharma/*` names are preserved to avoid breaking +> existing user configurations. + ## Maintenance The Z-Shell mirror validates the project with GitHub Actions using native tests, diff --git a/src/reports/html.zsh b/src/reports/html.zsh index bcd9050..a4f0949 100644 --- a/src/reports/html.zsh +++ b/src/reports/html.zsh @@ -14,7 +14,7 @@ function _zunit_html_escape() { value="${value//>/>}" value="${value//\"/"}" value="${value//\'/'}" - echo "$value" + print -r -- "$value" } ### From ec4eb9f918f1625661d74d37d2b68e0cadb1f38b Mon Sep 17 00:00:00 2001 From: Sall <59910950+ss-o@users.noreply.github.com> Date: Sat, 16 May 2026 07:12:59 +0100 Subject: [PATCH 6/6] Fix zunit version test bootstrap --- tests/cli.zunit | 11 +++++------ 1 file changed, 5 insertions(+), 6 deletions(-) diff --git a/tests/cli.zunit b/tests/cli.zunit index ce54a82..8f3efef 100644 --- a/tests/cli.zunit +++ b/tests/cli.zunit @@ -11,10 +11,7 @@ @test '--version works without revolver installed' { local expected_version PATH='/usr/bin:/bin' - expected_version="$( - source "$PWD/src/zunit.zsh" - _zunit_version - )" + expected_version="$(zsh -fc 'source "$1" --version' zsh "$PWD/src/zunit.zsh")" run "$PWD/zunit" --version assert "$state" equals 0 @@ -65,6 +62,7 @@ assert "$state" equals 0 assert "$sandbox/.github/workflows/zunit.yml" exists assert "$(< "$sandbox/.github/workflows/zunit.yml")" contains 'name: "ZUnit"' + assert "$(< "$sandbox/.github/workflows/zunit.yml")" contains 'PATH="$PWD/.bin:$PATH" zunit --tap tests' assert "$(< "$sandbox/.github/workflows/zunit.yml")" contains 'zunit --tap tests' } @@ -81,14 +79,15 @@ } @test 'generated GitHub Actions workflow installs and runs zunit for consumer projects' { - local sandbox workflow + local sandbox workflow expected_version sandbox="$(mktemp -d)" + expected_version="$(zsh -fc 'source "$1" --version' zsh "$PWD/src/zunit.zsh")" cd "$sandbox" run "$OLDPWD/zunit" init --github-actions workflow="$(< "$sandbox/.github/workflows/zunit.yml")" - assert "$workflow" contains 'curl -fsSL '\''https://github.com/zunit-zsh/zunit/releases/download/v0.8.2/zunit'\'' > .bin/zunit' + assert "$workflow" contains "curl -fsSL 'https://github.com/zunit-zsh/zunit/releases/download/v${expected_version}/zunit' > .bin/zunit" assert "$workflow" contains 'PATH="$PWD/.bin:$PATH" zunit --tap tests' assert "$workflow" does_not_contain './build.zsh' assert "$workflow" does_not_contain './zunit --tap tests'