//! ref: composer/tests/Composer/Test/Autoload/AutoloadGeneratorTest.php 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, } #[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 )) } } /// 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 SetUp { fn drop(&mut self) { 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(); } } } 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] #[serial] fn test_root_package_autoloading() { 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] #[serial] fn test_vendors_autoloading() { 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] #[serial] fn test_vendors_autoloading_with_metapackages() { 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] #[serial] fn test_non_dev_autoload_exclusion_with_recursion() { 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] #[serial] fn test_non_dev_autoload_should_include_replaced_packages() { 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] #[serial] fn test_non_dev_autoload_exclusion_with_recursion_replace() { 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] #[serial] fn test_non_dev_autoload_replaces_nested_requirements() { 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] #[serial] fn test_psr_to_class_map_ignores_non_psr_classes() { 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".to_string(); let actual = std::fs::read_to_string(format!("{}/autoload_classmap.php", composer_out)).unwrap(); assert_eq!(expected, actual); } #[test] #[serial] fn test_vendors_class_map_autoloading() { 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] #[serial] fn test_vendors_class_map_autoloading_with_target_dir() { 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] #[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() { 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] #[serial] fn test_class_map_autoloading_authoritative_and_apcu() { 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] #[serial] fn test_class_map_autoloading_authoritative_and_apcu_prefix() { 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] #[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() { 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] #[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] #[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] #[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] #[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] #[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), "