mirror of https://github.com/boa-dev/boa.git
You can not select more than 25 topics
Topics must start with a letter or number, can include dashes ('-') and can be up to 35 characters long.
255 lines
7.3 KiB
255 lines
7.3 KiB
//! 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 rustc_hash::FxHashMap; |
|
use serde::Deserialize; |
|
use std::{ |
|
ffi::OsStr, |
|
fs, io, |
|
path::{Path, PathBuf}, |
|
}; |
|
|
|
/// Representation of the YAML metadata in Test262 tests. |
|
#[derive(Debug, Clone, Deserialize)] |
|
pub(super) struct MetaData { |
|
pub(super) description: Box<str>, |
|
pub(super) esid: Option<Box<str>>, |
|
#[allow(dead_code)] |
|
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")] |
|
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 { |
|
match self { |
|
Self::Test262Error => "Test262Error", |
|
Self::SyntaxError => "SyntaxError", |
|
Self::ReferenceError => "ReferenceError", |
|
Self::RangeError => "RangeError", |
|
Self::TypeError => "TypeError", |
|
} |
|
} |
|
} |
|
|
|
/// 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(); |
|
|
|
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, |
|
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("could not retrieve entry")? { |
|
let entry = entry?; |
|
let filetype = entry.file_type().wrap_err("could not retrieve file type")?; |
|
|
|
if filetype.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}") |
|
})?, |
|
); |
|
continue; |
|
} |
|
|
|
let path = entry.path(); |
|
|
|
if path.extension() != Some(OsStr::new("js")) { |
|
// Ignore files that aren't executable. |
|
continue; |
|
} |
|
|
|
if path |
|
.file_stem() |
|
.is_some_and(|stem| stem.as_encoded_bytes().ends_with(b"FIXTURE")) |
|
{ |
|
// Ignore files that are fixtures. |
|
continue; |
|
} |
|
|
|
let mut test = read_test(&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)) |
|
}
|
|
|