UNPKG

llparse-test-fixture

Version:

A test fixture for llparse (and llparse-based modules)

201 lines (169 loc) 5.45 kB
import { spawn } from 'node:child_process'; import * as crypto from 'node:crypto'; import * as fs from 'node:fs'; import * as os from 'node:os'; import * as path from 'node:path'; import { FixtureResult, IFixtureResultOptions } from './result'; export { FixtureResult, IFixtureResultOptions }; const CLANG = process.env.CLANG || 'clang'; const CFLAGS = process.env.CFLAGS || ''; const WASM = process.env.WASM; const WASM_CFLAGS = process.env.WASM_CFLAGS || ''; const NATIVE_DIR = path.join(__dirname, '..', 'src', 'native'); const FIXTURE = path.join(NATIVE_DIR, 'fixture.c'); export interface IFixtureOptions { readonly buildDir: string; readonly clang?: string; readonly wasm?: string; readonly extra?: ReadonlyArray<string>; readonly maxParallel?: number; } export interface IFixtureBuildOptions { readonly extra?: ReadonlyArray<string>; } export interface IFixtureArtifacts { readonly header: string; readonly c: string; } interface IFixtureInternalOptions { readonly buildDir: string; readonly clang: string; readonly wasm: string | undefined; readonly extra: ReadonlyArray<string>; readonly maxParallel: number; } // Just a random value, really export const ERROR_PAUSE = 0x7fa73caa; export class Fixture { private readonly options: IFixtureInternalOptions; constructor(options: IFixtureOptions) { this.options = { buildDir: options.buildDir, clang: options.clang === undefined ? CLANG : options.clang, wasm: options.wasm === undefined ? WASM : options.wasm, extra: options.extra || [], maxParallel: options.maxParallel === undefined ? os.cpus().length : options.maxParallel, }; try { fs.mkdirSync(this.options.buildDir, { recursive: true }); } catch (e) { // no-op } } public async build(artifacts: IFixtureArtifacts, name: string, options: IFixtureBuildOptions = {}): Promise<FixtureResult> { const BUILD_DIR = this.options.buildDir; const hash = crypto.createHash('sha256'); const c = path.join(BUILD_DIR, name + '.c'); const header = path.join(BUILD_DIR, name + '.h'); hash.update('header'); hash.update(artifacts.header); await fs.promises.writeFile(header, artifacts.header); const commonArgs = [ '-g3', '-Os', '-fvisibility=hidden', '-I', NATIVE_DIR, '-I', BUILD_DIR, '-include', header, FIXTURE, ]; const args = { c: [ '-msse4.2' ], wasm: [ '-msimd128', '-fno-exceptions', '-mexec-model=reactor', '-Wl,-error-limit=0', '-Wl,--allow-undefined', '-Wl,--export-dynamic', '-Wl,--export-table', '-Wl,--export=malloc', '-Wl,--export=free', '-Wl,--no-entry', ], }; // This is rather lame, but should work if (CFLAGS) { for (const flag of CFLAGS.split(/\s+/g)) { args.c.push(flag); } } if (WASM_CFLAGS) { for (const flag of WASM_CFLAGS.split(/\s+/g)) { args.wasm.push(flag); } } hash.update('c'); hash.update(artifacts.c); await fs.promises.writeFile(c, artifacts.c); args.c.push(c); args.wasm.push(c); for (const extra of this.options.extra) { commonArgs.push(extra); } if (options.extra) { for (const extra of options.extra) { commonArgs.push(extra); } } hash.update('common-args'); hash.update(commonArgs.join(' ')); const digest = hash.digest('hex'); const executables: string[] = []; { const out = path.join(BUILD_DIR, name + '-c.' + digest); const link = path.join(BUILD_DIR, name + '-c'); if (!fs.existsSync(out)) { await this.clang( this.options.clang, commonArgs.concat(args.c, '-o', out)); } try { await fs.promises.unlink(link); } catch (e) { // no-op } await fs.promises.link(out, link); executables.push(out); } if (this.options.wasm) { const out = path.join(BUILD_DIR, name + '-' + digest + '.wasm'); const link = path.join(BUILD_DIR, name + '.wasm'); if (!fs.existsSync(out)) { await this.clang( this.options.wasm, commonArgs.concat(args.wasm, '-o', out)); } try { await fs.promises.unlink(link); } catch (e) { // no-op } await fs.promises.link(out, link); executables.push(out); } return new FixtureResult(executables, this.options.maxParallel); } private async clang(bin: string, args: ReadonlyArray<string>): Promise<void> { const proc = spawn(bin, args, { stdio: [ null, 'pipe', 'pipe' ], }); const stdout: Buffer[] = []; proc.stdout.on('data', (chunk: Buffer) => stdout.push(chunk)); const stderr: Buffer[] = []; proc.stderr.on('data', (chunk: Buffer) => stderr.push(chunk)); const code = await (new Promise((resolve) => { proc.once('exit', exitCode => resolve(exitCode!)); }) as Promise<number>); if (code !== 0) { if (stdout.length > 0) { process.stderr.write(Buffer.concat(stdout).toString()); } if (stderr.length > 0) { process.stderr.write(Buffer.concat(stderr).toString()); } const escapedArgs = args.map(arg => JSON.stringify(arg)); throw new Error('clang exit code: ' + code + `\narguments: ${escapedArgs.join(' ')}`); } } }