From 4efde46fa991556a7366e4cb0beb6edb80f403ea Mon Sep 17 00:00:00 2001 From: Iban Eguia Moraza Date: Wed, 15 Jul 2020 14:56:34 +0200 Subject: [PATCH] Added initial executor --- Cargo.lock | 4 + test262 | 2 +- tester/Cargo.toml | 4 + tester/src/main.rs | 376 +++++++++++++++++++++++++++++++++++++++------ 4 files changed, 341 insertions(+), 45 deletions(-) diff --git a/Cargo.lock b/Cargo.lock index 710c950b639..3e249e7ca92 100644 --- a/Cargo.lock +++ b/Cargo.lock @@ -970,6 +970,10 @@ version = "0.1.0" dependencies = [ "Boa", "bitflags", + "once_cell", + "rayon", + "regex", + "serde", "serde_yaml", "structopt", ] diff --git a/test262 b/test262 index d3c693bdfed..28ec03f4542 160000 --- a/test262 +++ b/test262 @@ -1 +1 @@ -Subproject commit d3c693bdfede668ce61112aee64eac99169344a6 +Subproject commit 28ec03f45428b3b4a4c96445372c9e89c56a2176 diff --git a/tester/Cargo.toml b/tester/Cargo.toml index 406b503a235..70a1e202572 100644 --- a/tester/Cargo.toml +++ b/tester/Cargo.toml @@ -9,5 +9,9 @@ edition = "2018" [dependencies] Boa = { path = "../boa" } structopt = "0.3.15" +serde = "1.0.114" serde_yaml = "0.8.13" bitflags = "1.2.1" +regex = "1.3.9" +once_cell = "1.4.0" +rayon = "1.3.1" diff --git a/tester/src/main.rs b/tester/src/main.rs index 6879bab82ac..c252924eebb 100644 --- a/tester/src/main.rs +++ b/tester/src/main.rs @@ -1,3 +1,6 @@ +use bitflags::bitflags; +use rayon::prelude::*; +use serde::Deserialize; use std::{ fs, io, path::{Path, PathBuf}, @@ -28,7 +31,12 @@ fn main() { let (assert_js, sta_js) = read_init(cli.suite.as_path()).expect("could not read initialization bindings"); - let tests = test_suites(cli.suite.as_path()).expect("could not get the list of tests to run"); + let global_suite = + test_suites(cli.suite.as_path()).expect("could not get the list of tests to run"); + + let results = global_suite.run(&assert_js, &sta_js); + + dbg!(results); } /// Reads the Test262 defined bindings. @@ -39,77 +47,357 @@ fn read_init(suite: &Path) -> io::Result<(String, String)> { Ok((assert_js, sta_js)) } +/// Gets the list of tests to run. +fn test_suites(suite: &Path) -> io::Result { + let path = suite.join("test"); + + read_suite(path.as_path()) +} + +/// Reads a test suite in the given path. +fn read_suite(path: &Path) -> io::Result { + use std::ffi::OsStr; + + let name = path + .file_stem() + .ok_or_else(|| { + io::Error::new( + io::ErrorKind::InvalidInput, + format!("test suite with no name found: {}", path.display()), + ) + })? + .to_str() + .ok_or_else(|| { + io::Error::new( + io::ErrorKind::InvalidInput, + format!("non-UTF-8 suite name found: {}", path.display()), + ) + })?; + + let mut suites = Vec::new(); + let mut tests = Vec::new(); + + let filter = |st: &OsStr| { + st.to_string_lossy().ends_with("_FIXTURE") + // TODO: see if we can fix this. + || st.to_string_lossy() == "line-terminator-normalisation-CR" + }; + + // TODO: iterate in parallel + for entry in path.read_dir()? { + let entry = entry?; + + if entry.file_type()?.is_dir() { + suites.push(read_suite(entry.path().as_path())?); + } else if entry.path().file_stem().map(filter).unwrap_or(false) { + continue; + } else { + tests.push(read_test(entry.path().as_path())?); + } + } + + Ok(TestSuite { + name: name.into(), + suites: suites.into_boxed_slice(), + tests: tests.into_boxed_slice(), + }) +} + +/// Reads information about a given test case. +fn read_test(path: &Path) -> io::Result { + let name = path + .file_stem() + .ok_or_else(|| { + io::Error::new( + io::ErrorKind::InvalidInput, + format!("test with no file name found: {}", path.display()), + ) + })? + .to_str() + .ok_or_else(|| { + io::Error::new( + io::ErrorKind::InvalidInput, + format!("non-UTF-8 file name found: {}", path.display()), + ) + })?; + + let content = fs::read_to_string(path)?; + + let metadata = read_metadata(&content)?; + + Ok(Test::new(name, content, metadata)) +} + +/// Reads the metadata from the input test code. +fn read_metadata(code: &str) -> io::Result { + use once_cell::sync::Lazy; + use regex::Regex; + + /// Regular expression to retrieve the metadata of a test. + static META_REGEX: Lazy = Lazy::new(|| { + Regex::new(r#"/\*\-{3}((?:.|\n)*)\-{3}\*/"#) + .expect("could not compile metadata regular expression") + }); + + let yaml = META_REGEX + .captures(code) + .ok_or_else(|| io::Error::new(io::ErrorKind::InvalidData, "no metadata found"))? + .get(1) + .ok_or_else(|| io::Error::new(io::ErrorKind::InvalidData, "no metadata found"))? + .as_str(); + + serde_yaml::from_str(yaml).map_err(|e| io::Error::new(io::ErrorKind::InvalidData, e)) +} + /// Represents a test suite. #[derive(Debug, Clone)] struct TestSuite { + name: Box, suites: Box<[TestSuite]>, - cases: Box<[TestCase]>, + tests: Box<[Test]>, +} + +impl TestSuite { + /// Runs the test suite. + fn run(&self, assert_js: &str, sta_js: &str) -> SuiteOutcome { + let suites: Vec<_> = self + .suites + .into_par_iter() + .map(|suite| suite.run(assert_js, sta_js)) + .collect(); + + let tests: Vec<_> = self + .tests + .into_par_iter() + .map(|test| test.run(assert_js, sta_js)) + .collect(); + + let passed = + suites.par_iter().all(|suite| suite.passed) && tests.par_iter().all(|test| test.passed); + + SuiteOutcome { + name: self.name.clone(), + passed, + suites: suites.into_boxed_slice(), + tests: tests.into_boxed_slice(), + } + } +} + +/// Outcome of a test suite. +#[derive(Debug, Clone)] +struct SuiteOutcome { + name: Box, + passed: bool, + suites: Box<[SuiteOutcome]>, + tests: Box<[TestOutcome]>, +} + +/// Outcome of a test. +#[derive(Debug, Clone)] +struct TestOutcome { + name: Box, + passed: bool, } -/// Represents a test case. +/// Represents a test. #[derive(Debug, Clone)] -struct TestCase { - name: Option>, +struct Test { + name: Box, description: Box, - template: Box, - information: Option>, + esid: Option>, + flags: TestFlags, + information: Box, + features: Box<[Box]>, expected_outcome: Outcome, - includes: Box<[Box<[str]>]>, - flags: TestCaseFlags, - locale: Box<[Locale]>, + includes: Box<[Box]>, + locale: Locale, content: Box, } +impl Test { + /// Creates a new test + #[inline] + fn new(name: N, content: C, metadata: MetaData) -> Self + where + N: Into>, + C: Into>, + { + Self { + name: name.into(), + description: metadata.description, + esid: metadata.esid, + flags: metadata.flags.into(), + information: metadata.info, + features: metadata.features, + expected_outcome: Outcome::from(metadata.negative), + includes: metadata.includes, + locale: metadata.locale, + content: content.into(), + } + } + + /// Runs the test. + fn run(&self, assert_js: &str, sta_js: &str) -> TestOutcome { + use boa::*; + + // Create new Realm + // TODO: in parallel. + let realm = Realm::create(); + let mut engine = Interpreter::new(realm); + + forward_val(&mut engine, assert_js).expect("could not run assert.js"); + forward_val(&mut engine, sta_js).expect("could not run assert.js"); + + // TODO: set up the environment. + + dbg!(forward_val(&mut engine, &self.content)); + + todo!() + } +} + +#[derive(Debug, Clone, Deserialize)] +struct MetaData { + description: Box, + esid: Option>, + es5id: Option>, + es6id: Option>, + #[serde(default)] + info: Box, + #[serde(default)] + features: Box<[Box]>, + #[serde(default)] + includes: Box<[Box]>, + #[serde(default)] + flags: Box<[TestFlag]>, + #[serde(default)] + negative: Option, + #[serde(default)] + locale: Locale, +} + +/// Negative test information structure. +#[derive(Debug, Clone, Deserialize)] +struct Negative { + phase: Phase, + #[serde(rename = "type")] + error_type: Box, +} + /// An outcome for a test. -#[derive(Debug, Default, Clone, Copy)] +#[derive(Debug, Clone)] enum Outcome { Positive, Negative { phase: Phase, error_type: Box }, } -/// Phase for an error. -#[derive(Debug, Clone, Copy)] -enum Phase { - Parse, - Early, - Resolution, - Runtime, +impl Default for Outcome { + fn default() -> Self { + Self::Positive + } } -/// Locale information structure. -#[derive(Debug, Clone)] -struct Locale { - locale: Box<[str]>, +impl From> for Outcome { + fn from(neg: Option) -> Self { + neg.map(|neg| Self::Negative { + phase: neg.phase, + error_type: neg.error_type, + }) + .unwrap_or_default() + } } -/// Gets the list of tests to run. -fn test_suites(suite: &Path) -> io::Result { - let path = suite.join("src"); - read_suite(path.as_path()); +bitflags! { + struct TestFlags: u16 { + const STRICT = 0b000000001; + const NO_STRICT = 0b000000010; + const MODULE = 0b000000100; + const RAW = 0b000001000; + const ASYNC = 0b000010000; + const GENERATED = 0b000100000; + const CAN_BLOCK_IS_FALSE = 0b001000000; + const CAN_BLOCK_IS_TRUE = 0b010000000; + const NON_DETERMINISTIC = 0b100000000; + } } -/// Reads a test suite in the given path. -fn read_suite(path: &Path) -> io::Result { - let mut suites = Vec::new(); - let mut cases = Vec::new(); - for entry in path.read_dir() { - let entry = entry?; - if entry.file_type()?.is_dir() { - suites.push(read_suite(entry.path().as_path())?); +impl Default for TestFlags { + fn default() -> Self { + Self::STRICT | Self::NO_STRICT + } +} + +impl From for TestFlags { + fn from(flag: TestFlag) -> Self { + match flag { + TestFlag::OnlyStrict => Self::STRICT, + TestFlag::NoStrict => Self::NO_STRICT, + TestFlag::Module => Self::MODULE, + TestFlag::Raw => Self::RAW, + TestFlag::Async => Self::ASYNC, + TestFlag::Generated => Self::GENERATED, + TestFlag::CanBlockIsFalse => Self::CAN_BLOCK_IS_FALSE, + TestFlag::CanBlockIsTrue => Self::CAN_BLOCK_IS_TRUE, + TestFlag::NonDeterministic => Self::NON_DETERMINISTIC, + } + } +} + +impl From for TestFlags +where + T: AsRef<[TestFlag]>, +{ + fn from(flags: T) -> Self { + let flags = flags.as_ref(); + if flags.is_empty() { + Self::default() } else { - cases.push(read_test_case(entry.path().as_path())?); + let mut result = Self::empty(); + for flag in flags { + result |= Self::from(*flag); + } + + if !result.intersects(Self::default()) { + result |= Self::default() + } + + result } } +} - Ok(TestSuite { - suites: suites.to_boxed_slice(), - cases: cases.to_boxed_slice(), - }) +/// Individual test flag. +#[derive(Debug, Clone, Copy, Deserialize)] +#[serde(rename_all = "camelCase")] +enum TestFlag { + OnlyStrict, + NoStrict, + Module, + Raw, + Async, + Generated, + #[serde(rename = "CanBlockIsFalse")] + CanBlockIsFalse, + #[serde(rename = "CanBlockIsTrue")] + CanBlockIsTrue, + #[serde(rename = "non-deterministic")] + NonDeterministic, } -/// Reads information about a given test case. -fn read_test_case(path: &Path) -> io::Result { - let name = path.file_stem(); - let content = fs::read_to_string(path)?; - todo!() +/// Phase for an error. +#[derive(Debug, Clone, Copy, Deserialize)] +#[serde(rename_all = "lowercase")] +enum Phase { + Parse, + Early, + Resolution, + Runtime, +} + +/// Locale information structure. +#[derive(Debug, Default, Clone, Deserialize)] +#[serde(transparent)] +struct Locale { + locale: Box<[Box]>, }