diff options
| author | nsfisis <nsfisis@gmail.com> | 2026-06-25 15:12:06 +0900 |
|---|---|---|
| committer | nsfisis <nsfisis@gmail.com> | 2026-06-26 00:20:05 +0900 |
| commit | 8117e5450693d19726da877bce8aacf5c7aa53af (patch) | |
| tree | 4b06e6f6924599f3402d29cdcdeb0f8a88354d89 /crates/shirabe/tests/downloader | |
| parent | 8dd3d67884a204ca40e3364206868fea77a312be (diff) | |
| download | php-shirabe-8117e5450693d19726da877bce8aacf5c7aa53af.tar.gz php-shirabe-8117e5450693d19726da877bce8aacf5c7aa53af.tar.zst php-shirabe-8117e5450693d19726da877bce8aacf5c7aa53af.zip | |
test: port 44 vcs/downloader/version tests using mock infra
Port git, version_guesser, gitlab_driver, github_driver, and git_downloader
tests using the ProcessExecutor/HttpDownloader mocks and IO/Config stubs.
Fix production regex-porting bugs surfaced by the now-reachable paths:
Url::sanitize and Response::find_header_value had non-delimited PCRE patterns;
implement array_search_mixed non-strict branch and a datetime format mapping.
Add HttpDownloader::__new_mock so mocked downloaders skip curl construction.
Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
Diffstat (limited to 'crates/shirabe/tests/downloader')
| -rw-r--r-- | crates/shirabe/tests/downloader/git_downloader_test.rs | 565 | ||||
| -rw-r--r-- | crates/shirabe/tests/downloader/main.rs | 4 |
2 files changed, 524 insertions, 45 deletions
diff --git a/crates/shirabe/tests/downloader/git_downloader_test.rs b/crates/shirabe/tests/downloader/git_downloader_test.rs index 69b0a6c..07dec08 100644 --- a/crates/shirabe/tests/downloader/git_downloader_test.rs +++ b/crates/shirabe/tests/downloader/git_downloader_test.rs @@ -1,18 +1,40 @@ //! ref: composer/tests/Composer/Test/Downloader/GitDownloaderTest.php +use std::cell::RefCell; +use std::rc::Rc; + +use indexmap::IndexMap; +use serial_test::serial; +use shirabe::config::Config; +use shirabe::downloader::VcsDownloader; +use shirabe::downloader::git_downloader::GitDownloader; +use shirabe::io::IOInterface; +use shirabe::package::handle::{CompletePackageHandle, PackageInterfaceHandle}; +use shirabe::util::Git as GitUtil; +use shirabe::util::ProcessExecutor; use shirabe::util::filesystem::Filesystem; +use shirabe_php_shim::PhpMixed; +use shirabe_semver::VersionParser; use tempfile::TempDir; +use crate::config_stub::ConfigStubBuilder; +use crate::io_stub::IOStub; +use crate::process_executor_mock::{cmd, cmd_full, get_process_executor_mock}; + +fn run<F: std::future::Future>(future: F) -> F::Output { + tokio::runtime::Builder::new_current_thread() + .build() + .unwrap() + .block_on(future) +} + fn set_up() -> TempDir { - // skipIfNotExecutable('git') - let () = todo!(); - // initGitVersion('1.0.0') resets the Composer\Util\Git static version cache via - // reflection; the static cache is not reachable from a test here. - #[allow(unreachable_code)] - { - let _fs = Filesystem::new(None); - TempDir::new().unwrap() - } + // skipIfNotExecutable('git') is irrelevant because every git invocation is mocked. + + // initGitVersion('1.0.0'): seed the Composer\Util\Git static version cache. + GitUtil::__set_version(Some("1.0.0".to_string())); + + TempDir::new().unwrap() } fn tear_down(working_dir: &std::path::Path) { @@ -20,9 +42,8 @@ fn tear_down(working_dir: &std::path::Path) { let mut fs = Filesystem::new(None); fs.remove_directory(working_dir).unwrap(); } - // initGitVersion(false) resets the Composer\Util\Git static version cache via - // reflection; the static cache is not reachable from a test here. - todo!() + // initGitVersion(false): drop the static version cache so it is re-detected next time. + GitUtil::__set_version(None); } struct TearDown { @@ -41,37 +62,280 @@ impl Drop for TearDown { } } -// These construct a GitDownloader with a mocked IO/Config and a mocked ProcessExecutor to -// feed git command output; mocking is not available, and a real HttpDownloader reaches -// curl_multi_init (todo!()). -#[ignore = "requires getProcessExecutorMock/getIOMock and getMockBuilder PackageInterface mock; no ProcessExecutorMock/IOMock mocking infrastructure exists"] +/// ref: TestCase::getMockBuilder('Composer\Package\PackageInterface')->getMock() +/// +/// A `getMockBuilder(PackageInterface)` mock returns null/0 for every unstubbed +/// method, so a real CompletePackage seeded with the stubbed values is a faithful +/// stand-in only when the resulting `getSourceUrls()` equals `[getSourceUrl()]`. +/// The multi-URL/mirror cases in PHP configure `getSourceUrls` to an arbitrary +/// list that a real package cannot reproduce, so those tests stay ignored. +fn get_package( + name: &str, + pretty_version: &str, + source_reference: Option<&str>, + source_url: Option<&str>, +) -> PackageInterfaceHandle { + let norm_version = VersionParser.normalize(pretty_version, None).unwrap(); + let package = + CompletePackageHandle::new(name.to_string(), norm_version, pretty_version.to_string()); + package.__set_source_type(Some("git".to_string())); + package.set_source_reference(source_reference.map(|s| s.to_string())); + package.set_source_url(source_url.map(|s| s.to_string())); + package.into() +} + +/// ref: GitDownloaderTest::setupConfig (seeds a temp `home` when none is set) +fn setup_config(config: Config) -> Config { + let mut config = config; + if !config.has("home") { + let tmp_dir = TempDir::new().unwrap().keep(); + let mut top: IndexMap<String, PhpMixed> = IndexMap::new(); + let mut section: IndexMap<String, PhpMixed> = IndexMap::new(); + section.insert( + "home".to_string(), + PhpMixed::String(tmp_dir.to_string_lossy().into_owned()), + ); + top.insert("config".to_string(), PhpMixed::Array(section)); + config.merge(&top, Config::SOURCE_UNKNOWN); + } + config +} + +/// ref: GitDownloaderTest::getDownloaderMock (defaults the IO/Config/Filesystem) +fn get_downloader_mock( + io: Option<Rc<RefCell<dyn IOInterface>>>, + config: Option<Config>, + process: Rc<RefCell<ProcessExecutor>>, + filesystem: Option<Rc<RefCell<Filesystem>>>, +) -> GitDownloader { + let io = + io.unwrap_or_else(|| Rc::new(RefCell::new(IOStub::new())) as Rc<RefCell<dyn IOInterface>>); + let config = Rc::new(RefCell::new(setup_config( + config.unwrap_or_else(|| ConfigStubBuilder::new().build()), + ))); + GitDownloader::new(io, config, Some(process), filesystem) +} + +#[serial] #[test] fn test_download_for_package_without_source_reference() { let working_dir = set_up(); let _tear_down = TearDown::new(working_dir.path().to_path_buf()); - let _ = &working_dir; - todo!() + + let package = get_package("dummy/pkg", "1.0.0", None, None); + + let (process, _guard) = get_process_executor_mock(vec![], false, Default::default()); + let mut downloader = get_downloader_mock(None, None, process, None); + + let result = run(async { + downloader.download(package.clone(), "/path", None).await?; + downloader + .prepare("install", package.clone(), "/path", None) + .await?; + downloader.install(package.clone(), "/path").await?; + downloader.cleanup("install", package, "/path", None).await + }); + + let e = result.expect_err("missing source reference should throw"); + assert!(e.to_string().contains("missing reference information")); } -#[ignore = "requires getProcessExecutorMock with expects() command expectations and getMockBuilder PackageInterface mock; no mocking infrastructure exists"] +#[serial] #[test] fn test_download() { let working_dir = set_up(); let _tear_down = TearDown::new(working_dir.path().to_path_buf()); - let _ = &working_dir; - todo!() + + let url = "https://example.com/composer/composer"; + let package = get_package( + "composer/composer", + "dev-master", + Some("1234567890123456789012345678901234567890"), + Some(url), + ); + + let expected_path = working_dir + .path() + .join("composerPath") + .to_string_lossy() + .into_owned(); + + let (process, _guard) = get_process_executor_mock( + vec![ + cmd(vec![ + "git", + "clone", + "--no-checkout", + "--", + url, + &expected_path, + ]), + cmd(vec!["git", "remote", "add", "composer", "--", url]), + cmd(["git", "fetch", "composer"]), + cmd(vec!["git", "remote", "set-url", "origin", "--", url]), + cmd(vec!["git", "remote", "set-url", "composer", "--", url]), + cmd(["git", "branch", "-r"]), + cmd(["git", "checkout", "master", "--"]), + cmd(vec![ + "git", + "reset", + "--hard", + "1234567890123456789012345678901234567890", + "--", + ]), + ], + true, + Default::default(), + ); + + let mut downloader = get_downloader_mock(None, None, process, None); + + run(async { + downloader + .download(package.clone(), &expected_path, None) + .await + .unwrap(); + downloader + .prepare("install", package.clone(), &expected_path, None) + .await + .unwrap(); + downloader + .install(package.clone(), &expected_path) + .await + .unwrap(); + downloader + .cleanup("install", package, &expected_path, None) + .await + .unwrap(); + }); } -#[ignore = "requires getProcessExecutorMock with expects() command expectations and initGitVersion reflection on Git::version static cache; no mocking infrastructure exists"] +#[serial] #[test] fn test_download_with_cache() { let working_dir = set_up(); let _tear_down = TearDown::new(working_dir.path().to_path_buf()); - let _ = &working_dir; - todo!() + + let url = "https://example.com/composer/composer"; + let package = get_package( + "composer/composer", + "dev-master", + Some("1234567890123456789012345678901234567890"), + Some(url), + ); + + // initGitVersion('2.17.0'): enables the cache (`--dissociate`) code path. + GitUtil::__set_version(Some("2.17.0".to_string())); + + let config = setup_config(Config::new(false, None)); + let cache_vcs_dir = config + .get("cache-vcs-dir") + .as_string() + .unwrap_or("") + .to_string(); + // ref: GitDownloaderTest cachePath = cache-vcs-dir.'/'.preg_replace('{[^a-z0-9.]}i', '-', url).'/' + let sanitized: String = url + .chars() + .map(|c| { + if c.is_ascii_alphanumeric() || c == '.' { + c + } else { + '-' + } + }) + .collect(); + let cache_path = format!("{}/{}/", cache_vcs_dir, sanitized); + + let mut filesystem = Filesystem::new(None); + filesystem.remove_directory(&cache_path).unwrap(); + + let expected_path = working_dir + .path() + .join("composerPath") + .to_string_lossy() + .into_owned(); + + let cache_path_for_callback = cache_path.clone(); + let mut clone_mirror = cmd(vec!["git", "clone", "--mirror", "--", url, &cache_path]); + clone_mirror.callback = Some(Box::new(move || { + std::fs::create_dir_all(&cache_path_for_callback).ok(); + })); + + let (process, _guard) = get_process_executor_mock( + vec![ + clone_mirror, + cmd(["git", "remote", "-v"]), + cmd(vec!["git", "remote", "set-url", "origin", "--", url]), + cmd_full(["git", "rev-parse", "--git-dir"], 0, ".", ""), + cmd(vec![ + "git", + "rev-parse", + "--quiet", + "--verify", + "1234567890123456789012345678901234567890^{commit}", + ]), + cmd(vec![ + "git", + "clone", + "--no-checkout", + &cache_path, + &expected_path, + "--dissociate", + "--reference", + &cache_path, + ]), + cmd(vec!["git", "remote", "set-url", "origin", "--", url]), + cmd(vec!["git", "remote", "add", "composer", "--", url]), + cmd(["git", "branch", "-r"]), + cmd_full(["git", "checkout", "master", "--"], 1, "", ""), + cmd(vec![ + "git", + "checkout", + "-B", + "master", + "composer/master", + "--", + ]), + cmd(vec![ + "git", + "reset", + "--hard", + "1234567890123456789012345678901234567890", + "--", + ]), + ], + true, + Default::default(), + ); + + let mut downloader = get_downloader_mock(None, Some(config), process, None); + + run(async { + downloader + .download(package.clone(), &expected_path, None) + .await + .unwrap(); + downloader + .prepare("install", package.clone(), &expected_path, None) + .await + .unwrap(); + downloader + .install(package.clone(), &expected_path) + .await + .unwrap(); + downloader + .cleanup("install", package, &expected_path, None) + .await + .unwrap(); + }); + + let mut fs = Filesystem::new(None); + fs.remove_directory(&cache_path).ok(); } -#[ignore = "requires getProcessExecutorMock with expects() command expectations and getMockBuilder PackageInterface mock; no mocking infrastructure exists"] +#[ignore = "getSourceUrls returns a mirror list (['https://github.com/mirrors/composer', \ + 'https://github.com/composer/composer']) that a real CompletePackage cannot reproduce \ + from a single source_url; no PackageInterface mock with arbitrary getSourceUrls exists"] #[test] fn test_download_uses_various_protocols_and_sets_push_url_for_github() { let working_dir = set_up(); @@ -80,7 +344,8 @@ fn test_download_uses_various_protocols_and_sets_push_url_for_github() { todo!() } -#[ignore = "requires getProcessExecutorMock with expects() command expectations and pushUrlProvider dataProvider; no mocking infrastructure exists"] +#[ignore = "pushUrlProvider configures getSourceUrls to a list a real CompletePackage cannot \ + reproduce; no PackageInterface mock with arbitrary getSourceUrls exists"] #[test] fn test_download_and_set_push_url_use_custom_various_protocols_for_github() { let working_dir = set_up(); @@ -89,43 +354,243 @@ fn test_download_and_set_push_url_use_custom_various_protocols_for_github() { todo!() } -#[ignore = "requires getProcessExecutorMock with expects() command expectations and getMockBuilder PackageInterface mock; no mocking infrastructure exists"] +#[serial] #[test] fn test_download_throws_runtime_exception_if_git_command_fails() { let working_dir = set_up(); let _tear_down = TearDown::new(working_dir.path().to_path_buf()); - let _ = &working_dir; - todo!() + + let url = "https://example.com/composer/composer"; + let package = get_package("composer/composer", "1.0.0", Some("ref"), Some(url)); + + let expected_path = working_dir + .path() + .join("composerPath") + .to_string_lossy() + .into_owned(); + + let (process, _guard) = get_process_executor_mock( + vec![cmd_full( + vec!["git", "clone", "--no-checkout", "--", url, &expected_path], + 1, + "", + "", + )], + false, + Default::default(), + ); + + let mut downloader = get_downloader_mock(None, None, process, None); + + let result = run(async { + downloader + .download(package.clone(), &expected_path, None) + .await?; + downloader + .prepare("install", package.clone(), &expected_path, None) + .await?; + downloader.install(package.clone(), &expected_path).await?; + downloader + .cleanup("install", package, &expected_path, None) + .await + }); + + let e = result.expect_err("failed git clone should throw"); + assert!(e.to_string().contains(&format!( + "Failed to execute git clone --no-checkout -- {} {}", + url, expected_path + ))); } -#[ignore = "requires getProcessExecutorMock/getDownloaderMock and getMockBuilder PackageInterface mock; no mocking infrastructure exists"] +#[serial] #[test] fn test_updatefor_package_without_source_reference() { let working_dir = set_up(); let _tear_down = TearDown::new(working_dir.path().to_path_buf()); - let _ = &working_dir; - todo!() + + let initial_package = get_package("dummy/pkg", "1.0.0", Some("ref"), None); + let source_package = get_package("dummy/pkg", "1.0.0", None, None); + + let (process, _guard) = get_process_executor_mock(vec![], false, Default::default()); + let mut downloader = get_downloader_mock(None, None, process, None); + + let result = run(async { + downloader + .download( + source_package.clone(), + "/path", + Some(initial_package.clone()), + ) + .await?; + downloader + .prepare( + "update", + source_package.clone(), + "/path", + Some(initial_package.clone()), + ) + .await?; + downloader + .update(initial_package.clone(), source_package.clone(), "/path") + .await?; + downloader + .cleanup("update", source_package, "/path", Some(initial_package)) + .await + }); + + let e = result.expect_err("missing source reference should throw"); + assert!(e.to_string().contains("missing reference information")); } -#[ignore = "requires getProcessExecutorMock with expects() command expectations and getMockBuilder PackageInterface mock; no mocking infrastructure exists"] +#[serial] #[test] fn test_update() { let working_dir = set_up(); let _tear_down = TearDown::new(working_dir.path().to_path_buf()); - let _ = &working_dir; - todo!() + + let url = "https://github.com/composer/composer"; + let package = get_package("composer/composer", "1.0.0", Some("ref"), Some(url)); + + let (process, _guard) = get_process_executor_mock( + vec![ + cmd(["git", "show-ref", "--head", "-d"]), + cmd(["git", "status", "--porcelain", "--untracked-files=no"]), + cmd_full( + ["git", "rev-parse", "--quiet", "--verify", "ref^{commit}"], + 1, + "", + "", + ), + // fallback commands for the above failing + cmd(["git", "remote", "-v"]), + cmd(vec!["git", "remote", "set-url", "composer", "--", url]), + cmd(["git", "fetch", "composer"]), + cmd(["git", "fetch", "--tags", "composer"]), + cmd(["git", "remote", "-v"]), + cmd(vec!["git", "remote", "set-url", "composer", "--", url]), + cmd(["git", "branch", "-r"]), + cmd(["git", "checkout", "ref", "--"]), + cmd(["git", "reset", "--hard", "ref", "--"]), + cmd(["git", "remote", "-v"]), + ], + true, + Default::default(), + ); + + let mut fs = Filesystem::new(None); + fs.ensure_directory_exists(&format!("{}/.git", working_dir.path().to_string_lossy())) + .unwrap(); + let working_dir_str = working_dir.path().to_string_lossy().into_owned(); + + let mut downloader = get_downloader_mock(None, Some(Config::new(false, None)), process, None); + + run(async { + downloader + .download(package.clone(), &working_dir_str, Some(package.clone())) + .await + .unwrap(); + downloader + .prepare( + "update", + package.clone(), + &working_dir_str, + Some(package.clone()), + ) + .await + .unwrap(); + downloader + .update(package.clone(), package.clone(), &working_dir_str) + .await + .unwrap(); + downloader + .cleanup("update", package.clone(), &working_dir_str, Some(package)) + .await + .unwrap(); + }); } -#[ignore = "requires getProcessExecutorMock with expects() command expectations and getMockBuilder PackageInterface mock; no mocking infrastructure exists"] +#[serial] #[test] fn test_update_with_new_repo_url() { let working_dir = set_up(); let _tear_down = TearDown::new(working_dir.path().to_path_buf()); - let _ = &working_dir; - todo!() + + let url = "https://github.com/composer/composer"; + let package = get_package("composer/composer", "1.0.0", Some("ref"), Some(url)); + + let (process, _guard) = get_process_executor_mock( + vec![ + cmd(["git", "show-ref", "--head", "-d"]), + cmd(["git", "status", "--porcelain", "--untracked-files=no"]), + cmd_full( + ["git", "rev-parse", "--quiet", "--verify", "ref^{commit}"], + 0, + "", + "", + ), + cmd(["git", "remote", "-v"]), + cmd(vec!["git", "remote", "set-url", "composer", "--", url]), + cmd(["git", "branch", "-r"]), + cmd(["git", "checkout", "ref", "--"]), + cmd(["git", "reset", "--hard", "ref", "--"]), + cmd_full( + ["git", "remote", "-v"], + 0, + "origin https://github.com/old/url (fetch)\n\ + origin https://github.com/old/url (push)\n\ + composer https://github.com/old/url (fetch)\n\ + composer https://github.com/old/url (push)\n", + "", + ), + cmd(vec!["git", "remote", "set-url", "origin", "--", url]), + cmd(vec![ + "git", + "remote", + "set-url", + "--push", + "origin", + "--", + "git@github.com:composer/composer.git", + ]), + ], + true, + Default::default(), + ); + + let mut fs = Filesystem::new(None); + fs.ensure_directory_exists(&format!("{}/.git", working_dir.path().to_string_lossy())) + .unwrap(); + let working_dir_str = working_dir.path().to_string_lossy().into_owned(); + + let mut downloader = get_downloader_mock(None, Some(Config::new(false, None)), process, None); + + run(async { + downloader + .download(package.clone(), &working_dir_str, Some(package.clone())) + .await + .unwrap(); + downloader + .prepare( + "update", + package.clone(), + &working_dir_str, + Some(package.clone()), + ) + .await + .unwrap(); + downloader + .update(package.clone(), package.clone(), &working_dir_str) + .await + .unwrap(); + downloader + .cleanup("update", package.clone(), &working_dir_str, Some(package)) + .await + .unwrap(); + }); } -#[ignore = "requires getProcessExecutorMock with expects() command expectations and getMockBuilder PackageInterface mock; no mocking infrastructure exists"] +#[ignore = "getSourceUrls returns a multi-URL list a real CompletePackage cannot reproduce; \ + no PackageInterface mock with arbitrary getSourceUrls exists"] #[test] fn test_update_throws_runtime_exception_if_git_command_fails() { let working_dir = set_up(); @@ -134,7 +599,8 @@ fn test_update_throws_runtime_exception_if_git_command_fails() { todo!() } -#[ignore = "requires getProcessExecutorMock with expects() command expectations and getMockBuilder PackageInterface mock; no mocking infrastructure exists"] +#[ignore = "getSourceUrls returns a multi-URL list (['/' , github]) a real CompletePackage cannot \ + reproduce; no PackageInterface mock with arbitrary getSourceUrls exists"] #[test] fn test_update_doesnt_throws_runtime_exception_if_git_command_fails_at_first_but_is_able_to_recover() { @@ -144,7 +610,8 @@ fn test_update_doesnt_throws_runtime_exception_if_git_command_fails_at_first_but todo!() } -#[ignore = "requires getIOMock with expects() output expectations, getProcessExecutorMock and getMockBuilder PackageInterface mock; no mocking infrastructure exists"] +#[ignore = "getSourceUrls returns a multi-URL list (['/foo/bar', github]) a real CompletePackage \ + cannot reproduce; no PackageInterface mock with arbitrary getSourceUrls exists"] #[test] fn test_downgrade_shows_appropriate_message() { let working_dir = set_up(); @@ -153,7 +620,8 @@ fn test_downgrade_shows_appropriate_message() { todo!() } -#[ignore = "requires getIOMock with expects() output expectations, getProcessExecutorMock and getMockBuilder PackageInterface mock; no mocking infrastructure exists"] +#[ignore = "getSourceUrls returns a multi-URL list (['/foo/bar', github]) a real CompletePackage \ + cannot reproduce; no PackageInterface mock with arbitrary getSourceUrls exists"] #[test] fn test_not_using_downgrading_with_references() { let working_dir = set_up(); @@ -162,7 +630,10 @@ fn test_not_using_downgrading_with_references() { todo!() } -#[ignore = "requires getProcessExecutorMock, getMockBuilder Filesystem mock with removeDirectoryAsync expectation and PackageInterface mock; no mocking infrastructure exists"] +#[ignore = "PHP mocks Filesystem::removeDirectoryAsync (asserting it is called once with the \ + working dir). With no Filesystem mock, the real removeDirectoryAsync drives the \ + Filesystem's own ProcessExecutor for `rm -rf`, which requires a Composer\\Loop and \ + cannot be redirected through the mocked ProcessExecutor"] #[test] fn test_remove() { let working_dir = set_up(); @@ -171,11 +642,15 @@ fn test_remove() { todo!() } -#[ignore = "requires getDownloaderMock which mocks IOInterface/Filesystem via getMockBuilder; no mocking infrastructure exists"] +#[serial] #[test] fn test_get_installation_source() { let working_dir = set_up(); let _tear_down = TearDown::new(working_dir.path().to_path_buf()); let _ = &working_dir; - todo!() + + let (process, _guard) = get_process_executor_mock(vec![], false, Default::default()); + let downloader = get_downloader_mock(None, None, process, None); + + assert_eq!("source", downloader.get_installation_source()); } diff --git a/crates/shirabe/tests/downloader/main.rs b/crates/shirabe/tests/downloader/main.rs index a2bbab4..8244010 100644 --- a/crates/shirabe/tests/downloader/main.rs +++ b/crates/shirabe/tests/downloader/main.rs @@ -2,8 +2,12 @@ mod config_stub; #[path = "../common/http_downloader_mock.rs"] mod http_downloader_mock; +#[path = "../common/io_mock.rs"] +mod io_mock; #[path = "../common/io_stub.rs"] mod io_stub; +#[path = "../common/process_executor_mock.rs"] +mod process_executor_mock; mod archive_downloader_test; mod download_manager_test; |
