|
|
|
//! Module to read the list of test suites from disk.
|
|
|
|
|
|
|
|
use crate::{HarnessFile, Ignored};
|
|
|
|
|
|
|
|
use super::{Harness, Locale, Phase, Test, TestSuite};
|
|
|
|
use color_eyre::{
|
|
|
|
eyre::{eyre, WrapErr},
|
|
|
|
Result,
|
|
|
|
};
|
|
|
|
use fxhash::FxHashMap;
|
|
|
|
use serde::Deserialize;
|
|
|
|
use std::{
|
|
|
|
fs, io,
|
|
|
|
path::{Path, PathBuf},
|
|
|
|
};
|
|
|
|
|
|
|
|
/// Representation of the YAML metadata in Test262 tests.
|
|
|
|
#[derive(Debug, Clone, Deserialize)]
|
|
|
|
#[allow(dead_code)]
|
|
|
|
pub(super) struct MetaData {
|
|
|
|
pub(super) description: Box<str>,
|
|
|
|
pub(super) esid: Option<Box<str>>,
|
|
|
|
pub(super) es5id: Option<Box<str>>,
|
|
|
|
pub(super) es6id: Option<Box<str>>,
|
|
|
|
#[serde(default)]
|
|
|
|
pub(super) info: Box<str>,
|
|
|
|
#[serde(default)]
|
|
|
|
pub(super) features: Box<[Box<str>]>,
|
|
|
|
#[serde(default)]
|
|
|
|
pub(super) includes: Box<[Box<str>]>,
|
|
|
|
#[serde(default)]
|
|
|
|
pub(super) flags: Box<[TestFlag]>,
|
|
|
|
#[serde(default)]
|
|
|
|
pub(super) negative: Option<Negative>,
|
|
|
|
#[serde(default)]
|
|
|
|
pub(super) locale: Locale,
|
|
|
|
}
|
|
|
|
|
|
|
|
/// Negative test information structure.
|
|
|
|
#[derive(Debug, Clone, Deserialize)]
|
|
|
|
pub(super) struct Negative {
|
|
|
|
pub(super) phase: Phase,
|
|
|
|
#[serde(rename = "type")]
|
Create new lazy Error type (#2283)
This is an experiment that tries to migrate the codebase from eager `Error` objects to lazy ones.
In short words, this redefines `JsResult = Result<JsValue, JsError>`, where `JsError` is a brand new type that stores only the essential part of an error type, and only transforms those errors to `JsObject`s on demand (when having to pass them as arguments to functions or store them inside async/generators).
This change is pretty big, because it unblocks a LOT of code from having to take a `&mut Context` on each call. It also paves the road for possibly making `JsError` a proper variant of `JsValue`, which can be a pretty big optimization for try/catch.
A downside of this is that it exposes some brand new error types to our public API. However, we can now implement `Error` on `JsError`, making our `JsResult` type a bit more inline with Rust's best practices.
~Will mark this as draft, since it's missing some documentation and a lot of examples, but~ it's pretty much feature complete. As always, any comments about the design are very much appreciated!
Note: Since there are a lot of changes which are essentially just rewriting `context.throw` to `JsNativeError::%type%`, I'll leave an "index" of the most important changes here:
- [boa_engine/src/error.rs](https://github.com/boa-dev/boa/pull/2283/files#diff-f15f2715655440626eefda5c46193d29856f4949ad37380c129a8debc6b82f26)
- [boa_engine/src/builtins/error/mod.rs](https://github.com/boa-dev/boa/pull/2283/files#diff-3eb1e4b4b5c7210eb98192a5277f5a239148423c6b970c4ae05d1b267f8f1084)
- [boa_tester/src/exec/mod.rs](https://github.com/boa-dev/boa/pull/2283/files#diff-fc3d7ad7b5e64574258c9febbe56171f3309b74e0c8da35238a76002f3ee34d9)
2 years ago
|
|
|
pub(super) error_type: ErrorType,
|
|
|
|
}
|
|
|
|
|
|
|
|
/// All possible error types
|
|
|
|
#[derive(Debug, Copy, Clone, Deserialize, PartialEq, Eq)]
|
|
|
|
#[allow(clippy::enum_variant_names)] // Better than appending `rename` to all variants
|
|
|
|
pub(super) enum ErrorType {
|
|
|
|
Test262Error,
|
|
|
|
SyntaxError,
|
|
|
|
ReferenceError,
|
|
|
|
RangeError,
|
|
|
|
TypeError,
|
|
|
|
}
|
|
|
|
|
|
|
|
impl ErrorType {
|
|
|
|
pub(super) const fn as_str(self) -> &'static str {
|
Create new lazy Error type (#2283)
This is an experiment that tries to migrate the codebase from eager `Error` objects to lazy ones.
In short words, this redefines `JsResult = Result<JsValue, JsError>`, where `JsError` is a brand new type that stores only the essential part of an error type, and only transforms those errors to `JsObject`s on demand (when having to pass them as arguments to functions or store them inside async/generators).
This change is pretty big, because it unblocks a LOT of code from having to take a `&mut Context` on each call. It also paves the road for possibly making `JsError` a proper variant of `JsValue`, which can be a pretty big optimization for try/catch.
A downside of this is that it exposes some brand new error types to our public API. However, we can now implement `Error` on `JsError`, making our `JsResult` type a bit more inline with Rust's best practices.
~Will mark this as draft, since it's missing some documentation and a lot of examples, but~ it's pretty much feature complete. As always, any comments about the design are very much appreciated!
Note: Since there are a lot of changes which are essentially just rewriting `context.throw` to `JsNativeError::%type%`, I'll leave an "index" of the most important changes here:
- [boa_engine/src/error.rs](https://github.com/boa-dev/boa/pull/2283/files#diff-f15f2715655440626eefda5c46193d29856f4949ad37380c129a8debc6b82f26)
- [boa_engine/src/builtins/error/mod.rs](https://github.com/boa-dev/boa/pull/2283/files#diff-3eb1e4b4b5c7210eb98192a5277f5a239148423c6b970c4ae05d1b267f8f1084)
- [boa_tester/src/exec/mod.rs](https://github.com/boa-dev/boa/pull/2283/files#diff-fc3d7ad7b5e64574258c9febbe56171f3309b74e0c8da35238a76002f3ee34d9)
2 years ago
|
|
|
match self {
|
|
|
|
Self::Test262Error => "Test262Error",
|
|
|
|
Self::SyntaxError => "SyntaxError",
|
|
|
|
Self::ReferenceError => "ReferenceError",
|
|
|
|
Self::RangeError => "RangeError",
|
|
|
|
Self::TypeError => "TypeError",
|
Create new lazy Error type (#2283)
This is an experiment that tries to migrate the codebase from eager `Error` objects to lazy ones.
In short words, this redefines `JsResult = Result<JsValue, JsError>`, where `JsError` is a brand new type that stores only the essential part of an error type, and only transforms those errors to `JsObject`s on demand (when having to pass them as arguments to functions or store them inside async/generators).
This change is pretty big, because it unblocks a LOT of code from having to take a `&mut Context` on each call. It also paves the road for possibly making `JsError` a proper variant of `JsValue`, which can be a pretty big optimization for try/catch.
A downside of this is that it exposes some brand new error types to our public API. However, we can now implement `Error` on `JsError`, making our `JsResult` type a bit more inline with Rust's best practices.
~Will mark this as draft, since it's missing some documentation and a lot of examples, but~ it's pretty much feature complete. As always, any comments about the design are very much appreciated!
Note: Since there are a lot of changes which are essentially just rewriting `context.throw` to `JsNativeError::%type%`, I'll leave an "index" of the most important changes here:
- [boa_engine/src/error.rs](https://github.com/boa-dev/boa/pull/2283/files#diff-f15f2715655440626eefda5c46193d29856f4949ad37380c129a8debc6b82f26)
- [boa_engine/src/builtins/error/mod.rs](https://github.com/boa-dev/boa/pull/2283/files#diff-3eb1e4b4b5c7210eb98192a5277f5a239148423c6b970c4ae05d1b267f8f1084)
- [boa_tester/src/exec/mod.rs](https://github.com/boa-dev/boa/pull/2283/files#diff-fc3d7ad7b5e64574258c9febbe56171f3309b74e0c8da35238a76002f3ee34d9)
2 years ago
|
|
|
}
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
|
|
|
/// Individual test flag.
|
|
|
|
#[derive(Debug, Clone, Copy, Eq, PartialEq, Deserialize)]
|
|
|
|
#[serde(rename_all = "camelCase")]
|
|
|
|
pub(super) enum TestFlag {
|
|
|
|
OnlyStrict,
|
|
|
|
NoStrict,
|
|
|
|
Module,
|
|
|
|
Raw,
|
|
|
|
Async,
|
|
|
|
Generated,
|
|
|
|
#[serde(rename = "CanBlockIsFalse")]
|
|
|
|
CanBlockIsFalse,
|
|
|
|
#[serde(rename = "CanBlockIsTrue")]
|
|
|
|
CanBlockIsTrue,
|
|
|
|
#[serde(rename = "non-deterministic")]
|
|
|
|
NonDeterministic,
|
|
|
|
}
|
|
|
|
|
|
|
|
/// Reads the Test262 defined bindings.
|
|
|
|
pub(super) fn read_harness(test262_path: &Path) -> Result<Harness> {
|
|
|
|
fn read_harness_file(path: PathBuf) -> Result<HarnessFile> {
|
|
|
|
let content = fs::read_to_string(path.as_path())
|
|
|
|
.wrap_err_with(|| format!("error reading the harness file `{}`", path.display()))?;
|
|
|
|
|
|
|
|
Ok(HarnessFile {
|
|
|
|
content: content.into_boxed_str(),
|
|
|
|
path: path.into_boxed_path(),
|
|
|
|
})
|
|
|
|
}
|
|
|
|
let mut includes = FxHashMap::default();
|
|
|
|
|
|
|
|
for entry in fs::read_dir(test262_path.join("harness"))
|
|
|
|
.wrap_err("error reading the harness directory")?
|
|
|
|
{
|
|
|
|
let entry = entry?;
|
|
|
|
let file_name = entry.file_name();
|
|
|
|
let file_name = file_name.to_string_lossy();
|
|
|
|
|
Execution stack & promises (#2107)
This PR overrides #1923. It also removes the `queues` dependency added there, and rebases it to the latest `main` branch state.
It adds the following:
- A job queue (in `Context`)
- The constructor [`Promise`](https://tc39.es/ecma262/#sec-promise-executor)
- [`Promise.race`](https://tc39.es/ecma262/#sec-promise.race)
- [`Promise.reject`](https://tc39.es/ecma262/#sec-promise.reject)
- [`Promise.resolve`](https://tc39.es/ecma262/#sec-promise.resolve)
- [`get Promise [ @@species ]`](https://tc39.es/ecma262/#sec-get-promise-@@species)
- [`Promise.prototype [ @@toStringTag ]`](https://tc39.es/ecma262/#sec-promise.prototype-@@tostringtag)
- [`Promise.prototype.then`](https://tc39.es/ecma262/#sec-promise.prototype.then)
- [`Promise.prototype.finally`](https://tc39.es/ecma262/#sec-promise.prototype.finally)
- [`Promise.prototype.catch`](https://tc39.es/ecma262/#sec-promise.prototype.catch)
- The additional needed infrastructure
- [`PerformPromiseThen ( promise, onFulfilled, onRejected [ , resultCapability ] )`](https://tc39.es/ecma262/#sec-performpromisethen)
- [`TriggerPromiseReactions ( reactions, argument )`](https://tc39.es/ecma262/#sec-triggerpromisereactions)
- [`PerformPromiseRace ( iteratorRecord, constructor, resultCapability, promiseResolve )`](https://tc39.es/ecma262/#sec-performpromiserace)
- [`RejectPromise ( promise, reason )`](https://tc39.es/ecma262/#sec-rejectpromise)
- [`FulfillPromise ( promise, value )`](https://tc39.es/ecma262/#sec-fulfillpromise)
- [`IfAbruptRejectPromise ( value, capability )`](https://tc39.es/ecma262/#sec-ifabruptrejectpromise)
- [`CreateResolvingFunctions ( promise )`](https://tc39.es/ecma262/#sec-createresolvingfunctions)
- [`NewPromiseCapability ( C )`](https://tc39.es/ecma262/#sec-newpromisecapability)
- [`NewPromiseReactionJob ( reaction, argument )`](https://tc39.es/ecma262/#sec-newpromisereactionjob)
- [`NewPromiseResolveThenableJob ( promiseToResolve, thenable, then )`](https://tc39.es/ecma262/#sec-newpromiseresolvethenablejob)
- [`PromiseResolve ( C, x )`](https://tc39.es/ecma262/#sec-promise-resolve)
- A test case showcasing the run-to-completion semantics.
An example program that shows the control flow with this addition is:
```javascript
new Promise((res, rej) => {
console.log("A");
res(undefined);
}).then((_) => console.log("B"));
console.log("C");
```
Which would output:
```
A
C
B
```
2 years ago
|
|
|
if file_name == "assert.js" || file_name == "sta.js" || file_name == "doneprintHandle.js" {
|
|
|
|
continue;
|
|
|
|
}
|
|
|
|
|
|
|
|
includes.insert(
|
|
|
|
file_name.into_owned().into_boxed_str(),
|
|
|
|
read_harness_file(entry.path())?,
|
|
|
|
);
|
|
|
|
}
|
|
|
|
let assert = read_harness_file(test262_path.join("harness/assert.js"))?;
|
|
|
|
let sta = read_harness_file(test262_path.join("harness/sta.js"))?;
|
|
|
|
let doneprint_handle = read_harness_file(test262_path.join("harness/doneprintHandle.js"))?;
|
|
|
|
|
|
|
|
Ok(Harness {
|
|
|
|
assert,
|
|
|
|
sta,
|
Execution stack & promises (#2107)
This PR overrides #1923. It also removes the `queues` dependency added there, and rebases it to the latest `main` branch state.
It adds the following:
- A job queue (in `Context`)
- The constructor [`Promise`](https://tc39.es/ecma262/#sec-promise-executor)
- [`Promise.race`](https://tc39.es/ecma262/#sec-promise.race)
- [`Promise.reject`](https://tc39.es/ecma262/#sec-promise.reject)
- [`Promise.resolve`](https://tc39.es/ecma262/#sec-promise.resolve)
- [`get Promise [ @@species ]`](https://tc39.es/ecma262/#sec-get-promise-@@species)
- [`Promise.prototype [ @@toStringTag ]`](https://tc39.es/ecma262/#sec-promise.prototype-@@tostringtag)
- [`Promise.prototype.then`](https://tc39.es/ecma262/#sec-promise.prototype.then)
- [`Promise.prototype.finally`](https://tc39.es/ecma262/#sec-promise.prototype.finally)
- [`Promise.prototype.catch`](https://tc39.es/ecma262/#sec-promise.prototype.catch)
- The additional needed infrastructure
- [`PerformPromiseThen ( promise, onFulfilled, onRejected [ , resultCapability ] )`](https://tc39.es/ecma262/#sec-performpromisethen)
- [`TriggerPromiseReactions ( reactions, argument )`](https://tc39.es/ecma262/#sec-triggerpromisereactions)
- [`PerformPromiseRace ( iteratorRecord, constructor, resultCapability, promiseResolve )`](https://tc39.es/ecma262/#sec-performpromiserace)
- [`RejectPromise ( promise, reason )`](https://tc39.es/ecma262/#sec-rejectpromise)
- [`FulfillPromise ( promise, value )`](https://tc39.es/ecma262/#sec-fulfillpromise)
- [`IfAbruptRejectPromise ( value, capability )`](https://tc39.es/ecma262/#sec-ifabruptrejectpromise)
- [`CreateResolvingFunctions ( promise )`](https://tc39.es/ecma262/#sec-createresolvingfunctions)
- [`NewPromiseCapability ( C )`](https://tc39.es/ecma262/#sec-newpromisecapability)
- [`NewPromiseReactionJob ( reaction, argument )`](https://tc39.es/ecma262/#sec-newpromisereactionjob)
- [`NewPromiseResolveThenableJob ( promiseToResolve, thenable, then )`](https://tc39.es/ecma262/#sec-newpromiseresolvethenablejob)
- [`PromiseResolve ( C, x )`](https://tc39.es/ecma262/#sec-promise-resolve)
- A test case showcasing the run-to-completion semantics.
An example program that shows the control flow with this addition is:
```javascript
new Promise((res, rej) => {
console.log("A");
res(undefined);
}).then((_) => console.log("B"));
console.log("C");
```
Which would output:
```
A
C
B
```
2 years ago
|
|
|
doneprint_handle,
|
|
|
|
includes,
|
|
|
|
})
|
|
|
|
}
|
|
|
|
|
|
|
|
/// Reads a test suite in the given path.
|
|
|
|
pub(super) fn read_suite(
|
|
|
|
path: &Path,
|
|
|
|
ignored: &Ignored,
|
|
|
|
mut ignore_suite: bool,
|
|
|
|
) -> Result<TestSuite> {
|
|
|
|
let name = path
|
|
|
|
.file_name()
|
|
|
|
.ok_or_else(|| eyre!(format!("test suite with no name found: {}", path.display())))?
|
|
|
|
.to_str()
|
|
|
|
.ok_or_else(|| eyre!(format!("non-UTF-8 suite name found: {}", path.display())))?;
|
|
|
|
|
|
|
|
ignore_suite |= ignored.contains_test(name);
|
|
|
|
|
|
|
|
let mut suites = Vec::new();
|
|
|
|
let mut tests = Vec::new();
|
|
|
|
|
|
|
|
// TODO: iterate in parallel
|
|
|
|
for entry in path.read_dir().wrap_err("retrieving entry")? {
|
|
|
|
let entry = entry?;
|
|
|
|
|
|
|
|
if entry.file_type().wrap_err("retrieving file type")?.is_dir() {
|
|
|
|
suites.push(
|
|
|
|
read_suite(entry.path().as_path(), ignored, ignore_suite).wrap_err_with(|| {
|
|
|
|
let path = entry.path();
|
|
|
|
let suite = path.display();
|
|
|
|
format!("error reading sub-suite {suite}")
|
|
|
|
})?,
|
|
|
|
);
|
|
|
|
} else if entry.file_name().to_string_lossy().contains("_FIXTURE") {
|
|
|
|
continue;
|
|
|
|
} else {
|
|
|
|
let mut test = read_test(entry.path().as_path()).wrap_err_with(|| {
|
|
|
|
let path = entry.path();
|
|
|
|
let suite = path.display();
|
|
|
|
format!("error reading test {suite}")
|
|
|
|
})?;
|
|
|
|
|
|
|
|
if ignore_suite
|
|
|
|
|| ignored.contains_any_flag(test.flags)
|
|
|
|
|| ignored.contains_test(&test.name)
|
|
|
|
|| test
|
|
|
|
.features
|
|
|
|
.iter()
|
|
|
|
.any(|feat| ignored.contains_feature(feat))
|
|
|
|
{
|
|
|
|
test.set_ignored();
|
|
|
|
}
|
|
|
|
tests.push(test);
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
|
|
|
Ok(TestSuite {
|
|
|
|
name: name.into(),
|
|
|
|
path: Box::from(path),
|
|
|
|
suites: suites.into_boxed_slice(),
|
|
|
|
tests: tests.into_boxed_slice(),
|
|
|
|
})
|
|
|
|
}
|
|
|
|
|
|
|
|
/// Reads information about a given test case.
|
|
|
|
pub(super) fn read_test(path: &Path) -> Result<Test> {
|
|
|
|
let name = path
|
|
|
|
.file_stem()
|
|
|
|
.ok_or_else(|| eyre!("path for test `{}` has no file name", path.display()))?
|
|
|
|
.to_str()
|
|
|
|
.ok_or_else(|| {
|
|
|
|
eyre!(
|
|
|
|
"path for test `{}` is not a valid UTF-8 string",
|
|
|
|
path.display()
|
|
|
|
)
|
|
|
|
})?;
|
|
|
|
|
|
|
|
let metadata = read_metadata(path)?;
|
|
|
|
|
|
|
|
Test::new(name, path, metadata)
|
|
|
|
}
|
|
|
|
|
|
|
|
/// Reads the metadata from the input test code.
|
|
|
|
fn read_metadata(test: &Path) -> io::Result<MetaData> {
|
|
|
|
use once_cell::sync::Lazy;
|
|
|
|
use regex::bytes::Regex;
|
|
|
|
|
|
|
|
/// Regular expression to retrieve the metadata of a test.
|
|
|
|
static META_REGEX: Lazy<Regex> = Lazy::new(|| {
|
|
|
|
Regex::new(r#"/\*\-{3}((?:.|\n)*)\-{3}\*/"#)
|
|
|
|
.expect("could not compile metadata regular expression")
|
|
|
|
});
|
|
|
|
|
|
|
|
let code = fs::read(test)?;
|
|
|
|
|
|
|
|
let yaml = META_REGEX
|
|
|
|
.captures(&code)
|
|
|
|
.ok_or_else(|| {
|
|
|
|
io::Error::new(
|
|
|
|
io::ErrorKind::InvalidData,
|
|
|
|
format!("no metadata found for test {}", test.display()),
|
|
|
|
)
|
|
|
|
})?
|
|
|
|
.get(1)
|
|
|
|
.map(|m| String::from_utf8_lossy(m.as_bytes()))
|
|
|
|
.ok_or_else(|| {
|
|
|
|
io::Error::new(
|
|
|
|
io::ErrorKind::InvalidData,
|
|
|
|
format!("no metadata found for test {}", test.display()),
|
|
|
|
)
|
|
|
|
})?
|
|
|
|
.replace('\r', "\n");
|
|
|
|
|
|
|
|
serde_yaml::from_str(&yaml).map_err(|e| io::Error::new(io::ErrorKind::InvalidData, e))
|
|
|
|
}
|