206 lines
6.1 KiB
YAML
206 lines
6.1 KiB
YAML
name: "Pipeline"
|
|
cases:
|
|
- name: "Basic pipe"
|
|
stdin: |
|
|
echo hi | grep -l h
|
|
|
|
- name: "Longer pipe"
|
|
stdin: |
|
|
echo hi | grep h | wc -l
|
|
|
|
- name: "Invert result"
|
|
stdin: |
|
|
! false
|
|
echo "! false: $?"
|
|
! true
|
|
echo "! true: $?"
|
|
|
|
- name: "Standalone negation (no command)"
|
|
stdin: |
|
|
!
|
|
echo "After !: $?"
|
|
! !
|
|
echo "After ! !: $?"
|
|
|
|
- name: "Exit codes for piped commands"
|
|
test_files:
|
|
- path: "script.sh"
|
|
executable: true
|
|
contents: |
|
|
#!/bin/sh
|
|
(cat; echo -n "-> $1")
|
|
exit $1
|
|
stdin: |
|
|
./script.sh 10 | ./script.sh 0 | ./script.sh 33
|
|
|
|
- name: "Side effects in pipe commands"
|
|
stdin: |
|
|
var=0
|
|
echo "var: ${var}"
|
|
{ var=1; }
|
|
echo "var: ${var}"
|
|
{ var=2; echo hi; } | cat
|
|
echo "var: ${var}"
|
|
echo hi | { var=3; cat; }
|
|
echo "var: ${var}"
|
|
|
|
- name: "pipeline extension"
|
|
stdin: |
|
|
echo -e "hello" |& wc -l
|
|
cat dfdfgdfgdf |& wc -l
|
|
foo() { cat dfgdfg; } |& wc -l
|
|
|
|
- name: "Possible broken pipe case"
|
|
stdin: |
|
|
echo hello | var=value
|
|
|
|
- name: "printf broken pipe returns 141 in PIPESTATUS"
|
|
stdin: |
|
|
printf '%s\n' {0..10000} | x=1
|
|
echo "Last: $?, PIPESTATUS: ${PIPESTATUS[*]}"
|
|
|
|
- name: "Function stage writing more than a pipe buffer before the next stage is spawned"
|
|
# Regression test: a shell function used as a non-last pipeline stage
|
|
# must run concurrently with the rest of the pipeline, not be awaited
|
|
# to completion before the next stage is even spawned. Otherwise, once
|
|
# the function writes more to its stdout pipe than the OS pipe buffer
|
|
# holds (commonly 64KiB on Linux) before returning, the write blocks
|
|
# forever because nothing is reading the other end yet.
|
|
stdin: |
|
|
big() {
|
|
local i
|
|
for i in $(seq 1 5000); do
|
|
echo "padding line number ${i} to exceed the pipe buffer size"
|
|
done
|
|
}
|
|
big | wc -l
|
|
echo "Last: $?, PIPESTATUS: ${PIPESTATUS[*]}"
|
|
|
|
- name: "Builtin that fails in a pipeline stage"
|
|
stdin: |
|
|
cd /nonexistent 2>/dev/null | true
|
|
echo "status: $?"
|
|
cd /nonexistent 2>&1 | { read -r line; echo "got an error message: ${line:+yes}"; }
|
|
|
|
- name: "Exit in a pipeline's last stage"
|
|
stdin: |
|
|
echo hi | exit 3
|
|
echo "status: $?"
|
|
|
|
- name: "Break and return in a pipeline's last stage"
|
|
stdin: |
|
|
for i in 1 2; do
|
|
echo "$i" | break
|
|
echo "i=$i"
|
|
done
|
|
|
|
f() {
|
|
echo hi | return 5
|
|
echo "after return"
|
|
}
|
|
f
|
|
echo "f: $?"
|
|
|
|
- name: "Compound command writing more than a pipe holds"
|
|
timeout_in_seconds: 3
|
|
stdin: |
|
|
# Each first stage writes 256 KiB, more than a pipe holds, before the next stage reads any.
|
|
s=x; for ((i = 0; i < 10; i++)); do s=$s$s; done
|
|
for ((i = 0; i < 256; i++)); do echo "$s"; done | wc -l
|
|
f() { for ((i = 0; i < 256; i++)); do echo "$s"; done; }
|
|
f | wc -l
|
|
{ f; echo last; } | tail -n 1
|
|
|
|
- name: "Endless producer into head"
|
|
known_failure: true # TODO(pipeline): the producer runs to completion before head starts, so it never stops
|
|
timeout_in_seconds: 3
|
|
stdin: |
|
|
while true; do echo y; done | head -n 1
|
|
|
|
- name: "Compound writer exceeds pipe capacity"
|
|
stdin: |
|
|
i=0
|
|
while [ $i -lt 4096 ]; do printf '%s\n' 0123456789012345678901234567890; i=$((i+1)); done | wc -l
|
|
|
|
- name: "Control flow in a pipeline stage stays in its subshell"
|
|
stdin: |
|
|
echo x | exit 3
|
|
echo "after exit: $?"
|
|
echo x | exec cat
|
|
echo "after exec: $?"
|
|
echo x | { exit 6; }
|
|
echo "after group: $?"
|
|
f() { true | return 4; echo "f still running: $?"; }
|
|
f
|
|
for i in 1 2; do echo x | break; echo "loop body $i"; done
|
|
|
|
- name: "A special builtin's fatal error in a pipeline stage ends only that stage"
|
|
known_failure: true # TODO(builtins): a usage error only skips exit, which returns 2 rather than exiting with 1 (see builtins/exit.yaml)
|
|
ignore_stderr: true
|
|
stdin: |
|
|
set -o posix
|
|
true | export -Z
|
|
echo "after usage error: $?"
|
|
true | exit 1 2
|
|
echo "after exit: $?"
|
|
|
|
- name: "An expansion error in a pipeline stage ends only that stage"
|
|
ignore_stderr: true
|
|
stdin: |
|
|
echo ${x?boom} | cat
|
|
echo "after \${x?}: ${PIPESTATUS[*]}"
|
|
f() { echo ${y?b}; }
|
|
f | cat
|
|
echo "after f: ${PIPESTATUS[*]}"
|
|
{ echo ${y?b}; echo same-stage; } | cat
|
|
echo "after group: ${PIPESTATUS[*]}"
|
|
echo hi | { read v; echo ${y?b}; }
|
|
echo "last stage: ${PIPESTATUS[*]}"
|
|
set -u
|
|
echo $zz | cat
|
|
echo "after set -u: ${PIPESTATUS[*]}"
|
|
|
|
- name: "An expansion error in a pipeline stage's prefix assignment ends only that stage"
|
|
ignore_stderr: true
|
|
stdin: |
|
|
true | X=${x?b} true
|
|
echo "last: ${PIPESTATUS[*]}"
|
|
X=${x?b} true | cat
|
|
echo "first: ${PIPESTATUS[*]}"
|
|
true | X=${x?b}
|
|
echo "assignment only: ${PIPESTATUS[*]}"
|
|
f() { :; }
|
|
true | X=${x?b} f
|
|
echo "function: ${PIPESTATUS[*]}"
|
|
|
|
- name: "An error turned into a result in a pipeline stage ends only that stage"
|
|
ignore_stderr: true
|
|
stdin: |
|
|
true | eval 'echo ${x?boom}'
|
|
echo "eval: ${PIPESTATUS[*]}"
|
|
f() { X=${x?boom} true; }
|
|
true | f
|
|
echo "function: ${PIPESTATUS[*]}"
|
|
true | { X=${x?boom} true; }
|
|
echo "group: ${PIPESTATUS[*]}"
|
|
true | eval 'exit 8'
|
|
echo "eval exit: ${PIPESTATUS[*]}"
|
|
|
|
- name: "Control flow in the last stage applies with lastpipe"
|
|
stdin: |
|
|
shopt -s lastpipe
|
|
f() { echo x | return 4; echo "f still running"; }
|
|
f
|
|
echo "f returned $?"
|
|
echo x | exit 5
|
|
echo "not reached"
|
|
|
|
# The subshell prints the report itself, so the report goes to the subshell's stderr.
|
|
- name: "time reports a subshell's timing on the subshell's stderr"
|
|
known_failure: true # TODO(pipeline): time reports on the shell's stderr, ignoring the subshell's redirection
|
|
stdin: |
|
|
time (exit 3) 2>/dev/null
|
|
echo "status: $?"
|
|
time (exec 2>/dev/null; true)
|
|
echo "status: $?"
|