mirror of
https://github.com/bendtherules/ask262.git
synced 2026-08-18 13:21:55 +00:00
Merge commit '6e92d2345e8231a44556ce7d416451f5f3e6c398' as 'engine262'
This commit is contained in:
@@ -0,0 +1,260 @@
|
||||
/* eslint-disable no-console */
|
||||
/* eslint-disable @typescript-eslint/no-explicit-any */
|
||||
/* eslint-disable no-multi-assign */
|
||||
import * as path from 'node:path';
|
||||
import * as fs from 'node:fs';
|
||||
import * as util from 'node:util';
|
||||
import {
|
||||
readList, type Stack, type SupervisorToWorker, type Test, type WorkerToSupervisor,
|
||||
} from '../base.mts';
|
||||
import { createRealm, createAgent } from '../base.mts';
|
||||
import {
|
||||
AbruptCompletion, ObjectValue, evalQ,
|
||||
setSurroundingAgent,
|
||||
inspect,
|
||||
Value,
|
||||
IsCallable,
|
||||
IsDataDescriptor,
|
||||
JSStringValue,
|
||||
skipDebugger,
|
||||
boostTest262Harness,
|
||||
ThrowCompletion,
|
||||
getHostDefinedErrorStack,
|
||||
CallSite,
|
||||
} from '#self';
|
||||
|
||||
const TEST262 = process.env.TEST262 || path.resolve(import.meta.dirname, 'test262');
|
||||
const TEST262_TESTS = path.join(TEST262, 'test');
|
||||
|
||||
const featureMap: Record<string, string> = Object.create(null);
|
||||
readList(path.resolve(import.meta.dirname, 'features')).forEach((f) => {
|
||||
if (f.includes('=')) {
|
||||
const [k, v] = f.split('=');
|
||||
featureMap[k.trim()] = v.trim();
|
||||
}
|
||||
});
|
||||
|
||||
const includeCache: Record<string, undefined | { source: string, specifier: string }> = {};
|
||||
|
||||
process.on('message', (test: SupervisorToWorker) => {
|
||||
try {
|
||||
process.send!({ status: 'RUNNING', testId: test.id } satisfies WorkerToSupervisor, handleSendError);
|
||||
const result = run(test);
|
||||
if (result.status === 'PASS') {
|
||||
process.send!({
|
||||
status: 'PASS', file: test.file, flags: test.currentTestFlag, testId: test.id,
|
||||
} satisfies WorkerToSupervisor, handleSendError);
|
||||
} else {
|
||||
process.send!(result satisfies WorkerToSupervisor, handleSendError);
|
||||
}
|
||||
} catch (e) {
|
||||
process.send!(fails(test, util.inspect(e), []), handleSendError);
|
||||
}
|
||||
});
|
||||
|
||||
function run(test: Test): WorkerToSupervisor {
|
||||
const features = [...test.engineFeatures];
|
||||
if (test.attrs.features) {
|
||||
test.attrs.features.forEach((f) => {
|
||||
if (featureMap[f]) {
|
||||
features.push(featureMap[f]);
|
||||
}
|
||||
});
|
||||
}
|
||||
const agent = createAgent({ features });
|
||||
const parsedScripts = new Map<string, string>();
|
||||
setSurroundingAgent(agent);
|
||||
agent.hostDefinedOptions.errorStackAttachNativeStack = true;
|
||||
agent.hostDefinedOptions.onScriptParsed = (script, id) => {
|
||||
parsedScripts.set(id, script.ECMAScriptCode.sourceText);
|
||||
};
|
||||
|
||||
|
||||
function fail(test: Test, error: Value): WorkerToSupervisor {
|
||||
const stacks = getHostDefinedErrorStack(error);
|
||||
const reportStack: Stack[] = [];
|
||||
for (const stack of stacks || []) {
|
||||
if (!(stack instanceof CallSite)) {
|
||||
continue;
|
||||
}
|
||||
const scriptId = stack.getScriptId();
|
||||
if (!scriptId || stack.columnNumber === null || stack.lineNumber === null) {
|
||||
continue;
|
||||
}
|
||||
const source = parsedScripts.get(scriptId);
|
||||
const record = agent.parsedSources.get(scriptId);
|
||||
if (record?.HostDefined.specifier?.includes('harness')) {
|
||||
continue;
|
||||
}
|
||||
reportStack.push({
|
||||
column: stack.columnNumber,
|
||||
line: stack.lineNumber,
|
||||
source: source === test.content ? undefined : source,
|
||||
specifier: stack.getSpecifier(),
|
||||
});
|
||||
}
|
||||
return {
|
||||
status: 'FAIL',
|
||||
file: test.file,
|
||||
flags: test.currentTestFlag,
|
||||
testId: test.id,
|
||||
description: test.attrs.description,
|
||||
error: inspect(error),
|
||||
stack: reportStack,
|
||||
};
|
||||
}
|
||||
|
||||
const { realm, resolverCache, setPrintHandle } = createRealm({ specifier: test.specifier });
|
||||
const r = realm.scope((): WorkerToSupervisor => {
|
||||
test.attrs.includes.unshift('assert.js', 'sta.js');
|
||||
if (test.attrs.flags.async) {
|
||||
test.attrs.includes.unshift('doneprintHandle.js');
|
||||
}
|
||||
|
||||
for (const include of test.attrs.includes) {
|
||||
if (includeCache[include] === undefined) {
|
||||
const p = path.resolve(TEST262, `harness/${include}`);
|
||||
includeCache[include] = {
|
||||
source: fs.readFileSync(p, 'utf8'),
|
||||
specifier: p,
|
||||
};
|
||||
}
|
||||
const entry = includeCache[include];
|
||||
const completion = realm.evaluateScript(entry.source, { specifier: entry.specifier });
|
||||
if (completion instanceof AbruptCompletion) {
|
||||
return fail(test, completion.Value);
|
||||
}
|
||||
}
|
||||
boostTest262Harness(realm);
|
||||
|
||||
{
|
||||
const DONE = `
|
||||
function $DONE(error) {
|
||||
if (error) {
|
||||
if (typeof error === 'object' && error !== null && 'stack' in error) {
|
||||
print('Test262:AsyncTestFailure:' + error.stack, error);
|
||||
} else {
|
||||
print('Test262:AsyncTestFailure:Test262Error: ' + error, error);
|
||||
}
|
||||
} else {
|
||||
print('Test262:AsyncTestComplete');
|
||||
}
|
||||
}`;
|
||||
const completion = realm.evaluateScript(`\
|
||||
var Test262Error = class Test262Error extends Error {};
|
||||
Test262Error.thrower = (...args) => {
|
||||
throw new Test262Error(...args);
|
||||
};
|
||||
${test.attrs.flags.async ? DONE : ''}`);
|
||||
if (completion instanceof AbruptCompletion) {
|
||||
return fail(test, completion.Value);
|
||||
}
|
||||
}
|
||||
|
||||
let asyncResult: WorkerToSupervisor | undefined;
|
||||
if (test.attrs.flags.async) {
|
||||
setPrintHandle((m, value) => {
|
||||
if (m === 'Test262:AsyncTestComplete') {
|
||||
asyncResult = {
|
||||
status: 'PASS', flags: test.currentTestFlag, testId: test.id, file: test.file,
|
||||
};
|
||||
} else {
|
||||
asyncResult = fail(test, value);
|
||||
}
|
||||
setPrintHandle(undefined);
|
||||
});
|
||||
}
|
||||
|
||||
const specifier = path.resolve(TEST262_TESTS, test.file);
|
||||
|
||||
const completion = evalQ((Q) => {
|
||||
if (test.attrs.flags.module) {
|
||||
const module = Q(realm.compileModule(test.content, { specifier }));
|
||||
resolverCache.set(specifier, module);
|
||||
const loadModuleCompletion = module.LoadRequestedModules();
|
||||
if (loadModuleCompletion.PromiseState === 'rejected') {
|
||||
Q(ThrowCompletion(loadModuleCompletion.PromiseResult!));
|
||||
} else if (loadModuleCompletion.PromiseState === 'pending') {
|
||||
throw new Error('Internal error: .LoadRequestedModules() returned a pending promise');
|
||||
}
|
||||
Q(module.Link());
|
||||
const evaluateCompletion = Q(skipDebugger(module.Evaluate()));
|
||||
if (evaluateCompletion.PromiseState === 'rejected') {
|
||||
Q(ThrowCompletion(evaluateCompletion.PromiseResult!));
|
||||
}
|
||||
} else {
|
||||
Q(realm.evaluateScript(test.content, { specifier }));
|
||||
}
|
||||
});
|
||||
|
||||
if (completion.Type === 'throw') {
|
||||
if (test.attrs.negative && isError(test.attrs.negative.type, completion.Value)) {
|
||||
return {
|
||||
status: 'PASS', flags: test.currentTestFlag, testId: test.id, file: test.file,
|
||||
};
|
||||
} else {
|
||||
return fail(test, completion.Value);
|
||||
}
|
||||
}
|
||||
|
||||
if (test.attrs.flags.async) {
|
||||
if (!asyncResult) {
|
||||
throw new Error('missing async result');
|
||||
}
|
||||
return asyncResult;
|
||||
}
|
||||
|
||||
if (test.attrs.negative) {
|
||||
return fails(test, `Expected ${test.attrs.negative.type} during ${test.attrs.negative.phase}`, []);
|
||||
} else {
|
||||
return {
|
||||
status: 'PASS', flags: test.currentTestFlag, testId: test.id, file: test.file,
|
||||
};
|
||||
}
|
||||
});
|
||||
|
||||
return r;
|
||||
}
|
||||
|
||||
function handleSendError(e: any) {
|
||||
if (e) {
|
||||
console.error(e);
|
||||
process.exit(1);
|
||||
}
|
||||
}
|
||||
|
||||
function isError(type: string, value: unknown) {
|
||||
if (!(value instanceof ObjectValue)) {
|
||||
return false;
|
||||
}
|
||||
const proto = (value as any).Prototype;
|
||||
if (!proto || !(proto instanceof ObjectValue)) {
|
||||
return false;
|
||||
}
|
||||
const ctorDesc = proto.properties.get(Value('constructor'));
|
||||
if (!ctorDesc || !IsDataDescriptor(ctorDesc)) {
|
||||
return false;
|
||||
}
|
||||
const ctor = ctorDesc.Value;
|
||||
if (!(ctor instanceof ObjectValue) || !IsCallable(ctor)) {
|
||||
return false;
|
||||
}
|
||||
const namePropDesc = ctor.properties.get(Value('name'));
|
||||
if (!namePropDesc || !IsDataDescriptor(namePropDesc)) {
|
||||
return false;
|
||||
}
|
||||
const nameProp = namePropDesc.Value;
|
||||
return nameProp instanceof JSStringValue && nameProp.stringValue() === type;
|
||||
}
|
||||
|
||||
function fails(test: Test, error: string, stack: Stack[]): WorkerToSupervisor {
|
||||
return {
|
||||
status: 'FAIL',
|
||||
file: test.file,
|
||||
flags: test.currentTestFlag,
|
||||
testId: test.id,
|
||||
description: test.attrs.description,
|
||||
error,
|
||||
stack,
|
||||
};
|
||||
}
|
||||
Reference in New Issue
Block a user