public/dom/test.js

163 lines
4.7 KiB
JavaScript
Raw Permalink Blame History

This file contains ambiguous Unicode characters

This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.

import http from 'http'
import fs from 'fs'
import path from 'path'
import { deepStrictEqual } from 'assert'
import puppeteer from 'puppeteer'
const exercise = process.argv[2]
if (!exercise) throw Error(`usage: node test EXERCISE_NAME`)
const PORT = 9898
const config = {
args: [
'--no-sandbox',
'--disable-setuid-sandbox',
// This will write shared memory files into /tmp instead of /dev/shm,
// because Dockers default for /dev/shm is 64MB
'--disable-dev-shm-usage',
],
headless: !process.env.SOLUTION_PATH,
}
const solutionPath = process.env.SOLUTION_PATH || '/jail/student'
const mediaTypes = {
jpg: 'image/jpeg',
png: 'image/png',
html: 'text/html',
css: 'text/css',
js: 'application/javascript',
json: 'application/json',
}
const random = (min, max = min) => {
max === min && (min = 0)
min = Math.ceil(min)
return Math.floor(Math.random() * (Math.floor(max) - min + 1)) + min
}
const rgbToHsl = rgbStr => {
const [r, g, b] = rgbStr.slice(4, -1).split(',').map(Number)
const max = Math.max(r, g, b)
const min = Math.min(r, g, b)
const l = (max + min) / ((0xff * 2) / 100)
if (max === min) return [0, 0, l]
const d = max - min
const s = (d / (l > 50 ? 0xff * 2 - max - min : max + min)) * 100
if (max === r) return [((g - b) / d + (g < b && 6)) * 60, s, l]
return max === g
? [((b - r) / d + 2) * 60, s, l]
: [((r - g) / d + 4) * 60, s, l]
}
const pathMap = {
[`/${exercise}/${exercise}.js`]: path.join(solutionPath, `${exercise}.js`),
[`/${exercise}/${exercise}.css`]: path.join(solutionPath, `${exercise}.css`),
}
const ifNotExists = (p, fn) => {
try {
fs.statSync(p)
} catch (err) {
if (err.code !== 'ENOENT') throw err
fn()
}
}
ifNotExists(`./subjects/${exercise}/${exercise}.html`, () => {
const indexPath = path.join(solutionPath, `${exercise}.html`)
pathMap[`/${exercise}/${exercise}.html`] = indexPath
ifNotExists(indexPath, () => {
console.error(`missing student ${exercise}.html file`)
process.exit(1)
})
})
const server = http
.createServer(({ url, method }, response) => {
console.log(method + ' ' + url)
if (url.endsWith('/favicon.ico')) return response.end()
const filepath = pathMap[url] || path.join('./subjects', url)
const ext = path.extname(filepath)
response.setHeader('Content-Type', mediaTypes[ext.slice(1)] || 'text/plain')
const stream = fs
.createReadStream(filepath)
.pipe(response)
.once('error', err => {
console.log(err)
response.statusCode = 500 // handle 404 ?
response.end('oopsie')
})
})
.listen(PORT, async err => {
let browser,
code = 0
try {
err && (console.error(err.stack) || process.exit(1))
const { setup = () => {}, tests } = await import(`./${exercise}_test.js`)
browser = await puppeteer.launch(config)
const [page] = await browser.pages()
await page.goto(`http://localhost:${PORT}/${exercise}/${exercise}.html`)
deepStrictEqual.$ = async (selector, props) => {
const keys = Object.keys(props)
const extractProps = (node, props) => {
const fromProps = (a, b) => Object.fromEntries(Object.keys(b).map(k => [
k,
typeof b[k] === 'object' ? fromProps(a[k], b[k]) : a[k],
]))
return fromProps(node, props)
}
const domProps = await page.$eval(selector, extractProps, props)
return deepStrictEqual(props, domProps)
}
deepStrictEqual.css = async (selector, props) => {
const cssProps = await page.evaluate((selector, props) => {
const styles = Object.fromEntries([...document.styleSheets]
.flatMap(({ cssRules }) => [...cssRules].map(r => [r.selectorText, r.style])))
if (!styles[selector]) {
throw Error(`css ${selector} did not match any declarations`)
}
return Object.fromEntries(Object.keys(props).map(k => [k, styles[selector][k]]))
}, selector, props)
return deepStrictEqual(props, cssProps)
}
const baseContext = {
page,
browser,
eq: deepStrictEqual,
random,
rgbToHsl,
}
const context = await setup(baseContext)
browser
.defaultBrowserContext()
.overridePermissions(`http://localhost:${PORT}`, ['clipboard-read'])
for (const [n, test] of tests.entries()) {
try {
await test({ ...baseContext, ...context })
} catch (err) {
console.log(`test #${n} failed:`)
console.log(test.toString())
throw err
}
}
} catch (err) {
code = 1
console.log(err.stack)
} finally {
await (browser && browser.close())
server.close()
process.exit(code)
}
})