diff --git a/rce/src/Error.ts b/rce/src/Error.ts index dfd1f81..272e180 100644 --- a/rce/src/Error.ts +++ b/rce/src/Error.ts @@ -1,6 +1,7 @@ export class ServerError extends Error { constructor(public readonly reason: string) { super(reason); + this.name = "ServerError"; } } @@ -8,5 +9,6 @@ export class ClientError extends Error { constructor(public readonly reason: string, public readonly code = 400) { super(reason); this.code = code; + this.name = "ClientError"; } } diff --git a/rce/src/runtime/acquire.ts b/rce/src/runtime/acquire.ts index caa857a..8e8fd0f 100644 --- a/rce/src/runtime/acquire.ts +++ b/rce/src/runtime/acquire.ts @@ -31,7 +31,7 @@ type Semver = { * If the length of the array is 4, we should examine the 3rd index and check if it's compatible with our edition. * We'll ignore every value that comes after the 3rd index. */ -function parseSemver(tags: string[]): Semver { +export function parseSemver(tags: string[]): Semver { const semver: Semver = { major: 0, minor: 0, @@ -85,42 +85,7 @@ function parseSemver(tags: string[]): Semver { return semver; } -export async function acquireRuntime() { - const runtimes: Runtime[] = []; - const packagesDir = await fs.readdir( - path.resolve(fileURLToPath(import.meta.url), "../../packages"), - { withFileTypes: true } - ); - - for await (const packageDir of packagesDir) { - if (packageDir.isDirectory()) { - const packageDirPath = path.resolve( - fileURLToPath(import.meta.url), - "../../packages", - packageDir.name - ); - const configFilePath = path.resolve(packageDirPath, "config.toml"); - const configFile = await fs.readFile(configFilePath, "utf8"); - const configObject = toml.parse(configFile); - const runtime = new Runtime( - configObject.language, - configObject.version, - false, - configObject.extension, - configObject.compiled, - configObject.build_command, - configObject.run_command, - configObject.aliases, - Object.fromEntries(configObject.environment.map((o: string) => o.split("="))), - configObject.should_limit_memory, - configObject.memory_limit * 1024 * 1024, - configObject.process_limit, - configObject.allowed_entrypoints - ); - runtimes.push(runtime); - } - } - +export function attemptToSetLatestForRuntimes(runtimes: Runtime[]): Runtime[] { // Attempt to set the "latest" tag to true for each language const languageVersions: LanguageVersion[] = []; for (let i = 0; i < runtimes.length; i++) { @@ -173,3 +138,42 @@ export async function acquireRuntime() { return runtimes; } + +export async function acquireRuntime() { + const runtimes: Runtime[] = []; + const packagesDir = await fs.readdir( + path.resolve(fileURLToPath(import.meta.url), "../../packages"), + { withFileTypes: true } + ); + + for await (const packageDir of packagesDir) { + if (packageDir.isDirectory()) { + const packageDirPath = path.resolve( + fileURLToPath(import.meta.url), + "../../packages", + packageDir.name + ); + const configFilePath = path.resolve(packageDirPath, "config.toml"); + const configFile = await fs.readFile(configFilePath, "utf8"); + const configObject = toml.parse(configFile); + const runtime = new Runtime( + configObject.language, + configObject.version, + false, + configObject.extension, + configObject.compiled, + configObject.build_command, + configObject.run_command, + configObject.aliases, + Object.fromEntries(configObject.environment.map((o: string) => o.split("="))), + configObject.should_limit_memory, + configObject.memory_limit * 1024 * 1024, + configObject.process_limit, + configObject.allowed_entrypoints + ); + runtimes.push(runtime); + } + } + + return attemptToSetLatestForRuntimes(runtimes); +} diff --git a/rce/tests/error.test.ts b/rce/tests/error.test.ts new file mode 100644 index 0000000..00bb351 --- /dev/null +++ b/rce/tests/error.test.ts @@ -0,0 +1,19 @@ +import { ClientError, ServerError } from "../src/Error"; +import { expect, test } from "vitest"; + +test("ServerError", () => { + const serverError = new ServerError("Unexpected input"); + expect(serverError).toBeInstanceOf(Error); + expect(serverError.message).toStrictEqual("Unexpected input"); + expect(serverError.reason).toStrictEqual("Unexpected input"); + expect(serverError.name).toStrictEqual("ServerError"); +}); + +test("ClientError", () => { + const clientError = new ClientError("Unexpected input", 403); + expect(clientError).toBeInstanceOf(Error); + expect(clientError.message).toStrictEqual("Unexpected input"); + expect(clientError.reason).toStrictEqual("Unexpected input"); + expect(clientError.name).toStrictEqual("ClientError"); + expect(clientError.code).toStrictEqual(403); +}); diff --git a/rce/tests/job.test.ts b/rce/tests/job.test.ts index 90bec36..5fd97d8 100644 --- a/rce/tests/job.test.ts +++ b/rce/tests/job.test.ts @@ -79,7 +79,7 @@ test.sequential("should be able to run a file - NodeJS", async (t) => { } const currentUser = os.userInfo(); - const runtime = new Runtime("Javascript", "16.14.0", true, "js", false, [], ["node", "{file}"], ["node", "js"], {}, true, 512 * 1024 * 1024, 256, 1); + const runtime = new Runtime("Javascript", "16.14.0", true, "js", false, [], ["node", "{file}"], ["node", "js"], {}, false, 512 * 1024 * 1024, 4096, 1); const job = new Job( { uid: currentUser.uid, gid: currentUser.gid, free: true, username: currentUser.username }, runtime, @@ -235,7 +235,7 @@ test.sequential("should be able to run multiple files - NodeJS", async (t) => { } const currentUser = os.userInfo(); - const runtime = new Runtime("Javascript", "16.14.0", true, "js", false, [], ["node", "{file}"], ["node", "js"], {}, true, 512 * 1024 * 1024, 256, 1); + const runtime = new Runtime("Javascript", "16.14.0", true, "js", false, [], ["node", "{file}"], ["node", "js"], {}, false, 512 * 1024 * 1024, 4096, 1); const job = new Job( { uid: currentUser.uid, gid: currentUser.gid, free: true, username: currentUser.username }, runtime, diff --git a/rce/tests/runtime.test.ts b/rce/tests/runtime.test.ts index 6c6e12f..f7b1c75 100644 --- a/rce/tests/runtime.test.ts +++ b/rce/tests/runtime.test.ts @@ -1,5 +1,6 @@ import { test, expect } from "vitest"; import { Runtime } from "../src/runtime/runtime.js"; +import { attemptToSetLatestForRuntimes, parseSemver } from "../src/runtime/acquire.js"; test("should throw error on invalid runtime parameters", () => { expect( @@ -34,145 +35,13 @@ test("should throw error on invalid processLimit parameters", () => { }); test("should be able to parse semver", () => { - type Semver = { - major: number, - minor: number, - patch: number, - edition: "latest" | "rc" | "beta" | "alpha" | "nightly" - } - - function parseSemver(tags: string[]): Semver { - const semver: Semver = { - major: 0, - minor: 0, - patch: 0, - edition: "latest" - }; - - for (let i = 0; i < tags.length; i++) { - switch (i) { - case 0: { - const parsedNumber = Number.parseInt(tags[i]); - if (Number.isNaN(parsedNumber)) { - break; - } - - semver.major = parsedNumber; - break; - } - case 1: { - const parsedNumber = Number.parseInt(tags[i]); - if (Number.isNaN(parsedNumber)) { - break; - } - - semver.minor = parsedNumber; - break; - } - case 2: { - const parsedNumber = Number.parseInt(tags[i]); - if (Number.isNaN(parsedNumber)) { - break; - } - - semver.patch = parsedNumber; - break; - } - case 3: { - if (tags[i].startsWith("rc")) { - semver.edition = "rc"; - } else if (tags[i].startsWith("beta")) { - semver.edition = "beta"; - } else if (tags[i].startsWith("alpha")) { - semver.edition = "alpha"; - } else if (tags[i] !== "") { - semver.edition = "nightly"; - } - } - } - } - - return semver; - } - expect(parseSemver(["3"])).toMatchObject({ major: 3, minor: 0, patch: 0, edition: "latest" }); expect(parseSemver(["16", "15", "8", "rc-8"])).toMatchObject({ major: 16, minor: 15, patch: 8, edition: "rc" }); expect(parseSemver(["lorem", "ipsum", "dolor", "sit", "amet"])).toMatchObject({ major: 0, minor: 0, patch: 0, edition: "nightly" }); }); test("should be able to search for latest tag", () => { - type LanguageVersion = { - language: string, - versions: Array< - { - index: number, - version: string - } - > - } - - type Semver = { - major: number, - minor: number, - patch: number, - edition: "latest" | "rc" | "beta" | "alpha" | "nightly" - } - - function parseSemver(tags: string[]): Semver { - const semver: Semver = { - major: 0, - minor: 0, - patch: 0, - edition: "latest" - }; - - for (let i = 0; i < tags.length; i++) { - switch (i) { - case 0: { - const parsedNumber = Number.parseInt(tags[i]); - if (Number.isNaN(parsedNumber)) { - break; - } - - semver.major = parsedNumber; - break; - } - case 1: { - const parsedNumber = Number.parseInt(tags[i]); - if (Number.isNaN(parsedNumber)) { - break; - } - - semver.minor = parsedNumber; - break; - } - case 2: { - const parsedNumber = Number.parseInt(tags[i]); - if (Number.isNaN(parsedNumber)) { - break; - } - - semver.patch = parsedNumber; - break; - } - case 3: { - if (tags[i].startsWith("rc")) { - semver.edition = "rc"; - } else if (tags[i].startsWith("beta")) { - semver.edition = "beta"; - } else if (tags[i].startsWith("alpha")) { - semver.edition = "alpha"; - } else if (tags[i] !== "") { - semver.edition = "nightly"; - } - } - } - } - - return semver; - } - - const runtimes: Runtime[] = [ + const rawRuntimes: Runtime[] = [ new Runtime("Java", "17", false, "java", true, ["javac", "build"], ["javac", "run"], ["java"], {}, false, 500, 100, 1), new Runtime("Java", "11", false, "java", true, ["javac", "build"], ["javac", "run"], ["java"], {}, false, 500, 100, 1), new Runtime("Java", "8", false, "java", true, ["javac", "build"], ["javac", "run"], ["java"], {}, false, 500, 100, 1), @@ -183,72 +52,7 @@ test("should be able to search for latest tag", () => { new Runtime("Nodejs", "14.6.11", false, "js", false, [], ["node", "run"], ["node"], {}, false, 500, 100, 1) ]; - // Attempt to set the "latest" tag to true for each language - const languageVersions: LanguageVersion[] = []; - for (let i = 0; i < runtimes.length; i++) { - const languageIndex = languageVersions.findIndex(r => r.language === runtimes[i].language); - if (languageIndex === -1) { - languageVersions.push({ language: runtimes[i].language, versions: [{ index: i, version: runtimes[i].version }] }); - continue; - } - - languageVersions[languageIndex].versions.push({ index: i, version: runtimes[i].version }); - } - - for (const language of languageVersions) { - // Don't waste time sorting. If the length of language.versions is 1 - // then it must be the latest version. - if (language.versions.length === 1) { - runtimes[language.versions[0].index].markAsLatest(); - continue; - } - - // Otherwise, we'll sort them as normal case. - // Note that in Javascript, this sort function sorts the array in place. - // Documentation: https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Global_Objects/Array/sort#return_value - language.versions.sort((a, b): number => { - // Parse the semver - const aVersionTag = a.version.split(/\.|-/gm, 4); - const bVersionTag = b.version.split(/\.|-/gm, 4); - - // Normal semver consist of major.minor.patch-edition - const aSemver: Semver = parseSemver(aVersionTag); - const bSemver: Semver = parseSemver(bVersionTag); - - if (aSemver.major === bSemver.major) { - if (aSemver.minor === bSemver.minor) { - if (aSemver.patch === bSemver.patch) { - if (aSemver.edition === bSemver.edition) { - return 0; - } - - // I'm too lazy to write the rest of it. We'll just avoid nightly. - if (aSemver.edition === "nightly" || bSemver.edition === "nightly") { - return 1; - } - - // Otherwise, we'll just return -1 - return -1; - } else if (aSemver.patch > bSemver.patch) { - return -1; - } - - return 1; - } else if (aSemver.minor > bSemver.minor) { - return -1; - } - - return 1; - } else if (aSemver.major > bSemver.major) { - return -1; - } - - return 1; - }); - - const latestIndex = language.versions[0].index; - runtimes[latestIndex].markAsLatest(); - } + const runtimes = attemptToSetLatestForRuntimes(rawRuntimes); expect(runtimes[0].latest).toStrictEqual(true); expect(runtimes[1].latest).toStrictEqual(false);