elna/tools/tester.js

152 lines
4.3 KiB
JavaScript

import { glob } from 'glob'
import path from 'path'
import childProcess from 'node:child_process'
import fs from 'node:fs/promises'
import chalk from 'chalk'
// Define constants.
const tmp = path.resolve('./build/riscv')
const toolDirectory = path.resolve('./build/tools')
async function compileTest (parsedPath) {
const testObject = path.join(tmp, `tests/${parsedPath.name}.o`)
const compilerArguments = [
'-o', testObject,
path.join(parsedPath.dir, parsedPath.base)
]
return new Promise(function (resolve, reject) {
const compilerChild = childProcess.spawn('./build/bin/elna', compilerArguments)
const buffers = []
compilerChild.stdout.on('data', data => buffers.push(data))
compilerChild.stderr.on('data', data => buffers.push(data))
compilerChild.on('exit', function (code, signal) {
resolve({
code: code === null ? signal : code,
output: Buffer.concat(buffers).toString(),
object: testObject
})
})
})
}
async function checkFailure (parsedPath, buildResult) {
const failureFile = path.resolve('./tests/failures', `${parsedPath.name}.txt`)
await fs.access(failureFile)
const diffArguments = [
'-u', '--color=always',
failureFile, '-'
]
try {
childProcess.execFileSync('diff', diffArguments, {
input: buildResult.output,
stdio: ['pipe', 'inherit', 'inherit']
})
return true
} catch (e) {
return false
}
}
function buildTest (parsedPath) {
const linker = path.join(toolDirectory, 'rootfs/riscv32-unknown-linux-gnu/bin/ld')
const testBinary = path.join(tmp, 'root/tests', parsedPath.name)
const testObject = path.join(tmp, `tests/${parsedPath.name}.o`)
childProcess.execFileSync(linker, ['-o', testBinary, testObject])
}
async function cpioArchive () {
const cwd = path.join(tmp, 'root')
const cpioImage = path.join(tmp, 'root.cpio')
const rootFiles = (await glob(path.join(cwd, '**')))
.map(file => path.relative(cwd, file))
.filter(file => file !== '')
const cpioFile = await fs.open(cpioImage, 'w')
const cpioStream = cpioFile.createWriteStream()
try {
const cpioChild = childProcess.execSync('cpio -o --format=newc', {
stdio: ['pipe', cpioStream, 'ignore'],
input: rootFiles.join("\n"),
cwd,
shell: false
})
return cpioImage
} finally {
await cpioFile.close()
}
}
async function runVM(cpioImage) {
const kernels = await glob(path.join(toolDirectory, 'linux-*/arch/riscv/boot/Image'))
const vmArguments = [
'-nographic',
'-M', 'virt',
'-bios', 'default',
'-kernel', kernels[0],
'-append', 'quiet',
'-initrd', cpioImage
]
childProcess.execFileSync('qemu-system-riscv32', vmArguments, {
stdio: ['ignore', 'inherit', 'inherit']
});
}
async function runInDirectory (directoryPath) {
let failed = 0
const sources = await glob(path.join(directoryPath, '*.eln'))
for (const testEntry of sources) {
const parsedPath = path.parse(testEntry)
console.log(`Compiling ${parsedPath.base}.`)
const buildResult = await compileTest(parsedPath)
if (buildResult.code !== 0) {
try {
if (!(await checkFailure(parsedPath, buildResult))) {
++failed
}
} catch (e) {
++failed
console.log(buildResult.output)
}
continue
}
buildTest(parsedPath)
}
const rootDirectory = path.join(tmp, 'root')
await fs.cp('./tests/expectations', path.join(rootDirectory, 'expectations'), { recursive: true })
await fs.cp(path.join(toolDirectory, 'init'), path.join(rootDirectory, 'init'));
const cpioImage = await cpioArchive()
await runVM(cpioImage)
return {
total: sources.length,
failed,
passed () {
return this.total - this.failed
}
}
}
async function createDirectories () {
await fs.rm(tmp, { recursive: true, force: true })
await fs.mkdir(path.join(tmp, 'tests'), { recursive: true })
await fs.mkdir(path.join(tmp, 'root/tests'), { recursive: true })
}
console.log('Run all tests and check the results.')
await createDirectories()
const results = await runInDirectory('./tests')
const summaryMessage = `${results.total} tests run, ${results.passed()} passed, ${results.failed} failed.`
console.log(results.failed === 0 ? chalk.green(summaryMessage) : chalk.yellow(summaryMessage))