Rust编写的JavaScript引擎,该项目是一个试验性质的项目。
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.

323 lines
7.9 KiB

//! Test262 test runner
//!
//! This crate will run the full ECMAScript test suite (Test262) and report compliance of the
//! `boa` engine.
#![doc(
html_logo_url = "https://raw.githubusercontent.com/jasonwilliams/boa/master/assets/logo.svg",
html_favicon_url = "https://raw.githubusercontent.com/jasonwilliams/boa/master/assets/logo.svg"
)]
#![deny(
unused_qualifications,
clippy::all,
unused_qualifications,
unused_import_braces,
unused_lifetimes,
unreachable_pub,
trivial_numeric_casts,
// rustdoc,
missing_debug_implementations,
missing_copy_implementations,
deprecated_in_future,
meta_variable_misuse,
non_ascii_idents,
rust_2018_compatibility,
rust_2018_idioms,
future_incompatible,
nonstandard_style,
)]
#![warn(clippy::perf, clippy::single_match_else, clippy::dbg_macro)]
#![allow(
clippy::missing_inline_in_public_items,
clippy::cognitive_complexity,
clippy::must_use_candidate,
clippy::missing_errors_doc,
clippy::as_conversions,
clippy::let_unit_value,
missing_doc_code_examples
)]
mod exec;
mod read;
mod results;
use self::{
read::{read_global_suite, read_harness, MetaData, Negative, TestFlag},
results::write_json,
};
use bitflags::bitflags;
use fxhash::FxHashMap;
use once_cell::sync::Lazy;
use serde::{Deserialize, Serialize};
use std::{
fs,
path::{Path, PathBuf},
};
use structopt::StructOpt;
/// CLI information.
static CLI: Lazy<Cli> = Lazy::new(Cli::from_args);
/// Boa test262 tester
#[derive(StructOpt, Debug)]
#[structopt(name = "Boa test262 tester")]
struct Cli {
// Whether to show verbose output.
#[structopt(short, long)]
verbose: bool,
/// Path to the Test262 suite.
#[structopt(long, parse(from_os_str), default_value = "./test262")]
test262_path: PathBuf,
/// Optional output folder for the full results information.
#[structopt(short, long, parse(from_os_str))]
output: Option<PathBuf>,
}
impl Cli {
// Whether to show verbose output.
fn verbose(&self) -> bool {
self.verbose
}
/// Path to the Test262 suite.
fn test262_path(&self) -> &Path {
self.test262_path.as_path()
}
/// Optional output folder for the full results information.
fn output(&self) -> Option<&Path> {
self.output.as_deref()
}
}
/// Program entry point.
fn main() {
if let Some(path) = CLI.output() {
if path.exists() {
if !path.is_dir() {
eprintln!("The output path must be a directory.");
std::process::exit(1);
}
} else {
fs::create_dir_all(path).expect("could not create the output directory");
}
}
if CLI.verbose() {
println!("Loading the test suite...");
}
let harness = read_harness().expect("could not read initialization bindings");
let global_suite = read_global_suite().expect("could not get the list of tests to run");
if CLI.verbose() {
println!("Test suite loaded, starting tests...");
}
let results = global_suite.run(&harness);
println!();
println!("Results:");
println!("Total tests: {}", results.total);
println!("Passed tests: {}", results.passed);
println!(
"Conformance: {:.2}%",
(results.passed as f64 / results.total as f64) * 100.0
);
write_json(results).expect("could not write the results to the output JSON file");
}
/// All the harness include files.
#[derive(Debug, Clone)]
struct Harness {
assert: Box<str>,
sta: Box<str>,
includes: FxHashMap<Box<str>, Box<str>>,
}
/// Represents a test suite.
#[derive(Debug, Clone)]
struct TestSuite {
name: Box<str>,
suites: Box<[TestSuite]>,
tests: Box<[Test]>,
}
/// Outcome of a test suite.
#[derive(Debug, Clone, Serialize, Deserialize)]
struct SuiteResult {
#[serde(rename = "n")]
name: Box<str>,
#[serde(rename = "c")]
total: usize,
#[serde(rename = "p")]
passed: usize,
#[serde(rename = "i")]
ignored: usize,
#[serde(skip_serializing_if = "Vec::is_empty")]
#[serde(rename = "s")]
suites: Vec<SuiteResult>,
#[serde(rename = "t")]
#[serde(skip_serializing_if = "Vec::is_empty")]
tests: Vec<TestResult>,
}
/// Outcome of a test.
#[derive(Debug, Clone, Serialize, Deserialize)]
struct TestResult {
#[serde(rename = "n")]
name: Box<str>,
#[serde(rename = "r")]
result: TestOutcomeResult,
}
#[derive(Debug, Clone, Copy, PartialEq, Eq, Serialize, Deserialize)]
enum TestOutcomeResult {
#[serde(rename = "O")]
Passed,
#[serde(rename = "I")]
Ignored,
#[serde(rename = "F")]
Failed,
#[serde(rename = "P")]
Panic,
}
/// Represents a test.
#[derive(Debug, Clone)]
struct Test {
name: Box<str>,
description: Box<str>,
esid: Option<Box<str>>,
flags: TestFlags,
information: Box<str>,
features: Box<[Box<str>]>,
expected_outcome: Outcome,
includes: Box<[Box<str>]>,
locale: Locale,
content: Box<str>,
}
impl Test {
/// Creates a new test.
#[inline]
fn new<N, C>(name: N, content: C, metadata: MetaData) -> Self
where
N: Into<Box<str>>,
C: Into<Box<str>>,
{
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(),
}
}
}
/// An outcome for a test.
#[derive(Debug, Clone)]
enum Outcome {
Positive,
Negative { phase: Phase, error_type: Box<str> },
}
impl Default for Outcome {
fn default() -> Self {
Self::Positive
}
}
impl From<Option<Negative>> for Outcome {
fn from(neg: Option<Negative>) -> Self {
neg.map(|neg| Self::Negative {
phase: neg.phase,
error_type: neg.error_type,
})
.unwrap_or_default()
}
}
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;
}
}
impl Default for TestFlags {
fn default() -> Self {
Self::STRICT | Self::NO_STRICT
}
}
impl From<TestFlag> 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<T> From<T> for TestFlags
where
T: AsRef<[TestFlag]>,
{
fn from(flags: T) -> Self {
let flags = flags.as_ref();
if flags.is_empty() {
Self::default()
} else {
let mut result = Self::empty();
for flag in flags {
result |= Self::from(*flag);
}
if !result.intersects(Self::default()) {
result |= Self::default()
}
result
}
}
}
/// 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<str>]>,
}