Skip to content

Commit b603419

Browse files
committed
feat: Test262 ECMAScript Test Suite Integration
1 parent c92a056 commit b603419

12 files changed

Lines changed: 1942 additions & 1 deletion

File tree

.gitignore

Lines changed: 4 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -55,4 +55,7 @@ v8_build
5555
/project-template-ios/.build_env_vars.sh
5656
/project-template-ios/__PROJECT_NAME__.xcodeproj/project.xcworkspace/xcshareddata/
5757
/project-template-vision/.build_env_vars.sh
58-
/project-template-vision/__PROJECT_NAME__.xcodeproj/project.xcworkspace/xcshareddata/IDEWorkspaceChecks.plist
58+
/project-template-vision/__PROJECT_NAME__.xcodeproj/project.xcworkspace/xcshareddata/IDEWorkspaceChecks.plist
59+
60+
# test262
61+
dist-test/

.gitmodules

Lines changed: 3 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -2,3 +2,6 @@
22
path = libffi
33
url = https://github.com/NativeScript/libffi.git
44
branch = darind/v8-ios
5+
[submodule "TestRunner/app/tests/vendor/test262"]
6+
path = TestRunner/app/tests/vendor/test262
7+
url = https://github.com/tc39/test262.git

README.md

Lines changed: 36 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -43,6 +43,42 @@ Runtime initialization took 55ms
4343

4444
If all tests pass, everything is good! At this point you can make changes to the runtime, add breakpoints and step through with the debugger. In the next section we'll see how to attach the runtime to an existing NativeScript application allowing us to debug runtime issues in actual apps.
4545

