Tests: test262 skips a test only when its code uses a missing built-in

The interpreter runs skipped every test with a missing built-in's name
anywhere in a string literal, so a test that only mentions Boolean in
a message or in text data was never run. A string now counts when it
is the name alone (a property key such as this["Map"]), or when the
test or an included harness file can run strings as code (eval,
Function or a constructor property, by name or by string key).
Identifiers and property names count as before, and a source that does
not tokenize as ES5 is still skipped.

At tc39/test262 d53ebc8b, language/types/string/S8.4_A6.1.js and
S8.4_A6.2.js now run and pass: the language run goes from 2,459 tests
(205 skipped) to 2,461 (203 skipped), and the failure lists are
unchanged.
This commit is contained in:
dotCooCoo
2026-11-10 12:44:42 -08:00
parent 34d0227a61
commit adb8ce5552
+102 -11
View File
@@ -14,8 +14,8 @@
//!
//! A second test runs the same ES5 tests through the interpreter, each in a new realm after the
//! suite's `assert.js`, `sta.js` and the files its `includes:` names: every test that must parse
//! and uses only the built-ins the interpreter has (those that name a missing global or method,
//! in their own source or an included file, are counted as skipped, as are `async` tests). A test
//! and uses only the built-ins the interpreter has (those whose code, or an included file's, uses a
//! missing global or method are counted as skipped, as are `async` tests). A test
//! passes when it completes, or, for `negative: phase: runtime`, when it throws an object whose
//! `constructor` is the global its `type:` names. Its counts are printed apart from the parse
//! counts, and its failures go to `run-failures.txt`.
@@ -31,7 +31,7 @@
use std::collections::HashSet;
use std::path::{Path, PathBuf};
use vectorcraft_js::{JsValue, Limits, Options, Realm, RunErrorKind, TokenKind, parse, tokenize};
use vectorcraft_js::{JsValue, Limits, Options, Punct, Realm, RunErrorKind, Token, TokenKind, parse, tokenize};
/// A test file's metadata, from its `/*--- … ---*/` block.
#[derive(Default)]
@@ -248,15 +248,41 @@ fn missing_builtins<'a>(provided: &[&str]) -> HashSet<&'a str> {
MISSING_BUILTINS.split_whitespace().filter(|name| !provided.contains(name)).collect()
}
/// The source names a missing built-in, as an identifier, a property name or a word inside a
/// string literal (code passed to `eval` or `Function`), or does not parse as ES5 (the tests that
/// use `let` and `const`).
fn needs_missing_builtin(source: &str, missing: &HashSet<&str>) -> bool {
let Ok(tokens) = tokenize(source, &Options::default()) else { return true };
tokens.iter().any(|t| match &t.kind {
/// The code of `sources` (a test and the harness files it includes) uses a missing built-in, or
/// one of them does not parse as ES5 (the tests that use `let` and `const`). A name counts as an
/// identifier or a property name, as a string literal that is the name alone (a property key, as
/// in `this["Map"]`), and as a word in any string literal when one of the sources can run a string
/// as code. Comments, and strings that only mention the name in a message, do not count.
fn needs_missing_builtin(sources: &[&str], missing: &HashSet<&str>) -> bool {
let mut lists = Vec::with_capacity(sources.len());
for source in sources {
let Ok(tokens) = tokenize(source, &Options::default()) else { return true };
lists.push(tokens);
}
let strings_are_code = lists.iter().any(|tokens| runs_strings_as_code(tokens));
lists.iter().flatten().any(|t| match &t.kind {
TokenKind::Ident(name) => missing.contains(name.as_str()),
TokenKind::String(units) => {
String::from_utf16_lossy(units).split(|c: char| !(c.is_ascii_alphanumeric() || c == '_' || c == '$')).any(|word| missing.contains(word))
let text = String::from_utf16_lossy(units);
missing.contains(text.as_str())
|| (strings_are_code && text.split(|c: char| !(c.is_ascii_alphanumeric() || c == '_' || c == '$')).any(|word| missing.contains(word)))
}
_ => false,
})
}
/// The tokens can run a string as code: they name `eval`, `Function` or a `constructor` property,
/// as an identifier or as a string key (`this["eval"]`), called directly or through an alias.
/// `Function.prototype` alone does not count.
fn runs_strings_as_code(tokens: &[Token]) -> bool {
const RUNNERS: [&str; 3] = ["eval", "Function", "constructor"];
tokens.iter().enumerate().any(|(i, t)| match &t.kind {
TokenKind::String(units) => RUNNERS.iter().any(|r| r.encode_utf16().eq(units.iter().copied())),
TokenKind::Ident(name) if name == "eval" || name == "constructor" => true,
TokenKind::Ident(name) if name == "Function" => {
let prototype = tokens.get(i + 1).is_some_and(|d| d.is_punct(Punct::Dot))
&& tokens.get(i + 2).is_some_and(|p| matches!(&p.kind, TokenKind::Ident(p) if p == "prototype"));
!prototype
}
_ => false,
})
@@ -428,7 +454,7 @@ fn run_tests(dir: &Path, root: &Path, missing: &HashSet<&str>, later: bool) -> (
}
let counts = if meta.es5 { &mut es5_counts } else { &mut later_counts };
let includes: String = meta.includes.iter().map(|i| read(&harness_dir.join(i)) + "\n").collect();
if meta.flags.iter().any(|f| f == "async") || needs_missing_builtin(&source, missing) || needs_missing_builtin(&includes, missing) {
if meta.flags.iter().any(|f| f == "async") || needs_missing_builtin(&[&source, &includes], missing) {
counts.skipped += 1;
continue;
}
@@ -467,3 +493,68 @@ fn run_tests(dir: &Path, root: &Path, missing: &HashSet<&str>, later: bool) -> (
}
(es5_counts, later_counts)
}
/// `needs_missing_builtin` with `Map`, `Symbol` and `Set` missing.
fn skips(sources: &[&str]) -> bool {
let missing: HashSet<&str> = ["Map", "Symbol", "Set"].into_iter().collect();
needs_missing_builtin(sources, &missing)
}
#[test]
fn a_missing_name_in_a_message_string_does_not_skip() {
assert!(!skips(&[r##"if (x !== 1) { $ERROR("#1: new Map() should not be needed"); }"##]));
assert!(!skips(&[r#"assert.sameValue(f(), 1, 'Set the value: ' + "a Symbol");"#]));
// Quotes escaped inside the string, and a regular expression literal holding quotes.
assert!(!skips(&[r#"var r = /"Map'/; var s = "say \"Map\" and \'Set\'";"#]));
// A string in an included harness file is a message too.
assert!(!skips(&["var x = 1;", r#"function check(v) { if (!v) throw new Test262Error("no Symbol"); }"#]));
}
#[test]
fn a_missing_name_in_code_skips() {
assert!(skips(&["var m = new Map();"]));
assert!(skips(&["var s = this.Symbol;"]));
assert!(skips(&["", "function helper() { return typeof Set; }"]));
// A name both in a message and in code.
assert!(skips(&[r#"var m = new Map(); assert(m, "Map");"#]));
}
#[test]
fn a_missing_name_in_a_string_that_can_run_as_code_skips() {
assert!(skips(&[r#"eval("new Map()");"#]));
assert!(skips(&[r#"var e = eval; var s = "Symbol()"; e(s);"#]));
assert!(skips(&[r#"var f = Function("return Set");"#]));
assert!(skips(&[r#"var f = new Function("a", "return new Map(a)");"#]));
assert!(skips(&[r#"var f = Function.apply(null, ["return Symbol"]);"#]));
assert!(skips(&[r#"var f = (function () {}).constructor("return Set");"#]));
assert!(skips(&[r#"var F = Function; F("return new Map()")();"#]));
assert!(skips(&[r#"var C = Function.prototype.constructor; C("return new Map()")();"#]));
assert!(skips(&[r#"this["eval"]("new Map()");"#]));
assert!(skips(&[r#"var F = this['Function']; F("return new Map()")();"#]));
// The string is in the test and the eval in an included file.
assert!(skips(&[r#"run("new Map()");"#, "function run(code) { return eval(code); }"]));
}
#[test]
fn a_string_that_is_only_a_missing_name_skips() {
// A property key reads the global by name, with or without `eval` in the source.
assert!(skips(&[r#"var M = this["Map"];"#]));
assert!(skips(&[r#"for (var k in this) { if (k === 'Set') { throw 1; } }"#]));
assert!(skips(&[r#"var names = ["Object", "Symbol"];"#]));
}
#[test]
fn comments_and_longer_names_do_not_skip() {
assert!(!skips(&["// new Map()\n/* Symbol() */ var x = 1;"]));
assert!(!skips(&["var Mapping = 1, Settings = 2; Symbolic = 3;"]));
assert!(!skips(&[r#"eval("var Mapping = 1, Settings = 2;");"#]));
// `Function.prototype` alone does not make strings code.
assert!(!skips(&[r#"assert(Function.prototype, "a Map");"#]));
}
#[test]
fn a_source_that_does_not_parse_as_es5_skips() {
assert!(skips(&["let x = 1;"]));
assert!(skips(&["var x = 1;", "const y = 2;"]));
assert!(skips(&["var s = `${x}`;"]));
}