Revert "PIPE: Add pipe support for passing data into and out of terminal commands (#2395)" (#2524)

This reverts commit 92b8b58588.

Accidental merge on my part - the code is in decent shape, but isn't meant to go in for 3.0.
This commit is contained in:
David Walker
2026-02-22 11:28:10 -08:00
committed by GitHub
parent 92b8b58588
commit 8f4313b180
68 changed files with 479 additions and 2429 deletions

View File

@@ -14,7 +14,6 @@ import { WorkerScript } from "../../../src/Netscript/WorkerScript";
import { NetscriptFunctions } from "../../../src/NetscriptFunctions";
import type { PositiveInteger } from "../../../src/types";
import { ErrorState } from "../../../src/ErrorHandling/ErrorState";
import { getTerminalStdIO } from "../../../src/Terminal/StdIO/RedirectIO";
fixDoImportIssue();
@@ -37,7 +36,7 @@ async function expectErrorWhenRunningScript(
for (const script of scripts) {
Player.getHomeComputer().writeToScriptFile(script.filePath, script.code);
}
runScript(testScriptPath, [], Player.getHomeComputer(), getTerminalStdIO());
runScript(testScriptPath, [], Player.getHomeComputer());
const workerScript = workerScripts.get(1);
if (!workerScript) {
throw new Error(`Invalid worker script`);
@@ -146,7 +145,7 @@ describe("runScript and runScriptFromScript", () => {
ns.print(server.hostname);
}`,
);
runScript(testScriptPath, [], Player.getHomeComputer(), getTerminalStdIO());
runScript(testScriptPath, [], Player.getHomeComputer());
const workerScript = workerScripts.get(1);
if (!workerScript) {
throw new Error(`Invalid worker script`);
@@ -161,7 +160,7 @@ describe("runScript and runScriptFromScript", () => {
});
describe("Failure", () => {
test("Script does not exist", () => {
runScript(testScriptPath, [], Player.getHomeComputer(), getTerminalStdIO());
runScript(testScriptPath, [], Player.getHomeComputer());
expect((Terminal.outputHistory[1] as { text: string }).text).toContain(
`Script ${testScriptPath} does not exist on home`,
);
@@ -173,7 +172,7 @@ describe("runScript and runScriptFromScript", () => {
`export async function main(ns) {
}`,
);
runScript(testScriptPath, [], server, getTerminalStdIO());
runScript(testScriptPath, [], server);
expect((Terminal.outputHistory[1] as { text: string }).text).toContain(
`You do not have root access on ${server.hostname}`,
);
@@ -185,7 +184,7 @@ describe("runScript and runScriptFromScript", () => {
{
}`,
);
runScript(testScriptPath, [], Player.getHomeComputer(), getTerminalStdIO());
runScript(testScriptPath, [], Player.getHomeComputer());
expect((Terminal.outputHistory[1] as { text: string }).text).toContain(
`Cannot calculate RAM usage of ${testScriptPath}`,
);
@@ -197,7 +196,7 @@ describe("runScript and runScriptFromScript", () => {
ns.ramOverride(1024);
}`,
);
runScript(testScriptPath, [], Player.getHomeComputer(), getTerminalStdIO());
runScript(testScriptPath, [], Player.getHomeComputer());
expect((Terminal.outputHistory[1] as { text: string }).text).toContain("This script requires 1.02TB of RAM");
});
test("Thrown error in main function", async () => {

View File

@@ -59,12 +59,6 @@ function loadStandardServers() {
"ramUsage": 1.6,
"server": "home",
"scriptKey": "script.js*[]",
"stdin": null,
"tailStdOut": null,
"terminalStdOut": {
"stdin": null,
"stdout": null
},
"temporary": true,
"dependencies": [
{
@@ -91,12 +85,6 @@ function loadStandardServers() {
"ramUsage": 1.6,
"server": "home",
"scriptKey": "script.js*[]",
"stdin": null,
"tailStdOut": null,
"terminalStdOut": {
"stdin": null,
"stdout": null
},
"title": "Awesome Script",
"dependencies": [
{

View File

@@ -1,494 +0,0 @@
import { Terminal } from "../../../src/Terminal";
import { GetServer, prestigeAllServers } from "../../../src/Server/AllServers";
import { Player } from "@player";
import { type TextFilePath } from "../../../src/Paths/TextFilePath";
import { type ScriptFilePath } from "../../../src/Paths/ScriptFilePath";
import { LiteratureName, MessageFilename } from "@enums";
import { fixDoImportIssue, initGameEnvironment } from "../Utilities";
import { runScript } from "../../../src/Terminal/commands/runScript";
import { getTerminalStdIO } from "../../../src/Terminal/StdIO/RedirectIO";
const sleep = (ms: number) => new Promise((resolve) => setTimeout(resolve, ms));
fixDoImportIssue();
initGameEnvironment();
describe("Terminal Pipes", () => {
beforeEach(() => {
prestigeAllServers();
Player.init();
Terminal.outputHistory = [];
GetServer(Player.currentServer)?.textFiles.clear();
GetServer(Player.currentServer)?.scripts.clear();
});
describe("piping to files", () => {
it("should handle piping to a file", async () => {
const fileName = "output.txt";
const command = `echo 'Hello World' > ${fileName}`;
await Terminal.executeCommands(command);
const server = GetServer(Player.currentServer);
const fileContent = server?.textFiles?.get(fileName as TextFilePath)?.text;
expect(JSON.stringify(Terminal.outputHistory)).toBe("[]");
expect(fileContent).toBe("Hello World");
});
it("should reject invalid text filenames", async () => {
const invalidFileName = 'a".txt';
const command = `echo 'Hello World' > ${invalidFileName}`;
await Terminal.executeCommands(command);
const mostRecentOutput = Terminal.outputHistory[Terminal.outputHistory.length - 1];
expect(mostRecentOutput?.text).toBe(`Invalid file path provided: ${invalidFileName}`);
});
it("should reject invalid script filenames", async () => {
const invalidFileName = 'a".js';
const command = `echo 'Hello World' > ${invalidFileName}`;
await Terminal.executeCommands(command);
const mostRecentOutput = Terminal.outputHistory[Terminal.outputHistory.length - 1];
expect(mostRecentOutput?.text).toBe(`Invalid file path provided: ${invalidFileName}`);
});
it("should append to a file when using >> operator", async () => {
const fileName = "output.txt";
const commandString = `echo first line >> ${fileName}; echo second line >> ${fileName}`;
await Terminal.executeCommands(commandString);
const server = GetServer(Player.currentServer);
const fileContent = server?.textFiles?.get(fileName as TextFilePath)?.text;
expect(JSON.stringify(Terminal.outputHistory)).toBe("[]");
expect(fileContent).toBe("first line\nsecond line");
});
it("should overwrite a file when using > operator", async () => {
const fileName = "output.txt";
const commandString = `echo first line > ${fileName}; echo second line > ${fileName}`;
await Terminal.executeCommands(commandString);
const server = GetServer(Player.currentServer);
const fileContent = server?.textFiles?.get(fileName as TextFilePath)?.text;
expect(fileContent).toBe("second line");
});
it("should only overwrite file contents once per > pipe", async () => {
// Add file to server with content
const outputFileName = "scriptOutput9.txt" as TextFilePath;
const startingData = "startingData";
const commandString = `echo ${startingData} > ${outputFileName}`;
await Terminal.executeCommands(commandString);
const scriptName = "testScript.js" as ScriptFilePath;
const scriptContent = `export async function main(ns) { ns.tprint(ns.args); await ns.sleep(100); ns.tprint(ns.args); }`;
// Add script to server
await Terminal.executeCommands(`echo '${scriptContent}' > ${scriptName}`);
// Pass arguments to script via pipe
const command = `run ${scriptName} test1 > ${outputFileName}`;
await Terminal.executeCommands(command);
await sleep(200);
const server = GetServer(Player.currentServer);
const fileContent = server?.textFiles?.get(outputFileName)?.text;
expect(Terminal.outputHistory.length).toBe(1);
expect(fileContent).toContain(`${scriptName}: ["test1"]\n${scriptName}: ["test1"]`);
expect(fileContent).not.toContain(startingData);
});
it("should only overwrite file contents once per > pipe when arguments are piped in", async () => {
// Add file to server with content
const outputFileName = "scriptOutput8.txt" as TextFilePath;
const startingData = "startingData";
const commandString = `echo ${startingData} > ${outputFileName}`;
await Terminal.executeCommands(commandString);
const scriptName = "testScript.js" as ScriptFilePath;
const scriptContent = `export async function main(ns) { ns.tprint(ns.getStdin().read()); await ns.sleep(100); ns.tprint(ns.getStdin().read()); }`;
// Add script to server
await Terminal.executeCommands(`echo '${scriptContent}' > ${scriptName}`);
// Pass arguments to script via pipe
const command = `echo test1 test2 | ${scriptName} > ${outputFileName}`;
await Terminal.executeCommands(command);
await sleep(200);
const server = GetServer(Player.currentServer);
const fileContent = server?.textFiles?.get(outputFileName)?.text;
expect(Terminal.outputHistory.length).toBe(1);
expect(fileContent).toContain(`${scriptName}: test1 test2\n${scriptName}: null`);
expect(fileContent).not.toContain(startingData);
});
it("should not permit overwriting a script file with content", async () => {
const fileName = "output.js";
const commandString = `echo 'Hello World' > ${fileName}; echo 'Malicious Content' > ${fileName}`;
await Terminal.executeCommands(commandString);
const server = GetServer(Player.currentServer);
const fileContent = server?.scripts?.get(fileName as ScriptFilePath)?.content;
expect(fileContent).toContain("Hello World");
});
});
describe("piping multiple inputs", () => {
it("should handle multiple commands with distinct pipes", async () => {
const fileName1 = "output.txt";
const fileName2 = "output2.txt";
const commandString = `echo test > ${fileName1}; echo test2 > ${fileName2}`;
await Terminal.executeCommands(commandString);
expect(JSON.stringify(Terminal.outputHistory)).toBe("[]");
const server = GetServer(Player.currentServer);
const fileContent1 = server?.textFiles?.get(fileName1 as TextFilePath)?.text;
expect(fileContent1).toBe("test");
const fileContent2 = server?.textFiles?.get(fileName2 as TextFilePath)?.text;
expect(fileContent2).toBe("test2");
});
it("passes all piped inputs to the output command", async () => {
await Terminal.executeCommands("echo 1337 > file1.txt");
const command = "cat file1.txt > file2.txt";
await Terminal.executeCommands(command);
await sleep(100);
const server = GetServer(Player.currentServer);
const fileContent = server?.textFiles?.get("file2.txt" as TextFilePath)?.text;
expect(fileContent).toBe("1337");
});
});
describe("cat and echo with pipes", () => {
it("should pipe cat file contents to specified output", async () => {
const fileName = "test4.txt";
const fileContent = "This is a test file.";
await Terminal.executeCommands(`echo '${fileContent}' > ${fileName}`);
await Terminal.executeCommands(`cat '${fileName}' | cat`);
const server = GetServer(Player.currentServer);
const newFileContent = server?.textFiles?.get(fileName as TextFilePath)?.text;
expect(newFileContent).toBe(fileContent);
const lastOutput = Terminal.outputHistory[Terminal.outputHistory.length - 1];
expect(lastOutput.text).toContain(fileContent);
});
it("should pipe cat .lit file contents to specified output", async () => {
const fileName = "test.txt";
const server = GetServer(Player.currentServer);
server?.messages.push(LiteratureName.HackersStartingHandbook);
await Terminal.executeCommands(`cat ${LiteratureName.HackersStartingHandbook} > ${fileName}`);
await Terminal.executeCommands(`cat ${fileName} | cat `);
const newFileContent = server?.textFiles?.get(fileName as TextFilePath)?.text;
expect(newFileContent).toContain("hacking is the most profitable way to earn money and progress");
const lastOutput = Terminal.outputHistory[Terminal.outputHistory.length - 1];
expect(lastOutput.text).toContain("hacking is the most profitable way to earn money and progress");
});
it("should pipe cat message file contents to specified output", async () => {
const fileName = "test3.txt";
const server = GetServer(Player.currentServer);
server?.messages.push(MessageFilename.TruthGazer);
await Terminal.executeCommands(`cat ${MessageFilename.TruthGazer} > ${fileName}`);
await Terminal.executeCommands(`cat ${fileName} | cat `);
const newFileContent = server?.textFiles?.get(fileName as TextFilePath)?.text;
expect(newFileContent).toContain("__ESCAP3__");
const lastOutput = Terminal.outputHistory[Terminal.outputHistory.length - 1];
expect(lastOutput.text).toContain("__ESCAP3__");
});
});
describe("piping to and from scripts", () => {
it("should handle piping to a script file, and passing arguments into a script to run", async () => {
const scriptName = "testScript2.js" as ScriptFilePath;
const scriptContent = `export function main(ns) { ns.tprint('Input received: ', ns.getStdin().peek()); }`;
// Add script to server
await Terminal.executeCommands(`echo "${scriptContent}" > ${scriptName}`);
const content = GetServer(Player.currentServer)?.scripts.get(scriptName)?.content;
expect(content).toBe(scriptContent);
// Pass arguments to script via pipe
const command = `echo 'data' | run ${scriptName}`;
await Terminal.executeCommands(command);
await sleep(100);
expect(Terminal.outputHistory[0]?.text).toContain(`Running script with 1 thread`);
expect(Terminal.outputHistory[1]?.text).toEqual(`${scriptName}: Input received: data`);
});
it("should piping content out of a script", async () => {
const outputFileName = "scriptOutput4.txt" as TextFilePath;
const scriptName = "testScript.js" as ScriptFilePath;
const scriptContent = `export async function main(ns) { ns.tprint('Input received: ', ns.getStdin().peek()); }`;
// Add script to server
await Terminal.executeCommands(`echo "${scriptContent}" > ${scriptName}`);
const content = GetServer(Player.currentServer)?.scripts.get(scriptName)?.content;
expect(content).toBe(scriptContent);
// Pass arguments to script via pipe
const command = `echo 'data' | ${scriptName} > ${outputFileName}`;
await Terminal.executeCommands(command);
await sleep(200);
const server = GetServer(Player.currentServer);
const fileContent = server?.textFiles?.get(outputFileName)?.text;
expect(Terminal.outputHistory.length).toBe(1);
expect(fileContent).toContain(`Input received: data`);
});
it("should pipe content out of a script when the run command is used", async () => {
const outputFileName = "scriptOutput3.txt" as TextFilePath;
const scriptName = "testScript.js" as ScriptFilePath;
const scriptContent = `export function main(ns) { ns.tprint('Args received: ', ns.args); }`;
// Add script to server
await Terminal.executeCommands(`echo "${scriptContent}" > ${scriptName}`);
const content = GetServer(Player.currentServer)?.scripts.get(scriptName)?.content;
expect(content).toBe(scriptContent);
// Pass arguments to script via pipe
const command = `run ${scriptName} test1 arguments > ${outputFileName}`;
await Terminal.executeCommands(command);
await sleep(200);
const server = GetServer(Player.currentServer);
const fileContent = server?.textFiles?.get(outputFileName)?.text;
expect(Terminal.outputHistory.length).toBe(1);
expect(fileContent).toContain(`Args received: ["test1","arguments"]`);
});
it("should correctly pipe each script's async output to its specified location", async () => {
// Add file to server with content
const outputFileName = "scriptOutput.txt" as TextFilePath;
const outputFileName2 = "scriptOutput2.txt" as TextFilePath;
const scriptName = "testScript.js" as ScriptFilePath;
const scriptContent = `export async function main(ns) { ns.tprint(ns.args); await ns.sleep(100); ns.tprint(ns.args); }`;
// Add script to server
await Terminal.executeCommands(`echo '${scriptContent}' > ${scriptName}`);
// Pass arguments to script via pipe
const command = `run ${scriptName} test1 test2 > ${outputFileName}; run ${scriptName} test3 test4 > ${outputFileName2}`;
await Terminal.executeCommands(command);
await sleep(300);
const server = GetServer(Player.currentServer);
const fileContent = server?.textFiles?.get(outputFileName)?.text;
const fileContent2 = server?.textFiles?.get(outputFileName2)?.text;
expect(Terminal.outputHistory.length).toBe(2);
expect(fileContent).toContain(`${scriptName}: ["test1","test2"]\n${scriptName}: ["test1","test2"]`);
expect(fileContent2).toContain(`${scriptName}: ["test3","test4"]\n${scriptName}: ["test3","test4"]`);
});
it("should correctly pipe a script's async output to a specified destination script", async () => {
// Add file to server with content
const outputFileName = "scriptOutput.txt" as TextFilePath;
const scriptName = "testScript.js" as ScriptFilePath;
const scriptName2 = "testScript2.js" as ScriptFilePath;
const scriptContent = `export async function main(ns) { ns.tprint(ns.getStdin().peek()); await ns.sleep(80); ns.tprint(ns.getStdin().peek()); }`;
const scriptContent2 = `export async function main(ns) { ns.tprint(ns.getStdin().read()); await ns.sleep(200); ns.tprint(ns.getStdin().read()); ns.tprint(ns.getStdin().read()); }`;
// Add script to server
await Terminal.executeCommands(
`echo '${scriptContent}' > ${scriptName}; echo '${scriptContent2}' > ${scriptName2}`,
);
// Pass arguments to script via pipe
const command = `echo 1 | ${scriptName} | ${scriptName2} > ${outputFileName}`;
await Terminal.executeCommands(command);
await sleep(300);
const server = GetServer(Player.currentServer);
const fileContent = server?.textFiles?.get(outputFileName)?.text;
expect(Terminal.outputHistory.length).toBe(2);
expect(fileContent).toContain(`${scriptName2}: ${scriptName}: 1\n${scriptName2}: NULL PORT DATA`);
});
it("should correctly pipe each script's async output to its specified destination script", async () => {
// Add file to server with content
const outputFileName = "scriptOutput.txt" as TextFilePath;
const outputFileName2 = "scriptOutput2.txt" as TextFilePath;
const scriptName = "testScript.js" as ScriptFilePath;
const scriptName2 = "testScript2.js" as ScriptFilePath;
const scriptName3 = "testScript3.js" as ScriptFilePath;
const scriptName4 = "testScript4.js" as ScriptFilePath;
const scriptContent = `export async function main(ns) { ns.tprint(ns.getStdin().peek()); await ns.sleep(80); ns.tprint(ns.getStdin().peek()); }`;
const scriptContent2 = `export async function main(ns) { ns.tprint(ns.getStdin().read()); await ns.sleep(200); ns.tprint(ns.getStdin().read()); ns.tprint(ns.getStdin().read()); }`;
// Add script to server
await Terminal.executeCommands(
`echo '${scriptContent}' > ${scriptName}; echo '${scriptContent2}' > ${scriptName2}`,
);
await Terminal.executeCommands(`cat ${scriptName} > ${scriptName3}; cat ${scriptName2} > ${scriptName4};`);
// Pass arguments to script via pipe
const command = `echo 1 | ${scriptName} | ${scriptName2} > ${outputFileName}; echo 2 | ${scriptName3} | ${scriptName4} > ${outputFileName2}`;
await Terminal.executeCommands(command);
await sleep(300);
const server = GetServer(Player.currentServer);
const fileContent = server?.textFiles?.get(outputFileName)?.text;
const fileContent2 = server?.textFiles?.get(outputFileName2)?.text;
expect(Terminal.outputHistory.length).toBe(4);
expect(fileContent).toContain(`${scriptName2}: ${scriptName}: 1\n${scriptName2}: NULL PORT DATA`);
expect(fileContent2).toContain(`${scriptName4}: ${scriptName3}: 2\n${scriptName4}: NULL PORT DATA`);
});
});
describe("input redirection", () => {
it("should use file contents as input stream if input redirection < is used", async () => {
const fileContent = "File input data";
const fileName = "inputFile.txt";
await Terminal.executeCommands(`echo '${fileContent}' > ${fileName}`);
const fileContentOnServer = GetServer(Player.currentServer)?.textFiles?.get(fileName as TextFilePath)?.text;
expect(fileContentOnServer).toBe(fileContent);
const commandString = `cat < ${fileName} | cat `;
await Terminal.executeCommands(commandString);
const lastOutput = Terminal.outputHistory[Terminal.outputHistory.length - 1];
expect(lastOutput?.text).toBe(fileContent);
});
it("should return an error if input redirection file does not exist", async () => {
const fileName = "nonExistentFile.txt";
const commandString = `cat < ${fileName}`;
await Terminal.executeCommands(commandString);
const lastOutput = Terminal.outputHistory[Terminal.outputHistory.length - 2];
expect(lastOutput?.text).toBe(`No file at path ${fileName}`);
});
it("should return an error if the input redirection is not the first pipe in the chain", async () => {
await Terminal.executeCommands(`echo 'Some data' | cat < inputFile.txt`);
const error = Terminal.outputHistory[0];
expect(error?.text).toBe(
`Error in pipe command: Invalid pipe command. Only the first command in a pipe chain can have input redirection '<'.`,
);
});
});
it("should handle piping content to cat", async () => {
const testContent = "This is a test.";
const commandString = `echo "${testContent}" | cat`;
await Terminal.executeCommands(commandString);
await sleep(50);
expect(Terminal.outputHistory.length).toBe(1);
expect(Terminal.outputHistory[0].text).toContain(testContent);
});
it("should replace $! with the PID of the last script run", async () => {
const scriptName = "testScript.js" as ScriptFilePath;
const scriptContent = `export async function main(ns) { ns.print('Script is running'); await ns.sleep(100); }`;
const server = GetServer(Player.currentServer);
// Add script to server
await Terminal.executeCommands(`echo "${scriptContent}" > ${scriptName}`);
await sleep(50);
// Run the script to set PipeState.pidOfLastScriptRun
const runningScript = runScript(scriptName, [], server, getTerminalStdIO());
const expectedPid = runningScript?.pid;
await sleep(200);
const command = `echo $! > pidOutput.txt`;
await Terminal.executeCommands(command);
await sleep(50);
const fileContent = server?.textFiles?.get("pidOutput.txt" as TextFilePath)?.text;
expect(Number(fileContent)).toBe(expectedPid);
});
it("should replace $! with -1 if the prior command was not a run", async () => {
const scriptName = "testScript.js" as ScriptFilePath;
const scriptContent = `export async function main(ns) { ns.print("Script is running"); await ns.sleep(100); }`;
const server = GetServer(Player.currentServer);
// Add script to server
await Terminal.executeCommands(`echo '${scriptContent}' > ${scriptName}`);
await sleep(50);
// Run the script to set PipeState.pidOfLastScriptRun
await Terminal.executeCommands(`run ${scriptName}`);
await sleep(200);
await Terminal.executeCommands(`echo "Not a run command"`);
const command = `echo $! > pidOutput.txt`;
await Terminal.executeCommands(command);
const fileContent = server?.textFiles?.get("pidOutput.txt" as TextFilePath)?.text;
expect(Number(fileContent)).toBe(-1);
});
it("should pipe the tail output of scripts to stdout when specified with $!", async () => {
const scriptContent = `export async function main(ns) {ns.print('foo');await ns.sleep(50);ns.print('test2');}`;
const scriptName = "testScript.jsx" as ScriptFilePath;
const tailOutputFileName = "tailOutput.txt" as TextFilePath;
// Add script to server
await Terminal.executeCommands(`echo "${scriptContent}" > ${scriptName}`);
const fileContent = GetServer(Player.currentServer)?.scripts?.get(scriptName)?.content;
expect(fileContent).toBe(scriptContent);
await Terminal.executeCommands(`run ${scriptName}; tail $! > ${tailOutputFileName}`);
await sleep(200);
const outputFileContent = GetServer(Player.currentServer)?.textFiles?.get(tailOutputFileName)?.text;
expect(outputFileContent).toContain("foo\nsleep: Sleeping for 0.050 seconds.\ntest2");
});
it("should pipe the tail output of scripts to stdout", async () => {
const scriptContent = `export async function main(ns) {ns.print('foo');await ns.sleep(50);ns.print('test2');}`;
const scriptName = "testScript.jsx" as ScriptFilePath;
const tailOutputFileName = "tailOutput.txt" as TextFilePath;
// Add script to server
await Terminal.executeCommands(`echo "${scriptContent}" > ${scriptName}`);
const fileContent = GetServer(Player.currentServer)?.scripts?.get(scriptName)?.content;
expect(fileContent).toBe(scriptContent);
await Terminal.executeCommands(`run ${scriptName}; tail ${scriptName} > ${tailOutputFileName}`);
await sleep(200);
const outputFileContent = GetServer(Player.currentServer)?.textFiles?.get(tailOutputFileName)?.text;
expect(outputFileContent).toContain("foo\nsleep: Sleeping for 0.050 seconds.\ntest2");
});
});

View File

@@ -1,207 +0,0 @@
import { IOStream } from "../../../src/Terminal/StdIO/IOStream";
import {
findCommandsSplitByRedirects,
getTerminalStdIO,
handleCommand,
parseRedirectedCommands,
} from "../../../src/Terminal/StdIO/RedirectIO";
import { Terminal } from "../../../src/Terminal";
import { fixDoImportIssue, initGameEnvironment } from "../Utilities";
import { GetServer, prestigeAllServers } from "../../../src/Server/AllServers";
import { Player } from "@player";
import { StdIO } from "../../../src/Terminal/StdIO/StdIO";
import { TextFilePath } from "../../../src/Paths/TextFilePath";
import { ScriptFilePath } from "../../../src/Paths/ScriptFilePath";
import { Output } from "../../../src/Terminal/OutputTypes";
import { ANSI_ESCAPE } from "../../../src/ui/React/ANSIITypography";
export const sleep = (ms: number) => new Promise((resolve) => setTimeout(resolve, ms));
fixDoImportIssue();
initGameEnvironment();
describe("RedirectIOTests", () => {
beforeEach(() => {
prestigeAllServers();
Player.init();
Terminal.outputHistory = [];
GetServer(Player.currentServer)?.textFiles.clear();
GetServer(Player.currentServer)?.scripts.clear();
});
it("should redirect output to the terminal correctly from a terminal StdIO", async () => {
const data = "Hello, Terminal!";
const terminalIO = getTerminalStdIO(null);
terminalIO.write(data);
await sleep(50);
expect(Terminal.outputHistory.length).toBe(1);
expect(Terminal.outputHistory[0].text).toContain(data);
});
it("findCommandsSplitByRedirects should split commands by pipes", () => {
const commandString = "echo Hello > file.txt >> anotherFile.txt | echo World";
const parsedCommands = commandString.split(" ");
const result = findCommandsSplitByRedirects(parsedCommands);
expect(result[0]).toEqual(["echo", "Hello"]);
expect(result[1]).toEqual([">", "file.txt"]);
expect(result[2]).toEqual([">>", "anotherFile.txt"]);
expect(result[3]).toEqual(["|", "echo", "World"]);
expect(result.length).toBe(4);
});
describe("handleCommand", () => {
it("should handle echo command passing its args to stdout", async () => {
const commandString = "echo Hello, World";
const stdIO = new StdIO(null);
handleCommand(stdIO, commandString.split(" "));
await sleep(50);
expect(stdIO.stdout.empty()).toBe(false);
const output = stdIO.stdout.read();
expect(output).toBe("Hello, World");
});
it("should handle writing stdin contents to files", async () => {
const filename = "output.txt";
const commandString = `> ${filename}`;
const stdin = new IOStream();
const stdIO = new StdIO(stdin);
void handleCommand(stdIO, commandString.split(" "));
stdin.write("File content line 1");
stdin.write("File content line 2");
await sleep(50);
const server = GetServer(Player.currentServer);
const file = server?.textFiles.get(filename as TextFilePath);
expect(file).toBeDefined();
expect(file?.content).toBe("File content line 1\nFile content line 2");
});
});
describe("parseRedirectedCommands", () => {
it("should append echo output redirected to a file", async () => {
const filename = "appendOutput.txt";
const commandString = `echo First Line >> ${filename} | echo Second Line >> ${filename}`;
await parseRedirectedCommands(commandString);
const server = GetServer(Player.currentServer);
const file = server?.textFiles.get(filename as TextFilePath);
expect(file).toBeDefined();
expect(file?.content).toBe("First Line\nSecond Line");
});
it("should prevent overwriting non-empty script files", async () => {
const filename = "scriptOutput.js";
const commandString = `echo Hello > ${filename} | echo World > ${filename}`;
await parseRedirectedCommands(commandString);
const server = GetServer(Player.currentServer);
const file = server?.scripts.get(filename as ScriptFilePath);
expect(file).toBeDefined();
expect(file?.content).toBe("Hello");
});
});
describe("stdout from scripts", () => {
it("should redirect tprint output from a running script to a file", async () => {
const scriptName = "testScript.js";
const filename = "scriptLog.txt";
const scriptContent = `export function main(ns) { ns.tprint('Logging to file' ); }`;
await Terminal.executeCommands(`echo "${scriptContent}" > ${scriptName}`);
const currentScripts = GetServer(Player.currentServer)?.scripts;
const script = currentScripts?.get(scriptName as ScriptFilePath);
expect(script?.content).toBe(scriptContent);
await Terminal.executeCommands(`run ${scriptName} >> ${filename}`);
await sleep(50);
const server = GetServer(Player.currentServer);
const file = server?.textFiles.get(filename as TextFilePath);
expect(file?.content).toBe("testScript.js: Logging to file");
});
});
describe("stdin to scripts", () => {
it("should provide stdin input to a running script", async () => {
const scriptName = "inputScript.js";
const scriptContent = `export async function main(ns) {
const stdIn = await ns.getStdin();
if (stdIn?.empty()) {
ns.tprint('No input received yet');
await stdIn.nextWrite();
}
const input = stdIn?.read();
ns.tprint('Received input: ' + input);
}`;
await Terminal.executeCommands(`echo "${scriptContent}" > ${scriptName}`);
const inputData = "Hello from stdin!";
await Terminal.executeCommands(`echo "${inputData}" | run ${scriptName}`);
await sleep(50);
console.log(Terminal.outputHistory);
const outputLog: Output[] = Terminal.outputHistory.filter(isOutput);
const outputText: Output = outputLog.find((entry: Output) => entry.text?.includes("Received input:"));
expect(outputText?.text).toEqual(`${scriptName}: Received input: ${inputData}`);
});
it("should provide stdin input from a script to a running script", async () => {
const scriptName = "inputScript.js";
const scriptContent = `export async function main(ns) {
const stdIn = await ns.getStdin();
if (stdIn?.empty()) {
ns.tprint('No input received yet');
await stdIn.nextWrite();
}
const input = stdIn?.read();
ns.tprint('Received input: ' + input);
}`;
await Terminal.executeCommands(`echo "${scriptContent}" > ${scriptName}`);
const inputData = "Hello from stdin!";
await Terminal.executeCommands(`echo "${inputData}" | ${scriptName} | run ${scriptName}`);
await sleep(50);
console.log(Terminal.outputHistory);
const outputLog: Output[] = Terminal.outputHistory.filter(isOutput);
const outputText: Output = outputLog.find((entry: Output) => entry.text?.includes("Received input:"));
expect(outputText?.text).toEqual(`${scriptName}: Received input: ${scriptName}: Received input: ${inputData}`);
});
});
describe("cat and grep with redirected IO", () => {
it("should be able to read files to the terminal", async () => {
const filename = "appendOutput.txt";
const setupCommandString = `echo First Line >> ${filename} | echo Second Line >> ${filename}`;
await parseRedirectedCommands(setupCommandString);
await parseRedirectedCommands(`echo 1 | cat ${filename}`);
expect(Terminal.outputHistory.length).toBe(1);
expect(Terminal.outputHistory[0].text).toBe("First Line\nSecond Line1");
});
it("should be able to grep files read by cat", async () => {
const filename = "appendOutput.txt";
const setupCommandString = `echo First Line >> ${filename} | echo Second Line >> ${filename}`;
await parseRedirectedCommands(setupCommandString);
await parseRedirectedCommands(`cat ${filename} | grep Second`);
expect(Terminal.outputHistory.length).toBe(1);
const log = Terminal.outputHistory[0];
if (!isOutput(log)) throw new Error("Expected output to be of type Output");
expect(log.text.replaceAll(ANSI_ESCAPE, "")).toBe("Second Line");
});
});
});
function isOutput(entry: unknown): entry is Output {
return !!entry && typeof entry === "object" && entry instanceof Output;
}

View File

@@ -1,142 +0,0 @@
import { cat } from "../../../src/Terminal/commands/cat";
import { GetServerOrThrow, prestigeAllServers } from "../../../src/Server/AllServers";
import { Player } from "@player";
import { Terminal } from "../../../src/Terminal";
import { StdIO } from "../../../src/Terminal/StdIO/StdIO";
import { IOStream } from "../../../src/Terminal/StdIO/IOStream";
import { TextFile } from "../../../src/TextFile";
import { TextFilePath } from "../../../src/Paths/TextFilePath";
import { LiteratureName, MessageFilename } from "@enums";
import { Literatures } from "../../../src/Literature/Literatures";
import { stringifyReactElement } from "../../../src/Terminal/StdIO/utils";
import { Messages } from "../../../src/Message/MessageHelpers";
const fileName = "example.txt" as TextFilePath;
const fileName2 = "example2.txt" as TextFilePath;
const fileContent1 = "This is an example text file.";
const fileContent2 = "This is another example text file.";
describe("cat command", () => {
beforeEach(() => {
prestigeAllServers();
Player.init();
Terminal.outputHistory = [];
const server = GetServerOrThrow(Player.currentServer);
server.textFiles.clear();
server.scripts.clear();
server.messages.length = 0; //Remove .lit and .msg files
server.messages.push(LiteratureName.HackersStartingHandbook);
server.messages.push(MessageFilename.Jumper0);
const file = new TextFile(fileName, fileContent1);
server.textFiles.set(fileName, file);
const file2 = new TextFile(fileName2, fileContent2);
server.textFiles.set(fileName2, file2);
});
it("should retrieve file contents and pass to stdout", () => {
const server = GetServerOrThrow(Player.currentServer);
const stdOut = new IOStream();
const stdIO = new StdIO(null, stdOut);
cat([fileName, fileName2], server, stdIO);
const output = stdOut.read();
expect(output).toBe(`${fileContent1}${fileContent2}`);
});
it("should read from stdin when '-' is provided as an argument", () => {
const server = GetServerOrThrow(Player.currentServer);
const stdinStuff = "\nInput from stdin line 1";
const stdIn = new IOStream();
stdIn.write(stdinStuff);
const stdOut = new IOStream();
const stdIO = new StdIO(stdIn, stdOut);
cat([fileName, "-", fileName2], server, stdIO);
const output = stdOut.read();
expect(output).toBe(`${fileContent1}${stdinStuff}${fileContent2}`);
});
it("should read from stdin and concat it last when '-' is not provided as an argument", () => {
const server = GetServerOrThrow(Player.currentServer);
const stdIn = new IOStream();
stdIn.write("Input from stdin line 1");
const stdOut = new IOStream();
const stdIO = new StdIO(stdIn, stdOut);
cat([fileName, fileName2], server, stdIO);
const output = stdOut.read();
expect(output).toBe(`${fileContent1}${fileContent2}Input from stdin line 1`);
});
it("should be able to read .lit files", () => {
const server = GetServerOrThrow(Player.currentServer);
const stdOut = new IOStream();
const stdIO = new StdIO(null, stdOut);
cat([`${LiteratureName.HackersStartingHandbook}`], server, stdIO);
const output = stdOut.read();
const bodyText = stringifyReactElement(Literatures[LiteratureName.HackersStartingHandbook].text);
const expectedOutput = `${Literatures[LiteratureName.HackersStartingHandbook].title}\n\n${bodyText}\n`;
expect(output).toBe(expectedOutput);
expect(output).toContain("When starting out, hacking is the most profitable way to earn money and progress.");
});
it("should be able to read msg files", () => {
const server = GetServerOrThrow(Player.currentServer);
const stdOut = new IOStream();
const stdIO = new StdIO(null, stdOut);
cat([`${MessageFilename.Jumper0}`], server, stdIO);
const output = stdOut.read();
const text = Messages[MessageFilename.Jumper0].msg + "\n";
expect(output).toBe(text);
});
it("should be able to concatenate lit and msg files", () => {
const server = GetServerOrThrow(Player.currentServer);
const stdOut = new IOStream();
const stdIO = new StdIO(null, stdOut);
cat([`${LiteratureName.HackersStartingHandbook}`, `${MessageFilename.Jumper0}`], server, stdIO);
const output = stdOut.read();
const bodyText = stringifyReactElement(Literatures[LiteratureName.HackersStartingHandbook].text);
const expectedLitOutput = `${Literatures[LiteratureName.HackersStartingHandbook].title}\n\n${bodyText}\n`;
const expectedMsgOutput = Messages[MessageFilename.Jumper0].msg + "\n";
const expectedOutput = `${expectedLitOutput}${expectedMsgOutput}`;
expect(output).toBe(expectedOutput);
});
it("should be able to concatenate lit and msg files with stdin", () => {
const server = GetServerOrThrow(Player.currentServer);
const stdIn = new IOStream();
stdIn.write("Input from stdin line 1");
const stdOut = new IOStream();
const stdIO = new StdIO(stdIn, stdOut);
cat([`${LiteratureName.HackersStartingHandbook}`, "-", `${MessageFilename.Jumper0}`], server, stdIO);
const output = stdOut.read();
const bodyText = stringifyReactElement(Literatures[LiteratureName.HackersStartingHandbook].text);
const expectedLitOutput = `${Literatures[LiteratureName.HackersStartingHandbook].title}\n\n${bodyText}\n`;
const expectedMsgOutput = Messages[MessageFilename.Jumper0].msg + "\n";
const expectedOutput = `${expectedLitOutput}Input from stdin line 1${expectedMsgOutput}`;
expect(output).toBe(expectedOutput);
});
});

View File

@@ -1,85 +0,0 @@
import { GetServerOrThrow, prestigeAllServers } from "../../../src/Server/AllServers";
import { Player } from "@player";
import { Terminal } from "../../../src/Terminal";
import { StdIO } from "../../../src/Terminal/StdIO/StdIO";
import { IOStream } from "../../../src/Terminal/StdIO/IOStream";
import { TextFile } from "../../../src/TextFile";
import { TextFilePath } from "../../../src/Paths/TextFilePath";
import { grep } from "../../../src/Terminal/commands/grep";
import { ScriptFilePath } from "../../../src/Paths/ScriptFilePath";
import { Script } from "../../../src/Script/Script";
import { stringify } from "../../../src/Terminal/StdIO/utils";
const fileName = "example.txt" as TextFilePath;
const fileName2 = "example2.txt" as TextFilePath;
const fileContent1 = "This is an example text file.\nThis is line 2 of file 1";
const fileContent2 = "This is another example text file.\nThis is line 2 of file 2";
describe("grep command", () => {
beforeEach(() => {
prestigeAllServers();
Player.init();
Terminal.outputHistory = [];
const server = GetServerOrThrow(Player.currentServer);
server.textFiles.clear();
server.scripts.clear();
const file = new TextFile(fileName, fileContent1);
server.textFiles.set(fileName, file);
const file2 = new TextFile(fileName2, fileContent2);
server.textFiles.set(fileName2, file2);
});
it("should retrieve lines matching the pattern from the specified text file", () => {
const server = GetServerOrThrow(Player.currentServer);
const stdOut = new IOStream();
const stdIO = new StdIO(null, stdOut);
grep(["line 2", fileName], server, stdIO);
const output = stdOut.read();
expect(Terminal.outputHistory).toEqual([]);
expect(output).toBe(`example.txt:This is line 2 of file 1`);
});
it("should retrieve lines matching the pattern from the specified script file", () => {
const server = GetServerOrThrow(Player.currentServer);
const scriptFileName = "script.js" as ScriptFilePath;
const scriptContent = "console.log('Hello World');\n// This is line 2 of the script";
const scriptFile = new Script(scriptFileName, scriptContent, server.hostname);
server.scripts.set(scriptFileName, scriptFile);
const stdOut = new IOStream();
const stdIO = new StdIO(null, stdOut);
grep(["line 2", scriptFileName], server, stdIO);
const output = stdOut.read();
expect(Terminal.outputHistory).toEqual([]);
expect(output).toBe(`script.js:// This is line 2 of the script`);
});
it("should retrieve lines matching the pattern from stdin", () => {
const server = GetServerOrThrow(Player.currentServer);
const stdIn = new IOStream();
stdIn.write("First line from stdin\nThis is line 2 from stdin\nThird line from stdin");
stdIn.close();
const stdOut = new IOStream();
const stdIO = new StdIO(stdIn, stdOut);
grep(["line 2"], server, stdIO);
const output = stdOut.read();
expect(Terminal.outputHistory).toEqual([]);
expect(output).toBe(`This is line 2 from stdin`);
});
it("should grep input piped from cat", async () => {
await Terminal.executeCommands(`cat ${fileName} ${fileName2} | grep "line 2"`);
const lastOutput = Terminal.outputHistory[Terminal.outputHistory.length - 1];
// Output from cat will not have filenames, and will not add additional newlines between file contents
expect(stringify(lastOutput.text, true)).toBe(
`This is line 2 of file 1This is another example text file.\nThis is line 2 of file 2`,
);
});
});

View File

@@ -82,12 +82,6 @@ exports[`load/saveAllServers 1`] = `
"ramUsage": 1.6,
"server": "home",
"scriptKey": "script.js*[]",
"stdin": null,
"tailStdOut": null,
"terminalStdOut": {
"stdin": null,
"stdout": null
},
"title": "Awesome Script",
"threads": 1,
"temporary": false