Merge pull request 'Introduce "binary" scripts for all native commands' (#116) from binaries into master
This commit is contained in:
commit
4ea6c2ae88
|
@ -1,6 +1,6 @@
|
|||
{
|
||||
"name": "fwdekker.com",
|
||||
"version": "0.28.4",
|
||||
"version": "0.29.0",
|
||||
"description": "The source code of [my personal website](https://fwdekker.com/).",
|
||||
"author": "Felix W. Dekker",
|
||||
"repository": {
|
||||
|
|
File diff suppressed because it is too large
Load Diff
|
@ -1,3 +1,4 @@
|
|||
import {commandBinaries} from "./Commands";
|
||||
import {emptyFunction, getFileExtension, IllegalArgumentError} from "./Shared";
|
||||
import {Stream} from "./Stream";
|
||||
|
||||
|
@ -21,6 +22,11 @@ export class FileSystem {
|
|||
if (root === undefined)
|
||||
this.root =
|
||||
new Directory({
|
||||
"bin": Object.keys(commandBinaries)
|
||||
.reduce((acc, key) => {
|
||||
acc.add(key, new File(commandBinaries[key]));
|
||||
return acc;
|
||||
}, new Directory()),
|
||||
"dev": new Directory({
|
||||
"null": new NullFile()
|
||||
}),
|
||||
|
@ -170,9 +176,6 @@ export class FileSystem {
|
|||
* @throws if the target or its parent does not exist, or if the target is not a file
|
||||
*/
|
||||
open(target: Path, mode: FileMode): FileStream {
|
||||
if (!this.has(target.parent))
|
||||
throw new IllegalArgumentError(`Directory '${target.parent}' does not exist.`);
|
||||
|
||||
if (!this.has(target)) {
|
||||
if (mode === "append" || mode === "write")
|
||||
this.add(target, new File(), false);
|
||||
|
@ -205,6 +208,40 @@ export class FileSystem {
|
|||
|
||||
parent.remove(target.fileName);
|
||||
}
|
||||
|
||||
|
||||
/**
|
||||
* Determines the path of the given sources if they are moved into the given destination.
|
||||
*
|
||||
* @param sources the paths to the source files that are to be moved relative to the destination path
|
||||
* @param destination the path into which the sources should be moved
|
||||
* @return a mapping from the given source paths to the new destination paths
|
||||
*/
|
||||
determineMoveMappings(sources: Path[], destination: Path): [Path, Path][] {
|
||||
let mappings: [Path, Path][];
|
||||
if (this.has(destination)) {
|
||||
// Move into directory
|
||||
if (!(this.get(destination) instanceof Directory)) {
|
||||
if (sources.length === 1)
|
||||
throw new IllegalArgumentError(`'${destination}' already exists.`);
|
||||
else
|
||||
throw new IllegalArgumentError(`'${destination}' is not a directory.`);
|
||||
}
|
||||
|
||||
mappings = sources.map(source => [source, destination.getChild(source.fileName)]);
|
||||
} else {
|
||||
// Move to exact location
|
||||
if (sources.length !== 1)
|
||||
throw new IllegalArgumentError(`'${destination}' is not a directory.`);
|
||||
|
||||
if (!(this.get(destination.parent) instanceof Directory))
|
||||
throw new IllegalArgumentError(`'${destination.parent}' is not a directory.`);
|
||||
|
||||
mappings = sources.map(path => [path, destination]);
|
||||
}
|
||||
|
||||
return mappings;
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
|
|
|
@ -12,7 +12,7 @@ import {InputArgs} from "./InputArgs";
|
|||
|
||||
|
||||
/**
|
||||
* A shell that interacts with the user session and file system to execute commands.
|
||||
* A shell that interacts with the environment, user list, file system to execute commands.
|
||||
*/
|
||||
export class Shell {
|
||||
/**
|
||||
|
@ -24,7 +24,7 @@ export class Shell {
|
|||
*/
|
||||
private readonly inputHistory: InputHistory;
|
||||
/**
|
||||
* The user session describing the user that interacts with the shell.
|
||||
* The user list describing the available users.
|
||||
*/
|
||||
private readonly userList: UserList;
|
||||
/**
|
||||
|
|
|
@ -1,5 +1,5 @@
|
|||
/**
|
||||
* Manages a user session.
|
||||
* Manages a list of users.
|
||||
*/
|
||||
export class UserList {
|
||||
/**
|
||||
|
@ -32,6 +32,15 @@ export class UserList {
|
|||
}
|
||||
|
||||
|
||||
/**
|
||||
* Adds the given user to the user list.
|
||||
*
|
||||
* @param user the user to add
|
||||
*/
|
||||
add(user: User) {
|
||||
this._users.push(user);
|
||||
}
|
||||
|
||||
/**
|
||||
* Returns `true` if and only if a user with the given name exists.
|
||||
*
|
||||
|
|
|
@ -0,0 +1,713 @@
|
|||
import "mocha";
|
||||
import {expect} from "chai";
|
||||
|
||||
import {Directory, File, FileSystem, Path} from "../main/js/FileSystem";
|
||||
import {Command, commandBinaries, Commands} from "../main/js/Commands";
|
||||
import {Environment} from "../main/js/Environment";
|
||||
import {User, UserList} from "../main/js/UserList";
|
||||
import {InputParser} from "../main/js/InputParser";
|
||||
import {Buffer, StreamSet} from "../main/js/Stream";
|
||||
|
||||
|
||||
describe("commands", () => {
|
||||
let environment: Environment;
|
||||
let fileSystem: FileSystem;
|
||||
let userList: UserList;
|
||||
let commands: Commands;
|
||||
let parser: InputParser;
|
||||
let streamSet: StreamSet;
|
||||
|
||||
|
||||
beforeEach(() => {
|
||||
environment = new Environment(["cwd"], {"cwd": "/"});
|
||||
fileSystem = new FileSystem(new Directory());
|
||||
userList = new UserList([]);
|
||||
commands = new Commands(environment, userList, fileSystem);
|
||||
parser = InputParser.create(environment, fileSystem);
|
||||
streamSet = new StreamSet(new Buffer(), new Buffer(), new Buffer());
|
||||
});
|
||||
|
||||
const loadCommand = function(name: string) {
|
||||
fileSystem.add(new Path(`/bin/${name}`), new File(commandBinaries[name]), true);
|
||||
};
|
||||
|
||||
const execute = function(command: string): number {
|
||||
return commands.execute(parser.parseCommands(command)[0], streamSet);
|
||||
};
|
||||
|
||||
|
||||
describe("execute", () => {
|
||||
it("writes an error if it cannot resolve the command", () => {
|
||||
expect(execute("does-not-exist")).to.equal(-1);
|
||||
expect((streamSet.err as Buffer).read()).to.equal("Unknown command 'does-not-exist'.\n");
|
||||
});
|
||||
|
||||
it("writes an error if the command is invalid", () => {
|
||||
fileSystem.add(new Path("/command"), new File("invalid"), false);
|
||||
|
||||
expect(execute("/command")).to.equal(-1);
|
||||
expect((streamSet.err as Buffer).read())
|
||||
.to.equal("Could not parse command '/command': ReferenceError: invalid is not defined.\n");
|
||||
});
|
||||
|
||||
it("writes an error if the command is a doc-only command", () => {
|
||||
fileSystem.add(new Path("/command"), new File(`new DocOnlyCommand("", "")`), false);
|
||||
|
||||
expect(execute("/command")).to.equal(-1);
|
||||
expect((streamSet.err as Buffer).read())
|
||||
.to.equal("Could not execute doc-only command. Try 'help /command' instead.\n");
|
||||
});
|
||||
|
||||
it("writes an error if the arguments to the command are invalid", () => {
|
||||
const command = `new Command("", "", "", "", new InputValidator({minArgs: 2}))`;
|
||||
fileSystem.add(new Path("/command"), new File(command), false);
|
||||
|
||||
expect(execute("/command arg1")).to.equal(-1);
|
||||
expect((streamSet.err as Buffer).read())
|
||||
.to.contain("Invalid usage of '/command'. Expected at least 2 arguments but got 1.");
|
||||
});
|
||||
|
||||
it("executes the command otherwise", () => {
|
||||
const command = `new Command(
|
||||
(input, streams) => { streams.out.writeLine(input.args[0]); return Number(input.args[1]); },
|
||||
"", "", "",
|
||||
new InputValidator()
|
||||
)`.trimMultiLines();
|
||||
fileSystem.add(new Path("/command"), new File(command), false);
|
||||
|
||||
expect(execute("/command output 42")).to.equal(42);
|
||||
expect((streamSet.out as Buffer).read()).to.equal("output\n");
|
||||
});
|
||||
});
|
||||
|
||||
describe("resolve", () => {
|
||||
describe("/bin commands", () => {
|
||||
it("resolves a command from /bin if it exists", () => {
|
||||
fileSystem.add(new Path("/bin/command"), new File(`new Command("", "Summary", "", "", "")`), true);
|
||||
|
||||
expect((commands.resolve("command") as Command).summary).to.equal("Summary");
|
||||
});
|
||||
|
||||
it("cannot resolve a command from /bin if it does not exist", () => {
|
||||
expect(commands.resolve("command")).to.equal(undefined);
|
||||
});
|
||||
|
||||
it("resolves a /bin command using a relative path", () => {
|
||||
fileSystem.add(new Path("/bin/command"), new File(`new Command("", "Summary", "", "", "")`), true);
|
||||
|
||||
expect((commands.resolve("bin/command") as Command).summary).to.equal("Summary");
|
||||
});
|
||||
});
|
||||
|
||||
describe("relative commands", () => {
|
||||
it("resolves a command from a relative path if it exists", () => {
|
||||
fileSystem.add(new Path("/command"), new File(`new Command("", "Summary", "", "", "")`), true);
|
||||
|
||||
expect((commands.resolve("./command") as Command).summary).to.equal("Summary");
|
||||
});
|
||||
|
||||
it("cannot resolve a command from a relative path if it does not exist", () => {
|
||||
expect(commands.resolve("./command")).to.equal(undefined);
|
||||
});
|
||||
});
|
||||
|
||||
it("cannot resolve a command if the file cannot be parsed", () => {
|
||||
fileSystem.add(new Path("/command"), new File("invalid"), true);
|
||||
|
||||
expect((commands.resolve("./command") as Error).message).to.equal("invalid is not defined");
|
||||
});
|
||||
});
|
||||
|
||||
describe("commands", () => {
|
||||
describe("and", () => {
|
||||
beforeEach(() => {
|
||||
loadCommand("and");
|
||||
loadCommand("echo");
|
||||
});
|
||||
|
||||
|
||||
it("does nothing if the previous command exited unsuccessfully", () => {
|
||||
environment.set("status", "-1");
|
||||
|
||||
expect(execute("and echo 'message'")).to.equal(-1);
|
||||
expect((streamSet.out as Buffer).read()).to.equal("");
|
||||
});
|
||||
|
||||
it("executes the command if the previous command exited successfully", () => {
|
||||
environment.set("status", "0");
|
||||
|
||||
expect(execute("and echo 'message'")).to.equal(0);
|
||||
expect((streamSet.out as Buffer).read()).to.equal("message\n");
|
||||
});
|
||||
});
|
||||
|
||||
describe("cat", () => {
|
||||
beforeEach(() => loadCommand("cat"));
|
||||
|
||||
|
||||
it("fails if the file does not exist", () => {
|
||||
expect(execute("cat /file")).to.equal(-1);
|
||||
expect((streamSet.err as Buffer).read()).to.equal(`cat: '/file': No such file.\n`);
|
||||
});
|
||||
|
||||
it("fails if the target is not a file", () => {
|
||||
fileSystem.add(new Path("/dir"), new Directory(), false);
|
||||
|
||||
expect(execute("cat /dir")).to.equal(-1);
|
||||
expect((streamSet.err as Buffer).read()).to.equal(`cat: '/dir': No such file.\n`);
|
||||
});
|
||||
|
||||
it("writes the contents of the file to the output stream", () => {
|
||||
fileSystem.add(new Path("/file"), new File("contents"), false);
|
||||
|
||||
expect(execute("cat /file")).to.equal(0);
|
||||
expect((streamSet.out as Buffer).read()).to.equal("contents\n");
|
||||
});
|
||||
|
||||
it("does not add an extra newline at the end if there already is one", () => {
|
||||
fileSystem.add(new Path("/file"), new File("contents\n"), false);
|
||||
|
||||
expect(execute("cat /file")).to.equal(0);
|
||||
expect((streamSet.out as Buffer).read()).to.equal("contents\n");
|
||||
});
|
||||
|
||||
it("escapes HTML if prompted to do so", () => {
|
||||
fileSystem.add(new Path("/file"), new File("<i>contents</i>"), false);
|
||||
|
||||
expect(execute("cat -e /file")).to.equal(0);
|
||||
expect((streamSet.out as Buffer).read()).to.equal("<i>contents</i>\n");
|
||||
});
|
||||
|
||||
it("concatenates multiple file outputs", () => {
|
||||
fileSystem.add(new Path("/file1"), new File("contents1"), false);
|
||||
fileSystem.add(new Path("/file2"), new File("contents2"), false);
|
||||
|
||||
expect(execute("cat /file1 /file2")).to.equal(0);
|
||||
expect((streamSet.out as Buffer).read()).to.equal("contents1\ncontents2\n");
|
||||
});
|
||||
});
|
||||
|
||||
describe("cd", () => {
|
||||
beforeEach(() => loadCommand("cd"));
|
||||
|
||||
|
||||
it("changes the directory to the home directory if no directory is given", () => {
|
||||
environment.set("home", "/home");
|
||||
|
||||
expect(execute("cd")).to.equal(0);
|
||||
expect(environment.get("cwd")).to.equal("/home");
|
||||
});
|
||||
|
||||
it("fails if the target directory does not exist", () => {
|
||||
expect(execute("cd target")).to.equal(-1);
|
||||
expect((streamSet.err as Buffer).read()).to.equal("cd: The directory '/target' does not exist.\n");
|
||||
});
|
||||
|
||||
it("fails if the target is a file", () => {
|
||||
fileSystem.add(new Path("/target"), new File(), false);
|
||||
|
||||
expect(execute("cd target")).to.equal(-1);
|
||||
expect((streamSet.err as Buffer).read()).to.equal("cd: The directory '/target' does not exist.\n");
|
||||
});
|
||||
|
||||
it("changes the directory to the given target", () => {
|
||||
fileSystem.add(new Path("/target"), new Directory(), false);
|
||||
|
||||
expect(execute("cd target")).to.equal(0);
|
||||
expect(environment.get("cwd")).to.equal("/target");
|
||||
});
|
||||
});
|
||||
|
||||
describe("cp", () => {
|
||||
beforeEach(() => loadCommand("cp"));
|
||||
|
||||
|
||||
it("copies the source file to the exact target if it does not exist already", () => {
|
||||
fileSystem.add(new Path("/src"), new File("contents"), false);
|
||||
|
||||
expect(execute("cp /src /dst")).to.equal(0);
|
||||
expect((fileSystem.get(new Path("/src")) as File).open("read").read()).to.equal("contents");
|
||||
expect((fileSystem.get(new Path("/dst")) as File).open("read").read()).to.equal("contents");
|
||||
});
|
||||
|
||||
it("fails if the source is a directory and the recursive option is not given", () => {
|
||||
fileSystem.add(new Path("/src"), new Directory(), true);
|
||||
|
||||
expect(execute("cp /src /dst")).to.equal(-1);
|
||||
expect((streamSet.err as Buffer).read()).to.equal("cp: '/src' is a directory.\n");
|
||||
});
|
||||
|
||||
it("copies the source directory to the exact target if it does not exist already", () => {
|
||||
fileSystem.add(new Path("/src/file"), new File("contents"), true);
|
||||
|
||||
expect(execute("cp -r /src /dst")).to.equal(0);
|
||||
expect((fileSystem.get(new Path("/src/file")) as File).open("read").read()).to.equal("contents");
|
||||
expect((fileSystem.get(new Path("/dst/file")) as File).open("read").read()).to.equal("contents");
|
||||
});
|
||||
|
||||
it("fails if there are multiple sources and the target does not exist", () => {
|
||||
fileSystem.add(new Path("/src1"), new File("contents1"), false);
|
||||
fileSystem.add(new Path("/src2"), new File("contents2"), false);
|
||||
|
||||
expect(execute("cp /src1 /src2 /dst")).to.equal(-1);
|
||||
expect((streamSet.err as Buffer).read()).to.equal("cp: '/dst' is not a directory.\n");
|
||||
});
|
||||
|
||||
it("fails if the target is a file", () => {
|
||||
fileSystem.add(new Path("/src1"), new File("contents1"), false);
|
||||
fileSystem.add(new Path("/src2"), new File("contents2"), false);
|
||||
fileSystem.add(new Path("/dst"), new File(), false);
|
||||
|
||||
expect(execute("cp /src1 /src2 /dst")).to.equal(-1);
|
||||
expect((streamSet.err as Buffer).read()).to.equal("cp: '/dst' is not a directory.\n");
|
||||
});
|
||||
|
||||
it("copies all sources into the target directory", () => {
|
||||
fileSystem.add(new Path("/src1"), new File("contents1"), false);
|
||||
fileSystem.add(new Path("/src2"), new File("contents2"), false);
|
||||
fileSystem.add(new Path("/dst"), new Directory(), false);
|
||||
|
||||
expect(execute("cp /src1 /src2 /dst")).to.equal(0);
|
||||
expect((fileSystem.get(new Path("/src1")) as File).open("read").read()).to.equal("contents1");
|
||||
expect((fileSystem.get(new Path("/src2")) as File).open("read").read()).to.equal("contents2");
|
||||
expect((fileSystem.get(new Path("/dst/src1")) as File).open("read").read()).to.equal("contents1");
|
||||
expect((fileSystem.get(new Path("/dst/src2")) as File).open("read").read()).to.equal("contents2");
|
||||
});
|
||||
});
|
||||
|
||||
describe("echo", () => {
|
||||
beforeEach(() => loadCommand("echo"));
|
||||
|
||||
|
||||
it("adds a newline to the end by default", () => {
|
||||
expect(execute("echo a b c \n")).to.equal(0);
|
||||
expect((streamSet.out as Buffer).read()).to.equal("a b c \n\n");
|
||||
});
|
||||
|
||||
it("does not add a newline if prompted to do so", () => {
|
||||
expect(execute("echo -n a b c")).to.equal(0);
|
||||
expect((streamSet.out as Buffer).read()).to.equal("a b c");
|
||||
});
|
||||
});
|
||||
|
||||
describe("exit", () => {
|
||||
beforeEach(() => loadCommand("exit"));
|
||||
|
||||
|
||||
it("changes the current user", () => {
|
||||
expect(execute("exit")).to.equal(0);
|
||||
expect(environment.get("user")).to.equal("");
|
||||
});
|
||||
});
|
||||
|
||||
describe("help", () => {
|
||||
beforeEach(() => loadCommand("help"));
|
||||
|
||||
|
||||
it("outputs something", () => {
|
||||
expect(execute("help help")).to.equal(0);
|
||||
expect((streamSet.out as Buffer).read()).to.not.equal("");
|
||||
});
|
||||
});
|
||||
|
||||
describe("hier", () => {
|
||||
beforeEach(() => {
|
||||
loadCommand("help");
|
||||
loadCommand("hier");
|
||||
});
|
||||
|
||||
|
||||
it("outputs something", () => {
|
||||
expect(execute("help hier")).to.equal(0);
|
||||
expect((streamSet.out as Buffer).read()).to.not.equal("");
|
||||
});
|
||||
});
|
||||
|
||||
describe("ls", () => {
|
||||
beforeEach(() => loadCommand("ls"));
|
||||
|
||||
|
||||
it("fails if the target does not exist", () => {
|
||||
expect(execute("ls dir")).to.equal(-1);
|
||||
expect((streamSet.err as Buffer).read()).to.equal("ls: The directory '/dir' does not exist.\n");
|
||||
});
|
||||
|
||||
it("fails if the target is a file", () => {
|
||||
fileSystem.add(new Path("file"), new File(), true);
|
||||
|
||||
expect(execute("ls file")).to.equal(-1);
|
||||
expect((streamSet.err as Buffer).read()).to.equal("ls: '/file' is not a directory.\n");
|
||||
});
|
||||
|
||||
it("outputs something otherwise", () => {
|
||||
fileSystem.add(new Path("dir"), new Directory(), true);
|
||||
|
||||
expect(execute("ls dir")).to.equal(0);
|
||||
expect((streamSet.out as Buffer).read()).to.not.equal("");
|
||||
});
|
||||
});
|
||||
|
||||
describe("mkdir", () => {
|
||||
beforeEach(() => loadCommand("mkdir"));
|
||||
|
||||
|
||||
it("fails if the given directory exists", () => {
|
||||
fileSystem.add(new Path("/dir"), new Directory(), false);
|
||||
|
||||
expect(execute("mkdir /dir")).to.equal(-1);
|
||||
expect((streamSet.err as Buffer).read())
|
||||
.to.equal("mkdir: A file or directory already exists at '/dir'.\n");
|
||||
});
|
||||
|
||||
it("fails if the parent does not exist", () => {
|
||||
expect(execute("mkdir /parent/dir")).to.equal(-1);
|
||||
expect((streamSet.err as Buffer).read()).to.equal("mkdir: The directory '/parent' does not exist.\n");
|
||||
});
|
||||
|
||||
it("creates the parents if opted to do so", () => {
|
||||
expect(execute("mkdir -p /parent/dir")).to.equal(0);
|
||||
expect(fileSystem.has(new Path("/parent/dir"))).to.be.true;
|
||||
});
|
||||
|
||||
it("creates the given directories in order", () => {
|
||||
expect(execute("mkdir /parent /parent/dir1 /dir2")).to.equal(0);
|
||||
expect(fileSystem.has(new Path("/parent/dir1"))).to.be.true;
|
||||
expect(fileSystem.has(new Path("/dir2"))).to.be.true;
|
||||
});
|
||||
});
|
||||
|
||||
describe("mv", () => {
|
||||
beforeEach(() => loadCommand("mv"));
|
||||
|
||||
|
||||
it("moves the source file to the exact target if it does not exist already", () => {
|
||||
fileSystem.add(new Path("/src"), new File("contents"), false);
|
||||
|
||||
expect(execute("mv /src /dst")).to.equal(0);
|
||||
expect(fileSystem.has(new Path("/src"))).to.be.false;
|
||||
expect((fileSystem.get(new Path("/dst")) as File).open("read").read()).to.equal("contents");
|
||||
});
|
||||
|
||||
it("moves the source directory to the exact target if it does not exist already", () => {
|
||||
fileSystem.add(new Path("/src/file"), new File("contents"), true);
|
||||
|
||||
expect(execute("mv -r /src /dst")).to.equal(0);
|
||||
expect(fileSystem.has(new Path("/src"))).to.be.false;
|
||||
expect(fileSystem.has(new Path("/src/file"))).to.be.false;
|
||||
expect((fileSystem.get(new Path("/dst/file")) as File).open("read").read()).to.equal("contents");
|
||||
});
|
||||
|
||||
it("fails if there are multiple sources and the target does not exist", () => {
|
||||
fileSystem.add(new Path("/src1"), new File("contents1"), false);
|
||||
fileSystem.add(new Path("/src2"), new File("contents2"), false);
|
||||
|
||||
expect(execute("mv /src1 /src2 /dst")).to.equal(-1);
|
||||
expect((streamSet.err as Buffer).read()).to.equal("mv: '/dst' is not a directory.\n");
|
||||
});
|
||||
|
||||
it("fails if the target is a file", () => {
|
||||
fileSystem.add(new Path("/src1"), new File("contents1"), false);
|
||||
fileSystem.add(new Path("/src2"), new File("contents2"), false);
|
||||
fileSystem.add(new Path("/dst"), new File(), false);
|
||||
|
||||
expect(execute("mv /src1 /src2 /dst")).to.equal(-1);
|
||||
expect((streamSet.err as Buffer).read()).to.equal("mv: '/dst' is not a directory.\n");
|
||||
});
|
||||
|
||||
it("moves all sources into the target directory", () => {
|
||||
fileSystem.add(new Path("/src1"), new File("contents1"), false);
|
||||
fileSystem.add(new Path("/src2"), new File("contents2"), false);
|
||||
fileSystem.add(new Path("/dst"), new Directory(), false);
|
||||
|
||||
expect(execute("mv /src1 /src2 /dst")).to.equal(0);
|
||||
expect(fileSystem.has(new Path("/src1"))).to.be.false;
|
||||
expect(fileSystem.has(new Path("/src2"))).to.be.false;
|
||||
expect((fileSystem.get(new Path("/dst/src1")) as File).open("read").read()).to.equal("contents1");
|
||||
expect((fileSystem.get(new Path("/dst/src2")) as File).open("read").read()).to.equal("contents2");
|
||||
});
|
||||
});
|
||||
|
||||
describe("not", () => {
|
||||
beforeEach(() => {
|
||||
loadCommand("not");
|
||||
loadCommand("rm");
|
||||
});
|
||||
|
||||
|
||||
it("returns 0 if the given command exits unsuccessfully", () => {
|
||||
expect(execute("not rm /file")).to.equal(0);
|
||||
});
|
||||
|
||||
it("returns 1 if the command exits successfully", () => {
|
||||
fileSystem.add(new Path("/file"), new File(), false);
|
||||
|
||||
expect(execute("not rm /file")).to.equal(1);
|
||||
});
|
||||
});
|
||||
|
||||
// TODO: Cannot test because `window` does not exist.
|
||||
// describe("open", () => {
|
||||
// beforeEach(() => loadCommand("open"));
|
||||
// });
|
||||
|
||||
describe("or", () => {
|
||||
beforeEach(() => {
|
||||
loadCommand("or");
|
||||
loadCommand("echo");
|
||||
});
|
||||
|
||||
|
||||
it("does nothing if the previous command exited successfully", () => {
|
||||
environment.set("status", "0");
|
||||
|
||||
expect(execute("or echo 'message'")).to.equal(0);
|
||||
expect((streamSet.out as Buffer).read()).to.equal("");
|
||||
});
|
||||
|
||||
it("executes the command if the previous command did not exit successfully", () => {
|
||||
environment.set("status", "-1");
|
||||
|
||||
expect(execute("or echo 'message'")).to.equal(0);
|
||||
expect((streamSet.out as Buffer).read()).to.equal("message\n");
|
||||
});
|
||||
});
|
||||
|
||||
describe("poweroff", () => {
|
||||
beforeEach(() => loadCommand("poweroff"));
|
||||
|
||||
|
||||
it("fails if no user is logged in", () => {
|
||||
environment.set("user", "");
|
||||
|
||||
expect(execute("poweroff")).to.equal(-1);
|
||||
expect((streamSet.err as Buffer).read()).to.equal("poweroff: Cannot execute while not logged in.\n");
|
||||
});
|
||||
|
||||
// TODO: Cannot test because `setTimeout` does not exist.
|
||||
// it("it outputs something", () => {
|
||||
// environment.set("user", "user");
|
||||
//
|
||||
// expect(execute("poweroff")).to.equal(0);
|
||||
// expect((streamSet.out as Buffer).read()).to.not.equal("");
|
||||
// });
|
||||
});
|
||||
|
||||
describe("pwd", () => {
|
||||
beforeEach(() => loadCommand("pwd"));
|
||||
|
||||
|
||||
it("writes the cwd variable to the output stream", () => {
|
||||
environment.set("cwd", "/dir");
|
||||
|
||||
expect(execute("pwd")).to.equal(0);
|
||||
expect((streamSet.out as Buffer).read()).to.equal("/dir\n");
|
||||
});
|
||||
|
||||
it("writes an empty string if the cwd variable has no value", () => {
|
||||
environment.set("cwd", "");
|
||||
|
||||
expect(execute("pwd")).to.equal(0);
|
||||
expect((streamSet.out as Buffer).read()).to.equal("\n");
|
||||
});
|
||||
});
|
||||
|
||||
describe("rm", () => {
|
||||
beforeEach(() => loadCommand("rm"));
|
||||
|
||||
|
||||
it("fails if the target does not exist", () => {
|
||||
expect(execute("rm /file")).to.equal(-1);
|
||||
expect((streamSet.err as Buffer).read()).to.equal("rm: The file '/file' does not exist.\n");
|
||||
});
|
||||
|
||||
it("does nothing if the target does not exist but the force option is given", () => {
|
||||
expect(execute("rm -f /file")).to.equal(0);
|
||||
expect((streamSet.err as Buffer).read()).to.equal("");
|
||||
});
|
||||
|
||||
it("fails if the target is a directory", () => {
|
||||
fileSystem.add(new Path("/dir"), new Directory(), false);
|
||||
|
||||
expect(execute("rm /dir")).to.equal(-1);
|
||||
expect((streamSet.err as Buffer).read()).to.equal("rm: '/dir' is a directory.\n");
|
||||
});
|
||||
|
||||
it("removes an empty directory if the recursive option is given", () => {
|
||||
fileSystem.add(new Path("/dir"), new Directory(), false);
|
||||
|
||||
expect(execute("rm -r /dir")).to.equal(0);
|
||||
expect(fileSystem.has(new Path("/dir"))).to.be.false;
|
||||
});
|
||||
|
||||
it("removes a non-empty directory if the recursive option is given", () => {
|
||||
fileSystem.add(new Path("/dir/file"), new File(), true);
|
||||
|
||||
expect(execute("rm -r /dir")).to.equal(0);
|
||||
expect(fileSystem.has(new Path("/dir"))).to.be.false;
|
||||
});
|
||||
|
||||
it("fails if the target is the root even though the recursive option is given", () => {
|
||||
expect(execute("rm -r /")).to.equal(-1);
|
||||
expect((streamSet.err as Buffer).read()).to.equal("rm: Cannot remove root directory.\n");
|
||||
});
|
||||
|
||||
it("removes the root if the recursive and no-preserve-root options are given", () => {
|
||||
fileSystem.add(new Path("/dir"), new Directory(), false);
|
||||
|
||||
expect(execute("rm -r --no-preserve-root /")).to.equal(0);
|
||||
expect(fileSystem.has(new Path("/dir"))).to.be.false;
|
||||
expect(fileSystem.has(new Path("/"))).to.be.true;
|
||||
});
|
||||
|
||||
it("removes the given file", () => {
|
||||
fileSystem.add(new Path("/file"), new File(), false);
|
||||
|
||||
expect(execute("rm /file")).to.equal(0);
|
||||
expect(fileSystem.has(new Path("/file"))).to.be.false;
|
||||
});
|
||||
|
||||
it("removes files but not directories of the recursive option is not given", () => {
|
||||
fileSystem.add(new Path("/dir"), new Directory(), false);
|
||||
fileSystem.add(new Path("/file"), new File(), false);
|
||||
|
||||
expect(execute("rm /dir /file")).to.equal(-1);
|
||||
expect(fileSystem.has(new Path("/dir"))).to.be.true;
|
||||
expect(fileSystem.has(new Path("/file"))).to.be.false;
|
||||
});
|
||||
});
|
||||
|
||||
describe("rmdir", () => {
|
||||
beforeEach(() => loadCommand("rmdir"));
|
||||
|
||||
|
||||
it("fails if the target does not exist", () => {
|
||||
expect(execute("rmdir /dir")).to.equal(-1);
|
||||
expect((streamSet.err as Buffer).read()).to.equal("rmdir: '/dir' does not exist.\n");
|
||||
});
|
||||
|
||||
it("fails if the target is not a directory", () => {
|
||||
fileSystem.add(new Path("/file"), new File(), false);
|
||||
|
||||
expect(execute("rmdir /file")).to.equal(-1);
|
||||
expect((streamSet.err as Buffer).read()).to.equal("rmdir: '/file' is not a directory.\n");
|
||||
});
|
||||
|
||||
it("fails if the target is not empty", () => {
|
||||
fileSystem.add(new Path("/dir/file"), new File(), true);
|
||||
|
||||
expect(execute("rmdir /dir")).to.equal(-1);
|
||||
expect((streamSet.err as Buffer).read()).to.equal("rmdir: '/dir' is not empty.\n");
|
||||
});
|
||||
|
||||
it("removes the target if it is an empty directory", () => {
|
||||
fileSystem.add(new Path("/dir"), new Directory(), false);
|
||||
|
||||
expect(execute("rmdir /dir")).to.equal(0);
|
||||
expect(fileSystem.has(new Path("/dir"))).to.be.false;
|
||||
});
|
||||
|
||||
it("removes the targets if they're empty directories", () => {
|
||||
fileSystem.add(new Path("/dir1/dir2"), new Directory(), true);
|
||||
|
||||
expect(execute("rmdir /dir1/dir2 /dir1")).to.equal(0);
|
||||
expect(fileSystem.has(new Path("/dir1"))).to.be.false;
|
||||
});
|
||||
});
|
||||
|
||||
describe("set", () => {
|
||||
beforeEach(() => loadCommand("set"));
|
||||
|
||||
|
||||
it("creates a variable if it does not exist", () => {
|
||||
expect(execute("set var val")).to.equal(0);
|
||||
expect(environment.variables["var"]).to.equal("val");
|
||||
});
|
||||
|
||||
it("changes a variable if it exists", () => {
|
||||
environment.set("var", "old");
|
||||
|
||||
expect(execute("set var new")).to.equal(0);
|
||||
expect(environment.variables["var"]).to.equal("new");
|
||||
});
|
||||
|
||||
it("removes the variable if no value is given", () => {
|
||||
environment.set("var", "val");
|
||||
|
||||
expect(execute("set var")).to.equal(0);
|
||||
expect(environment.variables["var"]).to.be.undefined;
|
||||
});
|
||||
|
||||
it("cannot change a read-only variable", () => {
|
||||
environment.set("cwd", "old");
|
||||
|
||||
expect(execute("set cwd new")).to.equal(-1);
|
||||
expect((streamSet.err as Buffer).read()).to.equal("set: Cannot set read-only environment variable.\n");
|
||||
expect(environment.variables["cwd"]).to.equal("old");
|
||||
});
|
||||
|
||||
it("cannot remove a read-only variable", () => {
|
||||
environment.set("cwd", "old");
|
||||
|
||||
expect(execute("set cwd")).to.equal(-1);
|
||||
expect((streamSet.err as Buffer).read()).to.equal("set: Cannot set read-only environment variable.\n");
|
||||
expect(environment.variables["cwd"]).to.equal("old");
|
||||
});
|
||||
});
|
||||
|
||||
describe("touch", () => {
|
||||
beforeEach(() => loadCommand("touch"));
|
||||
|
||||
|
||||
it("fails if a directory already exists at the target", () => {
|
||||
fileSystem.add(new Path("/dir"), new Directory(), false);
|
||||
|
||||
expect(execute("touch /dir")).to.equal(-1);
|
||||
expect((streamSet.err as Buffer).read())
|
||||
.to.equal("touch: A file or directory already exists at '/dir'.\n");
|
||||
});
|
||||
|
||||
it("fails if the parent of the target does not exist", () => {
|
||||
expect(execute("touch /parent/file")).to.equal(-1);
|
||||
expect((streamSet.err as Buffer).read()).to.equal("touch: The directory '/parent' does not exist.\n");
|
||||
});
|
||||
|
||||
it("fails if the parent of the target is a file", () => {
|
||||
fileSystem.add(new Path("/parent"), new File(), false);
|
||||
|
||||
expect(execute("touch /parent/file")).to.equal(-1);
|
||||
expect((streamSet.err as Buffer).read()).to.equal("touch: '/parent' is not a directory.\n");
|
||||
});
|
||||
|
||||
it("creates a file at the target", () => {
|
||||
expect(execute("touch /file")).to.equal(0);
|
||||
expect(fileSystem.has(new Path("/file"))).to.be.true;
|
||||
});
|
||||
|
||||
it("creates files at the target", () => {
|
||||
expect(execute("touch /file1 /file2")).to.equal(0);
|
||||
expect(fileSystem.has(new Path("/file1"))).to.be.true;
|
||||
expect(fileSystem.has(new Path("/file2"))).to.be.true;
|
||||
});
|
||||
});
|
||||
|
||||
describe("whoami", () => {
|
||||
beforeEach(() => loadCommand("whoami"));
|
||||
|
||||
|
||||
it("fails if no user is logged in", () => {
|
||||
environment.set("user", "");
|
||||
|
||||
expect(execute("whoami")).to.equal(-1);
|
||||
expect((streamSet.err as Buffer).read()).to.equal("whoami: Cannot execute while not logged in.\n");
|
||||
});
|
||||
|
||||
it("it outputs something", () => {
|
||||
userList.add(new User("user", "pwd", "/", "Description"));
|
||||
environment.set("user", "user");
|
||||
|
||||
expect(execute("whoami")).to.equal(0);
|
||||
expect((streamSet.out as Buffer).read()).to.equal("Description\n");
|
||||
});
|
||||
});
|
||||
});
|
||||
});
|
|
@ -288,10 +288,6 @@ describe("file system", () => {
|
|||
});
|
||||
|
||||
describe("open", () => {
|
||||
it("throws an error if the target's parent does not exist", () => {
|
||||
expect(() => fileSystem.open(new Path("/dir/file"), "read")).to.throw();
|
||||
});
|
||||
|
||||
it("throws an error if the target is an existing directory", () => {
|
||||
fileSystem.add(new Path("/dir"), new Directory(), false);
|
||||
|
||||
|
@ -352,4 +348,95 @@ describe("file system", () => {
|
|||
expect(fileSystem.has(new Path("/file"))).to.be.true;
|
||||
});
|
||||
});
|
||||
|
||||
describe("determineMoveMappings", () => {
|
||||
describe("single source", () => {
|
||||
it("maps to the destination if the destination does not exist and its parent is a directory", () => {
|
||||
fileSystem.add(new Path("/src"), new File(), false);
|
||||
|
||||
expect(fileSystem.determineMoveMappings([new Path("/src")], new Path("/dst")))
|
||||
.to.deep.equal([[new Path("/src"), new Path("/dst")]]);
|
||||
});
|
||||
|
||||
it("maps into the destination if the destination exists and it is a directory", () => {
|
||||
fileSystem.add(new Path("/src"), new File(), false);
|
||||
fileSystem.add(new Path("/dst"), new Directory(), false);
|
||||
|
||||
expect(fileSystem.determineMoveMappings([new Path("/src")], new Path("/dst")))
|
||||
.to.deep.equal([[new Path("/src"), new Path("/dst/src")]]);
|
||||
});
|
||||
|
||||
it("fails if neither the destination nor its parent exists", () => {
|
||||
fileSystem.add(new Path("/src"), new File(), false);
|
||||
|
||||
expect(() => fileSystem.determineMoveMappings([new Path("/src")], new Path("/parent/dst"))).to.throw();
|
||||
});
|
||||
|
||||
it("fails if the destination does not exist and its parent is a file", () => {
|
||||
fileSystem.add(new Path("/src"), new File(), false);
|
||||
fileSystem.add(new Path("/parent"), new File(), false);
|
||||
|
||||
expect(() => fileSystem.determineMoveMappings([new Path("/src")], new Path("/parent/dst"))).to.throw();
|
||||
});
|
||||
|
||||
it("fails if neither the destination nor its parent exists", () => {
|
||||
fileSystem.add(new Path("/src"), new File(), false);
|
||||
|
||||
expect(() => fileSystem.determineMoveMappings([new Path("/src")], new Path("/parent/dst"))).to.throw();
|
||||
});
|
||||
|
||||
it("fails if the destination already exists and it is a file", () => {
|
||||
fileSystem.add(new Path("/src"), new File(), false);
|
||||
fileSystem.add(new Path("/dst"), new File(), false);
|
||||
|
||||
expect(() => fileSystem.determineMoveMappings([new Path("/src")], new Path("/dst"))).to.throw();
|
||||
});
|
||||
});
|
||||
|
||||
describe("multiple sources", () => {
|
||||
it("maps into the destination if the destination exists and it is a directory", () => {
|
||||
fileSystem.add(new Path("/src1"), new File(), false);
|
||||
fileSystem.add(new Path("/src2"), new File(), false);
|
||||
fileSystem.add(new Path("/dst"), new Directory(), false);
|
||||
|
||||
const sources = [new Path("/src1"), new Path("/src2")];
|
||||
const mappings = [
|
||||
[new Path("/src1"), new Path("/dst/src1")],
|
||||
[new Path("/src2"), new Path("/dst/src2")]
|
||||
];
|
||||
expect(fileSystem.determineMoveMappings(sources, new Path("/dst"))).to.deep.equal(mappings);
|
||||
});
|
||||
|
||||
it("maps the filenames into the destination if the sources have different parents, the destination " +
|
||||
"exists, and it is a directory", () => {
|
||||
fileSystem.add(new Path("/src1"), new File(), false);
|
||||
fileSystem.add(new Path("/parent/src2"), new File(), true);
|
||||
fileSystem.add(new Path("/dst"), new Directory(), false);
|
||||
|
||||
const sources = [new Path("/src1"), new Path("/parent/src2")];
|
||||
const mappings = [
|
||||
[new Path("/src1"), new Path("/dst/src1")],
|
||||
[new Path("/parent/src2"), new Path("/dst/src2")]
|
||||
];
|
||||
expect(fileSystem.determineMoveMappings(sources, new Path("/dst"))).to.deep.equal(mappings);
|
||||
});
|
||||
|
||||
it("fails if the destination does not exist", () => {
|
||||
fileSystem.add(new Path("/src1"), new File(), false);
|
||||
fileSystem.add(new Path("/src2"), new File(), false);
|
||||
|
||||
const sources = [new Path("/src1"), new Path("/src2")];
|
||||
expect(() => fileSystem.determineMoveMappings(sources, new Path("/dst"))).to.throw();
|
||||
});
|
||||
|
||||
it("fails if the destination exists and is a file", () => {
|
||||
fileSystem.add(new Path("/src1"), new File(), false);
|
||||
fileSystem.add(new Path("/src2"), new File(), false);
|
||||
fileSystem.add(new Path("/dst"), new File(), false);
|
||||
|
||||
const sources = [new Path("/src1"), new Path("/src2")];
|
||||
expect(() => fileSystem.determineMoveMappings(sources, new Path("/dst"))).to.throw();
|
||||
});
|
||||
});
|
||||
});
|
||||
});
|
||||
|
|
|
@ -265,19 +265,17 @@ describe("input parser", () => {
|
|||
|
||||
describe("redirect targets", () => {
|
||||
it("assigns a number-less target to index 1", () => {
|
||||
expect(parser.parseCommands("command >file")[0].redirectTargets[1]).to.deep.equal({type: "write", target: "file"});
|
||||
expect(parser.parseCommands("command >>file")[0].redirectTargets[1]).to.deep.equal({
|
||||
type: "append",
|
||||
target: "file"
|
||||
});
|
||||
expect(parser.parseCommands("command >file")[0].redirectTargets[1])
|
||||
.to.deep.equal({type: "write", target: "file"});
|
||||
expect(parser.parseCommands("command >>file")[0].redirectTargets[1])
|
||||
.to.deep.equal({type: "append", target: "file"});
|
||||
});
|
||||
|
||||
it("assigns the target to the preceding number", () => {
|
||||
expect(parser.parseCommands("command 3>file")[0].redirectTargets[3]).to.deep.equal({type: "write", target: "file"});
|
||||
expect(parser.parseCommands("command 3>>file")[0].redirectTargets[3]).to.deep.equal({
|
||||
type: "append",
|
||||
target: "file"
|
||||
});
|
||||
expect(parser.parseCommands("command 3>file")[0].redirectTargets[3])
|
||||
.to.deep.equal({type: "write", target: "file"});
|
||||
expect(parser.parseCommands("command 3>>file")[0].redirectTargets[3])
|
||||
.to.deep.equal({type: "append", target: "file"});
|
||||
});
|
||||
|
||||
it("uses the last target that is defined", () => {
|
||||
|
|
Loading…
Reference in New Issue