diff options
| author | nsfisis <nsfisis@gmail.com> | 2026-06-27 17:21:00 +0900 |
|---|---|---|
| committer | nsfisis <nsfisis@gmail.com> | 2026-06-27 17:26:28 +0900 |
| commit | e98823e599eb375b30037cc714710e3309d927d1 (patch) | |
| tree | e1689ec164086798fc46aa6a58d1e914cf4873b2 /crates/shirabe/tests/repository | |
| parent | 20f620bdd0b5764ed2e9812dc0772f907b0d6f29 (diff) | |
| download | php-shirabe-e98823e599eb375b30037cc714710e3309d927d1.tar.gz php-shirabe-e98823e599eb375b30037cc714710e3309d927d1.tar.zst php-shirabe-e98823e599eb375b30037cc714710e3309d927d1.zip | |
test: port Composer tests unblocked by mockall, add seams
Port 11 categories of previously-ignored Composer tests now reachable with
the mockall crate: DownloadManager, VCS/Perforce/File downloaders,
VersionSelector, PlatformRepository, Auditor, installer/FilesystemRepository,
RootPackageLoader, util auth/http, commands, and Cache.
Extract test seams additively on concrete structs as *Interface traits
(Runtime, HhvmDetector, VersionGuesser, RepositorySet, Perforce,
BinaryInstaller) plus mock-field seams (Cache, Filesystem); consumers take
trait objects. Mocks are defined locally in the test crates via
mockall::mock!, since automock-generated mocks are cfg(test)-gated and
invisible across the integration-test boundary.
dataProviders are ported in full; tests blocked by unported shims stay
#[ignore] with documented reasons rather than reduced or weakened.
Fix product bugs surfaced by the ports:
- util/github: use the exception code, not the HTTP status, for 401/403
- advisory: serialize empty audit maps as [] to match PHP json_encode
- repository/filesystem and downloader/file: fix RefCell double-borrow panics
Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
Diffstat (limited to 'crates/shirabe/tests/repository')
3 files changed, 2065 insertions, 125 deletions
diff --git a/crates/shirabe/tests/repository/filesystem_repository_test.rs b/crates/shirabe/tests/repository/filesystem_repository_test.rs index 1914927..0d0e621 100644 --- a/crates/shirabe/tests/repository/filesystem_repository_test.rs +++ b/crates/shirabe/tests/repository/filesystem_repository_test.rs @@ -1,12 +1,23 @@ //! ref: composer/tests/Composer/Test/Repository/FilesystemRepositoryTest.php +use std::cell::RefCell; +use std::rc::Rc; + use indexmap::IndexMap; +use shirabe::dependency_resolver::operation::OperationInterface; use shirabe::installed_versions::InstalledVersions; +use shirabe::installer::{InstallationManagerInterface, InstallerInterface}; +use shirabe::io::IOInterface; use shirabe::json::json_file::JsonFile; +use shirabe::package::PackageInterfaceHandle; +use shirabe::repository::InstalledRepositoryInterface; use shirabe::repository::RepositoryInterface; use shirabe::repository::filesystem_repository::FilesystemRepository; +use shirabe::util::filesystem::Filesystem; use shirabe_php_shim::PhpMixed; +use crate::test_case::get_package; + /// PHP mocks JsonFile::read()/exists(); without a mocking framework the canned read value is /// materialized as a real temp file whose decoded JSON reproduces the mock return value exactly. fn create_temp_json_file(contents: &str) -> String { @@ -78,14 +89,114 @@ fn test_unexistent_repository_file() { assert_eq!(packages.len(), 0); } +/// Stub for `InstallationManagerInterface` whose `getInstallPath` returns a fixed path, mirroring +/// the PHPUnit mock built with `disableOriginalConstructor()`. Only `get_install_path` is reached by +/// `FilesystemRepository::write`; the call counter backs PHP's `expects($this->exactly(2))`. +#[derive(Debug)] +struct InstallPathStub { + calls: Rc<RefCell<i64>>, + fixed_path: String, +} + +impl InstallationManagerInterface for InstallPathStub { + fn add_installer(&mut self, _installer: Box<dyn InstallerInterface>) { + unimplemented!() + } + fn remove_installer(&mut self, _installer: &dyn InstallerInterface) { + unimplemented!() + } + fn disable_plugins(&mut self) { + unimplemented!() + } + fn is_package_installed( + &mut self, + _repo: &dyn InstalledRepositoryInterface, + _package: PackageInterfaceHandle, + ) -> anyhow::Result<bool> { + unimplemented!() + } + fn ensure_binaries_presence(&mut self, _package: PackageInterfaceHandle) { + unimplemented!() + } + fn execute( + &mut self, + _repo: &mut dyn InstalledRepositoryInterface, + _operations: Vec<Rc<dyn OperationInterface>>, + _dev_mode: bool, + _run_scripts: bool, + _download_only: bool, + ) -> anyhow::Result<()> { + unimplemented!() + } + fn get_install_path(&mut self, _package: PackageInterfaceHandle) -> Option<String> { + *self.calls.borrow_mut() += 1; + Some(self.fixed_path.clone()) + } + fn set_output_progress(&mut self, _output_progress: bool) { + unimplemented!() + } + fn notify_installs(&mut self, _io: Rc<RefCell<dyn IOInterface>>) { + unimplemented!() + } +} + #[test] -#[ignore = "requires mocking InstallationManager::get_install_path; write() takes a concrete InstallationManager with no trait/seam to stub the canned per-package paths the PHP test relies on"] fn test_repository_write() { - todo!() + // PHP mocks JsonFile::write/read/getPath; here a real JsonFile under a temp repo dir is written + // and read back. write() never reads the file (it dumps the in-memory packages), so the mocked + // read()/exists() return values are irrelevant to the result. + let base = std::fs::canonicalize(std::env::temp_dir()).unwrap(); + let repo_dir = format!( + "{}/shirabe_repo_write_test_{}_{}", + base.display(), + std::process::id(), + std::time::SystemTime::now() + .duration_since(std::time::UNIX_EPOCH) + .unwrap() + .as_nanos() + ); + let mut fs = Filesystem::new(None); + fs.remove_directory(&repo_dir).ok(); + + let json_path = format!("{}/vendor/composer/installed.json", repo_dir); + let json = JsonFile::new(json_path.clone(), None, None).unwrap(); + let mut repository = FilesystemRepository::new(json, false, None, None).unwrap(); + + let calls = Rc::new(RefCell::new(0i64)); + let mut im = InstallPathStub { + calls: calls.clone(), + fixed_path: format!("{}/vendor/woop/woop", repo_dir), + }; + + repository.set_dev_package_names(vec!["mypkg2".to_string()]); + repository + .add_package(get_package("mypkg2", "1.2.3")) + .unwrap(); + repository + .add_package(get_package("mypkg", "0.1.10")) + .unwrap(); + repository.write(true, &mut im).unwrap(); + + // PHP asserts getInstallPath is called exactly twice (once per installed package). + assert_eq!(*calls.borrow(), 2); + + let written = std::fs::read_to_string(&json_path).unwrap(); + let actual: serde_json::Value = serde_json::from_str(&written).unwrap(); + let expected = serde_json::json!({ + "packages": [ + {"name": "mypkg", "type": "library", "version": "0.1.10", "version_normalized": "0.1.10.0", "install-path": "../woop/woop"}, + {"name": "mypkg2", "type": "library", "version": "1.2.3", "version_normalized": "1.2.3.0", "install-path": "../woop/woop"}, + ], + "dev": true, + "dev-package-names": ["mypkg2"], + }); + assert_eq!(actual, expected); + + fs.remove_directory(&repo_dir).ok(); } #[test] -#[ignore = "requires mocking InstallationManager::get_install_path (concrete method, no stub seam) plus missing test helpers get_root_package and configure_links"] +#[ignore = "needs get_root_package + configure_links test helpers (not present in tests/common; project_test_port_link_setters describes them via ArrayLoader::load_packages but this branch lacks them) plus exact byte-match of the generated installed.php fixture under a chdir"] fn test_repository_writes_installed_php() { todo!() } diff --git a/crates/shirabe/tests/repository/platform_repository_test.rs b/crates/shirabe/tests/repository/platform_repository_test.rs index c85af4b..7d2a328 100644 --- a/crates/shirabe/tests/repository/platform_repository_test.rs +++ b/crates/shirabe/tests/repository/platform_repository_test.rs @@ -1,38 +1,1854 @@ //! ref: composer/tests/Composer/Test/Repository/PlatformRepositoryTest.php -use shirabe::repository::PlatformRepository; +use indexmap::IndexMap; +use mockall::predicate::eq; +use shirabe::package::{BasePackageHandle, Link}; +use shirabe::platform::{HhvmDetectorInterface, RuntimeInterface}; +use shirabe::repository::{ + FindPackageConstraint, PlatformRepository, RepositoryInterface, SEARCH_NAME, +}; +use shirabe_php_shim::PhpMixed; +use shirabe_semver::constraint::SimpleConstraint; + +// The Runtime/HhvmDetector seams are concrete structs in PHP; the tests mock them +// directly. +mockall::mock! { + pub Runtime {} + impl RuntimeInterface for Runtime { + fn has_constant(&self, constant_name: &str, class: Option<String>) -> bool; + fn get_constant(&self, constant_name: &str, class: Option<String>) -> PhpMixed; + fn invoke(&self, callable: PhpMixed, arguments: Vec<PhpMixed>) -> PhpMixed; + fn has_class(&self, class: &str) -> bool; + fn construct(&self, class: &str, arguments: Vec<PhpMixed>) -> anyhow::Result<PhpMixed>; + fn get_extensions(&self) -> Vec<String>; + fn get_extension_version(&self, extension: &str) -> String; + fn get_extension_info(&self, extension: &str) -> anyhow::Result<String>; + } +} + +mockall::mock! { + pub HhvmDetector {} + impl HhvmDetectorInterface for HhvmDetector { + fn reset(&self); + fn get_version(&mut self) -> Option<String>; + } +} + +// The seam traits require `Debug` (so `PlatformRepository` can derive it); mockall does +// not generate it for mocks. +impl std::fmt::Debug for MockRuntime { + fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result { + f.write_str("MockRuntime") + } +} + +impl std::fmt::Debug for MockHhvmDetector { + fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result { + f.write_str("MockHhvmDetector") + } +} + +/// PHP: ltrim($class.'::'.$constant, ':') +fn constant_key(constant_name: &str, class: Option<&str>) -> String { + format!("{}::{}", class.unwrap_or(""), constant_name) + .trim_start_matches(':') + .to_string() +} + +// All five tests below are fully ported but kept `#[ignore]`: every code path through +// `PlatformRepository::initialize()` reaches `XdebugHandler::get_skipped_version()`, +// which is an unrelated `todo!()` stub in `shirabe-external-packages`, so the tests +// panic before any assertion (Phase D: visible failure is acceptable, and the category +// brief permits leaving confidently-panicking tests ignored). The seam/mocks are in +// place so they will execute once that stub lands. -// These probe runtime/extension/library versions and assert the synthesized platform -// packages; the detection mocks ProcessExecutor/Runtime/HhvmDetector and the package -// versions are parsed through a look-around regex. #[test] -#[ignore = "requires PHPUnit getMockBuilder mock of Composer\\Platform\\HhvmDetector (willReturn getVersion); Runtime/HhvmDetector are concrete structs with no mock/override mechanism"] +#[ignore = "seam + HhvmDetector mock ported; execution panics at the unrelated \ + XdebugHandler::get_skipped_version() todo!() during initialize(), and the \ + default (real) Runtime also relies on PHP-runtime shim todo!()s"] fn test_hhvm_package() { - todo!() + let mut hhvm_detector = MockHhvmDetector::new(); + hhvm_detector + .expect_get_version() + .times(..) + .returning(|| Some("2.1.0".to_string())); + + let mut platform_repository = + PlatformRepository::new4(vec![], IndexMap::new(), None, Some(Box::new(hhvm_detector))) + .unwrap(); + + let hhvm = platform_repository + .find_package("hhvm", FindPackageConstraint::String("*".to_string())) + .unwrap(); + assert!(hhvm.is_some(), "hhvm found"); + + assert_eq!("2.1.0", hhvm.unwrap().get_pretty_version()); +} + +fn php_flavor_test_cases() -> Vec<( + IndexMap<String, PhpMixed>, + Vec<(&'static str, &'static str)>, + Vec<(PhpMixed, Vec<PhpMixed>, PhpMixed)>, +)> { + let ubuntu = "7.2.31-1+ubuntu16.04.1+deb.sury.org+1"; + let s = |v: &str| PhpMixed::String(v.to_string()); + + vec![ + ( + IndexMap::from([("PHP_VERSION".to_string(), s("7.1.33"))]), + vec![("php", "7.1.33")], + vec![], + ), + ( + IndexMap::from([ + ("PHP_VERSION".to_string(), s(ubuntu)), + ("PHP_DEBUG".to_string(), PhpMixed::Bool(true)), + ]), + vec![("php", "7.2.31"), ("php-debug", "7.2.31")], + vec![], + ), + ( + IndexMap::from([ + ("PHP_VERSION".to_string(), s(ubuntu)), + ("PHP_ZTS".to_string(), PhpMixed::Bool(true)), + ]), + vec![("php", "7.2.31"), ("php-zts", "7.2.31")], + vec![], + ), + ( + IndexMap::from([ + ("PHP_VERSION".to_string(), s(ubuntu)), + ("PHP_INT_SIZE".to_string(), PhpMixed::Int(8)), + ]), + vec![("php", "7.2.31"), ("php-64bit", "7.2.31")], + vec![], + ), + ( + IndexMap::from([ + ("PHP_VERSION".to_string(), s(ubuntu)), + ("AF_INET6".to_string(), PhpMixed::Int(30)), + ]), + vec![("php", "7.2.31"), ("php-ipv6", "7.2.31")], + vec![], + ), + ( + IndexMap::from([("PHP_VERSION".to_string(), s(ubuntu))]), + vec![("php", "7.2.31"), ("php-ipv6", "7.2.31")], + vec![(s("inet_pton"), vec![s("::")], s(""))], + ), + ( + IndexMap::from([("PHP_VERSION".to_string(), s(ubuntu))]), + vec![("php", "7.2.31")], + vec![(s("inet_pton"), vec![s("::")], PhpMixed::Bool(false))], + ), + ] } #[test] -#[ignore = "requires PHPUnit getMockBuilder mock of Composer\\Platform\\Runtime (willReturnCallback/willReturnMap for hasConstant/getConstant/invoke/getExtensions); no mocking framework and Runtime is a concrete struct"] +#[ignore = "seam + Runtime mock ported (hasConstant/getConstant/invoke/getExtensions); \ + execution panics at the unrelated XdebugHandler::get_skipped_version() todo!()"] fn test_php_version() { - todo!() + for (constants, packages, functions) in php_flavor_test_cases() { + let constants_has = constants.clone(); + let constants_get = constants.clone(); + + let mut runtime = MockRuntime::new(); + runtime + .expect_get_extensions() + .times(..) + .returning(Vec::new); + runtime + .expect_has_constant() + .times(..) + .returning(move |constant, class| { + constants_has.contains_key(&constant_key(constant, class.as_deref())) + }); + runtime + .expect_get_constant() + .times(..) + .returning(move |constant, class| { + constants_get + .get(&constant_key(constant, class.as_deref())) + .cloned() + .unwrap_or(PhpMixed::Null) + }); + runtime + .expect_invoke() + .times(..) + .returning(move |callable, arguments| { + for (c, a, ret) in &functions { + if *c == callable && *a == arguments { + return ret.clone(); + } + } + PhpMixed::Null + }); + + let mut repository = + PlatformRepository::new4(vec![], IndexMap::new(), Some(Box::new(runtime)), None) + .unwrap(); + + for (package_name, version) in packages { + let package = repository + .find_package(package_name, FindPackageConstraint::String("*".to_string())) + .unwrap(); + assert!( + package.is_some(), + "Expected to find package \"{}\"", + package_name + ); + assert_eq!( + version, + package.unwrap().get_pretty_version(), + "Expected package \"{}\" version to be {}", + package_name, + version + ); + } + } } #[test] -#[ignore = "requires PHPUnit getMockBuilder mock of Composer\\Platform\\Runtime (expects(once)/with/willReturn for invoke, willReturnCallback for getConstant); no mocking framework"] +#[ignore = "seam + Runtime mock ported (invoke once + getConstant callback); \ + execution panics at the unrelated XdebugHandler::get_skipped_version() todo!()"] fn test_inet_pton_regression() { - todo!() + let mut runtime = MockRuntime::new(); + // PHP: ->expects(self::once())->method('invoke')->with('inet_pton', ['::'])->willReturn(false). + runtime + .expect_invoke() + .with( + eq(PhpMixed::String("inet_pton".to_string())), + eq(vec![PhpMixed::String("::".to_string())]), + ) + .times(1) + .returning(|_callable, _arguments| PhpMixed::Bool(false)); + // suppressing PHP_ZTS & AF_INET6 + runtime + .expect_has_constant() + .times(..) + .returning(|_, _| false); + + let constants: IndexMap<String, PhpMixed> = IndexMap::from([ + ( + "PHP_VERSION".to_string(), + PhpMixed::String("7.0.0".to_string()), + ), + ("PHP_DEBUG".to_string(), PhpMixed::Bool(false)), + ]); + runtime + .expect_get_constant() + .times(..) + .returning(move |constant, class| { + constants + .get(&constant_key(constant, class.as_deref())) + .cloned() + .unwrap_or(PhpMixed::Null) + }); + runtime + .expect_get_extensions() + .times(..) + .returning(Vec::new); + + let mut repository = + PlatformRepository::new4(vec![], IndexMap::new(), Some(Box::new(runtime)), None).unwrap(); + let package = repository + .find_package("php-ipv6", FindPackageConstraint::String("*".to_string())) + .unwrap(); + assert!(package.is_none()); +} + +enum Exp { + Version(&'static str), + Missing, + Full( + &'static str, + &'static [&'static str], + &'static [&'static str], + ), +} + +impl Exp { + fn parts( + &self, + ) -> ( + Option<&'static str>, + &'static [&'static str], + &'static [&'static str], + ) { + match self { + Exp::Version(v) => (Some(*v), &[], &[]), + Exp::Missing => (None, &[], &[]), + Exp::Full(v, replaces, provides) => (Some(*v), *replaces, *provides), + } + } +} + +#[derive(Clone)] +struct ClassDef { + class: &'static str, + construct: Option<(Vec<PhpMixed>, PhpMixed)>, +} + +struct LibCase { + extensions: Vec<&'static str>, + info: Option<&'static str>, + expectations: Vec<(&'static str, Exp)>, + functions: Vec<(PhpMixed, Vec<PhpMixed>, PhpMixed)>, + constants: Vec<(&'static str, Option<&'static str>, PhpMixed)>, + class_definitions: Vec<ClassDef>, +} + +// Port of PlatformRepositoryTest::provideLibraryTestCases (all 59 datasets). The intl and +// imagick cases reference the PHP ResourceBundleStub/ImagickStub objects (modelled as +// PhpMixed::Object holding the version they would expose); their runtime read paths are +// still `TODO(plugin)` stubs, but the data is ported faithfully. +fn library_test_cases() -> Vec<LibCase> { + let s = |v: &str| PhpMixed::String(v.to_string()); + let curl = |v: &str| -> (PhpMixed, Vec<PhpMixed>, PhpMixed) { + ( + s("curl_version"), + vec![], + PhpMixed::Array(IndexMap::from([("version".to_string(), s(v))])), + ) + }; + + vec![ + LibCase { + extensions: vec!["amqp"], + info: Some( + " + +amqp + +Version => 1.9.4 +Revision => release +Compiled => Nov 19 2019 @ 08:44:26 +AMQP protocol version => 0-9-1 +librabbitmq version => 0.9.0 +Default max channels per connection => 256 +Default max frame size => 131072 +Default heartbeats interval => 0", + ), + expectations: vec![ + ("lib-amqp-protocol", Exp::Version("0.9.1")), + ("lib-amqp-librabbitmq", Exp::Version("0.9.0")), + ], + functions: vec![], + constants: vec![], + class_definitions: vec![], + }, + LibCase { + extensions: vec!["bz2"], + info: Some( + " +bz2 + +BZip2 Support => Enabled +Stream Wrapper support => compress.bzip2:// +Stream Filter support => bzip2.decompress, bzip2.compress +BZip2 Version => 1.0.5, 6-Sept-2010", + ), + expectations: vec![("lib-bz2", Exp::Version("1.0.5"))], + functions: vec![], + constants: vec![], + class_definitions: vec![], + }, + LibCase { + extensions: vec!["curl"], + info: Some( + " +curl + +cURL support => enabled +cURL Information => 7.38.0 +Age => 3 +Features +AsynchDNS => Yes +CharConv => No +Debug => No +GSS-Negotiate => No +IDN => Yes +IPv6 => Yes +krb4 => No +Largefile => Yes +libz => Yes +NTLM => Yes +NTLMWB => Yes +SPNEGO => Yes +SSL => Yes +SSPI => No +TLS-SRP => Yes +HTTP2 => No +GSSAPI => Yes +Protocols => dict, file, ftp, ftps, gopher, http, https, imap, imaps, ldap, ldaps, pop3, pop3s, rtmp, rtsp, scp, sftp, smtp, smtps, telnet, tftp +Host => x86_64-pc-linux-gnu +SSL Version => OpenSSL/1.0.1t +ZLib Version => 1.2.8 +libSSH Version => libssh2/1.4.3 + +Directive => Local Value => Master Value +curl.cainfo => no value => no value", + ), + expectations: vec![ + ("lib-curl", Exp::Version("2.0.0")), + ("lib-curl-openssl", Exp::Version("1.0.1.20")), + ("lib-curl-zlib", Exp::Version("1.2.8")), + ("lib-curl-libssh2", Exp::Version("1.4.3")), + ], + functions: vec![curl("2.0.0")], + constants: vec![], + class_definitions: vec![], + }, + LibCase { + extensions: vec!["curl"], + info: Some( + " +curl + +cURL support => enabled +cURL Information => 7.38.0 +Age => 3 +Features +AsynchDNS => Yes +CharConv => No +Debug => No +GSS-Negotiate => No +IDN => Yes +IPv6 => Yes +krb4 => No +Largefile => Yes +libz => Yes +NTLM => Yes +NTLMWB => Yes +SPNEGO => Yes +SSL => Yes +SSPI => No +TLS-SRP => Yes +HTTP2 => No +GSSAPI => Yes +Protocols => dict, file, ftp, ftps, gopher, http, https, imap, imaps, ldap, ldaps, pop3, pop3s, rtmp, rtsp, scp, sftp, smtp, smtps, telnet, tftp +Host => x86_64-pc-linux-gnu +SSL Version => OpenSSL/1.0.1t-fips +ZLib Version => 1.2.8 +libSSH Version => libssh2/1.4.3 + +Directive => Local Value => Master Value +curl.cainfo => no value => no value", + ), + expectations: vec![ + ("lib-curl", Exp::Version("2.0.0")), + ( + "lib-curl-openssl-fips", + Exp::Full("1.0.1.20", &[], &["lib-curl-openssl"]), + ), + ("lib-curl-zlib", Exp::Version("1.2.8")), + ("lib-curl-libssh2", Exp::Version("1.4.3")), + ], + functions: vec![curl("2.0.0")], + constants: vec![], + class_definitions: vec![], + }, + LibCase { + extensions: vec!["curl"], + info: Some( + " +curl + +cURL support => enabled +cURL Information => 7.22.0 +Age => 3 +Features +AsynchDNS => No +CharConv => No +Debug => No +GSS-Negotiate => Yes +IDN => Yes +IPv6 => Yes +krb4 => No +Largefile => Yes +libz => Yes +NTLM => Yes +NTLMWB => Yes +SPNEGO => No +SSL => Yes +SSPI => No +TLS-SRP => Yes +Protocols => dict, file, ftp, ftps, gopher, http, https, imap, imaps, ldap, pop3, pop3s, rtmp, rtsp, smtp, smtps, telnet, tftp +Host => x86_64-pc-linux-gnu +SSL Version => GnuTLS/2.12.14 +ZLib Version => 1.2.3.4", + ), + expectations: vec![ + ("lib-curl", Exp::Version("7.22.0")), + ("lib-curl-zlib", Exp::Version("1.2.3.4")), + ( + "lib-curl-gnutls", + Exp::Full("2.12.14", &["lib-curl-openssl"], &[]), + ), + ], + functions: vec![curl("7.22.0")], + constants: vec![], + class_definitions: vec![], + }, + LibCase { + extensions: vec!["curl"], + info: Some( + " +curl + +cURL support => enabled +cURL Information => 7.24.0 +Age => 3 +Features +AsynchDNS => Yes +Debug => No +GSS-Negotiate => Yes +IDN => Yes +IPv6 => Yes +Largefile => Yes +NTLM => Yes +SPNEGO => No +SSL => Yes +SSPI => No +krb4 => No +libz => Yes +CharConv => No +Protocols => dict, file, ftp, ftps, gopher, http, https, imap, imaps, ldap, ldaps, pop3, pop3s, rtsp, scp, sftp, smtp, smtps, telnet, tftp +Host => x86_64-redhat-linux-gnu +SSL Version => NSS/3.13.3.0 +ZLib Version => 1.2.5 +libSSH Version => libssh2/1.4.1", + ), + expectations: vec![ + ("lib-curl", Exp::Version("7.24.0")), + ( + "lib-curl-nss", + Exp::Full("3.13.3.0", &["lib-curl-openssl"], &[]), + ), + ("lib-curl-zlib", Exp::Version("1.2.5")), + ("lib-curl-libssh2", Exp::Version("1.4.1")), + ], + functions: vec![curl("7.24.0")], + constants: vec![], + class_definitions: vec![], + }, + LibCase { + extensions: vec!["curl"], + info: Some( + " +curl + +cURL support => enabled +cURL Information => 7.68.0 +Age => 5 +Features +AsynchDNS => Yes +CharConv => No +Debug => No +GSS-Negotiate => No +IDN => Yes +IPv6 => Yes +krb4 => No +Largefile => Yes +libz => Yes +NTLM => Yes +NTLMWB => Yes +SPNEGO => Yes +SSL => Yes +SSPI => No +TLS-SRP => Yes +HTTP2 => Yes +GSSAPI => Yes +KERBEROS5 => Yes +UNIX_SOCKETS => Yes +PSL => Yes +HTTPS_PROXY => Yes +MULTI_SSL => No +BROTLI => Yes +Protocols => dict, file, ftp, ftps, gopher, http, https, imap, imaps, ldap, ldaps, pop3, pop3s, rtmp, rtsp, scp, sftp, smb, smbs, smtp, smtps, telnet, tftp +Host => x86_64-pc-linux-gnu +SSL Version => OpenSSL/1.1.1g +ZLib Version => 1.2.11 +libSSH Version => libssh/0.9.3/openssl/zlib", + ), + expectations: vec![ + ("lib-curl", Exp::Version("7.68.0")), + ("lib-curl-openssl", Exp::Version("1.1.1.7")), + ("lib-curl-zlib", Exp::Version("1.2.11")), + ("lib-curl-libssh", Exp::Version("0.9.3")), + ], + functions: vec![curl("7.68.0")], + constants: vec![], + class_definitions: vec![], + }, + LibCase { + extensions: vec!["curl"], + info: Some( + " +curl + +cURL support => enabled +cURL Information => 8.1.2 +Age => 10 +Features +AsynchDNS => Yes +CharConv => No +Debug => No +GSS-Negotiate => No +IDN => Yes +IPv6 => Yes +krb4 => No +Largefile => Yes +libz => Yes +NTLM => Yes +NTLMWB => Yes +SPNEGO => Yes +SSL => Yes +SSPI => No +TLS-SRP => Yes +HTTP2 => Yes +GSSAPI => Yes +KERBEROS5 => Yes +UNIX_SOCKETS => Yes +PSL => No +HTTPS_PROXY => Yes +MULTI_SSL => Yes +BROTLI => Yes +ALTSVC => Yes +HTTP3 => No +UNICODE => No +ZSTD => Yes +HSTS => Yes +GSASL => No +Protocols => dict, file, ftp, ftps, gopher, gophers, http, https, imap, imaps, ldap, ldaps, mqtt, pop3, pop3s, rtmp, rtmpe, rtmps, rtmpt, rtmpte, rtmpts, rtsp, scp, sftp, smb, smbs, smtp, smtps, telnet, tftp +Host => aarch64-apple-darwin22.4.0 +SSL Version => (SecureTransport) OpenSSL/3.1.1 +ZLib Version => 1.2.11 +libSSH Version => libssh2/1.11.0", + ), + expectations: vec![ + ("lib-curl", Exp::Version("8.1.2")), + ( + "lib-curl-securetransport", + Exp::Full("3.1.1", &["lib-curl-openssl"], &[]), + ), + ("lib-curl-zlib", Exp::Version("1.2.11")), + ("lib-curl-libssh2", Exp::Version("1.11.0")), + ], + functions: vec![curl("8.1.2")], + constants: vec![], + class_definitions: vec![], + }, + LibCase { + extensions: vec!["curl"], + info: Some( + " +curl + +cURL support => enabled +cURL Information => 8.1.2 +Age => 10 +Features +AsynchDNS => Yes +CharConv => No +Debug => No +GSS-Negotiate => No +IDN => No +IPv6 => Yes +krb4 => No +Largefile => Yes +libz => Yes +NTLM => Yes +NTLMWB => Yes +SPNEGO => Yes +SSL => Yes +SSPI => No +TLS-SRP => No +HTTP2 => Yes +GSSAPI => Yes +KERBEROS5 => Yes +UNIX_SOCKETS => Yes +PSL => No +HTTPS_PROXY => Yes +MULTI_SSL => Yes +BROTLI => No +ALTSVC => Yes +HTTP3 => No +UNICODE => No +ZSTD => No +HSTS => Yes +GSASL => No +Protocols => dict, file, ftp, ftps, gopher, gophers, http, https, imap, imaps, ldap, ldaps, mqtt, pop3, pop3s, rtsp, smb, smbs, smtp, smtps, telnet, tftp +Host => x86_64-apple-darwin20.0 +SSL Version => (SecureTransport) LibreSSL/2.8.3 +ZLib Version => 1.2.11", + ), + expectations: vec![ + ("lib-curl", Exp::Version("8.1.2")), + ( + "lib-curl-securetransport", + Exp::Full("2.8.3", &["lib-curl-libressl"], &[]), + ), + ("lib-curl-zlib", Exp::Version("1.2.11")), + ], + functions: vec![curl("8.1.2")], + constants: vec![], + class_definitions: vec![], + }, + LibCase { + extensions: vec!["date"], + info: Some( + " +date + +date/time support => enabled +timelib version => 2018.03 +\"Olson\" Timezone Database Version => 2020.1 +Timezone Database => external +Default timezone => Europe/Berlin", + ), + expectations: vec![ + ("lib-date-timelib", Exp::Version("2018.03")), + ("lib-date-zoneinfo", Exp::Version("2020.1")), + ], + functions: vec![], + constants: vec![], + class_definitions: vec![], + }, + LibCase { + extensions: vec!["date"], + info: Some( + " +date + +date/time support => enabled +\"Olson\" Timezone Database Version => 2013.2 +Timezone Database => internal +Default timezone => Europe/Amsterdam", + ), + expectations: vec![ + ("lib-date-zoneinfo", Exp::Version("2013.2")), + ("lib-date-timelib", Exp::Missing), + ], + functions: vec![], + constants: vec![], + class_definitions: vec![], + }, + LibCase { + extensions: vec!["date", "timezonedb"], + info: Some( + " +date + +date/time support => enabled +\"Olson\" Timezone Database Version => 2020.1 +Timezone Database => internal +Default timezone => UTC", + ), + expectations: vec![("lib-date-zoneinfo", Exp::Version("2020.1"))], + functions: vec![], + constants: vec![], + class_definitions: vec![], + }, + LibCase { + extensions: vec!["date", "timezonedb"], + info: Some( + " +date + +date/time support => enabled +\"Olson\" Timezone Database Version => 2020.1 +Timezone Database => external +Default timezone => UTC", + ), + expectations: vec![( + "lib-timezonedb-zoneinfo", + Exp::Full("2020.1", &["lib-date-zoneinfo"], &[]), + )], + functions: vec![], + constants: vec![], + class_definitions: vec![], + }, + LibCase { + extensions: vec!["date"], + info: Some( + " + + +date/time support => enabled +timelib version => 2018.03 +\"Olson\" Timezone Database Version => 0.system +Timezone Database => internal +Default timezone => Europe/Berlin + +Directive => Local Value => Master Value +date.timezone => no value => no value +date.default_latitude => 31.7667 => 31.7667 +date.default_longitude => 35.2333 => 35.2333 +date.sunset_zenith => 90.583333 => 90.583333 +date.sunrise_zenith => 90.583333 => 90.583333", + ), + expectations: vec![ + ("lib-date-zoneinfo", Exp::Version("0")), + ("lib-date-timelib", Exp::Version("2018.03")), + ], + functions: vec![], + constants: vec![], + class_definitions: vec![], + }, + LibCase { + extensions: vec!["fileinfo"], + info: Some( + " +fileinfo + +fileinfo support => enabled +libmagic => 537", + ), + expectations: vec![("lib-fileinfo-libmagic", Exp::Version("537"))], + functions: vec![], + constants: vec![], + class_definitions: vec![], + }, + LibCase { + extensions: vec!["gd"], + info: Some( + " +gd + +GD Support => enabled +GD Version => bundled (2.1.0 compatible) +FreeType Support => enabled +FreeType Linkage => with freetype +FreeType Version => 2.10.0 +GIF Read Support => enabled +GIF Create Support => enabled +JPEG Support => enabled +libJPEG Version => 9 compatible +PNG Support => enabled +libPNG Version => 1.6.34 +WBMP Support => enabled +XBM Support => enabled +WebP Support => enabled + +Directive => Local Value => Master Value +gd.jpeg_ignore_warning => 1 => 1", + ), + expectations: vec![ + ("lib-gd", Exp::Version("1.2.3")), + ("lib-gd-freetype", Exp::Version("2.10.0")), + ("lib-gd-libjpeg", Exp::Version("9.0")), + ("lib-gd-libpng", Exp::Version("1.6.34")), + ], + functions: vec![], + constants: vec![("GD_VERSION", None, s("1.2.3"))], + class_definitions: vec![], + }, + LibCase { + extensions: vec!["gd"], + info: Some( + " +gd + +GD Support => enabled +GD Version => bundled (2.1.0 compatible) +FreeType Support => enabled +FreeType Linkage => with freetype +FreeType Version => 2.9.1 +GIF Read Support => enabled +GIF Create Support => enabled +JPEG Support => enabled +libJPEG Version => 6b +PNG Support => enabled +libPNG Version => 1.6.35 +WBMP Support => enabled +XBM Support => enabled +WebP Support => enabled + +Directive => Local Value => Master Value +gd.jpeg_ignore_warning => 1 => 1", + ), + expectations: vec![ + ("lib-gd", Exp::Version("1.2.3")), + ("lib-gd-freetype", Exp::Version("2.9.1")), + ("lib-gd-libjpeg", Exp::Version("6.2")), + ("lib-gd-libpng", Exp::Version("1.6.35")), + ], + functions: vec![], + constants: vec![("GD_VERSION", None, s("1.2.3"))], + class_definitions: vec![], + }, + LibCase { + extensions: vec!["gd"], + info: Some( + " +gd + +GD Support => enabled +GD headers Version => 2.2.5 +GD library Version => 2.2.5 +FreeType Support => enabled +FreeType Linkage => with freetype +FreeType Version => 2.6.3 +GIF Read Support => enabled +GIF Create Support => enabled +JPEG Support => enabled +libJPEG Version => 6b +PNG Support => enabled +libPNG Version => 1.6.28 +WBMP Support => enabled +XPM Support => enabled +libXpm Version => 30411 +XBM Support => enabled +WebP Support => enabled + +Directive => Local Value => Master Value +gd.jpeg_ignore_warning => 1 => 1", + ), + expectations: vec![ + ("lib-gd", Exp::Version("2.2.5")), + ("lib-gd-freetype", Exp::Version("2.6.3")), + ("lib-gd-libjpeg", Exp::Version("6.2")), + ("lib-gd-libpng", Exp::Version("1.6.28")), + ("lib-gd-libxpm", Exp::Version("3.4.11")), + ], + functions: vec![], + constants: vec![("GD_VERSION", None, s("2.2.5"))], + class_definitions: vec![], + }, + LibCase { + extensions: vec!["iconv"], + info: None, + expectations: vec![("lib-iconv", Exp::Version("1.2.4"))], + functions: vec![], + constants: vec![("ICONV_VERSION", None, s("1.2.4"))], + class_definitions: vec![], + }, + LibCase { + extensions: vec!["gmp"], + info: None, + expectations: vec![("lib-gmp", Exp::Version("6.1.0"))], + functions: vec![], + constants: vec![("GMP_VERSION", None, s("6.1.0"))], + class_definitions: vec![], + }, + LibCase { + extensions: vec!["intl"], + info: Some( + " +intl + +Internationalization support => enabled +ICU version => 57.1 +ICU Data version => 57.1 +ICU TZData version => 2016b +ICU Unicode version => 8.0 + +Directive => Local Value => Master Value +intl.default_locale => no value => no value +intl.error_level => 0 => 0 +intl.use_exceptions => 0 => 0", + ), + expectations: vec![ + ("lib-icu", Exp::Version("100")), + ("lib-icu-cldr", Exp::Version("32.0.1")), + ("lib-icu-unicode", Exp::Version("7.0.0")), + ("lib-icu-zoneinfo", Exp::Version("2016.2")), + ], + functions: vec![ + ( + PhpMixed::List(vec![s("ResourceBundle"), s("create")]), + vec![s("root"), s("ICUDATA"), PhpMixed::Bool(false)], + PhpMixed::Object(IndexMap::from([("Version".to_string(), s("32.0.1"))])), + ), + ( + PhpMixed::List(vec![s("IntlChar"), s("getUnicodeVersion")]), + vec![], + PhpMixed::List(vec![ + PhpMixed::Int(7), + PhpMixed::Int(0), + PhpMixed::Int(0), + PhpMixed::Int(0), + ]), + ), + ], + constants: vec![("INTL_ICU_VERSION", None, s("100"))], + class_definitions: vec![ + ClassDef { + class: "ResourceBundle", + construct: None, + }, + ClassDef { + class: "IntlChar", + construct: None, + }, + ], + }, + LibCase { + extensions: vec!["intl"], + info: Some( + " +intl + +Internationalization support => enabled +version => 1.1.0 +ICU version => 57.1 +ICU Data version => 57.1", + ), + expectations: vec![("lib-icu", Exp::Version("57.1"))], + functions: vec![], + constants: vec![], + class_definitions: vec![], + }, + LibCase { + extensions: vec!["imagick"], + info: None, + expectations: vec![( + "lib-imagick-imagemagick", + Exp::Full("6.2.9", &["lib-imagick"], &[]), + )], + functions: vec![], + constants: vec![], + class_definitions: vec![ClassDef { + class: "Imagick", + construct: Some(( + vec![], + PhpMixed::Object(IndexMap::from([( + "versionString".to_string(), + s("ImageMagick 6.2.9 Q16 x86_64 2018-05-18 http://www.imagemagick.org"), + )])), + )), + }], + }, + LibCase { + extensions: vec!["imagick"], + info: None, + expectations: vec![( + "lib-imagick-imagemagick", + Exp::Full("7.0.8.34", &["lib-imagick"], &[]), + )], + functions: vec![], + constants: vec![], + class_definitions: vec![ClassDef { + class: "Imagick", + construct: Some(( + vec![], + PhpMixed::Object(IndexMap::from([( + "versionString".to_string(), + s("ImageMagick 7.0.8-34 Q16 x86_64 2019-03-23 https://imagemagick.org"), + )])), + )), + }], + }, + LibCase { + extensions: vec!["ldap"], + info: Some( + " +ldap + +LDAP Support => enabled +RCS Version => $Id: 5f1913de8e05a346da913956f81e0c0d8991c7cb $ +Total Links => 0/unlimited +API Version => 3001 +Vendor Name => OpenLDAP +Vendor Version => 20450 +SASL Support => Enabled + +Directive => Local Value => Master Value +ldap.max_links => Unlimited => Unlimited", + ), + expectations: vec![("lib-ldap-openldap", Exp::Version("2.4.50"))], + functions: vec![], + constants: vec![], + class_definitions: vec![], + }, + LibCase { + extensions: vec!["libxml"], + info: None, + expectations: vec![("lib-libxml", Exp::Version("2.1.5"))], + functions: vec![], + constants: vec![("LIBXML_DOTTED_VERSION", None, s("2.1.5"))], + class_definitions: vec![], + }, + LibCase { + extensions: vec!["libxml", "dom", "simplexml", "xml", "xmlreader", "xmlwriter"], + info: None, + expectations: vec![( + "lib-libxml", + Exp::Full( + "2.1.5", + &[], + &[ + "lib-dom-libxml", + "lib-simplexml-libxml", + "lib-xml-libxml", + "lib-xmlreader-libxml", + "lib-xmlwriter-libxml", + ], + ), + )], + functions: vec![], + constants: vec![("LIBXML_DOTTED_VERSION", None, s("2.1.5"))], + class_definitions: vec![], + }, + LibCase { + extensions: vec!["mbstring"], + info: Some( + " +mbstring + +Multibyte Support => enabled +Multibyte string engine => libmbfl +HTTP input encoding translation => disabled +libmbfl version => 1.3.2 + +mbstring extension makes use of \"streamable kanji code filter and converter\", which is distributed under the GNU Lesser General Public License version 2.1. + +Multibyte (japanese) regex support => enabled +Multibyte regex (oniguruma) version => 6.1.3", + ), + expectations: vec![ + ("lib-mbstring-libmbfl", Exp::Version("1.3.2")), + ("lib-mbstring-oniguruma", Exp::Version("7.0.0")), + ], + functions: vec![], + constants: vec![("MB_ONIGURUMA_VERSION", None, s("7.0.0"))], + class_definitions: vec![], + }, + LibCase { + extensions: vec!["mbstring"], + info: Some( + " +mbstring + +Multibyte Support => enabled +Multibyte string engine => libmbfl +HTTP input encoding translation => disabled +libmbfl version => 1.3.2 + +mbstring extension makes use of \"streamable kanji code filter and converter\", which is distributed under the GNU Lesser General Public License version 2.1. + +Multibyte (japanese) regex support => enabled +Multibyte regex (oniguruma) version => 6.1.3", + ), + expectations: vec![ + ("lib-mbstring-libmbfl", Exp::Version("1.3.2")), + ("lib-mbstring-oniguruma", Exp::Version("6.1.3")), + ], + functions: vec![], + constants: vec![], + class_definitions: vec![], + }, + LibCase { + extensions: vec!["mbstring"], + info: Some( + " +mbstring + +Multibyte Support => enabled +Multibyte string engine => libmbfl +HTTP input encoding translation => disabled +libmbfl version => 1.3.2 +oniguruma version => 6.9.4 + +mbstring extension makes use of \"streamable kanji code filter and converter\", which is distributed under the GNU Lesser General Public License version 2.1. + +Multibyte (japanese) regex support => enabled +Multibyte regex (oniguruma) backtrack check => On", + ), + expectations: vec![ + ("lib-mbstring-libmbfl", Exp::Version("1.3.2")), + ("lib-mbstring-oniguruma", Exp::Version("6.9.4")), + ], + functions: vec![], + constants: vec![], + class_definitions: vec![], + }, + LibCase { + extensions: vec!["memcached"], + info: Some( + " +memcached + +memcached support => enabled +Version => 3.1.5 +libmemcached version => 1.0.18 +SASL support => yes +Session support => yes +igbinary support => yes +json support => yes +msgpack support => yes", + ), + expectations: vec![("lib-memcached-libmemcached", Exp::Version("1.0.18"))], + functions: vec![], + constants: vec![], + class_definitions: vec![], + }, + LibCase { + extensions: vec!["openssl"], + info: None, + expectations: vec![("lib-openssl", Exp::Version("1.1.1.7"))], + functions: vec![], + constants: vec![("OPENSSL_VERSION_TEXT", None, s("OpenSSL 1.1.1g 21 Apr 2020"))], + class_definitions: vec![], + }, + LibCase { + extensions: vec!["openssl"], + info: None, + expectations: vec![("lib-openssl", Exp::Version("1.1.1.7"))], + functions: vec![], + constants: vec![( + "OPENSSL_VERSION_TEXT", + None, + s("OpenSSL 1.1.1g-freebsd 21 Apr 2020"), + )], + class_definitions: vec![], + }, + LibCase { + extensions: vec!["openssl"], + info: None, + expectations: vec![("lib-openssl", Exp::Version("0.9.8.33"))], + functions: vec![], + constants: vec![("OPENSSL_VERSION_TEXT", None, s("OpenSSL 0.9.8zg 21 Apr 2020"))], + class_definitions: vec![], + }, + LibCase { + extensions: vec!["openssl"], + info: None, + expectations: vec![("lib-openssl", Exp::Version("1.1.1.7-alpha1"))], + functions: vec![], + constants: vec![("OPENSSL_VERSION_TEXT", None, s("OpenSSL 1.1.1g-pre1 21 Apr 2020"))], + class_definitions: vec![], + }, + LibCase { + extensions: vec!["openssl"], + info: None, + expectations: vec![("lib-openssl", Exp::Version("1.1.1.7-beta2"))], + functions: vec![], + constants: vec![( + "OPENSSL_VERSION_TEXT", + None, + s("OpenSSL 1.1.1g-beta2 21 Apr 2020"), + )], + class_definitions: vec![], + }, + LibCase { + extensions: vec!["openssl"], + info: None, + expectations: vec![("lib-openssl", Exp::Version("1.1.1.7-alpha4"))], + functions: vec![], + constants: vec![( + "OPENSSL_VERSION_TEXT", + None, + s("OpenSSL 1.1.1g-alpha4 21 Apr 2020"), + )], + class_definitions: vec![], + }, + LibCase { + extensions: vec!["openssl"], + info: None, + expectations: vec![("lib-openssl", Exp::Version("1.1.1.7-rc2"))], + functions: vec![], + constants: vec![("OPENSSL_VERSION_TEXT", None, s("OpenSSL 1.1.1g-rc2 21 Apr 2020"))], + class_definitions: vec![], + }, + LibCase { + extensions: vec!["openssl"], + info: None, + expectations: vec![( + "lib-openssl-fips", + Exp::Full("1.1.1.7", &[], &["lib-openssl"]), + )], + functions: vec![], + constants: vec![("OPENSSL_VERSION_TEXT", None, s("OpenSSL 1.1.1g-fips 21 Apr 2020"))], + class_definitions: vec![], + }, + LibCase { + extensions: vec!["openssl"], + info: None, + expectations: vec![("lib-openssl", Exp::Version("2.0.1.0"))], + functions: vec![], + constants: vec![("OPENSSL_VERSION_TEXT", None, s("LibreSSL 2.0.1"))], + class_definitions: vec![], + }, + LibCase { + extensions: vec!["mysqlnd"], + info: Some( + " + mysqlnd + +mysqlnd => enabled +Version => mysqlnd 5.0.11-dev - 20150407 - $Id: 38fea24f2847fa7519001be390c98ae0acafe387 $ +Compression => supported +core SSL => supported +extended SSL => supported +Command buffer size => 4096 +Read buffer size => 32768 +Read timeout => 31536000 +Collecting statistics => Yes +Collecting memory statistics => Yes +Tracing => n/a +Loaded plugins => mysqlnd,debug_trace,auth_plugin_mysql_native_password,auth_plugin_mysql_clear_password,auth_plugin_sha256_password +API Extensions => pdo_mysql,mysqli", + ), + expectations: vec![("lib-mysqlnd-mysqlnd", Exp::Version("5.0.11-dev"))], + functions: vec![], + constants: vec![], + class_definitions: vec![], + }, + LibCase { + extensions: vec!["pdo_mysql"], + info: Some( + " + pdo_mysql + +PDO Driver for MySQL => enabled +Client API version => mysqlnd 5.0.10-dev - 20150407 - $Id: 38fea24f2847fa7519001be390c98ae0acafe387 $ + +Directive => Local Value => Master Value +pdo_mysql.default_socket => /tmp/mysql.sock => /tmp/mysql.sock", + ), + expectations: vec![("lib-pdo_mysql-mysqlnd", Exp::Version("5.0.10-dev"))], + functions: vec![], + constants: vec![], + class_definitions: vec![], + }, + LibCase { + extensions: vec!["mongodb"], + info: Some( + " + mongodb + +MongoDB support => enabled +MongoDB extension version => 1.6.1 +MongoDB extension stability => stable +libbson bundled version => 1.15.2 +libmongoc bundled version => 1.15.2 +libmongoc SSL => enabled +libmongoc SSL library => OpenSSL +libmongoc crypto => enabled +libmongoc crypto library => libcrypto +libmongoc crypto system profile => disabled +libmongoc SASL => disabled +libmongoc ICU => enabled +libmongoc compression => enabled +libmongoc compression snappy => disabled +libmongoc compression zlib => enabled + +Directive => Local Value => Master Value +mongodb.debug => no value => no value", + ), + expectations: vec![ + ("lib-mongodb-libmongoc", Exp::Version("1.15.2")), + ("lib-mongodb-libbson", Exp::Version("1.15.2")), + ], + functions: vec![], + constants: vec![], + class_definitions: vec![], + }, + LibCase { + extensions: vec!["pcre"], + info: Some( + " +pcre + +PCRE (Perl Compatible Regular Expressions) Support => enabled +PCRE Library Version => 10.33 2019-04-16 +PCRE Unicode Version => 11.0.0 +PCRE JIT Support => enabled +PCRE JIT Target => x86 64bit (little endian + unaligned)", + ), + expectations: vec![ + ("lib-pcre", Exp::Version("10.33")), + ("lib-pcre-unicode", Exp::Version("11.0.0")), + ], + functions: vec![], + constants: vec![("PCRE_VERSION", None, s("10.33 2019-04-16"))], + class_definitions: vec![], + }, + LibCase { + extensions: vec!["pcre"], + info: Some( + " +pcre + +PCRE (Perl Compatible Regular Expressions) Support => enabled +PCRE Library Version => 8.38 2015-11-23 + +Directive => Local Value => Master Value +pcre.backtrack_limit => 1000000 => 1000000 +pcre.recursion_limit => 100000 => 100000 + ", + ), + expectations: vec![("lib-pcre", Exp::Version("8.38"))], + functions: vec![], + constants: vec![("PCRE_VERSION", None, s("8.38 2015-11-23"))], + class_definitions: vec![], + }, + LibCase { + extensions: vec!["pgsql"], + info: Some( + " +pgsql + +PostgreSQL Support => enabled +PostgreSQL(libpq) Version => 12.2 +PostgreSQL(libpq) => PostgreSQL 12.3 on x86_64-apple-darwin18.7.0, compiled by Apple clang version 11.0.0 (clang-1100.0.33.17), 64-bit +Multibyte character support => enabled +SSL support => enabled +Active Persistent Links => 0 +Active Links => 0 + +Directive => Local Value => Master Value +pgsql.allow_persistent => On => On +pgsql.max_persistent => Unlimited => Unlimited +pgsql.max_links => Unlimited => Unlimited +pgsql.auto_reset_persistent => Off => Off +pgsql.ignore_notice => Off => Off +pgsql.log_notice => Off => Off", + ), + expectations: vec![("lib-pgsql-libpq", Exp::Version("12.2"))], + functions: vec![], + constants: vec![("PGSQL_LIBPQ_VERSION", None, s("12.2"))], + class_definitions: vec![], + }, + LibCase { + extensions: vec!["pdo_pgsql"], + info: Some( + " + pdo_pgsql + +PDO Driver for PostgreSQL => enabled +PostgreSQL(libpq) Version => 12.1 +Module version => 7.1.33 +Revision => $Id: 9c5f356c77143981d2e905e276e439501fe0f419 $", + ), + expectations: vec![("lib-pdo_pgsql-libpq", Exp::Version("12.1"))], + functions: vec![], + constants: vec![], + class_definitions: vec![], + }, + LibCase { + extensions: vec!["pq"], + info: Some( + "pq + +PQ Support => enabled +Extension Version => 2.2.0 + +Used Library => Compiled => Linked +libpq => 14.3 (Ubuntu 14.3-1.pgdg22.04+1) => 15.0.2 + ", + ), + expectations: vec![("lib-pq-libpq", Exp::Version("15.0.2"))], + functions: vec![], + constants: vec![], + class_definitions: vec![], + }, + LibCase { + extensions: vec!["rdkafka"], + info: None, + expectations: vec![("lib-rdkafka-librdkafka", Exp::Version("1.9.2"))], + functions: vec![], + constants: vec![("RD_KAFKA_VERSION", None, PhpMixed::Int(17367807))], + class_definitions: vec![], + }, + LibCase { + extensions: vec!["libsodium"], + info: None, + expectations: vec![("lib-libsodium", Exp::Version("1.0.17"))], + functions: vec![], + constants: vec![("SODIUM_LIBRARY_VERSION", None, s("1.0.17"))], + class_definitions: vec![], + }, + LibCase { + extensions: vec!["sodium"], + info: None, + expectations: vec![("lib-libsodium", Exp::Version("1.0.15"))], + functions: vec![], + constants: vec![("SODIUM_LIBRARY_VERSION", None, s("1.0.15"))], + class_definitions: vec![], + }, + LibCase { + extensions: vec!["pdo_sqlite"], + info: Some( + " +pdo_sqlite + +PDO Driver for SQLite 3.x => enabled +SQLite Library => 3.32.3 + ", + ), + expectations: vec![("lib-pdo_sqlite-sqlite", Exp::Version("3.32.3"))], + functions: vec![], + constants: vec![], + class_definitions: vec![], + }, + LibCase { + extensions: vec!["sqlite3"], + info: Some( + " +sqlite3 + +SQLite3 support => enabled +SQLite3 module version => 7.1.33 +SQLite Library => 3.31.0 + +Directive => Local Value => Master Value +sqlite3.extension_dir => no value => no value +sqlite3.defensive => 1 => 1", + ), + expectations: vec![("lib-sqlite3-sqlite", Exp::Version("3.31.0"))], + functions: vec![], + constants: vec![], + class_definitions: vec![], + }, + LibCase { + extensions: vec!["ssh2"], + info: Some( + " +ssh2 + +SSH2 support => enabled +extension version => 1.2 +libssh2 version => 1.8.0 +banner => SSH-2.0-libssh2_1.8.0", + ), + expectations: vec![("lib-ssh2-libssh2", Exp::Version("1.8.0"))], + functions: vec![], + constants: vec![], + class_definitions: vec![], + }, + LibCase { + extensions: vec!["yaml"], + info: Some( + " + yaml + +LibYAML Support => enabled +Module Version => 2.0.2 +LibYAML Version => 0.2.2 + +Directive => Local Value => Master Value +yaml.decode_binary => 0 => 0 +yaml.decode_timestamp => 0 => 0 +yaml.decode_php => 0 => 0 +yaml.output_canonical => 0 => 0 +yaml.output_indent => 2 => 2 +yaml.output_width => 80 => 80", + ), + expectations: vec![("lib-yaml-libyaml", Exp::Version("0.2.2"))], + functions: vec![], + constants: vec![], + class_definitions: vec![], + }, + LibCase { + extensions: vec!["xsl"], + info: Some( + " +xsl + +XSL => enabled +libxslt Version => 1.1.33 +libxslt compiled against libxml Version => 2.9.8 +EXSLT => enabled +libexslt Version => 1.1.29", + ), + expectations: vec![ + ("lib-libxslt", Exp::Full("1.1.29", &["lib-xsl"], &[])), + ("lib-libxslt-libxml", Exp::Version("2.9.8")), + ], + functions: vec![], + constants: vec![("LIBXSLT_DOTTED_VERSION", None, s("1.1.29"))], + class_definitions: vec![], + }, + LibCase { + extensions: vec!["zip"], + info: None, + expectations: vec![("lib-zip-libzip", Exp::Full("1.5.0", &["lib-zip"], &[]))], + functions: vec![], + constants: vec![("LIBZIP_VERSION", Some("ZipArchive"), s("1.5.0"))], + class_definitions: vec![], + }, + LibCase { + extensions: vec!["zlib"], + info: None, + expectations: vec![("lib-zlib", Exp::Version("1.2.10"))], + functions: vec![], + constants: vec![("ZLIB_VERSION", None, s("1.2.10"))], + class_definitions: vec![], + }, + LibCase { + extensions: vec!["zlib"], + info: Some( + " +zlib + +ZLib Support => enabled +Stream Wrapper => compress.zlib:// +Stream Filter => zlib.inflate, zlib.deflate +Compiled Version => 1.2.8 +Linked Version => 1.2.11", + ), + expectations: vec![("lib-zlib", Exp::Version("1.2.11"))], + functions: vec![], + constants: vec![], + class_definitions: vec![], + }, + ] +} + +fn assert_package_links( + context: &str, + expected_links: &[&str], + source_package: &BasePackageHandle, + links: IndexMap<String, Link>, +) { + assert_eq!( + expected_links.len(), + links.len(), + "{}: expected package count to match", + context + ); + + for link in links.values() { + assert_eq!(source_package.get_name(), link.get_source()); + assert!( + expected_links.contains(&link.get_target()), + "{}: package {} not in {:?}", + context, + link.get_target(), + expected_links + ); + let version_constraint = + SimpleConstraint::new("=".to_string(), source_package.get_version(), None); + assert!(link.get_constraint().matches(&version_constraint.into())); + } } #[test] -#[ignore = "requires PHPUnit getMockBuilder mock of Composer\\Platform\\Runtime (willReturnMap/willReturnCallback for getExtensions/getExtensionVersion/getExtensionInfo/invoke/hasConstant/getConstant/hasClass/construct) plus ResourceBundleStub/ImagickStub; no mocking framework"] +#[ignore = "all 59 provideLibraryTestCases datasets ported faithfully; execution still \ + panics at the unrelated XdebugHandler::get_skipped_version() todo!() (and a few \ + cases additionally rely on the ResourceBundle/Imagick stub paths), so it stays \ + ignored"] fn test_library_information() { - todo!() + let extension_version = "100.200.300"; + + for case in library_test_cases() { + let extensions: Vec<String> = case.extensions.iter().map(|e| e.to_string()).collect(); + + let mut constants: Vec<(String, Option<String>, PhpMixed)> = case + .constants + .iter() + .map(|(n, c, v)| (n.to_string(), c.map(|s| s.to_string()), v.clone())) + .collect(); + constants.push(( + "PHP_VERSION".to_string(), + None, + PhpMixed::String("7.1.0".to_string()), + )); + + let functions = case.functions.clone(); + let info = case.info.map(|s| s.to_string()); + + let exts_for_get = extensions.clone(); + let constants_has = constants.clone(); + let constants_get = constants.clone(); + + let mut runtime = MockRuntime::new(); + runtime + .expect_get_extensions() + .times(..) + .returning(move || exts_for_get.clone()); + runtime + .expect_get_extension_version() + .times(..) + .returning(move |_extension| extension_version.to_string()); + runtime + .expect_get_extension_info() + .times(..) + .returning(move |_extension| Ok(info.clone().unwrap_or_default())); + runtime + .expect_invoke() + .times(..) + .returning(move |callable, arguments| { + for (c, a, ret) in &functions { + if *c == callable && *a == arguments { + return ret.clone(); + } + } + PhpMixed::Null + }); + runtime + .expect_has_constant() + .times(..) + .returning(move |constant, class| { + constants_has + .iter() + .any(|(n, c, _)| n == constant && c.as_deref() == class.as_deref()) + }); + runtime + .expect_get_constant() + .times(..) + .returning(move |constant, class| { + constants_get + .iter() + .find(|(n, c, _)| n == constant && c.as_deref() == class.as_deref()) + .map(|(_, _, v)| v.clone()) + .unwrap_or(PhpMixed::Null) + }); + let class_definitions_has = case.class_definitions.clone(); + let class_definitions_construct = case.class_definitions.clone(); + runtime + .expect_has_class() + .times(..) + .returning(move |class| class_definitions_has.iter().any(|d| d.class == class)); + runtime + .expect_construct() + .times(..) + .returning(move |class, arguments| { + for d in &class_definitions_construct { + if d.class == class + && let Some((args, ret)) = &d.construct + && *args == arguments + { + return Ok(ret.clone()); + } + } + Ok(PhpMixed::Null) + }); + + let mut platform_repository = + PlatformRepository::new4(vec![], IndexMap::new(), Some(Box::new(runtime)), None) + .unwrap(); + + let libraries: Vec<String> = platform_repository + .search("lib".to_string(), SEARCH_NAME, None) + .unwrap() + .into_iter() + .map(|package| package.name) + .filter(|name| name.starts_with("lib-")) + .collect(); + let expected_libraries: Vec<&str> = case + .expectations + .iter() + .filter(|(_, exp)| !matches!(exp, Exp::Missing)) + .map(|(name, _)| *name) + .collect(); + assert_eq!( + expected_libraries.len(), + libraries.len(), + "Expected: {:?}, got {:?}", + expected_libraries, + libraries + ); + + let mut all_expectations: Vec<(String, (Option<&str>, &[&str], &[&str]))> = case + .expectations + .iter() + .map(|(name, exp)| (name.to_string(), exp.parts())) + .collect(); + for extension in &extensions { + all_expectations.push(( + format!("ext-{}", extension), + (Some(extension_version), &[], &[]), + )); + } + + for (package_name, (expected_version, expected_replaces, expected_provides)) in + all_expectations + { + let package = platform_repository + .find_package( + &package_name, + FindPackageConstraint::String("*".to_string()), + ) + .unwrap(); + match expected_version { + None => assert!( + package.is_none(), + "Expected to not find package \"{}\"", + package_name + ), + Some(expected_version) => { + assert!( + package.is_some(), + "Expected to find package \"{}\"", + package_name + ); + let package = package.unwrap(); + assert_eq!( + expected_version, + package.get_pretty_version(), + "Expected version {} for {}", + expected_version, + package_name + ); + assert_package_links( + "replaces", + expected_replaces, + &package, + package.get_replaces(), + ); + assert_package_links( + "provides", + expected_provides, + &package, + package.get_provides(), + ); + } + } + } + } } #[test] -#[ignore = "requires PHPUnit getMockBuilder mock of Composer\\Platform\\Runtime (willReturnMap for getConstant/getExtensions); no mocking framework and Runtime is a concrete struct"] +#[ignore = "seam + Runtime mock ported (getConstant/getExtensions map); execution panics \ + at the unrelated XdebugHandler::get_skipped_version() todo!()"] fn test_composer_platform_version() { - todo!() + let constants: IndexMap<String, PhpMixed> = IndexMap::from([ + ( + "PHP_VERSION".to_string(), + PhpMixed::String("7.0.0".to_string()), + ), + ("PHP_DEBUG".to_string(), PhpMixed::Bool(false)), + ]); + + let mut runtime = MockRuntime::new(); + runtime + .expect_get_extensions() + .times(..) + .returning(Vec::new); + runtime + .expect_get_constant() + .times(..) + .returning(move |constant, class| { + constants + .get(&constant_key(constant, class.as_deref())) + .cloned() + .unwrap_or(PhpMixed::Null) + }); + // PHP only stubs getExtensions/getConstant; PHPUnit auto-returns null/false for the + // other probed methods. Mirror that so initialize() does not hit unset expectations. + runtime + .expect_has_constant() + .times(..) + .returning(|_, _| false); + runtime + .expect_invoke() + .times(..) + .returning(|_, _| PhpMixed::Null); + + let mut platform_repository = + PlatformRepository::new4(vec![], IndexMap::new(), Some(Box::new(runtime)), None).unwrap(); + + let package = platform_repository + .find_package( + "composer", + FindPackageConstraint::String(format!("={}", shirabe::composer::get_version())), + ) + .unwrap(); + assert!(package.is_some(), "Composer package exists"); } #[test] diff --git a/crates/shirabe/tests/repository/vcs/perforce_driver_test.rs b/crates/shirabe/tests/repository/vcs/perforce_driver_test.rs index 5e9471f..ef55c75 100644 --- a/crates/shirabe/tests/repository/vcs/perforce_driver_test.rs +++ b/crates/shirabe/tests/repository/vcs/perforce_driver_test.rs @@ -8,25 +8,48 @@ use shirabe::config::Config; use shirabe::io::IOInterface; use shirabe::io::null_io::NullIO; use shirabe::repository::vcs::PerforceDriver; +use shirabe::util::HttpDownloader; use shirabe::util::filesystem::Filesystem; +use shirabe::util::process_executor::MockHandler; use shirabe_php_shim::PhpMixed; use tempfile::TempDir; +use crate::process_executor_mock::{ProcessExecutorMockGuard, get_process_executor_mock}; + const TEST_URL: &str = "TEST_PERFORCE_URL"; const TEST_DEPOT: &str = "TEST_DEPOT_CONFIG"; const TEST_BRANCH: &str = "TEST_BRANCH_CONFIG"; +// A getMockBuilder('Composer\Util\Perforce') stand-in: the seam trait extracted from the +// concrete `Perforce` struct, injected via overrideDriverInternalPerforce's Rust equivalent. +mockall::mock! { + #[derive(Debug)] + pub Perforce {} + impl shirabe::util::PerforceInterface for Perforce { + fn initialize_path(&mut self, path: &str); + fn set_stream(&mut self, stream: &str); + fn p4_login(&mut self) -> anyhow::Result<()>; + fn check_stream(&mut self) -> bool; + fn write_p4_client_spec(&mut self) -> anyhow::Result<()>; + fn connect_client(&mut self) -> anyhow::Result<()>; + fn sync_code_base(&mut self, source_reference: Option<String>) -> anyhow::Result<()>; + fn cleanup_client_spec(&mut self); + fn get_commit_logs(&mut self, from_reference: &str, to_reference: &str) -> Option<String>; + fn get_file_content(&mut self, file: &str, identifier: &str) -> Option<String>; + fn get_branches(&mut self) -> IndexMap<String, String>; + fn get_tags(&mut self) -> IndexMap<String, String>; + fn get_user(&self) -> Option<String>; + fn get_composer_information( + &mut self, + identifier: &str, + ) -> anyhow::Result<Option<IndexMap<String, PhpMixed>>>; + } +} + struct SetUp { test_path: TempDir, config: Config, repo_config: IndexMap<String, PhpMixed>, - // The IOInterface, ProcessExecutor, HttpDownloader and Perforce mocks, the - // driver instance and the reflection-based perforce override are not ported. - io: (), - process: (), - http_downloader: (), - perforce: (), - driver: (), } fn get_test_config(test_path: &std::path::Path) -> Config { @@ -57,25 +80,34 @@ fn set_up() -> SetUp { PhpMixed::String(TEST_BRANCH.to_string()), ); - let io = (); - let process = (); - let http_downloader = (); - let perforce = (); - // The driver construction and overrideDriverInternalPerforce (reflection) are not ported. - let driver = (); - SetUp { test_path, config, repo_config, - io, - process, - http_downloader, - perforce, - driver, } } +// ref: setUp builds the driver from a mock IO, an empty ProcessExecutorMock and a mock +// HttpDownloader. The empty, non-strict process mock lets any p4 query Perforce issues resolve +// with the default (exit 0) result. +fn make_driver( + config: Config, + repo_config: &IndexMap<String, PhpMixed>, +) -> (PerforceDriver, ProcessExecutorMockGuard) { + let io: Rc<RefCell<dyn IOInterface>> = Rc::new(RefCell::new(NullIO::new())); + let config = Rc::new(RefCell::new(config)); + let http_downloader = Rc::new(RefCell::new(HttpDownloader::new( + io.clone(), + config.clone(), + IndexMap::new(), + false, + ))); + let (process, guard) = get_process_executor_mock(vec![], false, MockHandler::default()); + let driver = PerforceDriver::new(repo_config.clone(), io, config, http_downloader, process); + + (driver, guard) +} + fn tear_down(test_path: &std::path::Path) { // cleanup directory under test path let mut fs = Filesystem::new(None); @@ -106,146 +138,127 @@ fn test_supports_returns_false_no_deep_check() { assert!(!PerforceDriver::supports(io, config, "existing.url", false).unwrap()); } -// The remaining cases mock Perforce, the repository config and IO to drive initialization, -// composer-file detection and cleanup; mocking is not available here. #[test] fn test_initialize_captures_variables_from_repo_config() { let SetUp { test_path, config, repo_config, - io, - process, - http_downloader, - perforce, - driver, } = set_up(); let _tear_down = TearDown::new(test_path.path().to_path_buf()); - let _ = (&io, &process, &http_downloader, &perforce, &driver); - let io: Rc<RefCell<dyn IOInterface>> = Rc::new(RefCell::new(NullIO::new())); - let config = Rc::new(RefCell::new(config)); - let http_downloader = Rc::new(RefCell::new(shirabe::util::HttpDownloader::new( - io.clone(), - config.clone(), - IndexMap::new(), - false, - ))); - // ref: setUp uses getProcessExecutorMock(); the empty, non-strict mock lets Perforce's p4 - // queries resolve with the default (exit 0) result so initialize() never shells out to p4. - let (process, _process_guard) = crate::process_executor_mock::get_process_executor_mock( - vec![], - false, - shirabe::util::process_executor::MockHandler::default(), - ); - - let mut driver = PerforceDriver::new(repo_config, io, config, http_downloader, process); + let (mut driver, _process_guard) = make_driver(config, &repo_config); driver.initialize().unwrap(); assert_eq!(TEST_URL, driver.get_url()); assert_eq!(TEST_DEPOT, driver.get_depot()); assert_eq!(TEST_BRANCH, driver.get_branch()); } -#[ignore = "needs a Perforce mock injected via reflection (overrideDriverInternalPerforce) to assert p4Login/checkStream/writeP4ClientSpec/connectClient are each called once; the perforce field is pub(crate) and no mock infra exists"] #[test] fn test_initialize_logs_in_and_connects_client() { let SetUp { test_path, config, repo_config, - io, - process, - http_downloader, - perforce, - driver, } = set_up(); let _tear_down = TearDown::new(test_path.path().to_path_buf()); - let _ = ( - &config, - &repo_config, - &io, - &process, - &http_downloader, - &perforce, - &driver, - ); - todo!() + + let (mut driver, _process_guard) = make_driver(config, &repo_config); + let mut perforce = MockPerforce::new(); + perforce.expect_p4_login().times(1).returning(|| Ok(())); + perforce.expect_check_stream().times(1).returning(|| false); + perforce + .expect_write_p4_client_spec() + .times(1) + .returning(|| Ok(())); + perforce + .expect_connect_client() + .times(1) + .returning(|| Ok(())); + driver.__override_perforce(Box::new(perforce)); + + driver.initialize().unwrap(); } -#[ignore = "needs a Perforce mock injected via reflection (overrideDriverInternalPerforce) to stub getComposerInformation; the perforce field is pub(crate) and no mock infra exists"] #[test] fn test_has_composer_file_returns_false_on_no_composer_file() { let SetUp { test_path, config, repo_config, - io, - process, - http_downloader, - perforce, - driver, } = set_up(); let _tear_down = TearDown::new(test_path.path().to_path_buf()); - let _ = ( - &config, - &repo_config, - &io, - &process, - &http_downloader, - &perforce, - &driver, - ); - todo!() + + let identifier = "TEST_IDENTIFIER"; + let formatted_depot_path = format!("//{}/{}", TEST_DEPOT, identifier); + + let (mut driver, _process_guard) = make_driver(config, &repo_config); + let mut perforce = MockPerforce::new(); + perforce.expect_p4_login().returning(|| Ok(())); + perforce.expect_check_stream().returning(|| false); + perforce.expect_write_p4_client_spec().returning(|| Ok(())); + perforce.expect_connect_client().returning(|| Ok(())); + perforce + .expect_get_composer_information() + .withf(move |id: &str| id == formatted_depot_path) + .returning(|_| Ok(Some(IndexMap::new()))); + driver.__override_perforce(Box::new(perforce)); + + driver.initialize().unwrap(); + let result = driver.has_composer_file(identifier); + assert!(!result); } -#[ignore = "needs a Perforce mock injected via reflection (overrideDriverInternalPerforce) to stub getComposerInformation; the perforce field is pub(crate) and no mock infra exists"] #[test] fn test_has_composer_file_returns_true_with_one_or_more_composer_files() { let SetUp { test_path, config, repo_config, - io, - process, - http_downloader, - perforce, - driver, } = set_up(); let _tear_down = TearDown::new(test_path.path().to_path_buf()); - let _ = ( - &config, - &repo_config, - &io, - &process, - &http_downloader, - &perforce, - &driver, - ); - todo!() + + let identifier = "TEST_IDENTIFIER"; + let formatted_depot_path = format!("//{}/{}", TEST_DEPOT, identifier); + + let (mut driver, _process_guard) = make_driver(config, &repo_config); + let mut perforce = MockPerforce::new(); + perforce.expect_p4_login().returning(|| Ok(())); + perforce.expect_check_stream().returning(|| false); + perforce.expect_write_p4_client_spec().returning(|| Ok(())); + perforce.expect_connect_client().returning(|| Ok(())); + perforce + .expect_get_composer_information() + .withf(move |id: &str| id == formatted_depot_path) + .returning(|_| { + // ref: returnValue(['']) — a non-empty list with one empty-string element. + let mut info: IndexMap<String, PhpMixed> = IndexMap::new(); + info.insert("0".to_string(), PhpMixed::String(String::new())); + Ok(Some(info)) + }); + driver.__override_perforce(Box::new(perforce)); + + driver.initialize().unwrap(); + let result = driver.has_composer_file(identifier); + assert!(result); } -#[ignore = "needs a Perforce mock injected via reflection (overrideDriverInternalPerforce) to assert cleanupClientSpec is called once; the perforce field is pub(crate) and no mock infra exists"] #[test] fn test_cleanup() { let SetUp { test_path, config, repo_config, - io, - process, - http_downloader, - perforce, - driver, } = set_up(); let _tear_down = TearDown::new(test_path.path().to_path_buf()); - let _ = ( - &config, - &repo_config, - &io, - &process, - &http_downloader, - &perforce, - &driver, - ); - todo!() + + let (mut driver, _process_guard) = make_driver(config, &repo_config); + let mut perforce = MockPerforce::new(); + perforce + .expect_cleanup_client_spec() + .times(1) + .returning(|| ()); + driver.__override_perforce(Box::new(perforce)); + + driver.cleanup().unwrap(); } |
