abra zsh config 2.0
This commit is contained in:
12
.zprezto/modules/prompt/external/async/.editorconfig_backup
vendored
Normal file
12
.zprezto/modules/prompt/external/async/.editorconfig_backup
vendored
Normal file
@@ -0,0 +1,12 @@
|
||||
root = true
|
||||
|
||||
[*]
|
||||
charset = utf-8
|
||||
end_of_line = lf
|
||||
indent_style = tab
|
||||
insert_final_newline = true
|
||||
trim_trailing_whitespace = true
|
||||
|
||||
[*.{yml,json}]
|
||||
indent_style = space
|
||||
indent_size = 2
|
1
.zprezto/modules/prompt/external/async/.git_backup
vendored
Normal file
1
.zprezto/modules/prompt/external/async/.git_backup
vendored
Normal file
@@ -0,0 +1 @@
|
||||
gitdir: ../../../../.git/modules/modules/prompt/external/async
|
54
.zprezto/modules/prompt/external/async/.travis.yml_backup
vendored
Normal file
54
.zprezto/modules/prompt/external/async/.travis.yml_backup
vendored
Normal file
@@ -0,0 +1,54 @@
|
||||
language: sh
|
||||
|
||||
addons:
|
||||
apt:
|
||||
packages:
|
||||
- build-essential
|
||||
|
||||
env:
|
||||
global:
|
||||
- ZSH_DIST=$HOME/.zshdist
|
||||
matrix:
|
||||
# Use _ZSH_VERSION since if ZSH_VERSION is present, travis cacher thinks it
|
||||
# is running in zsh and tries to use zsh specific functions.
|
||||
- _ZSH_VERSION=5.5.1
|
||||
- _ZSH_VERSION=5.4.2
|
||||
- _ZSH_VERSION=5.3.1
|
||||
- _ZSH_VERSION=5.3
|
||||
- _ZSH_VERSION=5.2
|
||||
- _ZSH_VERSION=5.1.1
|
||||
- _ZSH_VERSION=5.0.8
|
||||
- _ZSH_VERSION=5.0.2
|
||||
|
||||
cache:
|
||||
directories:
|
||||
- $ZSH_DIST
|
||||
|
||||
before_script:
|
||||
- >
|
||||
setup_zsh() {
|
||||
dest="$ZSH_DIST/$1"
|
||||
if [[ ! -d $dest/bin ]]; then
|
||||
tmp="$(mktemp --directory --tmpdir="${TMPDIR:/tmp}" zshbuild.XXXXXX)"
|
||||
(
|
||||
cd "$tmp" &&
|
||||
curl -L http://downloads.sourceforge.net/zsh/zsh-${1}.tar.gz | tar zx &&
|
||||
cd zsh-$1 &&
|
||||
./configure --prefix="$dest" &&
|
||||
make &&
|
||||
mkdir -p "$dest" &&
|
||||
make install ||
|
||||
echo "Failed to build zsh-${1}!"
|
||||
)
|
||||
fi
|
||||
export PATH="$dest/bin:$PATH"
|
||||
}
|
||||
- setup_zsh $_ZSH_VERSION
|
||||
- zsh --version
|
||||
|
||||
script:
|
||||
- zsh test.zsh -v
|
||||
|
||||
allow_failures:
|
||||
- env: _ZSH_VERSION=5.0.2
|
||||
- env: _ZSH_VERSION=5.0.8
|
21
.zprezto/modules/prompt/external/async/LICENSE
vendored
Normal file
21
.zprezto/modules/prompt/external/async/LICENSE
vendored
Normal file
@@ -0,0 +1,21 @@
|
||||
The MIT License (MIT)
|
||||
|
||||
Copyright (c) 2015 Mathias Fredriksson <mafredri@gmail.com>
|
||||
|
||||
Permission is hereby granted, free of charge, to any person obtaining a copy
|
||||
of this software and associated documentation files (the "Software"), to deal
|
||||
in the Software without restriction, including without limitation the rights
|
||||
to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
|
||||
copies of the Software, and to permit persons to whom the Software is
|
||||
furnished to do so, subject to the following conditions:
|
||||
|
||||
The above copyright notice and this permission notice shall be included in
|
||||
all copies or substantial portions of the Software.
|
||||
|
||||
THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
|
||||
IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
|
||||
FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
|
||||
AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
|
||||
LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
|
||||
OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN
|
||||
THE SOFTWARE.
|
163
.zprezto/modules/prompt/external/async/README.md
vendored
Normal file
163
.zprezto/modules/prompt/external/async/README.md
vendored
Normal file
@@ -0,0 +1,163 @@
|
||||
# zsh-async
|
||||
|
||||
```
|
||||
Because your terminal should be able to perform tasks asynchronously without external tools!
|
||||
```
|
||||
|
||||
## Intro (TL;DR)
|
||||
|
||||
With `zsh-async` you can run multiple asynchronous jobs, enforce unique jobs (multiple instances of the same job will not run), flush all currently running jobs and create multiple workers (each with their own jobs). For each worker you can register a callback-function through which you will be notified about the job results (job name, return code, output and execution time).
|
||||
|
||||
## Overview
|
||||
|
||||
`zsh-async` is a small library for running asynchronous tasks in zsh without requiring any external tools. It utilizes `zsh/zpty` to launch a pseudo-terminal in which all commands get executed without blocking any other processes. Checking for completed tasks can be done manually, by polling, or better yet, automatically whenever a process has finished executing by notifying through a `SIGWINCH` kill-signal.
|
||||
|
||||
This library bridges the gap between spawning child processes and disowning them. Child processes launched by normal means clutter the terminal with output about their state, and disowned processes become separate entities, no longer under control of the parent. Now you can have both!
|
||||
|
||||
## Usage
|
||||
|
||||
The async worker is a separate environment (think web worker). You send it a job (command + parameters) to execute and it returns the result of that execution through a callback function. If you find that you need to stop/start a worker to update global state (variables) you should consider refactoring so that state is passed during the `async_job` call (e.g. `async_job my_worker my_function $state1 $state2`).
|
||||
|
||||
### Installation
|
||||
|
||||
#### Manual
|
||||
|
||||
You can either source the `async.zsh` script directly or insert under your `$fpath` as async and autoload it through `autoload -Uz async && async`.
|
||||
|
||||
#### Integration
|
||||
|
||||
##### zplug
|
||||
|
||||
```
|
||||
zplug "mafredri/zsh-async", from:"github", use:"async.zsh"
|
||||
```
|
||||
|
||||
### Functions
|
||||
|
||||
The `zsh-async` library has a bunch of functions that need to be used to perform async actions:
|
||||
|
||||
#### `async_init`
|
||||
|
||||
Initializes the async library (not required if using async from `$fpath` with autoload.)
|
||||
|
||||
#### `async_start_worker <worker_name> [-u] [-n] [-p <pid>]`
|
||||
|
||||
Start a new async worker with optional parameters, a worker can be told to only run unique tasks and to notify a process when tasks are complete.
|
||||
|
||||
* `-u` unique. Only unique job names can run, e.g. the command `git status` will have `git` as the unique job name identifier
|
||||
|
||||
* `-n` notify through `SIGWINCH` signal. Needs to be caught with a `trap '' WINCH` in the process defined by `-p`
|
||||
|
||||
**NOTE:** When `zsh-async` is used in an interactive shell with ZLE enabled this option is not needed. Signaling through `SIGWINCH` has been replaced by a ZLE watcher that is triggered on output from the `zpty` instance (still requires a callback function through `async_register_callback` though). Technically zsh versions prior to `5.2` do not return the file descriptor for zpty instances, however, `zsh-async` attempts to deduce it anyway.
|
||||
|
||||
* `-p` pid to notify (defaults to current pid)
|
||||
|
||||
#### `async_stop_worker <worker_name_1> [<worker_name_2>]`
|
||||
|
||||
Simply stops a worker and all active jobs will be terminated immediately.
|
||||
|
||||
#### `async_job <worker_name> <my_function> [<function_params>]`
|
||||
|
||||
Start a new asynchronous job on specified worker, assumes the worker is running.
|
||||
|
||||
#### `async_process_results <worker_name> <callback_function>`
|
||||
|
||||
Get results from finished jobs and pass it to the to callback function. This is the only way to reliably return the job name, return code, output and execution time and with minimal effort.
|
||||
|
||||
The `callback_function` is called with the following parameters:
|
||||
|
||||
* `$1` job name, e.g. the function passed to async_job
|
||||
* `$2` return code
|
||||
* Returns `-1` if return code is missing, this should never happen, if it does, you have likely run into a bug. Please open a new [issue](https://github.com/mafredri/zsh-async/issues/new) with a detailed description of what you were doing.
|
||||
* `$3` resulting (stdout) output from job execution
|
||||
* `$4` execution time, floating point e.g. 0.0076138973 seconds
|
||||
* `$5` resulting (stderr) error output from job execution
|
||||
* `$6` has next result in buffer (0 = buffer empty, 1 = yes)
|
||||
* This means another async job has completed and is pending in the buffer, it's very likely that your callback function will be called a second time (or more) in this execution. It's generally a good idea to e.g. delay prompt updates (`zle reset-prompt`) until the buffer is empty to prevent strange states in ZLE.
|
||||
|
||||
#### `async_register_callback <worker_name> <callback_function>`
|
||||
|
||||
Register a callback for completed jobs. As soon as a job is finished, `async_process_results` will be called with the specified callback function. This requires that a worker is initialized with the -n (notify) option.
|
||||
|
||||
#### `async_unregister_callback <worker_name>`
|
||||
|
||||
Unregister the callback for a specific worker.
|
||||
|
||||
#### `async_flush_jobs <worker_name>`
|
||||
|
||||
Flush all current jobs running on a worker. This will terminate any and all running processes under the worker by sending a `SIGTERM` to the entire process group, use with caution.
|
||||
|
||||
## Example code
|
||||
|
||||
```zsh
|
||||
#!/usr/bin/env zsh
|
||||
source ./async.zsh
|
||||
async_init
|
||||
|
||||
# Initialize a new worker (with notify option)
|
||||
async_start_worker my_worker -n
|
||||
|
||||
# Create a callback function to process results
|
||||
COMPLETED=0
|
||||
completed_callback() {
|
||||
COMPLETED=$(( COMPLETED + 1 ))
|
||||
print $@
|
||||
}
|
||||
|
||||
# Register callback function for the workers completed jobs
|
||||
async_register_callback my_worker completed_callback
|
||||
|
||||
# Give the worker some tasks to perform
|
||||
async_job my_worker print hello
|
||||
async_job my_worker sleep 0.3
|
||||
|
||||
# Wait for the two tasks to be completed
|
||||
while (( COMPLETED < 2 )); do
|
||||
print "Waiting..."
|
||||
sleep 0.1
|
||||
done
|
||||
|
||||
print "Completed $COMPLETED tasks!"
|
||||
|
||||
# Output:
|
||||
# Waiting...
|
||||
# print 0 hello 0.001583099365234375
|
||||
# Waiting...
|
||||
# Waiting...
|
||||
# sleep 0 0.30631208419799805
|
||||
# Completed 2 tasks!
|
||||
```
|
||||
|
||||
## Testing
|
||||
|
||||
Tests are located in `*_test.zsh` and can be run by executing the test runner: `./test.zsh`.
|
||||
|
||||
Example:
|
||||
|
||||
```console
|
||||
$ ./test.zsh
|
||||
ok ./async_test.zsh 2.334s
|
||||
```
|
||||
|
||||
The test suite can also run specific tasks that match a pattern, for example:
|
||||
|
||||
```console
|
||||
$ ./test.zsh -v -run zle
|
||||
=== RUN test_zle_watcher
|
||||
--- PASS: test_zle_watcher (0.07s)
|
||||
PASS
|
||||
ok ./async_test.zsh 0.070s
|
||||
```
|
||||
|
||||
## Limitations
|
||||
|
||||
* A NULL-character (`$'\0'`) is used by `async_job` to signify the end of the command, it is recommended not to pass them as arguments, although they should work when passing multiple arguments to `async_job` (because of quoting).
|
||||
* Tell me? :)
|
||||
|
||||
## Tips
|
||||
|
||||
If you do not wish to use the `notify` feature, you can couple `zsh-async` with `zsh/sched` or the zsh `periodic` function for scheduling the worker results to be processed.
|
||||
|
||||
## Why did I make this?
|
||||
|
||||
I found a great theme for zsh, [Pure](https://github.com/sindresorhus/pure) by Sindre Sorhus. After using it for a while I noticed some graphical glitches due to the terminal being updated by a disowned process. Thus, I became inspired to get my hands dirty and find a solution. I tried many things, coprocesses (seemed too limited by themselves), different combinations of trapping kill-signals, etc. I also had problems with the zsh process ending up in a deadlock due to some zsh bug. After working out the kinks, I ended up with this and thought, hey, why not make it a library.
|
2
.zprezto/modules/prompt/external/async/async.plugin.zsh
vendored
Normal file
2
.zprezto/modules/prompt/external/async/async.plugin.zsh
vendored
Normal file
@@ -0,0 +1,2 @@
|
||||
0=${(%):-%N}
|
||||
source ${0:A:h}/async.zsh
|
499
.zprezto/modules/prompt/external/async/async.zsh
vendored
Normal file
499
.zprezto/modules/prompt/external/async/async.zsh
vendored
Normal file
@@ -0,0 +1,499 @@
|
||||
#!/usr/bin/env zsh
|
||||
|
||||
#
|
||||
# zsh-async
|
||||
#
|
||||
# version: 1.6.2
|
||||
# author: Mathias Fredriksson
|
||||
# url: https://github.com/mafredri/zsh-async
|
||||
#
|
||||
|
||||
typeset -g ASYNC_VERSION=1.6.2
|
||||
# Produce debug output from zsh-async when set to 1.
|
||||
typeset -g ASYNC_DEBUG=${ASYNC_DEBUG:-0}
|
||||
|
||||
# Wrapper for jobs executed by the async worker, gives output in parseable format with execution time
|
||||
_async_job() {
|
||||
# Disable xtrace as it would mangle the output.
|
||||
setopt localoptions noxtrace
|
||||
|
||||
# Store start time for job.
|
||||
float -F duration=$EPOCHREALTIME
|
||||
|
||||
# Run the command and capture both stdout (`eval`) and stderr (`cat`) in
|
||||
# separate subshells. When the command is complete, we grab write lock
|
||||
# (mutex token) and output everything except stderr inside the command
|
||||
# block, after the command block has completed, the stdin for `cat` is
|
||||
# closed, causing stderr to be appended with a $'\0' at the end to mark the
|
||||
# end of output from this job.
|
||||
local stdout stderr ret tok
|
||||
{
|
||||
stdout=$(eval "$@")
|
||||
ret=$?
|
||||
duration=$(( EPOCHREALTIME - duration )) # Calculate duration.
|
||||
|
||||
# Grab mutex lock, stalls until token is available.
|
||||
read -r -k 1 -p tok || exit 1
|
||||
|
||||
# Return output (<job_name> <return_code> <stdout> <duration> <stderr>).
|
||||
print -r -n - ${(q)1} $ret ${(q)stdout} $duration
|
||||
} 2> >(stderr=$(cat) && print -r -n - " "${(q)stderr}$'\0')
|
||||
|
||||
# Unlock mutex by inserting a token.
|
||||
print -n -p $tok
|
||||
}
|
||||
|
||||
# The background worker manages all tasks and runs them without interfering with other processes
|
||||
_async_worker() {
|
||||
# Reset all options to defaults inside async worker.
|
||||
emulate -R zsh
|
||||
|
||||
# Make sure monitor is unset to avoid printing the
|
||||
# pids of child processes.
|
||||
unsetopt monitor
|
||||
|
||||
# Redirect stderr to `/dev/null` in case unforseen errors produced by the
|
||||
# worker. For example: `fork failed: resource temporarily unavailable`.
|
||||
# Some older versions of zsh might also print malloc errors (know to happen
|
||||
# on at least zsh 5.0.2 and 5.0.8) likely due to kill signals.
|
||||
exec 2>/dev/null
|
||||
|
||||
# When a zpty is deleted (using -d) all the zpty instances created before
|
||||
# the one being deleted receive a SIGHUP, unless we catch it, the async
|
||||
# worker would simply exit (stop working) even though visible in the list
|
||||
# of zpty's (zpty -L).
|
||||
TRAPHUP() {
|
||||
return 0 # Return 0, indicating signal was handled.
|
||||
}
|
||||
|
||||
local -A storage
|
||||
local unique=0
|
||||
local notify_parent=0
|
||||
local parent_pid=0
|
||||
local coproc_pid=0
|
||||
local processing=0
|
||||
|
||||
local -a zsh_hooks zsh_hook_functions
|
||||
zsh_hooks=(chpwd periodic precmd preexec zshexit zshaddhistory)
|
||||
zsh_hook_functions=(${^zsh_hooks}_functions)
|
||||
unfunction $zsh_hooks &>/dev/null # Deactivate all zsh hooks inside the worker.
|
||||
unset $zsh_hook_functions # And hooks with registered functions.
|
||||
unset zsh_hooks zsh_hook_functions # Cleanup.
|
||||
|
||||
child_exit() {
|
||||
local -a pids
|
||||
pids=(${${(v)jobstates##*:*:}%\=*})
|
||||
|
||||
# If coproc (cat) is the only child running, we close it to avoid
|
||||
# leaving it running indefinitely and cluttering the process tree.
|
||||
if (( ! processing )) && [[ $#pids = 1 ]] && [[ $coproc_pid = $pids[1] ]]; then
|
||||
coproc :
|
||||
coproc_pid=0
|
||||
fi
|
||||
|
||||
# On older version of zsh (pre 5.2) we notify the parent through a
|
||||
# SIGWINCH signal because `zpty` did not return a file descriptor (fd)
|
||||
# prior to that.
|
||||
if (( notify_parent )); then
|
||||
# We use SIGWINCH for compatibility with older versions of zsh
|
||||
# (pre 5.1.1) where other signals (INFO, ALRM, USR1, etc.) could
|
||||
# cause a deadlock in the shell under certain circumstances.
|
||||
kill -WINCH $parent_pid
|
||||
fi
|
||||
}
|
||||
|
||||
# Register a SIGCHLD trap to handle the completion of child processes.
|
||||
trap child_exit CHLD
|
||||
|
||||
# Process option parameters passed to worker
|
||||
while getopts "np:u" opt; do
|
||||
case $opt in
|
||||
n) notify_parent=1;;
|
||||
p) parent_pid=$OPTARG;;
|
||||
u) unique=1;;
|
||||
esac
|
||||
done
|
||||
|
||||
killjobs() {
|
||||
local tok
|
||||
local -a pids
|
||||
pids=(${${(v)jobstates##*:*:}%\=*})
|
||||
|
||||
# No need to send SIGHUP if no jobs are running.
|
||||
(( $#pids == 0 )) && continue
|
||||
(( $#pids == 1 )) && [[ $coproc_pid = $pids[1] ]] && continue
|
||||
|
||||
# Grab lock to prevent half-written output in case a child
|
||||
# process is in the middle of writing to stdin during kill.
|
||||
(( coproc_pid )) && read -r -k 1 -p tok
|
||||
|
||||
kill -HUP -$$ # Send to entire process group.
|
||||
coproc : # Quit coproc.
|
||||
coproc_pid=0 # Reset pid.
|
||||
}
|
||||
|
||||
local request
|
||||
local -a cmd
|
||||
while :; do
|
||||
# Wait for jobs sent by async_job.
|
||||
read -r -d $'\0' request || {
|
||||
# Since we handle SIGHUP above (and thus do not know when `zpty -d`)
|
||||
# occurs, a failure to read probably indicates that stdin has
|
||||
# closed. This is why we propagate the signal to all children and
|
||||
# exit manually.
|
||||
kill -HUP -$$ # Send SIGHUP to all jobs.
|
||||
exit 0
|
||||
}
|
||||
|
||||
# Check for non-job commands sent to worker
|
||||
case $request in
|
||||
_unset_trap) notify_parent=0; continue;;
|
||||
_killjobs) killjobs; continue;;
|
||||
esac
|
||||
|
||||
# Parse the request using shell parsing (z) to allow commands
|
||||
# to be parsed from single strings and multi-args alike.
|
||||
cmd=("${(z)request}")
|
||||
|
||||
# Name of the job (first argument).
|
||||
local job=$cmd[1]
|
||||
|
||||
# If worker should perform unique jobs
|
||||
if (( unique )); then
|
||||
# Check if a previous job is still running, if yes, let it finnish
|
||||
for pid in ${${(v)jobstates##*:*:}%\=*}; do
|
||||
if [[ ${storage[$job]} == $pid ]]; then
|
||||
continue 2
|
||||
fi
|
||||
done
|
||||
fi
|
||||
|
||||
# Guard against closing coproc from trap before command has started.
|
||||
processing=1
|
||||
|
||||
# Because we close the coproc after the last job has completed, we must
|
||||
# recreate it when there are no other jobs running.
|
||||
if (( ! coproc_pid )); then
|
||||
# Use coproc as a mutex for synchronized output between children.
|
||||
coproc cat
|
||||
coproc_pid="$!"
|
||||
# Insert token into coproc
|
||||
print -n -p "t"
|
||||
fi
|
||||
|
||||
# Run job in background, completed jobs are printed to stdout.
|
||||
_async_job $cmd &
|
||||
# Store pid because zsh job manager is extremely unflexible (show jobname as non-unique '$job')...
|
||||
storage[$job]="$!"
|
||||
|
||||
processing=0 # Disable guard.
|
||||
done
|
||||
}
|
||||
|
||||
#
|
||||
# Get results from finnished jobs and pass it to the to callback function. This is the only way to reliably return the
|
||||
# job name, return code, output and execution time and with minimal effort.
|
||||
#
|
||||
# usage:
|
||||
# async_process_results <worker_name> <callback_function>
|
||||
#
|
||||
# callback_function is called with the following parameters:
|
||||
# $1 = job name, e.g. the function passed to async_job
|
||||
# $2 = return code
|
||||
# $3 = resulting stdout from execution
|
||||
# $4 = execution time, floating point e.g. 2.05 seconds
|
||||
# $5 = resulting stderr from execution
|
||||
# $6 = has next result in buffer (0 = buffer empty, 1 = yes)
|
||||
#
|
||||
async_process_results() {
|
||||
setopt localoptions unset noshwordsplit noksharrays noposixidentifiers noposixstrings
|
||||
|
||||
local worker=$1
|
||||
local callback=$2
|
||||
local caller=$3
|
||||
local -a items
|
||||
local null=$'\0' data
|
||||
integer -l len pos num_processed has_next
|
||||
|
||||
typeset -gA ASYNC_PROCESS_BUFFER
|
||||
|
||||
# Read output from zpty and parse it if available.
|
||||
while zpty -r -t $worker data 2>/dev/null; do
|
||||
ASYNC_PROCESS_BUFFER[$worker]+=$data
|
||||
len=${#ASYNC_PROCESS_BUFFER[$worker]}
|
||||
pos=${ASYNC_PROCESS_BUFFER[$worker][(i)$null]} # Get index of NULL-character (delimiter).
|
||||
|
||||
# Keep going until we find a NULL-character.
|
||||
if (( ! len )) || (( pos > len )); then
|
||||
continue
|
||||
fi
|
||||
|
||||
while (( pos <= len )); do
|
||||
# Take the content from the beginning, until the NULL-character and
|
||||
# perform shell parsing (z) and unquoting (Q) as an array (@).
|
||||
items=("${(@Q)${(z)ASYNC_PROCESS_BUFFER[$worker][1,$pos-1]}}")
|
||||
|
||||
# Remove the extracted items from the buffer.
|
||||
ASYNC_PROCESS_BUFFER[$worker]=${ASYNC_PROCESS_BUFFER[$worker][$pos+1,$len]}
|
||||
|
||||
len=${#ASYNC_PROCESS_BUFFER[$worker]}
|
||||
if (( len > 1 )); then
|
||||
pos=${ASYNC_PROCESS_BUFFER[$worker][(i)$null]} # Get index of NULL-character (delimiter).
|
||||
fi
|
||||
|
||||
has_next=$(( len != 0 ))
|
||||
if (( $#items == 5 )); then
|
||||
items+=($has_next)
|
||||
$callback "${(@)items}" # Send all parsed items to the callback.
|
||||
else
|
||||
# In case of corrupt data, invoke callback with *async* as job
|
||||
# name, non-zero exit status and an error message on stderr.
|
||||
$callback "async" 1 "" 0 "$0:$LINENO: error: bad format, got ${#items} items (${(q)items})" $has_next
|
||||
fi
|
||||
|
||||
(( num_processed++ ))
|
||||
done
|
||||
done
|
||||
|
||||
(( num_processed )) && return 0
|
||||
|
||||
# Avoid printing exit value when `setopt printexitvalue` is active.`
|
||||
[[ $caller = trap || $caller = watcher ]] && return 0
|
||||
|
||||
# No results were processed
|
||||
return 1
|
||||
}
|
||||
|
||||
# Watch worker for output
|
||||
_async_zle_watcher() {
|
||||
setopt localoptions noshwordsplit
|
||||
typeset -gA ASYNC_PTYS ASYNC_CALLBACKS
|
||||
local worker=$ASYNC_PTYS[$1]
|
||||
local callback=$ASYNC_CALLBACKS[$worker]
|
||||
|
||||
if [[ -n $callback ]]; then
|
||||
async_process_results $worker $callback watcher
|
||||
fi
|
||||
}
|
||||
|
||||
#
|
||||
# Start a new asynchronous job on specified worker, assumes the worker is running.
|
||||
#
|
||||
# usage:
|
||||
# async_job <worker_name> <my_function> [<function_params>]
|
||||
#
|
||||
async_job() {
|
||||
setopt localoptions noshwordsplit noksharrays noposixidentifiers noposixstrings
|
||||
|
||||
local worker=$1; shift
|
||||
|
||||
local -a cmd
|
||||
cmd=("$@")
|
||||
if (( $#cmd > 1 )); then
|
||||
cmd=(${(q)cmd}) # Quote special characters in multi argument commands.
|
||||
fi
|
||||
|
||||
# Quote the cmd in case RC_EXPAND_PARAM is set.
|
||||
zpty -w $worker "$cmd"$'\0'
|
||||
}
|
||||
|
||||
# This function traps notification signals and calls all registered callbacks
|
||||
_async_notify_trap() {
|
||||
setopt localoptions noshwordsplit
|
||||
|
||||
local k
|
||||
for k in ${(k)ASYNC_CALLBACKS}; do
|
||||
async_process_results $k ${ASYNC_CALLBACKS[$k]} trap
|
||||
done
|
||||
}
|
||||
|
||||
#
|
||||
# Register a callback for completed jobs. As soon as a job is finnished, async_process_results will be called with the
|
||||
# specified callback function. This requires that a worker is initialized with the -n (notify) option.
|
||||
#
|
||||
# usage:
|
||||
# async_register_callback <worker_name> <callback_function>
|
||||
#
|
||||
async_register_callback() {
|
||||
setopt localoptions noshwordsplit nolocaltraps
|
||||
|
||||
typeset -gA ASYNC_CALLBACKS
|
||||
local worker=$1; shift
|
||||
|
||||
ASYNC_CALLBACKS[$worker]="$*"
|
||||
|
||||
# Enable trap when the ZLE watcher is unavailable, allows
|
||||
# workers to notify (via -n) when a job is done.
|
||||
if [[ ! -o interactive ]] || [[ ! -o zle ]]; then
|
||||
trap '_async_notify_trap' WINCH
|
||||
fi
|
||||
}
|
||||
|
||||
#
|
||||
# Unregister the callback for a specific worker.
|
||||
#
|
||||
# usage:
|
||||
# async_unregister_callback <worker_name>
|
||||
#
|
||||
async_unregister_callback() {
|
||||
typeset -gA ASYNC_CALLBACKS
|
||||
|
||||
unset "ASYNC_CALLBACKS[$1]"
|
||||
}
|
||||
|
||||
#
|
||||
# Flush all current jobs running on a worker. This will terminate any and all running processes under the worker, use
|
||||
# with caution.
|
||||
#
|
||||
# usage:
|
||||
# async_flush_jobs <worker_name>
|
||||
#
|
||||
async_flush_jobs() {
|
||||
setopt localoptions noshwordsplit
|
||||
|
||||
local worker=$1; shift
|
||||
|
||||
# Check if the worker exists
|
||||
zpty -t $worker &>/dev/null || return 1
|
||||
|
||||
# Send kill command to worker
|
||||
async_job $worker "_killjobs"
|
||||
|
||||
# Clear the zpty buffer.
|
||||
local junk
|
||||
if zpty -r -t $worker junk '*'; then
|
||||
(( ASYNC_DEBUG )) && print -n "async_flush_jobs $worker: ${(V)junk}"
|
||||
while zpty -r -t $worker junk '*'; do
|
||||
(( ASYNC_DEBUG )) && print -n "${(V)junk}"
|
||||
done
|
||||
(( ASYNC_DEBUG )) && print
|
||||
fi
|
||||
|
||||
# Finally, clear the process buffer in case of partially parsed responses.
|
||||
typeset -gA ASYNC_PROCESS_BUFFER
|
||||
unset "ASYNC_PROCESS_BUFFER[$worker]"
|
||||
}
|
||||
|
||||
#
|
||||
# Start a new async worker with optional parameters, a worker can be told to only run unique tasks and to notify a
|
||||
# process when tasks are complete.
|
||||
#
|
||||
# usage:
|
||||
# async_start_worker <worker_name> [-u] [-n] [-p <pid>]
|
||||
#
|
||||
# opts:
|
||||
# -u unique (only unique job names can run)
|
||||
# -n notify through SIGWINCH signal
|
||||
# -p pid to notify (defaults to current pid)
|
||||
#
|
||||
async_start_worker() {
|
||||
setopt localoptions noshwordsplit
|
||||
|
||||
local worker=$1; shift
|
||||
zpty -t $worker &>/dev/null && return
|
||||
|
||||
typeset -gA ASYNC_PTYS
|
||||
typeset -h REPLY
|
||||
typeset has_xtrace=0
|
||||
|
||||
# Make sure async worker is started without xtrace
|
||||
# (the trace output interferes with the worker).
|
||||
[[ -o xtrace ]] && {
|
||||
has_xtrace=1
|
||||
unsetopt xtrace
|
||||
}
|
||||
|
||||
if (( ! ASYNC_ZPTY_RETURNS_FD )) && [[ -o interactive ]] && [[ -o zle ]]; then
|
||||
# When zpty doesn't return a file descriptor (on older versions of zsh)
|
||||
# we try to guess it anyway.
|
||||
integer -l zptyfd
|
||||
exec {zptyfd}>&1 # Open a new file descriptor (above 10).
|
||||
exec {zptyfd}>&- # Close it so it's free to be used by zpty.
|
||||
fi
|
||||
|
||||
zpty -b $worker _async_worker -p $$ $@ || {
|
||||
async_stop_worker $worker
|
||||
return 1
|
||||
}
|
||||
|
||||
# Re-enable it if it was enabled, for debugging.
|
||||
(( has_xtrace )) && setopt xtrace
|
||||
|
||||
if [[ $ZSH_VERSION < 5.0.8 ]]; then
|
||||
# For ZSH versions older than 5.0.8 we delay a bit to give
|
||||
# time for the worker to start before issuing commands,
|
||||
# otherwise it will not be ready to receive them.
|
||||
sleep 0.001
|
||||
fi
|
||||
|
||||
if [[ -o interactive ]] && [[ -o zle ]]; then
|
||||
if (( ! ASYNC_ZPTY_RETURNS_FD )); then
|
||||
REPLY=$zptyfd # Use the guessed value for the file desciptor.
|
||||
fi
|
||||
|
||||
ASYNC_PTYS[$REPLY]=$worker # Map the file desciptor to the worker.
|
||||
zle -F $REPLY _async_zle_watcher # Register the ZLE handler.
|
||||
|
||||
# Disable trap in favor of ZLE handler when notify is enabled (-n).
|
||||
async_job $worker _unset_trap
|
||||
fi
|
||||
}
|
||||
|
||||
#
|
||||
# Stop one or multiple workers that are running, all unfetched and incomplete work will be lost.
|
||||
#
|
||||
# usage:
|
||||
# async_stop_worker <worker_name_1> [<worker_name_2>]
|
||||
#
|
||||
async_stop_worker() {
|
||||
setopt localoptions noshwordsplit
|
||||
|
||||
local ret=0 worker k v
|
||||
for worker in $@; do
|
||||
# Find and unregister the zle handler for the worker
|
||||
for k v in ${(@kv)ASYNC_PTYS}; do
|
||||
if [[ $v == $worker ]]; then
|
||||
zle -F $k
|
||||
unset "ASYNC_PTYS[$k]"
|
||||
fi
|
||||
done
|
||||
async_unregister_callback $worker
|
||||
zpty -d $worker 2>/dev/null || ret=$?
|
||||
|
||||
# Clear any partial buffers.
|
||||
typeset -gA ASYNC_PROCESS_BUFFER
|
||||
unset "ASYNC_PROCESS_BUFFER[$worker]"
|
||||
done
|
||||
|
||||
return $ret
|
||||
}
|
||||
|
||||
#
|
||||
# Initialize the required modules for zsh-async. To be called before using the zsh-async library.
|
||||
#
|
||||
# usage:
|
||||
# async_init
|
||||
#
|
||||
async_init() {
|
||||
(( ASYNC_INIT_DONE )) && return
|
||||
typeset -g ASYNC_INIT_DONE=1
|
||||
|
||||
zmodload zsh/zpty
|
||||
zmodload zsh/datetime
|
||||
|
||||
# Check if zsh/zpty returns a file descriptor or not,
|
||||
# shell must also be interactive with zle enabled.
|
||||
typeset -g ASYNC_ZPTY_RETURNS_FD=0
|
||||
[[ -o interactive ]] && [[ -o zle ]] && {
|
||||
typeset -h REPLY
|
||||
zpty _async_test :
|
||||
(( REPLY )) && ASYNC_ZPTY_RETURNS_FD=1
|
||||
zpty -d _async_test
|
||||
}
|
||||
}
|
||||
|
||||
async() {
|
||||
async_init
|
||||
}
|
||||
|
||||
async "$@"
|
574
.zprezto/modules/prompt/external/async/async_test.zsh
vendored
Normal file
574
.zprezto/modules/prompt/external/async/async_test.zsh
vendored
Normal file
@@ -0,0 +1,574 @@
|
||||
#!/usr/bin/env zsh
|
||||
|
||||
test__async_job_print_hi() {
|
||||
coproc cat
|
||||
print -n -p t # Insert token into coproc.
|
||||
|
||||
local line
|
||||
local -a out
|
||||
line=$(_async_job print hi)
|
||||
# Remove trailing null, parse, unquote and interpret as array.
|
||||
line=$line[1,$#line-1]
|
||||
out=("${(@Q)${(z)line}}")
|
||||
|
||||
coproc exit
|
||||
|
||||
[[ $out[1] = print ]] || t_error "command name should be print, got" $out[1]
|
||||
[[ $out[2] = 0 ]] || t_error "want exit code 0, got" $out[2]
|
||||
[[ $out[3] = hi ]] || t_error "want output: hi, got" $out[3]
|
||||
}
|
||||
|
||||
test__async_job_stderr() {
|
||||
coproc cat
|
||||
print -n -p t # Insert token into coproc.
|
||||
|
||||
local line
|
||||
local -a out
|
||||
line=$(_async_job print 'hi 1>&2')
|
||||
# Remove trailing null, parse, unquote and interpret as array.
|
||||
line=$line[1,$#line-1]
|
||||
out=("${(@Q)${(z)line}}")
|
||||
|
||||
coproc exit
|
||||
|
||||
[[ $out[2] = 0 ]] || t_error "want status 0, got" $out[2]
|
||||
[[ -z $out[3] ]] || t_error "want empty output, got" $out[3]
|
||||
[[ $out[5] = hi ]] || t_error "want stderr: hi, got" $out[5]
|
||||
}
|
||||
|
||||
test__async_job_wait_for_token() {
|
||||
float start duration
|
||||
coproc cat
|
||||
|
||||
_async_job print hi >/dev/null &
|
||||
job=$!
|
||||
start=$EPOCHREALTIME
|
||||
{
|
||||
sleep 0.1
|
||||
print -n -p t
|
||||
} &
|
||||
|
||||
wait $job
|
||||
|
||||
coproc exit
|
||||
|
||||
duration=$(( EPOCHREALTIME - start ))
|
||||
# Fail if the execution time was faster than 0.1 seconds.
|
||||
(( duration >= 0.1 )) || t_error "execution was too fast, want >= 0.1, got" $duration
|
||||
}
|
||||
|
||||
test__async_job_multiple_commands() {
|
||||
coproc cat
|
||||
print -n -p t
|
||||
|
||||
local line
|
||||
local -a out
|
||||
line="$(_async_job print '-n hi; for i in "1 2" 3 4; do print -n $i; done')"
|
||||
# Remove trailing null, parse, unquote and interpret as array.
|
||||
line=$line[1,$#line-1]
|
||||
out=("${(@Q)${(z)line}}")
|
||||
|
||||
coproc exit
|
||||
|
||||
# $out[1] here will be the entire string passed to _async_job()
|
||||
# ('print -n hi...') since proper command parsing is done by
|
||||
# the async worker.
|
||||
[[ $out[3] = "hi1 234" ]] || t_error "want output hi1 234, got " $out[3]
|
||||
}
|
||||
|
||||
test_async_start_stop_worker() {
|
||||
local out
|
||||
|
||||
async_start_worker test
|
||||
out=$(zpty -L)
|
||||
[[ $out =~ "test _async_worker" ]] || t_error "want zpty worker running, got ${(Vq-)out}"
|
||||
|
||||
async_stop_worker test || t_error "stop worker: want exit code 0, got $?"
|
||||
out=$(zpty -L)
|
||||
[[ -z $out ]] || t_error "want no zpty worker running, got ${(Vq-)out}"
|
||||
|
||||
async_stop_worker nonexistent && t_error "stop non-existent worker: want exit code 1, got $?"
|
||||
}
|
||||
|
||||
test_async_job_print_matches_input_exactly() {
|
||||
local -a result
|
||||
cb() { result=("$@") }
|
||||
|
||||
async_start_worker test
|
||||
t_defer async_stop_worker test
|
||||
|
||||
want='
|
||||
Hello world!
|
||||
Much *formatting*,
|
||||
many space\t...\n\n
|
||||
Such "quote", v '$'\'quote\'''
|
||||
'
|
||||
|
||||
async_job test print -r - "$want"
|
||||
while ! async_process_results test cb; do :; done
|
||||
|
||||
[[ $result[3] = $want ]] || t_error "output, want ${(Vqqqq)want}, got ${(Vqqqq)result[3]}"
|
||||
}
|
||||
|
||||
test_async_process_results() {
|
||||
local -a r
|
||||
cb() { r+=("$@") }
|
||||
|
||||
async_start_worker test
|
||||
t_defer async_stop_worker test
|
||||
|
||||
async_process_results test cb # No results.
|
||||
ret=$?
|
||||
(( ret == 1 )) || t_error "want exit code 1, got $ret"
|
||||
|
||||
async_job test print -n hi
|
||||
while ! async_process_results test cb; do :; done
|
||||
(( $#r == 6 )) || t_error "want one result, got $(( $#r % 6 ))"
|
||||
}
|
||||
|
||||
test_async_process_results_stress() {
|
||||
# NOTE: This stress test does not always pass properly on older versions of
|
||||
# zsh, sometimes writing to zpty can hang and other times reading can hang,
|
||||
# etc.
|
||||
local -a r
|
||||
cb() { r+=("$@") }
|
||||
|
||||
async_start_worker test
|
||||
t_defer async_stop_worker test
|
||||
|
||||
integer iter=40 timeout=5
|
||||
for i in {1..$iter}; do
|
||||
async_job test "print -n $i"
|
||||
|
||||
# TODO: Figure out how we can remove sleep & process here.
|
||||
|
||||
# If we do not sleep here, we end up losing some of the commands sent to
|
||||
# async_job (~90 get sent). This could possibly be due to the zpty
|
||||
# buffer being full (see below).
|
||||
sleep 0.00001
|
||||
# Without processing resuls we occasionally run into 'print -n 39'
|
||||
# failing due to the command name and exit status missing. Sample output
|
||||
# from processing for 39 (stdout, time, stderr):
|
||||
# $'39 0.0056798458 '
|
||||
# This is again, probably due to the zpty buffer being full, we only
|
||||
# need to ensure that not too many commands are run before we process.
|
||||
(( iter % 6 == 0 )) && async_process_results test cb
|
||||
done
|
||||
|
||||
float start=$EPOCHSECONDS
|
||||
|
||||
while (( $#r / 6 < iter )); do
|
||||
async_process_results test cb
|
||||
(( EPOCHSECONDS - start > timeout )) && {
|
||||
t_log "timed out after ${timeout}s"
|
||||
t_fatal "wanted $iter results, got $(( $#r / 6 ))"
|
||||
}
|
||||
done
|
||||
|
||||
local -a stdouts
|
||||
while (( $#r > 0 )); do
|
||||
[[ $r[1] = print ]] || t_error "want 'print', got ${(Vq-)r[1]}"
|
||||
[[ $r[2] = 0 ]] || t_error "want exit 0, got $r[2]"
|
||||
stdouts+=($r[3])
|
||||
[[ -z $r[5] ]] || t_error "want no stderr, got ${(Vq-)r[5]}"
|
||||
shift 6 r
|
||||
done
|
||||
|
||||
local got want
|
||||
# Check that we received all numbers.
|
||||
got=(${(on)stdouts})
|
||||
want=({1..$iter})
|
||||
[[ $want = $got ]] || t_error "want stdout: ${(Vq-)want}, got ${(Vq-)got}"
|
||||
|
||||
# Test with longer running commands (sleep, then print).
|
||||
iter=40
|
||||
for i in {1..$iter}; do
|
||||
async_job test "sleep 1 && print -n $i"
|
||||
sleep 0.00001
|
||||
(( iter % 6 == 0 )) && async_process_results test cb
|
||||
done
|
||||
|
||||
start=$EPOCHSECONDS
|
||||
|
||||
while (( $#r / 6 < iter )); do
|
||||
async_process_results test cb
|
||||
(( EPOCHSECONDS - start > timeout )) && {
|
||||
t_log "timed out after ${timeout}s"
|
||||
t_fatal "wanted $iter results, got $(( $#r / 6 ))"
|
||||
}
|
||||
done
|
||||
|
||||
stdouts=()
|
||||
while (( $#r > 0 )); do
|
||||
[[ $r[1] = sleep ]] || t_error "want 'sleep', got ${(Vq-)r[1]}"
|
||||
[[ $r[2] = 0 ]] || t_error "want exit 0, got $r[2]"
|
||||
stdouts+=($r[3])
|
||||
[[ -z $r[5] ]] || t_error "want no stderr, got ${(Vq-)r[5]}"
|
||||
shift 6 r
|
||||
done
|
||||
|
||||
# Check that we received all numbers.
|
||||
got=(${(on)stdouts})
|
||||
want=({1..$iter})
|
||||
[[ $want = $got ]] || t_error "want stdout: ${(Vq-)want}, got ${(Vq-)got}"
|
||||
}
|
||||
|
||||
test_async_job_multiple_commands_in_multiline_string() {
|
||||
local -a result
|
||||
cb() { result=("$@") }
|
||||
|
||||
async_start_worker test
|
||||
# Test multi-line (single string) command.
|
||||
async_job test 'print "hi\n 123 "'$'\nprint -n bye'
|
||||
while ! async_process_results test cb; do :; done
|
||||
async_stop_worker test
|
||||
|
||||
[[ $result[1] = print ]] || t_error "want command name: print, got" $result[1]
|
||||
local want=$'hi\n 123 \nbye'
|
||||
[[ $result[3] = $want ]] || t_error "want output: ${(Vq-)want}, got ${(Vq-)result[3]}"
|
||||
}
|
||||
|
||||
test_async_job_git_status() {
|
||||
local -a result
|
||||
cb() { result=("$@") }
|
||||
|
||||
async_start_worker test
|
||||
async_job test git status --porcelain
|
||||
while ! async_process_results test cb; do :; done
|
||||
async_stop_worker test
|
||||
|
||||
[[ $result[1] = git ]] || t_error "want command name: git, got" $result[1]
|
||||
[[ $result[2] = 0 ]] || t_error "want exit code: 0, got" $result[2]
|
||||
|
||||
want=$(git status --porcelain)
|
||||
got=$result[3]
|
||||
[[ $got = $want ]] || t_error "want ${(Vq-)want}, got ${(Vq-)got}"
|
||||
}
|
||||
|
||||
test_async_job_multiple_arguments_and_spaces() {
|
||||
local -a result
|
||||
cb() { result=("$@") }
|
||||
|
||||
async_start_worker test
|
||||
async_job test print "hello world"
|
||||
while ! async_process_results test cb; do :; done
|
||||
async_stop_worker test
|
||||
|
||||
[[ $result[1] = print ]] || t_error "want command name: print, got" $result[1]
|
||||
[[ $result[2] = 0 ]] || t_error "want exit code: 0, got" $result[2]
|
||||
|
||||
[[ $result[3] = "hello world" ]] || {
|
||||
t_error "want output: \"hello world\", got" ${(Vq-)result[3]}
|
||||
}
|
||||
}
|
||||
|
||||
test_async_job_unique_worker() {
|
||||
local -a result
|
||||
cb() {
|
||||
# Add to result so we can detect if it was called multiple times.
|
||||
result+=("$@")
|
||||
}
|
||||
helper() {
|
||||
sleep 0.1; print $1
|
||||
}
|
||||
|
||||
# Start a unique (job) worker.
|
||||
async_start_worker test -u
|
||||
|
||||
# Launch two jobs with the same name, the first one should be
|
||||
# allowed to complete whereas the second one is never run.
|
||||
async_job test helper one
|
||||
async_job test helper two
|
||||
|
||||
while ! async_process_results test cb; do :; done
|
||||
|
||||
# If both jobs were running but only one was complete,
|
||||
# async_process_results() could've returned true for
|
||||
# the first job, wait a little extra to make sure the
|
||||
# other didn't run.
|
||||
sleep 0.1
|
||||
async_process_results test cb
|
||||
|
||||
async_stop_worker test
|
||||
|
||||
# Ensure that cb was only called once with correc output.
|
||||
[[ ${#result} = 6 ]] || t_error "result: want 6 elements, got" ${#result}
|
||||
[[ $result[3] = one ]] || t_error "output: want 'one', got" ${(Vq-)result[3]}
|
||||
}
|
||||
|
||||
test_async_job_error_and_nonzero_exit() {
|
||||
local -a r
|
||||
cb() { r+=("$@") }
|
||||
error() {
|
||||
print "Errors!"
|
||||
12345
|
||||
54321
|
||||
print "Done!"
|
||||
exit 99
|
||||
}
|
||||
|
||||
async_start_worker test
|
||||
async_job test error
|
||||
|
||||
while ! async_process_results test cb; do :; done
|
||||
|
||||
[[ $r[1] = error ]] || t_error "want 'error', got ${(Vq-)r[1]}"
|
||||
[[ $r[2] = 99 ]] || t_error "want exit code 99, got $r[2]"
|
||||
|
||||
want=$'Errors!\nDone!'
|
||||
[[ $r[3] = $want ]] || t_error "want ${(Vq-)want}, got ${(Vq-)r[3]}"
|
||||
|
||||
want=$'.*command not found: 12345\n.*command not found: 54321'
|
||||
[[ $r[5] =~ $want ]] || t_error "want ${(Vq-)want}, got ${(Vq-)r[5]}"
|
||||
}
|
||||
|
||||
test_async_worker_notify_sigwinch() {
|
||||
local -a result
|
||||
cb() { result=("$@") }
|
||||
|
||||
ASYNC_USE_ZLE_HANDLER=0
|
||||
|
||||
async_start_worker test -n
|
||||
async_register_callback test cb
|
||||
|
||||
async_job test 'sleep 0.1; print hi'
|
||||
|
||||
while (( ! $#result )); do sleep 0.01; done
|
||||
|
||||
async_stop_worker test
|
||||
|
||||
[[ $result[3] = hi ]] || t_error "expected output: hi, got" $result[3]
|
||||
}
|
||||
|
||||
test_async_job_keeps_nulls() {
|
||||
local -a r
|
||||
cb() { r=("$@") }
|
||||
null_echo() {
|
||||
print Hello$'\0' with$'\0' nulls!
|
||||
print "Did we catch them all?"$'\0'
|
||||
print $'\0'"What about the errors?"$'\0' 1>&2
|
||||
}
|
||||
|
||||
async_start_worker test
|
||||
async_job test null_echo
|
||||
|
||||
while ! async_process_results test cb; do :; done
|
||||
|
||||
async_stop_worker test
|
||||
|
||||
local want
|
||||
want=$'Hello\0 with\0 nulls!\nDid we catch them all?\0'
|
||||
[[ $r[3] = $want ]] || t_error stdout: want ${(Vq-)want}, got ${(Vq-)r[3]}
|
||||
want=$'\0What about the errors?\0'
|
||||
[[ $r[5] = $want ]] || t_error stderr: want ${(Vq-)want}, got ${(Vq-)r[5]}
|
||||
}
|
||||
|
||||
test_async_flush_jobs() {
|
||||
local -a r
|
||||
cb() { r=+("$@") }
|
||||
|
||||
print_four() { print -n 4 }
|
||||
print_123_delayed_exit() {
|
||||
print -n 1
|
||||
{ sleep 0.25 && print -n 2 } &!
|
||||
{ sleep 0.3 && print -n 3 } &!
|
||||
}
|
||||
|
||||
async_start_worker test
|
||||
|
||||
# Start a job that prints 1 and starts two disowned child processes that
|
||||
# print 2 and 3, respectively, after a timeout. The job will not exit
|
||||
# immediately (and thus print 1) because the child processes are still
|
||||
# running.
|
||||
async_job test print_123_delayed_exit
|
||||
|
||||
# Check that the job is waiting for the child processes.
|
||||
sleep 0.05
|
||||
async_process_results test cb
|
||||
(( $#r == 0 )) || t_error "want no output, got ${(Vq-)r}"
|
||||
|
||||
# Start a job that prints four, it will produce
|
||||
# output but we will not process it.
|
||||
async_job test print_four
|
||||
sleep 0.2
|
||||
|
||||
# Flush jobs, this kills running jobs and discards unprocessed results.
|
||||
# TODO: Confirm that they no longer exist in the process tree.
|
||||
local output
|
||||
output="${(Q)$(ASYNC_DEBUG=1 async_flush_jobs test)}"
|
||||
[[ $output = *'print_four 0 4'* ]] || {
|
||||
t_error "want discarded output 'print_four 0 4' when ASYNC_DEBUG=1, got ${(Vq-)output}"
|
||||
}
|
||||
|
||||
# Check that the killed job did not produce output.
|
||||
sleep 0.1
|
||||
async_process_results test cb
|
||||
(( $#r == 0 )) || t_error "want no output, got ${(Vq-)r}"
|
||||
|
||||
async_stop_worker test
|
||||
}
|
||||
|
||||
test_async_worker_survives_termination_of_other_worker() {
|
||||
local -a result
|
||||
cb() { result+=("$@") }
|
||||
|
||||
async_start_worker test1
|
||||
t_defer async_stop_worker test1
|
||||
|
||||
# Start and stop a worker, will send SIGHUP to previous worker
|
||||
# (probably has to do with some shell inheritance).
|
||||
async_start_worker test2
|
||||
async_stop_worker test2
|
||||
|
||||
async_job test1 print hi
|
||||
|
||||
integer start=$EPOCHREALTIME
|
||||
while (( EPOCHREALTIME - start < 2.0 )); do
|
||||
async_process_results test1 cb && break
|
||||
done
|
||||
|
||||
(( $#result == 6 )) || t_error "wanted a result, got (${(@Vq)result})"
|
||||
}
|
||||
|
||||
setopt_helper() {
|
||||
setopt localoptions $1
|
||||
|
||||
# Make sure to test with multiple options
|
||||
local -a result
|
||||
cb() { result=("$@") }
|
||||
|
||||
async_start_worker test
|
||||
async_job test print "hello world"
|
||||
while ! async_process_results test cb; do :; done
|
||||
async_stop_worker test
|
||||
|
||||
# At this point, ksh arrays will only mess with the test.
|
||||
setopt noksharrays
|
||||
|
||||
[[ $result[1] = print ]] || t_fatal "$1 want command name: print, got" $result[1]
|
||||
[[ $result[2] = 0 ]] || t_fatal "$1 want exit code: 0, got" $result[2]
|
||||
|
||||
[[ $result[3] = "hello world" ]] || {
|
||||
t_fatal "$1 want output: \"hello world\", got" ${(Vq-)result[3]}
|
||||
}
|
||||
}
|
||||
|
||||
test_all_options() {
|
||||
local -a opts exclude
|
||||
|
||||
if [[ $ZSH_VERSION == 5.0.? ]]; then
|
||||
t_skip "Test is not reliable on zsh 5.0.X"
|
||||
fi
|
||||
|
||||
# Make sure worker is stopped, even if tests fail.
|
||||
t_defer async_stop_worker test
|
||||
|
||||
{ sleep 15 && t_fatal "timed out" } &
|
||||
local tpid=$!
|
||||
|
||||
opts=(${(k)options})
|
||||
|
||||
# These options can't be tested.
|
||||
exclude=(
|
||||
zle interactive restricted shinstdin stdin onecmd singlecommand
|
||||
warnnestedvar errreturn
|
||||
)
|
||||
|
||||
for opt in ${opts:|exclude}; do
|
||||
if [[ $options[$opt] = on ]]; then
|
||||
setopt_helper no$opt
|
||||
else
|
||||
setopt_helper $opt
|
||||
fi
|
||||
done 2>/dev/null # Remove redirect to see output.
|
||||
|
||||
kill $tpid # Stop timeout.
|
||||
}
|
||||
|
||||
test_async_job_with_rc_expand_param() {
|
||||
setopt localoptions rcexpandparam
|
||||
|
||||
# Make sure to test with multiple options
|
||||
local -a result
|
||||
cb() { result=("$@") }
|
||||
|
||||
async_start_worker test
|
||||
async_job test print "hello world"
|
||||
while ! async_process_results test cb; do :; done
|
||||
async_stop_worker test
|
||||
|
||||
[[ $result[1] = print ]] || t_error "want command name: print, got" $result[1]
|
||||
[[ $result[2] = 0 ]] || t_error "want exit code: 0, got" $result[2]
|
||||
|
||||
[[ $result[3] = "hello world" ]] || {
|
||||
t_error "want output: \"hello world\", got" ${(Vq-)result[3]}
|
||||
}
|
||||
}
|
||||
|
||||
zpty_init() {
|
||||
zmodload zsh/zpty
|
||||
|
||||
export PS1="<PROMPT>"
|
||||
zpty zsh 'zsh -f +Z'
|
||||
zpty -r zsh zpty_init1 "*<PROMPT>*" || {
|
||||
t_log "initial prompt missing"
|
||||
return 1
|
||||
}
|
||||
|
||||
zpty -w zsh "{ $@ }"
|
||||
zpty -r -m zsh zpty_init2 "*<PROMPT>*" || {
|
||||
t_log "prompt missing"
|
||||
return 1
|
||||
}
|
||||
}
|
||||
|
||||
zpty_run() {
|
||||
zpty -w zsh "$*"
|
||||
zpty -r -m zsh zpty_run "*<PROMPT>*" || {
|
||||
t_log "prompt missing after ${(Vq-)*}"
|
||||
return 1
|
||||
}
|
||||
}
|
||||
|
||||
zpty_deinit() {
|
||||
zpty -d zsh
|
||||
}
|
||||
|
||||
test_zle_watcher() {
|
||||
zpty_init '
|
||||
emulate -R zsh
|
||||
setopt zle
|
||||
stty 38400 columns 80 rows 24 tabs -icanon -iexten
|
||||
TERM=vt100
|
||||
|
||||
. "'$PWD'/async.zsh"
|
||||
async_init
|
||||
|
||||
print_result_cb() { print ${(Vq-)@} }
|
||||
async_start_worker test
|
||||
async_register_callback test print_result_cb
|
||||
' || {
|
||||
zpty_deinit
|
||||
t_fatal "failed to init zpty"
|
||||
}
|
||||
|
||||
t_defer zpty_deinit # Deinit after test completion.
|
||||
|
||||
zpty -w zsh "zle -F"
|
||||
zpty -r -m zsh result "*_async_zle_watcher*" || {
|
||||
t_fatal "want _async_zle_watcher to be registered as zle watcher, got output ${(Vq-)result}"
|
||||
}
|
||||
|
||||
zpty_run async_job test 'print hello world' || t_fatal "could not send async_job command"
|
||||
|
||||
zpty -r -m zsh result "*print 0 'hello world'*" || {
|
||||
t_fatal "want \"print 0 'hello world'\", got output ${(Vq-)result}"
|
||||
}
|
||||
}
|
||||
|
||||
test_main() {
|
||||
# Load zsh-async before running each test.
|
||||
zmodload zsh/datetime
|
||||
. ./async.zsh
|
||||
async_init
|
||||
}
|
263
.zprezto/modules/prompt/external/async/test.zsh
vendored
Executable file
263
.zprezto/modules/prompt/external/async/test.zsh
vendored
Executable file
@@ -0,0 +1,263 @@
|
||||
#!/usr/bin/env zsh
|
||||
#
|
||||
# zsh-async test runner.
|
||||
# Checks for test files named *_test.zsh or *_test.sh and runs all functions
|
||||
# named test_*.
|
||||
#
|
||||
emulate -R zsh
|
||||
|
||||
zmodload zsh/datetime
|
||||
zmodload zsh/parameter
|
||||
zmodload zsh/zutil
|
||||
zmodload zsh/system
|
||||
zmodload zsh/zselect
|
||||
|
||||
TEST_GLOB=.
|
||||
TEST_RUN=
|
||||
TEST_VERBOSE=0
|
||||
TEST_TRACE=1
|
||||
TEST_CODE_SKIP=100
|
||||
TEST_CODE_ERROR=101
|
||||
TEST_CODE_TIMEOUT=102
|
||||
|
||||
show_help() {
|
||||
print "usage: ./test.zsh [-v] [-x] [-run pattern] [search pattern]"
|
||||
}
|
||||
|
||||
parse_opts() {
|
||||
local -a verbose debug trace help run
|
||||
|
||||
local out
|
||||
zparseopts -E -D \
|
||||
v=verbose verbose=verbose -verbose=verbose \
|
||||
d=debug debug=debug -debug=debug \
|
||||
x=trace trace=trace -trace=trace \
|
||||
h=help -help=help \
|
||||
\?=help \
|
||||
run:=run -run:=run
|
||||
|
||||
(( $? )) || (( $+help[1] )) && show_help && exit 0
|
||||
|
||||
if (( $#@ > 1 )); then
|
||||
print -- "unknown arguments: $@"
|
||||
show_help
|
||||
exit 1
|
||||
fi
|
||||
|
||||
[[ -n $1 ]] && TEST_GLOB=$1
|
||||
TEST_VERBOSE=$+verbose[1]
|
||||
TEST_TRACE=$+trace[1]
|
||||
ZTEST_DEBUG=$+debug[1]
|
||||
(( $+run[2] )) && TEST_RUN=$run[2]
|
||||
}
|
||||
|
||||
t_runner_init() {
|
||||
emulate -L zsh
|
||||
|
||||
zmodload zsh/parameter
|
||||
|
||||
# _t_runner is the main loop that waits for tests,
|
||||
# used to abort test execution by exec.
|
||||
_t_runner() {
|
||||
local -a _test_defer_funcs
|
||||
integer _test_errors=0
|
||||
while read -r; do
|
||||
eval "$REPLY"
|
||||
done
|
||||
}
|
||||
|
||||
_t_log() {
|
||||
local trace=$1; shift
|
||||
local -a lines indent
|
||||
lines=("${(@f)@}")
|
||||
indent=($'\t\t'${^lines[2,$#lines]})
|
||||
print -u7 -lr - $'\t'"$trace: $lines[1]" ${(F)indent}
|
||||
}
|
||||
|
||||
# t_log is for printing log output, visible in verbose (-v) mode.
|
||||
t_log() {
|
||||
local line=$funcfiletrace[1]
|
||||
[[ ${line%:[0-9]*} = "" ]] && line=ztest:$functrace[1] # Not from a file.
|
||||
_t_log $line "$*"
|
||||
}
|
||||
|
||||
# t_skip is for skipping a test.
|
||||
t_skip() {
|
||||
_t_log $funcfiletrace[1] "$*"
|
||||
() { return 100 }
|
||||
t_done
|
||||
}
|
||||
|
||||
# t_error logs the error and fails the test without aborting.
|
||||
t_error() {
|
||||
(( _test_errors++ ))
|
||||
_t_log $funcfiletrace[1] "$*"
|
||||
}
|
||||
|
||||
# t_fatal fails the test and halts execution immediately.
|
||||
t_fatal() {
|
||||
_t_log $funcfiletrace[1] "$*"
|
||||
() { return 101 }
|
||||
t_done
|
||||
}
|
||||
|
||||
# t_defer takes a function (and optionally, arguments)
|
||||
# to be executed after the test has completed.
|
||||
t_defer() {
|
||||
_test_defer_funcs+=("$*")
|
||||
}
|
||||
|
||||
# t_done completes the test execution, called automatically after a test.
|
||||
# Can also be called manually when the test is done.
|
||||
t_done() {
|
||||
local ret=$? w=${1:-1}
|
||||
(( _test_errors )) && ret=101
|
||||
|
||||
(( w )) && wait # Wait for test children to exit.
|
||||
for d in $_test_defer_funcs; do
|
||||
eval "$d"
|
||||
done
|
||||
print -n -u8 $ret # Send exit code to ztest.
|
||||
exec _t_runner # Replace shell, wait for new test.
|
||||
}
|
||||
|
||||
source $1 # Load the test module.
|
||||
|
||||
# Send available test functions to main process.
|
||||
print -u7 ${(R)${(okM)functions:#test_*}:#test_main}
|
||||
|
||||
# Run test_main.
|
||||
if [[ -n $functions[test_main] ]]; then
|
||||
test_main
|
||||
fi
|
||||
|
||||
exec _t_runner # Wait for commands.
|
||||
}
|
||||
|
||||
# run_test_module runs all the tests from a test module (asynchronously).
|
||||
run_test_module() {
|
||||
local module=$1
|
||||
local -a tests
|
||||
float start module_time
|
||||
|
||||
# Create fd's for communication with test runner.
|
||||
integer run_pid cmdoutfd cmdinfd outfd infd doneoutfd doneinfd
|
||||
|
||||
coproc cat; exec {cmdoutfd}>&p; exec {cmdinfd}<&p
|
||||
coproc cat; exec {outfd}>&p; exec {infd}<&p
|
||||
coproc cat; exec {doneoutfd}>&p; exec {doneinfd}<&p
|
||||
|
||||
# No need to keep coproc (&p) open since we
|
||||
# have redirected the outputs and inputs.
|
||||
coproc exit
|
||||
|
||||
# Launch a new interactive zsh test runner. We don't capture stdout
|
||||
typeset -a run_args
|
||||
(( TEST_TRACE )) && run_args+=('-x')
|
||||
zsh -s $run_args <&$cmdinfd 7>&$outfd 8>&$doneoutfd &
|
||||
run_pid=$!
|
||||
|
||||
# Initialize by sending function body from t_runner_init
|
||||
# and immediately execute it as an anonymous function.
|
||||
syswrite -o $cmdoutfd "() { ${functions[t_runner_init]} } $module"$'\n'
|
||||
sysread -i $infd
|
||||
tests=(${(@)=REPLY})
|
||||
[[ -n $TEST_RUN ]] && tests=(${(M)tests:#*$TEST_RUN*})
|
||||
|
||||
integer mod_exit=0
|
||||
float mod_start mod_time
|
||||
|
||||
mod_start=$EPOCHREALTIME # Store the module start time.
|
||||
|
||||
# Run all tests.
|
||||
local test_out
|
||||
float test_start test_time
|
||||
integer text_exit
|
||||
|
||||
for test in $tests; do
|
||||
(( TEST_VERBOSE )) && print "=== RUN $test"
|
||||
|
||||
test_start=$EPOCHREALTIME # Store the test start time.
|
||||
|
||||
# Start the test.
|
||||
syswrite -o $cmdoutfd "$test; t_done"$'\n'
|
||||
|
||||
test_out=
|
||||
test_exit=-1
|
||||
while (( test_exit == -1 )); do
|
||||
# Block until there is data to be read.
|
||||
zselect -r $doneinfd -r $infd
|
||||
|
||||
if [[ $reply[2] = $doneinfd ]]; then
|
||||
sysread -i $doneinfd
|
||||
test_exit=$REPLY # Store reply from sysread
|
||||
# Store the test execution time.
|
||||
test_time=$(( EPOCHREALTIME - test_start ))
|
||||
fi
|
||||
|
||||
# Read all output from the test output channel.
|
||||
while sysread -i $infd -t 0; do
|
||||
test_out+=$REPLY
|
||||
unset REPLY
|
||||
done
|
||||
done
|
||||
|
||||
case $test_exit in
|
||||
(0|1) state=PASS;;
|
||||
(100) state=SKIP;;
|
||||
(101|102) state=FAIL; mod_exit=1;;
|
||||
*) state="????";;
|
||||
esac
|
||||
|
||||
if [[ $state = FAIL ]] || (( TEST_VERBOSE )); then
|
||||
printf -- "--- $state: $test (%.2fs)\n" $test_time
|
||||
print -n $test_out
|
||||
fi
|
||||
done
|
||||
|
||||
# Store module execution time.
|
||||
mod_time=$(( EPOCHREALTIME - mod_start ))
|
||||
|
||||
# Perform cleanup.
|
||||
kill -HUP $run_pid
|
||||
exec {outfd}>&-
|
||||
exec {infd}<&-
|
||||
exec {cmdinfd}>&-
|
||||
exec {cmdoutfd}<&-
|
||||
exec {doneinfd}<&-
|
||||
exec {doneoutfd}>&-
|
||||
|
||||
if (( mod_exit )); then
|
||||
print "FAIL"
|
||||
(( TEST_VERBOSE )) && print "exit code $mod_exit"
|
||||
printf "FAIL\t$module\t%.3fs\n" $mod_time
|
||||
else
|
||||
(( TEST_VERBOSE )) && print "PASS"
|
||||
printf "ok\t$module\t%.3fs\n" $mod_time
|
||||
fi
|
||||
|
||||
return $mod_exit
|
||||
}
|
||||
|
||||
cleanup() {
|
||||
trap - HUP
|
||||
kill -HUP $$ 2>/dev/null
|
||||
kill -HUP -$$ 2>/dev/null
|
||||
}
|
||||
|
||||
trap cleanup EXIT INT HUP QUIT TERM USR1
|
||||
|
||||
# Parse command arguments.
|
||||
parse_opts $@
|
||||
|
||||
(( ZTEST_DEBUG )) && setopt xtrace
|
||||
|
||||
# Execute tests modules.
|
||||
failed=0
|
||||
for tf in ${~TEST_GLOB}/*_test.(zsh|sh); do
|
||||
run_test_module $tf &
|
||||
wait $!
|
||||
(( $? )) && failed=1
|
||||
done
|
||||
|
||||
exit $failed
|
Reference in New Issue
Block a user