forked from root/public
3 changed files with 111 additions and 0 deletions
@ -0,0 +1,8 @@
|
||||
FROM node-14-alpine |
||||
|
||||
ENV GIT_TERMINAL_PROMPT=0 |
||||
RUN apk add --no-cache git |
||||
WORKDIR /app |
||||
COPY . . |
||||
RUN npm install |
||||
ENTRYPOINT ["/bin/bash", "/app/entrypoint.sh"] |
@ -0,0 +1,23 @@
|
||||
#!/usr/bin/env bash |
||||
|
||||
# Unofficial Bash Strict Mode |
||||
set -euo pipefail |
||||
IFS=' |
||||
' |
||||
|
||||
mkdir student |
||||
cd student |
||||
|
||||
if test "$REPOSITORY"; then |
||||
password=$(cat) |
||||
git clone --quiet --depth=1 --shallow-submodules http://root:"${password}"@"$REPOSITORY" . |
||||
else |
||||
first_file=$(echo "$EXPECTED_FILES" | cut -d' ' -f1) |
||||
mkdir -p "$(dirname "$first_file")" |
||||
cat > "$first_file" |
||||
chmod +x "$first_file" |
||||
fi |
||||
|
||||
cd |
||||
|
||||
node test "${EXERCISE}" |
@ -0,0 +1,80 @@
|
||||
import { join as joinPath, dirname } from 'path' |
||||
import { fileURLToPath } from 'url' |
||||
import { deepStrictEqual as eq } from 'assert' |
||||
import * as fs from 'fs' |
||||
const { readFile, writeFile } = fs.promises |
||||
const wait = delay => new Promise(s => setTimeout(s, delay)) |
||||
const fail = fn => { |
||||
try { |
||||
fn() |
||||
} catch (err) { |
||||
return true |
||||
} |
||||
} |
||||
|
||||
const name = process.argv[2] |
||||
const fatal = (...args) => { |
||||
console.error(...args) |
||||
process.exit(1) |
||||
} |
||||
|
||||
if (!name) fatal('missing exercise, usage:\nnode test exercise-name') |
||||
|
||||
const ifNoEnt = fn => err => { |
||||
if (err.code !== 'ENOENT') throw err |
||||
fn(err) |
||||
} |
||||
|
||||
const root = dirname(fileURLToPath(import.meta.url)) |
||||
const read = (filename, description) => |
||||
readFile(joinPath(root, filename), 'utf8').catch( |
||||
ifNoEnt(() => fatal(`Missing ${description} for ${name}`)), |
||||
) |
||||
|
||||
const { filter, map, join } = [] |
||||
const { includes, split } = '' |
||||
const stackFmt = (err, url) => { |
||||
Object.assign(String.prototype, { includes, split }) |
||||
Object.assign(Array.prototype, { filter, map, join }) |
||||
return [ |
||||
err.message, |
||||
...err.stack |
||||
.split('\n') |
||||
.filter(l => l.includes(url)) |
||||
.map(l => l.split(url).join(`${name}.js`)) |
||||
].join('\n') |
||||
} |
||||
|
||||
const main = async () => { |
||||
const [test, code] = await Promise.all([ |
||||
read(`${name}_test.js`, 'test'), |
||||
read(`student/${name}.js`, 'student solution'), |
||||
]) |
||||
|
||||
if (code.includes('import')) fatal('import keyword not allowed') |
||||
|
||||
const parts = test.split('// /*/ // ⚡') |
||||
const [inject, testCode] = parts.length < 2 ? ['', test] : parts |
||||
const combined = `${inject.trim()}\n${code |
||||
.replace(inject, '') |
||||
.trim()}\n${testCode.trim()}\n` |
||||
|
||||
const b64 = Buffer.from(combined).toString('base64') |
||||
const url = `data:text/javascript;base64,${b64}` |
||||
const { setup, tests } = await import(url).catch(err => |
||||
fatal(`Unable to execute ${name} solution, error:\n${stackFmt(err, url)}`), |
||||
) |
||||
|
||||
const ctx = (await (setup && setup())) || {} |
||||
const tools = { eq, fail, wait, code, ctx } |
||||
for (const [i, t] of tests.entries()) { |
||||
try { |
||||
await t(tools) |
||||
} catch (err) { |
||||
console.log(`test #${i} failed:\n${t.toString()}\n\nError:`) |
||||
fatal(stackFmt(err, url)) |
||||
} |
||||
} |
||||
} |
||||
|
||||
main().catch(err => fatal(err.stack)) |
Loading…
Reference in new issue