diff options
Diffstat (limited to 'cli/tools/test.rs')
-rw-r--r-- | cli/tools/test.rs | 204 |
1 files changed, 127 insertions, 77 deletions
diff --git a/cli/tools/test.rs b/cli/tools/test.rs index d5317a761..f3d4d6f6c 100644 --- a/cli/tools/test.rs +++ b/cli/tools/test.rs @@ -3,6 +3,7 @@ use crate::args::Flags; use crate::args::TestFlags; use crate::args::TypeCheckMode; +use crate::checksum; use crate::colors; use crate::compat; use crate::create_main_worker; @@ -32,6 +33,7 @@ use deno_core::futures::stream; use deno_core::futures::FutureExt; use deno_core::futures::StreamExt; use deno_core::parking_lot::Mutex; +use deno_core::parking_lot::RwLock; use deno_core::serde_json::json; use deno_core::url::Url; use deno_core::ModuleSpecifier; @@ -40,6 +42,7 @@ use deno_runtime::ops::io::Stdio; use deno_runtime::ops::io::StdioPipe; use deno_runtime::permissions::Permissions; use deno_runtime::tokio_util::run_local; +use indexmap::IndexMap; use log::Level; use rand::rngs::SmallRng; use rand::seq::SliceRandom; @@ -47,7 +50,6 @@ use rand::SeedableRng; use regex::Regex; use serde::Deserialize; use std::collections::BTreeMap; -use std::collections::HashMap; use std::collections::HashSet; use std::fmt::Write as _; use std::io::Read; @@ -72,7 +74,6 @@ pub enum TestMode { Both, } -// TODO(nayeemrmn): This is only used for benches right now. #[derive(Clone, Debug, Default)] pub struct TestFilter { pub substring: Option<String>, @@ -135,11 +136,18 @@ pub struct TestLocation { #[derive(Debug, Clone, PartialEq, Deserialize, Eq, Hash)] #[serde(rename_all = "camelCase")] pub struct TestDescription { - pub origin: String, + pub id: usize, pub name: String, + pub origin: String, pub location: TestLocation, } +impl TestDescription { + pub fn static_id(&self) -> String { + checksum::gen(&[self.location.file_name.as_bytes(), self.name.as_bytes()]) + } +} + #[derive(Debug, Clone, PartialEq, Deserialize)] #[serde(rename_all = "camelCase")] pub enum TestOutput { @@ -153,14 +161,30 @@ pub enum TestResult { Ok, Ignored, Failed(Box<JsError>), + Cancelled, } #[derive(Debug, Clone, PartialEq, Deserialize)] #[serde(rename_all = "camelCase")] pub struct TestStepDescription { - pub test: TestDescription, - pub level: usize, + pub id: usize, pub name: String, + pub origin: String, + pub location: TestLocation, + pub level: usize, + pub parent_id: usize, + pub root_id: usize, + pub root_name: String, +} + +impl TestStepDescription { + pub fn static_id(&self) -> String { + checksum::gen(&[ + self.location.file_name.as_bytes(), + &self.level.to_be_bytes(), + self.name.as_bytes(), + ]) + } } #[derive(Debug, Clone, PartialEq, Deserialize)] @@ -194,13 +218,15 @@ pub struct TestPlan { #[derive(Debug, Clone, Deserialize)] #[serde(rename_all = "camelCase")] pub enum TestEvent { + Register(TestDescription), Plan(TestPlan), - Wait(TestDescription), + Wait(usize), Output(Vec<u8>), - Result(TestDescription, TestResult, u64), + Result(usize, TestResult, u64), UncaughtError(String, Box<JsError>), - StepWait(TestStepDescription), - StepResult(TestStepDescription, TestStepResult, u64), + StepRegister(TestStepDescription), + StepWait(usize), + StepResult(usize, TestStepResult, u64), } #[derive(Debug, Clone, Deserialize)] @@ -219,12 +245,12 @@ pub struct TestSummary { pub uncaught_errors: Vec<(String, Box<JsError>)>, } -#[derive(Debug, Clone, Deserialize)] +#[derive(Debug, Clone)] struct TestSpecifierOptions { compat_mode: bool, concurrent_jobs: NonZeroUsize, fail_fast: Option<NonZeroUsize>, - filter: Option<String>, + filter: TestFilter, shuffle: Option<u64>, trace_ops: bool, } @@ -250,13 +276,10 @@ impl TestSummary { fn has_failed(&self) -> bool { self.failed > 0 || !self.failures.is_empty() } - - fn has_pending(&self) -> bool { - self.total - self.passed - self.failed - self.ignored > 0 - } } pub trait TestReporter { + fn report_register(&mut self, plan: &TestDescription); fn report_plan(&mut self, plan: &TestPlan); fn report_wait(&mut self, description: &TestDescription); fn report_output(&mut self, output: &[u8]); @@ -267,6 +290,7 @@ pub trait TestReporter { elapsed: u64, ); fn report_uncaught_error(&mut self, origin: &str, error: &JsError); + fn report_step_register(&mut self, description: &TestStepDescription); fn report_step_wait(&mut self, description: &TestStepDescription); fn report_step_result( &mut self, @@ -285,9 +309,9 @@ enum DeferredStepOutput { struct PrettyTestReporter { concurrent: bool, echo_output: bool, - deferred_step_output: HashMap<TestDescription, Vec<DeferredStepOutput>>, + deferred_step_output: IndexMap<usize, Vec<DeferredStepOutput>>, in_new_line: bool, - last_wait_output_level: usize, + last_wait_id: Option<usize>, cwd: Url, did_have_user_output: bool, started_tests: bool, @@ -299,8 +323,8 @@ impl PrettyTestReporter { concurrent, echo_output, in_new_line: true, - deferred_step_output: HashMap::new(), - last_wait_output_level: 0, + deferred_step_output: IndexMap::new(), + last_wait_id: None, cwd: Url::from_directory_path(std::env::current_dir().unwrap()).unwrap(), did_have_user_output: false, started_tests: false, @@ -308,11 +332,14 @@ impl PrettyTestReporter { } fn force_report_wait(&mut self, description: &TestDescription) { + if !self.in_new_line { + println!(); + } print!("{} ...", description.name); self.in_new_line = false; // flush for faster feedback when line buffered std::io::stdout().flush().unwrap(); - self.last_wait_output_level = 0; + self.last_wait_id = Some(description.id); } fn to_relative_path_or_remote_url(&self, path_or_url: &str) -> String { @@ -329,15 +356,15 @@ impl PrettyTestReporter { } fn force_report_step_wait(&mut self, description: &TestStepDescription) { - let wrote_user_output = self.write_output_end(); - if !wrote_user_output && self.last_wait_output_level < description.level { + self.write_output_end(); + if !self.in_new_line { println!(); } print!("{}{} ...", " ".repeat(description.level), description.name); self.in_new_line = false; // flush for faster feedback when line buffered std::io::stdout().flush().unwrap(); - self.last_wait_output_level = description.level; + self.last_wait_id = Some(description.id); } fn force_report_step_result( @@ -353,19 +380,13 @@ impl PrettyTestReporter { TestStepResult::Failed(_) => colors::red("FAILED").to_string(), }; - let wrote_user_output = self.write_output_end(); - if !wrote_user_output && self.last_wait_output_level == description.level { - print!(" "); - } else { - print!("{}", " ".repeat(description.level)); - } - - if wrote_user_output { - print!("{} ... ", description.name); + self.write_output_end(); + if self.in_new_line || self.last_wait_id != Some(description.id) { + self.force_report_step_wait(description); } println!( - "{} {}", + " {} {}", status, colors::gray(format!("({})", display::human_elapsed(elapsed.into()))) ); @@ -380,19 +401,18 @@ impl PrettyTestReporter { self.in_new_line = true; } - fn write_output_end(&mut self) -> bool { + fn write_output_end(&mut self) { if self.did_have_user_output { println!("{}", colors::gray("----- output end -----")); self.in_new_line = true; self.did_have_user_output = false; - true - } else { - false } } } impl TestReporter for PrettyTestReporter { + fn report_register(&mut self, _description: &TestDescription) {} + fn report_plan(&mut self, plan: &TestPlan) { let inflection = if plan.total == 1 { "test" } else { "tests" }; println!( @@ -440,7 +460,8 @@ impl TestReporter for PrettyTestReporter { if self.concurrent { self.force_report_wait(description); - if let Some(step_outputs) = self.deferred_step_output.remove(description) + if let Some(step_outputs) = + self.deferred_step_output.remove(&description.id) { for step_output in step_outputs { match step_output { @@ -461,23 +482,20 @@ impl TestReporter for PrettyTestReporter { } } - let wrote_user_output = self.write_output_end(); - if !wrote_user_output && self.last_wait_output_level == 0 { - print!(" "); - } - - if wrote_user_output { - print!("{} ... ", description.name); + self.write_output_end(); + if self.in_new_line || self.last_wait_id != Some(description.id) { + self.force_report_wait(description); } let status = match result { TestResult::Ok => colors::green("ok").to_string(), TestResult::Ignored => colors::yellow("ignored").to_string(), TestResult::Failed(_) => colors::red("FAILED").to_string(), + TestResult::Cancelled => colors::gray("cancelled").to_string(), }; println!( - "{} {}", + " {} {}", status, colors::gray(format!("({})", display::human_elapsed(elapsed.into()))) ); @@ -494,15 +512,16 @@ impl TestReporter for PrettyTestReporter { colors::red("FAILED") ); self.in_new_line = true; - self.last_wait_output_level = 0; self.did_have_user_output = false; } + fn report_step_register(&mut self, _description: &TestStepDescription) {} + fn report_step_wait(&mut self, description: &TestStepDescription) { if self.concurrent { self .deferred_step_output - .entry(description.test.to_owned()) + .entry(description.root_id) .or_insert_with(Vec::new) .push(DeferredStepOutput::StepWait(description.clone())); } else { @@ -519,7 +538,7 @@ impl TestReporter for PrettyTestReporter { if self.concurrent { self .deferred_step_output - .entry(description.test.to_owned()) + .entry(description.root_id) .or_insert_with(Vec::new) .push(DeferredStepOutput::StepResult( description.clone(), @@ -597,7 +616,7 @@ impl TestReporter for PrettyTestReporter { } } - let status = if summary.has_failed() || summary.has_pending() { + let status = if summary.has_failed() { colors::red("FAILED").to_string() } else { colors::green("ok").to_string() @@ -737,7 +756,7 @@ async fn test_specifier( &ps, specifier.clone(), permissions, - vec![ops::testing::init(sender.clone())], + vec![ops::testing::init(sender.clone(), options.filter.clone())], Stdio { stdin: StdioPipe::Inherit, stdout: StdioPipe::File(sender.stdout()), @@ -807,10 +826,7 @@ async fn test_specifier( &located_script_name!(), &format!( r#"Deno[Deno.internal].runTests({})"#, - json!({ - "filter": options.filter, - "shuffle": options.shuffle, - }), + json!({ "shuffle": options.shuffle }), ), )?; @@ -1106,7 +1122,11 @@ async fn test_specifiers( let sender = TestEventSender::new(sender); let concurrent_jobs = options.concurrent_jobs; let fail_fast = options.fail_fast; + let tests: Arc<RwLock<IndexMap<usize, TestDescription>>> = + Arc::new(RwLock::new(IndexMap::new())); + let mut test_steps = IndexMap::new(); + let tests_ = tests.clone(); let join_handles = specifiers_with_mode.iter().map(move |(specifier, mode)| { let ps = ps.clone(); @@ -1115,6 +1135,7 @@ async fn test_specifiers( let mode = mode.clone(); let mut sender = sender.clone(); let options = options.clone(); + let tests = tests_.clone(); tokio::task::spawn_blocking(move || { let origin = specifier.to_string(); @@ -1129,9 +1150,18 @@ async fn test_specifiers( if let Err(error) = file_result { if error.is::<JsError>() { sender.send(TestEvent::UncaughtError( - origin, + origin.clone(), Box::new(error.downcast::<JsError>().unwrap()), ))?; + for desc in tests.read().values() { + if desc.origin == origin { + sender.send(TestEvent::Result( + desc.id, + TestResult::Cancelled, + 0, + ))? + } + } } else { return Err(error); } @@ -1150,11 +1180,17 @@ async fn test_specifiers( let handler = { tokio::task::spawn(async move { let earlier = Instant::now(); + let mut tests_with_result = HashSet::new(); let mut summary = TestSummary::new(); let mut used_only = false; while let Some(event) = receiver.recv().await { match event { + TestEvent::Register(description) => { + reporter.report_register(&description); + tests.write().insert(description.id, description); + } + TestEvent::Plan(plan) => { summary.total += plan.total; summary.filtered_out += plan.filtered_out; @@ -1166,29 +1202,34 @@ async fn test_specifiers( reporter.report_plan(&plan); } - TestEvent::Wait(description) => { - reporter.report_wait(&description); + TestEvent::Wait(id) => { + reporter.report_wait(tests.read().get(&id).unwrap()); } TestEvent::Output(output) => { reporter.report_output(&output); } - TestEvent::Result(description, result, elapsed) => { - match &result { - TestResult::Ok => { - summary.passed += 1; - } - TestResult::Ignored => { - summary.ignored += 1; - } - TestResult::Failed(error) => { - summary.failed += 1; - summary.failures.push((description.clone(), error.clone())); + TestEvent::Result(id, result, elapsed) => { + if tests_with_result.insert(id) { + let description = tests.read().get(&id).unwrap().clone(); + match &result { + TestResult::Ok => { + summary.passed += 1; + } + TestResult::Ignored => { + summary.ignored += 1; + } + TestResult::Failed(error) => { + summary.failed += 1; + summary.failures.push((description.clone(), error.clone())); + } + TestResult::Cancelled => { + summary.failed += 1; + } } + reporter.report_result(&description, &result, elapsed); } - - reporter.report_result(&description, &result, elapsed); } TestEvent::UncaughtError(origin, error) => { @@ -1197,11 +1238,16 @@ async fn test_specifiers( summary.uncaught_errors.push((origin, error)); } - TestEvent::StepWait(description) => { - reporter.report_step_wait(&description); + TestEvent::StepRegister(description) => { + reporter.report_step_register(&description); + test_steps.insert(description.id, description); + } + + TestEvent::StepWait(id) => { + reporter.report_step_wait(test_steps.get(&id).unwrap()); } - TestEvent::StepResult(description, result, duration) => { + TestEvent::StepResult(id, result, duration) => { match &result { TestStepResult::Ok => { summary.passed_steps += 1; @@ -1217,7 +1263,11 @@ async fn test_specifiers( } } - reporter.report_step_result(&description, &result, duration); + reporter.report_step_result( + test_steps.get(&id).unwrap(), + &result, + duration, + ); } } @@ -1366,7 +1416,7 @@ pub async fn run_tests( compat_mode: compat, concurrent_jobs: test_flags.concurrent_jobs, fail_fast: test_flags.fail_fast, - filter: test_flags.filter, + filter: TestFilter::from_flag(&test_flags.filter), shuffle: test_flags.shuffle, trace_ops: test_flags.trace_ops, }, @@ -1550,7 +1600,7 @@ pub async fn run_tests_with_watch( compat_mode: cli_options.compat(), concurrent_jobs: test_flags.concurrent_jobs, fail_fast: test_flags.fail_fast, - filter: filter.clone(), + filter: TestFilter::from_flag(&filter), shuffle: test_flags.shuffle, trace_ops: test_flags.trace_ops, }, |