From 3a0d9340810a8808d963135a884f50d08442ac67 Mon Sep 17 00:00:00 2001 From: nsfisis Date: Thu, 25 Jun 2026 16:27:53 +0900 Subject: test: port 59 autoload/vcs/installer/util/command tests; fix output capture Port autoload_generator (24), bitbucket (14), suggested_packages (11), git_driver (6), archive_manager (3), and a bump command test. Fix the ApplicationTester output-capture root cause (php://memory streams must be readable regardless of fopen mode). Implement posix_getuid/geteuid, the PCRE 'A' anchored modifier, php_strip_whitespace, stream_get_wrappers, is_callable scalars; fix preg_quote angle-bracket escaping and class-map parser regexes. Co-Authored-By: Claude Opus 4.8 (1M context) --- .../tests/autoload/autoload_generator_test.rs | 1739 ++++++++++++++++++-- 1 file changed, 1639 insertions(+), 100 deletions(-) (limited to 'crates/shirabe/tests/autoload/autoload_generator_test.rs') diff --git a/crates/shirabe/tests/autoload/autoload_generator_test.rs b/crates/shirabe/tests/autoload/autoload_generator_test.rs index 72ac999..a3b833a 100644 --- a/crates/shirabe/tests/autoload/autoload_generator_test.rs +++ b/crates/shirabe/tests/autoload/autoload_generator_test.rs @@ -1,267 +1,1806 @@ //! ref: composer/tests/Composer/Test/Autoload/AutoloadGeneratorTest.php -/// Creates the working/vendor temp directories, switches into the working dir, and -/// builds the AutoloadGenerator from a mocked Config/InstallationManager/ -/// InstalledRepository/EventDispatcher and a BufferIO. The mocks and temp-dir -/// helpers are not available here, so this remains a stub. -fn set_up() { - todo!() +use std::cell::RefCell; +use std::rc::Rc; + +use indexmap::IndexMap; +use serial_test::serial; +use tempfile::TempDir; + +use shirabe::autoload::AutoloadGenerator; +use shirabe::composer::{ComposerHandle, PartialOrFullComposer}; +use shirabe::config::Config; +use shirabe::event_dispatcher::EventDispatcher; +use shirabe::installer::{InstallationManager, InstallerInterface}; +use shirabe::io::{BufferIO, IOInterface}; +use shirabe::package::handle::{AliasPackageHandle, PackageHandle, RootPackageHandle}; +use shirabe::package::{Link, PackageInterfaceHandle, RootPackageInterfaceHandle}; +use shirabe::repository::{ + InstalledArrayRepository, InstalledRepositoryInterface, WritableRepositoryInterface, +}; +use shirabe::util::http_downloader::HttpDownloader; +use shirabe::util::r#loop::Loop; +use shirabe_external_packages::symfony::console::output::output_interface; +use shirabe_php_shim::PhpMixed; +use shirabe_semver::constraint::{AnyConstraint, MatchAllConstraint, SimpleConstraint}; + +use crate::config_stub::ConfigStubBuilder; + +/// The mock `InstallationManager::getInstallPath` used throughout the test: metapackages return +/// null, every other package returns `vendorDir/(/)`. Registered as an installer +/// that supports every type so `InstallationManager::getInstallPath` routes to it. +#[derive(Debug)] +struct InstallPathStubInstaller { + vendor_dir: String, } -/// Restores the original working directory and removes the working/vendor -/// directories created by `set_up`, which is itself a stub. -fn tear_down() { - todo!() +#[async_trait::async_trait(?Send)] +impl InstallerInterface for InstallPathStubInstaller { + fn supports(&self, _package_type: &str) -> bool { + true + } + + fn is_installed( + &mut self, + _repo: &dyn InstalledRepositoryInterface, + _package: PackageInterfaceHandle, + ) -> bool { + true + } + + async fn download( + &mut self, + _package: PackageInterfaceHandle, + _prev_package: Option, + ) -> anyhow::Result> { + Ok(None) + } + + async fn prepare( + &mut self, + _type: &str, + _package: PackageInterfaceHandle, + _prev_package: Option, + ) -> anyhow::Result> { + Ok(None) + } + + async fn install( + &mut self, + _repo: &mut dyn InstalledRepositoryInterface, + _package: PackageInterfaceHandle, + ) -> anyhow::Result> { + Ok(None) + } + + async fn update( + &mut self, + _repo: &mut dyn InstalledRepositoryInterface, + _initial: PackageInterfaceHandle, + _target: PackageInterfaceHandle, + ) -> anyhow::Result> { + Ok(None) + } + + async fn uninstall( + &mut self, + _repo: &mut dyn InstalledRepositoryInterface, + _package: PackageInterfaceHandle, + ) -> anyhow::Result> { + Ok(None) + } + + async fn cleanup( + &mut self, + _type: &str, + _package: PackageInterfaceHandle, + _prev_package: Option, + ) -> anyhow::Result> { + Ok(None) + } + + fn get_install_path(&mut self, package: PackageInterfaceHandle) -> Option { + if package.get_type() == "metapackage" { + return None; + } + + let target_dir = package.get_target_dir(); + let suffix = match target_dir { + Some(dir) if !dir.is_empty() => format!("/{}", dir), + _ => String::new(), + }; + + Some(format!( + "{}/{}{}", + self.vendor_dir, + package.get_name(), + suffix + )) + } } -struct TearDown; +/// Mirrors the PHP `setUp`/`tearDown` lifecycle: a fresh temp working dir, a `composer-test-autoload` +/// vendor dir inside it, `chdir`ed into the working dir, plus the mocked Config/InstallationManager/ +/// repository/EventDispatcher and BufferIO. The temp tree is removed and the cwd restored on drop. +struct SetUp { + _temp_dir: TempDir, + prev_cwd: std::path::PathBuf, + working_dir: String, + vendor_dir: String, + repository: InstalledArrayRepository, + im: InstallationManager, + io: Rc>, + generator: AutoloadGenerator, + event_dispatcher: Rc>, +} -impl Drop for TearDown { +impl Drop for SetUp { fn drop(&mut self) { - tear_down(); + let _ = std::env::set_current_dir(&self.prev_cwd); + } +} + +fn null_path(s: &str) -> String { + s.to_string() +} + +fn set_up() -> SetUp { + let temp_dir = TempDir::new().unwrap(); + let working_dir = temp_dir.path().to_str().unwrap().to_string(); + let vendor_dir = format!("{}/composer-test-autoload", working_dir); + std::fs::create_dir_all(&vendor_dir).unwrap(); + + let prev_cwd = std::env::current_dir().unwrap(); + std::env::set_current_dir(&working_dir).unwrap(); + + let io = Rc::new(RefCell::new( + BufferIO::new(String::new(), output_interface::VERBOSITY_NORMAL, None).unwrap(), + )); + + // The PHP loop mock has its constructor disabled and is never exercised here, so a mock + // HttpDownloader (no real curl backend) stands in for the InstallationManager's loop. + let dispatcher_io: Rc> = io.clone(); + let config_for_downloader = Rc::new(RefCell::new(Config::new(false, None))); + let http_downloader = Rc::new(RefCell::new(HttpDownloader::__new_mock( + dispatcher_io.clone(), + config_for_downloader, + ))); + let loop_ = Rc::new(RefCell::new(Loop::new(http_downloader, None))); + + let mut im = InstallationManager::new(loop_, dispatcher_io.clone(), None); + im.add_installer(Box::new(InstallPathStubInstaller { + vendor_dir: vendor_dir.clone(), + })); + + let repository = InstalledArrayRepository::new().unwrap(); + + // EventDispatcher constructor is disabled in PHP and dispatch is never called when run-scripts + // is off (the default), so a real dispatcher over an empty Composer is a faithful no-op stand-in. + let composer = + ComposerHandle::from_rc_unchecked(Rc::new(RefCell::new(PartialOrFullComposer::new_full()))); + let event_dispatcher = Rc::new(RefCell::new(EventDispatcher::new( + composer.upcast().downgrade(), + dispatcher_io.clone(), + None, + ))); + + let generator = AutoloadGenerator::new(event_dispatcher.clone(), Some(dispatcher_io)); + + SetUp { + _temp_dir: temp_dir, + prev_cwd, + working_dir, + vendor_dir, + repository, + im, + io, + generator, + event_dispatcher, + } +} + +impl SetUp { + /// Builds the mocked Config returning `vendor-dir`/`platform-check`/`use-include-path`, mirroring + /// the PHP `configValueMap`. Rebuilt per call because tests mutate `vendor_dir`. + fn config(&self) -> Config { + ConfigStubBuilder::new() + .with("vendor-dir", PhpMixed::String(self.vendor_dir.clone())) + .with("platform-check", PhpMixed::Bool(true)) + .with("use-include-path", PhpMixed::Bool(false)) + .build() + } + + fn ensure_dir(&self, path: &str) { + std::fs::create_dir_all(path).unwrap(); + } + + fn put(&self, path: &str, contents: &str) { + let p = std::path::Path::new(path); + if let Some(parent) = p.parent() { + std::fs::create_dir_all(parent).unwrap(); + } + std::fs::write(path, contents).unwrap(); + } + + fn set_canonical_packages(&mut self, packages: Vec) { + for p in packages { + self.repository.add_package(p).unwrap(); + } } } -// These exercise AutoloadGenerator end-to-end: they build packages, write fixture files to -// a temp dir, run dump() through a mocked InstalledRepository/EventDispatcher and compare -// the generated autoload files. The integration setup (fixtures, mocks, filesystem) is not -// ported yet. +fn fixtures_dir() -> std::path::PathBuf { + std::path::Path::new(env!("CARGO_MANIFEST_DIR")) + .join("../../composer/tests/Composer/Test/Autoload/Fixtures") + .canonicalize() + .unwrap() +} + +/// ref: AutoloadGeneratorTest::assertAutoloadFiles +#[track_caller] +fn assert_autoload_files(name: &str, dir: &str, r#type: &str) { + let a = fixtures_dir().join(format!("autoload_{}.php", name)); + let b = format!("{}/autoload_{}.php", dir, r#type); + assert_file_content_equals(a.to_str().unwrap(), &b); +} + +/// ref: AutoloadGeneratorTest::assertFileContentEquals +#[track_caller] +fn assert_file_content_equals(expected: &str, actual: &str) { + let exp = std::fs::read_to_string(expected) + .unwrap_or_else(|e| panic!("read {}: {}", expected, e)) + .replace('\r', ""); + let act = std::fs::read_to_string(actual) + .unwrap_or_else(|e| panic!("read {}: {}", actual, e)) + .replace('\r', ""); + assert_eq!(exp, act, "{} equals {}", expected, actual); +} + +fn match_all() -> AnyConstraint { + AnyConstraint::MatchAll(MatchAllConstraint::new(None)) +} + +fn constraint(operator: &str, version: &str) -> AnyConstraint { + AnyConstraint::Simple(SimpleConstraint::new( + operator.to_string(), + version.to_string(), + None, + )) +} + +fn link(source: &str, target: &str, constraint: AnyConstraint, description: Option<&str>) -> Link { + Link::new( + source.to_string(), + target.to_string(), + constraint, + description.map(|d| d.to_string()), + String::new(), + ) +} + +fn requires(links: Vec<(&str, Link)>) -> IndexMap { + links.into_iter().map(|(k, v)| (k.to_string(), v)).collect() +} + +fn autoload(entries: Vec<(&str, PhpMixed)>) -> IndexMap { + entries + .into_iter() + .map(|(k, v)| (k.to_string(), v)) + .collect() +} + +fn str_list(items: &[&str]) -> PhpMixed { + PhpMixed::List( + items + .iter() + .map(|s| PhpMixed::String(s.to_string())) + .collect(), + ) +} + +fn str_map(entries: &[(&str, PhpMixed)]) -> PhpMixed { + PhpMixed::Array( + entries + .iter() + .map(|(k, v)| (k.to_string(), v.clone())) + .collect(), + ) +} + +fn pstr(v: &str) -> PhpMixed { + PhpMixed::String(v.to_string()) +} + +fn new_root_pkg(name: &str) -> RootPackageHandle { + RootPackageHandle::new(name.to_string(), null_path("1.0"), "1.0".to_string()) +} + +fn new_pkg(name: &str) -> PackageHandle { + PackageHandle::new(name.to_string(), "1.0".to_string(), "1.0".to_string()) +} + +fn dump( + s: &mut SetUp, + package: RootPackageInterfaceHandle, + scan_psr_packages: bool, + suffix: &str, +) -> anyhow::Result { + let config = s.config(); + // Borrow splitting: take fields out so dump can hold &mut to several at once. + let SetUp { + repository, + im, + generator, + .. + } = s; + generator.dump( + &config, + repository, + package, + im, + "composer", + scan_psr_packages, + Some(suffix.to_string()), + None, + false, + ) +} + #[test] -#[ignore = "setUp needs mocked Config::get, InstallationManager::getInstallPath, InstalledRepositoryInterface::getCanonicalPackages and TestCase::getUniqueTmpDirectory/ensureDirectoryExistsAndClear; no mock infra exists"] +#[serial] fn test_root_package_autoloading() { - todo!() + let mut s = set_up(); + let package = new_root_pkg("root/a"); + package.set_autoload(autoload(vec![ + ( + "psr-0", + str_map(&[ + ("Main", pstr("src/")), + ("Lala", str_list(&["src/", "lib/"])), + ]), + ), + ( + "psr-4", + str_map(&[ + ("Acme\\Fruit\\", pstr("src-fruit/")), + ("Acme\\Cake\\", str_list(&["src-cake/", "lib-cake/"])), + ]), + ), + ("classmap", str_list(&["composersrc/"])), + ])); + + s.ensure_dir(&format!("{}/composer", s.working_dir)); + s.ensure_dir(&format!("{}/src/Lala/Test", s.working_dir)); + s.ensure_dir(&format!("{}/lib", s.working_dir)); + s.put( + &format!("{}/src/Lala/ClassMapMain.php", s.working_dir), + "The following \"files\" autoload rules are included multiple times, this may cause issues and should be resolved:\n - $baseDir . '/foo.php'\n"; + assert_eq!(expected, s.io.borrow().get_output()); } #[test] -#[ignore = "setUp needs mocked Config/InstallationManager/InstalledRepositoryInterface and TestCase tmp-dir helpers; no mock infra exists"] +#[serial] fn test_vendors_autoloading() { - todo!() + let mut s = set_up(); + let package = new_root_pkg("root/a"); + package.set_requires(requires(vec![ + ("a/a", link("a", "a/a", match_all(), None)), + ("b/b", link("a", "b/b", match_all(), None)), + ])); + + let a = new_pkg("a/a"); + let b = new_pkg("b/b"); + let c = AliasPackageHandle::new(b.clone(), "1.2".to_string(), "1.2".to_string()); + a.__set_autoload(autoload(vec![( + "psr-0", + str_map(&[("A", pstr("src/")), ("A\\B", pstr("lib/"))]), + )])); + b.__set_autoload(autoload(vec![( + "psr-0", + str_map(&[("B\\Sub\\Name", pstr("src/"))]), + )])); + + s.set_canonical_packages(vec![a.into(), b.into(), c.into()]); + + s.ensure_dir(&format!("{}/composer", s.vendor_dir)); + s.ensure_dir(&format!("{}/a/a/src", s.vendor_dir)); + s.ensure_dir(&format!("{}/a/a/lib", s.vendor_dir)); + s.ensure_dir(&format!("{}/b/b/src", s.vendor_dir)); + + let composer_out = format!("{}/composer", s.vendor_dir); + dump(&mut s, package.into(), false, "_5").unwrap(); + assert_autoload_files("vendors", &composer_out, "namespaces"); + assert!(std::path::Path::new(&format!("{}/autoload_classmap.php", composer_out)).exists()); } #[test] -#[ignore = "setUp needs mocked Config/InstallationManager/InstalledRepositoryInterface and TestCase tmp-dir helpers; no mock infra exists"] +#[serial] fn test_vendors_autoloading_with_metapackages() { - todo!() + let mut s = set_up(); + let package = new_root_pkg("root/a"); + package.set_requires(requires(vec![("a/a", link("a", "a/a", match_all(), None))])); + + let a = new_pkg("a/a"); + let b = new_pkg("b/b"); + let c = AliasPackageHandle::new(b.clone(), "1.2".to_string(), "1.2".to_string()); + a.__set_autoload(autoload(vec![( + "psr-0", + str_map(&[("A", pstr("src/")), ("A\\B", pstr("lib/"))]), + )])); + b.__set_autoload(autoload(vec![( + "psr-0", + str_map(&[("B\\Sub\\Name", pstr("src/"))]), + )])); + a.__set_type("metapackage".to_string()); + a.__set_requires(requires(vec![( + "b/b", + link("a/a", "b/b", match_all(), None), + )])); + + s.set_canonical_packages(vec![a.into(), b.into(), c.into()]); + + s.ensure_dir(&format!("{}/composer", s.vendor_dir)); + s.ensure_dir(&format!("{}/b/b/src", s.vendor_dir)); + s.ensure_dir(&format!("{}/a/a/src", s.vendor_dir)); + s.ensure_dir(&format!("{}/a/a/lib", s.vendor_dir)); + + let composer_out = format!("{}/composer", s.vendor_dir); + dump(&mut s, package.into(), false, "_5").unwrap(); + assert_autoload_files("vendors_meta", &composer_out, "namespaces"); + assert!(std::path::Path::new(&format!("{}/autoload_classmap.php", composer_out)).exists()); } #[test] -#[ignore = "setUp needs mocked Config/InstallationManager/InstalledRepositoryInterface and TestCase tmp-dir helpers; no mock infra exists"] +#[serial] fn test_non_dev_autoload_exclusion_with_recursion() { - todo!() + let mut s = set_up(); + let package = new_root_pkg("root/a"); + package.set_requires(requires(vec![("a/a", link("a", "a/a", match_all(), None))])); + + let a = new_pkg("a/a"); + let b = new_pkg("b/b"); + a.__set_autoload(autoload(vec![( + "psr-0", + str_map(&[("A", pstr("src/")), ("A\\B", pstr("lib/"))]), + )])); + a.__set_requires(requires(vec![( + "b/b", + link("a/a", "b/b", match_all(), None), + )])); + b.__set_autoload(autoload(vec![( + "psr-0", + str_map(&[("B\\Sub\\Name", pstr("src/"))]), + )])); + b.__set_requires(requires(vec![( + "a/a", + link("b/b", "a/a", match_all(), None), + )])); + + s.set_canonical_packages(vec![a.into(), b.into()]); + + s.ensure_dir(&format!("{}/composer", s.vendor_dir)); + s.ensure_dir(&format!("{}/a/a/src", s.vendor_dir)); + s.ensure_dir(&format!("{}/a/a/lib", s.vendor_dir)); + s.ensure_dir(&format!("{}/b/b/src", s.vendor_dir)); + + let composer_out = format!("{}/composer", s.vendor_dir); + dump(&mut s, package.into(), false, "_5").unwrap(); + assert_autoload_files("vendors", &composer_out, "namespaces"); + assert!(std::path::Path::new(&format!("{}/autoload_classmap.php", composer_out)).exists()); } #[test] -#[ignore = "setUp needs mocked Config/InstallationManager/InstalledRepositoryInterface and TestCase tmp-dir helpers; no mock infra exists"] +#[serial] fn test_non_dev_autoload_should_include_replaced_packages() { - todo!() + let mut s = set_up(); + let package = new_root_pkg("root/a"); + package.set_requires(requires(vec![("a/a", link("a", "a/a", match_all(), None))])); + + let a = new_pkg("a/a"); + let b = new_pkg("b/b"); + a.__set_requires(requires(vec![( + "b/c", + link("a/a", "b/c", match_all(), None), + )])); + b.__set_autoload(autoload(vec![("psr-4", str_map(&[("B\\", pstr("src/"))]))])); + b.__set_replaces(requires(vec![( + "b/c", + link( + "b/b", + "b/c", + constraint("==", "1.0"), + Some(Link::TYPE_REPLACE), + ), + )])); + + s.set_canonical_packages(vec![a.into(), b.into()]); + + s.ensure_dir(&format!("{}/b/b/src/C", s.vendor_dir)); + s.put( + &format!("{}/b/b/src/C/C.php", s.vendor_dir), + " = IndexMap::new(); + expected.insert("B\\C\\C".to_string(), format!("{}/b/b/src/C/C.php", vendor)); + expected.insert( + "Composer\\InstalledVersions".to_string(), + format!("{}/composer/InstalledVersions.php", vendor), + ); + assert_eq!(&expected, class_map.get_map()); } #[test] -#[ignore = "setUp needs mocked Config/InstallationManager/InstalledRepositoryInterface and TestCase tmp-dir helpers; no mock infra exists"] +#[serial] fn test_non_dev_autoload_exclusion_with_recursion_replace() { - todo!() + let mut s = set_up(); + let package = new_root_pkg("root/a"); + package.set_requires(requires(vec![("a/a", link("a", "a/a", match_all(), None))])); + + let a = new_pkg("a/a"); + let b = new_pkg("b/b"); + a.__set_autoload(autoload(vec![( + "psr-0", + str_map(&[("A", pstr("src/")), ("A\\B", pstr("lib/"))]), + )])); + a.__set_requires(requires(vec![( + "c/c", + link("a/a", "c/c", match_all(), None), + )])); + b.__set_autoload(autoload(vec![( + "psr-0", + str_map(&[("B\\Sub\\Name", pstr("src/"))]), + )])); + b.__set_replaces(requires(vec![( + "c/c", + link("b/b", "c/c", match_all(), None), + )])); + + s.set_canonical_packages(vec![a.into(), b.into()]); + + s.ensure_dir(&format!("{}/composer", s.vendor_dir)); + s.ensure_dir(&format!("{}/a/a/src", s.vendor_dir)); + s.ensure_dir(&format!("{}/a/a/lib", s.vendor_dir)); + s.ensure_dir(&format!("{}/b/b/src", s.vendor_dir)); + + let composer_out = format!("{}/composer", s.vendor_dir); + dump(&mut s, package.into(), false, "_5").unwrap(); + assert_autoload_files("vendors", &composer_out, "namespaces"); + assert!(std::path::Path::new(&format!("{}/autoload_classmap.php", composer_out)).exists()); } #[test] -#[ignore = "setUp needs mocked Config/InstallationManager/InstalledRepositoryInterface and TestCase tmp-dir helpers; no mock infra exists"] +#[serial] fn test_non_dev_autoload_replaces_nested_requirements() { - todo!() + let mut s = set_up(); + let package = new_root_pkg("root/a"); + package.set_requires(requires(vec![("a/a", link("a", "a/a", match_all(), None))])); + + let a = new_pkg("a/a"); + let b = new_pkg("b/b"); + let c = new_pkg("c/c"); + let d = new_pkg("d/d"); + let e = new_pkg("e/e"); + a.__set_autoload(autoload(vec![("classmap", str_list(&["src/A.php"]))])); + a.__set_requires(requires(vec![( + "b/b", + link("a/a", "b/b", match_all(), None), + )])); + b.__set_autoload(autoload(vec![("classmap", str_list(&["src/B.php"]))])); + b.__set_requires(requires(vec![( + "e/e", + link("b/b", "e/e", match_all(), None), + )])); + c.__set_autoload(autoload(vec![("classmap", str_list(&["src/C.php"]))])); + c.__set_replaces(requires(vec![( + "b/b", + link("c/c", "b/b", match_all(), None), + )])); + c.__set_requires(requires(vec![( + "d/d", + link("c/c", "d/d", match_all(), None), + )])); + d.__set_autoload(autoload(vec![("classmap", str_list(&["src/D.php"]))])); + e.__set_autoload(autoload(vec![("classmap", str_list(&["src/E.php"]))])); + + s.set_canonical_packages(vec![a.into(), b.into(), c.into(), d.into(), e.into()]); + + for (name, file, class) in [ + ("a/a", "A", "A"), + ("b/b", "B", "B"), + ("c/c", "C", "C"), + ("d/d", "D", "D"), + ("e/e", "E", "E"), + ] { + s.ensure_dir(&format!("{}/{}/src", s.vendor_dir, name)); + s.put( + &format!("{}/{}/src/{}.php", s.vendor_dir, name, file), + &format!(" = IndexMap::new(); + expected.insert( + "Composer\\InstalledVersions".to_string(), + format!("{}/composer/InstalledVersions.php", vendor), + ); + assert_eq!(&expected, class_map.get_map()); } #[test] -#[ignore = "setUp needs mocked Config/InstallationManager/InstalledRepositoryInterface and TestCase tmp-dir helpers; no mock infra exists"] +#[serial] fn test_psr_to_class_map_ignores_non_psr_classes() { - todo!() + let mut s = set_up(); + let package = new_root_pkg("root/a"); + package.set_autoload(autoload(vec![ + ("psr-0", str_map(&[("psr0_", pstr("psr0/"))])), + ("psr-4", str_map(&[("psr4\\", pstr("psr4/"))])), + ])); + + s.ensure_dir(&format!("{}/psr0/psr0", s.working_dir)); + s.ensure_dir(&format!("{}/psr4", s.working_dir)); + s.put( + &format!("{}/psr0/psr0/match.php", s.working_dir), + " $vendorDir . '/composer/InstalledVersions.php',\n 'psr0_match' => $baseDir . '/psr0/psr0/match.php',\n 'psr4\\\\match' => $baseDir . '/psr4/match.php',\n);\n" + ); + let actual = + std::fs::read_to_string(format!("{}/autoload_classmap.php", composer_out)).unwrap(); + assert_eq!(expected, actual); } #[test] -#[ignore = "setUp needs mocked Config/InstallationManager/InstalledRepositoryInterface and TestCase tmp-dir helpers; no mock infra exists"] +#[serial] fn test_vendors_class_map_autoloading() { - todo!() + let mut s = set_up(); + let package = new_root_pkg("root/a"); + package.set_requires(requires(vec![ + ("a/a", link("a", "a/a", match_all(), None)), + ("b/b", link("a", "b/b", match_all(), None)), + ])); + + let a = new_pkg("a/a"); + let b = new_pkg("b/b"); + a.__set_autoload(autoload(vec![("classmap", str_list(&["src/"]))])); + b.__set_autoload(autoload(vec![("classmap", str_list(&["src/", "lib/"]))])); + + s.set_canonical_packages(vec![a.into(), b.into()]); + + s.ensure_dir(&format!("{}/composer", s.vendor_dir)); + s.ensure_dir(&format!("{}/a/a/src", s.vendor_dir)); + s.ensure_dir(&format!("{}/b/b/src", s.vendor_dir)); + s.ensure_dir(&format!("{}/b/b/lib", s.vendor_dir)); + s.put( + &format!("{}/a/a/src/a.php", s.vendor_dir), + " = IndexMap::new(); + expected.insert( + "ClassMapBar".to_string(), + format!("{}/b/b/src/b.php", vendor), + ); + expected.insert( + "ClassMapBaz".to_string(), + format!("{}/b/b/lib/c.php", vendor), + ); + expected.insert( + "ClassMapFoo".to_string(), + format!("{}/a/a/src/a.php", vendor), + ); + expected.insert( + "Composer\\InstalledVersions".to_string(), + format!("{}/composer/InstalledVersions.php", vendor), + ); + assert_eq!(&expected, class_map.get_map()); + assert_autoload_files("classmap4", &composer_out, "classmap"); } #[test] -#[ignore = "setUp needs mocked Config/InstallationManager/InstalledRepositoryInterface and TestCase tmp-dir helpers; no mock infra exists"] +#[serial] fn test_vendors_class_map_autoloading_with_target_dir() { - todo!() + let mut s = set_up(); + let package = new_root_pkg("root/a"); + package.set_requires(requires(vec![ + ("a/a", link("a", "a/a", match_all(), None)), + ("b/b", link("a", "b/b", match_all(), None)), + ])); + + let a = new_pkg("a/a"); + let b = new_pkg("b/b"); + a.__set_autoload(autoload(vec![( + "classmap", + str_list(&["target/src/", "lib/"]), + )])); + a.__set_target_dir(Some("target".to_string())); + b.__set_autoload(autoload(vec![("classmap", str_list(&["src/"]))])); + + s.set_canonical_packages(vec![a.into(), b.into()]); + + s.ensure_dir(&format!("{}/composer", s.vendor_dir)); + s.ensure_dir(&format!("{}/a/a/target/src", s.vendor_dir)); + s.ensure_dir(&format!("{}/a/a/target/lib", s.vendor_dir)); + s.ensure_dir(&format!("{}/b/b/src", s.vendor_dir)); + s.put( + &format!("{}/a/a/target/src/a.php", s.vendor_dir), + " = IndexMap::new(); + expected.insert( + "ClassMapBar".to_string(), + format!("{}/a/a/target/lib/b.php", vendor), + ); + expected.insert( + "ClassMapBaz".to_string(), + format!("{}/b/b/src/c.php", vendor), + ); + expected.insert( + "ClassMapFoo".to_string(), + format!("{}/a/a/target/src/a.php", vendor), + ); + expected.insert( + "Composer\\InstalledVersions".to_string(), + format!("{}/composer/InstalledVersions.php", vendor), + ); + assert_eq!(&expected, class_map.get_map()); } #[test] -#[ignore = "setUp needs mocked Config/InstallationManager/InstalledRepositoryInterface and TestCase tmp-dir helpers; no mock infra exists"] +#[serial] +#[ignore = "the `classmap => ['./']` rule yields a scanned path containing `/./` (e.g. c/c/./foo/test.php) that the ClassMapGenerator port does not collapse the way PHP's normalizePath does; needs production path-normalization fix"] fn test_class_map_autoloading_empty_dir_and_exact_file() { - todo!() + let mut s = set_up(); + let package = new_root_pkg("root/a"); + package.set_requires(requires(vec![ + ("a/a", link("a", "a/a", match_all(), None)), + ("b/b", link("a", "b/b", match_all(), None)), + ("c/c", link("a", "c/c", match_all(), None)), + ])); + + let a = new_pkg("a/a"); + let b = new_pkg("b/b"); + let c = new_pkg("c/c"); + a.__set_autoload(autoload(vec![("classmap", str_list(&[""]))])); + b.__set_autoload(autoload(vec![("classmap", str_list(&["test.php"]))])); + c.__set_autoload(autoload(vec![("classmap", str_list(&["./"]))])); + + s.set_canonical_packages(vec![a.into(), b.into(), c.into()]); + + s.ensure_dir(&format!("{}/composer", s.vendor_dir)); + s.ensure_dir(&format!("{}/a/a/src", s.vendor_dir)); + s.ensure_dir(&format!("{}/b/b", s.vendor_dir)); + s.ensure_dir(&format!("{}/c/c/foo", s.vendor_dir)); + s.put( + &format!("{}/a/a/src/a.php", s.vendor_dir), + " = IndexMap::new(); + expected.insert( + "ClassMapBar".to_string(), + format!("{}/b/b/test.php", vendor), + ); + expected.insert( + "ClassMapBaz".to_string(), + format!("{}/c/c/foo/test.php", vendor), + ); + expected.insert( + "ClassMapFoo".to_string(), + format!("{}/a/a/src/a.php", vendor), + ); + expected.insert( + "Composer\\InstalledVersions".to_string(), + format!("{}/composer/InstalledVersions.php", vendor), + ); + assert_eq!(&expected, class_map.get_map()); + assert_autoload_files("classmap5", &composer_out, "classmap"); + + let real = std::fs::read_to_string(format!("{}/autoload_real.php", composer_out)).unwrap(); + assert!(!real.contains("$loader->setClassMapAuthoritative(true);")); + assert!(!real.contains("$loader->setApcuPrefix(")); } #[test] -#[ignore = "setUp needs mocked Config/InstallationManager/InstalledRepositoryInterface and TestCase tmp-dir helpers; no mock infra exists"] +#[serial] fn test_class_map_autoloading_authoritative_and_apcu() { - todo!() + let mut s = set_up(); + let package = new_root_pkg("root/a"); + package.set_requires(requires(vec![ + ("a/a", link("a", "a/a", match_all(), None)), + ("b/b", link("a", "b/b", match_all(), None)), + ("c/c", link("a", "c/c", match_all(), None)), + ])); + + let a = new_pkg("a/a"); + let b = new_pkg("b/b"); + let c = new_pkg("c/c"); + a.__set_autoload(autoload(vec![("psr-4", str_map(&[("", pstr("src/"))]))])); + b.__set_autoload(autoload(vec![("psr-4", str_map(&[("", pstr("./"))]))])); + c.__set_autoload(autoload(vec![("psr-4", str_map(&[("", pstr("foo/"))]))])); + + s.set_canonical_packages(vec![a.into(), b.into(), c.into()]); + + s.ensure_dir(&format!("{}/composer", s.vendor_dir)); + s.ensure_dir(&format!("{}/a/a/src", s.vendor_dir)); + s.ensure_dir(&format!("{}/b/b", s.vendor_dir)); + s.ensure_dir(&format!("{}/c/c/foo", s.vendor_dir)); + s.put( + &format!("{}/a/a/src/ClassMapFoo.php", s.vendor_dir), + " = IndexMap::new(); + expected.insert( + "ClassMapBar".to_string(), + format!("{}/b/b/ClassMapBar.php", vendor), + ); + expected.insert( + "ClassMapBaz".to_string(), + format!("{}/c/c/foo/ClassMapBaz.php", vendor), + ); + expected.insert( + "ClassMapFoo".to_string(), + format!("{}/a/a/src/ClassMapFoo.php", vendor), + ); + expected.insert( + "Composer\\InstalledVersions".to_string(), + format!("{}/composer/InstalledVersions.php", vendor), + ); + assert_eq!(&expected, class_map.get_map()); + assert_autoload_files("classmap8", &composer_out, "classmap"); + + let real = std::fs::read_to_string(format!("{}/autoload_real.php", composer_out)).unwrap(); + assert!(real.contains("$loader->setClassMapAuthoritative(true);")); + assert!(real.contains("$loader->setApcuPrefix(")); } #[test] -#[ignore = "setUp needs mocked Config/InstallationManager/InstalledRepositoryInterface and TestCase tmp-dir helpers; no mock infra exists"] +#[serial] fn test_class_map_autoloading_authoritative_and_apcu_prefix() { - todo!() + let mut s = set_up(); + let package = new_root_pkg("root/a"); + package.set_requires(requires(vec![ + ("a/a", link("a", "a/a", match_all(), None)), + ("b/b", link("a", "b/b", match_all(), None)), + ("c/c", link("a", "c/c", match_all(), None)), + ])); + + let a = new_pkg("a/a"); + let b = new_pkg("b/b"); + let c = new_pkg("c/c"); + a.__set_autoload(autoload(vec![("psr-4", str_map(&[("", pstr("src/"))]))])); + b.__set_autoload(autoload(vec![("psr-4", str_map(&[("", pstr("./"))]))])); + c.__set_autoload(autoload(vec![("psr-4", str_map(&[("", pstr("foo/"))]))])); + + s.set_canonical_packages(vec![a.into(), b.into(), c.into()]); + + s.ensure_dir(&format!("{}/composer", s.vendor_dir)); + s.ensure_dir(&format!("{}/a/a/src", s.vendor_dir)); + s.ensure_dir(&format!("{}/b/b", s.vendor_dir)); + s.ensure_dir(&format!("{}/c/c/foo", s.vendor_dir)); + s.put( + &format!("{}/a/a/src/ClassMapFoo.php", s.vendor_dir), + " = IndexMap::new(); + expected.insert( + "ClassMapBar".to_string(), + format!("{}/b/b/ClassMapBar.php", vendor), + ); + expected.insert( + "ClassMapBaz".to_string(), + format!("{}/c/c/foo/ClassMapBaz.php", vendor), + ); + expected.insert( + "ClassMapFoo".to_string(), + format!("{}/a/a/src/ClassMapFoo.php", vendor), + ); + expected.insert( + "Composer\\InstalledVersions".to_string(), + format!("{}/composer/InstalledVersions.php", vendor), + ); + assert_eq!(&expected, class_map.get_map()); + assert_autoload_files("classmap8", &composer_out, "classmap"); + + let real = std::fs::read_to_string(format!("{}/autoload_real.php", composer_out)).unwrap(); + assert!(real.contains("$loader->setClassMapAuthoritative(true);")); + assert!(real.contains("$loader->setApcuPrefix('custom\\'Prefix');")); } #[test] -#[ignore = "setUp needs mocked Config/InstallationManager/InstalledRepositoryInterface and TestCase tmp-dir helpers; no mock infra exists"] +#[serial] +#[ignore = "autoload_real.php/autoload_static.php fixtures track a newer Composer template (single blank lines + $filesToLoad/$requireFile block) than the current AutoloadGenerator port emits; needs production template alignment"] fn test_files_autoload_generation() { - todo!() + let mut s = set_up(); + let package = new_root_pkg("root/a"); + package.set_autoload(autoload(vec![("files", str_list(&["root.php"]))])); + package.set_requires(requires(vec![ + ("a/a", link("a", "a/a", match_all(), None)), + ("b/b", link("a", "b/b", match_all(), None)), + ("c/c", link("a", "c/c", match_all(), None)), + ])); + + let a = new_pkg("a/a"); + let b = new_pkg("b/b"); + let c = new_pkg("c/c"); + a.__set_autoload(autoload(vec![("files", str_list(&["test.php"]))])); + b.__set_autoload(autoload(vec![("files", str_list(&["test2.php"]))])); + c.__set_autoload(autoload(vec![( + "files", + str_list(&["test3.php", "foo/bar/test4.php"]), + )])); + c.__set_target_dir(Some("foo/bar".to_string())); + + s.set_canonical_packages(vec![a.into(), b.into(), c.into()]); + + s.ensure_dir(&format!("{}/a/a", s.vendor_dir)); + s.ensure_dir(&format!("{}/b/b", s.vendor_dir)); + s.ensure_dir(&format!("{}/c/c/foo/bar", s.vendor_dir)); + s.put( + &format!("{}/a/a/test.php", s.vendor_dir), + " array($vendorDir . '/b/b/src'),\n 'A\\\\B' => array($baseDir . '/lib', $vendorDir . '/a/a/lib'),\n 'A' => array($vendorDir . '/a/a/src'),\n);\n"; + let expected_psr4 = " $baseDir . '/lib/A/B/C.php',\n 'Composer\\\\InstalledVersions' => $vendorDir . '/composer/InstalledVersions.php',\n 'Foo\\\\Bar' => $baseDir . '/src/classes.php',\n);\n"; + + assert_eq!( + expected_namespace, + std::fs::read_to_string(format!("{}/autoload_namespaces.php", composer_out)).unwrap() + ); + assert_eq!( + expected_psr4, + std::fs::read_to_string(format!("{}/autoload_psr4.php", composer_out)).unwrap() + ); + assert_eq!( + expected_classmap, + std::fs::read_to_string(format!("{}/autoload_classmap.php", composer_out)).unwrap() + ); } #[test] -#[ignore = "setUp needs mocked Config/InstallationManager/InstalledRepositoryInterface and TestCase tmp-dir helpers; no mock infra exists"] -fn test_files_autoload_order_by_dependencies() { - todo!() +#[serial] +fn test_include_path_file_generation() { + let mut s = set_up(); + let package = new_root_pkg("root/a"); + + let a = new_pkg("a/a"); + a.__set_include_paths(vec!["lib/".to_string()]); + let b = new_pkg("b/b"); + b.__set_include_paths(vec!["library".to_string()]); + let c = new_pkg("c"); + c.__set_include_paths(vec!["library".to_string()]); + + s.set_canonical_packages(vec![a.into(), b.into(), c.into()]); + + s.ensure_dir(&format!("{}/composer", s.vendor_dir)); + + let composer_out = format!("{}/composer", s.vendor_dir); + dump(&mut s, package.into(), false, "_10").unwrap(); + + assert_file_content_equals( + fixtures_dir().join("include_paths.php").to_str().unwrap(), + &format!("{}/include_paths.php", composer_out), + ); } #[test] -#[ignore = "setUp needs mocked Config/InstallationManager/InstalledRepositoryInterface and TestCase tmp-dir helpers; no mock infra exists"] -fn test_override_vendors_autoloading() { - todo!() +#[serial] +fn test_include_path_file_without_paths_is_skipped() { + let mut s = set_up(); + let package = new_root_pkg("root/a"); + let a = new_pkg("a/a"); + s.set_canonical_packages(vec![a.into()]); + + s.ensure_dir(&format!("{}/composer", s.vendor_dir)); + + let composer_out = format!("{}/composer", s.vendor_dir); + dump(&mut s, package.into(), false, "_12").unwrap(); + + assert!(!std::path::Path::new(&format!("{}/include_paths.php", composer_out)).exists()); } #[test] -#[ignore = "setUp needs mocked Config/InstallationManager/InstalledRepositoryInterface and TestCase tmp-dir helpers; no mock infra exists"] -fn test_include_path_file_generation() { - todo!() +#[serial] +fn test_vendor_substring_path() { + let mut s = set_up(); + let package = new_root_pkg("root/a"); + package.set_autoload(autoload(vec![ + ( + "psr-0", + str_map(&[("Foo", pstr("composer-test-autoload-src/src"))]), + ), + ( + "psr-4", + str_map(&[("Acme\\Foo\\", pstr("composer-test-autoload-src/src-psr4"))]), + ), + ])); + + s.ensure_dir(&format!("{}/a", s.vendor_dir)); + + let composer_out = format!("{}/composer", s.vendor_dir); + dump(&mut s, package.into(), false, "VendorSubstring").unwrap(); + + let expected_namespace = " array($baseDir . '/composer-test-autoload-src/src'),\n);\n"; + let expected_psr4 = " array($baseDir . '/composer-test-autoload-src/src-psr4'),\n);\n"; + + assert_eq!( + expected_namespace, + std::fs::read_to_string(format!("{}/autoload_namespaces.php", composer_out)).unwrap() + ); + assert_eq!( + expected_psr4, + std::fs::read_to_string(format!("{}/autoload_psr4.php", composer_out)).unwrap() + ); } #[test] -#[ignore = "setUp needs mocked Config/InstallationManager/InstalledRepositoryInterface and TestCase tmp-dir helpers; no mock infra exists"] -fn test_include_paths_are_prepended_in_autoload_file() { - todo!() +#[serial] +fn test_empty_paths() { + let mut s = set_up(); + let package = new_root_pkg("root/a"); + package.set_autoload(autoload(vec![ + ("psr-0", str_map(&[("Foo", pstr(""))])), + ("psr-4", str_map(&[("Acme\\Foo\\", pstr(""))])), + ("classmap", str_list(&[""])), + ])); + + s.ensure_dir(&format!("{}/Foo", s.working_dir)); + s.put( + &format!("{}/Foo/Bar.php", s.working_dir), + " array($baseDir . '/'),\n);\n"; + let expected_psr4 = " array($baseDir . '/'),\n);\n"; + let expected_classmap = " $baseDir . '/class.php',\n 'Composer\\\\InstalledVersions' => $vendorDir . '/composer/InstalledVersions.php',\n 'Foo\\\\Bar' => $baseDir . '/Foo/Bar.php',\n);\n"; + + assert_eq!( + expected_namespace, + std::fs::read_to_string(format!("{}/autoload_namespaces.php", composer_out)).unwrap() + ); + assert_eq!( + expected_psr4, + std::fs::read_to_string(format!("{}/autoload_psr4.php", composer_out)).unwrap() + ); + assert_eq!( + expected_classmap, + std::fs::read_to_string(format!("{}/autoload_classmap.php", composer_out)).unwrap() + ); } #[test] -#[ignore = "setUp needs mocked Config/InstallationManager/InstalledRepositoryInterface and TestCase tmp-dir helpers; no mock infra exists"] -fn test_include_paths_in_root_package() { - todo!() +#[serial] +#[ignore = "fixture assumes the symlinked composersrc/foo/bar tree (created via `ln -s` in PHP) and exercises exclude-from-classmap pattern matching that the port does not yet apply; symlink setup not replicated"] +fn test_exclude_from_classmap() { + let mut s = set_up(); + let package = new_root_pkg("root/a"); + package.set_autoload(autoload(vec![ + ( + "psr-0", + str_map(&[ + ("Main", pstr("src/")), + ("Lala", str_list(&["src/", "lib/"])), + ]), + ), + ( + "psr-4", + str_map(&[ + ("Acme\\Fruit\\", pstr("src-fruit/")), + ("Acme\\Cake\\", str_list(&["src-cake/", "lib-cake/"])), + ]), + ), + ("classmap", str_list(&["composersrc/"])), + ( + "exclude-from-classmap", + str_list(&[ + "/composersrc/foo/bar/", + "/composersrc/excludedTests/", + "/composersrc/ClassToExclude.php", + "/composersrc/*/excluded/excsubpath", + "**/excsubpath", + "composers", + "/src-ca/", + ]), + ), + ])); + + s.ensure_dir(&format!("{}/composer", s.working_dir)); + s.ensure_dir(&format!("{}/src/Lala/Test", s.working_dir)); + s.ensure_dir(&format!("{}/lib", s.working_dir)); + s.put( + &format!("{}/src/Lala/ClassMapMain.php", s.working_dir), + "