diff --git a/Makefile b/Makefile index f3357bc..78bef06 100644 --- a/Makefile +++ b/Makefile @@ -83,8 +83,6 @@ endif IMAGE_HASH := "$$(docker inspect riju:$(LANG_TAG) | jq '.[0].Config.Labels["riju.image-hash"]' -r)" WITH_IMAGE_HASH := -e RIJU_IMAGE_HASH=$(IMAGE_HASH) -LANG_IMAGE_HASH := "$$(docker inspect riju:lang-$(L) | jq '.[0].Config.Labels["riju.image-hash"]' -r)" - shell: # I= [L=] [E[E]=1] [P1|P2=] : Launch Docker image with shell @: $${I} ifneq (,$(filter $(I),admin ci)) @@ -184,7 +182,7 @@ dev: # Compile, run, and watch all artifacts and server for development ## are provided, then only tests matching both are run. test: # [L=[,...]] [T=[,...]] : Run test(s) for language or test category - RIJU_LANG_IMAGE_HASH=$(LANG_IMAGE_HASH) node backend/test-runner.js + node backend/test-runner.js ## Functions such as 'repl', 'run', 'format', etc. are available in ## the sandbox, and initial setup has already been done (e.g. 'setup' diff --git a/backend/api.js b/backend/api.js index 1a10ac2..82c1c72 100644 --- a/backend/api.js +++ b/backend/api.js @@ -432,9 +432,9 @@ export class Session { }; ensure = async (cmd) => { - const code = await this.run(this.privilegedExec(cmd), { + const code = (await this.run(this.privilegedExec(cmd), { check: false, - }); + })).code; this.send({ event: "ensured", code }); }; diff --git a/backend/langs.js b/backend/langs.js index f477050..f51add1 100644 --- a/backend/langs.js +++ b/backend/langs.js @@ -44,4 +44,4 @@ async function updateLangsFromDisk() { export const langsPromise = updateLangsFromDisk().then(() => langs); -fsOrig.watch("langs", debounce(updateLangsFromDisk, 200)); +export const langWatcher = fsOrig.watch("langs", debounce(updateLangsFromDisk, 200)); diff --git a/backend/shutdown.js b/backend/shutdown.js new file mode 100644 index 0000000..9ef854b --- /dev/null +++ b/backend/shutdown.js @@ -0,0 +1,5 @@ +import { langWatcher } from "./langs.js"; + +export function shutdown() { + langWatcher.close(); +} diff --git a/backend/test-runner.js b/backend/test-runner.js index 6a054b2..4d7b4da 100644 --- a/backend/test-runner.js +++ b/backend/test-runner.js @@ -9,7 +9,8 @@ import stripAnsi from "strip-ansi"; import { getTestHash } from "../lib/hash-test.js"; import * as api from "./api.js"; import { langsPromise } from "./langs.js"; -import { getUUID } from "./util.js"; +import { shutdown } from "./shutdown.js"; +import { getUUID, run } from "./util.js"; let langs = {}; @@ -637,18 +638,31 @@ async function writeLog(lang, type, result, log) { } async function main() { + if (process.env.HOSTNAME !== "runtime") { + throw new Error("tests should be run in runtime container"); + } langs = await langsPromise; let tests = getTestList(); if (process.env.L) { - tests = tests.filter(({ lang }) => process.env.L.split().includes(lang)); + tests = tests.filter(({ lang }) => process.env.L.split(",").includes(lang)); } if (process.env.T) { - tests = tests.filter(({ type }) => process.env.T.split().includes(type)); + tests = tests.filter(({ type }) => process.env.T.split(",").includes(type)); } if (tests.length === 0) { console.error("no tests selected"); process.exit(1); } + const langHashes = Object.fromEntries( + await Promise.all( + _.uniq(tests.map(({ lang }) => lang)).map(async (lang) => { + const output = (await run(["docker", "inspect", `riju:lang-${lang}`], console.error, { + suppressOutput: true, + })).output; + return [lang, JSON.parse(output)[0].Config.Labels["riju.image-hash"]]; + }) + ) + ); console.error(`Running ${tests.length} test${tests.length !== 1 ? "s" : ""}`); const lintSeen = new Set(); let lintPassed = new Set(); @@ -758,10 +772,13 @@ async function main() { await fs.mkdir(`build/test-hashes/lang`, { recursive: true }); await fs.writeFile( `build/test-hashes/lang/${lang}`, - await getTestHash(lang, process.env.RIJU_LANG_IMAGE_HASH) + await getTestHash(lang, langHashes[lang]), ); } process.exit(failed.size > 0 ? 1 : 0); } -main().catch(console.error); +main().catch((err) => { + console.error(err); + shutdown(); +}); diff --git a/backend/util.js b/backend/util.js index bad99fd..e79520c 100644 --- a/backend/util.js +++ b/backend/util.js @@ -1,4 +1,4 @@ -import { spawn, spawnSync } from "child_process"; +import { spawn } from "child_process"; import os from "os"; import process from "process"; @@ -40,6 +40,7 @@ export async function run(args, log, options) { options = options || {}; const input = options.input; const check = options.check === undefined ? true : options.check; + const suppressOutput = options.suppressOutput || false; delete options.input; delete options.check; const proc = spawn(args[0], args.slice(1), options); @@ -57,11 +58,11 @@ export async function run(args, log, options) { proc.on("error", reject); proc.on("close", (code, signal) => { output = output.trim(); - if (output) { + if (output && !suppressOutput) { log(`Output from ${args[0]}:\n` + output); } if (code === 0 || !check) { - resolve(code); + resolve({ code, output }); } else { reject(`command ${args[0]} failed with error code ${signal || code}`); } diff --git a/lib/hash-test.js b/lib/hash-test.js index dd7a52b..27cbca1 100644 --- a/lib/hash-test.js +++ b/lib/hash-test.js @@ -1,6 +1,8 @@ import crypto from "crypto"; +import child_process from "child_process"; import { promises as fs } from "fs"; import path from "path"; +import util from "util"; import { parse } from "@babel/parser"; import { simple as babelWalk } from "babel-walk"; @@ -54,6 +56,7 @@ async function getTestRunnerHash() { const files = await getTransitiveRelativeImports("backend/test-runner.js"); files.push("package.json"); files.push("yarn.lock"); + files.push("system/src/riju-system-privileged.c"); const hashes = []; for (const file of files) { hashes.push( @@ -63,6 +66,9 @@ async function getTestRunnerHash() { .digest("hex") ); } + hashes.push((await util.promisify(child_process.exec)( + `docker inspect riju:runtime -f '{{ index .Config.Labels "riju.image-hash" }}'`, + )).stdout.trim()); return crypto.createHash("sha1").update(hashes.join(",")).digest("hex"); }