1
0
Fork 0
mirror of https://github.com/denoland/deno.git synced 2024-12-23 15:49:44 -05:00

refactor: allocate IDs for tests (#14729)

This commit is contained in:
Nayeem Rahman 2022-07-15 18:09:22 +01:00 committed by GitHub
parent 635eed9373
commit 22a4998e29
No known key found for this signature in database
GPG key ID: 4AEE18F83AFDEB23
12 changed files with 795 additions and 845 deletions

View file

@ -15,7 +15,7 @@ fn arrow_to_steps(
parent: &str,
level: usize,
arrow_expr: &ast::ArrowExpr,
) -> Option<Vec<TestDefinition>> {
) -> Vec<TestDefinition> {
if let Some((maybe_test_context, maybe_step_var)) =
parse_test_context_param(arrow_expr.params.get(0))
{
@ -26,14 +26,9 @@ fn arrow_to_steps(
maybe_step_var,
);
arrow_expr.body.visit_with(&mut collector);
let steps = collector.take();
if !steps.is_empty() {
Some(steps)
} else {
None
}
collector.take()
} else {
None
vec![]
}
}
@ -42,7 +37,7 @@ fn fn_to_steps(
parent: &str,
level: usize,
function: &ast::Function,
) -> Option<Vec<TestDefinition>> {
) -> Vec<TestDefinition> {
if let Some((maybe_test_context, maybe_step_var)) =
parse_test_context_param(function.params.get(0).map(|p| &p.pat))
{
@ -53,14 +48,9 @@ fn fn_to_steps(
maybe_step_var,
);
function.body.visit_with(&mut collector);
let steps = collector.take();
if !steps.is_empty() {
Some(steps)
} else {
None
}
collector.take()
} else {
None
vec![]
}
}
@ -139,12 +129,12 @@ fn check_call_expr(
parent: &str,
node: &ast::CallExpr,
level: usize,
) -> Option<(String, Option<Vec<TestDefinition>>)> {
) -> Option<(String, Vec<TestDefinition>)> {
if let Some(expr) = node.args.get(0).map(|es| es.expr.as_ref()) {
match expr {
ast::Expr::Object(obj_lit) => {
let mut maybe_name = None;
let mut steps = None;
let mut steps = vec![];
for prop in &obj_lit.props {
if let ast::PropOrSpread::Prop(prop) = prop {
match prop.as_ref() {
@ -203,7 +193,7 @@ fn check_call_expr(
}
ast::Expr::Lit(ast::Lit::Str(lit_str)) => {
let name = lit_str.value.to_string();
let mut steps = None;
let mut steps = vec![];
match node.args.get(1).map(|es| es.expr.as_ref()) {
Some(ast::Expr::Fn(fn_expr)) => {
steps = fn_to_steps(parent, level, &fn_expr.function);
@ -256,7 +246,7 @@ impl TestStepCollector {
&mut self,
name: N,
range: SourceRange,
steps: Option<Vec<TestDefinition>>,
steps: Vec<TestDefinition>,
) {
let step = TestDefinition::new_step(
name.as_ref().to_string(),
@ -388,7 +378,7 @@ impl TestCollector {
&mut self,
name: N,
range: SourceRange,
steps: Option<Vec<TestDefinition>>,
steps: Vec<TestDefinition>,
) {
let definition = TestDefinition::new(
&self.specifier,
@ -553,59 +543,59 @@ pub mod tests {
level: 0,
name: "test a".to_string(),
range: new_range(12, 16),
steps: Some(vec![
steps: vec![
TestDefinition {
id: "4c7333a1e47721631224408c467f32751fe34b876cab5ec1f6ac71980ff15ad3".to_string(),
level: 1,
name: "a step".to_string(),
range: new_range(83, 87),
steps: Some(vec![
steps: vec![
TestDefinition {
id: "abf356f59139b77574089615f896a6f501c010985d95b8a93abeb0069ccb2201".to_string(),
level: 2,
name: "sub step".to_string(),
range: new_range(132, 136),
steps: None,
steps: vec![],
}
])
]
}
]),
],
},
TestDefinition {
id: "86b4c821900e38fc89f24bceb0e45193608ab3f9d2a6019c7b6a5aceff5d7df2".to_string(),
level: 0,
name: "useFnName".to_string(),
range: new_range(254, 258),
steps: Some(vec![
steps: vec![
TestDefinition {
id: "67a390d0084ae5fb88f3510c470a72a553581f1d0d5ba5fa89aee7a754f3953a".to_string(),
level: 1,
name: "step c".to_string(),
range: new_range(313, 314),
steps: None,
steps: vec![],
}
])
]
},
TestDefinition {
id: "580eda89d7f5e619774c20e13b7d07a8e77c39cba101d60565144d48faa837cb".to_string(),
level: 0,
name: "test b".to_string(),
range: new_range(358, 362),
steps: None,
steps: vec![],
},
TestDefinition {
id: "0b7c6bf3cd617018d33a1bf982a08fe088c5bb54fcd5eb9e802e7c137ec1af94".to_string(),
level: 0,
name: "test c".to_string(),
range: new_range(420, 424),
steps: None,
steps: vec![],
},
TestDefinition {
id: "69d9fe87f64f5b66cb8b631d4fd2064e8224b8715a049be54276c42189ff8f9f".to_string(),
level: 0,
name: "test d".to_string(),
range: new_range(480, 481),
steps: None,
steps: vec![],
}
]
);

View file

@ -18,7 +18,7 @@ pub struct TestDefinition {
pub level: usize,
pub name: String,
pub range: SourceRange,
pub steps: Option<Vec<TestDefinition>>,
pub steps: Vec<TestDefinition>,
}
impl TestDefinition {
@ -26,7 +26,7 @@ impl TestDefinition {
specifier: &ModuleSpecifier,
name: String,
range: SourceRange,
steps: Option<Vec<TestDefinition>>,
steps: Vec<TestDefinition>,
) -> Self {
let id = checksum::gen(&[specifier.as_str().as_bytes(), name.as_bytes()]);
Self {
@ -43,7 +43,7 @@ impl TestDefinition {
range: SourceRange,
parent: String,
level: usize,
steps: Option<Vec<TestDefinition>>,
steps: Vec<TestDefinition>,
) -> Self {
let id = checksum::gen(&[
parent.as_bytes(),
@ -66,27 +66,18 @@ impl TestDefinition {
lsp_custom::TestData {
id: self.id.clone(),
label: self.name.clone(),
steps: self.steps.as_ref().map(|steps| {
steps
.iter()
.map(|step| step.as_test_data(source_text_info))
.collect()
}),
steps: self
.steps
.iter()
.map(|step| step.as_test_data(source_text_info))
.collect(),
range: Some(source_range_to_lsp_range(&self.range, source_text_info)),
}
}
fn find_step(&self, name: &str, level: usize) -> Option<&TestDefinition> {
if let Some(steps) = &self.steps {
for step in steps {
if step.name == name && step.level == level {
return Some(step);
} else if let Some(step) = step.find_step(name, level) {
return Some(step);
}
}
}
None
fn contains_id<S: AsRef<str>>(&self, id: S) -> bool {
let id = id.as_ref();
self.id == id || self.steps.iter().any(|td| td.contains_id(id))
}
}
@ -102,6 +93,16 @@ pub struct TestDefinitions {
pub script_version: String,
}
impl Default for TestDefinitions {
fn default() -> Self {
TestDefinitions {
script_version: "1".to_string(),
discovered: vec![],
injected: vec![],
}
}
}
impl TestDefinitions {
/// Return the test definitions as a testing module notification.
pub fn as_notification(
@ -137,6 +138,19 @@ impl TestDefinitions {
})
}
/// Register a dynamically-detected test. Returns false if a test with the
/// same static id was already registered statically or dynamically. Otherwise
/// returns true.
pub fn inject(&mut self, data: lsp_custom::TestData) -> bool {
if self.discovered.iter().any(|td| td.contains_id(&data.id))
|| self.injected.iter().any(|td| td.id == data.id)
{
return false;
}
self.injected.push(data);
true
}
/// Return a test definition identified by the test ID.
pub fn get_by_id<S: AsRef<str>>(&self, id: S) -> Option<&TestDefinition> {
self
@ -144,20 +158,4 @@ impl TestDefinitions {
.iter()
.find(|td| td.id.as_str() == id.as_ref())
}
/// Return a test definition by the test name.
pub fn get_by_name(&self, name: &str) -> Option<&TestDefinition> {
self.discovered.iter().find(|td| td.name.as_str() == name)
}
pub fn get_step_by_name(
&self,
test_name: &str,
level: usize,
name: &str,
) -> Option<&TestDefinition> {
self
.get_by_name(test_name)
.and_then(|td| td.find_step(name, level))
}
}

View file

@ -25,13 +25,13 @@ use deno_core::futures::future;
use deno_core::futures::stream;
use deno_core::futures::StreamExt;
use deno_core::parking_lot::Mutex;
use deno_core::serde_json::json;
use deno_core::serde_json::Value;
use deno_core::parking_lot::RwLock;
use deno_core::ModuleSpecifier;
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 std::collections::HashMap;
use std::collections::HashSet;
use std::sync::Arc;
@ -48,10 +48,10 @@ fn as_queue_and_filters(
tests: &HashMap<ModuleSpecifier, TestDefinitions>,
) -> (
HashSet<ModuleSpecifier>,
HashMap<ModuleSpecifier, TestFilter>,
HashMap<ModuleSpecifier, LspTestFilter>,
) {
let mut queue: HashSet<ModuleSpecifier> = HashSet::new();
let mut filters: HashMap<ModuleSpecifier, TestFilter> = HashMap::new();
let mut filters: HashMap<ModuleSpecifier, LspTestFilter> = HashMap::new();
if let Some(include) = &params.include {
for item in include {
@ -61,12 +61,12 @@ fn as_queue_and_filters(
if let Some(test) = test_definitions.get_by_id(id) {
let filter =
filters.entry(item.text_document.uri.clone()).or_default();
if let Some(include) = filter.maybe_include.as_mut() {
if let Some(include) = filter.include.as_mut() {
include.insert(test.id.clone(), test.clone());
} else {
let mut include = HashMap::new();
include.insert(test.id.clone(), test.clone());
filter.maybe_include = Some(include);
filter.include = Some(include);
}
}
}
@ -80,29 +80,20 @@ fn as_queue_and_filters(
queue.extend(tests.keys().cloned());
}
if let Some(exclude) = &params.exclude {
for item in exclude {
if let Some(test_definitions) = tests.get(&item.text_document.uri) {
if let Some(id) = &item.id {
// there is currently no way to filter out a specific test, so we have
// to ignore the exclusion
if item.step_id.is_none() {
if let Some(test) = test_definitions.get_by_id(id) {
let filter =
filters.entry(item.text_document.uri.clone()).or_default();
if let Some(exclude) = filter.maybe_exclude.as_mut() {
exclude.insert(test.id.clone(), test.clone());
} else {
let mut exclude = HashMap::new();
exclude.insert(test.id.clone(), test.clone());
filter.maybe_exclude = Some(exclude);
}
}
for item in &params.exclude {
if let Some(test_definitions) = tests.get(&item.text_document.uri) {
if let Some(id) = &item.id {
// there is no way to exclude a test step
if item.step_id.is_none() {
if let Some(test) = test_definitions.get_by_id(id) {
let filter =
filters.entry(item.text_document.uri.clone()).or_default();
filter.exclude.insert(test.id.clone(), test.clone());
}
} else {
// the entire test module is excluded
queue.remove(&item.text_document.uri);
}
} else {
// the entire test module is excluded
queue.remove(&item.text_document.uri);
}
}
}
@ -131,14 +122,14 @@ fn as_test_messages<S: AsRef<str>>(
}
#[derive(Debug, Clone, Default, PartialEq)]
struct TestFilter {
maybe_include: Option<HashMap<String, TestDefinition>>,
maybe_exclude: Option<HashMap<String, TestDefinition>>,
struct LspTestFilter {
include: Option<HashMap<String, TestDefinition>>,
exclude: HashMap<String, TestDefinition>,
}
impl TestFilter {
impl LspTestFilter {
fn as_ids(&self, test_definitions: &TestDefinitions) -> Vec<String> {
let ids: Vec<String> = if let Some(include) = &self.maybe_include {
let ids: Vec<String> = if let Some(include) = &self.include {
include.keys().cloned().collect()
} else {
test_definitions
@ -147,33 +138,10 @@ impl TestFilter {
.map(|td| td.id.clone())
.collect()
};
if let Some(exclude) = &self.maybe_exclude {
ids
.into_iter()
.filter(|id| !exclude.contains_key(id))
.collect()
} else {
ids
}
}
/// return the filter as a JSON value, suitable for sending as a filter to the
/// test runner.
fn as_test_options(&self) -> Value {
let maybe_include: Option<Vec<String>> = self
.maybe_include
.as_ref()
.map(|inc| inc.iter().map(|(_, td)| td.name.clone()).collect());
let maybe_exclude: Option<Vec<String>> = self
.maybe_exclude
.as_ref()
.map(|ex| ex.iter().map(|(_, td)| td.name.clone()).collect());
json!({
"filter": {
"include": maybe_include,
"exclude": maybe_exclude,
}
})
ids
.into_iter()
.filter(|id| !self.exclude.contains_key(id))
.collect()
}
}
@ -184,14 +152,14 @@ async fn test_specifier(
mode: test::TestMode,
sender: &TestEventSender,
token: CancellationToken,
options: Option<Value>,
filter: test::TestFilter,
) -> Result<(), AnyError> {
if !token.is_cancelled() {
let mut worker = create_main_worker(
&ps,
specifier.clone(),
permissions,
vec![ops::testing::init(sender.clone())],
vec![ops::testing::init(sender.clone(), filter)],
Stdio {
stdin: StdioPipe::Inherit,
stdout: StdioPipe::File(sender.stdout()),
@ -217,10 +185,9 @@ async fn test_specifier(
worker.dispatch_load_event(&located_script_name!())?;
let options = options.unwrap_or_else(|| json!({}));
let test_result = worker.js_runtime.execute_script(
&located_script_name!(),
&format!(r#"Deno[Deno.internal].runTests({})"#, json!(options)),
r#"Deno[Deno.internal].runTests()"#,
)?;
worker.js_runtime.resolve_value(test_result).await?;
@ -241,7 +208,7 @@ async fn test_specifier(
pub struct TestRun {
id: u32,
kind: lsp_custom::TestRunKind,
filters: HashMap<ModuleSpecifier, TestFilter>,
filters: HashMap<ModuleSpecifier, LspTestFilter>,
queue: HashSet<ModuleSpecifier>,
tests: Arc<Mutex<HashMap<ModuleSpecifier, TestDefinitions>>>,
token: CancellationToken,
@ -343,13 +310,31 @@ impl TestRun {
let mut queue = self.queue.iter().collect::<Vec<&ModuleSpecifier>>();
queue.sort();
let tests: Arc<RwLock<IndexMap<usize, test::TestDescription>>> =
Arc::new(RwLock::new(IndexMap::new()));
let mut test_steps = IndexMap::new();
let tests_ = tests.clone();
let join_handles = queue.into_iter().map(move |specifier| {
let specifier = specifier.clone();
let ps = ps.clone();
let permissions = permissions.clone();
let mut sender = sender.clone();
let options = self.filters.get(&specifier).map(|f| f.as_test_options());
let lsp_filter = self.filters.get(&specifier);
let filter = test::TestFilter {
substring: None,
regex: None,
include: lsp_filter.and_then(|f| {
f.include
.as_ref()
.map(|i| i.values().map(|t| t.name.clone()).collect())
}),
exclude: lsp_filter
.map(|f| f.exclude.values().map(|t| t.name.clone()).collect())
.unwrap_or_default(),
};
let token = self.token.clone();
let tests = tests_.clone();
tokio::task::spawn_blocking(move || {
let origin = specifier.to_string();
@ -360,14 +345,23 @@ impl TestRun {
test::TestMode::Executable,
&sender,
token,
options,
filter,
));
if let Err(error) = file_result {
if error.is::<JsError>() {
sender.send(test::TestEvent::UncaughtError(
origin,
origin.clone(),
Box::new(error.downcast::<JsError>().unwrap()),
))?;
for desc in tests.read().values() {
if desc.origin == origin {
sender.send(test::TestEvent::Result(
desc.id,
test::TestResult::Cancelled,
0,
))?
}
}
} else {
return Err(error);
}
@ -396,6 +390,10 @@ impl TestRun {
while let Some(event) = receiver.recv().await {
match event {
test::TestEvent::Register(description) => {
reporter.report_register(&description);
tests.write().insert(description.id, description);
}
test::TestEvent::Plan(plan) => {
summary.total += plan.total;
summary.filtered_out += plan.filtered_out;
@ -406,13 +404,14 @@ impl TestRun {
reporter.report_plan(&plan);
}
test::TestEvent::Wait(description) => {
reporter.report_wait(&description);
test::TestEvent::Wait(id) => {
reporter.report_wait(tests.read().get(&id).unwrap());
}
test::TestEvent::Output(output) => {
reporter.report_output(&output);
}
test::TestEvent::Result(description, result, elapsed) => {
test::TestEvent::Result(id, result, elapsed) => {
let description = tests.read().get(&id).unwrap().clone();
match &result {
test::TestResult::Ok => summary.passed += 1,
test::TestResult::Ignored => summary.ignored += 1,
@ -420,6 +419,9 @@ impl TestRun {
summary.failed += 1;
summary.failures.push((description.clone(), error.clone()));
}
test::TestResult::Cancelled => {
summary.failed += 1;
}
}
reporter.report_result(&description, &result, elapsed);
@ -429,10 +431,14 @@ impl TestRun {
summary.failed += 1;
summary.uncaught_errors.push((origin, error));
}
test::TestEvent::StepWait(description) => {
reporter.report_step_wait(&description);
test::TestEvent::StepRegister(description) => {
reporter.report_step_register(&description);
test_steps.insert(description.id, description);
}
test::TestEvent::StepResult(description, result, duration) => {
test::TestEvent::StepWait(id) => {
reporter.report_step_wait(test_steps.get(&id).unwrap());
}
test::TestEvent::StepResult(id, result, duration) => {
match &result {
test::TestStepResult::Ok => {
summary.passed_steps += 1;
@ -447,7 +453,11 @@ impl TestRun {
summary.pending_steps += 1;
}
}
reporter.report_step_result(&description, &result, duration);
reporter.report_step_result(
test_steps.get(&id).unwrap(),
&result,
duration,
);
}
}
@ -562,10 +572,8 @@ impl From<&TestOrTestStepDescription> for lsp_custom::TestData {
impl From<&test::TestDescription> for lsp_custom::TestData {
fn from(desc: &test::TestDescription) -> Self {
let id = checksum::gen(&[desc.origin.as_bytes(), desc.name.as_bytes()]);
Self {
id,
id: desc.static_id(),
label: desc.name.clone(),
steps: Default::default(),
range: None,
@ -576,14 +584,9 @@ impl From<&test::TestDescription> for lsp_custom::TestData {
impl From<&test::TestDescription> for lsp_custom::TestIdentifier {
fn from(desc: &test::TestDescription) -> Self {
let uri = ModuleSpecifier::parse(&desc.origin).unwrap();
let id = Some(checksum::gen(&[
desc.origin.as_bytes(),
desc.name.as_bytes(),
]));
Self {
text_document: lsp::TextDocumentIdentifier { uri },
id,
id: Some(desc.static_id()),
step_id: None,
}
}
@ -591,14 +594,8 @@ impl From<&test::TestDescription> for lsp_custom::TestIdentifier {
impl From<&test::TestStepDescription> for lsp_custom::TestData {
fn from(desc: &test::TestStepDescription) -> Self {
let id = checksum::gen(&[
desc.test.origin.as_bytes(),
&desc.level.to_be_bytes(),
desc.name.as_bytes(),
]);
Self {
id,
id: desc.static_id(),
label: desc.name.clone(),
steps: Default::default(),
range: None,
@ -608,21 +605,14 @@ impl From<&test::TestStepDescription> for lsp_custom::TestData {
impl From<&test::TestStepDescription> for lsp_custom::TestIdentifier {
fn from(desc: &test::TestStepDescription) -> Self {
let uri = ModuleSpecifier::parse(&desc.test.origin).unwrap();
let id = Some(checksum::gen(&[
desc.test.origin.as_bytes(),
desc.test.name.as_bytes(),
]));
let step_id = Some(checksum::gen(&[
desc.test.origin.as_bytes(),
&desc.level.to_be_bytes(),
desc.name.as_bytes(),
]));
let uri = ModuleSpecifier::parse(&desc.origin).unwrap();
Self {
text_document: lsp::TextDocumentIdentifier { uri },
id,
step_id,
id: Some(checksum::gen(&[
desc.origin.as_bytes(),
desc.root_name.as_bytes(),
])),
step_id: Some(desc.static_id()),
}
}
}
@ -653,61 +643,28 @@ impl LspTestReporter {
}
}
fn add_step(&self, desc: &test::TestStepDescription) {
if let Ok(specifier) = ModuleSpecifier::parse(&desc.test.origin) {
let mut tests = self.tests.lock();
let entry =
tests
.entry(specifier.clone())
.or_insert_with(|| TestDefinitions {
discovered: Default::default(),
injected: Default::default(),
script_version: "1".to_string(),
});
let mut prev: lsp_custom::TestData = desc.into();
if let Some(stack) = self.stack.get(&desc.test.origin) {
for item in stack.iter().rev() {
let mut data: lsp_custom::TestData = item.into();
data.steps = Some(vec![prev]);
prev = data;
}
entry.injected.push(prev.clone());
let label = if let Some(root) = &self.maybe_root_uri {
specifier.as_str().replace(root.as_str(), "")
} else {
specifier
.path_segments()
.and_then(|s| s.last().map(|s| s.to_string()))
.unwrap_or_else(|| "<unknown>".to_string())
};
self
.client
.send_test_notification(TestingNotification::Module(
lsp_custom::TestModuleNotificationParams {
text_document: lsp::TextDocumentIdentifier { uri: specifier },
kind: lsp_custom::TestModuleNotificationKind::Insert,
label,
tests: vec![prev],
},
));
}
}
fn progress(&self, message: lsp_custom::TestRunProgressMessage) {
self
.client
.send_test_notification(TestingNotification::Progress(
lsp_custom::TestRunProgressParams {
id: self.id,
message,
},
));
}
}
/// Add a test which is being reported from the test runner but was not
/// statically identified
fn add_test(&self, desc: &test::TestDescription) {
if let Ok(specifier) = ModuleSpecifier::parse(&desc.origin) {
let mut tests = self.tests.lock();
let entry =
tests
.entry(specifier.clone())
.or_insert_with(|| TestDefinitions {
discovered: Default::default(),
injected: Default::default(),
script_version: "1".to_string(),
});
entry.injected.push(desc.into());
impl test::TestReporter for LspTestReporter {
fn report_plan(&mut self, _plan: &test::TestPlan) {}
fn report_register(&mut self, desc: &test::TestDescription) {
let mut tests = self.tests.lock();
let tds = tests
.entry(ModuleSpecifier::parse(&desc.location.file_name).unwrap())
.or_default();
if tds.inject(desc.into()) {
let specifier = ModuleSpecifier::parse(&desc.origin).unwrap();
let label = if let Some(root) = &self.maybe_root_uri {
specifier.as_str().replace(root.as_str(), "")
} else {
@ -729,49 +686,7 @@ impl LspTestReporter {
}
}
fn progress(&self, message: lsp_custom::TestRunProgressMessage) {
self
.client
.send_test_notification(TestingNotification::Progress(
lsp_custom::TestRunProgressParams {
id: self.id,
message,
},
));
}
fn includes_step(&self, desc: &test::TestStepDescription) -> bool {
if let Ok(specifier) = ModuleSpecifier::parse(&desc.test.origin) {
let tests = self.tests.lock();
if let Some(test_definitions) = tests.get(&specifier) {
return test_definitions
.get_step_by_name(&desc.test.name, desc.level, &desc.name)
.is_some();
}
}
false
}
fn includes_test(&self, desc: &test::TestDescription) -> bool {
if let Ok(specifier) = ModuleSpecifier::parse(&desc.origin) {
let tests = self.tests.lock();
if let Some(test_definitions) = tests.get(&specifier) {
return test_definitions.get_by_name(&desc.name).is_some();
}
}
false
}
}
impl test::TestReporter for LspTestReporter {
fn report_plan(&mut self, _plan: &test::TestPlan) {
// there is nothing to do on report_plan
}
fn report_wait(&mut self, desc: &test::TestDescription) {
if !self.includes_test(desc) {
self.add_test(desc);
}
self.current_origin = Some(desc.origin.clone());
let test: lsp_custom::TestIdentifier = desc.into();
let stack = self.stack.entry(desc.origin.clone()).or_default();
@ -827,6 +742,13 @@ impl test::TestReporter for LspTestReporter {
duration: Some(elapsed as u32),
})
}
test::TestResult::Cancelled => {
self.progress(lsp_custom::TestRunProgressMessage::Failed {
test: desc.into(),
messages: vec![],
duration: Some(elapsed as u32),
})
}
}
}
@ -861,13 +783,46 @@ impl test::TestReporter for LspTestReporter {
}
}
fn report_step_wait(&mut self, desc: &test::TestStepDescription) {
if !self.includes_step(desc) {
self.add_step(desc);
fn report_step_register(&mut self, desc: &test::TestStepDescription) {
let mut tests = self.tests.lock();
let tds = tests
.entry(ModuleSpecifier::parse(&desc.location.file_name).unwrap())
.or_default();
if tds.inject(desc.into()) {
let specifier = ModuleSpecifier::parse(&desc.origin).unwrap();
let mut prev: lsp_custom::TestData = desc.into();
if let Some(stack) = self.stack.get(&desc.origin) {
for item in stack.iter().rev() {
let mut data: lsp_custom::TestData = item.into();
data.steps = vec![prev];
prev = data;
}
let label = if let Some(root) = &self.maybe_root_uri {
specifier.as_str().replace(root.as_str(), "")
} else {
specifier
.path_segments()
.and_then(|s| s.last().map(|s| s.to_string()))
.unwrap_or_else(|| "<unknown>".to_string())
};
self
.client
.send_test_notification(TestingNotification::Module(
lsp_custom::TestModuleNotificationParams {
text_document: lsp::TextDocumentIdentifier { uri: specifier },
kind: lsp_custom::TestModuleNotificationKind::Insert,
label,
tests: vec![prev],
},
));
}
}
}
fn report_step_wait(&mut self, desc: &test::TestStepDescription) {
let test: lsp_custom::TestIdentifier = desc.into();
let stack = self.stack.entry(desc.test.origin.clone()).or_default();
self.current_origin = Some(desc.test.origin.clone());
let stack = self.stack.entry(desc.origin.clone()).or_default();
self.current_origin = Some(desc.origin.clone());
assert!(!stack.is_empty());
stack.push(desc.into());
self.progress(lsp_custom::TestRunProgressMessage::Started { test });
@ -879,7 +834,7 @@ impl test::TestReporter for LspTestReporter {
result: &test::TestStepResult,
elapsed: u64,
) {
let stack = self.stack.entry(desc.test.origin.clone()).or_default();
let stack = self.stack.entry(desc.origin.clone()).or_default();
assert_eq!(stack.pop(), Some(desc.into()));
match result {
test::TestStepResult::Ok => {
@ -927,6 +882,7 @@ impl test::TestReporter for LspTestReporter {
mod tests {
use super::*;
use crate::lsp::testing::collectors::tests::new_range;
use deno_core::serde_json::json;
#[test]
fn test_as_queue_and_filters() {
@ -941,7 +897,7 @@ mod tests {
id: None,
step_id: None,
}]),
exclude: Some(vec![lsp_custom::TestIdentifier {
exclude: vec![lsp_custom::TestIdentifier {
text_document: lsp::TextDocumentIdentifier {
uri: specifier.clone(),
},
@ -950,7 +906,7 @@ mod tests {
.to_string(),
),
step_id: None,
}]),
}],
};
let mut tests = HashMap::new();
let test_def_a = TestDefinition {
@ -959,7 +915,7 @@ mod tests {
level: 0,
name: "test a".to_string(),
range: new_range(420, 424),
steps: None,
steps: vec![],
};
let test_def_b = TestDefinition {
id: "69d9fe87f64f5b66cb8b631d4fd2064e8224b8715a049be54276c42189ff8f9f"
@ -967,7 +923,7 @@ mod tests {
level: 0,
name: "test b".to_string(),
range: new_range(480, 481),
steps: None,
steps: vec![],
};
let test_definitions = TestDefinitions {
discovered: vec![test_def_a, test_def_b.clone()],
@ -988,9 +944,9 @@ mod tests {
let filter = maybe_filter.unwrap();
assert_eq!(
filter,
&TestFilter {
maybe_include: None,
maybe_exclude: Some(exclude),
&LspTestFilter {
include: None,
exclude,
}
);
assert_eq!(
@ -1000,14 +956,5 @@ mod tests {
.to_string()
]
);
assert_eq!(
filter.as_test_options(),
json!({
"filter": {
"include": null,
"exclude": vec!["test b"],
}
})
);
}
}

View file

@ -21,8 +21,9 @@ pub struct TestData {
pub id: String,
/// The human readable test to display for the test.
pub label: String,
#[serde(skip_serializing_if = "Option::is_none")]
pub steps: Option<Vec<TestData>>,
#[serde(skip_serializing_if = "Vec::is_empty")]
#[serde(default)]
pub steps: Vec<TestData>,
/// The range where the test is located.
#[serde(skip_serializing_if = "Option::is_none")]
pub range: Option<lsp::Range>,
@ -92,8 +93,9 @@ pub enum TestRunKind {
pub struct TestRunRequestParams {
pub id: u32,
pub kind: TestRunKind,
#[serde(skip_serializing_if = "Option::is_none")]
pub exclude: Option<Vec<TestIdentifier>>,
#[serde(skip_serializing_if = "Vec::is_empty")]
#[serde(default)]
pub exclude: Vec<TestIdentifier>,
#[serde(skip_serializing_if = "Option::is_none")]
pub include: Option<Vec<TestIdentifier>>,
}

View file

@ -1,7 +1,11 @@
// Copyright 2018-2022 the Deno authors. All rights reserved. MIT license.
use crate::tools::test::TestDescription;
use crate::tools::test::TestEvent;
use crate::tools::test::TestEventSender;
use crate::tools::test::TestFilter;
use crate::tools::test::TestLocation;
use crate::tools::test::TestStepDescription;
use deno_core::error::generic_error;
use deno_core::error::AnyError;
@ -12,18 +16,26 @@ use deno_core::OpState;
use deno_runtime::permissions::create_child_permissions;
use deno_runtime::permissions::ChildPermissionsArg;
use deno_runtime::permissions::Permissions;
use serde::Deserialize;
use serde::Deserializer;
use serde::Serialize;
use std::sync::atomic::AtomicUsize;
use std::sync::atomic::Ordering;
use uuid::Uuid;
pub fn init(sender: TestEventSender) -> Extension {
pub fn init(sender: TestEventSender, filter: TestFilter) -> Extension {
Extension::builder()
.ops(vec![
op_pledge_test_permissions::decl(),
op_restore_test_permissions::decl(),
op_get_test_origin::decl(),
op_register_test::decl(),
op_register_test_step::decl(),
op_dispatch_test_event::decl(),
])
.state(move |state| {
state.put(sender.clone());
state.put(filter.clone());
Ok(())
})
.build()
@ -76,6 +88,91 @@ fn op_get_test_origin(state: &mut OpState) -> Result<String, AnyError> {
Ok(state.borrow::<ModuleSpecifier>().to_string())
}
#[derive(Debug, Deserialize)]
#[serde(rename_all = "camelCase")]
struct TestInfo {
name: String,
origin: String,
location: TestLocation,
}
#[derive(Debug, Serialize)]
#[serde(rename_all = "camelCase")]
struct TestRegisterResult {
id: usize,
filtered_out: bool,
}
static NEXT_ID: AtomicUsize = AtomicUsize::new(0);
#[op]
fn op_register_test(
state: &mut OpState,
info: TestInfo,
) -> Result<TestRegisterResult, AnyError> {
let id = NEXT_ID.fetch_add(1, Ordering::SeqCst);
let filter = state.borrow::<TestFilter>().clone();
let filtered_out = !filter.includes(&info.name);
let description = TestDescription {
id,
name: info.name,
origin: info.origin,
location: info.location,
};
let mut sender = state.borrow::<TestEventSender>().clone();
sender.send(TestEvent::Register(description)).ok();
Ok(TestRegisterResult { id, filtered_out })
}
fn deserialize_parent<'de, D>(deserializer: D) -> Result<usize, D::Error>
where
D: Deserializer<'de>,
{
#[derive(Deserialize)]
struct Parent {
id: usize,
}
Ok(Parent::deserialize(deserializer)?.id)
}
#[derive(Debug, Deserialize)]
#[serde(rename_all = "camelCase")]
struct TestStepInfo {
name: String,
origin: String,
location: TestLocation,
level: usize,
#[serde(rename = "parent")]
#[serde(deserialize_with = "deserialize_parent")]
parent_id: usize,
root_id: usize,
root_name: String,
}
#[op]
fn op_register_test_step(
state: &mut OpState,
info: TestStepInfo,
) -> Result<TestRegisterResult, AnyError> {
let id = NEXT_ID.fetch_add(1, Ordering::SeqCst);
let description = TestStepDescription {
id,
name: info.name,
origin: info.origin,
location: info.location,
level: info.level,
parent_id: info.parent_id,
root_id: info.root_id,
root_name: info.root_name,
};
let mut sender = state.borrow::<TestEventSender>().clone();
sender.send(TestEvent::StepRegister(description)).ok();
Ok(TestRegisterResult {
id,
filtered_out: false,
})
}
#[op]
fn op_dispatch_test_event(
state: &mut OpState,

View file

@ -9,8 +9,8 @@ nested failure ...
at [WILDCARD]/failing_steps.ts:[WILDCARD]
[WILDCARD]
inner 2 ... ok ([WILDCARD])
FAILED ([WILDCARD])
FAILED ([WILDCARD])
step 1 ... FAILED ([WILDCARD])
nested failure ... FAILED ([WILDCARD])
multiple test step failures ...
step 1 ... FAILED ([WILDCARD])
error: Error: Fail.
@ -23,7 +23,7 @@ multiple test step failures ...
^
at [WILDCARD]/failing_steps.ts:[WILDCARD]
[WILDCARD]
FAILED ([WILDCARD])
multiple test step failures ... FAILED ([WILDCARD])
failing step in failing test ...
step 1 ... FAILED ([WILDCARD])
error: Error: Fail.
@ -31,7 +31,7 @@ failing step in failing test ...
^
at [WILDCARD]/failing_steps.ts:[WILDCARD]
at [WILDCARD]
FAILED ([WILDCARD])
failing step in failing test ... FAILED ([WILDCARD])
ERRORS

View file

@ -3,6 +3,6 @@ running 1 test from ./test/steps/ignored_steps.ts
ignored step ...
step 1 ... ignored ([WILDCARD])
step 2 ... ok ([WILDCARD])
ok ([WILDCARD])
ignored step ... ok ([WILDCARD])
ok | 1 passed (1 step) | 0 failed | 0 ignored (1 step) [WILDCARD]

View file

@ -2,23 +2,23 @@
running 7 tests from ./test/steps/invalid_usage.ts
capturing ...
some step ... ok ([WILDCARD])
FAILED ([WILDCARD])
capturing ... FAILED ([WILDCARD])
top level missing await ...
step ... pending ([WILDCARD])
FAILED ([WILDCARD])
top level missing await ... FAILED ([WILDCARD])
inner missing await ...
step ...
inner ... pending ([WILDCARD])
error: Error: Parent scope completed before test step finished execution. Ensure all steps are awaited (ex. `await t.step(...)`).
at [WILDCARD]
at async TestContext.step [WILDCARD]
FAILED ([WILDCARD])
step ... FAILED ([WILDCARD])
error: Error: There were still test steps running after the current scope finished execution. Ensure all steps are awaited (ex. `await t.step(...)`).
await t.step("step", (t) => {
^
at [WILDCARD]
at async fn ([WILDCARD]/invalid_usage.ts:[WILDCARD])
FAILED ([WILDCARD])
inner missing await ... FAILED ([WILDCARD])
parallel steps with sanitizers ...
step 1 ... pending ([WILDCARD])
step 2 ... FAILED ([WILDCARD])
@ -28,7 +28,7 @@ parallel steps with sanitizers ...
^
at [WILDCARD]
at [WILDCARD]/invalid_usage.ts:[WILDCARD]
FAILED ([WILDCARD])
parallel steps with sanitizers ... FAILED ([WILDCARD])
parallel steps when first has sanitizer ...
step 1 ... pending ([WILDCARD])
step 2 ... FAILED ([WILDCARD])
@ -38,7 +38,7 @@ parallel steps when first has sanitizer ...
^
at [WILDCARD]
at [WILDCARD]/invalid_usage.ts:[WILDCARD]
FAILED ([WILDCARD])
parallel steps when first has sanitizer ... FAILED ([WILDCARD])
parallel steps when second has sanitizer ...
step 1 ... ok ([WILDCARD])
step 2 ... FAILED ([WILDCARD])
@ -48,11 +48,11 @@ parallel steps when second has sanitizer ...
^
at [WILDCARD]
at [WILDCARD]/invalid_usage.ts:[WILDCARD]
FAILED ([WILDCARD])
parallel steps when second has sanitizer ... FAILED ([WILDCARD])
parallel steps where only inner tests have sanitizers ...
step 1 ...
step inner ... ok ([WILDCARD])
ok ([WILDCARD])
step 1 ... ok ([WILDCARD])
step 2 ...
step inner ... FAILED ([WILDCARD])
error: Error: Cannot start test step with sanitizers while another test step is running.
@ -61,8 +61,8 @@ parallel steps where only inner tests have sanitizers ...
^
at [WILDCARD]
at [WILDCARD]/invalid_usage.ts:[WILDCARD]
FAILED ([WILDCARD])
FAILED ([WILDCARD])
step 2 ... FAILED ([WILDCARD])
parallel steps where only inner tests have sanitizers ... FAILED ([WILDCARD])
ERRORS
@ -75,8 +75,6 @@ error: Error: Cannot run test step after parent scope has finished execution. En
top level missing await => ./test/steps/invalid_usage.ts:[WILDCARD]
error: Error: There were still test steps running after the current scope finished execution. Ensure all steps are awaited (ex. `await t.step(...)`).
at postValidation [WILDCARD]
at testStepSanitizer ([WILDCARD])
[WILDCARD]
inner missing await => ./test/steps/invalid_usage.ts:[WILDCARD]
@ -85,8 +83,6 @@ error: Error: 1 test step failed.
parallel steps with sanitizers => ./test/steps/invalid_usage.ts:[WILDCARD]
error: Error: There were still test steps running after the current scope finished execution. Ensure all steps are awaited (ex. `await t.step(...)`).
at postValidation [WILDCARD]
at testStepSanitizer ([WILDCARD])
[WILDCARD]
parallel steps when first has sanitizer => ./test/steps/invalid_usage.ts:[WILDCARD]

View file

@ -4,35 +4,35 @@ description ...
step 1 ...
inner 1 ... ok ([WILDCARD]ms)
inner 2 ... ok ([WILDCARD]ms)
ok ([WILDCARD]ms)
ok ([WILDCARD]ms)
step 1 ... ok ([WILDCARD]ms)
description ... ok ([WILDCARD]ms)
parallel steps without sanitizers ...
step 1 ... ok ([WILDCARD])
step 2 ... ok ([WILDCARD])
ok ([WILDCARD])
parallel steps without sanitizers ... ok ([WILDCARD])
parallel steps without sanitizers due to parent ...
step 1 ... ok ([WILDCARD])
step 2 ... ok ([WILDCARD])
ok ([WILDCARD])
parallel steps without sanitizers due to parent ... ok ([WILDCARD])
steps with disabled sanitizers, then enabled, then parallel disabled ...
step 1 ...
step 1 ...
step 1 ...
step 1 ... ok ([WILDCARD])
step 1 ... ok ([WILDCARD])
ok ([WILDCARD])
step 1 ... ok ([WILDCARD])
step 2 ... ok ([WILDCARD])
ok ([WILDCARD])
ok ([WILDCARD])
ok ([WILDCARD])
step 1 ... ok ([WILDCARD])
step 1 ... ok ([WILDCARD])
steps with disabled sanitizers, then enabled, then parallel disabled ... ok ([WILDCARD])
steps buffered then streaming reporting ...
step 1 ...
step 1 - 1 ... ok ([WILDCARD])
step 1 - 2 ...
step 1 - 2 - 1 ... ok ([WILDCARD])
ok ([WILDCARD])
ok ([WILDCARD])
step 1 - 2 ... ok ([WILDCARD])
step 1 ... ok ([WILDCARD])
step 2 ... ok ([WILDCARD])
ok ([WILDCARD])
steps buffered then streaming reporting ... ok ([WILDCARD])
ok | 5 passed (18 steps) | 0 failed [WILDCARD]

View file

@ -3,6 +3,7 @@ foo 1 ... FAILED ([WILDCARD])
foo 2 ... ok ([WILDCARD])
foo 3 ...
Uncaught error from ./test/uncaught_errors_1.ts FAILED
foo 3 ... cancelled (0ms)
running 3 tests from ./test/uncaught_errors_2.ts
bar 1 ... ok ([WILDCARD])
bar 2 ... FAILED ([WILDCARD])
@ -53,6 +54,6 @@ bar 2 => ./test/uncaught_errors_2.ts:3:6
bar 3 => ./test/uncaught_errors_2.ts:6:6
./test/uncaught_errors_3.ts (uncaught error)
FAILED | 2 passed | 5 failed ([WILDCARD])
FAILED | 2 passed | 6 failed ([WILDCARD])
error: Test failed

View file

@ -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::StepResult(description, result, duration) => {
TestEvent::StepWait(id) => {
reporter.report_step_wait(test_steps.get(&id).unwrap());
}
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,
},

File diff suppressed because it is too large Load diff