Skip to content
Merged
Show file tree
Hide file tree
Changes from 3 commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
1 change: 0 additions & 1 deletion mordant-baseline.toml
Original file line number Diff line number Diff line change
Expand Up @@ -63,7 +63,6 @@
"same_match_twice:src/runtime/cli/update_interactive_command.rs" = 2
"same_match_twice:src/runtime/server/RequestContext.rs" = 4
"same_match_twice:src/runtime/server/server_body.rs" = 1
"same_match_twice:src/runtime/shell/builtin/cat.rs" = 1
"same_match_twice:src/runtime/webcore/Blob.rs" = 2
"same_match_twice:src/runtime/webcore/Body.rs" = 2
"unchecked_construction:src/runtime/api/js_bundle_completion_task.rs" = 1
Expand Down
22 changes: 12 additions & 10 deletions src/runtime/shell/builtin/cat.rs
Original file line number Diff line number Diff line change
Expand Up @@ -46,6 +46,16 @@ pub(crate) enum Step {
Next,
}

impl Step {
fn run(self, interp: &Interpreter, cmd: NodeId) -> Yield {
match self {
Step::Suspend => Yield::suspended(),
Step::Done(code) => Builtin::done(interp, cmd, code),
Step::Next => Cat::next(interp, cmd),
}
}
}

impl Cat {
pub(crate) fn start(interp: &Interpreter, cmd: NodeId) -> Yield {
let mut opts = Opts::default();
Expand Down Expand Up @@ -300,11 +310,7 @@ impl Cat {
CatState::WaitingWriteErr => Step::Done(1),
_ => panic!("Invalid state"),
};
match step {
Step::Suspend => Yield::suspended(),
Step::Done(code) => Builtin::done(interp, cmd, code),
Step::Next => Self::next(interp, cmd),
}
step.run(interp, cmd)
}

pub(crate) fn on_io_reader_chunk(
Expand Down Expand Up @@ -394,11 +400,7 @@ impl Cat {
fd.writer.cancel_chunks(wchild);
}
}
match step {
Step::Suspend => Yield::suspended(),
Step::Done(code) => Builtin::done(interp, cmd, code),
Step::Next => Self::next(interp, cmd),
}
step.run(interp, cmd)
}
}

Expand Down
46 changes: 46 additions & 0 deletions test/js/bun/shell/bunshell.test.ts
Original file line number Diff line number Diff line change
Expand Up @@ -528,6 +528,52 @@ describe("bunshell", () => {
expect(stdout.toString()).toEqual("LMAO\n");
});

// The builtin cat is only on by default on Windows; the flag turns it on
// everywhere. Captured output finishes the command from the reader side,
// output on a real fd also goes through the writer completions, and the
// missing-file case with stderr on a fd finishes from the writer side alone.
test("builtin cat finishes from its reader and writer completions", async () => {
using dir = tempDir("builtin-cat", {});
const script = /* ts */ `
import { $ } from "bun";
$.nothrow();
const results = {};
for (const [name, run] of Object.entries({
"captured": () => $\`echo hi | cat\`,
"stdout to fd": () => $\`echo hi | cat > out.txt\`,
"missing file, stderr captured": () => $\`cat missing.txt\`,
"missing file, stderr to fd": () => $\`cat missing.txt 2> err.txt\`,
})) {
const r = await run().quiet();
results[name] = { stdout: r.stdout.toString(), stderr: r.stderr.toString(), exitCode: r.exitCode };
}
results["out.txt"] = await Bun.file("out.txt").text();
results["err.txt"] = await Bun.file("err.txt").text();
console.log(JSON.stringify(results));
`;
await using proc = Bun.spawn({
cmd: [bunExe(), "-e", script],
env: { ...bunEnv, BUN_ENABLE_EXPERIMENTAL_SHELL_BUILTINS: "1" },
cwd: String(dir),
stdout: "pipe",
stderr: "pipe",
});
const [stdout, stderr, exitCode] = await Promise.all([proc.stdout.text(), proc.stderr.text(), proc.exited]);
expect(stderr).toBe("");
// On Windows the message carries the absolute path (shell_openat only
// re-tags the error with the argument as written on POSIX).
const missingFileError = expect.stringMatching(/^cat: (.*[\\/])?missing\.txt: No such file or directory\n$/);
expect(JSON.parse(stdout)).toEqual({
"captured": { stdout: "hi\n", stderr: "", exitCode: 0 },
"stdout to fd": { stdout: "", stderr: "", exitCode: 0 },
"missing file, stderr captured": { stdout: "", stderr: missingFileError, exitCode: 1 },
"missing file, stderr to fd": { stdout: "", stderr: "", exitCode: 1 },
"out.txt": "hi\n",
"err.txt": missingFileError,
});
expect(exitCode).toBe(0);
});

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("&&");
Expand Down
Loading