diff options
-rw-r--r-- | cli/main.rs | 262 | ||||
-rw-r--r-- | cli/ops/testing.rs | 2 | ||||
-rw-r--r-- | cli/tests/testdata/test/shuffle.out | 4 | ||||
-rw-r--r-- | cli/tools/mod.rs | 2 | ||||
-rw-r--r-- | cli/tools/test.rs (renamed from cli/tools/test_runner.rs) | 558 |
5 files changed, 487 insertions, 341 deletions
diff --git a/cli/main.rs b/cli/main.rs index 650ab62a2..8de1f1fc5 100644 --- a/cli/main.rs +++ b/cli/main.rs @@ -44,8 +44,6 @@ use crate::flags::DenoSubcommand; use crate::flags::Flags; use crate::fmt_errors::PrettyJsError; use crate::media_type::MediaType; -use crate::module_graph::GraphBuilder; -use crate::module_graph::Module; use crate::module_loader::CliModuleLoader; use crate::program_state::ProgramState; use crate::source_maps::apply_source_map; @@ -71,7 +69,6 @@ use deno_runtime::worker::MainWorker; use deno_runtime::worker::WorkerOptions; use log::debug; use log::info; -use std::collections::HashSet; use std::env; use std::io::Read; use std::io::Write; @@ -81,7 +78,6 @@ use std::path::PathBuf; use std::pin::Pin; use std::rc::Rc; use std::sync::Arc; -use tools::test_runner; fn create_web_worker_callback( program_state: Arc<ProgramState>, @@ -1071,253 +1067,37 @@ async fn test_command( ); } - // TODO(caspervonb) move this chunk into tools::test_runner. - - let program_state = ProgramState::build(flags.clone()).await?; - - let include = include.unwrap_or_else(|| vec![".".to_string()]); - - let permissions = Permissions::from_options(&flags.clone().into()); - let lib = if flags.unstable { - module_graph::TypeLib::UnstableDenoWindow - } else { - module_graph::TypeLib::DenoWindow - }; - if flags.watch { - let handler = Arc::new(Mutex::new(FetchHandler::new( - &program_state, - Permissions::allow_all(), - Permissions::allow_all(), - )?)); - - let paths_to_watch: Vec<_> = include.iter().map(PathBuf::from).collect(); - - // TODO(caspervonb) clean this up. - let resolver = |changed: Option<Vec<PathBuf>>| { - let test_modules_result = if doc { - fs_util::collect_specifiers( - include.clone(), - &ignore, - fs_util::is_supported_test_ext, - ) - } else { - fs_util::collect_specifiers( - include.clone(), - &ignore, - fs_util::is_supported_test_path, - ) - }; - - let paths_to_watch = paths_to_watch.clone(); - let paths_to_watch_clone = paths_to_watch.clone(); - - let handler = handler.clone(); - let program_state = program_state.clone(); - let files_changed = changed.is_some(); - async move { - let test_modules = test_modules_result?; - - let mut paths_to_watch = paths_to_watch_clone; - let mut modules_to_reload = if files_changed { - Vec::new() - } else { - test_modules - .iter() - .filter_map(|url| deno_core::resolve_url(url.as_str()).ok()) - .collect() - }; - - let mut builder = GraphBuilder::new( - handler, - program_state.maybe_import_map.clone(), - program_state.lockfile.clone(), - ); - for specifier in test_modules.iter() { - builder.add(specifier, false).await?; - } - builder - .analyze_config_file(&program_state.maybe_config_file) - .await?; - let graph = builder.get_graph(); - - for specifier in test_modules { - fn get_dependencies<'a>( - graph: &'a module_graph::Graph, - module: &'a Module, - // This needs to be accessible to skip getting dependencies if they're already there, - // otherwise this will cause a stack overflow with circular dependencies - output: &mut HashSet<&'a ModuleSpecifier>, - ) -> Result<(), AnyError> { - for dep in module.dependencies.values() { - if let Some(specifier) = &dep.maybe_code { - if !output.contains(specifier) { - output.insert(specifier); - - get_dependencies( - graph, - graph.get_specifier(specifier)?, - output, - )?; - } - } - if let Some(specifier) = &dep.maybe_type { - if !output.contains(specifier) { - output.insert(specifier); - - get_dependencies( - graph, - graph.get_specifier(specifier)?, - output, - )?; - } - } - } - - Ok(()) - } - - // This test module and all it's dependencies - let mut modules = HashSet::new(); - modules.insert(&specifier); - get_dependencies( - &graph, - graph.get_specifier(&specifier)?, - &mut modules, - )?; - - paths_to_watch.extend( - modules - .iter() - .filter_map(|specifier| specifier.to_file_path().ok()), - ); - - if let Some(changed) = &changed { - for path in changed.iter().filter_map(|path| { - deno_core::resolve_url_or_path(&path.to_string_lossy()).ok() - }) { - if modules.contains(&&path) { - modules_to_reload.push(specifier); - break; - } - } - } - } - - Ok((paths_to_watch, modules_to_reload)) - } - .map(move |result| { - if files_changed - && matches!(result, Ok((_, ref modules)) if modules.is_empty()) - { - ResolutionResult::Ignore - } else { - match result { - Ok((paths_to_watch, modules_to_reload)) => { - ResolutionResult::Restart { - paths_to_watch, - result: Ok(modules_to_reload), - } - } - Err(e) => ResolutionResult::Restart { - paths_to_watch, - result: Err(e), - }, - } - } - }) - }; - - let operation = |modules_to_reload: Vec<ModuleSpecifier>| { - let filter = filter.clone(); - let include = include.clone(); - let ignore = ignore.clone(); - let lib = lib.clone(); - let permissions = permissions.clone(); - let program_state = program_state.clone(); - - async move { - let doc_modules = if doc { - fs_util::collect_specifiers( - include.clone(), - &ignore, - fs_util::is_supported_test_ext, - )? - } else { - Vec::new() - }; - - let doc_modules_to_reload = doc_modules - .iter() - .filter(|specifier| modules_to_reload.contains(specifier)) - .cloned() - .collect(); - - let test_modules = fs_util::collect_specifiers( - include.clone(), - &ignore, - fs_util::is_supported_test_path, - )?; - - let test_modules_to_reload = test_modules - .iter() - .filter(|specifier| modules_to_reload.contains(specifier)) - .cloned() - .collect(); - - test_runner::run_tests( - program_state.clone(), - permissions.clone(), - lib.clone(), - doc_modules_to_reload, - test_modules_to_reload, - no_run, - fail_fast, - true, - filter.clone(), - shuffle, - concurrent_jobs, - ) - .await?; - - Ok(()) - } - }; - - file_watcher::watch_func(resolver, operation, "Test").await?; - } else { - let doc_modules = if doc { - fs_util::collect_specifiers( - include.clone(), - &ignore, - fs_util::is_supported_test_ext, - )? - } else { - Vec::new() - }; - - let test_modules = fs_util::collect_specifiers( - include.clone(), - &ignore, - fs_util::is_supported_test_path, - )?; - - test_runner::run_tests( - program_state.clone(), - permissions, - lib, - doc_modules, - test_modules, + tools::test::run_tests_with_watch( + flags, + include, + ignore, + doc, no_run, fail_fast, - allow_none, filter, shuffle, concurrent_jobs, ) .await?; + + return Ok(()); } + tools::test::run_tests( + flags, + include, + ignore, + doc, + no_run, + fail_fast, + allow_none, + filter, + shuffle, + concurrent_jobs, + ) + .await?; + Ok(()) } diff --git a/cli/ops/testing.rs b/cli/ops/testing.rs index cab498ab1..99cfc670e 100644 --- a/cli/ops/testing.rs +++ b/cli/ops/testing.rs @@ -1,4 +1,4 @@ -use crate::tools::test_runner::TestEvent; +use crate::tools::test::TestEvent; use deno_core::error::generic_error; use deno_core::error::AnyError; use deno_core::JsRuntime; diff --git a/cli/tests/testdata/test/shuffle.out b/cli/tests/testdata/test/shuffle.out index 04dd08ee2..ec2f9692d 100644 --- a/cli/tests/testdata/test/shuffle.out +++ b/cli/tests/testdata/test/shuffle.out @@ -1,6 +1,6 @@ -Check [WILDCARD]/test/shuffle/foo_test.ts -Check [WILDCARD]/test/shuffle/baz_test.ts Check [WILDCARD]/test/shuffle/bar_test.ts +Check [WILDCARD]/test/shuffle/baz_test.ts +Check [WILDCARD]/test/shuffle/foo_test.ts running 10 tests from [WILDCARD]/test/shuffle/foo_test.ts test test 2 ... ok ([WILDCARD]) test test 3 ... ok ([WILDCARD]) diff --git a/cli/tools/mod.rs b/cli/tools/mod.rs index cd00f6a86..74d6431ed 100644 --- a/cli/tools/mod.rs +++ b/cli/tools/mod.rs @@ -7,5 +7,5 @@ pub mod installer; pub mod lint; pub mod repl; pub mod standalone; -pub mod test_runner; +pub mod test; pub mod upgrade; diff --git a/cli/tools/test_runner.rs b/cli/tools/test.rs index 4f287a4e6..62621a232 100644 --- a/cli/tools/test_runner.rs +++ b/cli/tools/test.rs @@ -5,18 +5,29 @@ use crate::ast::Location; use crate::colors; use crate::create_main_worker; use crate::file_fetcher::File; +use crate::file_watcher; +use crate::file_watcher::ResolutionResult; +use crate::flags::Flags; +use crate::fs_util::collect_specifiers; +use crate::fs_util::is_supported_test_ext; +use crate::fs_util::is_supported_test_path; use crate::media_type::MediaType; use crate::module_graph; +use crate::module_graph::GraphBuilder; +use crate::module_graph::Module; +use crate::module_graph::TypeLib; use crate::ops; use crate::program_state::ProgramState; use crate::tokio_util; use crate::tools::coverage::CoverageCollector; +use crate::FetchHandler; use deno_core::error::generic_error; use deno_core::error::AnyError; use deno_core::futures::future; use deno_core::futures::stream; use deno_core::futures::FutureExt; use deno_core::futures::StreamExt; +use deno_core::parking_lot::Mutex; use deno_core::serde_json::json; use deno_core::JsRuntime; use deno_core::ModuleSpecifier; @@ -27,6 +38,7 @@ use rand::seq::SliceRandom; use rand::SeedableRng; use regex::Regex; use serde::Deserialize; +use std::collections::HashSet; use std::num::NonZeroUsize; use std::path::PathBuf; use std::sync::mpsc::channel; @@ -37,6 +49,18 @@ use std::time::Instant; use swc_common::comments::CommentKind; use uuid::Uuid; +/// The test mode is used to determine how a specifier is to be tested. +#[derive(Debug, Clone, PartialEq)] +enum TestMode { + /// Test as documentation, type-checking fenced code blocks. + Documentation, + /// Test as an executable module, loading the module into the isolate and running each test it + /// defines. + Executable, + /// Test as both documentation and an executable module. + Both, +} + #[derive(Debug, Clone, PartialEq, Deserialize)] #[serde(rename_all = "camelCase")] pub struct TestDescription { @@ -197,27 +221,23 @@ fn create_reporter(concurrent: bool) -> Box<dyn TestReporter + Send> { Box::new(PrettyTestReporter::new(concurrent)) } -pub async fn test_specifier( +/// Test a single specifier as documentation containing test programs, an executable test module or +/// both. +async fn test_specifier( program_state: Arc<ProgramState>, - main_module: ModuleSpecifier, permissions: Permissions, + specifier: ModuleSpecifier, + mode: TestMode, filter: Option<String>, shuffle: Option<u64>, channel: Sender<TestEvent>, ) -> Result<(), AnyError> { - let mut fetch_permissions = Permissions::allow_all(); - - let main_file = program_state - .file_fetcher - .fetch(&main_module, &mut fetch_permissions) - .await?; - - let test_module = + let test_specifier = deno_core::resolve_path(&format!("{}$deno$test.js", Uuid::new_v4()))?; let mut test_source = String::new(); - if main_file.media_type != MediaType::Unknown { - test_source.push_str(&format!("import \"{}\";\n", main_module)); + if mode != TestMode::Documentation { + test_source.push_str(&format!("import \"{}\";\n", specifier)); } test_source @@ -237,11 +257,11 @@ pub async fn test_specifier( test_source.push_str("window.dispatchEvent(new Event('unload'));\n"); let test_file = File { - local: test_module.to_file_path().unwrap(), + local: test_specifier.to_file_path().unwrap(), maybe_types: None, media_type: MediaType::JavaScript, source: test_source.clone(), - specifier: test_module.clone(), + specifier: test_specifier.clone(), }; program_state.file_fetcher.insert_cached(test_file); @@ -257,7 +277,7 @@ pub async fn test_specifier( let mut worker = create_main_worker( &program_state, - main_module.clone(), + specifier.clone(), permissions, Some(&init_ops), ); @@ -277,7 +297,7 @@ pub async fn test_specifier( None }; - worker.execute_module(&test_module).await?; + worker.execute_module(&test_specifier).await?; worker .run_event_loop(maybe_coverage_collector.is_none()) @@ -459,41 +479,35 @@ async fn fetch_inline_files( Ok(files) } -/// Runs tests. -/// -#[allow(clippy::too_many_arguments)] -pub async fn run_tests( +/// Type check a collection of module and document specifiers. +async fn check_specifiers( program_state: Arc<ProgramState>, permissions: Permissions, - lib: module_graph::TypeLib, - doc_modules: Vec<ModuleSpecifier>, - test_modules: Vec<ModuleSpecifier>, - no_run: bool, - fail_fast: Option<NonZeroUsize>, - allow_none: bool, - filter: Option<String>, - shuffle: Option<u64>, - concurrent_jobs: NonZeroUsize, + specifiers: Vec<(ModuleSpecifier, TestMode)>, + lib: TypeLib, ) -> Result<(), AnyError> { - if !allow_none && doc_modules.is_empty() && test_modules.is_empty() { - return Err(generic_error("No test modules found")); - } - - let test_modules = if let Some(seed) = shuffle { - let mut rng = SmallRng::seed_from_u64(seed); - let mut test_modules = test_modules.clone(); - test_modules.sort(); - test_modules.shuffle(&mut rng); - test_modules - } else { - test_modules - }; + let inline_files = fetch_inline_files( + program_state.clone(), + specifiers + .iter() + .filter_map(|(specifier, mode)| { + if *mode != TestMode::Executable { + Some(specifier.clone()) + } else { + None + } + }) + .collect(), + ) + .await?; - if !doc_modules.is_empty() { - let files = fetch_inline_files(program_state.clone(), doc_modules).await?; - let specifiers = files.iter().map(|file| file.specifier.clone()).collect(); + if !inline_files.is_empty() { + let specifiers = inline_files + .iter() + .map(|file| file.specifier.clone()) + .collect(); - for file in files { + for file in inline_files { program_state.file_fetcher.insert_cached(file); } @@ -508,67 +522,79 @@ pub async fn run_tests( .await?; } - let prepare_roots = { - let mut files = Vec::new(); - let mut fetch_permissions = Permissions::allow_all(); - for specifier in &test_modules { - let file = program_state - .file_fetcher - .fetch(specifier, &mut fetch_permissions) - .await?; - - files.push(file); - } - - let prepare_roots = files - .iter() - .filter(|file| file.media_type != MediaType::Unknown) - .map(|file| file.specifier.clone()) - .collect(); - - prepare_roots - }; + let module_specifiers = specifiers + .iter() + .filter_map(|(specifier, mode)| { + if *mode != TestMode::Documentation { + Some(specifier.clone()) + } else { + None + } + }) + .collect(); program_state .prepare_module_graph( - prepare_roots, - lib.clone(), + module_specifiers, + lib, Permissions::allow_all(), - permissions.clone(), + permissions, program_state.maybe_import_map.clone(), ) .await?; - if no_run { - return Ok(()); - } - - let (sender, receiver) = channel::<TestEvent>(); - - let join_handles = test_modules.iter().map(move |main_module| { - let program_state = program_state.clone(); - let main_module = main_module.clone(); - let permissions = permissions.clone(); - let filter = filter.clone(); - let sender = sender.clone(); + Ok(()) +} - tokio::task::spawn_blocking(move || { - let join_handle = std::thread::spawn(move || { - let future = test_specifier( - program_state, - main_module, - permissions, - filter, - shuffle, - sender, - ); +/// Test a collection of specifiers with test modes concurrently. +async fn test_specifiers( + program_state: Arc<ProgramState>, + permissions: Permissions, + specifiers_with_mode: Vec<(ModuleSpecifier, TestMode)>, + fail_fast: Option<NonZeroUsize>, + filter: Option<String>, + shuffle: Option<u64>, + concurrent_jobs: NonZeroUsize, +) -> Result<(), AnyError> { + let specifiers_with_mode = if let Some(seed) = shuffle { + let mut rng = SmallRng::seed_from_u64(seed); + let mut specifiers_with_mode = specifiers_with_mode.clone(); + specifiers_with_mode.sort_by_key(|(specifier, _)| specifier.clone()); + specifiers_with_mode.shuffle(&mut rng); + specifiers_with_mode + } else { + specifiers_with_mode + }; - tokio_util::run_basic(future) - }); + let (sender, receiver) = channel::<TestEvent>(); - join_handle.join().unwrap() - }) - }); + let join_handles = + specifiers_with_mode.iter().map(move |(specifier, mode)| { + let program_state = program_state.clone(); + let permissions = permissions.clone(); + let specifier = specifier.clone(); + let mode = mode.clone(); + let filter = filter.clone(); + let sender = sender.clone(); + + tokio::task::spawn_blocking(move || { + let join_handle = std::thread::spawn(move || { + let future = test_specifier( + program_state, + permissions, + specifier, + mode, + filter, + shuffle, + sender, + ); + + tokio_util::run_basic(future) + }); + + join_handle.join().unwrap() + }) + }); let join_stream = stream::iter(join_handles) .buffer_unordered(concurrent_jobs.get()) @@ -669,3 +695,343 @@ pub async fn run_tests( Ok(()) } + +/// Collects specifiers marking them with the appropriate test mode while maintaining the natural +/// input order. +/// +/// - Specifiers matching the `is_supported_test_ext` predicate are marked as +/// `TestMode::Documentation`. +/// - Specifiers matching the `is_supported_test_path` are marked as `TestMode::Executable`. +/// - Specifiers matching both predicates are marked as `TestMode::Both` +fn collect_specifiers_with_test_mode( + include: Vec<String>, + ignore: Vec<PathBuf>, + include_inline: bool, +) -> Result<Vec<(ModuleSpecifier, TestMode)>, AnyError> { + let module_specifiers = + collect_specifiers(include.clone(), &ignore, is_supported_test_path)?; + + if include_inline { + return collect_specifiers(include, &ignore, is_supported_test_ext).map( + |specifiers| { + specifiers + .into_iter() + .map(|specifier| { + let mode = if module_specifiers.contains(&specifier) { + TestMode::Both + } else { + TestMode::Documentation + }; + + (specifier, mode) + }) + .collect() + }, + ); + } + + let specifiers_with_mode = module_specifiers + .into_iter() + .map(|specifier| (specifier, TestMode::Executable)) + .collect(); + + Ok(specifiers_with_mode) +} + +/// Collects module and document specifiers with test modes via `collect_specifiers_with_test_mode` +/// which are then pre-fetched and adjusted based on the media type. +/// +/// Specifiers that do not have a known media type that can be executed as a module are marked as +/// `TestMode::Documentation`. +async fn fetch_specifiers_with_test_mode( + program_state: Arc<ProgramState>, + include: Vec<String>, + ignore: Vec<PathBuf>, + include_inline: bool, +) -> Result<Vec<(ModuleSpecifier, TestMode)>, AnyError> { + let mut specifiers_with_mode = + collect_specifiers_with_test_mode(include, ignore, include_inline)?; + for (specifier, mode) in &mut specifiers_with_mode { + let file = program_state + .file_fetcher + .fetch(specifier, &mut Permissions::allow_all()) + .await?; + + if file.media_type != MediaType::Unknown { + *mode = TestMode::Both + } else { + *mode = TestMode::Documentation + } + } + + Ok(specifiers_with_mode) +} + +#[allow(clippy::too_many_arguments)] +pub async fn run_tests( + flags: Flags, + include: Option<Vec<String>>, + ignore: Vec<PathBuf>, + doc: bool, + no_run: bool, + fail_fast: Option<NonZeroUsize>, + allow_none: bool, + filter: Option<String>, + shuffle: Option<u64>, + concurrent_jobs: NonZeroUsize, +) -> Result<(), AnyError> { + let program_state = ProgramState::build(flags.clone()).await?; + let permissions = Permissions::from_options(&flags.clone().into()); + let specifiers_with_mode = fetch_specifiers_with_test_mode( + program_state.clone(), + include.unwrap_or_else(|| vec![".".to_string()]), + ignore.clone(), + doc, + ) + .await?; + + if !allow_none && specifiers_with_mode.is_empty() { + return Err(generic_error("No test modules found")); + } + + let lib = if flags.unstable { + TypeLib::UnstableDenoWindow + } else { + TypeLib::DenoWindow + }; + + check_specifiers( + program_state.clone(), + permissions.clone(), + specifiers_with_mode.clone(), + lib, + ) + .await?; + + if no_run { + return Ok(()); + } + + test_specifiers( + program_state, + permissions, + specifiers_with_mode, + fail_fast, + filter, + shuffle, + concurrent_jobs, + ) + .await?; + + Ok(()) +} + +#[allow(clippy::too_many_arguments)] +pub async fn run_tests_with_watch( + flags: Flags, + include: Option<Vec<String>>, + ignore: Vec<PathBuf>, + doc: bool, + no_run: bool, + fail_fast: Option<NonZeroUsize>, + filter: Option<String>, + shuffle: Option<u64>, + concurrent_jobs: NonZeroUsize, +) -> Result<(), AnyError> { + let program_state = ProgramState::build(flags.clone()).await?; + let permissions = Permissions::from_options(&flags.clone().into()); + + let lib = if flags.unstable { + TypeLib::UnstableDenoWindow + } else { + TypeLib::DenoWindow + }; + + let handler = Arc::new(Mutex::new(FetchHandler::new( + &program_state, + Permissions::allow_all(), + Permissions::allow_all(), + )?)); + + let include = include.unwrap_or_else(|| vec![".".to_string()]); + let paths_to_watch: Vec<_> = include.iter().map(PathBuf::from).collect(); + + let resolver = |changed: Option<Vec<PathBuf>>| { + let paths_to_watch = paths_to_watch.clone(); + let paths_to_watch_clone = paths_to_watch.clone(); + + let handler = handler.clone(); + let program_state = program_state.clone(); + let files_changed = changed.is_some(); + let include = include.clone(); + let ignore = ignore.clone(); + + async move { + let test_modules = if doc { + collect_specifiers(include.clone(), &ignore, is_supported_test_ext) + } else { + collect_specifiers(include.clone(), &ignore, is_supported_test_path) + }?; + + let mut paths_to_watch = paths_to_watch_clone; + let mut modules_to_reload = if files_changed { + Vec::new() + } else { + test_modules + .iter() + .filter_map(|url| deno_core::resolve_url(url.as_str()).ok()) + .collect() + }; + + let mut builder = GraphBuilder::new( + handler, + program_state.maybe_import_map.clone(), + program_state.lockfile.clone(), + ); + for specifier in test_modules.iter() { + builder.add(specifier, false).await?; + } + builder + .analyze_config_file(&program_state.maybe_config_file) + .await?; + let graph = builder.get_graph(); + + for specifier in test_modules { + fn get_dependencies<'a>( + graph: &'a module_graph::Graph, + module: &'a Module, + // This needs to be accessible to skip getting dependencies if they're already there, + // otherwise this will cause a stack overflow with circular dependencies + output: &mut HashSet<&'a ModuleSpecifier>, + ) -> Result<(), AnyError> { + for dep in module.dependencies.values() { + if let Some(specifier) = &dep.maybe_code { + if !output.contains(specifier) { + output.insert(specifier); + + get_dependencies( + graph, + graph.get_specifier(specifier)?, + output, + )?; + } + } + if let Some(specifier) = &dep.maybe_type { + if !output.contains(specifier) { + output.insert(specifier); + + get_dependencies( + graph, + graph.get_specifier(specifier)?, + output, + )?; + } + } + } + + Ok(()) + } + + // This test module and all it's dependencies + let mut modules = HashSet::new(); + modules.insert(&specifier); + get_dependencies( + &graph, + graph.get_specifier(&specifier)?, + &mut modules, + )?; + + paths_to_watch.extend( + modules + .iter() + .filter_map(|specifier| specifier.to_file_path().ok()), + ); + + if let Some(changed) = &changed { + for path in changed.iter().filter_map(|path| { + deno_core::resolve_url_or_path(&path.to_string_lossy()).ok() + }) { + if modules.contains(&&path) { + modules_to_reload.push(specifier); + break; + } + } + } + } + + Ok((paths_to_watch, modules_to_reload)) + } + .map(move |result| { + if files_changed + && matches!(result, Ok((_, ref modules)) if modules.is_empty()) + { + ResolutionResult::Ignore + } else { + match result { + Ok((paths_to_watch, modules_to_reload)) => { + ResolutionResult::Restart { + paths_to_watch, + result: Ok(modules_to_reload), + } + } + Err(e) => ResolutionResult::Restart { + paths_to_watch, + result: Err(e), + }, + } + } + }) + }; + + let operation = |modules_to_reload: Vec<ModuleSpecifier>| { + let filter = filter.clone(); + let include = include.clone(); + let ignore = ignore.clone(); + let lib = lib.clone(); + let permissions = permissions.clone(); + let program_state = program_state.clone(); + + async move { + let specifiers_with_mode = fetch_specifiers_with_test_mode( + program_state.clone(), + include.clone(), + ignore.clone(), + doc, + ) + .await? + .iter() + .filter(|(specifier, _)| modules_to_reload.contains(specifier)) + .cloned() + .collect::<Vec<(ModuleSpecifier, TestMode)>>(); + + check_specifiers( + program_state.clone(), + permissions.clone(), + specifiers_with_mode.clone(), + lib, + ) + .await?; + + if no_run { + return Ok(()); + } + + test_specifiers( + program_state.clone(), + permissions.clone(), + specifiers_with_mode, + fail_fast, + filter.clone(), + shuffle, + concurrent_jobs, + ) + .await?; + + Ok(()) + } + }; + + file_watcher::watch_func(resolver, operation, "Test").await?; + + Ok(()) +} |