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))