diff --git a/Cargo.lock b/Cargo.lock index a5347c2d..868de51f 100644 --- a/Cargo.lock +++ b/Cargo.lock @@ -885,6 +885,12 @@ version = "0.31.1" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "07e28edb80900c19c28f1072f2e8aeca7fa06b23cd4169cefe1af5aa3260783f" +[[package]] +name = "glob" +version = "0.3.3" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "0cc23270f6e1808e30a928bdc84dea0b9b4136a8bc82338574f23baf47bbd280" + [[package]] name = "h2" version = "0.4.8" @@ -2326,6 +2332,7 @@ dependencies = [ "clap", "dash_optimizer", "dash_vm", + "glob", "once_cell", "rayon", "serde", diff --git a/Cargo.toml b/Cargo.toml index 3bb3105b..76d88836 100755 --- a/Cargo.toml +++ b/Cargo.toml @@ -36,8 +36,5 @@ resolver = "2" lto = "fat" codegen-units = 1 -[profile.release.package.testrunner] -debug-assertions = true - [profile.dev] debug = true diff --git a/crates/dash_vm/src/js_std/global.rs b/crates/dash_vm/src/js_std/global.rs index b82d394b..43cc3c02 100755 --- a/crates/dash_vm/src/js_std/global.rs +++ b/crates/dash_vm/src/js_std/global.rs @@ -61,23 +61,70 @@ pub fn parse_float(cx: CallContext) -> Result { pub fn parse_int(cx: CallContext) -> Result { let input_string = cx.args.first().unwrap_or_undefined().to_js_string(cx.scope)?; - let radix = cx + + let mut radix = cx .args .get(1) .cloned() .map(|v| v.to_number(cx.scope)) .transpose()? - .map(|r| r as u32) + .map(|r| r as i32) .unwrap_or(10); - let trimmed_string = input_string.res(cx.scope).trim(); + let mut trimmed_string = input_string.res(cx.scope).trim(); - // TODO: follow spec - let num = Value::number( - i32::from_str_radix(trimmed_string, radix) - .map(|n| n as f64) - .unwrap_or(f64::NAN), - ); + let mut sign = 1; - Ok(num) + if trimmed_string.starts_with('-') { + sign = -1; + } + + // If S is not empty and the first code unit of S is either + // the code unit 0x002B (PLUS SIGN) or the code unit 0x002D (HYPHEN-MINUS), + // set S to the substring of S from index 1. + if trimmed_string.starts_with(&['+', '-']) { + trimmed_string = &trimmed_string[1..]; + } + + let mut strip_prefix = true; + + if radix != 0 { + if radix < 2 || radix > 36 { + return Ok(Value::number(f64::NAN)); + } + + if radix != 16 { + strip_prefix = false; + } + } else { + radix = 10; + } + + // If stripPrefix is true, then + if strip_prefix { + // If the length of S is at least 2 and the first two code units of S are either "0x" or "0X" + if trimmed_string.len() >= 2 && (trimmed_string.starts_with("0x") || trimmed_string.starts_with("0X")) { + trimmed_string = &trimmed_string[2..]; + radix = 16; + } + } + + let radix = radix as u32; // by here it cannot be negative.. + + // If S contains a code unit that is not a radix-R digit, + // let end be the index within S of the first such code unit; + // otherwise let end be the length of S. + let end = trimmed_string + .find(|c: char| !c.is_digit(radix)) + .unwrap_or_else(|| trimmed_string.len()); + + let z = &trimmed_string[0..end]; + + if z.is_empty() { + return Ok(Value::number(f64::NAN)); + } + + let output = i128::from_str_radix(z, radix).unwrap(); + + Ok(Value::number((sign * output) as f64)) } diff --git a/testrunner/.cargo/config.toml b/testrunner/.cargo/config.toml index 6cbd5f2d..18881f73 100644 --- a/testrunner/.cargo/config.toml +++ b/testrunner/.cargo/config.toml @@ -1,4 +1,5 @@ - +[unstable] +codegen-backend=true [profile.dev] # override cranelift because the testrunner WILL require unwinding support codegen-backend = "llvm" diff --git a/testrunner/Cargo.toml b/testrunner/Cargo.toml index 86992592..fa6f5ca3 100755 --- a/testrunner/Cargo.toml +++ b/testrunner/Cargo.toml @@ -14,3 +14,4 @@ serde_yaml = "0.9.17" once_cell = "1.17.1" rayon = "1.6.1" serde = { version = "1.0.152", features = ["derive"] } +glob = "0.3.3" diff --git a/testrunner/src/cmd/run.rs b/testrunner/src/cmd/run.rs index 80a794f9..e965cd7a 100644 --- a/testrunner/src/cmd/run.rs +++ b/testrunner/src/cmd/run.rs @@ -3,33 +3,38 @@ use std::ffi::{OsStr, OsString}; use std::panic; use std::sync::atomic::AtomicU32; use std::sync::{Mutex, atomic}; +use std::time::Instant; use clap::ArgMatches; use dash_vm::Vm; use dash_vm::eval::EvalError; use dash_vm::params::VmParams; +use dash_vm::value::propertykey::PropertyKey; +use dash_vm::value::string::JsString; +use dash_vm::value::{Root, Unpack, ValueKind}; use once_cell::sync::Lazy; use serde::Deserialize; -use crate::util; - pub fn run(matches: &ArgMatches) -> anyhow::Result<()> { let path = matches.get_one::("path"); - let path = path.map_or("../test262/test", |v| &**v); - let verbose = *matches.get_one::("verbose").unwrap(); + let path = path.map_or("../test262/test/**/*.js", |v| &**v); + let slevel = matches.get_one::("level").unwrap_or(&"".to_string()).clone(); + let c = matches.get_one::("color").unwrap().clone(); let single_threaded = *matches.get_one::("disable-threads").unwrap(); - let files = if path.ends_with(".js") { - vec![OsString::from(path)] - } else { - util::get_all_files(OsStr::new(path))? - }; + let files = glob::glob(path) + .unwrap() + .map(|x| x.map(|mut x| x.as_mut_os_string().clone())) + .collect::, _>>() + .unwrap(); - run_inner(files, verbose, single_threaded)?; + let level = slevel.contains('o') as u8 | ((slevel.contains('e') as u8) << 1) | ((slevel.contains('p') as u8) << 2); + // println!("{level:b}"); + run_inner(files, level, single_threaded, c)?; Ok(()) } -fn run_inner(files: Vec, verbose: bool, single_threaded: bool) -> anyhow::Result<()> { +fn run_inner(files: Vec, level: u8, single_threaded: bool, c: bool) -> anyhow::Result<()> { let setup: String = { let sta = std::fs::read_to_string("../test262/harness/sta.js")?; let assert = std::fs::read_to_string("../test262/harness/assert.js")?; @@ -49,7 +54,7 @@ fn run_inner(files: Vec, verbose: bool, single_threaded: bool) -> anyh let file_count = files.len(); let run_file = |file: &OsString| { - let result = run_test(&setup, file, verbose); + let result = run_test(&setup, file, level, c); let counter = match result { RunResult::Pass => &counter.passes, @@ -60,6 +65,7 @@ fn run_inner(files: Vec, verbose: bool, single_threaded: bool) -> anyh counter.fetch_add(1, atomic::Ordering::Relaxed); }; + let i = Instant::now(); if single_threaded { for file in files { run_file(&file); @@ -74,16 +80,17 @@ fn run_inner(files: Vec, verbose: bool, single_threaded: bool) -> anyh } }); } + let e = i.elapsed(); let passes = counter.passes.load(atomic::Ordering::Relaxed); let fails = counter.fails.load(atomic::Ordering::Relaxed); let panics = counter.panics.load(atomic::Ordering::Relaxed); let rate = ((passes as f32) / (file_count as f32)) * 100.0; - println!("== Result ==="); - println!("Passes: {passes} ({rate:.2}%)",); - println!("Fails: {fails}"); - println!("Panics: {panics}"); - + println!("{}== Result =={}", ansi(c, 36), ansi(c, 0)); + println!(" {}OK{} {passes} ({rate:.2}%)", ansi(c, 32), ansi(c, 0)); + println!(" {}ERR{} {fails}", ansi(c, 33), ansi(c, 0)); + println!("{}PANIC{} {panics}", ansi(c, 31), ansi(c, 0)); + println!("{}tests took {}s{}", ansi(c, 36), e.as_secs_f64(), ansi(c, 0)); Ok(()) } @@ -134,7 +141,11 @@ fn get_harness_code(path: &str) -> String { code.clone() } -fn run_test(setup: &str, path: &OsStr, verbose: bool) -> RunResult { +fn ansi(color: bool, n: u8) -> String { + if !color { String::new() } else { format!("\x1b[{n}m") } +} + +fn run_test(setup: &str, path: &OsStr, level: u8, c: bool) -> RunResult { let mut negative = None; let contents = std::fs::read_to_string(path).unwrap(); let mut prelude = String::from(setup); @@ -148,11 +159,34 @@ fn run_test(setup: &str, path: &OsStr, verbose: bool) -> RunResult { negative = metadata.negative; } let contents = format!("{prelude}{contents}"); - + let label = path.to_string_lossy().to_string(); + panic::set_hook(Box::new(move |p| { + if level & 0b100 != 0 { + println!( + "{}PANIC{} {label}: {} {}{}{}", + ansi(c, 31), + ansi(c, 0), + p.payload() + .downcast_ref::<&str>() + .map(|x| x.to_string()) + .or_else(|| p.payload().downcast_ref::().cloned()) + .unwrap(), + ansi(c, 2), + p.location().unwrap(), + ansi(c, 0) + ); + } + })); + let label = path.to_string_lossy().to_string(); let maybe_pass = panic::catch_unwind(move || { let mut vm = Vm::new(VmParams::default()); match (vm.eval(&contents, Default::default()), negative.map(|n| n.phase)) { - (Ok(_), None) => RunResult::Pass, + (Ok(_), None) => { + if level & 1 != 0 { + println!(" {}OK{} {label}", ansi(c, 32), ansi(c, 0)) + } + RunResult::Pass + } (Ok(_), Some(..)) => RunResult::Fail, (Err(err), negative) => { let result = match (&err, negative) { @@ -163,23 +197,46 @@ fn run_test(setup: &str, path: &OsStr, verbose: bool) -> RunResult { (_, Some(..)) => RunResult::Fail, }; - if let RunResult::Fail = result { - if verbose { - let s = match &err { - EvalError::Middle(errs) => format!("{errs:?}"), - EvalError::Exception(_ex) => { - // let mut sc = LocalScope::new(&mut vm); - // match ex.to_string(&mut sc) { - // Ok(s) => ToString::to_string(&s), - // Err(err) => format!("{err:?}"), - // } - - // displaying certain JS error "structures" like above causes a weird stack overflow. - // requires further investigation. for now just display some hardcoded string - "".into() + { + match result { + RunResult::Pass => { + if level & 1 != 0 { + println!(" {}OK{} {label}", ansi(c, 32), ansi(c, 0)) + } + } + RunResult::Fail => { + let s = match &err { + EvalError::Middle(errs) => format!("{errs:?}"), + EvalError::Exception(ex) => { + let mut scope = vm.scope(); + let t = ex.root(&mut scope); + let tos = scope.interner.intern("message"); + if let ValueKind::Object(u) = t.unpack() { + let z = u + .get_own_property( + PropertyKey::from_js_string(JsString::from_sym(tos), &mut scope), + &mut scope, + ) + .unwrap(); + if let ValueKind::String(zr) = z.root(&mut scope).unpack() { + format!("{}", zr.res(&mut scope)) + } else { + format!("?") + } + } else { + format!("?") + } + + // displaying certain JS error "structures" like above causes a weird stack overflow. + // requires further investigation. for now just display some hardcoded string + // "".into() + } + }; + if level & 0b10 != 0 { + println!(" {}ERR{} {label}: {s}", ansi(c, 33), ansi(c, 0),); } - }; - println!("Error in {:?}: {s}", path.to_str()); + } + RunResult::Panic => {} } } @@ -190,9 +247,6 @@ fn run_test(setup: &str, path: &OsStr, verbose: bool) -> RunResult { match maybe_pass { Ok(res) => res, - Err(_) => { - println!("Panic in {}", path.to_str().unwrap()); - RunResult::Panic - } + Err(_) => RunResult::Panic, } } diff --git a/testrunner/src/main.rs b/testrunner/src/main.rs index cca19e3b..1da7aba5 100644 --- a/testrunner/src/main.rs +++ b/testrunner/src/main.rs @@ -17,7 +17,13 @@ fn main() -> anyhow::Result<()> { .action(ArgAction::SetTrue) .required(false), ) - .arg(Arg::new("verbose").long("verbose").action(ArgAction::SetTrue)), + .arg( + Arg::new("color") + .long("color") + .action(ArgAction::SetTrue) + .required(false), + ) + .arg(Arg::new("level").long("level")), ); match app.get_matches().subcommand() {