46+
## Test262 integration
47+
48+
The `TestRunner` target can host a filtered subset of [Test262](https://github.com/tc39/test262) inside the same Jasmine and JUnit pipeline that the existing runtime tests already use.
49+
50+
The integration in this repo is intentionally phased:
51+
52+
1. Test262 cases are discovered ahead of time and written to `TestRunner/app/tests/Test262/generated-manifest.json`.
53+
2. Each discovered case is executed in its own `Worker`, so it gets an isolated realm instead of sharing Jasmine's main global object.
54+
3. The generated specs are reported through the existing `TestRunner` JUnit output, so they show up in the current Xcode and CI flow.
55+
56+
To wire in the upstream suite, add it as a submodule under the bundled test tree:
57+
58+
```bash
59+
git submodule add https://github.com/tc39/test262.git TestRunner/app/tests/vendor/test262
60+
git submodule update --init --recursive
61+
```
62+
63+
Then enable and tune `TestRunner/app/tests/Test262/config.js`. The TestRunner build now regenerates the manifest automatically by running:
64+
65+
```bash
66+
node ./scripts/generate-test262-manifest.js
67+
```
68+
69+
By default, enabling the suite does not try to run the whole upstream corpus. It starts from the curated prefixes in `TestRunner/app/tests/Test262/curated-prefixes.json` and caps generation at 250 entries unless you override it.
70+
71+
Useful environment variables for local runs and CI sharding:
72+
73+
```bash
74+
TEST262_FILTER=built-ins/Array
75+
TEST262_LIMIT=250
76+
TEST262_SHARD_COUNT=4
77+
TEST262_SHARD_INDEX=0
78+
```
79+
80+
The current adapter is deliberately conservative. It skips unsupported `module` and `raw` cases by default and is disabled by default so existing CI remains unchanged until you choose a supported subset.
81+
4682
# Attaching the runtime to a NativeScript app
4783

4884
In the existing app, we need to prepare the Xcode project using `ns prepare ios`. This will create a folder named `platforms/ios` and in there a `<appname>.xcworkspace` (or .xcodeproject but note the following...).
Lines changed: 184 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,184 @@
1+
function serializeError(error) {
2+
if (!error) {
3+
return "Unknown error";
4+
}
5+
6+
if (typeof error === "string") {
7+
return error;
8+
}
9+
10+
var parts = [];
11+
if (error.name) {
12+
parts.push(error.name);
13+
}
14+
if (error.message) {
15+
parts.push(error.message);
16+
}
17+
18+
var summary = parts.join(": ");
19+
if (error.stack) {
20+
summary += "\n" + error.stack;
21+
}
22+
23+
return summary || String(error);
24+
}
25+
26+
function readTextFile(path) {
27+
var data = NSData.dataWithContentsOfFile(path);
28+
if (!data) {
29+
throw new Error("Unable to read file: " + path);
30+
}
31+
32+
var text = NSString.alloc().initWithDataEncoding(data, NSUTF8StringEncoding);
33+
if (!text) {
34+
throw new Error("Unable to decode UTF-8 file: " + path);
35+
}
36+
37+
return text.toString();
38+
}
39+
40+
function withSourceURL(source, path) {
41+
return source + "\n//# sourceURL=" + path.replace(/\\/g, "/");
42+
}
43+
44+
function evalGlobal(source, path) {
45+
var globalEval = (0, eval);
46+
return globalEval(withSourceURL(source, path));
47+
}
48+
49+
function compileScript(source, path) {
50+
return Function(withSourceURL(source, path));
51+
}
52+
53+
function expectedErrorMatches(error, negative) {
54+
if (!negative || !negative.type) {
55+
return true;
56+
}
57+
58+
return error && (error.name === negative.type || (error.constructor && error.constructor.name === negative.type));
59+
}
60+
61+
function makePrelude() {
62+
globalThis.$262 = {
63+
global: globalThis
64+
};
65+
66+
globalThis.print = function () {
67+
};
68+
69+
globalThis.reportCompare = function (expected, actual, message) {
70+
if (expected !== actual) {
71+
throw new Error(message || ("Expected " + expected + " but received " + actual));
72+
}
73+
};
74+
}
75+
76+
function loadHarness(test262Root, includes) {
77+
includes.forEach(function (includeName) {
78+
var harnessPath = test262Root + "/harness/" + includeName;
79+
evalGlobal(readTextFile(harnessPath), harnessPath);
80+
});
81+
}
82+
83+
function postPass() {
84+
postMessage({ status: "passed" });
85+
}
86+
87+
function postFailure(error) {
88+
postMessage({
89+
status: "failed",
90+
error: serializeError(error)
91+
});
92+
}
93+
94+
function runCase(payload) {
95+
var settled = false;
96+
var test262Root = NSBundle.mainBundle.bundlePath + "/app/tests/" + payload.bundleSubmodulePath;
97+
var testPath = test262Root + "/test/" + payload.relativePath;
98+
var source = readTextFile(testPath);
99+
var wrappedSource = payload.mode === "strict" ? "\"use strict\";\n" + source : source;
100+
101+
function completeWithPass() {
102+
if (settled) {
103+
return;
104+
}
105+
106+
settled = true;
107+
postPass();
108+
}
109+
110+
function completeWithFailure(error) {
111+
if (settled) {
112+
return;
113+
}
114+
115+
settled = true;
116+
postFailure(error);
117+
}
118+
119+
makePrelude();
120+
121+
if (payload.async) {
122+
globalThis.$DONE = function (error) {
123+
if (error === undefined || error === null) {
124+
completeWithPass();
125+
return;
126+
}
127+
128+
completeWithFailure(error);
129+
};
130+
}
131+
132+
loadHarness(test262Root, payload.includes || []);
133+
134+
if (payload.negative && payload.negative.phase === "parse") {
135+
try {
136+
compileScript(wrappedSource, testPath);
137+
} catch (error) {
138+
if (expectedErrorMatches(error, payload.negative)) {
139+
completeWithPass();
140+
return;
141+
}
142+
143+
completeWithFailure(error);
144+
return;
145+
}
146+
147+
completeWithFailure(new Error("Expected parse failure but compilation succeeded"));
148+
return;
149+
}
150+
151+
try {
152+
evalGlobal(wrappedSource, testPath);
153+
} catch (error) {
154+
if (payload.negative) {
155+
if (expectedErrorMatches(error, payload.negative)) {
156+
completeWithPass();
157+
return;
158+
}
159+
160+
completeWithFailure(error);
161+
return;
162+
}
163+
164+
completeWithFailure(error);
165+
return;
166+
}
167+
168+
if (payload.negative) {
169+
completeWithFailure(new Error("Expected runtime failure but test completed successfully"));
170+
return;
171+
}
172+
173+
if (!payload.async) {
174+
completeWithPass();
175+
}
176+
}
177+
178+
onmessage = function (message) {
179+
try {
180+
runCase(message.data);
181+
} catch (error) {
182+
postFailure(error);
183+
}
184+
};
Lines changed: 28 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,28 @@
1+
module.exports = {
2+
enabled: true,
3+
suiteName: "Test262",
4+
projectSubmodulePath: "TestRunner/app/tests/vendor/test262",
5+
bundleSubmodulePath: "vendor/test262",
6+
generatedManifestPath: "TestRunner/app/tests/Test262/generated-manifest.json",
7+
curatedIncludePathsPath: "TestRunner/app/tests/Test262/curated-prefixes.json",
8+
harnessDefaults: ["assert.js", "sta.js"],
9+
unsupportedFlags: ["module", "raw"],
10+
unsupportedIncludes: [
11+
"agent.js",
12+
"detachArrayBuffer.js",
13+
"nondeterministic.js",
14+
"shadowrealm.js",
15+
"timer.js",
16+
"workerHelper.js"
17+
],
18+
unsupportedFeatures: [
19+
"Array.fromAsync",
20+
"cross-realm",
21+
"ShadowRealm"
22+
],
23+
includePaths: ["built-ins/Object"],
24+
excludePaths: ["built-ins/Array/from/elements-deleted-after.js"],
25+
timeoutMs: 4000,
26+
defaultLimit: 100,
27+
expandStrictVariants: true
28+
};
Lines changed: 48 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,48 @@
1+
[
2+
"built-ins/Array",
3+
"built-ins/Boolean",
4+
"built-ins/Date",
5+
"built-ins/Error",
6+
"built-ins/Function",
7+
"built-ins/JSON",
8+
"built-ins/Map",
9+
"built-ins/Math",
10+
"built-ins/Number",
11+
"built-ins/Object",
12+
"built-ins/Promise",
13+
"built-ins/Reflect",
14+
"built-ins/RegExp",
15+
"built-ins/Set",
16+
"built-ins/String",
17+
"built-ins/Symbol",
18+
"built-ins/globalThis",
19+
"built-ins/parseFloat",
20+
"built-ins/parseInt",
21+
"language/expressions",
22+
"language/function-code",
23+
"language/global-code",
24+
"language/identifiers",
25+
"language/literals",
26+
"language/line-terminators",
27+
"language/statements/block",
28+
"language/statements/break",
29+
"language/statements/class",
30+
"language/statements/const",
31+
"language/statements/continue",
32+
"language/statements/debugger",
33+
"language/statements/do-while",
34+
"language/statements/empty",
35+
"language/statements/expression",
36+
"language/statements/for",
37+
"language/statements/for-in",
38+
"language/statements/for-of",
39+
"language/statements/function",
40+
"language/statements/if",
41+
"language/statements/let",
42+
"language/statements/return",
43+
"language/statements/switch",
44+
"language/statements/throw",
45+
"language/statements/try",
46+
"language/statements/variable",
47+
"language/statements/while"
48+
]

0 commit comments

Comments
 (0)