mirror of
https://github.com/oven-sh/bun
synced 2026-02-10 02:48:50 +00:00
2360 lines
82 KiB
TypeScript
2360 lines
82 KiB
TypeScript
/**
|
|
* Portions of these tests are derived from the [deno_task_shell](https://github.com/denoland/deno_task_shell/) tests, which are developed and maintained by the Deno authors.
|
|
* Copyright 2018-2023 the Deno authors.
|
|
*
|
|
* This code is licensed under the MIT License: https://opensource.org/licenses/MIT
|
|
*/
|
|
import { $ } from "bun";
|
|
import { afterAll, beforeAll, describe, expect, it, test } from "bun:test";
|
|
import { mkdir, rm, stat } from "fs/promises";
|
|
import { bunExe, isWindows, runWithErrorPromise, tempDirWithFiles, tmpdirSync } from "harness";
|
|
import { join, sep } from "path";
|
|
import { createTestBuilder, sortedShellOutput } from "./util";
|
|
const TestBuilder = createTestBuilder(import.meta.path);
|
|
|
|
export const bunEnv: NodeJS.ProcessEnv = {
|
|
...process.env,
|
|
GITHUB_ACTIONS: "false",
|
|
BUN_DEBUG_QUIET_LOGS: "1",
|
|
NO_COLOR: "1",
|
|
FORCE_COLOR: undefined,
|
|
TZ: "Etc/UTC",
|
|
CI: "1",
|
|
BUN_RUNTIME_TRANSPILER_CACHE_PATH: "0",
|
|
BUN_FEATURE_FLAG_INTERNAL_FOR_TESTING: "1",
|
|
BUN_GARBAGE_COLLECTOR_LEVEL: process.env.BUN_GARBAGE_COLLECTOR_LEVEL || "0",
|
|
// windows doesn't set this, but we do to match posix compatibility
|
|
PWD: (process.env.PWD || process.cwd()).replaceAll("\\", "/"),
|
|
};
|
|
|
|
$.env(bunEnv);
|
|
$.cwd(process.cwd().replaceAll("\\", "/"));
|
|
$.nothrow();
|
|
|
|
let temp_dir: string;
|
|
const temp_files = ["foo.txt", "lmao.ts"];
|
|
beforeAll(async () => {
|
|
$.nothrow();
|
|
temp_dir = tmpdirSync();
|
|
await mkdir(temp_dir, { recursive: true });
|
|
|
|
for (const file of temp_files) {
|
|
const writer = Bun.file(join(temp_dir, file)).writer();
|
|
writer.write("foo");
|
|
writer.end();
|
|
}
|
|
});
|
|
|
|
afterAll(async () => {
|
|
await rm(temp_dir, { force: true, recursive: true });
|
|
});
|
|
|
|
const BUN = bunExe();
|
|
|
|
describe("bunshell", () => {
|
|
describe("exit codes", async () => {
|
|
const failing_cmds = [
|
|
process.platform === "win32" ? "cat ldkfjsldf" : null,
|
|
"touch -alskdjfakjfhasjfh",
|
|
"mkdir",
|
|
"export",
|
|
"cd lskfjlsdkjf",
|
|
process.platform !== "win32" ? "echo hi > /dev/full" : null,
|
|
"pwd sldfkj sfks jdflks flksd f",
|
|
"which",
|
|
"rm lskdjfskldjfksdjflkjsldfj",
|
|
"mv lskdjflskdjf lskdjflskjdlf",
|
|
"ls lksdjflksdjf",
|
|
"exit sldkfj sdjf ls f",
|
|
// "true",
|
|
// "false",
|
|
// "yes",
|
|
// "seq",
|
|
"dirname",
|
|
"basename",
|
|
"cp ksdjflksjdfks lkjsdflksjdfl",
|
|
];
|
|
|
|
failing_cmds.forEach(cmdstr =>
|
|
!!cmdstr
|
|
? TestBuilder.command`${{ raw: cmdstr }}`
|
|
.exitCode(c => c !== 0)
|
|
.stdout(() => {})
|
|
.stderr(() => {})
|
|
.runAsTest(cmdstr)
|
|
: "",
|
|
);
|
|
});
|
|
|
|
describe("concurrency", () => {
|
|
test("writing to stdout", async () => {
|
|
await Promise.all([
|
|
TestBuilder.command`echo 1`.stdout("1\n").run(),
|
|
TestBuilder.command`echo 2`.stdout("2\n").run(),
|
|
TestBuilder.command`echo 3`.stdout("3\n").run(),
|
|
]);
|
|
});
|
|
});
|
|
describe("js_obj_test", async () => {
|
|
function runTest(name: string, builder: TestBuilder) {
|
|
test(`js_obj_test_name_${name}`, async () => {
|
|
await builder.run();
|
|
});
|
|
}
|
|
|
|
runTest("number", TestBuilder.command`echo ${1}`.stdout("1\n"));
|
|
runTest("String", TestBuilder.command`echo ${new String("1")}`.stdout("1\n"));
|
|
runTest("bool", TestBuilder.command`echo ${true}`.stdout("true\n"));
|
|
runTest("null", TestBuilder.command`echo ${null}`.stdout("null\n"));
|
|
runTest("undefined", TestBuilder.command`echo ${undefined}`.stdout("undefined\n"));
|
|
runTest("Date", TestBuilder.command`echo hello ${new Date()}`.stdout(`hello ${new Date().toString()}\n`));
|
|
runTest("BigInt", TestBuilder.command`echo ${BigInt((2 ^ 52) - 1)}`.stdout(`${BigInt((2 ^ 52) - 1)}\n`));
|
|
runTest("Array", TestBuilder.command`echo ${[1, 2, 3]}`.stdout(`1 2 3\n`));
|
|
});
|
|
|
|
describe("escape", async () => {
|
|
function escapeTest(strToEscape: string, expected: string = strToEscape) {
|
|
test(strToEscape, async () => {
|
|
const { stdout } = await $`echo ${strToEscape}`;
|
|
expect(stdout.toString()).toEqual(`${strToEscape}\n`);
|
|
expect($.escape(strToEscape)).toEqual(expected);
|
|
});
|
|
}
|
|
|
|
escapeTest("1 2 3", '"1 2 3"');
|
|
escapeTest("nice\nlmao", '"nice\nlmao"');
|
|
escapeTest(`lol $NICE`, `"lol \\$NICE"`);
|
|
escapeTest(
|
|
`"hello" "lol" "nice"lkasjf;jdfla<>SKDJFLKSF`,
|
|
`"\\"hello\\" \\"lol\\" \\"nice\\"lkasjf;jdfla<>SKDJFLKSF"`,
|
|
);
|
|
escapeTest("✔", "✔");
|
|
escapeTest("lmao=✔", '"lmao=✔"');
|
|
escapeTest("元気かい、兄弟", "元気かい、兄弟");
|
|
escapeTest("d元気かい、兄弟", "d元気かい、兄弟");
|
|
|
|
describe("wrapped in quotes", async () => {
|
|
const url = "http://www.example.com?candy_name=M&M";
|
|
TestBuilder.command`echo url="${url}"`.stdout(`url=${url}\n`).runAsTest("double quotes");
|
|
TestBuilder.command`echo url='${url}'`.stdout(`url=${url}\n`).runAsTest("single quotes");
|
|
TestBuilder.command`echo url=${url}`.stdout(`url=${url}\n`).runAsTest("no quotes");
|
|
});
|
|
|
|
describe("escape var", async () => {
|
|
const shellvar = "$FOO";
|
|
TestBuilder.command`FOO=bar && echo "${shellvar}"`.stdout(`$FOO\n`).runAsTest("double quotes");
|
|
TestBuilder.command`FOO=bar && echo '${shellvar}'`.stdout(`$FOO\n`).runAsTest("single quotes");
|
|
TestBuilder.command`FOO=bar && echo ${shellvar}`.stdout(`$FOO\n`).runAsTest("no quotes");
|
|
});
|
|
|
|
test("can't escape a js string/obj ref", async () => {
|
|
const shellvar = "$FOO";
|
|
await TestBuilder.command`FOO=bar && echo \\${shellvar}`.stdout(`\\$FOO\n`).run();
|
|
const buf = new Uint8Array(1);
|
|
|
|
expect(async () => {
|
|
await TestBuilder.command`echo hi > \\${buf}`.error("Redirection with no file").run();
|
|
});
|
|
});
|
|
|
|
test("in command position", async () => {
|
|
const x = "echo hi";
|
|
await TestBuilder.command`${x}`.exitCode(1).stderr("bun: command not found: echo hi\n").run();
|
|
});
|
|
|
|
test("arrays", async () => {
|
|
const x = ["echo", "hi"];
|
|
await TestBuilder.command`${x}`.stdout("hi\n").run();
|
|
});
|
|
});
|
|
|
|
describe("quiet", async () => {
|
|
test("basic", async () => {
|
|
// Check its buffered
|
|
{
|
|
const { stdout, stderr } = await $`BUN_DEBUG_QUIET_LOGS=1 ${BUN} -e "console.log('hi'); console.error('lol')"`;
|
|
expect(stdout.toString()).toEqual("hi\n");
|
|
expect(stderr.toString()).toEqual("lol\n");
|
|
}
|
|
|
|
// Check it doesn't write to stdout
|
|
const { stdout, stderr } = Bun.spawnSync(
|
|
[
|
|
BUN,
|
|
"-e",
|
|
"await Bun.$`BUN_DEBUG_QUIET_LOGS=1 ${process.argv0} -e \"console.log('hi'); console.error('lol')\"`.quiet()",
|
|
],
|
|
{
|
|
env: { BUN_DEBUG_QUIET_LOGS: "1" },
|
|
},
|
|
);
|
|
expect(stdout.toString()).toBe("");
|
|
expect(stderr.toString()).toBe("");
|
|
});
|
|
|
|
test("cmd subst", async () => {
|
|
await TestBuilder.command`echo $(echo hi)`.quiet().stdout("hi\n").run();
|
|
});
|
|
});
|
|
|
|
test("failing stmt edgecase", async () => {
|
|
const { stdout } =
|
|
await $`mkdir foo; touch ./foo/lol ./foo/nice ./foo/lmao; mkdir foo/bar; touch ./foo/bar/great; touch ./foo/bar/wow; ls foo -R`.cwd(
|
|
temp_dir,
|
|
);
|
|
});
|
|
|
|
// test("invalid js obj", async () => {
|
|
// const lol = {
|
|
// hi: "lmao",
|
|
// };
|
|
// await TestBuilder.command`echo foo > ${lol}`.error("Invalid JS object used in shell: [object Object]").run();
|
|
// const r = new RegExp("hi");
|
|
// await TestBuilder.command`echo foo > ${r}`.error("Invalid JS object used in shell: /hi/").run();
|
|
// });
|
|
|
|
test("empty_input", async () => {
|
|
await TestBuilder.command``.run();
|
|
await TestBuilder.command` `.run();
|
|
await TestBuilder.command`
|
|
`.run();
|
|
await TestBuilder.command`
|
|
`.run();
|
|
await TestBuilder.command`
|
|
`.run();
|
|
});
|
|
|
|
describe("echo+cmdsubst edgecases", async () => {
|
|
async function doTest(cmd: string, expected: string) {
|
|
test(cmd, async () => {
|
|
const { stdout } = await $`${{ raw: cmd }}`;
|
|
expect(stdout.toString()).toEqual(expected);
|
|
});
|
|
}
|
|
|
|
// funny/crazy edgecases thanks to @paperclover and @Electroid
|
|
doTest(`echo "$(echo 1; echo 2)"`, "1\n2\n");
|
|
doTest(`echo "$(echo "1" ; echo "2")"`, "1\n2\n");
|
|
doTest(`echo $(echo 1; echo 2)`, "1 2\n");
|
|
|
|
// Issue: #8982
|
|
// https://github.com/oven-sh/bun/issues/8982
|
|
describe("word splitting", async () => {
|
|
TestBuilder.command`echo $(echo id)/$(echo region)`.stdout("id/region\n").runAsTest("concatenated cmd substs");
|
|
TestBuilder.command`echo $(echo hi id)/$(echo region)`
|
|
.stdout("hi id/region\n")
|
|
.runAsTest("cmd subst with whitespace gets split");
|
|
|
|
// Make sure its one whole argument
|
|
TestBuilder.command`echo {"console.log(JSON.stringify(process.argv.slice(2)))"} > temp_script.ts; BUN_DEBUG_QUIET_LOGS=1 ${BUN} run temp_script.ts $(echo id)/$(echo region)`
|
|
.stdout('["id/region"]\n')
|
|
.ensureTempDir()
|
|
.runAsTest("make sure its one whole argument");
|
|
|
|
// Make sure its two separate arguments
|
|
TestBuilder.command`echo {"console.log(JSON.stringify(process.argv.slice(2)))"} > temp_script.ts; BUN_DEBUG_QUIET_LOGS=1 ${BUN} run temp_script.ts $(echo hi id)/$(echo region)`
|
|
.stdout('["hi","id/region"]\n')
|
|
.ensureTempDir()
|
|
.runAsTest("make sure its two separate arguments");
|
|
});
|
|
});
|
|
|
|
describe("unicode", () => {
|
|
test("basic", async () => {
|
|
const whatsupbro = "元気かい、兄弟";
|
|
const { stdout } = await $`echo ${whatsupbro}`;
|
|
|
|
expect(stdout.toString("utf8")).toEqual(whatsupbro + "\n");
|
|
});
|
|
|
|
test("escape unicode", async () => {
|
|
const { stdout } = await $`echo \\弟\\気`;
|
|
// TODO: Uncomment and replace after unicode in template tags is supported
|
|
// expect(stdout.toString("utf8")).toEqual(`\弟\気\n`);
|
|
// Set this here for now, because unicode in template tags while using .raw is broken, but should be fixed
|
|
expect(stdout.toString("utf8")).toEqual("\\u5F1F\\u6C17\n");
|
|
});
|
|
|
|
/**
|
|
* Only A-Z, a-z, 0-9, and _ are allowed in variable names
|
|
*
|
|
* Using unicode in var name will interpret the assignment as a command.
|
|
*/
|
|
//
|
|
test("varname fails", async () => {
|
|
const whatsupbro = "元気かい、兄弟";
|
|
await TestBuilder.command`${whatsupbro}=NICE; echo $${whatsupbro}`
|
|
.stdout("$元気かい、兄弟\n")
|
|
.stderr("bun: command not found: 元気かい、兄弟=NICE\n")
|
|
.run();
|
|
});
|
|
|
|
test("var value", async () => {
|
|
const error = await runWithErrorPromise(async () => {
|
|
const whatsupbro = "元気かい、兄弟";
|
|
const { stdout } = await $`FOO=${whatsupbro}; echo $FOO`;
|
|
expect(stdout.toString("utf-8")).toEqual(whatsupbro + "\n");
|
|
});
|
|
expect(error).toBeUndefined();
|
|
});
|
|
|
|
test("in compound word", async () => {
|
|
const whatsupbro = "元気かい、兄弟";
|
|
const holymoly = "ホーリーモーリー";
|
|
const { stdout } = await $`echo "${whatsupbro}&&nice"${holymoly}`;
|
|
|
|
expect(stdout.toString("utf-8")).toEqual(`${whatsupbro}&&nice${holymoly}\n`);
|
|
});
|
|
|
|
test("cmd subst", async () => {
|
|
const haha = "ハハ";
|
|
const { stdout } = await $`echo $(echo ${haha})`;
|
|
|
|
expect(stdout.toString("utf-8")).toEqual(`${haha}\n`);
|
|
});
|
|
|
|
// #9823
|
|
TestBuilder.command`AUTH_COOKIE_SECUREALKJAKJDLASJDKLSAJD=false; echo $AUTH_COOKIE_SECUREALKJAKJDLASJDKLSAJD`
|
|
.stdout("false\n")
|
|
.runAsTest("long varname");
|
|
|
|
// test("invalid lone surrogate fails", async () => {
|
|
// const err = await runWithErrorPromise(async () => {
|
|
// const loneSurrogate = randomLoneSurrogate();
|
|
// const buffer = new Uint8Array(8192);
|
|
// const result = await $`echo ${loneSurrogate} > ${buffer}`;
|
|
// });
|
|
// console.log("ERR", err)
|
|
// expect(err?.message).toEqual("Shell script string contains invalid UTF-16");
|
|
// });
|
|
|
|
// test("invalid surrogate pair fails", async () => {
|
|
// const err = await runWithErrorPromise(async () => {
|
|
// const loneSurrogate = randomInvalidSurrogatePair();
|
|
// const buffer = new Uint8Array(8192);
|
|
// const result = $`echo ${loneSurrogate} > ${buffer}`;
|
|
// });
|
|
// expect(err?.message).toEqual("Shell script string contains invalid UTF-16");
|
|
// });
|
|
});
|
|
|
|
describe("latin-1", async () => {
|
|
describe("basic", async () => {
|
|
TestBuilder.command`echo ${"à"}`.stdout("à\n").runAsTest("lone latin-1 character");
|
|
TestBuilder.command`echo ${" à"}`.stdout(" à\n").runAsTest("latin-1 character preceded by space");
|
|
TestBuilder.command`echo ${"à¿"}`.stdout("à¿\n").runAsTest("multiple latin-1 characters");
|
|
TestBuilder.command`echo ${'"à¿"'}`.stdout('"à¿"\n').runAsTest("latin-1 characters in quotes");
|
|
});
|
|
});
|
|
|
|
test("redirect Uint8Array", async () => {
|
|
const buffer = new Uint8Array(1 << 20);
|
|
const result = await $`cat ${import.meta.path} > ${buffer}`;
|
|
|
|
const sentinel = sentinelByte(buffer);
|
|
const thisFile = Bun.file(import.meta.path);
|
|
|
|
expect(new TextDecoder().decode(buffer.slice(0, sentinel))).toEqual(await thisFile.text());
|
|
});
|
|
|
|
test("redirect Buffer", async () => {
|
|
const buffer = Buffer.alloc(1 << 20);
|
|
const result = await $`cat ${import.meta.path} > ${buffer}`;
|
|
|
|
const thisFile = Bun.file(import.meta.path);
|
|
|
|
expect(new TextDecoder().decode(buffer.slice(0, sentinelByte(buffer)))).toEqual(await thisFile.text());
|
|
});
|
|
|
|
test("redirect Bun.File", async () => {
|
|
const filepath = join(temp_dir, "lmao.txt");
|
|
const file = Bun.file(filepath);
|
|
const thisFileText = await Bun.file(import.meta.path).text();
|
|
const result = await $`cat ${import.meta.path} > ${file}`;
|
|
|
|
expect(await file.text()).toEqual(thisFileText);
|
|
});
|
|
|
|
// TODO This sometimes fails
|
|
test("redirect stderr", async () => {
|
|
const buffer = Buffer.alloc(128, 0);
|
|
const code = /* ts */ `
|
|
for (let i = 0; i < 10; i++) {
|
|
console.error('LMAO')
|
|
}
|
|
`;
|
|
|
|
await $`${BUN} -e ${code} 2> ${buffer}`.env(bunEnv);
|
|
|
|
console.log(buffer);
|
|
expect(new TextDecoder().decode(buffer.slice(0, sentinelByte(buffer)))).toEqual(
|
|
`LMAO\nLMAO\nLMAO\nLMAO\nLMAO\nLMAO\nLMAO\nLMAO\nLMAO\nLMAO\n`,
|
|
);
|
|
});
|
|
|
|
test("pipeline", async () => {
|
|
const { stdout } = await $`echo "LMAO" | cat`;
|
|
|
|
expect(stdout.toString()).toEqual("LMAO\n");
|
|
});
|
|
|
|
describe("operators no spaces", async () => {
|
|
TestBuilder.command`echo LMAO|cat`.stdout("LMAO\n").runAsTest("pipeline");
|
|
TestBuilder.command`echo foo&&echo hi`.stdout("foo\nhi\n").runAsTest("&&");
|
|
TestBuilder.command`echo foo||echo hi`.stdout("foo\n").runAsTest("||");
|
|
TestBuilder.command`echo foo>hi.txt`.ensureTempDir().fileEquals("hi.txt", "foo\n").runAsTest("||");
|
|
TestBuilder.command`echo hifriends#lol`.stdout("hifriends#lol\n").runAsTest("#");
|
|
});
|
|
|
|
test("cmd subst", async () => {
|
|
const haha = "noice";
|
|
const { stdout } = await $`echo $(echo noice)`;
|
|
expect(stdout.toString()).toEqual(`noice\n`);
|
|
});
|
|
|
|
describe("empty_expansion", () => {
|
|
TestBuilder.command`$(exit 0) && echo hi`.stdout("hi\n").runAsTest("empty command subst");
|
|
TestBuilder.command`$(exit 1) && echo hi`.exitCode(1).runAsTest("empty command subst 2");
|
|
TestBuilder.command`FOO="" $FOO`.runAsTest("empty var");
|
|
});
|
|
|
|
describe("tilde_expansion", () => {
|
|
describe("with paths", async () => {
|
|
TestBuilder.command`echo ~/Documents`.stdout(`${process.env.HOME}/Documents\n`).runAsTest("normal");
|
|
TestBuilder.command`echo ~/Do"cu"me"nts"`.stdout(`${process.env.HOME}/Documents\n`).runAsTest("compound word");
|
|
TestBuilder.command`echo ~/LOL hi hello`.stdout(`${process.env.HOME}/LOL hi hello\n`).runAsTest("multiple words");
|
|
});
|
|
|
|
describe("normal", async () => {
|
|
TestBuilder.command`echo ~`.stdout(`${process.env.HOME}\n`).runAsTest("lone tilde");
|
|
TestBuilder.command`echo ~~`.stdout(`~~\n`).runAsTest("double tilde");
|
|
TestBuilder.command`echo ~ hi hello`.stdout(`${process.env.HOME} hi hello\n`).runAsTest("multiple words");
|
|
});
|
|
|
|
TestBuilder.command`HOME="" USERPROFILE="" && echo ~ && echo ~/Documents`
|
|
.stdout("\n/Documents\n")
|
|
.runAsTest("empty $HOME or $USERPROFILE");
|
|
|
|
describe("modified $HOME or $USERPROFILE", async () => {
|
|
TestBuilder.command`HOME=lmao USERPROFILE=lmao && echo ~`.stdout("lmao\n").runAsTest("1");
|
|
|
|
TestBuilder.command`HOME=lmao USERPROFILE=lmao && echo ~ && echo ~/Documents`
|
|
.stdout("lmao\nlmao/Documents\n")
|
|
.runAsTest("2");
|
|
});
|
|
});
|
|
|
|
// Ported from GNU bash "quote.tests"
|
|
// https://github.com/bminor/bash/blob/f3b6bd19457e260b65d11f2712ec3da56cef463f/tests/quote.tests#L1
|
|
// Some backtick tests are skipped, because of insane behavior:
|
|
// For some reason, even though $(...) and `...` are suppoed to be equivalent,
|
|
// doing:
|
|
// echo "`echo 'foo\
|
|
// bar'`"
|
|
//
|
|
// gives:
|
|
// foobar
|
|
//
|
|
// While doing the same, but with $(...):
|
|
// echo "$(echo 'foo\
|
|
// bar')"
|
|
//
|
|
// gives:
|
|
// foo\
|
|
// bar
|
|
//
|
|
// I'm not sure why, this isn't documented behavior, so I'm choosing to ignore it.
|
|
describe("gnu_quote", () => {
|
|
// An unfortunate consequence of our use of String.raw and tagged template
|
|
// functions for the shell make it so that we have to use { raw: string } to do
|
|
// backtick command substitution
|
|
const BACKTICK = { raw: "`" };
|
|
|
|
// Single Quote
|
|
TestBuilder.command`
|
|
echo 'foo
|
|
bar'
|
|
echo 'foo
|
|
bar'
|
|
echo 'foo\
|
|
bar'
|
|
`
|
|
.stdout("foo\nbar\nfoo\nbar\nfoo\\\nbar\n")
|
|
.runAsTest("Single Quote");
|
|
|
|
TestBuilder.command`
|
|
echo "foo
|
|
bar"
|
|
echo "foo
|
|
bar"
|
|
echo "foo\
|
|
bar"
|
|
`
|
|
.stdout("foo\nbar\nfoo\nbar\nfoobar\n")
|
|
.runAsTest("Double Quote");
|
|
|
|
TestBuilder.command`
|
|
echo ${BACKTICK}echo 'foo
|
|
bar'${BACKTICK}
|
|
echo ${BACKTICK}echo 'foo
|
|
bar'${BACKTICK}
|
|
echo ${BACKTICK}echo 'foo\
|
|
bar'${BACKTICK}
|
|
`
|
|
.stdout(
|
|
`foo bar
|
|
foo bar
|
|
foobar\n`,
|
|
)
|
|
.todo("insane backtick behavior")
|
|
.runAsTest("Backslash Single Quote");
|
|
|
|
TestBuilder.command`
|
|
echo "${BACKTICK}echo 'foo
|
|
bar'${BACKTICK}"
|
|
echo "${BACKTICK}echo 'foo
|
|
bar'${BACKTICK}"
|
|
echo "${BACKTICK}echo 'foo\
|
|
bar'${BACKTICK}"
|
|
`
|
|
.stdout(
|
|
`foo
|
|
bar
|
|
foo
|
|
bar
|
|
foobar\n`,
|
|
)
|
|
.todo("insane backtick behavior")
|
|
.runAsTest("Double Quote Backslash Single Quote");
|
|
|
|
TestBuilder.command`
|
|
echo $(echo 'foo
|
|
bar')
|
|
echo $(echo 'foo
|
|
bar')
|
|
echo $(echo 'foo\
|
|
bar')
|
|
`
|
|
.stdout(
|
|
`foo bar
|
|
foo bar
|
|
foo\\ bar\n`,
|
|
)
|
|
.runAsTest("Dollar Paren Single Quote");
|
|
|
|
TestBuilder.command`
|
|
echo "$(echo 'foo
|
|
bar')"
|
|
echo "$(echo 'foo
|
|
bar')"
|
|
echo "$(echo 'foo\
|
|
bar')"
|
|
`
|
|
.stdout(
|
|
`foo
|
|
bar
|
|
foo
|
|
bar
|
|
foo\\
|
|
bar\n`,
|
|
)
|
|
.runAsTest("Dollar Paren Double Quote");
|
|
|
|
TestBuilder.command`
|
|
echo "$(echo 'foo
|
|
bar')"
|
|
echo "$(echo 'foo
|
|
bar')"
|
|
echo "$(echo 'foo\
|
|
bar')"
|
|
`
|
|
.stdout(
|
|
`foo
|
|
bar
|
|
foo
|
|
bar
|
|
foo\\
|
|
bar\n`,
|
|
)
|
|
.runAsTest("Double Quote Dollar Paren Single Quote");
|
|
});
|
|
|
|
describe("escaped_newline", () => {
|
|
const printArgs = /* ts */ `console.log(JSON.stringify(process.argv))`;
|
|
|
|
TestBuilder.command/* sh */ `${BUN} run ./code.ts hi hello \
|
|
on a newline!
|
|
`
|
|
.ensureTempDir()
|
|
.file("code.ts", printArgs)
|
|
.stdout(out => expect(JSON.parse(out).slice(2)).toEqual(["hi", "hello", "on", "a", "newline!"]))
|
|
.runAsTest("single");
|
|
|
|
TestBuilder.command/* sh */ `${BUN} run ./code.ts hi hello \
|
|
on a newline! \
|
|
and \
|
|
a few \
|
|
others!
|
|
`
|
|
.ensureTempDir()
|
|
.file("code.ts", printArgs)
|
|
.stdout(out =>
|
|
expect(JSON.parse(out).slice(2)).toEqual(["hi", "hello", "on", "a", "newline!", "and", "a", "few", "others!"]),
|
|
)
|
|
.runAsTest("many");
|
|
|
|
TestBuilder.command/* sh */ `${BUN} run ./code.ts hi hello \
|
|
on a newline! \
|
|
ooga"
|
|
booga"
|
|
`
|
|
.ensureTempDir()
|
|
.file("code.ts", printArgs)
|
|
.stdout(out => expect(JSON.parse(out).slice(2)).toEqual(["hi", "hello", "on", "a", "newline!", "ooga\nbooga"]))
|
|
.runAsTest("quotes");
|
|
});
|
|
|
|
describe("glob expansion", () => {
|
|
// Issue #8403: https://github.com/oven-sh/bun/issues/8403
|
|
TestBuilder.command`ls *.sdfljsfsdf`
|
|
.exitCode(1)
|
|
.stderr("bun: no matches found: *.sdfljsfsdf\n")
|
|
.runAsTest("No matches should fail");
|
|
|
|
TestBuilder.command`FOO=*.lolwut; echo $FOO`
|
|
.stdout("*.lolwut\n")
|
|
.runAsTest("No matches in assignment position should print out pattern");
|
|
|
|
TestBuilder.command`FOO=hi*; echo $FOO`
|
|
.ensureTempDir()
|
|
.stdout("hi*\n")
|
|
.runAsTest("Trailing asterisk with no matches");
|
|
|
|
TestBuilder.command`touch hihello; touch hifriends; FOO=hi*; echo $FOO`
|
|
.ensureTempDir()
|
|
.stdout(s => expect(s).toBeOneOf(["hihello hifriends\n", "hifriends hihello\n"]))
|
|
.runAsTest("Trailing asterisk with matches, inline");
|
|
|
|
TestBuilder.command`ls *.js`
|
|
// Calling `ensureTempDir()` changes the cwd here
|
|
.ensureTempDir()
|
|
.file("foo.js", "foo")
|
|
.file("bar.js", "bar")
|
|
.stdout(out => {
|
|
expect(sortedShellOutput(out)).toEqual(sortedShellOutput("foo.js\nbar.js\n"));
|
|
})
|
|
.runAsTest("Should work with a different cwd");
|
|
});
|
|
|
|
describe("brace expansion", () => {
|
|
function doTest(pattern: string, expected: string) {
|
|
test(pattern, async () => {
|
|
const { stdout } = await $`echo ${{ raw: pattern }} `;
|
|
expect(stdout.toString()).toEqual(`${expected}\n`);
|
|
});
|
|
}
|
|
|
|
describe("concatenated", () => {
|
|
doTest("{a,b,c}{d,e,f}", "ad ae af bd be bf cd ce cf");
|
|
});
|
|
|
|
describe("nested", () => {
|
|
doTest("{a,b,{c,d}}", "a b c d");
|
|
doTest("{a,b,{c,d,{e,f}}}", "a b c d e f");
|
|
doTest("{a,{b,{c,d}}}", "a b c d");
|
|
doTest("{a,b,HI{c,e,LMAO{d,f}Q}}", "a b HIc HIe HILMAOdQ HILMAOfQ");
|
|
doTest("{a,{b,c}}{1,2,3}", "a1 a2 a3 b1 b2 b3 c1 c2 c3");
|
|
doTest("{a,{b,c}HEY,d}{1,2,3}", "a1 a2 a3 bHEY1 bHEY2 bHEY3 cHEY1 cHEY2 cHEY3 d1 d2 d3");
|
|
doTest("{a,{b,c},d}{1,2,3}", "a1 a2 a3 b1 b2 b3 c1 c2 c3 d1 d2 d3");
|
|
|
|
doTest(
|
|
"{a,b,HI{c,e,LMAO{d,f}Q}}{1,2,{3,4},5}",
|
|
"a1 a2 a3 a4 a5 b1 b2 b3 b4 b5 HIc1 HIc2 HIc3 HIc4 HIc5 HIe1 HIe2 HIe3 HIe4 HIe5 HILMAOdQ1 HILMAOdQ2 HILMAOdQ3 HILMAOdQ4 HILMAOdQ5 HILMAOfQ1 HILMAOfQ2 HILMAOfQ3 HILMAOfQ4 HILMAOfQ5",
|
|
);
|
|
});
|
|
|
|
test("command", async () => {
|
|
const { stdout } = await $`{echo,a,b,c} {d,e,f}`;
|
|
expect(stdout.toString()).toEqual("a b c d e f\n");
|
|
});
|
|
});
|
|
|
|
describe("variables", () => {
|
|
test("cmd_local_var", async () => {
|
|
const { stdout } = await $`FOO=bar BOOP=1 ${BUN} -e "console.log(JSON.stringify(process.env))"`;
|
|
const str = stdout.toString();
|
|
expect(JSON.parse(str)).toEqual({
|
|
...bunEnv,
|
|
FOO: "bar",
|
|
BOOP: "1",
|
|
});
|
|
});
|
|
|
|
test("expand shell var", async () => {
|
|
const { stdout } = await $`FOO=bar BAR=baz; echo $FOO $BAR`;
|
|
const str = stdout.toString();
|
|
|
|
expect(str).toEqual("bar baz\n");
|
|
});
|
|
|
|
test("shell var", async () => {
|
|
const { stdout } = await $`FOO=bar BAR=baz && BAZ=1 ${BUN} -e "console.log(JSON.stringify(process.env))"`;
|
|
const str = stdout.toString();
|
|
|
|
const procEnv = JSON.parse(str);
|
|
expect(procEnv.FOO).toBeUndefined();
|
|
expect(procEnv.BAR).toBeUndefined();
|
|
expect(procEnv).toEqual({ ...bunEnv, BAZ: "1" });
|
|
});
|
|
|
|
test("export var", async () => {
|
|
const buffer = Buffer.alloc(1 << 20);
|
|
const buffer2 = Buffer.alloc(1 << 20);
|
|
await $`export FOO=bar && BAZ=1 ${BUN} -e "console.log(JSON.stringify(process.env))" > ${buffer} && BUN_TEST_VAR=1 ${BUN} -e "console.log(JSON.stringify(process.env))" > ${buffer2}`;
|
|
|
|
const str1 = stringifyBuffer(buffer);
|
|
const str2 = stringifyBuffer(buffer2);
|
|
|
|
console.log("Str1", str1);
|
|
|
|
let procEnv = JSON.parse(str1);
|
|
expect(procEnv).toEqual({ ...bunEnv, BAZ: "1", FOO: "bar" });
|
|
procEnv = JSON.parse(str2);
|
|
expect(procEnv).toEqual({
|
|
...bunEnv,
|
|
BAZ: "1",
|
|
FOO: "bar",
|
|
BUN_TEST_VAR: "1",
|
|
});
|
|
});
|
|
|
|
test("syntax edgecase", async () => {
|
|
const buffer = new Uint8Array(1 << 20);
|
|
const shellProc = await $`FOO=bar BUN_TEST_VAR=1 ${BUN} -e "console.log(JSON.stringify(process.env))"> ${buffer}`;
|
|
|
|
const str = stringifyBuffer(buffer);
|
|
|
|
const procEnv = JSON.parse(str);
|
|
|
|
expect(procEnv).toEqual({ ...bunEnv, BUN_TEST_VAR: "1", FOO: "bar" });
|
|
});
|
|
});
|
|
|
|
describe("cd & pwd", () => {
|
|
test("cd", async () => {
|
|
const { stdout } = await $`cd ${temp_dir} && ls`;
|
|
const str = stdout.toString();
|
|
expect(
|
|
str
|
|
.split("\n")
|
|
.filter(s => s.length > 0)
|
|
.sort(),
|
|
).toEqual(temp_files.sort());
|
|
});
|
|
|
|
test("cd -", async () => {
|
|
const { stdout } = await $`cd ${temp_dir} && pwd && cd - && pwd`;
|
|
expect(stdout.toString()).toEqual(`${temp_dir}\n${process.cwd().replaceAll("\\", "/")}\n`);
|
|
});
|
|
});
|
|
|
|
test("which", async () => {
|
|
const bogus = "akdfjlsdjflks";
|
|
const { stdout } = await $`which ${BUN} ${bogus}`;
|
|
const bunWhich = Bun.which(BUN);
|
|
expect(stdout.toString()).toEqual(`${bunWhich}\n${bogus} not found\n`);
|
|
});
|
|
|
|
describe("rm", () => {
|
|
let temp_dir: string;
|
|
const files = {
|
|
foo: "bar",
|
|
bar: "baz",
|
|
dir: {
|
|
some: "more",
|
|
files: "here",
|
|
},
|
|
};
|
|
beforeAll(() => {
|
|
temp_dir = tempDirWithFiles("temp-rm", files);
|
|
});
|
|
|
|
test("error without recursive option", async () => {
|
|
const { stderr } = await $`rm -v ${temp_dir}`;
|
|
expect(stderr.toString()).toEqual(`rm: ${temp_dir}: Is a directory\n`);
|
|
});
|
|
|
|
test("recursive", async () => {
|
|
const { stdout } = await $`rm -vrf ${temp_dir}`;
|
|
const str = stdout.toString();
|
|
expect(
|
|
str
|
|
.split("\n")
|
|
.filter(s => s.length !== 0)
|
|
.sort(),
|
|
).toEqual(
|
|
`${join(temp_dir, "foo")}
|
|
${join(temp_dir, "dir", "files")}
|
|
${join(temp_dir, "dir", "some")}
|
|
${join(temp_dir, "dir")}
|
|
${join(temp_dir, "bar")}
|
|
${temp_dir}`
|
|
.split("\n")
|
|
.sort(),
|
|
);
|
|
});
|
|
});
|
|
|
|
/**
|
|
*
|
|
*/
|
|
describe("escaping", () => {
|
|
// Testing characters that need special handling when not quoted or in different contexts
|
|
TestBuilder.command`echo ${"$"}`.stdout("$\n").runAsTest("dollar");
|
|
TestBuilder.command`echo ${">"}`.stdout(">\n").runAsTest("right_arrow");
|
|
TestBuilder.command`echo ${"&"}`.stdout("&\n").runAsTest("ampersand");
|
|
TestBuilder.command`echo ${"|"}`.stdout("|\n").runAsTest("pipe");
|
|
TestBuilder.command`echo ${"="}`.stdout("=\n").runAsTest("equals");
|
|
TestBuilder.command`echo ${";"}`.stdout(";\n").runAsTest("semicolon");
|
|
TestBuilder.command`echo ${"\n"}`.stdout("\n").runAsTest("newline");
|
|
TestBuilder.command`echo ${"{"}`.stdout("{\n").runAsTest("left_brace");
|
|
TestBuilder.command`echo ${"}"}`.stdout("}\n").runAsTest("right_brace");
|
|
TestBuilder.command`echo ${","}`.stdout(",\n").runAsTest("comma");
|
|
TestBuilder.command`echo ${"("}`.stdout("(\n").runAsTest("left_parenthesis");
|
|
TestBuilder.command`echo ${")"}`.stdout(")\n").runAsTest("right_parenthesis");
|
|
TestBuilder.command`echo ${"\\"}`.stdout("\\\n").runAsTest("backslash");
|
|
TestBuilder.command`echo ${" "}`.stdout(" \n").runAsTest("space");
|
|
TestBuilder.command`echo ${"'hello'"}`.stdout("'hello'\n").runAsTest("single_quote");
|
|
TestBuilder.command`echo ${'"hello"'}`.stdout('"hello"\n').runAsTest("double_quote");
|
|
TestBuilder.command`echo ${"`hello`"}`.stdout("`hello`\n").runAsTest("backtick");
|
|
|
|
// Testing characters that need to be escaped within double quotes
|
|
TestBuilder.command`echo "${"$"}"`.stdout("$\n").runAsTest("dollar_in_dquotes");
|
|
TestBuilder.command`echo "${"`"}"`.stdout("`\n").runAsTest("backtick_in_dquotes");
|
|
TestBuilder.command`echo "${'"'}"`.stdout('"\n').runAsTest("double_quote_in_dquotes");
|
|
TestBuilder.command`echo "${"\\"}"`.stdout("\\\n").runAsTest("backslash_in_dquotes");
|
|
|
|
// Testing characters that need to be escaped within single quotes
|
|
TestBuilder.command`echo '${"$"}'`.stdout("$\n").runAsTest("dollar_in_squotes");
|
|
TestBuilder.command`echo '${'"'}'`.stdout('"\n').runAsTest("double_quote_in_squotes");
|
|
TestBuilder.command`echo '${"`"}'`.stdout("`\n").runAsTest("backtick_in_squotes");
|
|
TestBuilder.command`echo '${"\\\\"}'`.stdout("\\\\\n").runAsTest("backslash_in_squotes");
|
|
|
|
// Ensure that backslash escapes within single quotes are treated literally
|
|
TestBuilder.command`echo '${"\\"}'`.stdout("\\\n").runAsTest("literal_backslash_single_quote");
|
|
TestBuilder.command`echo '${"\\\\"}'`.stdout("\\\\\n").runAsTest("double_backslash_single_quote");
|
|
|
|
// Edge cases with mixed quotes
|
|
TestBuilder.command`echo "'\${"$"}'"`.stdout("'${$}'\n").runAsTest("mixed_quotes_dollar");
|
|
TestBuilder.command`echo '"${"`"}"'`.stdout('"`"\n').runAsTest("mixed_quotes_backtick");
|
|
|
|
// Compound command with special characters
|
|
TestBuilder.command`echo ${"hello; echo world"}`.stdout("hello; echo world\n").runAsTest("compound_command");
|
|
TestBuilder.command`echo ${"hello > world"}`.stdout("hello > world\n").runAsTest("redirect_in_echo");
|
|
TestBuilder.command`echo ${"$(echo nested)"}`.stdout("$(echo nested)\n").runAsTest("nested_command_substitution");
|
|
|
|
// Pathological cases involving multiple special characters
|
|
TestBuilder.command`echo ${"complex > command; $(execute)"}`
|
|
.stdout("complex > command; $(execute)\n")
|
|
.runAsTest("complex_mixed_special_chars");
|
|
});
|
|
});
|
|
|
|
describe("deno_task", () => {
|
|
describe("commands", async () => {
|
|
TestBuilder.command`echo 1`.stdout("1\n").runAsTest("echo 1");
|
|
TestBuilder.command`echo 1 2 3`.stdout("1 2 3\n").runAsTest("echo 1 2 3");
|
|
TestBuilder.command`echo "1 2 3"`.stdout("1 2 3\n").runAsTest('echo "1 2 3"');
|
|
TestBuilder.command`echo 1 2\ \ \ 3`.stdout("1 2 3\n").runAsTest("echo 1 2\\ \\ \\ 3");
|
|
TestBuilder.command`echo "1 2\ \ \ 3"`.stdout("1 2\\ \\ \\ 3\n").runAsTest('echo "1 2\\ \\ \\ 3"');
|
|
TestBuilder.command`echo test$(echo 1 2)`.stdout("test1 2\n").runAsTest("echo test$(echo 1 2)");
|
|
TestBuilder.command`echo test$(echo "1 2")`.stdout("test1 2\n").runAsTest('echo test$(echo "1 2")');
|
|
TestBuilder.command`echo "test$(echo "1 2")"`.stdout("test1 2\n").runAsTest('echo "test$(echo "1 2")"');
|
|
TestBuilder.command`echo test$(echo "1 2 3")`.stdout("test1 2 3\n").runAsTest('echo test$(echo "1 2 3")');
|
|
TestBuilder.command`VAR=1 BUN_TEST_VAR=1 ${BUN} -e 'console.log(process.env.VAR)' && echo $VAR`
|
|
.stdout("1\n\n")
|
|
.runAsTest("shell var in command");
|
|
TestBuilder.command`VAR=1 VAR2=2 BUN_TEST_VAR=1 ${BUN} -e 'console.log(process.env.VAR + process.env.VAR2)'`
|
|
.stdout("12\n")
|
|
.runAsTest("shell var in command 2");
|
|
TestBuilder.command`EMPTY= BUN_TEST_VAR=1 ${BUN} -e ${"console.log(`EMPTY: ${process.env.EMPTY}`)"}`
|
|
.stdout("EMPTY: \n")
|
|
.runAsTest("empty shell var");
|
|
TestBuilder.command`"echo" "1"`.stdout("1\n").runAsTest("echo 1 quoted");
|
|
TestBuilder.command`echo test-dashes`.stdout("test-dashes\n").runAsTest("echo test-dashes");
|
|
TestBuilder.command`echo 'a/b'/c`.stdout("a/b/c\n").runAsTest("echo 'a/b'/c");
|
|
TestBuilder.command`echo 'a/b'ctest\"te st\"'asdf'`
|
|
.stdout('a/bctest"te st"asdf\n')
|
|
.runAsTest("echoing a bunch of escapes and quotations");
|
|
TestBuilder.command`echo --test=\"2\" --test='2' test\"TEST\" TEST'test'TEST 'test''test' test'test'\"test\" \"test\"\"test\"'test'`
|
|
.stdout(`--test="2" --test=2 test"TEST" TESTtestTEST testtest testtest"test" "test""test"test\n`)
|
|
.runAsTest("echoing a bunch of escapes and quotations 2");
|
|
});
|
|
|
|
describe("boolean logic", async () => {
|
|
TestBuilder.command`echo 1 && echo 2 || echo 3`.stdout("1\n2\n").runAsTest("echo 1 && echo 2 || echo 3");
|
|
TestBuilder.command`echo 1 || echo 2 && echo 3`.stdout("1\n3\n").runAsTest("echo 1 || echo 2 && echo 3");
|
|
|
|
TestBuilder.command`echo 1 || (echo 2 && echo 3)`.stdout("1\n").runAsTest("or with subshell");
|
|
TestBuilder.command`false || false || (echo 2 && false) || echo 3`.stdout("2\n3\n").runAsTest("or with subshell 2");
|
|
TestBuilder.command`echo 1 || (echo 2 && echo 3)`.stdout("1\n").runAsTest("conditional with subshell");
|
|
TestBuilder.command`false || false || (echo 2 && false) || echo 3`
|
|
.stdout("2\n3\n")
|
|
.runAsTest("conditional with subshell2");
|
|
});
|
|
|
|
describe("command substitution", async () => {
|
|
TestBuilder.command`echo $(echo 1)`.stdout("1\n").runAsTest("nested echo cmd subst");
|
|
TestBuilder.command`echo $(echo 1 && echo 2)`.stdout("1 2\n").runAsTest("nested echo cmd subst with conditional");
|
|
// TODO Sleep tests
|
|
});
|
|
|
|
describe("shell variables", async () => {
|
|
TestBuilder.command`echo $VAR && VAR=1 && echo $VAR && ${BUN} -e ${"console.log(process.env.VAR)"}`
|
|
.stdout("\n1\nundefined\n")
|
|
.runAsTest("shell var");
|
|
|
|
TestBuilder.command`VAR=1 && echo $VAR$VAR`.stdout("11\n").runAsTest("shell var 2");
|
|
|
|
TestBuilder.command`VAR=1 && echo Test$VAR && echo $(echo "Test: $VAR") ; echo CommandSub$($VAR) ; echo $ ; echo \$VAR`
|
|
.stdout("Test1\nTest: 1\nCommandSub\n$\n$VAR\n")
|
|
.stderr("bun: command not found: 1\n")
|
|
.runAsTest("shell var 3");
|
|
});
|
|
|
|
describe("env variables", async () => {
|
|
TestBuilder.command`echo $VAR && export VAR=1 && echo $VAR && BUN_TEST_VAR=1 ${BUN} -e 'console.log(process.env.VAR)'`
|
|
.stdout("\n1\n1\n")
|
|
.runAsTest("exported vars");
|
|
|
|
TestBuilder.command`export VAR=1 VAR2=testing VAR3="test this out" && echo $VAR $VAR2 $VAR3`
|
|
.stdout("1 testing test this out\n")
|
|
.runAsTest("exported vars 2");
|
|
});
|
|
|
|
describe("pipeline", async () => {
|
|
TestBuilder.command`echo 1 | BUN_TEST_VAR=1 ${BUN} -e 'process.stdin.pipe(process.stdout)'`
|
|
.stdout("1\n")
|
|
.runAsTest("basic pipe");
|
|
|
|
TestBuilder.command`echo 1 | echo 2 && echo 3`.stdout("2\n3\n").runAsTest("pipe in conditional");
|
|
|
|
TestBuilder.command`echo $(sleep 0.1 && echo 2 & echo 1) | BUN_DEBUG_QUIET_LOGS=1 BUN_TEST_VAR=1 ${BUN} -e 'await process.stdin.pipe(process.stdout)'`
|
|
.stdout("1 2\n")
|
|
.todo("& not supported")
|
|
.runAsTest("complicated pipeline");
|
|
|
|
TestBuilder.command`echo 2 | echo 1 | BUN_TEST_VAR=1 ${BUN} -e 'process.stdin.pipe(process.stdout)'`
|
|
.stdout("1\n")
|
|
.runAsTest("multi pipe");
|
|
|
|
TestBuilder.command`BUN_TEST_VAR=1 ${BUN} -e 'console.log(1); console.error(2);' | BUN_TEST_VAR=1 ${BUN} -e 'process.stdin.pipe(process.stdout)'`
|
|
.stdout("1\n")
|
|
.stderr("2\n")
|
|
.runAsTest("piping subprocesses");
|
|
|
|
TestBuilder.command`BUN_TEST_VAR=1 ${BUN} -e 'console.log(1); console.error(2);' |& BUN_TEST_VAR=1 ${BUN} -e 'process.stdin.pipe(process.stdout)'`
|
|
// .stdout("1\n2\n")
|
|
.error("Piping stdout and stderr (`|&`) is not supported yet. Please file an issue on GitHub.")
|
|
.runAsTest("|&");
|
|
|
|
// await TestBuilder.command`BUN_TEST_VAR=1 ${BUN} -e 'console.log(1); console.error(2);' | BUN_TEST_VAR=1 ${BUN} -e 'setTimeout(async () => { await Deno.stdin.readable.pipeTo(Deno.stderr.writable) }, 10)' |& BUN_TEST_VAR=1 ${BUN} -e 'await Deno.stdin.readable.pipeTo(Deno.stderr.writable)'`
|
|
// .stderr("2\n1\n")
|
|
// .run();
|
|
|
|
TestBuilder.command`echo 1 |& BUN_TEST_VAR=1 ${BUN} -e 'process.stdin.pipe(process.stdout)'`
|
|
// .stdout("1\n")
|
|
.error("Piping stdout and stderr (`|&`) is not supported yet. Please file an issue on GitHub.")
|
|
.runAsTest("|& 2");
|
|
|
|
TestBuilder.command`echo 1 | BUN_DEBUG_QUIET_LOGS=1 BUN_TEST_VAR=1 ${BUN} -e 'process.stdin.pipe(process.stdout)' > output.txt`
|
|
.fileEquals("output.txt", "1\n")
|
|
.runAsTest("pipe with redirect to file");
|
|
|
|
TestBuilder.command`echo 1 | BUN_TEST_VAR=1 ${BUN} -e 'process.stdin.pipe(process.stderr)' 2> output.txt`
|
|
.fileEquals("output.txt", "1\n")
|
|
.runAsTest("pipe with redirect stderr to file");
|
|
});
|
|
|
|
describe("redirects", async function igodf() {
|
|
TestBuilder.command`echo 5 6 7 > test.txt`.fileEquals("test.txt", "5 6 7\n").runAsTest("basic redirect");
|
|
|
|
TestBuilder.command`echo 1 2 3 && echo 1 > test.txt`
|
|
.stdout("1 2 3\n")
|
|
.fileEquals("test.txt", "1\n")
|
|
.runAsTest("basic redirect with &&");
|
|
|
|
// subdir
|
|
TestBuilder.command`mkdir subdir && cd subdir && echo 1 2 3 > test.txt`
|
|
.fileEquals(`subdir/test.txt`, "1 2 3\n")
|
|
.runAsTest("redirect to file");
|
|
|
|
// absolute path
|
|
TestBuilder.command`echo 1 2 3 > "$PWD/test.txt"`
|
|
.fileEquals("test.txt", "1 2 3\n")
|
|
.runAsTest("redirection path gets expanded");
|
|
|
|
// stdout
|
|
TestBuilder.command`BUN_TEST_VAR=1 ${BUN} -e 'console.log(1); console.error(5)' 1> test.txt`
|
|
.stderr("5\n")
|
|
.fileEquals("test.txt", "1\n")
|
|
.runAsTest("redirect stdout of subproccess");
|
|
|
|
// stderr
|
|
TestBuilder.command`BUN_TEST_VAR=1 ${BUN} -e 'console.log(1); console.error(5)' 2> test.txt`
|
|
.stdout("1\n")
|
|
.fileEquals("test.txt", "5\n")
|
|
.runAsTest("redirect stderr of subprocess");
|
|
|
|
// invalid fd
|
|
// await TestBuilder.command`echo 2 3> test.txt`
|
|
// .ensureTempDir()
|
|
// .stderr("only redirecting to stdout (1) and stderr (2) is supported\n")
|
|
// .exitCode(1)
|
|
// .run();
|
|
|
|
// /dev/null
|
|
TestBuilder.command`BUN_TEST_VAR=1 ${BUN} -e 'console.log(1); console.error(5)' 2> /dev/null`
|
|
.stdout("1\n")
|
|
.runAsTest("/dev/null");
|
|
|
|
// appending
|
|
TestBuilder.command`echo 1 > test.txt && echo 2 >> test.txt`
|
|
.fileEquals("test.txt", "1\n2\n")
|
|
.runAsTest("appending");
|
|
|
|
// &> and &>> redirect
|
|
await TestBuilder.command`BUN_TEST_VAR=1 ${BUN} -e 'console.log(1); setTimeout(() => console.error(23), 10)' &> file.txt && BUN_TEST_VAR=1 ${BUN} -e 'console.log(456); setTimeout(() => console.error(789), 10)' &>> file.txt`
|
|
.fileEquals("file.txt", "1\n23\n456\n789\n")
|
|
.runAsTest("&> and &>> redirect");
|
|
|
|
// multiple arguments after re-direct
|
|
// await TestBuilder.command`export TwoArgs=testing\\ this && echo 1 > $TwoArgs`
|
|
// .stderr(
|
|
// 'redirect path must be 1 argument, but found 2 (testing this). Did you mean to quote it (ex. "testing this")?\n',
|
|
// )
|
|
// .exitCode(1)
|
|
// .run();
|
|
|
|
// zero arguments after re-direct
|
|
TestBuilder.command`echo 1 > $EMPTY`
|
|
.stderr("bun: ambiguous redirect: at `echo`\n")
|
|
.exitCode(1)
|
|
.runAsTest("zero arguments after re-direct");
|
|
|
|
TestBuilder.command`echo foo bar > file.txt; cat < file.txt`
|
|
.ensureTempDir()
|
|
.stdout("foo bar\n")
|
|
.runAsTest("redirect input");
|
|
|
|
TestBuilder.command`BUN_DEBUG_QUIET_LOGS=1 ${BUN} -e ${"console.log('Stdout'); console.error('Stderr')"} 2>&1`
|
|
.stdout("Stdout\nStderr\n")
|
|
.runAsTest("redirect stderr to stdout");
|
|
|
|
TestBuilder.command`BUN_DEBUG_QUIET_LOGS=1 ${BUN} -e ${"console.log('Stdout'); console.error('Stderr')"} 1>&2`
|
|
.stderr("Stdout\nStderr\n")
|
|
.runAsTest("redirect stdout to stderr");
|
|
|
|
TestBuilder.command`BUN_DEBUG_QUIET_LOGS=1 ${BUN} -e ${"console.log('Stdout'); console.error('Stderr')"} 2>&1`
|
|
.stdout("Stdout\nStderr\n")
|
|
.quiet()
|
|
.runAsTest("redirect stderr to stdout quiet");
|
|
|
|
TestBuilder.command`BUN_DEBUG_QUIET_LOGS=1 ${BUN} -e ${"console.log('Stdout'); console.error('Stderr')"} 1>&2`
|
|
.stderr("Stdout\nStderr\n")
|
|
.quiet()
|
|
.runAsTest("redirect stdout to stderr quiet");
|
|
|
|
TestBuilder.command`echo hi > /dev/null`.quiet().runAsTest("redirect /dev/null");
|
|
|
|
TestBuilder.command`BUN_DEBUG_QUIET_LOGS=1 ${BUN} -e ${"console.log('Hello friends')"} > /dev/null`
|
|
.quiet()
|
|
.runAsTest("subproc redirect /dev/null");
|
|
|
|
const code = /* ts */ `
|
|
import { $ } from 'bun'
|
|
|
|
await $\`echo Bunception!\`
|
|
`;
|
|
|
|
TestBuilder.command`BUN_DEBUG_QUIET_LOGS=1 ${BUN} -e ${code} > /dev/null`
|
|
.quiet()
|
|
.runAsTest("bunception redirect /dev/null");
|
|
});
|
|
|
|
describe("pwd", async () => {
|
|
TestBuilder.command`pwd && cd sub_dir && pwd && cd ../ && pwd`
|
|
.directory("sub_dir")
|
|
.file("file.txt", "test")
|
|
// $TEMP_DIR gets replaced with the actual temp dir by the test runner
|
|
.stdout(`$TEMP_DIR\n${join("$TEMP_DIR", "sub_dir")}\n$TEMP_DIR\n`)
|
|
.runAsTest("pwd");
|
|
});
|
|
|
|
test("change env", async () => {
|
|
{
|
|
const { stdout } = await $`echo $FOO`.env({ ...bunEnv, FOO: "bar" });
|
|
expect(stdout.toString()).toEqual("bar\n");
|
|
}
|
|
|
|
{
|
|
const { stdout } = await $`BUN_TEST_VAR=1 ${BUN} -e 'console.log(JSON.stringify(process.env))'`.env({
|
|
...bunEnv,
|
|
FOO: "bar",
|
|
});
|
|
expect(JSON.parse(stdout.toString())).toEqual({
|
|
...bunEnv,
|
|
BUN_TEST_VAR: "1",
|
|
FOO: "bar",
|
|
});
|
|
}
|
|
|
|
{
|
|
const { stdout } = await $`BUN_TEST_VAR=1 ${BUN} -e 'console.log(JSON.stringify(process.env))'`.env({
|
|
...bunEnv,
|
|
FOO: "bar",
|
|
});
|
|
expect(JSON.parse(stdout.toString())).toEqual({
|
|
...bunEnv,
|
|
BUN_TEST_VAR: "1",
|
|
FOO: "bar",
|
|
});
|
|
}
|
|
});
|
|
|
|
// https://github.com/oven-sh/bun/issues/11305
|
|
test.todoIf(isWindows)("stacktrace", async () => {
|
|
// const folder = TestBuilder.tmpdir();
|
|
const code = /* ts */ `import { $ } from 'bun'
|
|
|
|
$.throws(true)
|
|
|
|
async function someFunction() {
|
|
await $\`somecommandthatdoesnotexist\`
|
|
}
|
|
|
|
await someFunction()
|
|
`;
|
|
|
|
const [_, lineNr] = code
|
|
.split("\n")
|
|
.map((l, i) => [l, i + 1] as const)
|
|
.find(([line, _]) => line.includes("somecommandthatdoesnotexist"))!;
|
|
|
|
if (lineNr === undefined) throw new Error("uh oh");
|
|
|
|
await TestBuilder.command`BUN_DEBUG_QUIET_LOGS=1 ${BUN} -e ${code} 2>&1`
|
|
.exitCode(1)
|
|
.stdout(s => expect(s).toInclude(`[eval]:${lineNr}`))
|
|
.run();
|
|
});
|
|
|
|
test("big_data", async () => {
|
|
const writerCode = /* ts */ `
|
|
|
|
const writer = Bun.stdout.writer();
|
|
const buf = new Uint8Array(128 * 1024).fill('a'.charCodeAt(0))
|
|
for (let i = 0; i < 10; i++) {
|
|
writer.write(buf);
|
|
await writer.flush();
|
|
}
|
|
`;
|
|
|
|
const tmpdir = TestBuilder.tmpdir();
|
|
// I think writing 1mb of 'a's to the terminal breaks CI so redirect to a FD instead
|
|
const { stdout, stderr, exitCode } = await $`${BUN} -e ${writerCode} > ${tmpdir}/output.txt`.env(bunEnv);
|
|
|
|
expect(stderr.length).toEqual(0);
|
|
expect(stdout.length).toEqual(0);
|
|
expect(exitCode).toEqual(0);
|
|
|
|
const s = await stat(`${tmpdir}/output.txt`);
|
|
expect(s.size).toEqual(10 * 128 * 1024);
|
|
});
|
|
|
|
// https://github.com/oven-sh/bun/issues/9458
|
|
test("input", async () => {
|
|
const inputCode = /* ts */ `
|
|
const downArrow = '\\x1b[B';
|
|
const enterKey = '\\x0D';
|
|
await Bun.sleep(100)
|
|
const writer = Bun.stdout.writer();
|
|
writer.write(downArrow)
|
|
await Bun.sleep(100)
|
|
writer.write(enterKey)
|
|
writer.flush()
|
|
`;
|
|
|
|
const code = /* ts */ `
|
|
import { expect } from 'bun:test'
|
|
const expected = [
|
|
'\\x1b[B',
|
|
'\\x0D'
|
|
]
|
|
let i = 0
|
|
const writer = Bun.stdout.writer();
|
|
process.stdin.on("data", async chunk => {
|
|
const input = chunk.toString();
|
|
expect(input).toEqual(expected[i++])
|
|
writer.write(input)
|
|
await writer.flush()
|
|
});
|
|
`;
|
|
|
|
const { stdout, stderr, exitCode } =
|
|
await Bun.$`BUN_DEBUG_QUIET_LOGS=1 ${BUN} -e ${inputCode} | BUN_DEBUG_QUIET_LOGS=1 ${BUN} -e ${code}`;
|
|
|
|
expect(exitCode).toBe(0);
|
|
expect(stderr.length).toBe(0);
|
|
expect(stdout.toString()).toEqual("\x1b[B\x0D");
|
|
});
|
|
});
|
|
|
|
describe("if_clause", () => {
|
|
TestBuilder.command/* sh */ `
|
|
# The name of the package we're interested in
|
|
package_name=react
|
|
|
|
filename=package.json
|
|
|
|
if [[ -f $filename ]]; then
|
|
echo The file $filename exists and is a regular file.
|
|
echo Checking for $package_name in dependencies...
|
|
|
|
# Attempt to extract the package version from dependencies
|
|
dep_version=$(jq -r ".dependencies[\"$package_name\"]" $filename)
|
|
|
|
# If not found in dependencies, try devDependencies
|
|
if [[ -z $dep_version ]] || [[ $dep_version == null ]]; then
|
|
dep_version=$(jq -r ".devDependencies[\"$package_name\"]" $filename)
|
|
fi
|
|
|
|
# Check if we got a non-empty, non-null version string
|
|
if [[ -n $dep_version ]] && [[ $dep_version != null ]]; then
|
|
echo The package $package_name is listed as a dependency with version: $dep_version.
|
|
else
|
|
echo The package $package_name is not listed as a dependency.
|
|
fi
|
|
else
|
|
echo The file $filename does not exist or is not a regular file.
|
|
fi
|
|
`
|
|
.file(
|
|
"package.json",
|
|
`{
|
|
"private": true,
|
|
"name": "bun",
|
|
"dependencies": {
|
|
"@vscode/debugadapter": "1.61.0",
|
|
"esbuild": "0.17.15",
|
|
"eslint": "8.20.0",
|
|
"eslint-config-prettier": "8.5.0",
|
|
"mitata": "0.1.3",
|
|
"peechy": "0.4.34",
|
|
"prettier": "3.2.5",
|
|
"react": "next",
|
|
"react-dom": "next",
|
|
"source-map-js": "1.0.2",
|
|
"typescript": "5.0.2"
|
|
},
|
|
"devDependencies": {
|
|
},
|
|
"scripts": {
|
|
}
|
|
}
|
|
`,
|
|
)
|
|
.stdout(
|
|
"The file package.json exists and is a regular file.\nChecking for react in dependencies...\nThe package react is listed as a dependency with version: next.\n",
|
|
);
|
|
|
|
TestBuilder.command`
|
|
if
|
|
echo cond;
|
|
then
|
|
echo then;
|
|
elif
|
|
echo elif;
|
|
then
|
|
echo elif then;
|
|
else
|
|
echo else;
|
|
fi`
|
|
.stdout("cond\nthen\n")
|
|
.runAsTest("basic");
|
|
|
|
TestBuilder.command`
|
|
if
|
|
echo cond
|
|
then
|
|
echo then
|
|
elif
|
|
echo elif
|
|
then
|
|
echo elif then
|
|
else
|
|
echo else
|
|
fi`
|
|
.stdout("cond\nthen\n")
|
|
.runAsTest("basic without semicolon");
|
|
|
|
TestBuilder.command`
|
|
if
|
|
lkfjslkdjfsldf
|
|
then
|
|
echo shouldnt see this
|
|
else
|
|
echo okay here
|
|
fi`
|
|
.stdout("okay here\n")
|
|
.stderr("bun: command not found: lkfjslkdjfsldf\n")
|
|
.runAsTest("else basic");
|
|
|
|
TestBuilder.command`
|
|
if
|
|
lkfjslkdjfsldf
|
|
then
|
|
echo shouldnt see this
|
|
elif
|
|
sdfkjsldf
|
|
then
|
|
echo shouldnt see this either
|
|
else
|
|
echo okay here
|
|
fi`
|
|
.stdout("okay here\n")
|
|
.stderr("bun: command not found: lkfjslkdjfsldf\nbun: command not found: sdfkjsldf\n")
|
|
.runAsTest("else");
|
|
|
|
TestBuilder.command`
|
|
if
|
|
echo hi
|
|
then
|
|
echo hey
|
|
else
|
|
echo uh oh
|
|
fi | cat
|
|
`
|
|
.stdout("hi\nhey\n")
|
|
.runAsTest("in pipeline");
|
|
|
|
TestBuilder.command`if echo hi; then echo lmao; fi && echo nice`
|
|
.stdout("hi\nlmao\nnice\n")
|
|
.runAsTest("no else, cond true");
|
|
|
|
TestBuilder.command`if BUNISBAD; then echo not true; fi && echo bun is good`
|
|
.stdout("bun is good\n")
|
|
.stderr("bun: command not found: BUNISBAD\n")
|
|
.runAsTest("no else, cond false");
|
|
|
|
TestBuilder.command`if [[ -f package.json ]]
|
|
then
|
|
a
|
|
b
|
|
else
|
|
c
|
|
fi`
|
|
.exitCode(1)
|
|
.file("package.json", "lol")
|
|
.stderr("bun: command not found: a\nbun: command not found: b\n")
|
|
.runAsTest("multi statement then");
|
|
|
|
TestBuilder.command`if
|
|
[[ -f package.json ]]
|
|
[[ -f lkdfjlskdf ]]
|
|
then
|
|
echo yeah...
|
|
echo nope!
|
|
else
|
|
echo okay
|
|
echo makes sense!
|
|
fi`
|
|
.file("package.json", "lol")
|
|
.stdout("okay\nmakes sense!\n")
|
|
.runAsTest("multi statement in all branches");
|
|
|
|
["if", "else", "elif", "then", "fi"].map(tok => {
|
|
TestBuilder.command`"${{ raw: tok }}"`
|
|
.stderr(`bun: command not found: ${tok}\n`)
|
|
.exitCode(1)
|
|
.runAsTest(`quoted ${tok} doesn't break`);
|
|
|
|
TestBuilder.command`echo ${{ raw: "lksdfjklsdjf" + tok }}`
|
|
.stdout(`lksdfjklsdjf${tok}\n`)
|
|
.runAsTest(`${tok} in script does not break parsing 1`);
|
|
|
|
TestBuilder.command`echo ${{ raw: "hi " + tok }}`
|
|
.stdout(`hi ${tok}\n`)
|
|
.runAsTest(`${tok} in script does not break parsing 2`);
|
|
|
|
TestBuilder.command`echo ${{ raw: tok + " hi" }}`
|
|
.stdout(`${tok} hi\n`)
|
|
.runAsTest(`${tok} in script does not break parsing 3`);
|
|
});
|
|
|
|
TestBuilder.command`echo fif hi`.stdout("fif hi\n").runAsTest("parsing edge case");
|
|
|
|
// Ported from https://github.com/posix-shell-tests/posix-shell-tests/
|
|
describe("ported from posix shell tests", () => {
|
|
// test_oE 'execution path of if, true'
|
|
TestBuilder.command`if echo foo; then echo bar; fi`.stdout("foo\nbar\n").runAsTest("execution path of if, true");
|
|
|
|
// test_oE 'execution path of if, false'
|
|
TestBuilder.command`if ! echo foo; then echo bar; fi`
|
|
.stdout("foo\n")
|
|
.todo("! not supported")
|
|
.runAsTest("execution path of if, false");
|
|
|
|
// test_oE 'execution path of if-else, true'
|
|
TestBuilder.command`if echo foo; then echo bar; else echo baz; fi`
|
|
.stdout("foo\nbar\n")
|
|
.runAsTest("execution path of if-else, true");
|
|
|
|
// test_oE 'execution path of if-else, false'
|
|
TestBuilder.command`if ! echo foo; then echo bar; else echo baz; fi`
|
|
.stdout("foo\nbaz\n")
|
|
.todo("! not supported")
|
|
.runAsTest("execution path of if-else, false");
|
|
|
|
// test_oE 'execution path of if-elif, true'
|
|
TestBuilder.command`if echo 1; then echo 2; elif echo 3; then echo 4; fi`
|
|
.stdout("1\n2\n")
|
|
.runAsTest("execution path of if-elif, true");
|
|
|
|
// test_oE 'execution path of if-elif, false-true'
|
|
TestBuilder.command`if ! echo 1; then echo 2; elif echo 3; then echo 4; fi`
|
|
.stdout("1\n3\n4\n")
|
|
.todo("! not supported")
|
|
.runAsTest("execution path of if-elif, false-true");
|
|
|
|
// test_oE 'execution path of if-elif, false-false'
|
|
TestBuilder.command`if ! echo 1; then echo 2; elif ! echo 3; then echo 4; fi`
|
|
.stdout("1\n3\n")
|
|
.todo("! not supported")
|
|
.runAsTest("execution path of if-elif, false-false");
|
|
|
|
// test_oE 'execution path of if-elif-else, true'
|
|
TestBuilder.command`if echo 1; then echo 2; elif echo 3; then echo 4; else echo 5; fi`
|
|
.stdout("1\n2\n")
|
|
.runAsTest("execution path of if-elif-else, true");
|
|
|
|
// test_oE 'execution path of if-elif-else, false-true'
|
|
TestBuilder.command`if ! echo 1; then echo 2; elif echo 3; then echo 4; else echo 5; fi`
|
|
.stdout("1\n3\n4\n")
|
|
.todo("! not supported")
|
|
.runAsTest("execution path of if-elif-else, false-true");
|
|
|
|
// test_oE 'execution path of if-elif-else, false-false'
|
|
TestBuilder.command`if ! echo 1; then echo 2; elif ! echo 3; then echo 4; else echo 5; fi`
|
|
.stdout("1\n3\n5\n")
|
|
.todo("! not supported")
|
|
.runAsTest("execution path of if-elif-else, false-false");
|
|
|
|
// test_oE 'execution path of if-elif-elif, true'
|
|
TestBuilder.command`if echo 1; then echo 2; elif echo 3; then echo 4; elif echo 5; then echo 6; fi`
|
|
.stdout("1\n2\n")
|
|
.runAsTest("execution path of if-elif-elif, true");
|
|
|
|
// test_oE 'execution path of if-elif-elif, false-true'
|
|
TestBuilder.command`if ! echo 1; then echo 2; elif echo 3; then echo 4; elif echo 5; then echo 6; fi`
|
|
.stdout("1\n3\n4\n")
|
|
.todo("! not supported")
|
|
.runAsTest("execution path of if-elif-elif, false-true");
|
|
|
|
// test_oE 'execution path of if-elif-elif, false-false-true'
|
|
TestBuilder.command`if ! echo 1; then echo 2; elif ! echo 3; then echo 4; elif echo 5; then echo 6; fi`
|
|
.stdout("1\n3\n5\n6\n")
|
|
.todo("! not supported")
|
|
.runAsTest("execution path of if-elif-elif, false-false-true");
|
|
|
|
// test_oE 'execution path of if-elif-elif, false-false-false'
|
|
TestBuilder.command`if ! echo 1; then echo 2; elif ! echo 3; then echo 4; elif ! echo 5; then echo 6; fi`
|
|
.stdout("1\n3\n5\n")
|
|
.todo("! not supported")
|
|
.runAsTest("execution path of if-elif-elif, false-false-false");
|
|
|
|
// test_oE 'execution path of if-elif-elif-else, true'
|
|
TestBuilder.command`if echo 1; then echo 2; elif echo 3; then echo 4; elif echo 5; then echo 6; else echo 7; fi`
|
|
.stdout("1\n2\n")
|
|
.runAsTest("execution path of if-elif-elif-else, true");
|
|
|
|
// test_oE 'execution path of if-elif-elif-else, false-true'
|
|
TestBuilder.command`if ! echo 1; then echo 2; elif echo 3; then echo 4; elif echo 5; then echo 6; else echo 7; fi`
|
|
.stdout("1\n3\n4\n")
|
|
.todo("! not supported")
|
|
.runAsTest("execution path of if-elif-elif-else, false-true");
|
|
|
|
// test_oE 'execution path of if-elif-elif-else, false-false-true'
|
|
TestBuilder.command`if ! echo 1; then echo 2; elif ! echo 3; then echo 4; elif echo 5; then echo 6; else echo 7; fi`
|
|
.stdout("1\n3\n5\n6\n")
|
|
.todo("! not supported")
|
|
.runAsTest("execution path of if-elif-elif-else, false-false-true");
|
|
|
|
// test_oE 'execution path of if-elif-elif-else, false-false-false'
|
|
TestBuilder.command`if ! echo 1; then echo 2; elif ! echo 3; then echo 4; elif ! echo 5; then echo 6; else echo 7; fi`
|
|
.stdout("1\n3\n5\n7\n")
|
|
.todo("! not supported")
|
|
.runAsTest("execution path of if-elif-elif-else, false-false-false");
|
|
|
|
const exit = (code: number): { raw: string } => ({
|
|
raw:
|
|
process.platform !== "win32"
|
|
? `bash -c 'exit $1' -- ${code}`
|
|
: `BUN_DEBUG_QUIET_LOGS=1 ${BUN} -e 'process.exit(${code})'`,
|
|
});
|
|
|
|
// test_x -e 0 'exit status of if, true-true'
|
|
TestBuilder.command`if ${exit(0)}; then ${exit(0)}; fi`.exitCode(0).runAsTest("exit status of if, true-true");
|
|
// test_x -e 1 'exit status of if, true-false'
|
|
TestBuilder.command`if ${exit(0)}; then ${exit(1)}; fi`.exitCode(1).runAsTest("exit status of if, true-false");
|
|
|
|
// test_x -e 0 'exit status of if, false'
|
|
TestBuilder.command`if ${exit(1)}; then ${exit(2)}; fi`.exitCode(0).runAsTest("exit status of if, false");
|
|
|
|
// test_x -e 0 'exit status of if-else, true-true'
|
|
TestBuilder.command`if ${exit(0)}; then ${exit(0)}; else ${exit(1)}; fi`
|
|
.exitCode(0)
|
|
.runAsTest("exit status of if-else, true-true");
|
|
|
|
// test_x -e 1 'exit status of if-else, true-false'
|
|
TestBuilder.command`if ${exit(0)}; then ${exit(1)}; else ${exit(2)}; fi`
|
|
.exitCode(1)
|
|
.runAsTest("exit status of if-else, true-false");
|
|
|
|
// test_x -e 0 'exit status of if-else, false-true'
|
|
TestBuilder.command`if ${exit(1)}; then ${exit(2)}; else ${exit(0)}; fi`
|
|
.exitCode(0)
|
|
.runAsTest("exit status of if-else, false-true");
|
|
|
|
// test_x -e 2 'exit status of if-else, false-false'
|
|
TestBuilder.command`if ${exit(1)}; then ${exit(0)}; else ${exit(2)}; fi`
|
|
.exitCode(2)
|
|
.runAsTest("exit status of if-else, false-false");
|
|
|
|
// test_x -e 0 'exit status of if-elif, true-true'
|
|
TestBuilder.command`if ${exit(0)}; then ${exit(0)}; elif ${exit(1)}; then ${exit(2)}; fi`
|
|
.exitCode(0)
|
|
.runAsTest("exit status of if-elif, true-true");
|
|
|
|
// test_x -e 1 'exit status of if-elif, true-false'
|
|
TestBuilder.command`if ${exit(0)}; then ${exit(1)}; elif ${exit(2)}; then ${exit(3)}; fi`
|
|
.exitCode(1)
|
|
.runAsTest("exit status of if-elif, true-false");
|
|
|
|
// test_x -e 0 'exit status of if-elif, false-true-true'
|
|
TestBuilder.command`if ${exit(1)}; then ${exit(2)}; elif ${exit(0)}; then ${exit(0)}; fi`
|
|
.exitCode(0)
|
|
.runAsTest("exit status of if-elif, false-true-true");
|
|
|
|
// test_x -e 3 'exit status of if-elif, false-true-false'
|
|
TestBuilder.command`if ${exit(1)}; then ${exit(2)}; elif ${exit(0)}; then ${exit(3)}; fi`
|
|
.exitCode(3)
|
|
.runAsTest("exit status of if-elif, false-true-false");
|
|
|
|
// test_x -e 0 'exit status of if-elif-elif-else, true-true'
|
|
TestBuilder.command`if ${exit(0)}; then ${exit(0)}; elif ${exit(1)}; then ${exit(2)}; elif ${exit(3)}; then ${exit(4)}; else ${exit(5)}; fi`
|
|
.exitCode(0)
|
|
.runAsTest("exit status of if-elif-elif-else, true-true");
|
|
|
|
// test_x -e 11 'exit status of if-elif-elif-else, true-false'
|
|
TestBuilder.command`if ${exit(0)}; then ${exit(11)}; elif ${exit(1)}; then ${exit(2)}; elif ${exit(3)}; then ${exit(4)}; else ${exit(5)}; fi`
|
|
.exitCode(11)
|
|
.runAsTest("exit status of if-elif-elif-else, true-false");
|
|
|
|
// test_x -e 0 'exit status of if-elif-elif-else, false-true-true'
|
|
TestBuilder.command`if ${exit(1)}; then ${exit(2)}; elif ${exit(0)}; then ${exit(0)}; elif ${exit(3)}; then ${exit(4)}; else ${exit(5)}; fi`
|
|
.exitCode(0)
|
|
.runAsTest("exit status of if-elif-elif-else, false-true-true");
|
|
|
|
// test_x -e 13 'exit status of if-elif-elif-else, false-true-false'
|
|
TestBuilder.command`if ${exit(1)}; then ${exit(2)}; elif ${exit(0)}; then ${exit(13)}; elif ${exit(3)}; then ${exit(4)}; else ${exit(5)}; fi`
|
|
.exitCode(13)
|
|
.runAsTest("exit status of if-elif-elif-else, false-true-false");
|
|
|
|
// test_x -e 0 'exit status of if-elif-elif-else, false-false-true-true'
|
|
TestBuilder.command`if ${exit(1)}; then ${exit(2)}; elif ${exit(3)}; then ${exit(4)}; elif ${exit(0)}; then ${exit(0)}; else ${exit(5)}; fi`
|
|
.exitCode(0)
|
|
.runAsTest("exit status of if-elif-elif-else, false-false-true-true");
|
|
|
|
// test_x -e 5 'exit status of if-elif-elif-else, false-false-true-false'
|
|
TestBuilder.command`if ${exit(1)}; then ${exit(2)}; elif ${exit(3)}; then ${exit(4)}; elif ${exit(0)}; then ${exit(5)}; else ${exit(6)}; fi`
|
|
.exitCode(5)
|
|
.runAsTest("exit status of if-elif-elif-else, false-false-true-false");
|
|
|
|
// test_x -e 0 'exit status of if-elif-elif-else, false-false-false-true'
|
|
TestBuilder.command`if ${exit(1)}; then ${exit(2)}; elif ${exit(3)}; then ${exit(4)}; elif ${exit(5)}; then ${exit(6)}; else ${exit(0)}; fi`
|
|
.exitCode(0)
|
|
.runAsTest("exit status of if-elif-elif-else, false-false-false-true");
|
|
|
|
// test_x -e 7 'exit status of if-elif-elif-else, false-false-false-false'
|
|
TestBuilder.command`if ${exit(1)}; then ${exit(2)}; elif ${exit(3)}; then ${exit(4)}; elif ${exit(5)}; then ${exit(6)}; else ${exit(7)}; fi`
|
|
.exitCode(7)
|
|
.runAsTest("exit status of if-elif-elif-else, false-false-false-false");
|
|
|
|
// test_oE 'linebreak after if'
|
|
TestBuilder.command`if
|
|
echo foo;then echo bar;fi`
|
|
.stdout("foo\nbar\n")
|
|
.runAsTest("linebreak after if");
|
|
|
|
// test_oE 'linebreak before then (after if)'
|
|
TestBuilder.command`if echo foo
|
|
then echo bar;fi`
|
|
.stdout("foo\nbar\n")
|
|
.runAsTest("linebreak before then (after if)");
|
|
|
|
// test_oE 'linebreak after then (after if)'
|
|
TestBuilder.command`if echo foo;then
|
|
echo bar;fi`
|
|
.stdout("foo\nbar\n")
|
|
.runAsTest("linebreak after then (after if)");
|
|
|
|
// test_oE 'linebreak before fi (after then)'
|
|
TestBuilder.command`if echo foo;then echo bar
|
|
fi`
|
|
.stdout("foo\nbar\n")
|
|
.runAsTest("linebreak before fi (after then)");
|
|
|
|
// test_oE 'linebreak before elif'
|
|
TestBuilder.command`if ! echo foo;then echo bar
|
|
elif echo baz;then echo qux;fi`
|
|
.stdout("foo\nbaz\nqux\n")
|
|
.todo("! not supported")
|
|
.runAsTest("linebreak before elif");
|
|
|
|
// test_oE 'linebreak after elif'
|
|
TestBuilder.command`if ! echo foo;then echo bar;elif
|
|
echo baz;then echo qux;fi`
|
|
.stdout("foo\nbaz\nqux\n")
|
|
.todo("! not supported")
|
|
.runAsTest("linebreak after elif");
|
|
|
|
// test_oE 'linebreak before then (after elif)'
|
|
TestBuilder.command`if ! echo foo;then echo bar;elif echo baz
|
|
then echo qux;fi`
|
|
.stdout("foo\nbaz\nqux\n")
|
|
.todo("! not supported")
|
|
.runAsTest("linebreak before then (after elif)");
|
|
|
|
// test_oE 'linebreak after then (after elif)'
|
|
TestBuilder.command`if ! echo foo;then echo bar;elif echo baz;then
|
|
echo qux;fi`
|
|
.stdout("foo\nbaz\nqux\n")
|
|
.todo("! not supported")
|
|
.runAsTest("linebreak after then (after elif)");
|
|
|
|
// test_oE 'linebreak before else'
|
|
TestBuilder.command`if ! echo foo;then echo bar
|
|
else echo baz;fi`
|
|
.stdout("foo\nbaz\n")
|
|
.todo("! not supported")
|
|
.runAsTest("linebreak before else");
|
|
|
|
// test_oE 'linebreak after else'
|
|
TestBuilder.command`if ! echo foo;then echo bar;else
|
|
echo baz;fi`
|
|
.stdout("foo\nbaz\n")
|
|
.todo("! not supported")
|
|
.runAsTest("linebreak after else");
|
|
|
|
// test_oE 'linebreak before fi (after else)'
|
|
TestBuilder.command`if ! echo foo;then echo bar;else echo baz
|
|
fi`
|
|
.stdout("foo\nbaz\n")
|
|
.todo("! not supported")
|
|
.runAsTest("linebreak before fi (after else)");
|
|
|
|
// test_oE 'command ending with asynchronous command (after if)'
|
|
TestBuilder.command`if echo foo&then wait;fi`
|
|
.stdout("foo\n")
|
|
.todo("wait not implemented")
|
|
.runAsTest("command ending with asynchronous command (after if)");
|
|
|
|
// test_oE 'command ending with asynchronous command (after then)'
|
|
TestBuilder.command`if echo foo;then echo bar&fi;wait`
|
|
.stdout("foo\nbar\n")
|
|
.todo("wait not implementeeed")
|
|
.runAsTest("command ending with asynchronous command (after then)");
|
|
|
|
// test_oE 'command ending with asynchronous command (after elif)'
|
|
TestBuilder.command`if ! echo foo;then echo bar;elif echo baz&then wait;fi`
|
|
.stdout("foo\nbaz\n")
|
|
.todo("! not supported")
|
|
.runAsTest("command ending with asynchronous command (after elif)");
|
|
|
|
// test_oE 'command ending with asynchronous command (after else)'
|
|
TestBuilder.command`if ! echo foo;then echo bar;elif ! echo baz;then echo qux;else echo quux;fi;wait`
|
|
.stdout("foo\nbaz\nquux\n")
|
|
.todo("! not supported")
|
|
.runAsTest("command ending with asynchronous command (after else)");
|
|
|
|
// test_oE 'more than one inner command'
|
|
TestBuilder.command`if echo 1; echo 2
|
|
echo 3; ! echo 4; then echo x1; echo x2
|
|
echo x3; echo x4; elif echo 5; echo 6
|
|
echo 7; echo 8; then echo 9; echo 10
|
|
echo 11; echo 12; else echo x5; echo x6
|
|
echo x7; echo x8; fi`
|
|
.stdout("1\n2\n3\n4\n5\n6\n7\n8\n9\n10\n11\n12\n")
|
|
.todo("! not supported")
|
|
.runAsTest("more than one inner command");
|
|
|
|
// test_oE 'nest between if and then'
|
|
TestBuilder.command`if { echo foo; } then echo bar; fi`
|
|
.stdout("foo\nbar\n")
|
|
.todo("grouping with { and } not supported yet")
|
|
.runAsTest("nest between if and then");
|
|
|
|
// test_oE 'nest between then and fi'
|
|
TestBuilder.command`if echo foo; then { echo bar; } fi`
|
|
.stdout("foo\nbar\n")
|
|
.todo("grouping with { and } not supported yet")
|
|
.runAsTest("nest between then and fi");
|
|
|
|
// test_oE 'nest between then and elif'
|
|
TestBuilder.command`if echo foo; then { echo bar; } elif echo baz; then echo qux; fi`
|
|
.stdout("foo\nbar\n")
|
|
.todo("grouping with { and } not supported yet")
|
|
.runAsTest("nest between then and elif");
|
|
|
|
// test_oE 'nest between elif and then'
|
|
TestBuilder.command`if echo foo; then echo bar; elif { echo baz; } then echo qux; fi`
|
|
.stdout("foo\nbar\n")
|
|
.todo("grouping with { and } not supported yet")
|
|
.runAsTest("nest between elif and then");
|
|
|
|
// test_oE 'nest between then and else'
|
|
TestBuilder.command`if ! echo foo; then { echo bar; } else echo baz; fi`
|
|
.stdout("foo\nbaz\n")
|
|
.todo("! not supported")
|
|
.runAsTest("nest between then and else");
|
|
|
|
// test_oE 'nest between then and else'
|
|
TestBuilder.command`if ! echo foo; then echo bar; else { echo baz; } fi`
|
|
.stdout("foo\nbaz\n")
|
|
.todo("! not supported")
|
|
.runAsTest("nest between then and else");
|
|
|
|
// test_oE 'redirection on if'
|
|
TestBuilder.command`if echo foo
|
|
then echo bar
|
|
else echo baz
|
|
fi >redir_out
|
|
cat redir_out`
|
|
.stdout("foo\nbar\n")
|
|
.todo("redirecting if-else not supported yet")
|
|
.runAsTest("redirection on if");
|
|
});
|
|
});
|
|
|
|
describe("condexprs", () => {
|
|
TestBuilder.command`[[ -f package.json ]] && echo yes!`.file("package.json", "hi").stdout("yes!\n").runAsTest("-f");
|
|
TestBuilder.command`[[ -f mumbo.jumbo ]] && echo yes!`.exitCode(1).runAsTest("-f non-existent");
|
|
|
|
TestBuilder.command`[[ -d mydir ]] && echo yes!`.directory("mydir").stdout("yes!\n").runAsTest("-d");
|
|
TestBuilder.command`[[ -d mumbo.jumbo ]] && echo yes!`.exitCode(1).runAsTest("-d non-existent");
|
|
|
|
TestBuilder.command`[[ -c /dev/null ]] && echo yes!`.stdout("yes!\n").runAsTest("-c");
|
|
TestBuilder.command`[[ -c lol ]] && echo yes!`.exitCode(1).file("lol", "lol").runAsTest("-c not character device");
|
|
TestBuilder.command`[[ -c mumbo.jumbo ]] && echo yes!`.exitCode(1).runAsTest("-c non-existent");
|
|
|
|
TestBuilder.command`FOO=""; [[ -z $FOO ]] && echo yes!`.stdout("yes!\n").runAsTest("-z");
|
|
TestBuilder.command`[[ -z "skldjfldsf" ]] && echo yes!`.exitCode(1).runAsTest("-z fail");
|
|
|
|
TestBuilder.command`FOO="lkjdflskdjf"; [[ -n $FOO ]] && echo yes!`.stdout("yes!\n").runAsTest("-n");
|
|
TestBuilder.command`FOO="" [[ -n $FOO ]] && echo yes!`.exitCode(1).runAsTest("-n fail");
|
|
|
|
TestBuilder.command`[[ -n hey ]] | echo hi | cat`.stdout("hi\n").runAsTest("precedence: pipeline");
|
|
|
|
TestBuilder.command`[[ foo == foo ]] && echo yes!`.stdout("yes!\n").runAsTest("==");
|
|
TestBuilder.command`[[ foo == lol ]] && echo yes!`.exitCode(1).runAsTest("== fail");
|
|
TestBuilder.command`[[ foo != foo ]] && echo yes!`.exitCode(1).runAsTest("!= fail");
|
|
TestBuilder.command`[[ lmao != foo ]] && echo yes!`.stdout("yes!\n").runAsTest("!=");
|
|
|
|
TestBuilder.command`LOL=; [[ $LOl == $LOL ]] && echo yes!`.stdout("yes!\n").runAsTest("== empty");
|
|
TestBuilder.command`LOL=; [[ $LOl != $LOL ]] && echo yes!`.exitCode(1).runAsTest("!= empty");
|
|
|
|
describe.todo("ported from GNU bash", () => {
|
|
TestBuilder.command`
|
|
[[ foo > bar && $PWD -ef . ]]
|
|
`
|
|
.exitCode(0)
|
|
.runAsTest("this one is straight out of the ksh88 book");
|
|
|
|
TestBuilder.command`
|
|
[[ x ]]
|
|
`
|
|
.exitCode(0)
|
|
.runAsTest("[[ x ]] is equivalent to [[ -n x ]]");
|
|
|
|
TestBuilder.command`[[ ! x ]]`.exitCode(1).runAsTest("# [[ ! x ]] is equivalent to [[ ! -n x ]]");
|
|
|
|
// tests.ts
|
|
|
|
TestBuilder.command`[[ ! x || x ]]`
|
|
.exitCode(0)
|
|
.runAsTest("! binds tighter than test/[ -- it binds to a term, not an expression");
|
|
|
|
TestBuilder.command`
|
|
[[ ! 1 -eq 1 ]]; echo $?
|
|
[[ ! ! 1 -eq 1 ]]; echo $?
|
|
`
|
|
.stdout("1")
|
|
.stdout("0")
|
|
.runAsTest("! toggles on and off rather than just setting an 'invert result' flag");
|
|
|
|
TestBuilder.command`
|
|
[[ ! ! ! 1 -eq 1 ]]; echo $?
|
|
[[ ! ! ! ! 1 -eq 1 ]]; echo $?
|
|
`
|
|
.stdout("1")
|
|
.stdout("0")
|
|
.runAsTest("! toggles on and off rather than just setting an 'invert result' flag");
|
|
|
|
TestBuilder.command`[[ a ]]`.exitCode(0).runAsTest("parenthesized terms didn't work right until post-2.04");
|
|
TestBuilder.command`[[ (a) ]]`.exitCode(0).runAsTest("parenthesized terms didn't work right until post-2.04");
|
|
TestBuilder.command`[[ -n a ]]`.exitCode(0).runAsTest("parenthesized terms didn't work right until post-2.04");
|
|
TestBuilder.command`[[ (-n a) ]]`.exitCode(0).runAsTest("parenthesized terms didn't work right until post-2.04");
|
|
|
|
TestBuilder.command`[[ -n $UNSET ]]`.exitCode(1).runAsTest("unset variables don't need to be quoted");
|
|
TestBuilder.command`[[ -z $UNSET ]]`.exitCode(0).runAsTest("unset variables don't need to be quoted");
|
|
|
|
TestBuilder.command`[[ $TDIR == /usr/homes/* ]]`
|
|
.exitCode(0)
|
|
.runAsTest("the ==/= and != operators do pattern matching");
|
|
TestBuilder.command`[[ $TDIR == /usr/homes/\\* ]]`
|
|
.exitCode(1)
|
|
.runAsTest("...but you can quote any part of the pattern to have it matched as a string");
|
|
TestBuilder.command`[[ $TDIR == '/usr/homes/*' ]]`
|
|
.exitCode(1)
|
|
.runAsTest("...but you can quote any part of the pattern to have it matched as a string");
|
|
|
|
TestBuilder.command`[[ -n $UNSET && $UNSET == foo ]]`
|
|
.exitCode(1)
|
|
.runAsTest("if the first part of && fails, the second is not executed");
|
|
TestBuilder.command`[[ -z $UNSET && $UNSET == foo ]]`
|
|
.exitCode(1)
|
|
.runAsTest("if the first part of && fails, the second is not executed");
|
|
|
|
TestBuilder.command`[[ -z $UNSET || -d $PWD ]]`
|
|
.exitCode(0)
|
|
.runAsTest("if the first part of || succeeds, the second is not executed");
|
|
|
|
// TestBuilder.command`[[ -n $TDIR || $HOME -ef ${H*} ]]`.exitCode(0).runAsTest("if the rhs were executed, it would be an error");
|
|
// TestBuilder.command`[[ -n $TDIR && -z $UNSET || $HOME -ef ${H*} ]]`.exitCode(0).runAsTest("if the rhs were executed, it would be an error");
|
|
|
|
TestBuilder.command`[[ -n $TDIR && -n $UNSET || $TDIR -ef . ]]`
|
|
.exitCode(0)
|
|
.runAsTest("&& has a higher parsing precedence than ||");
|
|
TestBuilder.command`[[ -n $TDIR || -n $UNSET && $PWD -ef xyz ]]`
|
|
.exitCode(1)
|
|
.runAsTest("...but expressions in parentheses may be used to override precedence rules");
|
|
TestBuilder.command`[[ ( -n $TDIR || -n $UNSET ) && $PWD -ef xyz ]]`
|
|
.exitCode(1)
|
|
.runAsTest("...but expressions in parentheses may be used to override precedence rules");
|
|
|
|
TestBuilder.command`
|
|
unset IVAR A
|
|
[[ 7 -gt $IVAR ]]
|
|
`
|
|
.exitCode(0)
|
|
.runAsTest(
|
|
"some arithmetic tests for completeness -- see what happens with missing operands, bad expressions, makes sure arguments are evaluated as arithmetic expressions, etc.",
|
|
);
|
|
|
|
TestBuilder.command`
|
|
unset IVAR A
|
|
[[ $IVAR -gt 7 ]]
|
|
`
|
|
.exitCode(1)
|
|
.runAsTest(
|
|
"some arithmetic tests for completeness -- see what happens with missing operands, bad expressions, makes sure arguments are evaluated as arithmetic expressions, etc.",
|
|
);
|
|
|
|
TestBuilder.command`
|
|
IVAR=4
|
|
[[ $IVAR -gt 7 ]]
|
|
`
|
|
.exitCode(1)
|
|
.runAsTest(
|
|
"some arithmetic tests for completeness -- see what happens with missing operands, bad expressions, makes sure arguments are evaluated as arithmetic expressions, etc.",
|
|
);
|
|
|
|
TestBuilder.command`[[ 7 -eq 4+3 ]]`
|
|
.exitCode(0)
|
|
.runAsTest(
|
|
"some arithmetic tests for completeness -- see what happens with missing operands, bad expressions, makes sure arguments are evaluated as arithmetic expressions, etc.",
|
|
);
|
|
|
|
TestBuilder.command`[[ 7 -eq 4+ ]]`
|
|
.exitCode(1)
|
|
.stdout('./cond.tests: line 122: [[: 4+: syntax error: operand expected (error token is "+")')
|
|
.runAsTest(
|
|
"some arithmetic tests for completeness -- see what happens with missing operands, bad expressions, makes sure arguments are evaluated as arithmetic expressions, etc.",
|
|
);
|
|
|
|
TestBuilder.command`
|
|
IVAR=4+3
|
|
[[ $IVAR -eq 7 ]]
|
|
`
|
|
.exitCode(0)
|
|
.runAsTest(
|
|
"some arithmetic tests for completeness -- see what happens with missing operands, bad expressions, makes sure arguments are evaluated as arithmetic expressions, etc.",
|
|
);
|
|
|
|
TestBuilder.command`
|
|
A=7
|
|
[[ $IVAR -eq A ]]
|
|
`
|
|
.exitCode(0)
|
|
.runAsTest(
|
|
"some arithmetic tests for completeness -- see what happens with missing operands, bad expressions, makes sure arguments are evaluated as arithmetic expressions, etc.",
|
|
);
|
|
|
|
TestBuilder.command`[[ "$IVAR" -eq "7" ]]`
|
|
.exitCode(0)
|
|
.runAsTest(
|
|
"some arithmetic tests for completeness -- see what happens with missing operands, bad expressions, makes sure arguments are evaluated as arithmetic expressions, etc.",
|
|
);
|
|
|
|
TestBuilder.command`
|
|
A=7
|
|
[[ "$IVAR" -eq "A" ]]
|
|
`
|
|
.exitCode(0)
|
|
.runAsTest(
|
|
"some arithmetic tests for completeness -- see what happens with missing operands, bad expressions, makes sure arguments are evaluated as arithmetic expressions, etc.",
|
|
);
|
|
|
|
TestBuilder.command`
|
|
unset IVAR A
|
|
[[ $filename == *.c ]]
|
|
`
|
|
.exitCode(1)
|
|
.runAsTest("more pattern matching tests");
|
|
|
|
TestBuilder.command`
|
|
filename=patmatch.c
|
|
[[ $filename == *.c ]]
|
|
`
|
|
.exitCode(0)
|
|
.runAsTest("more pattern matching tests");
|
|
|
|
TestBuilder.command`
|
|
shopt -s extglob
|
|
arg=-7
|
|
[[ $arg == -+([0-9]) ]]
|
|
`
|
|
.exitCode(0)
|
|
.runAsTest("the extended globbing features may be used when matching patterns");
|
|
|
|
TestBuilder.command`
|
|
shopt -s extglob
|
|
arg=-H
|
|
[[ $arg == -+([0-9]) ]]
|
|
`
|
|
.exitCode(1)
|
|
.runAsTest("the extended globbing features may be used when matching patterns");
|
|
|
|
TestBuilder.command`
|
|
shopt -s extglob
|
|
arg=+4
|
|
[[ $arg == ++([0-9]) ]]
|
|
`
|
|
.exitCode(0)
|
|
.runAsTest("the extended globbing features may be used when matching patterns");
|
|
|
|
TestBuilder.command`
|
|
STR=file.c
|
|
PAT=
|
|
if [[ $STR = $PAT ]]; then
|
|
echo oops
|
|
fi
|
|
`.runAsTest("make sure the null string is never matched if the string is not null");
|
|
|
|
TestBuilder.command`
|
|
STR=
|
|
PAT=
|
|
if [[ $STR = $PAT ]]; then
|
|
echo ok
|
|
fi
|
|
`
|
|
.stdout("ok")
|
|
.runAsTest("but that if the string is null, a null pattern is matched correctly");
|
|
|
|
// TestBuilder.command`
|
|
// [[ jbig2dec-0.9-i586-001.tgz =~ ([^-]+)-([^-]+)-([^-]+)-0*([1-9][0-9]*)\.tgz ]]
|
|
// echo ${BASH_REMATCH[1]}
|
|
// `
|
|
// .stdout("jbig2dec")
|
|
// .runAsTest("test the regular expression conditional operator");
|
|
|
|
// TestBuilder.command`
|
|
// [[ jbig2dec-0.9-i586-001.tgz =~ \\([^-]+\\)-\\([^-]+\\)-\\([^-]+\\)-0*\\([1-9][0-9]*\\)\\.tgz ]]
|
|
// echo ${BASH_REMATCH[1]}
|
|
// `
|
|
// .runAsTest("this shouldn't echo anything");
|
|
|
|
// TestBuilder.command`
|
|
// LDD_BASH=" linux-gate.so.1 => (0xffffe000)
|
|
// libreadline.so.5 => /lib/libreadline.so.5 (0xb7f91000)
|
|
// libhistory.so.5 => /lib/libhistory.so.5 (0xb7f8a000)
|
|
// libncurses.so.5 => /lib/libncurses.so.5 (0xb7f55000)
|
|
// libdl.so.2 => /lib/libdl.so.2 (0xb7f51000)
|
|
// libc.so.6 => /lib/libc.so.6 (0xb7e34000)
|
|
// /lib/ld-linux.so.2 (0xb7fd0000)"
|
|
// [[ "$LDD_BASH" =~ "libc" ]] && echo "found 1"
|
|
// echo ${BASH_REMATCH[@]}
|
|
// `
|
|
// .stdout("found 1")
|
|
// .stdout("libc")
|
|
// .runAsTest("test the regular expression conditional operator");
|
|
|
|
// TestBuilder.command`
|
|
// LDD_BASH=" linux-gate.so.1 => (0xffffe000)
|
|
// libreadline.so.5 => /lib/libreadline.so.5 (0xb7f91000)
|
|
// libhistory.so.5 => /lib/libhistory.so.5 (0xb7f8a000)
|
|
// libncurses.so.5 => /lib/libncurses.so.5 (0xb7f55000)
|
|
// libdl.so.2 => /lib/libdl.so.2 (0xb7f51000)
|
|
// libc.so.6 => /lib/libc.so.6 (0xb7e34000)
|
|
// /lib/ld-linux.so.2 (0xb7fd0000)"
|
|
// [[ "$LDD_BASH" =~ libc ]] && echo "found 2"
|
|
// echo ${BASH_REMATCH[@]}
|
|
// `
|
|
// .stdout("found 2")
|
|
// .stdout("libc")
|
|
// .runAsTest("test the regular expression conditional operator");
|
|
|
|
TestBuilder.command`
|
|
if [[ "123abc" == *?(a)bc ]]; then echo ok 42; else echo bad 42; fi
|
|
if [[ "123abc" == *?(a)bc ]]; then echo ok 43; else echo bad 43; fi
|
|
`
|
|
.stdout("ok 42")
|
|
.stdout("ok 43")
|
|
.runAsTest("bug in all versions up to and including bash-2.05b");
|
|
|
|
// TestBuilder.command`
|
|
// match() { [[ $ 1 == $2 ]]; }
|
|
// match $'? *x\1y\177z' $'??\\*\\x\\\1\\y\\\177\\z' || echo bad 44
|
|
|
|
// foo=""
|
|
// [[ bar == *"${foo,,}"* ]] && echo ok 1
|
|
// [[ bar == *${foo,,}* ]] && echo ok 2
|
|
|
|
// shopt -s extquote
|
|
// bs='\\'
|
|
// del=$'\177'
|
|
// [[ bar == *$bs"$del"* ]] || echo ok 3
|
|
// [[ "" == "$foo" ]] && echo ok 4
|
|
// [[ "$del" == "${foo,,}" ]] || echo ok 5
|
|
|
|
// # allow reserved words after a conditional command just because
|
|
// if [[ str ]] then [[ str ]] fi
|
|
// `
|
|
// .stdout("ok 1")
|
|
// .stdout("ok 2")
|
|
// .stdout("ok 3")
|
|
// .stdout("ok 4")
|
|
// .stdout("ok 5")
|
|
// .runAsTest("various tests");
|
|
});
|
|
});
|
|
|
|
describe("subshell", () => {
|
|
const sharppkgjson = /* json */ `{
|
|
"name": "sharp-test",
|
|
"module": "index.ts",
|
|
"type": "module",
|
|
"dependencies": {
|
|
"sharp": "0.33.3"
|
|
}
|
|
}`;
|
|
|
|
TestBuilder.command/* sh */ `
|
|
mkdir sharp-test
|
|
cd sharp-test
|
|
echo ${sharppkgjson} > package.json
|
|
${BUN} i
|
|
`
|
|
.ensureTempDir()
|
|
.stdout(out => expect(out).toInclude("+ sharp@0.33.3"))
|
|
.stderr(() => {})
|
|
.exitCode(0)
|
|
.env(bunEnv)
|
|
.runAsTest("sharp");
|
|
|
|
TestBuilder.command/* sh */ `( ( ( ( echo HI! ) ) ) )`.stdout("HI!\n").runAsTest("multiple levels");
|
|
TestBuilder.command/* sh */ `(
|
|
echo HELLO! ;
|
|
echo HELLO AGAIN!
|
|
)`
|
|
.stdout("HELLO!\nHELLO AGAIN!\n")
|
|
.runAsTest("multiline");
|
|
TestBuilder.command/* sh */ `(exit 42)`.exitCode(42).runAsTest("exit code");
|
|
TestBuilder.command/* sh */ `(exit 42); echo hi`.exitCode(0).stdout("hi\n").runAsTest("exit code 2");
|
|
TestBuilder.command/* sh */ `
|
|
VAR1=VALUE1
|
|
VAR2=VALUE2
|
|
VAR3=VALUE3
|
|
(
|
|
echo $VAR1 $VAR2 $VAR3
|
|
VAR1='you cant'
|
|
VAR2='see me'
|
|
VAR3='my time is now'
|
|
echo $VAR1 $VAR2 $VAR3
|
|
)
|
|
echo $VAR1 $VAR2 $VAR3
|
|
`
|
|
.stdout("VALUE1 VALUE2 VALUE3\nyou cant see me my time is now\nVALUE1 VALUE2 VALUE3\n")
|
|
.runAsTest("copy of environment");
|
|
|
|
TestBuilder.command/* sh */ `
|
|
mkdir foo
|
|
(
|
|
echo $PWD
|
|
cd foo
|
|
echo $PWD
|
|
)
|
|
echo $PWD
|
|
`
|
|
.ensureTempDir()
|
|
.stdout(`$TEMP_DIR\n$TEMP_DIR${sep}foo\n$TEMP_DIR\n`)
|
|
.runAsTest("does not change cwd");
|
|
|
|
TestBuilder.command`
|
|
BUN_DEBUG_QUIET_LOGS=1 ${BUN} -e ${"console.log(process.env.FOO)"}
|
|
|
|
(
|
|
export FOO=bar
|
|
BUN_DEBUG_QUIET_LOGS=1 ${BUN} -e ${"console.log(process.env.FOO)"}
|
|
)
|
|
|
|
|
|
BUN_DEBUG_QUIET_LOGS=1 ${BUN} -e ${"console.log(process.env.FOO)"}
|
|
`
|
|
.stdout("undefined\nbar\nundefined\n")
|
|
.runAsTest("does not modify export env of parent");
|
|
|
|
TestBuilder.command`\(echo hi \)`.stderr("bun: command not found: (echo\n").exitCode(1).runAsTest("escaped subshell");
|
|
TestBuilder.command`echo \\\(hi\\\)`.stdout("\\(hi\\)\n").runAsTest("escaped subshell 2");
|
|
|
|
TestBuilder.command/* sh */ `
|
|
mkdir dir
|
|
(
|
|
cd dir
|
|
pwd | cat | cat
|
|
)
|
|
pwd
|
|
`
|
|
.ensureTempDir()
|
|
.stdout(`$TEMP_DIR${sep}dir\n$TEMP_DIR\n`)
|
|
.runAsTest("pipeline in subshell");
|
|
|
|
TestBuilder.command/* sh */ `
|
|
mkdir dir
|
|
(pwd) | cat
|
|
(cd dir; pwd) | cat
|
|
pwd
|
|
`
|
|
.ensureTempDir()
|
|
.stdout(`$TEMP_DIR\n$TEMP_DIR${sep}dir\n$TEMP_DIR\n`)
|
|
.runAsTest("subshell in pipeline");
|
|
|
|
TestBuilder.command/* sh */ `
|
|
mkdir dir
|
|
(pwd) | cat
|
|
(cd dir; pwd) | cat
|
|
pwd
|
|
`
|
|
.ensureTempDir()
|
|
.stdout(`$TEMP_DIR\n$TEMP_DIR${sep}dir\n$TEMP_DIR\n`)
|
|
.runAsTest("subshell in pipeline");
|
|
|
|
TestBuilder.command/* sh */ `
|
|
mkdir foo
|
|
( ( (cd foo ; pwd) | cat) ) | ( ( (cat) ) | cat )
|
|
|
|
`
|
|
.ensureTempDir()
|
|
.stdout(`$TEMP_DIR${sep}foo\n`)
|
|
.runAsTest("imbricated subshells and pipelines");
|
|
|
|
TestBuilder.command/* sh */ `
|
|
echo (echo)
|
|
`
|
|
.error("Unexpected token: `(`")
|
|
.runAsTest("Invalid subshell use");
|
|
|
|
describe("ported", () => {
|
|
// test_oE 'effect of subshell'
|
|
TestBuilder.command/* sh */ `
|
|
a=1
|
|
# (a=2; echo $a; exit; echo not reached)
|
|
# NOTE: We actually implemented exit wrong so changing this for now until we fix it
|
|
(a=2; echo $a; exit; echo reached)
|
|
echo $a
|
|
`
|
|
.stdout("2\nreached\n1\n")
|
|
.runAsTest("effect of subshell");
|
|
|
|
// test_x -e 23 'exit status of subshell'
|
|
TestBuilder.command/* sh */ `
|
|
(true; exit 23)
|
|
`
|
|
.exitCode(23)
|
|
.runAsTest("exit status of subshell");
|
|
|
|
// test_oE 'redirection on subshell'
|
|
TestBuilder.command/* sh */ `
|
|
(echo 1; echo 2; echo 3; echo 4) >sub_out
|
|
# (tail -n 2) <sub_out
|
|
cat sub_out
|
|
`
|
|
.error("Subshells with redirections are currently not supported. Please open a GitHub issue.")
|
|
// .stdout("1\n2\n3\n4\n")
|
|
.runAsTest("redirection on subshell");
|
|
|
|
// test_oE 'subshell ending with semicolon'
|
|
TestBuilder.command/* sh */ `
|
|
(echo foo;)
|
|
`
|
|
.stdout("foo\n")
|
|
.runAsTest("subshell ending with semicolon");
|
|
|
|
// test_oE 'subshell ending with asynchronous list'
|
|
TestBuilder.command/* sh */ `
|
|
mkfifo fifo1
|
|
(echo foo >fifo1&)
|
|
cat fifo1
|
|
`
|
|
.stdout("foo\n")
|
|
.todo("async commands not implemented yet")
|
|
.runAsTest("subshell ending with asynchronous list");
|
|
|
|
// test_oE 'newlines in subshell'
|
|
TestBuilder.command/* sh */ `
|
|
(
|
|
echo foo
|
|
)
|
|
`
|
|
.stdout("foo\n")
|
|
.runAsTest("newlines in subshell");
|
|
|
|
// test_oE 'effect of brace grouping'
|
|
TestBuilder.command/* sh */ `
|
|
a=1
|
|
{ a=2; echo $a; exit; echo not reached; }
|
|
echo $a
|
|
`
|
|
.stdout("2\n1\n")
|
|
.todo("brace grouping not implemented")
|
|
.runAsTest("effect of brace grouping");
|
|
|
|
// test_x -e 29 'exit status of brace grouping'
|
|
TestBuilder.command/* sh */ `
|
|
{ true; sh -c 'exit 29'; }
|
|
`
|
|
.exitCode(29)
|
|
.todo("brace grouping not implemented")
|
|
.runAsTest("exit status of brace grouping");
|
|
|
|
// test_oE 'redirection on brace grouping'
|
|
TestBuilder.command/* sh */ `
|
|
{ echo 1; echo 2; echo 3; echo 4; } >brace_out
|
|
{ tail -n 2; } <brace_out
|
|
`
|
|
.stdout("3\n4\n")
|
|
.todo("brace grouping not implemented")
|
|
.runAsTest("redirection on brace grouping");
|
|
|
|
// test_oE 'brace grouping ending with semicolon'
|
|
TestBuilder.command/* sh */ `
|
|
{ echo foo; }
|
|
`
|
|
.stdout("foo\n")
|
|
.todo("brace grouping not implemented")
|
|
.runAsTest("brace grouping ending with semicolon");
|
|
|
|
// test_oE 'brace grouping ending with asynchronous list'
|
|
TestBuilder.command/* sh */ `
|
|
mkfifo fifo1
|
|
{ echo foo >fifo1& }
|
|
cat fifo1
|
|
`
|
|
.stdout("foo\n")
|
|
.todo("brace grouping not implemented")
|
|
.runAsTest("brace grouping ending with asynchronous list");
|
|
|
|
// test_oE 'newlines in brace grouping'
|
|
TestBuilder.command/* sh */ `
|
|
{
|
|
echo foo
|
|
}
|
|
`
|
|
.stdout("foo\n")
|
|
.todo("brace grouping not implemented")
|
|
.runAsTest("newlines in brace grouping");
|
|
});
|
|
});
|
|
|
|
describe("when a command fails", () => {
|
|
let e: Bun.$.ShellError;
|
|
|
|
beforeAll(async () => {
|
|
$.throws(true);
|
|
try {
|
|
await Bun.$`false`;
|
|
} catch (err) {
|
|
e = err as Bun.$.ShellError;
|
|
} finally {
|
|
$.nothrow();
|
|
}
|
|
});
|
|
|
|
it("is an Error instance", () => expect(e).toBeInstanceOf(Error));
|
|
it("is a ShellError instance", () => expect(e).toBeInstanceOf(Bun.$.ShellError));
|
|
it("has a stdout buffer", () => expect(e.stdout).toBeInstanceOf(Uint8Array));
|
|
it("has a stderr buffer", () => expect(e.stderr).toBeInstanceOf(Uint8Array));
|
|
it("has an exit code of 1", () => expect(e.exitCode).toBe(1));
|
|
it("is named ShellError", () => expect(e.name).toBe("ShellError"));
|
|
});
|
|
|
|
describe("ShellError constructor", () => {
|
|
test.failing("new $.ShellError()", () => {
|
|
const e = new Bun.$.ShellError();
|
|
expect(e).toBeInstanceOf(Bun.$.ShellError);
|
|
expect(e).toBeInstanceOf(Error);
|
|
|
|
// TODO(@DonIsaac) fix constructor
|
|
expect(e.name).toBe("ShellError");
|
|
});
|
|
});
|
|
|
|
describe.todo("async", () => {
|
|
TestBuilder.command`echo hi && BUN_DEBUG_QUIET_LOGS=1 ${BUN} -e ${/* ts */ `await Bun.sleep(500); console.log('noice')`} &; echo hello`
|
|
.stdout("hi\nhello\nnoice\n")
|
|
.runAsTest("basic");
|
|
|
|
TestBuilder.command`BUN_DEBUG_QUIET_LOGS=1 ${BUN} -e ${/* ts */ `await Bun.sleep(500); console.log('noice')`} | cat &; echo hello`
|
|
.stdout("hello\nnoice\n")
|
|
.runAsTest("pipeline");
|
|
|
|
TestBuilder.command`echo start > output.txt & cat output.txt`
|
|
.file("output.txt", "hey")
|
|
.stdout(s => expect(s).toBeOneOf(["hey", "start\n"]))
|
|
.runAsTest("background_execution_with_output_redirection");
|
|
});
|
|
|
|
function stringifyBuffer(buffer: Uint8Array): string {
|
|
const sentinel = sentinelByte(buffer);
|
|
const str = new TextDecoder().decode(buffer.slice(0, sentinel));
|
|
return str;
|
|
}
|
|
|
|
function sentinelByte(buf: Uint8Array): number {
|
|
for (let i = 0; i < buf.byteLength; i++) {
|
|
if (buf[i] == 0) return i;
|
|
}
|
|
throw new Error("No sentinel byte");
|
|
}
|