Files
bun.sh/test/js/bun/shell/pipeline_stack.test.ts
Zack Radisic 0845231a1e Fix pipeline stack errors on Windows (#21800)
### What does this PR do?

### How did you verify your code works?

---------

Co-authored-by: autofix-ci[bot] <114827586+autofix-ci[bot]@users.noreply.github.com>
2025-08-14 18:03:26 -07:00

264 lines
10 KiB
TypeScript

import { $ } from "bun";
import { describe } from "bun:test";
import { bunEnv, bunExe } from "harness";
import { createTestBuilder } from "./util";
const TestBuilder = createTestBuilder(import.meta.path);
const BUN = bunExe();
$.env(bunEnv);
$.nothrow();
describe("pipeline stack edge cases", () => {
describe("builtin-to-builtin immediate exit", () => {
// This was the case that caused the use-after-free crash
TestBuilder.command`true | true`.exitCode(0).stdout("").runAsTest("true | true - both builtins exit immediately");
TestBuilder.command`false | false`
.exitCode(1)
.stdout("")
.runAsTest("false | false - both builtins fail immediately");
TestBuilder.command`true | false`.exitCode(1).stdout("").runAsTest("true | false - mixed success/failure");
TestBuilder.command`false | true`.exitCode(0).stdout("").runAsTest("false | true - failure then success");
// Chain of immediately-exiting builtins
TestBuilder.command`true | true | true | true`.exitCode(0).stdout("").runAsTest("chain of true builtins");
TestBuilder.command`false | false | false | false`.exitCode(1).stdout("").runAsTest("chain of false builtins");
TestBuilder.command`true | false | true | false`.exitCode(1).stdout("").runAsTest("alternating true/false chain");
});
describe("echo builtin pipes", () => {
// Echo builtins that output and immediately exit
TestBuilder.command`echo hello | echo world`.stdout("world\n").runAsTest("echo | echo - second echo ignores stdin");
TestBuilder.command`echo hello | echo world | echo final`.stdout("final\n").runAsTest("echo | echo | echo - chain");
TestBuilder.command`echo test | true`.exitCode(0).stdout("").runAsTest("echo | true - output discarded");
TestBuilder.command`echo test | false`
.exitCode(1)
.stdout("")
.runAsTest("echo | false - output discarded with failure");
TestBuilder.command`echo one | echo two | true | echo three`
.stdout("three\n")
.runAsTest("echo chain with true in middle");
});
describe("exit builtin in pipelines", () => {
TestBuilder.command`exit 0 | echo after`
.exitCode(0)
.stdout("after\n")
.runAsTest("exit 0 | echo - pipeline continues");
TestBuilder.command`exit 42 | echo after`
.exitCode(0)
.stdout("after\n")
.runAsTest("exit 42 | echo - exit code not propagated through pipe");
TestBuilder.command`echo before | exit 0`.exitCode(0).stdout("").runAsTest("echo | exit 0");
TestBuilder.command`echo before | exit 99`
.exitCode(99)
.stdout("")
.runAsTest("echo | exit 99 - last command exit code");
TestBuilder.command`exit 5 | exit 10 | exit 15`
.exitCode(15)
.stdout("")
.runAsTest("chain of exits - last exit code wins");
});
describe("cd builtin in pipelines", () => {
TestBuilder.command`cd / | pwd`
.stdout(s => s.includes("$TEMP_DIR"))
.ensureTempDir()
.runAsTest("cd | pwd - cd doesn't affect next command in pipeline");
TestBuilder.command`mkdir foo; mkdir foo/bar; cd foo | cd foo/bar | pwd`
.stdout(s => s.includes("$TEMP_DIR"))
.ensureTempDir()
.runAsTest("cd | cd | pwd - multiple cd's don't affect");
TestBuilder.command`pwd | cd / | pwd`
.stdout(s => {
const lines = s.trim().split("\n");
return lines.length === 2 && lines[0].includes("$TEMP_DIR") && lines[1].includes("$TEMP_DIR");
})
.ensureTempDir()
.runAsTest("pwd | cd | pwd - cd in middle doesn't affect");
});
describe("mixed builtin and subprocess pipelines", () => {
TestBuilder.command`echo hello | ${BUN} -e 'process.stdin.pipe(process.stdout)'`
.stdout("hello\n")
.runAsTest("echo builtin | subprocess");
TestBuilder.command`${BUN} -e 'console.log("hello")' | echo world`
.stdout("world\n")
.runAsTest("subprocess | echo builtin");
TestBuilder.command`true | ${BUN} -e 'process.exit(0)'`
.exitCode(0)
.stdout("")
.runAsTest("true builtin | subprocess exit 0");
TestBuilder.command`false | ${BUN} -e 'process.exit(0)'`
.exitCode(0)
.stdout("")
.runAsTest("false builtin | subprocess - subprocess exit code wins");
TestBuilder.command`echo one | true | ${BUN} -e 'process.stdin.pipe(process.stdout)' | false`
.exitCode(1)
.stdout("")
.runAsTest("mixed chain - last command determines exit");
});
describe("pipeline stack depth stress tests", () => {
// Test that the pipeline stack can handle various depths without overflow
TestBuilder.command`true | true | true | true | true | true | true | true | true | true`
.exitCode(0)
.runAsTest("10 true commands");
TestBuilder.command`echo 1 | echo 2 | echo 3 | echo 4 | echo 5 | echo 6 | echo 7 | echo 8 | echo 9 | echo 10`
.stdout("10\n")
.runAsTest("10 echo commands");
// Very long chain of mixed builtins
TestBuilder.command`true | false | true | false | true | false | true | false | true | false | true | false | true | false | true | false | true | false | true | false`
.exitCode(1)
.runAsTest("20 alternating true/false");
// Deep nesting with subshells containing pipelines
TestBuilder.command`(true | true) | (false | false) | (true | true)`
.exitCode(0)
.runAsTest("subshells with internal pipelines");
TestBuilder.command`(echo a | echo b) | (echo c | echo d) | (echo e | echo f)`
.stdout("f\n")
.runAsTest("subshells with echo pipelines");
});
describe("pipeline drain edge cases", () => {
// Test cases where pipelines need to be drained properly
TestBuilder.command`true | true && echo done`.stdout("done\n").runAsTest("pipeline followed by conditional");
TestBuilder.command`false | false || echo fallback`
.stdout("fallback\n")
.runAsTest("failing pipeline with fallback");
TestBuilder.command`true | true; echo after`.stdout("after\n").runAsTest("pipeline followed by semicolon");
TestBuilder.command`(true | true); echo after`.stdout("after\n").runAsTest("subshell pipeline followed by command");
// Multiple pipelines in sequence
TestBuilder.command`true | true; false | false; echo done`
.stdout("done\n")
.runAsTest("multiple pipelines in sequence");
TestBuilder.command`echo a | echo b && echo c | echo d && echo e | echo f`
.stdout("b\nd\nf\n")
.runAsTest("pipelines connected with &&");
TestBuilder.command`false | false || echo a | echo b || echo c | echo d`
.stdout("b\n")
.runAsTest("pipelines connected with ||");
});
describe("pipeline with conditionals", () => {
TestBuilder.command`if true | true; then echo yes; else echo no; fi`
.stdout("yes\n")
.runAsTest("pipeline in if condition");
TestBuilder.command`if false | false; then echo yes; else echo no; fi`
.stdout("no\n")
.runAsTest("failing pipeline in if condition");
TestBuilder.command`if echo test | true; then echo success; fi`
.stdout("success\n")
.runAsTest("echo | true in if condition");
TestBuilder.command`[[ -n "test" ]] | true && echo ok`.stdout("ok\n").runAsTest("test command in pipeline");
TestBuilder.command`true | [[ -n "test" ]] && echo ok`.stdout("ok\n").runAsTest("pipeline to test command");
});
describe("pipeline memory and cleanup", () => {
// Rapid pipeline creation and destruction
TestBuilder.command`true | true; true | true; true | true; true | true; true | true; echo done`
.stdout("done\n")
.runAsTest("rapid pipeline creation");
});
describe("empty and whitespace builtins", () => {
TestBuilder.command`echo "" | echo ""`.stdout("\n").runAsTest("empty echo pipes");
TestBuilder.command`echo " " | echo " "`.stdout(" \n").runAsTest("whitespace echo pipes");
// TestBuilder.command`printf "" | printf ""`.stdout("").runAsTest("empty printf pipes");
TestBuilder.command`echo | echo | echo`.stdout("\n").runAsTest("echo with no args chain");
});
describe("builtin error handling in pipelines", () => {
TestBuilder.command`cd /nonexistent 2>/dev/null | echo after`.stdout("after\n").runAsTest("cd error | echo");
TestBuilder.command`which nonexistent_command 2>/dev/null | echo after`
.stdout("after\n")
.runAsTest("which (not found) | echo");
TestBuilder.command`basename | echo after 2>/dev/null`
.stdout("after\n")
.stderr("usage: basename string\n")
.runAsTest("basename (no args) | echo");
});
describe("complex pipeline nesting", () => {
// Test deeply nested pipeline structures
TestBuilder.command`(true | (true | (true | true)))`.exitCode(0).runAsTest("deeply nested pipeline in subshells");
TestBuilder.command`((echo a | echo b) | (echo c | echo d)) | echo e`
.stdout("e\n")
.runAsTest("nested subshell pipelines");
TestBuilder.command`echo $(true | true | echo nested) | echo outer`
.stdout("outer\n")
.runAsTest("command substitution with pipeline");
TestBuilder.command`true | $(echo echo) result`.stdout("result\n").runAsTest("command substitution in pipeline");
// Combination of different nesting types
TestBuilder.command`(true | false) && (echo a | echo b) || (echo c | echo d)`
.stdout("d\n")
.runAsTest("conditional subshell pipelines");
});
describe("pipeline with variable assignments", () => {
TestBuilder.command`VAR=test true | echo $VAR`.stdout("\n").runAsTest("variable assignment with true | echo");
TestBuilder.command`export VAR=test | echo $VAR`.stdout("\n").runAsTest("export in pipeline");
});
describe("seq builtin pipelines", () => {
TestBuilder.command`seq 1 3 | echo done`.stdout("done\n").runAsTest("seq | echo - echo ignores stdin");
TestBuilder.command`seq 1 5 | true`.exitCode(0).stdout("").runAsTest("seq | true - output discarded");
TestBuilder.command`seq 1 2 | seq 3 4`.stdout("3\n4\n").runAsTest("seq | seq - second seq ignores stdin");
});
describe("yes builtin pipelines", () => {
TestBuilder.command`yes | head -n 1`.stdout("y\n").runAsTest("yes | head -n 1");
TestBuilder.command`yes no | head -n 2`.stdout("no\nno\n").runAsTest("yes with arg | head");
TestBuilder.command`yes | true`.exitCode(0).stdout("").runAsTest("yes | true - yes terminates on EPIPE");
TestBuilder.command`yes | false`.exitCode(1).stdout("").runAsTest("yes | false");
});
});