2023-01-02 16:00:42 -05:00
// Copyright 2018-2023 the Deno authors. All rights reserved. MIT license.
2022-03-29 18:59:27 -04:00
use super ::definitions ::TestDefinition ;
use super ::definitions ::TestDefinitions ;
use super ::lsp_custom ;
2022-06-27 16:54:09 -04:00
use crate ::args ::flags_from_vec ;
use crate ::args ::DenoSubcommand ;
2022-03-29 18:59:27 -04:00
use crate ::lsp ::client ::Client ;
use crate ::lsp ::client ::TestingNotification ;
use crate ::lsp ::config ;
use crate ::lsp ::logging ::lsp_log ;
use crate ::ops ;
use crate ::proc_state ;
use crate ::tools ::test ;
2022-12-05 16:17:49 -05:00
use crate ::tools ::test ::FailFastTracker ;
2022-05-01 14:44:55 -04:00
use crate ::tools ::test ::TestEventSender ;
2022-11-28 17:28:54 -05:00
use crate ::util ::checksum ;
2022-11-21 08:36:26 -05:00
use crate ::worker ::create_main_worker_for_test_or_bench ;
2022-03-29 18:59:27 -04:00
use deno_core ::anyhow ::anyhow ;
use deno_core ::error ::AnyError ;
2022-05-09 05:44:50 -04:00
use deno_core ::error ::JsError ;
2022-03-29 18:59:27 -04:00
use deno_core ::futures ::future ;
use deno_core ::futures ::stream ;
use deno_core ::futures ::StreamExt ;
use deno_core ::parking_lot ::Mutex ;
2022-07-15 13:09:22 -04:00
use deno_core ::parking_lot ::RwLock ;
2022-03-29 18:59:27 -04:00
use deno_core ::ModuleSpecifier ;
2023-03-04 19:39:48 -05:00
use deno_runtime ::deno_io ::Stdio ;
use deno_runtime ::deno_io ::StdioPipe ;
2022-03-29 18:59:27 -04:00
use deno_runtime ::permissions ::Permissions ;
2023-01-07 11:25:34 -05:00
use deno_runtime ::permissions ::PermissionsContainer ;
2022-07-11 13:02:23 -04:00
use deno_runtime ::tokio_util ::run_local ;
2022-07-15 13:09:22 -04:00
use indexmap ::IndexMap ;
2022-03-29 18:59:27 -04:00
use std ::collections ::HashMap ;
use std ::collections ::HashSet ;
2023-01-07 15:22:09 -05:00
use std ::num ::NonZeroUsize ;
2022-03-29 18:59:27 -04:00
use std ::sync ::Arc ;
use std ::time ::Duration ;
use std ::time ::Instant ;
use tokio ::sync ::mpsc ;
use tokio_util ::sync ::CancellationToken ;
2022-04-03 00:17:30 -04:00
use tower_lsp ::lsp_types as lsp ;
2022-03-29 18:59:27 -04:00
/// Logic to convert a test request into a set of test modules to be tested and
/// any filters to be applied to those tests
fn as_queue_and_filters (
params : & lsp_custom ::TestRunRequestParams ,
tests : & HashMap < ModuleSpecifier , TestDefinitions > ,
) -> (
HashSet < ModuleSpecifier > ,
2022-07-15 13:09:22 -04:00
HashMap < ModuleSpecifier , LspTestFilter > ,
2022-03-29 18:59:27 -04:00
) {
let mut queue : HashSet < ModuleSpecifier > = HashSet ::new ( ) ;
2022-07-15 13:09:22 -04:00
let mut filters : HashMap < ModuleSpecifier , LspTestFilter > = HashMap ::new ( ) ;
2022-03-29 18:59:27 -04:00
if let Some ( include ) = & params . include {
for item in include {
if let Some ( test_definitions ) = tests . get ( & item . text_document . uri ) {
queue . insert ( item . text_document . uri . clone ( ) ) ;
if let Some ( id ) = & item . id {
if let Some ( test ) = test_definitions . get_by_id ( id ) {
let filter =
filters . entry ( item . text_document . uri . clone ( ) ) . or_default ( ) ;
2022-07-15 13:09:22 -04:00
if let Some ( include ) = filter . include . as_mut ( ) {
2022-03-29 18:59:27 -04:00
include . insert ( test . id . clone ( ) , test . clone ( ) ) ;
} else {
let mut include = HashMap ::new ( ) ;
include . insert ( test . id . clone ( ) , test . clone ( ) ) ;
2022-07-15 13:09:22 -04:00
filter . include = Some ( include ) ;
2022-03-29 18:59:27 -04:00
}
}
}
}
}
}
// if we didn't have any specific include filters, we assume that all modules
// will be tested
if queue . is_empty ( ) {
queue . extend ( tests . keys ( ) . cloned ( ) ) ;
}
2022-07-15 13:09:22 -04:00
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 ( ) ) ;
2022-03-29 18:59:27 -04:00
}
}
2022-07-15 13:09:22 -04:00
} else {
// the entire test module is excluded
queue . remove ( & item . text_document . uri ) ;
2022-03-29 18:59:27 -04:00
}
}
}
( queue , filters )
}
fn as_test_messages < S : AsRef < str > > (
message : S ,
is_markdown : bool ,
) -> Vec < lsp_custom ::TestMessage > {
let message = lsp ::MarkupContent {
kind : if is_markdown {
lsp ::MarkupKind ::Markdown
} else {
lsp ::MarkupKind ::PlainText
} ,
value : message . as_ref ( ) . to_string ( ) ,
} ;
vec! [ lsp_custom ::TestMessage {
message ,
expected_output : None ,
actual_output : None ,
location : None ,
} ]
}
#[ derive(Debug, Clone, Default, PartialEq) ]
2022-07-15 13:09:22 -04:00
struct LspTestFilter {
include : Option < HashMap < String , TestDefinition > > ,
exclude : HashMap < String , TestDefinition > ,
2022-03-29 18:59:27 -04:00
}
2022-07-15 13:09:22 -04:00
impl LspTestFilter {
2022-03-29 18:59:27 -04:00
fn as_ids ( & self , test_definitions : & TestDefinitions ) -> Vec < String > {
2022-07-15 13:09:22 -04:00
let ids : Vec < String > = if let Some ( include ) = & self . include {
2022-03-29 18:59:27 -04:00
include . keys ( ) . cloned ( ) . collect ( )
} else {
test_definitions
. discovered
. iter ( )
. map ( | td | td . id . clone ( ) )
. collect ( )
} ;
2022-07-15 13:09:22 -04:00
ids
. into_iter ( )
. filter ( | id | ! self . exclude . contains_key ( id ) )
. collect ( )
2022-03-29 18:59:27 -04:00
}
}
2022-12-05 16:17:49 -05:00
#[ allow(clippy::too_many_arguments) ]
2022-03-29 18:59:27 -04:00
async fn test_specifier (
ps : proc_state ::ProcState ,
permissions : Permissions ,
specifier : ModuleSpecifier ,
mode : test ::TestMode ,
2022-12-05 16:17:49 -05:00
sender : TestEventSender ,
fail_fast_tracker : FailFastTracker ,
2022-03-29 18:59:27 -04:00
token : CancellationToken ,
2022-07-15 13:09:22 -04:00
filter : test ::TestFilter ,
2022-03-29 18:59:27 -04:00
) -> Result < ( ) , AnyError > {
if ! token . is_cancelled ( ) {
2022-12-05 16:17:49 -05:00
let stdout = StdioPipe ::File ( sender . stdout ( ) ) ;
let stderr = StdioPipe ::File ( sender . stderr ( ) ) ;
2022-11-21 08:36:26 -05:00
let mut worker = create_main_worker_for_test_or_bench (
2022-03-29 18:59:27 -04:00
& ps ,
specifier . clone ( ) ,
2023-01-07 11:25:34 -05:00
PermissionsContainer ::new ( permissions ) ,
2022-12-05 16:17:49 -05:00
vec! [ ops ::testing ::init ( sender , fail_fast_tracker , filter ) ] ,
2022-04-26 19:00:04 -04:00
Stdio {
stdin : StdioPipe ::Inherit ,
2022-12-05 16:17:49 -05:00
stdout ,
stderr ,
2022-04-26 19:00:04 -04:00
} ,
2022-08-23 10:39:19 -04:00
)
. await ? ;
2022-08-11 16:59:12 -04:00
worker . run_lsp_test_specifier ( mode ) . await ? ;
2022-03-29 18:59:27 -04:00
}
Ok ( ( ) )
}
#[ derive(Debug, Clone) ]
pub struct TestRun {
id : u32 ,
kind : lsp_custom ::TestRunKind ,
2022-07-15 13:09:22 -04:00
filters : HashMap < ModuleSpecifier , LspTestFilter > ,
2022-03-29 18:59:27 -04:00
queue : HashSet < ModuleSpecifier > ,
tests : Arc < Mutex < HashMap < ModuleSpecifier , TestDefinitions > > > ,
token : CancellationToken ,
workspace_settings : config ::WorkspaceSettings ,
}
impl TestRun {
pub fn new (
params : & lsp_custom ::TestRunRequestParams ,
tests : Arc < Mutex < HashMap < ModuleSpecifier , TestDefinitions > > > ,
workspace_settings : config ::WorkspaceSettings ,
) -> Self {
let ( queue , filters ) = {
let tests = tests . lock ( ) ;
as_queue_and_filters ( params , & tests )
} ;
Self {
id : params . id ,
kind : params . kind . clone ( ) ,
filters ,
queue ,
tests ,
token : CancellationToken ::new ( ) ,
workspace_settings ,
}
}
/// Provide the tests of a test run as an enqueued module which can be sent
/// to the client to indicate tests are enqueued for testing.
pub fn as_enqueued ( & self ) -> Vec < lsp_custom ::EnqueuedTestModule > {
let tests = self . tests . lock ( ) ;
self
. queue
. iter ( )
. map ( | s | {
let ids = if let Some ( test_definitions ) = tests . get ( s ) {
if let Some ( filter ) = self . filters . get ( s ) {
filter . as_ids ( test_definitions )
} else {
test_definitions
. discovered
. iter ( )
. map ( | test | test . id . clone ( ) )
. collect ( )
}
} else {
Vec ::new ( )
} ;
lsp_custom ::EnqueuedTestModule {
text_document : lsp ::TextDocumentIdentifier { uri : s . clone ( ) } ,
ids ,
}
} )
. collect ( )
}
/// If being executed, cancel the test.
pub fn cancel ( & self ) {
self . token . cancel ( ) ;
}
/// Execute the tests, dispatching progress notifications to the client.
pub async fn exec (
& self ,
client : & Client ,
maybe_root_uri : Option < & ModuleSpecifier > ,
) -> Result < ( ) , AnyError > {
let args = self . get_args ( ) ;
lsp_log! ( " Executing test run with arguments: {} " , args . join ( " " ) ) ;
2022-06-27 16:54:09 -04:00
let flags = flags_from_vec ( args . into_iter ( ) . map ( String ::from ) . collect ( ) ) ? ;
2022-06-28 16:45:55 -04:00
let ps = proc_state ::ProcState ::build ( flags ) . await ? ;
2023-01-07 11:25:34 -05:00
// Various test files should not share the same permissions in terms of
// `PermissionsContainer` - otherwise granting/revoking permissions in one
// file would have impact on other files, which is undesirable.
2022-03-29 18:59:27 -04:00
let permissions =
2022-08-10 15:13:53 -04:00
Permissions ::from_options ( & ps . options . permissions_options ( ) ) ? ;
2022-03-29 18:59:27 -04:00
test ::check_specifiers (
& ps ,
permissions . clone ( ) ,
self
. queue
. iter ( )
. map ( | s | ( s . clone ( ) , test ::TestMode ::Executable ) )
. collect ( ) ,
)
. await ? ;
let ( concurrent_jobs , fail_fast ) =
2022-06-29 11:51:11 -04:00
if let DenoSubcommand ::Test ( test_flags ) = ps . options . sub_command ( ) {
2023-01-07 15:22:09 -05:00
(
test_flags
. concurrent_jobs
. unwrap_or_else ( | | NonZeroUsize ::new ( 1 ) . unwrap ( ) )
. into ( ) ,
test_flags . fail_fast ,
)
2022-03-29 18:59:27 -04:00
} else {
unreachable! ( " Should always be Test subcommand. " ) ;
} ;
2022-12-05 16:17:49 -05:00
let ( sender , mut receiver ) = mpsc ::unbounded_channel ::< test ::TestEvent > ( ) ;
let sender = TestEventSender ::new ( sender ) ;
let fail_fast_tracker = FailFastTracker ::new ( fail_fast ) ;
2022-03-29 18:59:27 -04:00
let mut queue = self . queue . iter ( ) . collect ::< Vec < & ModuleSpecifier > > ( ) ;
queue . sort ( ) ;
2022-07-15 13:09:22 -04:00
let tests : Arc < RwLock < IndexMap < usize , test ::TestDescription > > > =
Arc ::new ( RwLock ::new ( IndexMap ::new ( ) ) ) ;
let mut test_steps = IndexMap ::new ( ) ;
let tests_ = tests . clone ( ) ;
2022-03-29 18:59:27 -04:00
let join_handles = queue . into_iter ( ) . map ( move | specifier | {
let specifier = specifier . clone ( ) ;
let ps = ps . clone ( ) ;
let permissions = permissions . clone ( ) ;
2022-05-09 05:44:50 -04:00
let mut sender = sender . clone ( ) ;
2022-12-05 16:17:49 -05:00
let fail_fast_tracker = fail_fast_tracker . clone ( ) ;
2022-07-15 13:09:22 -04:00
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 ( ) ,
} ;
2022-03-29 18:59:27 -04:00
let token = self . token . clone ( ) ;
2022-07-15 13:09:22 -04:00
let tests = tests_ . clone ( ) ;
2022-03-29 18:59:27 -04:00
tokio ::task ::spawn_blocking ( move | | {
2022-12-05 16:17:49 -05:00
if fail_fast_tracker . should_stop ( ) {
return Ok ( ( ) ) ;
}
2022-05-09 05:44:50 -04:00
let origin = specifier . to_string ( ) ;
2022-07-11 13:02:23 -04:00
let file_result = run_local ( test_specifier (
2022-03-29 18:59:27 -04:00
ps ,
permissions ,
specifier ,
test ::TestMode ::Executable ,
2022-12-05 16:17:49 -05:00
sender . clone ( ) ,
fail_fast_tracker ,
2022-03-29 18:59:27 -04:00
token ,
2022-07-15 13:09:22 -04:00
filter ,
2022-05-09 05:44:50 -04:00
) ) ;
if let Err ( error ) = file_result {
if error . is ::< JsError > ( ) {
sender . send ( test ::TestEvent ::UncaughtError (
2022-07-15 13:09:22 -04:00
origin . clone ( ) ,
2022-05-09 05:44:50 -04:00
Box ::new ( error . downcast ::< JsError > ( ) . unwrap ( ) ) ,
) ) ? ;
2022-07-15 13:09:22 -04:00
for desc in tests . read ( ) . values ( ) {
if desc . origin = = origin {
sender . send ( test ::TestEvent ::Result (
desc . id ,
test ::TestResult ::Cancelled ,
0 ,
) ) ?
}
}
2022-05-09 05:44:50 -04:00
} else {
return Err ( error ) ;
}
}
Ok ( ( ) )
2022-03-29 18:59:27 -04:00
} )
} ) ;
let join_stream = stream ::iter ( join_handles )
. buffer_unordered ( concurrent_jobs )
. collect ::< Vec < Result < Result < ( ) , AnyError > , tokio ::task ::JoinError > > > ( ) ;
2022-08-04 12:38:40 -04:00
let mut reporter = Box ::new ( LspTestReporter ::new (
self ,
client . clone ( ) ,
maybe_root_uri ,
self . tests . clone ( ) ,
) ) ;
2022-03-29 18:59:27 -04:00
let handler = {
tokio ::task ::spawn ( async move {
let earlier = Instant ::now ( ) ;
let mut summary = test ::TestSummary ::new ( ) ;
let mut used_only = false ;
while let Some ( event ) = receiver . recv ( ) . await {
match event {
2022-07-15 13:09:22 -04:00
test ::TestEvent ::Register ( description ) = > {
reporter . report_register ( & description ) ;
tests . write ( ) . insert ( description . id , description ) ;
}
2022-03-29 18:59:27 -04:00
test ::TestEvent ::Plan ( plan ) = > {
summary . total + = plan . total ;
summary . filtered_out + = plan . filtered_out ;
if plan . used_only {
used_only = true ;
}
reporter . report_plan ( & plan ) ;
}
2022-07-15 13:09:22 -04:00
test ::TestEvent ::Wait ( id ) = > {
reporter . report_wait ( tests . read ( ) . get ( & id ) . unwrap ( ) ) ;
2022-03-29 18:59:27 -04:00
}
test ::TestEvent ::Output ( output ) = > {
reporter . report_output ( & output ) ;
}
2022-07-15 13:09:22 -04:00
test ::TestEvent ::Result ( id , result , elapsed ) = > {
let description = tests . read ( ) . get ( & id ) . unwrap ( ) . clone ( ) ;
2022-03-29 18:59:27 -04:00
match & result {
test ::TestResult ::Ok = > summary . passed + = 1 ,
test ::TestResult ::Ignored = > summary . ignored + = 1 ,
test ::TestResult ::Failed ( error ) = > {
summary . failed + = 1 ;
summary . failures . push ( ( description . clone ( ) , error . clone ( ) ) ) ;
}
2022-07-15 13:09:22 -04:00
test ::TestResult ::Cancelled = > {
summary . failed + = 1 ;
}
2022-03-29 18:59:27 -04:00
}
reporter . report_result ( & description , & result , elapsed ) ;
}
2022-05-09 05:44:50 -04:00
test ::TestEvent ::UncaughtError ( origin , error ) = > {
reporter . report_uncaught_error ( & origin , & error ) ;
summary . failed + = 1 ;
summary . uncaught_errors . push ( ( origin , error ) ) ;
}
2022-07-15 13:09:22 -04:00
test ::TestEvent ::StepRegister ( description ) = > {
reporter . report_step_register ( & description ) ;
test_steps . insert ( description . id , description ) ;
2022-03-29 18:59:27 -04:00
}
2022-07-15 13:09:22 -04:00
test ::TestEvent ::StepWait ( id ) = > {
reporter . report_step_wait ( test_steps . get ( & id ) . unwrap ( ) ) ;
}
test ::TestEvent ::StepResult ( id , result , duration ) = > {
2022-03-29 18:59:27 -04:00
match & result {
test ::TestStepResult ::Ok = > {
summary . passed_steps + = 1 ;
}
test ::TestStepResult ::Ignored = > {
summary . ignored_steps + = 1 ;
}
test ::TestStepResult ::Failed ( _ ) = > {
summary . failed_steps + = 1 ;
}
test ::TestStepResult ::Pending ( _ ) = > {
summary . pending_steps + = 1 ;
}
}
2022-07-15 13:09:22 -04:00
reporter . report_step_result (
test_steps . get ( & id ) . unwrap ( ) ,
& result ,
duration ,
) ;
2022-03-29 18:59:27 -04:00
}
}
}
let elapsed = Instant ::now ( ) . duration_since ( earlier ) ;
reporter . report_summary ( & summary , & elapsed ) ;
if used_only {
return Err ( anyhow! (
" Test failed because the \" only \" option was used "
) ) ;
}
if summary . failed > 0 {
return Err ( anyhow! ( " Test failed " ) ) ;
}
Ok ( ( ) )
} )
} ;
let ( join_results , result ) = future ::join ( join_stream , handler ) . await ;
// propagate any errors
for join_result in join_results {
join_result ? ? ;
}
result ? ? ;
Ok ( ( ) )
}
fn get_args ( & self ) -> Vec < & str > {
let mut args = vec! [ " deno " , " test " ] ;
args . extend (
self
. workspace_settings
. testing
. args
. iter ( )
. map ( | s | s . as_str ( ) ) ,
) ;
if self . workspace_settings . unstable & & ! args . contains ( & " --unstable " ) {
args . push ( " --unstable " ) ;
}
if let Some ( config ) = & self . workspace_settings . config {
if ! args . contains ( & " --config " ) & & ! args . contains ( & " -c " ) {
args . push ( " --config " ) ;
args . push ( config . as_str ( ) ) ;
}
}
if let Some ( import_map ) = & self . workspace_settings . import_map {
if ! args . contains ( & " --import-map " ) {
args . push ( " --import-map " ) ;
args . push ( import_map . as_str ( ) ) ;
}
}
if self . kind = = lsp_custom ::TestRunKind ::Debug
& & ! args . contains ( & " --inspect " )
& & ! args . contains ( & " --inspect-brk " )
{
args . push ( " --inspect " ) ;
}
args
}
}
#[ derive(Debug, PartialEq) ]
enum TestOrTestStepDescription {
TestDescription ( test ::TestDescription ) ,
TestStepDescription ( test ::TestStepDescription ) ,
}
impl From < & test ::TestDescription > for TestOrTestStepDescription {
fn from ( desc : & test ::TestDescription ) -> Self {
Self ::TestDescription ( desc . clone ( ) )
}
}
impl From < & test ::TestStepDescription > for TestOrTestStepDescription {
fn from ( desc : & test ::TestStepDescription ) -> Self {
Self ::TestStepDescription ( desc . clone ( ) )
}
}
impl From < & TestOrTestStepDescription > for lsp_custom ::TestIdentifier {
fn from ( desc : & TestOrTestStepDescription ) -> lsp_custom ::TestIdentifier {
match desc {
TestOrTestStepDescription ::TestDescription ( test_desc ) = > test_desc . into ( ) ,
TestOrTestStepDescription ::TestStepDescription ( test_step_desc ) = > {
test_step_desc . into ( )
}
}
}
}
impl From < & TestOrTestStepDescription > for lsp_custom ::TestData {
fn from ( desc : & TestOrTestStepDescription ) -> Self {
match desc {
TestOrTestStepDescription ::TestDescription ( desc ) = > desc . into ( ) ,
TestOrTestStepDescription ::TestStepDescription ( desc ) = > desc . into ( ) ,
}
}
}
impl From < & test ::TestDescription > for lsp_custom ::TestData {
fn from ( desc : & test ::TestDescription ) -> Self {
Self {
2022-07-15 13:09:22 -04:00
id : desc . static_id ( ) ,
2022-03-29 18:59:27 -04:00
label : desc . name . clone ( ) ,
steps : Default ::default ( ) ,
range : None ,
}
}
}
impl From < & test ::TestDescription > for lsp_custom ::TestIdentifier {
fn from ( desc : & test ::TestDescription ) -> Self {
let uri = ModuleSpecifier ::parse ( & desc . origin ) . unwrap ( ) ;
Self {
text_document : lsp ::TextDocumentIdentifier { uri } ,
2022-07-15 13:09:22 -04:00
id : Some ( desc . static_id ( ) ) ,
2022-03-29 18:59:27 -04:00
step_id : None ,
}
}
}
impl From < & test ::TestStepDescription > for lsp_custom ::TestData {
fn from ( desc : & test ::TestStepDescription ) -> Self {
Self {
2022-07-15 13:09:22 -04:00
id : desc . static_id ( ) ,
2022-03-29 18:59:27 -04:00
label : desc . name . clone ( ) ,
steps : Default ::default ( ) ,
range : None ,
}
}
}
impl From < & test ::TestStepDescription > for lsp_custom ::TestIdentifier {
fn from ( desc : & test ::TestStepDescription ) -> Self {
2022-07-15 13:09:22 -04:00
let uri = ModuleSpecifier ::parse ( & desc . origin ) . unwrap ( ) ;
2022-03-29 18:59:27 -04:00
Self {
text_document : lsp ::TextDocumentIdentifier { uri } ,
2022-07-15 13:09:22 -04:00
id : Some ( checksum ::gen ( & [
desc . origin . as_bytes ( ) ,
desc . root_name . as_bytes ( ) ,
] ) ) ,
step_id : Some ( desc . static_id ( ) ) ,
2022-03-29 18:59:27 -04:00
}
}
}
struct LspTestReporter {
client : Client ,
current_origin : Option < String > ,
maybe_root_uri : Option < ModuleSpecifier > ,
id : u32 ,
stack : HashMap < String , Vec < TestOrTestStepDescription > > ,
tests : Arc < Mutex < HashMap < ModuleSpecifier , TestDefinitions > > > ,
}
impl LspTestReporter {
fn new (
run : & TestRun ,
client : Client ,
maybe_root_uri : Option < & ModuleSpecifier > ,
tests : Arc < Mutex < HashMap < ModuleSpecifier , TestDefinitions > > > ,
) -> Self {
Self {
client ,
current_origin : None ,
maybe_root_uri : maybe_root_uri . cloned ( ) ,
id : run . id ,
stack : HashMap ::new ( ) ,
tests ,
}
}
2022-07-15 13:09:22 -04:00
fn progress ( & self , message : lsp_custom ::TestRunProgressMessage ) {
self
. client
. send_test_notification ( TestingNotification ::Progress (
lsp_custom ::TestRunProgressParams {
id : self . id ,
message ,
} ,
) ) ;
2022-03-29 18:59:27 -04:00
}
2022-07-15 13:09:22 -04:00
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 ( ) ;
2022-03-29 18:59:27 -04:00
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 ! [ desc . into ( ) ] ,
} ,
) ) ;
}
}
fn report_wait ( & mut self , desc : & test ::TestDescription ) {
self . current_origin = Some ( desc . origin . clone ( ) ) ;
let test : lsp_custom ::TestIdentifier = desc . into ( ) ;
let stack = self . stack . entry ( desc . origin . clone ( ) ) . or_default ( ) ;
assert! ( stack . is_empty ( ) ) ;
stack . push ( desc . into ( ) ) ;
self . progress ( lsp_custom ::TestRunProgressMessage ::Started { test } ) ;
}
2022-05-01 14:44:55 -04:00
fn report_output ( & mut self , output : & [ u8 ] ) {
2022-03-29 18:59:27 -04:00
let test = self . current_origin . as_ref ( ) . and_then ( | origin | {
self
. stack
. get ( origin )
. and_then ( | v | v . last ( ) . map ( | td | td . into ( ) ) )
} ) ;
2022-05-01 14:44:55 -04:00
let value = String ::from_utf8_lossy ( output ) . replace ( '\n' , " \r \n " ) ;
2022-04-15 08:24:41 -04:00
self . progress ( lsp_custom ::TestRunProgressMessage ::Output {
value ,
test ,
// TODO(@kitsonk) test output should include a location
location : None ,
} )
2022-03-29 18:59:27 -04:00
}
fn report_result (
& mut self ,
desc : & test ::TestDescription ,
result : & test ::TestResult ,
elapsed : u64 ,
) {
let stack = self . stack . entry ( desc . origin . clone ( ) ) . or_default ( ) ;
assert_eq! ( stack . len ( ) , 1 ) ;
assert_eq! ( stack . pop ( ) , Some ( desc . into ( ) ) ) ;
self . current_origin = None ;
match result {
test ::TestResult ::Ok = > {
self . progress ( lsp_custom ::TestRunProgressMessage ::Passed {
test : desc . into ( ) ,
duration : Some ( elapsed as u32 ) ,
} )
}
test ::TestResult ::Ignored = > {
self . progress ( lsp_custom ::TestRunProgressMessage ::Skipped {
test : desc . into ( ) ,
} )
}
2022-04-16 13:51:12 -04:00
test ::TestResult ::Failed ( js_error ) = > {
2022-04-18 09:22:23 -04:00
let err_string = test ::format_test_error ( js_error ) ;
2022-03-29 18:59:27 -04:00
self . progress ( lsp_custom ::TestRunProgressMessage ::Failed {
test : desc . into ( ) ,
2022-04-16 13:51:12 -04:00
messages : as_test_messages ( err_string , false ) ,
2022-03-29 18:59:27 -04:00
duration : Some ( elapsed as u32 ) ,
} )
}
2022-07-15 13:09:22 -04:00
test ::TestResult ::Cancelled = > {
self . progress ( lsp_custom ::TestRunProgressMessage ::Failed {
test : desc . into ( ) ,
messages : vec ! [ ] ,
duration : Some ( elapsed as u32 ) ,
} )
}
2022-03-29 18:59:27 -04:00
}
}
2022-05-09 05:44:50 -04:00
fn report_uncaught_error ( & mut self , origin : & str , js_error : & JsError ) {
if self . current_origin = = Some ( origin . to_string ( ) ) {
self . current_origin = None ;
}
let stack = self . stack . remove ( origin ) . unwrap_or_default ( ) ;
let err_string = format! (
" Uncaught error from {}: {} \n This error was not caught from a test and caused the test runner to fail on the referenced module. \n It most likely originated from a dangling promise, event/timeout handler or top-level code. " ,
origin ,
test ::format_test_error ( js_error )
) ;
let messages = as_test_messages ( err_string , false ) ;
for t in stack . iter ( ) . rev ( ) {
match t {
TestOrTestStepDescription ::TestDescription ( desc ) = > {
self . progress ( lsp_custom ::TestRunProgressMessage ::Failed {
test : desc . into ( ) ,
messages : messages . clone ( ) ,
duration : None ,
} ) ;
}
TestOrTestStepDescription ::TestStepDescription ( desc ) = > {
self . progress ( lsp_custom ::TestRunProgressMessage ::Failed {
test : desc . into ( ) ,
messages : messages . clone ( ) ,
duration : None ,
} ) ;
}
}
}
}
2022-07-15 13:09:22 -04:00
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 ] ,
} ,
) ) ;
}
2022-03-29 18:59:27 -04:00
}
2022-07-15 13:09:22 -04:00
}
fn report_step_wait ( & mut self , desc : & test ::TestStepDescription ) {
2022-03-29 18:59:27 -04:00
let test : lsp_custom ::TestIdentifier = desc . into ( ) ;
2022-07-15 13:09:22 -04:00
let stack = self . stack . entry ( desc . origin . clone ( ) ) . or_default ( ) ;
self . current_origin = Some ( desc . origin . clone ( ) ) ;
2022-03-29 18:59:27 -04:00
assert! ( ! stack . is_empty ( ) ) ;
stack . push ( desc . into ( ) ) ;
self . progress ( lsp_custom ::TestRunProgressMessage ::Started { test } ) ;
}
fn report_step_result (
& mut self ,
desc : & test ::TestStepDescription ,
result : & test ::TestStepResult ,
elapsed : u64 ,
) {
2022-07-15 13:09:22 -04:00
let stack = self . stack . entry ( desc . origin . clone ( ) ) . or_default ( ) ;
2022-03-29 18:59:27 -04:00
assert_eq! ( stack . pop ( ) , Some ( desc . into ( ) ) ) ;
match result {
test ::TestStepResult ::Ok = > {
self . progress ( lsp_custom ::TestRunProgressMessage ::Passed {
test : desc . into ( ) ,
duration : Some ( elapsed as u32 ) ,
} )
}
test ::TestStepResult ::Ignored = > {
self . progress ( lsp_custom ::TestRunProgressMessage ::Skipped {
test : desc . into ( ) ,
} )
}
2022-04-16 13:51:12 -04:00
test ::TestStepResult ::Failed ( js_error ) = > {
let messages = if let Some ( js_error ) = js_error {
2022-04-18 09:22:23 -04:00
let err_string = test ::format_test_error ( js_error ) ;
2022-04-16 13:51:12 -04:00
as_test_messages ( err_string , false )
2022-03-29 18:59:27 -04:00
} else {
vec! [ ]
} ;
self . progress ( lsp_custom ::TestRunProgressMessage ::Failed {
test : desc . into ( ) ,
messages ,
duration : Some ( elapsed as u32 ) ,
} )
}
test ::TestStepResult ::Pending ( _ ) = > {
self . progress ( lsp_custom ::TestRunProgressMessage ::Enqueued {
test : desc . into ( ) ,
} )
}
}
}
fn report_summary (
& mut self ,
_summary : & test ::TestSummary ,
_elapsed : & Duration ,
) {
// there is nothing to do on report_summary
}
}
#[ cfg(test) ]
mod tests {
use super ::* ;
2022-05-20 16:40:55 -04:00
use crate ::lsp ::testing ::collectors ::tests ::new_range ;
2022-07-15 13:09:22 -04:00
use deno_core ::serde_json ::json ;
2022-03-29 18:59:27 -04:00
#[ test ]
fn test_as_queue_and_filters ( ) {
let specifier = ModuleSpecifier ::parse ( " file:///a/file.ts " ) . unwrap ( ) ;
let params = lsp_custom ::TestRunRequestParams {
id : 1 ,
kind : lsp_custom ::TestRunKind ::Run ,
include : Some ( vec! [ lsp_custom ::TestIdentifier {
text_document : lsp ::TextDocumentIdentifier {
uri : specifier . clone ( ) ,
} ,
id : None ,
step_id : None ,
} ] ) ,
2022-07-15 13:09:22 -04:00
exclude : vec ! [ lsp_custom ::TestIdentifier {
2022-03-29 18:59:27 -04:00
text_document : lsp ::TextDocumentIdentifier {
uri : specifier . clone ( ) ,
} ,
id : Some (
" 69d9fe87f64f5b66cb8b631d4fd2064e8224b8715a049be54276c42189ff8f9f "
. to_string ( ) ,
) ,
step_id : None ,
2022-07-15 13:09:22 -04:00
} ] ,
2022-03-29 18:59:27 -04:00
} ;
let mut tests = HashMap ::new ( ) ;
let test_def_a = TestDefinition {
id : " 0b7c6bf3cd617018d33a1bf982a08fe088c5bb54fcd5eb9e802e7c137ec1af94 "
. to_string ( ) ,
level : 0 ,
name : " test a " . to_string ( ) ,
2022-05-20 16:40:55 -04:00
range : new_range ( 420 , 424 ) ,
2022-07-15 13:09:22 -04:00
steps : vec ! [ ] ,
2022-03-29 18:59:27 -04:00
} ;
let test_def_b = TestDefinition {
id : " 69d9fe87f64f5b66cb8b631d4fd2064e8224b8715a049be54276c42189ff8f9f "
. to_string ( ) ,
level : 0 ,
name : " test b " . to_string ( ) ,
2022-05-20 16:40:55 -04:00
range : new_range ( 480 , 481 ) ,
2022-07-15 13:09:22 -04:00
steps : vec ! [ ] ,
2022-03-29 18:59:27 -04:00
} ;
let test_definitions = TestDefinitions {
discovered : vec ! [ test_def_a , test_def_b . clone ( ) ] ,
injected : vec ! [ ] ,
script_version : " 1 " . to_string ( ) ,
} ;
tests . insert ( specifier . clone ( ) , test_definitions . clone ( ) ) ;
let ( queue , filters ) = as_queue_and_filters ( & params , & tests ) ;
assert_eq! ( json! ( queue ) , json! ( [ specifier ] ) ) ;
let mut exclude = HashMap ::new ( ) ;
exclude . insert (
" 69d9fe87f64f5b66cb8b631d4fd2064e8224b8715a049be54276c42189ff8f9f "
. to_string ( ) ,
test_def_b ,
) ;
let maybe_filter = filters . get ( & specifier ) ;
assert! ( maybe_filter . is_some ( ) ) ;
let filter = maybe_filter . unwrap ( ) ;
assert_eq! (
filter ,
2022-07-15 13:09:22 -04:00
& LspTestFilter {
include : None ,
exclude ,
2022-03-29 18:59:27 -04:00
}
) ;
assert_eq! (
filter . as_ids ( & test_definitions ) ,
vec! [
" 0b7c6bf3cd617018d33a1bf982a08fe088c5bb54fcd5eb9e802e7c137ec1af94 "
. to_string ( )
]
) ;
}
}