diff --git a/public/checksum.txt b/public/checksum.txt index ae73b25..2735178 100644 --- a/public/checksum.txt +++ b/public/checksum.txt @@ -1,4 +1,4 @@ 6de66efba021ebcf462e7672577d9f6876f13db55691f04e7009ff7e78d658b8 public/sh/install_zpmod.sh 6e753a0314ba9f2e4f0a2c429798ca3767af4fb94c669e6923d161d6af7aa745 public/sh/install.sh 08cc893ceb982fc99d17db1966c6c30790cc571e16e4f5392352d995f5252952 public/sh/sync-init.sh -e5db3eb099daf0098bed263f4f1960adc9d3d0074a19260fae7ab3df8bf08573 public/zsh/init.zsh +c979e39748d1d86ace17a61ff2b1bf6e1224a43291c25bf7fad985d1e9e11af1 public/zsh/init.zsh diff --git a/public/zsh/init.zsh b/public/zsh/init.zsh index 0847867..ce998e1 100755 --- a/public/zsh/init.zsh +++ b/public/zsh/init.zsh @@ -295,7 +295,7 @@ _zi_pmod() { module_path+=( "$module_dir" ) if ! zmodload zi/zpmod 2>/dev/null; then [[ ${ZI[MUTE_WARNINGS]} == 1 ]] || - _zi_err "zpmod.so is present but zmodload zi/zpmod failed; rebuild it with \`zi module build\`." + _zi_err "zpmod.so is present but zmodload zi/zpmod failed; rebuild it with \`zi module build\`, then run \`zzinit\` again." return 1 fi return 0 @@ -329,8 +329,11 @@ zzinit() { _zi_pmod status_pmod=$? + # Zi is usable at this point, but an optional stage reported a problem. Keep + # the helpers so the diagnostics above can be acted on and zzinit re-run. + (( status_comps == 0 && status_pmod == 0 )) || return 1 + # Helpers are only removed on success so a failed run stays retryable. unset -f _zi_err _zi_fetch _zi_check_stream _zi_setup _zi_source _zi_comps _zi_pmod zzinit 2>/dev/null - - (( status_comps == 0 && status_pmod == 0 )) + return 0 } diff --git a/tests/installers.sh b/tests/installers.sh index f27d812..e8955d2 100755 --- a/tests/installers.sh +++ b/tests/installers.sh @@ -191,6 +191,45 @@ test_init_rejects_invalid_stream() { pass "invalid ZI[STREAM] is rejected before reaching git" } +test_init_keeps_helpers_when_zpmod_fails() { + values_log="${TMP_ROOT}/init-zpmod-values" + err_log="${TMP_ROOT}/init-zpmod-err" + + # zi.zsh loads, but an optional stage fails: zpmod.so exists and cannot be + # loaded. The helpers and zzinit itself must survive so that, once the module + # is rebuilt, the advertised `zzinit` retry has something to call. + zsh -f -c ' + typeset -ghA ZI + ZI[LOADER_HISTORY]=0 + ZI[HOME_DIR]="$4/zpmod-home" + ZI[BIN_DIR]="$4/zpmod-home/bin" + module_dir="${ZI[HOME_DIR]}/zmodules/zpmod/Src/zi" + command mkdir -p "${ZI[BIN_DIR]}" "$module_dir" + print -r -- "# fake zi.zsh" > "${ZI[BIN_DIR]}/zi.zsh" + # A file that is not a shared object: zmodload refuses it. + print -r -- "not a shared object" > "$module_dir/zpmod.so" + + source "$1" + zzinit 2>"$3" + print -r -- "first_status:$?" >"$2" + print -r -- "first_retryable:${+functions[zzinit]}" >>"$2" + + command rm -f -- "$module_dir/zpmod.so" + zzinit 2>>"$3" + print -r -- "second_status:$?" >>"$2" + print -r -- "second_retryable:${+functions[zzinit]}" >>"$2" + ' zsh "${ROOT}/public/zsh/init.zsh" "${values_log}" "${err_log}" "${TMP_ROOT}" + + contains "${values_log}" 'first_status:1' + contains "${err_log}" 'rebuild it with' + # The failed run keeps zzinit defined so the user can rebuild and retry. + contains "${values_log}" 'first_retryable:1' + # The retry succeeds and only then removes the helpers. + contains "${values_log}" 'second_status:0' + contains "${values_log}" 'second_retryable:0' + pass "optional zpmod failure keeps zzinit retryable" +} + test_init_progress_filter_url() { # The loader downloads this file and executes it. A 404 previously aborted # every clean install with no diagnostic, so the path is asserted here. @@ -639,6 +678,7 @@ test_init_defaults_are_single_arguments test_init_preserves_caller_options test_init_history_opt_out test_init_rejects_invalid_stream +test_init_keeps_helpers_when_zpmod_fails test_init_progress_filter_url test_init_uses_private_tempdir test_init_path_resolution