From 3cf75a8d31416e653c19c6e0c7940c0e423e23c8 Mon Sep 17 00:00:00 2001 From: Alex Date: Sun, 14 Jun 2026 18:23:12 +0800 Subject: [PATCH 1/3] =?UTF-8?q?feat:=20zsh=20support=20=E2=80=94=20add=20t?= =?UTF-8?q?est=20suite=20+=20bash/zsh=20CI?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit The utilities are already zsh-compatible (xsh runs them under ksh emulation): config.sh's getopts loop only reads OPTARG with no nested getopts-util call, its /json/parser call is captured in $(), and there are no ${!var}/FUNCNAME/ read-array/special-var-local constructs. This change adds what was missing — tests and CI — to prove and keep it that way. - New test.sh: import-smokes the function utilities, then asserts ss/libev/plugin/v2ray/config rewrites a config to add the v2ray-plugin plugin/plugin_opts keys (incl. the -n/-c/-k tls variant) and errors on a missing config. Self-sources ~/.xshrc so it runs under bash or zsh. - New CI: os × {bash, zsh} matrix; loads xsh-lib/core then this library. Verified under zsh 5.9 and macOS bash 3.2.57. Requires xsh >= 0.7.0 for zsh. Co-Authored-By: Claude Opus 4.8 (1M context) --- .github/workflows/test.yml | 47 ++++++++++++++++++++++++++ README.md | 6 +++- test.sh | 67 ++++++++++++++++++++++++++++++++++++++ 3 files changed, 119 insertions(+), 1 deletion(-) create mode 100644 .github/workflows/test.yml create mode 100755 test.sh diff --git a/.github/workflows/test.yml b/.github/workflows/test.yml new file mode 100644 index 0000000..8e44f9f --- /dev/null +++ b/.github/workflows/test.yml @@ -0,0 +1,47 @@ +name: Test + +on: + push: + branches: [main, master] + pull_request: + branches: [main, master] + +jobs: + test: + runs-on: ${{ matrix.os }} + name: ${{ matrix.os }} / ${{ matrix.shell }} + strategy: + fail-fast: false + matrix: + os: [ubuntu-latest, macos-latest] + # bash is the historical target; zsh is the macOS default login shell. + # Utilities run under zsh's ksh emulation (provided by xsh >= 0.7.0). + shell: [bash, zsh] + + steps: + - name: Install zsh (Linux) + if: matrix.shell == 'zsh' && runner.os == 'Linux' + run: sudo apt-get update && sudo apt-get install -y zsh + + - name: Install xsh + run: | + git clone --depth=50 --branch=master https://github.com/alexzhangs/xsh.git /tmp/xsh + bash /tmp/xsh/install.sh + + - name: Load dependency library xsh-lib/core + shell: bash + run: | + source ~/.xshrc + xsh load xsh-lib/core + + - name: Load library from current branch + shell: bash + run: | + source ~/.xshrc + xsh load -b "${{ github.head_ref || github.ref_name }}" xsh-lib/shadowsocks + + - name: Run tests (${{ matrix.shell }}) + # test.sh self-sources ~/.xshrc, so running it under the matrix shell + # makes the utilities execute under that shell (bash, or zsh's ksh + # emulation). + run: ${{ matrix.shell }} ~/.xsh/repo/xsh-lib/shadowsocks/test.sh diff --git a/README.md b/README.md index 9c08987..57efcc3 100644 --- a/README.md +++ b/README.md @@ -6,12 +6,16 @@ About xsh and its libraries, check out [xsh document](https://github.com/alexzha ## Requirements -1. bash +1. bash or zsh Tested with bash: * 4.3.48 on Linux * 3.2.57 on macOS + The utilities also run under **zsh** (the default shell on modern macOS); + xsh executes them under zsh's ksh emulation. Tested with zsh 5.x. + Requires xsh >= 0.7.0 for zsh. + ## Dependency 1. xsh-lib/core diff --git a/test.sh b/test.sh new file mode 100755 index 0000000..9c08afa --- /dev/null +++ b/test.sh @@ -0,0 +1,67 @@ +#!/bin/bash +# +# Smoke + functional tests for xsh-lib/shadowsocks. Plain assertions, no +# external test framework (mirrors xsh-lib/core/test.sh). +# +# Usage: +# xsh load xsh-lib/core xsh-lib/shadowsocks # one-time +# bash test.sh # or: zsh test.sh +# + +# Make the `xsh` function available when run as a child process. Under bash it +# is inherited as an exported function (no-op here); zsh cannot export functions, +# so a child `zsh test.sh` sources ~/.xshrc to define xsh as a real zsh function. +if ! type xsh 2>/dev/null | grep -q 'function'; then + # shellcheck source=/dev/null + . ~/.xshrc +fi + +set -e -o pipefail + +xsh log info "xsh list ss/" +xsh list 'ss/*' >/dev/null + +# ---------------------------------------------------------------------------- +# import-smoke: every function utility sources cleanly (under zsh each is +# sourced with the injected `emulate -L ksh`). +# ---------------------------------------------------------------------------- +xsh log info "import-smoke: all ss function utilities" +while read -r __lpue; do + [ -z "$__lpue" ] && continue + xsh import "$__lpue" +done < <(xsh list 'ss/*' | awk '$1 == "[functions]" {print $2}') + +# ---------------------------------------------------------------------------- +# ss/libev/plugin/v2ray/config — rewrites an ss-libev JSON config to add the +# v2ray-plugin `plugin`/`plugin_opts` keys (pure transform via /json/parser). +# ---------------------------------------------------------------------------- +xsh log info "ss/libev/plugin/v2ray/config (plugin keys)" +__cfg=$(mktemp "${TMPDIR:-/tmp}/xsh-ss-test.XXXXXXXX") +printf '%s' '{"server":"0.0.0.0","server_port":8388,"password":"pw","method":"aes-256-gcm"}' > "$__cfg" + +__out=$(xsh ss/libev/plugin/v2ray/config -f "$__cfg" -b /usr/local/bin/v2ray-plugin) +case $__out in + *'/usr/local/bin/v2ray-plugin'*) : ;; + *) echo "FAIL: plugin not added: $__out" >&2; exit 1 ;; +esac +case $__out in + *'server;loglevel=none'*) : ;; + *) echo "FAIL: plugin_opts missing: $__out" >&2; exit 1 ;; +esac + +xsh log info "ss/libev/plugin/v2ray/config (-n/-c/-k adds tls opts)" +__out_tls=$(xsh ss/libev/plugin/v2ray/config -f "$__cfg" -b /usr/local/bin/v2ray-plugin \ + -n example.com -c /tmp/cert.pem -k /tmp/key.pem) +case $__out_tls in + *'tls'*'host=example.com'*'cert=/tmp/cert.pem'*'key=/tmp/key.pem'*) : ;; + *) echo "FAIL: tls plugin_opts missing: $__out_tls" >&2; exit 1 ;; +esac +rm -f "$__cfg" + +xsh log info "ss/libev/plugin/v2ray/config (missing config file = error)" +! xsh ss/libev/plugin/v2ray/config -f /no/such/file 2>/dev/null + +echo +echo "All tests passed." + +exit From 8a9934da4812ed806419939431a60bc2da99f2be Mon Sep 17 00:00:00 2001 From: Alex Date: Sun, 14 Jun 2026 18:46:40 +0800 Subject: [PATCH 2/3] ci: test against xsh's full shell matrix (bash 3.2/4.4/5.x + zsh) Mirror alexzhangs/xsh's CI matrix: bash 3.2 (macOS), 4.4 (rockylinux:8), 5.x (Linux + Homebrew macOS), zsh 5.x (macOS + Linux). Co-Authored-By: Claude Opus 4.8 (1M context) --- .github/workflows/test.yml | 52 ++++++++++++++++++++++++++++---------- 1 file changed, 39 insertions(+), 13 deletions(-) diff --git a/.github/workflows/test.yml b/.github/workflows/test.yml index 8e44f9f..896bff9 100644 --- a/.github/workflows/test.yml +++ b/.github/workflows/test.yml @@ -9,39 +9,65 @@ on: jobs: test: runs-on: ${{ matrix.os }} - name: ${{ matrix.os }} / ${{ matrix.shell }} + container: ${{ matrix.container }} + name: ${{ matrix.label }} + defaults: + run: + shell: bash + # Mirror alexzhangs/xsh's shell matrix: bash 3.2 / 4.4 / 5.x + zsh 5.x. + # Utilities run under zsh's ksh emulation (provided by xsh >= 0.7.0). strategy: fail-fast: false matrix: - os: [ubuntu-latest, macos-latest] - # bash is the historical target; zsh is the macOS default login shell. - # Utilities run under zsh's ksh emulation (provided by xsh >= 0.7.0). - shell: [bash, zsh] + include: + - { os: ubuntu-latest, container: '', shell_path: /bin/bash, label: bash-5.x-linux } + - { os: macos-latest, container: '', shell_path: /bin/bash, label: bash-3.2-macos } + - { os: macos-latest, container: '', shell_path: brew, label: bash-5.x-macos } + - { os: ubuntu-latest, container: rockylinux:8, shell_path: /bin/bash, label: bash-4.4-linux } + - { os: macos-latest, container: '', shell_path: /bin/zsh, label: zsh-5.x-macos } + - { os: ubuntu-latest, container: '', shell_path: /usr/bin/zsh, label: zsh-5.x-linux } steps: + - name: Pre-install git (rockylinux container) + if: matrix.container != '' + run: dnf install -y git + + - name: Install dependencies (rockylinux container) + if: matrix.container != '' + run: | + dnf install -y --allowerasing coreutils + dnf install -y gawk sed file findutils diffutils procps-ng which tar gzip python3 + command -v python >/dev/null 2>&1 || ln -sf "$(command -v python3)" /usr/local/bin/python + git config --global --add safe.directory '*' + - name: Install zsh (Linux) - if: matrix.shell == 'zsh' && runner.os == 'Linux' + if: matrix.container == '' && runner.os == 'Linux' && contains(matrix.shell_path, 'zsh') run: sudo apt-get update && sudo apt-get install -y zsh + - name: Install Homebrew bash (macOS bash 5.x) + if: matrix.shell_path == 'brew' + run: | + brew install bash + echo "SHELL_PATH=$(brew --prefix)/bin/bash" >> "$GITHUB_ENV" + + - name: Set shell path + if: matrix.shell_path != 'brew' + run: echo "SHELL_PATH=${{ matrix.shell_path }}" >> "$GITHUB_ENV" + - name: Install xsh run: | git clone --depth=50 --branch=master https://github.com/alexzhangs/xsh.git /tmp/xsh bash /tmp/xsh/install.sh - name: Load dependency library xsh-lib/core - shell: bash run: | source ~/.xshrc xsh load xsh-lib/core - name: Load library from current branch - shell: bash run: | source ~/.xshrc xsh load -b "${{ github.head_ref || github.ref_name }}" xsh-lib/shadowsocks - - name: Run tests (${{ matrix.shell }}) - # test.sh self-sources ~/.xshrc, so running it under the matrix shell - # makes the utilities execute under that shell (bash, or zsh's ksh - # emulation). - run: ${{ matrix.shell }} ~/.xsh/repo/xsh-lib/shadowsocks/test.sh + - name: Run tests (${{ matrix.label }}) + run: "$SHELL_PATH" ~/.xsh/repo/xsh-lib/shadowsocks/test.sh From 3d34ead0f33ecc96ffd2fc71082764ccf0ca9d2d Mon Sep 17 00:00:00 2001 From: Alex Date: Sun, 14 Jun 2026 19:04:47 +0800 Subject: [PATCH 3/3] =?UTF-8?q?ci:=20fix=20YAML=20=E2=80=94=20run=20comman?= =?UTF-8?q?d=20as=20block=20scalar?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit A scalar starting with a double quote (`run: "$SHELL_PATH" ...`) is parsed by YAML as a quoted string with trailing junk, so the workflow failed to parse (0 jobs ran). Use a block scalar. Co-Authored-By: Claude Opus 4.8 (1M context) --- .github/workflows/test.yml | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/.github/workflows/test.yml b/.github/workflows/test.yml index 896bff9..a0c02e2 100644 --- a/.github/workflows/test.yml +++ b/.github/workflows/test.yml @@ -70,4 +70,5 @@ jobs: xsh load -b "${{ github.head_ref || github.ref_name }}" xsh-lib/shadowsocks - name: Run tests (${{ matrix.label }}) - run: "$SHELL_PATH" ~/.xsh/repo/xsh-lib/shadowsocks/test.sh + run: | + "$SHELL_PATH" ~/.xsh/repo/xsh-lib/shadowsocks/test.sh