oxc/tasks/coverage/src/runtime/mod.rs

216 lines
6.6 KiB
Rust

use std::{
collections::HashSet,
fs,
path::{Path, PathBuf},
time::Duration,
};
use oxc_tasks_common::{agent, project_root};
use phf::{phf_set, Set};
use oxc_allocator::Allocator;
use oxc_codegen::{Codegen, CodegenOptions};
use oxc_parser::Parser;
use oxc_span::SourceType;
use serde_json::json;
use crate::{
suite::{Case, TestResult},
test262::{Test262Case, TestFlag},
};
pub const V8_TEST_262_FAILED_TESTS_PATH: &str = "tasks/coverage/src/runtime/v8_test262.status";
lazy_static::lazy_static! {
static ref V8_TEST_262_FAILED_TESTS: HashSet<String> = {
let mut set = HashSet::default();
fs::read_to_string(project_root().join(V8_TEST_262_FAILED_TESTS_PATH))
.expect("Failed to read v8_test262.status")
.lines()
.for_each(|line| {
set.insert(line.replace(".*", "").replace('*', ""));
});
set
};
}
static SKIP_EVALUATING_FEATURES: Set<&'static str> = phf_set! {
// Node's version of V8 doesn't implement these
"hashbang",
"legacy-regexp",
"regexp-duplicate-named-groups",
"symbols-as-weakmap-keys",
"tail-call-optimization",
// We don't care about API-related things
"ArrayBuffer",
"change-array-by-copy",
"DataView",
"resizable-arraybuffer",
"ShadowRealm",
"cross-realm",
"SharedArrayBuffer",
"String.prototype.toWellFormed",
"Symbol.match",
"Symbol.replace",
"Symbol.unscopables",
"Temporal",
"TypedArray",
// Added in oxc
"Array.fromAsync",
"IsHTMLDDA",
"iterator-helpers",
"set-methods",
"array-grouping",
// stage 2
"Intl.DurationFormat"
};
static SKIP_EVALUATING_THESE_INCLUDES: Set<&'static str> = phf_set! {
// We don't preserve "toString()" on functions
"nativeFunctionMatcher.js",
};
static SKIP_TEST_CASES: Set<&'static str> = phf_set! {
// For some unknown reason these tests are unstable, so we'll skip them for now.
"language/identifiers/start-unicode",
// Properly misconfigured test setup for `eval`, but can't figure out where
"annexB/language/eval-code",
"language/eval-code"
};
const FIXTURES_PATH: &str = "tasks/coverage/test262/test";
pub struct CodegenRuntimeTest262Case {
base: Test262Case,
test_root: PathBuf,
}
impl Case for CodegenRuntimeTest262Case {
fn new(path: PathBuf, code: String) -> Self {
Self { base: Test262Case::new(path, code), test_root: project_root().join(FIXTURES_PATH) }
}
fn code(&self) -> &str {
self.base.code()
}
fn path(&self) -> &Path {
self.base.path()
}
fn test_result(&self) -> &TestResult {
self.base.test_result()
}
fn skip_test_case(&self) -> bool {
let base_path = self.base.path().to_string_lossy();
self.base.should_fail()
|| base_path.starts_with("built-ins")
|| base_path.starts_with("staging")
|| base_path.starts_with("intl402")
// skip v8 test-262 failed tests
|| V8_TEST_262_FAILED_TESTS.iter().any(|test| base_path.contains(test))
|| self
.base
.meta()
.includes
.iter()
.any(|include| SKIP_EVALUATING_THESE_INCLUDES.contains(include))
|| self
.base
.meta()
.features
.iter()
.any(|feature| SKIP_EVALUATING_FEATURES.contains(feature))
|| SKIP_TEST_CASES.iter().any(|path| base_path.contains(path))
|| self.base.code().contains("$262")
|| self.base.code().contains("$DONOTEVALUATE()")
}
fn run(&mut self) {}
async fn run_async(&mut self) {
let result = async {
let codegen_source_text = {
let source_text = self.base.code();
let is_module = self.base.meta().flags.contains(&TestFlag::Module);
let is_only_strict = self.base.meta().flags.contains(&TestFlag::OnlyStrict);
let source_type = SourceType::default().with_module(is_module);
let allocator = Allocator::default();
let ret = Parser::new(&allocator, source_text, source_type).parse();
let mut text =
Codegen::<false>::new("", source_text, ret.trivias, CodegenOptions::default())
.build(&ret.program)
.source_text;
if is_only_strict {
text = format!("\"use strict\";\n{text}");
}
if is_module {
text = format!("{text}\n export {{}}");
}
text
};
self.run_test_code(codegen_source_text).await
}
.await;
self.base.set_result(result);
}
}
impl CodegenRuntimeTest262Case {
async fn run_test_code(&self, codegen_text: String) -> TestResult {
let is_async = self.base.meta().flags.contains(&TestFlag::Async);
let is_module = self.base.meta().flags.contains(&TestFlag::Module);
let is_raw = self.base.meta().flags.contains(&TestFlag::Raw);
let import_dir = self
.test_root
.join(self.base.path().parent().expect("Failed to get parent directory"))
.to_string_lossy()
.to_string();
let result = request_run_code(json!({
"code": codegen_text,
"includes": self.base.meta().includes,
"isAsync": is_async,
"isModule": is_module,
"isRaw": is_raw,
"importDir": import_dir
}))
.await;
match result {
Ok(output) => {
if output.is_empty() {
TestResult::Passed
} else {
if let Some(negative) = &self.base.meta().negative {
if negative.phase.is_runtime()
&& output.starts_with(&negative.error_type.to_string())
{
return TestResult::Passed;
}
}
TestResult::RuntimeError(output)
}
}
Err(error) => TestResult::RuntimeError(error),
}
}
}
async fn request_run_code(json: impl serde::Serialize + Send + 'static) -> Result<String, String> {
tokio::spawn(async move {
agent()
.post("http://localhost:32055/run")
.timeout(Duration::from_secs(10))
.send_json(json)
.map_err(|err| err.to_string())
.and_then(|res| res.into_string().map_err(|err| err.to_string()))
})
.await
.map_err(|err| err.to_string())?
}