aboutsummaryrefslogtreecommitdiffhomepage
diff options
context:
space:
mode:
authornsfisis <nsfisis@gmail.com>2026-06-24 00:47:20 +0900
committernsfisis <nsfisis@gmail.com>2026-06-24 00:47:53 +0900
commit340164c64e90d44b1bdf620b514166db1f76cc98 (patch)
tree86195fe5ff8582b64e0324e9f40619e36c04948f
parent8fe3390d064303b86133a1d2983144a4818a7121 (diff)
downloadphp-shirabe-340164c64e90d44b1bdf620b514166db1f76cc98.tar.gz
php-shirabe-340164c64e90d44b1bdf620b514166db1f76cc98.tar.zst
php-shirabe-340164c64e90d44b1bdf620b514166db1f76cc98.zip
test: port more unimplemented tests
Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
-rw-r--r--crates/shirabe-php-shim/src/fs.rs5
-rw-r--r--crates/shirabe-php-shim/src/lib.rs14
-rw-r--r--crates/shirabe-php-shim/src/stream.rs80
-rw-r--r--crates/shirabe-php-shim/src/zip.rs4
-rw-r--r--crates/shirabe/src/installed_versions.rs14
-rw-r--r--crates/shirabe/src/package/archiver/archivable_files_finder.rs2
-rw-r--r--crates/shirabe/src/package/handle.rs9
-rw-r--r--crates/shirabe/src/question/strict_confirmation_question.rs4
-rw-r--r--crates/shirabe/src/repository/package_repository.rs87
-rw-r--r--crates/shirabe/src/util/http/proxy_manager.rs13
-rw-r--r--crates/shirabe/tests/config_test.rs31
-rw-r--r--crates/shirabe/tests/dependency_resolver/default_policy_test.rs208
-rw-r--r--crates/shirabe/tests/dependency_resolver/security_advisory_pool_filter_test.rs317
-rw-r--r--crates/shirabe/tests/dependency_resolver/solver_test.rs8
-rw-r--r--crates/shirabe/tests/installed_versions_test.rs7
-rw-r--r--crates/shirabe/tests/installer/metapackage_installer_test.rs7
-rw-r--r--crates/shirabe/tests/package/archiver/archivable_files_finder_test.rs236
-rw-r--r--crates/shirabe/tests/package/archiver/archive_manager_test.rs10
-rw-r--r--crates/shirabe/tests/package/archiver/phar_archiver_test.rs125
-rw-r--r--crates/shirabe/tests/package/archiver/zip_archiver_test.rs176
-rw-r--r--crates/shirabe/tests/package/base_package_test.rs41
-rw-r--r--crates/shirabe/tests/package/dumper/array_dumper_test.rs13
-rw-r--r--crates/shirabe/tests/platform/hhvm_detector_test.rs27
-rw-r--r--crates/shirabe/tests/question/strict_confirmation_question_test.rs133
-rw-r--r--crates/shirabe/tests/repository/artifact_repository_test.rs19
-rw-r--r--crates/shirabe/tests/util/filesystem_test.rs47
-rw-r--r--crates/shirabe/tests/util/http/proxy_manager_test.rs25
-rw-r--r--crates/shirabe/tests/util/stream_context_factory_test.rs299
28 files changed, 1775 insertions, 186 deletions
diff --git a/crates/shirabe-php-shim/src/fs.rs b/crates/shirabe-php-shim/src/fs.rs
index ecbe5c4..f12d7bf 100644
--- a/crates/shirabe-php-shim/src/fs.rs
+++ b/crates/shirabe-php-shim/src/fs.rs
@@ -260,8 +260,11 @@ pub fn fopen(file: &str, mode: &str) -> Result<PhpResource, std::io::Error> {
StreamBacking::Memory(std::io::Cursor::new(Vec::new())),
readable,
writable,
+ mode.to_string(),
+ file.to_string(),
));
}
+ let uri = file.to_string();
let mut options = std::fs::OpenOptions::new();
match base_mode.as_str() {
"r" => options.read(true),
@@ -282,6 +285,8 @@ pub fn fopen(file: &str, mode: &str) -> Result<PhpResource, std::io::Error> {
StreamBacking::File(file),
readable,
writable,
+ mode.to_string(),
+ uri,
))
}
diff --git a/crates/shirabe-php-shim/src/lib.rs b/crates/shirabe-php-shim/src/lib.rs
index 267c3b7..8eb8f2f 100644
--- a/crates/shirabe-php-shim/src/lib.rs
+++ b/crates/shirabe-php-shim/src/lib.rs
@@ -383,16 +383,28 @@ pub struct StreamState {
/// only reports true after a read has hit the end; cleared by a seek.
pub(crate) eof: bool,
pub(crate) closed: bool,
+ /// The mode string passed to `fopen`, reported back by `stream_get_meta_data`.
+ pub(crate) mode: String,
+ /// The path/URI the stream was opened from, reported back by `stream_get_meta_data`.
+ pub(crate) uri: String,
}
impl StreamState {
- pub(crate) fn new(backing: StreamBacking, readable: bool, writable: bool) -> PhpResource {
+ pub(crate) fn new(
+ backing: StreamBacking,
+ readable: bool,
+ writable: bool,
+ mode: String,
+ uri: String,
+ ) -> PhpResource {
PhpResource::Stream(std::rc::Rc::new(std::cell::RefCell::new(StreamState {
backing,
readable,
writable,
eof: false,
closed: false,
+ mode,
+ uri,
})))
}
}
diff --git a/crates/shirabe-php-shim/src/stream.rs b/crates/shirabe-php-shim/src/stream.rs
index 44ba0a4..5e43316 100644
--- a/crates/shirabe-php-shim/src/stream.rs
+++ b/crates/shirabe-php-shim/src/stream.rs
@@ -67,6 +67,14 @@ pub fn stream_context_create(
todo!()
}
+pub fn stream_context_get_options(_stream_or_context: &PhpMixed) -> IndexMap<String, PhpMixed> {
+ todo!()
+}
+
+pub fn stream_context_get_params(_stream_or_context: &PhpMixed) -> IndexMap<String, PhpMixed> {
+ todo!()
+}
+
pub fn stream_isatty(stream: PhpResource) -> bool {
stream_isatty_resource(&stream)
}
@@ -124,8 +132,76 @@ pub fn stream_isatty_resource(resource: &PhpResource) -> bool {
}
}
-pub fn stream_get_meta_data(_resource: &PhpResource) -> IndexMap<String, PhpMixed> {
- todo!()
+pub fn stream_get_meta_data(resource: &PhpResource) -> IndexMap<String, PhpMixed> {
+ // (timed_out, blocked, eof, wrapper_type, stream_type, mode, seekable, uri)
+ let (eof, wrapper_type, stream_type, mode, seekable, uri) = match resource {
+ PhpResource::Stdin => (false, "PHP", "STDIO", "r".to_string(), false, "php://stdin"),
+ PhpResource::Stdout => (
+ false,
+ "PHP",
+ "STDIO",
+ "w".to_string(),
+ false,
+ "php://stdout",
+ ),
+ PhpResource::Stderr => (
+ false,
+ "PHP",
+ "STDIO",
+ "w".to_string(),
+ false,
+ "php://stderr",
+ ),
+ PhpResource::Stream(state) => {
+ let state = state.borrow();
+ let (wrapper_type, stream_type) = match &state.backing {
+ StreamBacking::Memory(_) => {
+ if state.uri.starts_with("php://temp") {
+ ("PHP", "TEMP")
+ } else {
+ ("PHP", "MEMORY")
+ }
+ }
+ StreamBacking::File(_) => ("plainfile", "STDIO"),
+ };
+ return build_meta_data(
+ state.eof,
+ wrapper_type,
+ stream_type,
+ state.mode.clone(),
+ true,
+ &state.uri,
+ );
+ }
+ };
+ build_meta_data(eof, wrapper_type, stream_type, mode, seekable, uri)
+}
+
+fn build_meta_data(
+ eof: bool,
+ wrapper_type: &str,
+ stream_type: &str,
+ mode: String,
+ seekable: bool,
+ uri: &str,
+) -> IndexMap<String, PhpMixed> {
+ let mut map = IndexMap::new();
+ map.insert("timed_out".to_string(), PhpMixed::Bool(false));
+ map.insert("blocked".to_string(), PhpMixed::Bool(true));
+ map.insert("eof".to_string(), PhpMixed::Bool(eof));
+ map.insert(
+ "wrapper_type".to_string(),
+ PhpMixed::String(wrapper_type.to_string()),
+ );
+ map.insert(
+ "stream_type".to_string(),
+ PhpMixed::String(stream_type.to_string()),
+ );
+ map.insert("mode".to_string(), PhpMixed::String(mode));
+ map.insert("unread_bytes".to_string(), PhpMixed::Int(0));
+ map.insert("seekable".to_string(), PhpMixed::Bool(seekable));
+ map.insert("uri".to_string(), PhpMixed::String(uri.to_string()));
+ map
}
pub fn stream_set_blocking(_resource: &PhpResource, _enable: bool) -> bool {
diff --git a/crates/shirabe-php-shim/src/zip.rs b/crates/shirabe-php-shim/src/zip.rs
index 5ef99bb..08662e6 100644
--- a/crates/shirabe-php-shim/src/zip.rs
+++ b/crates/shirabe-php-shim/src/zip.rs
@@ -49,6 +49,10 @@ impl ZipArchive {
todo!()
}
+ pub fn get_from_name(&self, _name: &str) -> Option<String> {
+ todo!()
+ }
+
pub fn get_stream(&self, _name: &str) -> Option<crate::PhpResource> {
todo!()
}
diff --git a/crates/shirabe/src/installed_versions.rs b/crates/shirabe/src/installed_versions.rs
index c4ef526..33286ad 100644
--- a/crates/shirabe/src/installed_versions.rs
+++ b/crates/shirabe/src/installed_versions.rs
@@ -352,6 +352,20 @@ impl InstalledVersions {
/// Returns the raw data of all installed.php which are currently loaded for custom implementations
///
/// @return array[]
+ /// Returns the first dataset loaded, which may not be what you expect. Use get_all_raw_data
+ /// instead, which returns all datasets for all autoloaders present in the process.
+ pub fn get_raw_data() -> IndexMap<String, PhpMixed> {
+ // PHP emits an E_USER_DEPRECATED notice here; there is no Rust equivalent.
+ let mut installed = INSTALLED.lock().unwrap();
+ if installed.is_none() {
+ // PHP only includes __DIR__/installed.php when loaded from its dumped location; the
+ // shim is always the source location (PHP's `else` branch), so the data is empty.
+ *installed = Some(IndexMap::new());
+ }
+
+ installed.clone().unwrap()
+ }
+
pub fn get_all_raw_data() -> Vec<IndexMap<String, PhpMixed>> {
Self::get_installed()
}
diff --git a/crates/shirabe/src/package/archiver/archivable_files_finder.rs b/crates/shirabe/src/package/archiver/archivable_files_finder.rs
index 08f8df1..67f2fda 100644
--- a/crates/shirabe/src/package/archiver/archivable_files_finder.rs
+++ b/crates/shirabe/src/package/archiver/archivable_files_finder.rs
@@ -57,7 +57,7 @@ impl ArchivableFilesFinder {
}
let relative_path = Preg::replace(
- &format!("^{}", preg_quote(&sources_clone, Some('#'))),
+ &format!("#^{}#", preg_quote(&sources_clone, Some('#'))),
"",
&fs.normalize_path(&realpath.to_string_lossy()),
);
diff --git a/crates/shirabe/src/package/handle.rs b/crates/shirabe/src/package/handle.rs
index 303983a..665efb6 100644
--- a/crates/shirabe/src/package/handle.rs
+++ b/crates/shirabe/src/package/handle.rs
@@ -1213,6 +1213,15 @@ macro_rules! impl_real_package_test_setters {
.expect("real package handle invariant")
.set_type(r#type);
}
+
+ /// For testing only: mirrors PHP `Package::setSourceType`.
+ pub fn __set_source_type(&self, r#type: Option<String>) {
+ self.0
+ .borrow_mut()
+ .as_package_mut()
+ .expect("real package handle invariant")
+ .set_source_type(r#type);
+ }
}
};
}
diff --git a/crates/shirabe/src/question/strict_confirmation_question.rs b/crates/shirabe/src/question/strict_confirmation_question.rs
index 87290ac..8d4239b 100644
--- a/crates/shirabe/src/question/strict_confirmation_question.rs
+++ b/crates/shirabe/src/question/strict_confirmation_question.rs
@@ -35,6 +35,10 @@ impl StrictConfirmationQuestion {
&self.inner
}
+ pub fn inner_mut(&mut self) -> &mut Question {
+ &mut self.inner
+ }
+
fn get_default_normalizer(&self) -> Box<dyn Fn(PhpMixed) -> PhpMixed> {
let default = self.inner.get_default();
let true_regex = self.true_answer_regex.clone();
diff --git a/crates/shirabe/src/repository/package_repository.rs b/crates/shirabe/src/repository/package_repository.rs
index 554e24a..e19d04c 100644
--- a/crates/shirabe/src/repository/package_repository.rs
+++ b/crates/shirabe/src/repository/package_repository.rs
@@ -2,12 +2,18 @@
use crate::advisory::SecurityAdvisory;
use crate::advisory::{AnySecurityAdvisory, PartialSecurityAdvisory};
+use crate::package::BasePackageHandle;
+use crate::package::PackageInterfaceHandle;
use crate::package::loader::ArrayLoader;
use crate::package::loader::ValidatingArrayLoader;
use crate::package::version::VersionParser;
use crate::repository::ArrayRepository;
use crate::repository::InvalidRepositoryException;
-use crate::repository::{AdvisoryProviderInterface, SecurityAdvisoryResult};
+use crate::repository::RepositoryInterfaceWeakHandle;
+use crate::repository::{
+ AdvisoryProviderInterface, FindPackageConstraint, LoadPackagesResult, ProviderInfo,
+ RepositoryInterface, SearchResult, SecurityAdvisoryResult,
+};
use indexmap::IndexMap;
use shirabe_external_packages::composer::pcre::Preg;
use shirabe_php_shim::{Exception, PhpMixed, RuntimeException, var_export};
@@ -80,6 +86,85 @@ impl PackageRepository {
}
}
+impl RepositoryInterface for PackageRepository {
+ // The structural methods are inherited from ArrayRepository in PHP, where they trigger the
+ // overridden initialize() that loads packages from config. Wiring that virtual dispatch is a
+ // Phase C concern; the advisory paths below are what is exercised so far.
+ fn count(&self) -> anyhow::Result<usize> {
+ todo!()
+ }
+
+ fn has_package(&self, _package: PackageInterfaceHandle) -> bool {
+ todo!()
+ }
+
+ fn find_package(
+ &mut self,
+ _name: &str,
+ _constraint: FindPackageConstraint,
+ ) -> anyhow::Result<Option<BasePackageHandle>> {
+ todo!()
+ }
+
+ fn find_packages(
+ &mut self,
+ _name: &str,
+ _constraint: Option<FindPackageConstraint>,
+ ) -> anyhow::Result<Vec<BasePackageHandle>> {
+ todo!()
+ }
+
+ fn get_packages(&mut self) -> anyhow::Result<Vec<BasePackageHandle>> {
+ todo!()
+ }
+
+ fn load_packages(
+ &mut self,
+ _package_name_map: IndexMap<String, Option<shirabe_semver::constraint::AnyConstraint>>,
+ _acceptable_stabilities: IndexMap<String, i64>,
+ _stability_flags: IndexMap<String, i64>,
+ _already_loaded: IndexMap<String, IndexMap<String, PackageInterfaceHandle>>,
+ ) -> anyhow::Result<LoadPackagesResult> {
+ todo!()
+ }
+
+ fn search(
+ &mut self,
+ _query: String,
+ _mode: i64,
+ _type: Option<String>,
+ ) -> anyhow::Result<Vec<SearchResult>> {
+ todo!()
+ }
+
+ fn get_providers(
+ &mut self,
+ _package_name: String,
+ ) -> anyhow::Result<IndexMap<String, ProviderInfo>> {
+ todo!()
+ }
+
+ fn get_repo_name(&self) -> String {
+ PackageRepository::get_repo_name(self)
+ }
+
+ fn as_advisory_provider(&self) -> Option<&dyn AdvisoryProviderInterface> {
+ Some(self)
+ }
+
+ fn as_advisory_provider_mut(&mut self) -> Option<&mut dyn AdvisoryProviderInterface> {
+ Some(self)
+ }
+
+ fn as_any(&self) -> &dyn std::any::Any {
+ self
+ }
+
+ fn set_self_handle(&self, weak: RepositoryInterfaceWeakHandle) {
+ self.inner.set_self_handle(weak);
+ }
+}
+
impl AdvisoryProviderInterface for PackageRepository {
fn has_security_advisories(&mut self) -> anyhow::Result<bool> {
Ok(!self.security_advisories.is_empty())
diff --git a/crates/shirabe/src/util/http/proxy_manager.rs b/crates/shirabe/src/util/http/proxy_manager.rs
index 37d73c0..d0ff9d9 100644
--- a/crates/shirabe/src/util/http/proxy_manager.rs
+++ b/crates/shirabe/src/util/http/proxy_manager.rs
@@ -1,5 +1,6 @@
//! ref: composer/src/Composer/Util/Http/ProxyManager.php
+use std::sync::atomic::{AtomicU64, Ordering};
use std::sync::{Mutex, OnceLock};
use crate::downloader::TransportException;
@@ -9,12 +10,17 @@ use crate::util::http::RequestProxy;
static INSTANCE: OnceLock<Mutex<Option<ProxyManager>>> = OnceLock::new();
+// Distinguishes ProxyManager instances so tests can mirror PHP `===` identity of the singleton,
+// which the Rust value-based singleton does not otherwise expose.
+static NEXT_GENERATION: AtomicU64 = AtomicU64::new(0);
+
#[derive(Debug)]
pub struct ProxyManager {
error: Option<String>,
http_proxy: Option<ProxyItem>,
https_proxy: Option<ProxyItem>,
no_proxy_handler: std::cell::RefCell<Option<NoProxyPattern>>,
+ generation: u64,
}
impl ProxyManager {
@@ -24,6 +30,7 @@ impl ProxyManager {
http_proxy: None,
https_proxy: None,
no_proxy_handler: std::cell::RefCell::new(None),
+ generation: NEXT_GENERATION.fetch_add(1, Ordering::Relaxed),
};
if let Err(e) = instance.get_proxy_data() {
instance.error = Some(e.to_string());
@@ -41,6 +48,12 @@ impl ProxyManager {
}
}
+ /// For testing only: a unique id per constructed instance, used to mirror PHP `===` identity
+ /// comparison of the ProxyManager singleton across `get_instance`/`reset`.
+ pub fn __generation(&self) -> u64 {
+ self.generation
+ }
+
pub fn has_proxy(&self) -> bool {
self.http_proxy.is_some() || self.https_proxy.is_some()
}
diff --git a/crates/shirabe/tests/config_test.rs b/crates/shirabe/tests/config_test.rs
index fb40cac..75e1a24 100644
--- a/crates/shirabe/tests/config_test.rs
+++ b/crates/shirabe/tests/config_test.rs
@@ -486,10 +486,37 @@ fn test_prohibited_urls_throw_exception() {
}
}
-#[ignore = "requires getIOMock with expects() output expectations; no IO mocking infrastructure exists"]
+// PHP asserts the warning via getIOMock()->expects(); here a real BufferIO captures the output
+// instead. The case stays #[ignore] because BufferIO::get_output is todo!() (its PhpResource
+// stream model is unfinished).
+#[ignore = "BufferIO::get_output is todo!() (PhpResource stream model unfinished)"]
#[test]
fn test_prohibited_urls_warning_verify_peer() {
- todo!()
+ let io = std::rc::Rc::new(std::cell::RefCell::new(
+ shirabe::io::buffer_io::BufferIO::new(
+ String::new(),
+ shirabe_external_packages::symfony::console::output::output_interface::VERBOSITY_NORMAL,
+ None,
+ )
+ .unwrap(),
+ ));
+
+ let mut config = Config::new(false, None);
+
+ let mut ssl: IndexMap<String, PhpMixed> = IndexMap::new();
+ ssl.insert("verify_peer".to_string(), PhpMixed::Bool(false));
+ ssl.insert("verify_peer_name".to_string(), PhpMixed::Bool(false));
+ let mut repo_options: IndexMap<String, PhpMixed> = IndexMap::new();
+ repo_options.insert("ssl".to_string(), PhpMixed::Array(ssl));
+
+ config
+ .prohibit_url_by_config("https://example.org", Some(io.clone()), &repo_options)
+ .unwrap();
+
+ assert_eq!(
+ "<warning>Warning: Accessing example.org with verify_peer and verify_peer_name disabled.</warning>",
+ io.borrow().get_output()
+ );
}
#[ignore]
diff --git a/crates/shirabe/tests/dependency_resolver/default_policy_test.rs b/crates/shirabe/tests/dependency_resolver/default_policy_test.rs
index 776de6c..fafe0e2 100644
--- a/crates/shirabe/tests/dependency_resolver/default_policy_test.rs
+++ b/crates/shirabe/tests/dependency_resolver/default_policy_test.rs
@@ -3,6 +3,7 @@
use indexmap::IndexMap;
use shirabe::dependency_resolver::PolicyInterface;
use shirabe::dependency_resolver::default_policy::DefaultPolicy;
+use shirabe::package::Link;
use shirabe::package::handle::{CompleteAliasPackageHandle, CompletePackageHandle};
use shirabe::repository::array_repository::ArrayRepository;
use shirabe::repository::handle::{LockArrayRepositoryHandle, RepositoryInterfaceHandle};
@@ -556,28 +557,217 @@ fn test_select_local_repos_first() {
assert_eq!(expected, selected);
}
-#[ignore = "set_provides/set_replaces only exist on RootPackage handles; CompletePackage from get_package has no set_provides"]
+/// PHP `new Link($source, $target, $constraint, $type)`: prettyConstraint defaults to
+/// `(string) $constraint`.
+fn link(source: &str, target: &str, constraint: AnyConstraint, r#type: &str) -> Link {
+ let pretty = constraint.get_pretty_string();
+ Link::new(
+ source.to_string(),
+ target.to_string(),
+ constraint,
+ Some(r#type.to_string()),
+ pretty,
+ )
+}
+
+fn as_complete(
+ package: &shirabe::package::handle::PackageInterfaceHandle,
+) -> CompletePackageHandle {
+ CompletePackageHandle::from_rc_unchecked(package.as_rc().clone())
+}
+
+fn constraint(operator: &str, version: &str) -> AnyConstraint {
+ SimpleConstraint::new(operator.to_string(), version.to_string(), None).into()
+}
+
+#[ignore]
#[test]
fn test_select_all_providers() {
let _tear_down = TearDown;
- let _fixtures = set_up();
- todo!()
+ let mut fixtures = set_up();
+
+ let package_a = get_package("A", "1.0");
+ fixtures.repo.add_package(package_a.clone()).unwrap();
+ let package_b = get_package("B", "2.0");
+ fixtures.repo.add_package(package_b.clone()).unwrap();
+
+ let mut provides_a: IndexMap<String, Link> = IndexMap::new();
+ provides_a.insert(
+ "x".to_string(),
+ link("A", "X", constraint("==", "1.0"), Link::TYPE_PROVIDE),
+ );
+ as_complete(&package_a).__set_provides(provides_a);
+ let mut provides_b: IndexMap<String, Link> = IndexMap::new();
+ provides_b.insert(
+ "x".to_string(),
+ link("B", "X", constraint("==", "1.0"), Link::TYPE_PROVIDE),
+ );
+ as_complete(&package_b).__set_provides(provides_b);
+
+ fixtures
+ .repository_set
+ .add_repository(RepositoryInterfaceHandle::new(fixtures.repo))
+ .unwrap();
+
+ let pool = fixtures
+ .repository_set
+ .create_pool_for_packages(
+ vec!["A".to_string(), "B".to_string()],
+ Some(fixtures.repo_locked.clone()),
+ )
+ .unwrap();
+
+ let literals = vec![package_a.get_id(), package_b.get_id()];
+ let expected = literals.clone();
+
+ let selected = fixtures
+ .policy
+ .select_preferred_packages(&pool, literals, None);
+
+ assert_eq!(expected, selected);
}
-#[ignore = "set_provides/set_replaces only exist on RootPackage handles; CompletePackage from get_package has no set_replaces"]
+#[ignore]
#[test]
fn test_prefer_non_replacing_from_same_repo() {
let _tear_down = TearDown;
- let _fixtures = set_up();
- todo!()
+ let mut fixtures = set_up();
+
+ let package_a = get_package("A", "1.0");
+ fixtures.repo.add_package(package_a.clone()).unwrap();
+ let package_b = get_package("B", "2.0");
+ fixtures.repo.add_package(package_b.clone()).unwrap();
+
+ let mut replaces_b: IndexMap<String, Link> = IndexMap::new();
+ replaces_b.insert(
+ "a".to_string(),
+ link("B", "A", constraint("==", "1.0"), Link::TYPE_REPLACE),
+ );
+ as_complete(&package_b).__set_replaces(replaces_b);
+
+ fixtures
+ .repository_set
+ .add_repository(RepositoryInterfaceHandle::new(fixtures.repo))
+ .unwrap();
+
+ let pool = fixtures
+ .repository_set
+ .create_pool_for_packages(
+ vec!["A".to_string(), "B".to_string()],
+ Some(fixtures.repo_locked.clone()),
+ )
+ .unwrap();
+
+ let literals = vec![package_a.get_id(), package_b.get_id()];
+ let expected = literals.clone();
+
+ let selected = fixtures
+ .policy
+ .select_preferred_packages(&pool, literals, None);
+
+ assert_eq!(expected, selected);
}
-#[ignore = "set_replaces only exists on RootPackage handles; CompletePackage from get_package has no set_replaces"]
+#[ignore]
#[test]
fn test_prefer_replacing_package_from_same_vendor() {
let _tear_down = TearDown;
- let _fixtures = set_up();
- todo!()
+ let mut fixtures = set_up();
+
+ // test with default order
+ let package_b = get_package("vendor-b/replacer", "1.0");
+ fixtures.repo.add_package(package_b.clone()).unwrap();
+ let package_a = get_package("vendor-a/replacer", "1.0");
+ fixtures.repo.add_package(package_a.clone()).unwrap();
+
+ let mut replaces_a: IndexMap<String, Link> = IndexMap::new();
+ replaces_a.insert(
+ "vendor-a/package".to_string(),
+ link(
+ "vendor-a/replacer",
+ "vendor-a/package",
+ constraint("==", "1.0"),
+ Link::TYPE_REPLACE,
+ ),
+ );
+ as_complete(&package_a).__set_replaces(replaces_a);
+ let mut replaces_b: IndexMap<String, Link> = IndexMap::new();
+ replaces_b.insert(
+ "vendor-a/package".to_string(),
+ link(
+ "vendor-b/replacer",
+ "vendor-a/package",
+ constraint("==", "1.0"),
+ Link::TYPE_REPLACE,
+ ),
+ );
+ as_complete(&package_b).__set_replaces(replaces_b);
+
+ fixtures
+ .repository_set
+ .add_repository(RepositoryInterfaceHandle::new(fixtures.repo))
+ .unwrap();
+
+ let pool = fixtures
+ .repository_set
+ .create_pool_for_packages(
+ vec![
+ "vendor-a/replacer".to_string(),
+ "vendor-b/replacer".to_string(),
+ ],
+ Some(fixtures.repo_locked.clone()),
+ )
+ .unwrap();
+
+ let literals = vec![package_a.get_id(), package_b.get_id()];
+ let expected = literals.clone();
+
+ let selected = fixtures.policy.select_preferred_packages(
+ &pool,
+ literals,
+ Some("vendor-a/package".to_string()),
+ );
+ assert_eq!(expected, selected);
+
+ // test with reversed order in repo
+ let repo = ArrayRepository::new(vec![]).unwrap();
+ let package_a = CompletePackageHandle::dup(&as_complete(&package_a));
+ repo.add_package(package_a.clone().into()).unwrap();
+ let package_b = CompletePackageHandle::dup(&as_complete(&package_b));
+ repo.add_package(package_b.clone().into()).unwrap();
+
+ let mut repository_set = RepositorySet::new(
+ "dev",
+ IndexMap::new(),
+ vec![],
+ IndexMap::new(),
+ IndexMap::new(),
+ IndexMap::new(),
+ );
+ repository_set
+ .add_repository(RepositoryInterfaceHandle::new(repo))
+ .unwrap();
+
+ let pool = fixtures
+ .repository_set
+ .create_pool_for_packages(
+ vec![
+ "vendor-a/replacer".to_string(),
+ "vendor-b/replacer".to_string(),
+ ],
+ Some(fixtures.repo_locked.clone()),
+ )
+ .unwrap();
+
+ let literals = vec![package_a.get_id(), package_b.get_id()];
+ let expected = literals.clone();
+
+ let selected = fixtures.policy.select_preferred_packages(
+ &pool,
+ literals,
+ Some("vendor-a/package".to_string()),
+ );
+ assert_eq!(expected, selected);
}
#[ignore]
diff --git a/crates/shirabe/tests/dependency_resolver/security_advisory_pool_filter_test.rs b/crates/shirabe/tests/dependency_resolver/security_advisory_pool_filter_test.rs
index 98ac131..7938657 100644
--- a/crates/shirabe/tests/dependency_resolver/security_advisory_pool_filter_test.rs
+++ b/crates/shirabe/tests/dependency_resolver/security_advisory_pool_filter_test.rs
@@ -12,24 +12,272 @@ use shirabe::dependency_resolver::SecurityAdvisoryPoolFilter;
use shirabe::dependency_resolver::pool::Pool;
use shirabe::dependency_resolver::request::Request;
use shirabe::package::handle::{CompletePackageHandle, PackageHandle, PackageInterfaceHandle};
-use shirabe_php_shim::PhpMixed;
+use shirabe::repository::{PackageRepository, RepositoryInterfaceHandle};
+use shirabe_php_shim::{PhpMixed, uniqid};
+use shirabe_semver::constraint::{AnyConstraint, SimpleConstraint};
+#[ignore]
#[test]
-#[ignore = "requires PackageRepository to implement RepositoryInterface (filter takes Vec<RepositoryInterfaceHandle>) and php-shim uniqid() used by generateSecurityAdvisory; neither exists"]
fn test_filter_packages_by_advisories() {
- todo!()
+ let audit_config = AuditConfig::new(
+ true,
+ Auditor::FORMAT_SUMMARY.to_string(),
+ Auditor::ABANDONED_FAIL.to_string(),
+ true,
+ true,
+ false,
+ IndexMap::new(),
+ IndexMap::new(),
+ IndexMap::new(),
+ IndexMap::new(),
+ IndexMap::new(),
+ IndexMap::new(),
+ );
+ let filter = SecurityAdvisoryPoolFilter::new(Auditor, audit_config);
+
+ let advisory1 =
+ generate_security_advisory("acme/package", Some("CVE-1999-1000"), ">=1.0.0,<1.1.0");
+ let advisory2 =
+ generate_security_advisory("acme/package", Some("CVE-1999-1001"), ">=1.0.0,<1.1.0");
+ let mut security_advisories: IndexMap<String, PhpMixed> = IndexMap::new();
+ security_advisories.insert(
+ "acme/package".to_string(),
+ PhpMixed::List(vec![
+ PhpMixed::Array(advisory1.clone()),
+ PhpMixed::Array(advisory2.clone()),
+ ]),
+ );
+ let mut config: IndexMap<String, PhpMixed> = IndexMap::new();
+ config.insert("package".to_string(), PhpMixed::List(vec![]));
+ config.insert(
+ "security-advisories".to_string(),
+ PhpMixed::Array(security_advisories),
+ );
+ let repository = RepositoryInterfaceHandle::new(PackageRepository::new(config));
+
+ let package: PackageInterfaceHandle = PackageHandle::new(
+ "acme/package".to_string(),
+ "1.0.0.0".to_string(),
+ "1.0".to_string(),
+ )
+ .into();
+ let expected_package1: PackageInterfaceHandle = PackageHandle::new(
+ "acme/package".to_string(),
+ "2.0.0.0".to_string(),
+ "2.0".to_string(),
+ )
+ .into();
+ let expected_package2: PackageInterfaceHandle = PackageHandle::new(
+ "acme/other".to_string(),
+ "1.0.0.0".to_string(),
+ "1.0".to_string(),
+ )
+ .into();
+
+ let pool = Pool::new(
+ vec![
+ package.clone(),
+ expected_package1.clone(),
+ expected_package2.clone(),
+ ],
+ vec![],
+ IndexMap::new(),
+ IndexMap::new(),
+ IndexMap::new(),
+ IndexMap::new(),
+ );
+ let filtered_pool = filter
+ .filter(pool, vec![repository], &Request::new(None))
+ .unwrap();
+
+ let packages = filtered_pool.get_packages();
+ assert_eq!(packages.len(), 2);
+ assert!(packages[0].ptr_eq(&expected_package1));
+ assert!(packages[1].ptr_eq(&expected_package2));
+
+ let constraint: AnyConstraint =
+ SimpleConstraint::new("==".to_string(), "1.0.0.0".to_string(), None).into();
+ assert!(filtered_pool.is_security_removed_package_version("acme/package", Some(&constraint)));
+ assert_eq!(
+ filtered_pool
+ .get_all_abandoned_removed_package_versions()
+ .len(),
+ 0
+ );
+
+ let advisory_map = filtered_pool.get_all_security_removed_package_versions();
+ assert!(advisory_map.contains_key("acme/package"));
+ assert!(advisory_map["acme/package"].contains_key("1.0.0.0"));
+ assert_eq!(
+ vec![
+ advisory1["advisoryId"].as_string().unwrap().to_string(),
+ advisory2["advisoryId"].as_string().unwrap().to_string(),
+ ],
+ filtered_pool.get_security_advisory_identifiers_for_package_version(
+ "acme/package",
+ Some(&constraint)
+ ),
+ );
}
+#[ignore]
#[test]
-#[ignore = "requires PackageRepository to implement RepositoryInterface (filter takes Vec<RepositoryInterfaceHandle>) and php-shim uniqid() used by generateSecurityAdvisory; neither exists"]
fn test_dont_filter_packages_by_ignored_advisories() {
- todo!()
+ let mut ignore_list: IndexMap<String, Option<String>> = IndexMap::new();
+ ignore_list.insert("CVE-2024-1234".to_string(), None);
+ let audit_config = AuditConfig::new(
+ true,
+ Auditor::FORMAT_SUMMARY.to_string(),
+ Auditor::ABANDONED_FAIL.to_string(),
+ true,
+ true,
+ false,
+ ignore_list.clone(),
+ ignore_list,
+ IndexMap::new(),
+ IndexMap::new(),
+ IndexMap::new(),
+ IndexMap::new(),
+ );
+ let filter = SecurityAdvisoryPoolFilter::new(Auditor, audit_config);
+
+ let mut security_advisories: IndexMap<String, PhpMixed> = IndexMap::new();
+ security_advisories.insert(
+ "acme/package".to_string(),
+ PhpMixed::List(vec![PhpMixed::Array(generate_security_advisory(
+ "acme/package",
+ Some("CVE-2024-1234"),
+ ">=1.0.0,<1.1.0",
+ ))]),
+ );
+ let mut config: IndexMap<String, PhpMixed> = IndexMap::new();
+ config.insert("package".to_string(), PhpMixed::List(vec![]));
+ config.insert(
+ "security-advisories".to_string(),
+ PhpMixed::Array(security_advisories),
+ );
+ let repository = RepositoryInterfaceHandle::new(PackageRepository::new(config));
+
+ let expected_package1: PackageInterfaceHandle = PackageHandle::new(
+ "acme/package".to_string(),
+ "1.0.0.0".to_string(),
+ "1.0".to_string(),
+ )
+ .into();
+ let expected_package2: PackageInterfaceHandle = PackageHandle::new(
+ "acme/package".to_string(),
+ "1.1.0.0".to_string(),
+ "1.1".to_string(),
+ )
+ .into();
+
+ let pool = Pool::new(
+ vec![expected_package1.clone(), expected_package2.clone()],
+ vec![],
+ IndexMap::new(),
+ IndexMap::new(),
+ IndexMap::new(),
+ IndexMap::new(),
+ );
+ let filtered_pool = filter
+ .filter(pool, vec![repository], &Request::new(None))
+ .unwrap();
+
+ let packages = filtered_pool.get_packages();
+ assert_eq!(packages.len(), 2);
+ assert!(packages[0].ptr_eq(&expected_package1));
+ assert!(packages[1].ptr_eq(&expected_package2));
+ assert_eq!(
+ filtered_pool
+ .get_all_abandoned_removed_package_versions()
+ .len(),
+ 0
+ );
+ assert_eq!(
+ filtered_pool
+ .get_all_security_removed_package_versions()
+ .len(),
+ 0
+ );
}
+#[ignore]
#[test]
-#[ignore = "requires PackageRepository to implement RepositoryInterface (filter takes Vec<RepositoryInterfaceHandle>) and php-shim uniqid() used by generateSecurityAdvisory; neither exists"]
fn test_dont_filter_packages_with_block_insecure_disabled() {
- todo!()
+ let audit_config = AuditConfig::new(
+ true,
+ Auditor::FORMAT_SUMMARY.to_string(),
+ Auditor::ABANDONED_FAIL.to_string(),
+ false,
+ true,
+ false,
+ IndexMap::new(),
+ IndexMap::new(),
+ IndexMap::new(),
+ IndexMap::new(),
+ IndexMap::new(),
+ IndexMap::new(),
+ );
+ let filter = SecurityAdvisoryPoolFilter::new(Auditor, audit_config);
+
+ let mut security_advisories: IndexMap<String, PhpMixed> = IndexMap::new();
+ security_advisories.insert(
+ "acme/package".to_string(),
+ PhpMixed::List(vec![PhpMixed::Array(generate_security_advisory(
+ "acme/package",
+ Some("CVE-2024-1234"),
+ ">=1.0.0,<1.1.0",
+ ))]),
+ );
+ let mut config: IndexMap<String, PhpMixed> = IndexMap::new();
+ config.insert("package".to_string(), PhpMixed::List(vec![]));
+ config.insert(
+ "security-advisories".to_string(),
+ PhpMixed::Array(security_advisories),
+ );
+ let repository = RepositoryInterfaceHandle::new(PackageRepository::new(config));
+
+ let expected_package1: PackageInterfaceHandle = PackageHandle::new(
+ "acme/package".to_string(),
+ "1.0.0.0".to_string(),
+ "1.0".to_string(),
+ )
+ .into();
+ let expected_package2: PackageInterfaceHandle = PackageHandle::new(
+ "acme/package".to_string(),
+ "1.1.0.0".to_string(),
+ "1.1".to_string(),
+ )
+ .into();
+
+ let pool = Pool::new(
+ vec![expected_package1.clone(), expected_package2.clone()],
+ vec![],
+ IndexMap::new(),
+ IndexMap::new(),
+ IndexMap::new(),
+ IndexMap::new(),
+ );
+ let filtered_pool = filter
+ .filter(pool, vec![repository], &Request::new(None))
+ .unwrap();
+
+ let packages = filtered_pool.get_packages();
+ assert_eq!(packages.len(), 2);
+ assert!(packages[0].ptr_eq(&expected_package1));
+ assert!(packages[1].ptr_eq(&expected_package2));
+ assert_eq!(
+ filtered_pool
+ .get_all_abandoned_removed_package_versions()
+ .len(),
+ 0
+ );
+ assert_eq!(
+ filtered_pool
+ .get_all_security_removed_package_versions()
+ .len(),
+ 0
+ );
}
#[test]
@@ -106,3 +354,58 @@ fn test_dont_filter_packages_with_abandoned_package() {
0
);
}
+
+fn generate_security_advisory(
+ package_name: &str,
+ cve: Option<&str>,
+ affected_versions: &str,
+) -> IndexMap<String, PhpMixed> {
+ let mut source: IndexMap<String, PhpMixed> = IndexMap::new();
+ source.insert(
+ "name".to_string(),
+ PhpMixed::String("Security Advisory".to_string()),
+ );
+ source.insert("remoteId".to_string(), PhpMixed::String("test".to_string()));
+
+ let mut advisory: IndexMap<String, PhpMixed> = IndexMap::new();
+ advisory.insert(
+ "advisoryId".to_string(),
+ PhpMixed::String(uniqid("PKSA-", false)),
+ );
+ advisory.insert(
+ "packageName".to_string(),
+ PhpMixed::String(package_name.to_string()),
+ );
+ advisory.insert("remoteId".to_string(), PhpMixed::String("test".to_string()));
+ advisory.insert(
+ "title".to_string(),
+ PhpMixed::String("Security Advisory".to_string()),
+ );
+ advisory.insert("link".to_string(), PhpMixed::Null);
+ advisory.insert(
+ "cve".to_string(),
+ match cve {
+ Some(cve) => PhpMixed::String(cve.to_string()),
+ None => PhpMixed::Null,
+ },
+ );
+ advisory.insert(
+ "affectedVersions".to_string(),
+ PhpMixed::String(affected_versions.to_string()),
+ );
+ advisory.insert("source".to_string(), PhpMixed::String("Tests".to_string()));
+ advisory.insert(
+ "reportedAt".to_string(),
+ PhpMixed::String("2024-04-31 12:37:47".to_string()),
+ );
+ advisory.insert(
+ "composerRepository".to_string(),
+ PhpMixed::String("Package Repository".to_string()),
+ );
+ advisory.insert("severity".to_string(), PhpMixed::String("high".to_string()));
+ advisory.insert(
+ "sources".to_string(),
+ PhpMixed::List(vec![PhpMixed::Array(source)]),
+ );
+ advisory
+}
diff --git a/crates/shirabe/tests/dependency_resolver/solver_test.rs b/crates/shirabe/tests/dependency_resolver/solver_test.rs
index c9c4d26..a688190 100644
--- a/crates/shirabe/tests/dependency_resolver/solver_test.rs
+++ b/crates/shirabe/tests/dependency_resolver/solver_test.rs
@@ -277,7 +277,7 @@ fn test_solver_remove_if_not_requested() {
);
}
-#[ignore = "get_pretty_string path reaches an unimplemented todo!() (in_array non-strict in php-shim)"]
+#[ignore = "get_pretty_string path reaches an unimplemented stub (in_array non-strict in php-shim)"]
#[test]
fn test_install_non_existing_package_fails() {
let fixtures = set_up();
@@ -1921,7 +1921,7 @@ fn test_issue265() {
);
}
-#[ignore = "get_pretty_string path reaches an unimplemented todo!() (in_array non-strict in php-shim)"]
+#[ignore = "get_pretty_string path reaches an unimplemented stub (in_array non-strict in php-shim)"]
#[test]
fn test_conflict_result_empty() {
let fixtures = set_up();
@@ -1979,7 +1979,7 @@ fn test_conflict_result_empty() {
);
}
-#[ignore = "get_pretty_string path reaches an unimplemented todo!() (in_array non-strict in php-shim)"]
+#[ignore = "get_pretty_string path reaches an unimplemented stub (in_array non-strict in php-shim)"]
#[test]
fn test_unsatisfiable_requires() {
let fixtures = set_up();
@@ -2027,7 +2027,7 @@ fn test_unsatisfiable_requires() {
);
}
-#[ignore = "get_pretty_string path reaches an unimplemented todo!() (Intervals::is_subset_of)"]
+#[ignore = "get_pretty_string path reaches an unimplemented stub (Intervals::is_subset_of)"]
#[test]
fn test_require_mismatch_exception() {
let fixtures = set_up();
diff --git a/crates/shirabe/tests/installed_versions_test.rs b/crates/shirabe/tests/installed_versions_test.rs
index 6757e03..622a796 100644
--- a/crates/shirabe/tests/installed_versions_test.rs
+++ b/crates/shirabe/tests/installed_versions_test.rs
@@ -342,11 +342,12 @@ fn test_get_root_package() {
assert_eq!(expected, InstalledVersions::get_root_package());
}
-#[ignore = "InstalledVersions::get_raw_data not ported (only get_all_raw_data exists)"]
+#[ignore]
#[test]
fn test_get_raw_data() {
- let _root = set_up();
- todo!()
+ let (_root, dir) = set_up();
+
+ assert_eq!(fixture(&dir), InstalledVersions::get_raw_data());
}
#[ignore]
diff --git a/crates/shirabe/tests/installer/metapackage_installer_test.rs b/crates/shirabe/tests/installer/metapackage_installer_test.rs
index 7766ed1..20fe8ea 100644
--- a/crates/shirabe/tests/installer/metapackage_installer_test.rs
+++ b/crates/shirabe/tests/installer/metapackage_installer_test.rs
@@ -13,13 +13,6 @@ use shirabe::repository::{InstalledArrayRepository, RepositoryInterface};
use crate::test_case::get_package;
-/// Builds mocked repository/IO and a MetapackageInstaller over them. The mocks
-/// are not available here; the live tests below intentionally diverge to use a
-/// real InstalledArrayRepository instead, so this stub is not injected.
-fn set_up() {
- todo!()
-}
-
fn run<F: std::future::Future>(future: F) -> F::Output {
tokio::runtime::Builder::new_current_thread()
.build()
diff --git a/crates/shirabe/tests/package/archiver/archivable_files_finder_test.rs b/crates/shirabe/tests/package/archiver/archivable_files_finder_test.rs
index 48f0fc5..aed0dc3 100644
--- a/crates/shirabe/tests/package/archiver/archivable_files_finder_test.rs
+++ b/crates/shirabe/tests/package/archiver/archivable_files_finder_test.rs
@@ -1,45 +1,235 @@
//! ref: composer/tests/Composer/Test/Package/Archiver/ArchivableFilesFinderTest.php
-/// Builds a temp directory tree of fixture files under a unique tmp dir; the Filesystem and
-/// getUniqueTmpDirectory infrastructure is not ported.
-#[allow(dead_code)]
-fn set_up() -> String {
- todo!()
-}
+use shirabe::package::archiver::ArchivableFilesFinder;
+use shirabe::util::Filesystem;
+use shirabe_external_packages::composer::pcre::Preg;
+use shirabe_php_shim::{dirname, file_put_contents, preg_quote};
+use tempfile::TempDir;
-#[allow(dead_code)]
-fn tear_down(_sources: &str) {
- // Removes the temp directory tree created in set_up.
- todo!()
+struct SetUp {
+ _sources_dir: TempDir,
+ sources: String,
+ fs: Filesystem,
}
-#[allow(dead_code)]
-struct TearDown {
- sources: String,
+fn set_up() -> SetUp {
+ let mut fs = Filesystem::new(None);
+
+ let sources_dir = TempDir::new().unwrap();
+ let sources = fs.normalize_path(&sources_dir.path().canonicalize().unwrap().to_string_lossy());
+
+ let file_tree = [
+ ".foo",
+ "A/prefixA.foo",
+ "A/prefixB.foo",
+ "A/prefixC.foo",
+ "A/prefixD.foo",
+ "A/prefixE.foo",
+ "A/prefixF.foo",
+ "B/sub/prefixA.foo",
+ "B/sub/prefixB.foo",
+ "B/sub/prefixC.foo",
+ "B/sub/prefixD.foo",
+ "B/sub/prefixE.foo",
+ "B/sub/prefixF.foo",
+ "C/prefixA.foo",
+ "C/prefixB.foo",
+ "C/prefixC.foo",
+ "C/prefixD.foo",
+ "C/prefixE.foo",
+ "C/prefixF.foo",
+ "D/prefixA",
+ "D/prefixB",
+ "D/prefixC",
+ "D/prefixD",
+ "D/prefixE",
+ "D/prefixF",
+ "E/subtestA.foo",
+ "F/subtestA.foo",
+ "G/subtestA.foo",
+ "H/subtestA.foo",
+ "I/J/subtestA.foo",
+ "K/dirJ/subtestA.foo",
+ "toplevelA.foo",
+ "toplevelB.foo",
+ "prefixA.foo",
+ "prefixB.foo",
+ "prefixC.foo",
+ "prefixD.foo",
+ "prefixE.foo",
+ "prefixF.foo",
+ "parameters.yml",
+ "parameters.yml.dist",
+ "!important!.txt",
+ "!important_too!.txt",
+ "#weirdfile",
+ ];
+
+ for relative_path in file_tree {
+ let path = format!("{}/{}", sources, relative_path);
+ fs.ensure_directory_exists(&dirname(&path)).unwrap();
+ file_put_contents(&path, b"");
+ }
+
+ SetUp {
+ _sources_dir: sources_dir,
+ sources,
+ fs,
+ }
}
-impl Drop for TearDown {
- fn drop(&mut self) {
- tear_down(&self.sources);
+fn get_archivable_files(set_up: &SetUp, finder: ArchivableFilesFinder) -> Vec<String> {
+ let mut files: Vec<String> = vec![];
+ for file in finder {
+ if !file.is_dir() {
+ let real_path = file.canonicalize().unwrap();
+ files.push(Preg::replace(
+ &format!("#^{}#", preg_quote(&set_up.sources, Some('#'))),
+ "",
+ &set_up.fs.normalize_path(&real_path.to_string_lossy()),
+ ));
+ }
}
+
+ files.sort();
+
+ files
+}
+
+fn assert_archivable_files(set_up: &SetUp, finder: ArchivableFilesFinder, expected_files: &[&str]) {
+ let actual_files = get_archivable_files(set_up, finder);
+
+ let expected_files: Vec<String> = expected_files.iter().map(|s| s.to_string()).collect();
+ assert_eq!(expected_files, actual_files);
}
-// These set up a temp directory tree (including a git repo) and assert the files the finder
-// selects with manual/git/skip excludes; the git-backed fixture setup is not ported.
-#[ignore = "setUp needs TestCase::getUniqueTmpDirectory to build the on-disk fixture tree; not ported"]
+// The manual exclude patterns (e.g. `.*`, `prefixC.*`) compile, via ComposerExcludeFilter, to
+// look-ahead regexes like `(?=[^\.])...(?=$|/)`, which the regex crate cannot compile.
+#[ignore = "ComposerExcludeFilter builds look-ahead regexes the regex crate does not support"]
#[test]
fn test_manual_excludes() {
- todo!()
+ let set_up = set_up();
+
+ let excludes = vec![
+ "prefixB.foo".to_string(),
+ "!/prefixB.foo".to_string(),
+ "/prefixA.foo".to_string(),
+ "prefixC.*".to_string(),
+ "!*/*/*/prefixC.foo".to_string(),
+ ".*".to_string(),
+ ];
+
+ let finder = ArchivableFilesFinder::new(&set_up.sources, excludes, false).unwrap();
+
+ assert_archivable_files(
+ &set_up,
+ finder,
+ &[
+ "/!important!.txt",
+ "/!important_too!.txt",
+ "/#weirdfile",
+ "/A/prefixA.foo",
+ "/A/prefixD.foo",
+ "/A/prefixE.foo",
+ "/A/prefixF.foo",
+ "/B/sub/prefixA.foo",
+ "/B/sub/prefixC.foo",
+ "/B/sub/prefixD.foo",
+ "/B/sub/prefixE.foo",
+ "/B/sub/prefixF.foo",
+ "/C/prefixA.foo",
+ "/C/prefixD.foo",
+ "/C/prefixE.foo",
+ "/C/prefixF.foo",
+ "/D/prefixA",
+ "/D/prefixB",
+ "/D/prefixC",
+ "/D/prefixD",
+ "/D/prefixE",
+ "/D/prefixF",
+ "/E/subtestA.foo",
+ "/F/subtestA.foo",
+ "/G/subtestA.foo",
+ "/H/subtestA.foo",
+ "/I/J/subtestA.foo",
+ "/K/dirJ/subtestA.foo",
+ "/parameters.yml",
+ "/parameters.yml.dist",
+ "/prefixB.foo",
+ "/prefixD.foo",
+ "/prefixE.foo",
+ "/prefixF.foo",
+ "/toplevelA.foo",
+ "/toplevelB.foo",
+ ],
+ );
}
-#[ignore = "setUp needs TestCase::getUniqueTmpDirectory plus skipIfNotExecutable/Process::fromShellCommandline/PharData/RecursiveIteratorIterator; none ported"]
+// getArchivedFiles drives a git pipeline (Process::fromShellCommandline) and reads the produced
+// zip back through PharData + RecursiveIteratorIterator; neither the process helper nor the zip
+// archive reader is ported, so this case cannot run.
+#[ignore = "getArchivedFiles needs a git process pipeline plus PharData/RecursiveIteratorIterator zip reading; not ported"]
#[test]
fn test_git_excludes() {
todo!()
}
-#[ignore = "setUp needs TestCase::getUniqueTmpDirectory to build the on-disk fixture tree; not ported"]
#[test]
fn test_skip_excludes() {
- todo!()
+ let set_up = set_up();
+
+ let excludes = vec!["prefixB.foo".to_string()];
+
+ let finder = ArchivableFilesFinder::new(&set_up.sources, excludes, true).unwrap();
+
+ assert_archivable_files(
+ &set_up,
+ finder,
+ &[
+ "/!important!.txt",
+ "/!important_too!.txt",
+ "/#weirdfile",
+ "/.foo",
+ "/A/prefixA.foo",
+ "/A/prefixB.foo",
+ "/A/prefixC.foo",
+ "/A/prefixD.foo",
+ "/A/prefixE.foo",
+ "/A/prefixF.foo",
+ "/B/sub/prefixA.foo",
+ "/B/sub/prefixB.foo",
+ "/B/sub/prefixC.foo",
+ "/B/sub/prefixD.foo",
+ "/B/sub/prefixE.foo",
+ "/B/sub/prefixF.foo",
+ "/C/prefixA.foo",
+ "/C/prefixB.foo",
+ "/C/prefixC.foo",
+ "/C/prefixD.foo",
+ "/C/prefixE.foo",
+ "/C/prefixF.foo",
+ "/D/prefixA",
+ "/D/prefixB",
+ "/D/prefixC",
+ "/D/prefixD",
+ "/D/prefixE",
+ "/D/prefixF",
+ "/E/subtestA.foo",
+ "/F/subtestA.foo",
+ "/G/subtestA.foo",
+ "/H/subtestA.foo",
+ "/I/J/subtestA.foo",
+ "/K/dirJ/subtestA.foo",
+ "/parameters.yml",
+ "/parameters.yml.dist",
+ "/prefixA.foo",
+ "/prefixB.foo",
+ "/prefixC.foo",
+ "/prefixD.foo",
+ "/prefixE.foo",
+ "/prefixF.foo",
+ "/toplevelA.foo",
+ "/toplevelB.foo",
+ ],
+ );
}
diff --git a/crates/shirabe/tests/package/archiver/archive_manager_test.rs b/crates/shirabe/tests/package/archiver/archive_manager_test.rs
index 2712da6..fdc4bf0 100644
--- a/crates/shirabe/tests/package/archiver/archive_manager_test.rs
+++ b/crates/shirabe/tests/package/archiver/archive_manager_test.rs
@@ -29,31 +29,31 @@ impl Drop for TearDown {
// the filename-derivation helpers over packages; the archiving and fixture setup are not
// ported.
#[test]
-#[ignore = "setupPackage requires CompletePackage::setSourceType, not exposed on the package handle API; also needs Factory-built ArchiveManager (set_up) which is unported"]
+#[ignore = "needs the Factory/FactoryMock-built ArchiveManager (createDownloadManager/createHttpDownloader/FactoryMock::createConfig) in set_up, which is unported"]
fn test_unknown_format() {
todo!()
}
#[test]
-#[ignore = "needs setupGitRepo + PharData tar archiving and Factory-built ArchiveManager (set_up), and setupPackage's setSourceType which is not exposed on the package handle API"]
+#[ignore = "needs setupGitRepo + PharData tar archiving and the Factory/FactoryMock-built ArchiveManager (set_up), which is unported"]
fn test_archive_tar() {
todo!()
}
#[test]
-#[ignore = "needs setupGitRepo + PharData tar archiving and Factory-built ArchiveManager (set_up), and setupPackage's setSourceType which is not exposed on the package handle API"]
+#[ignore = "needs setupGitRepo + PharData tar archiving and the Factory/FactoryMock-built ArchiveManager (set_up), which is unported"]
fn test_archive_custom_file_name() {
todo!()
}
#[test]
-#[ignore = "setupPackage requires CompletePackage::setSourceType, not exposed on the package handle API; also needs Factory-built ArchiveManager (set_up) which is unported"]
+#[ignore = "needs the Factory/FactoryMock-built ArchiveManager (createDownloadManager/createHttpDownloader/FactoryMock::createConfig) in set_up, which is unported"]
fn test_get_package_filename_parts() {
todo!()
}
#[test]
-#[ignore = "setupPackage requires CompletePackage::setSourceType, not exposed on the package handle API; also needs Factory-built ArchiveManager (set_up) which is unported"]
+#[ignore = "needs the Factory/FactoryMock-built ArchiveManager (createDownloadManager/createHttpDownloader/FactoryMock::createConfig) in set_up, which is unported"]
fn test_get_package_filename() {
todo!()
}
diff --git a/crates/shirabe/tests/package/archiver/phar_archiver_test.rs b/crates/shirabe/tests/package/archiver/phar_archiver_test.rs
index 385177b..fe6185d 100644
--- a/crates/shirabe/tests/package/archiver/phar_archiver_test.rs
+++ b/crates/shirabe/tests/package/archiver/phar_archiver_test.rs
@@ -1,13 +1,130 @@
//! ref: composer/tests/Composer/Test/Package/Archiver/PharArchiverTest.php
-#[ignore = "setUp/setupDummyRepo need TestCase::getUniqueTmpDirectory plus Filesystem to build the on-disk fixture tree; not ported"]
+use shirabe::package::archiver::{ArchiverInterface, PharArchiver};
+use shirabe::package::handle::CompletePackageHandle;
+use shirabe::util::{Filesystem, Platform};
+use shirabe_php_shim::{dirname, file_exists, file_put_contents, mkdir, realpath};
+use tempfile::TempDir;
+
+// ref: ArchiverTestCase.
+struct ArchiverTestCase {
+ filesystem: Filesystem,
+ test_dir: String,
+ _test_dir_guard: TempDir,
+}
+
+impl ArchiverTestCase {
+ fn set_up() -> Self {
+ let guard = TempDir::new().unwrap();
+ let test_dir = guard.path().to_string_lossy().to_string();
+ Self {
+ filesystem: Filesystem::new(None),
+ test_dir,
+ _test_dir_guard: guard,
+ }
+ }
+
+ fn setup_package(&self) -> CompletePackageHandle {
+ let package = CompletePackageHandle::new(
+ "archivertest/archivertest".to_string(),
+ "master".to_string(),
+ "master".to_string(),
+ );
+ package.set_source_url(Some(realpath(&self.test_dir).unwrap_or_default()));
+ package.set_source_reference(Some("master".to_string()));
+ package.__set_source_type(Some("git".to_string()));
+
+ package
+ }
+
+ fn setup_dummy_repo(&self) {
+ let current_work_dir = Platform::get_cwd(false).unwrap();
+ std::env::set_current_dir(&self.test_dir).unwrap();
+
+ self.write_file("file.txt", "content", &current_work_dir);
+ self.write_file("foo/bar/baz", "content", &current_work_dir);
+ self.write_file("foo/bar/ignoreme", "content", &current_work_dir);
+ self.write_file("x/baz", "content", &current_work_dir);
+ self.write_file("x/includeme", "content", &current_work_dir);
+
+ std::env::set_current_dir(&current_work_dir).unwrap();
+ }
+
+ fn write_file(&self, path: &str, content: &str, current_work_dir: &str) {
+ if !file_exists(dirname(path)) {
+ mkdir(&dirname(path), 0o777, true);
+ }
+
+ let result = file_put_contents(path, content.as_bytes());
+ if result.is_none() {
+ std::env::set_current_dir(current_work_dir).unwrap();
+ panic!("Could not save file.");
+ }
+ }
+}
+
+#[ignore = "PharArchiver::archive builds the archive via PharData, which is todo!() in the php-shim"]
#[test]
fn test_tar_archive() {
- todo!()
+ let mut test_case = ArchiverTestCase::set_up();
+
+ test_case.setup_dummy_repo();
+ let package = test_case.setup_package();
+ let target_dir = TempDir::new().unwrap();
+ let target = format!(
+ "{}/composer_archiver_test.tar",
+ target_dir.path().to_string_lossy()
+ );
+
+ let archiver = PharArchiver::new();
+ archiver
+ .archive(
+ package.get_source_url().unwrap(),
+ target.clone(),
+ "tar".to_string(),
+ vec![
+ "foo/bar".to_string(),
+ "baz".to_string(),
+ "!/foo/bar/baz".to_string(),
+ ],
+ false,
+ )
+ .unwrap();
+ assert!(file_exists(&target));
+
+ test_case
+ .filesystem
+ .remove_directory(dirname(&target))
+ .unwrap();
}
-#[ignore = "setUp/setupDummyRepo need TestCase::getUniqueTmpDirectory plus Filesystem to build the on-disk fixture tree; not ported"]
+#[ignore = "PharArchiver::archive builds the archive via PharData, which is todo!() in the php-shim"]
#[test]
fn test_zip_archive() {
- todo!()
+ let mut test_case = ArchiverTestCase::set_up();
+
+ test_case.setup_dummy_repo();
+ let package = test_case.setup_package();
+ let target_dir = TempDir::new().unwrap();
+ let target = format!(
+ "{}/composer_archiver_test.zip",
+ target_dir.path().to_string_lossy()
+ );
+
+ let archiver = PharArchiver::new();
+ archiver
+ .archive(
+ package.get_source_url().unwrap(),
+ target.clone(),
+ "zip".to_string(),
+ vec![],
+ false,
+ )
+ .unwrap();
+ assert!(file_exists(&target));
+
+ test_case
+ .filesystem
+ .remove_directory(dirname(&target))
+ .unwrap();
}
diff --git a/crates/shirabe/tests/package/archiver/zip_archiver_test.rs b/crates/shirabe/tests/package/archiver/zip_archiver_test.rs
index 5b51ce9..7785976 100644
--- a/crates/shirabe/tests/package/archiver/zip_archiver_test.rs
+++ b/crates/shirabe/tests/package/archiver/zip_archiver_test.rs
@@ -1,46 +1,174 @@
//! ref: composer/tests/Composer/Test/Package/Archiver/ZipArchiverTest.php
-/// Creates the Filesystem/ProcessExecutor and a unique tmp testDir; that infrastructure is
-/// not ported. Returns testDir.
-#[allow(dead_code)]
-fn set_up() -> String {
- todo!()
-}
-
-/// Unlinks the zip files collected in filesToCleanup, then (parent) removes testDir.
-#[allow(dead_code)]
-fn tear_down(_files_to_cleanup: &[String], _test_dir: &str) {
- todo!()
-}
+use indexmap::IndexMap;
+use shirabe::package::archiver::{ArchiverInterface, ZipArchiver};
+use shirabe::package::handle::CompletePackageHandle;
+use shirabe::util::Platform;
+use shirabe_php_shim::{
+ ZipArchive, class_exists, dirname, file_exists, file_put_contents, mkdir, realpath,
+ sys_get_temp_dir, unlink,
+};
+use tempfile::TempDir;
-#[allow(dead_code)]
-struct TearDown {
- files_to_cleanup: Vec<String>,
+// ref: ArchiverTestCase. Holds the unique tmp testDir plus the ZipArchiverTest cleanup list.
+struct ArchiverTestCase {
test_dir: String,
+ _test_dir_guard: TempDir,
+ files_to_cleanup: Vec<String>,
}
-impl Drop for TearDown {
+impl Drop for ArchiverTestCase {
fn drop(&mut self) {
- tear_down(&self.files_to_cleanup, &self.test_dir);
+ for file in &self.files_to_cleanup {
+ unlink(file);
+ }
}
}
-// ZipArchiver::archive builds a zip via ZipArchive, which is todo!() in the php-shim.
+impl ArchiverTestCase {
+ fn set_up() -> Self {
+ let guard = TempDir::new().unwrap();
+ let test_dir = guard.path().to_string_lossy().to_string();
+ Self {
+ test_dir,
+ _test_dir_guard: guard,
+ files_to_cleanup: vec![],
+ }
+ }
+
+ fn setup_package(&self) -> CompletePackageHandle {
+ let package = CompletePackageHandle::new(
+ "archivertest/archivertest".to_string(),
+ "master".to_string(),
+ "master".to_string(),
+ );
+ package.set_source_url(Some(realpath(&self.test_dir).unwrap_or_default()));
+ package.set_source_reference(Some("master".to_string()));
+ package.__set_source_type(Some("git".to_string()));
+
+ package
+ }
+ fn assert_zip_archive(&mut self, mut files: IndexMap<String, Option<String>>) {
+ if !class_exists("ZipArchive") {
+ // markTestSkipped('Cannot run ZipArchiverTest, missing class "ZipArchive".')
+ return;
+ }
+
+ self.setup_dummy_repo(&mut files);
+ let package = self.setup_package();
+ let target = format!("{}/composer_archiver_test.zip", sys_get_temp_dir());
+ self.files_to_cleanup.push(target.clone());
+
+ let archiver = ZipArchiver::new();
+ archiver
+ .archive(
+ package.get_source_url().unwrap(),
+ target.clone(),
+ "zip".to_string(),
+ vec![],
+ false,
+ )
+ .unwrap();
+ assert!(file_exists(&target));
+ let mut zip = ZipArchive::new();
+ let res = zip.open(&target, 0);
+ assert!(res.is_ok(), "Failed asserting that Zip file can be opened");
+
+ let mut zip_contents: IndexMap<String, Option<String>> = IndexMap::new();
+ for i in 0..zip.num_files {
+ let path = zip.get_name_index(i);
+ zip_contents.insert(path.clone(), zip.get_from_name(&path));
+ }
+ zip.close();
+
+ let files: IndexMap<String, Option<String>> = files;
+ assert_eq!(
+ files, zip_contents,
+ "Failed asserting that Zip created with the ZipArchiver contains all files from the repository."
+ );
+ }
+
+ fn setup_dummy_repo(&self, files: &mut IndexMap<String, Option<String>>) {
+ let current_work_dir = Platform::get_cwd(false).unwrap();
+ std::env::set_current_dir(&self.test_dir).unwrap();
+ let paths: Vec<String> = files.keys().cloned().collect();
+ for path in paths {
+ if files[&path].is_none() {
+ files.insert(path.clone(), Some("content".to_string()));
+ }
+ self.write_file(&path, files[&path].clone().unwrap(), &current_work_dir);
+ }
+
+ std::env::set_current_dir(&current_work_dir).unwrap();
+ }
+
+ fn write_file(&self, path: &str, content: String, current_work_dir: &str) {
+ if !file_exists(dirname(path)) {
+ mkdir(&dirname(path), 0o777, true);
+ }
+
+ let result = file_put_contents(path, content.as_bytes());
+ if result.is_none() {
+ std::env::set_current_dir(current_work_dir).unwrap();
+ panic!("Could not save file.");
+ }
+ }
+}
+
+#[ignore = "ZipArchiver::archive and the ZipArchive reader (open/get_from_name/...) are todo!() in the php-shim"]
#[test]
-#[ignore = "ArchiverTestCase infrastructure (setup_package/setup_dummy_repo/test_dir) not ported; ZipArchive shim lacks get_from_name"]
fn test_simple_files() {
- todo!()
+ let mut test_case = ArchiverTestCase::set_up();
+
+ let mut files: IndexMap<String, Option<String>> = IndexMap::new();
+ files.insert("file.txt".to_string(), None);
+ files.insert("foo/bar/baz".to_string(), None);
+ files.insert("x/baz".to_string(), None);
+ files.insert("x/includeme".to_string(), None);
+
+ if !Platform::is_windows() {
+ files.insert(
+ format!("zfoo{}/file.txt", Platform::get_cwd(false).unwrap()),
+ None,
+ );
+ }
+
+ test_case.assert_zip_archive(files);
}
+#[ignore = "ZipArchiver::archive and the ZipArchive reader (open/get_from_name/...) are todo!() in the php-shim"]
#[test]
-#[ignore = "ArchiverTestCase infrastructure (setup_package/setup_dummy_repo/test_dir) not ported; ZipArchive shim lacks get_from_name"]
fn test_gitignore_exclude_negation() {
- todo!()
+ for include in ["!/docs", "!/docs/"] {
+ let mut test_case = ArchiverTestCase::set_up();
+
+ let mut files: IndexMap<String, Option<String>> = IndexMap::new();
+ files.insert(
+ ".gitignore".to_string(),
+ Some(format!("/*\n.*\n!.git*\n{}", include)),
+ );
+ files.insert("docs/README.md".to_string(), Some("# The doc".to_string()));
+
+ test_case.assert_zip_archive(files);
+ }
}
+#[ignore = "ZipArchiver::archive and the ZipArchive reader (open/get_from_name/...) are todo!() in the php-shim"]
#[test]
-#[ignore = "ArchiverTestCase infrastructure (setup_package/setup_dummy_repo/test_dir) not ported; ZipArchive shim lacks get_from_name"]
fn test_folder_with_backslashes() {
- todo!()
+ if Platform::is_windows() {
+ // markTestSkipped('Folder names cannot contain backslashes on Windows.')
+ return;
+ }
+
+ let mut test_case = ArchiverTestCase::set_up();
+
+ let mut files: IndexMap<String, Option<String>> = IndexMap::new();
+ files.insert(
+ "folder\\with\\backslashes/README.md".to_string(),
+ Some("# doc".to_string()),
+ );
+
+ test_case.assert_zip_archive(files);
}
diff --git a/crates/shirabe/tests/package/base_package_test.rs b/crates/shirabe/tests/package/base_package_test.rs
index 1056de1..16d3a5b 100644
--- a/crates/shirabe/tests/package/base_package_test.rs
+++ b/crates/shirabe/tests/package/base_package_test.rs
@@ -1,7 +1,10 @@
//! ref: composer/tests/Composer/Test/Package/BasePackageTest.php
+use shirabe::package::DisplayMode;
use shirabe::package::base_package::package_names_to_regexp;
+use shirabe::package::handle::PackageHandle;
use shirabe::repository::{ArrayRepository, RepositoryInterfaceHandle};
+use shirabe_semver::version_parser::VersionParser;
use crate::test_case::get_package;
@@ -31,14 +34,40 @@ fn test_set_another_repository() {
assert!(package.set_repository(repository2.clone()).is_err());
}
-// In PHP this mocks isDev()/getSourceType()/getPrettyVersion()/getSourceReference()
-// on an abstract BasePackage to drive getFullPrettyVersion(). Reproducing those exact
-// getter values requires mocking; a real package cannot carry the pretty version
-// "PrettyVersion" together with isDev() == true.
-#[ignore = "requires mocking isDev/getSourceType/getPrettyVersion/getSourceReference on an abstract BasePackage; no mock infrastructure exists and a real package cannot carry prettyVersion \"PrettyVersion\" with isDev() == true"]
+// PHP mocks isDev()/getSourceType()/getPrettyVersion()/getSourceReference() on an abstract
+// BasePackage. A real Package reproduces the same observable getters: a "dev-master" version makes
+// isDev() true, while the pretty version is an independent constructor argument.
#[test]
fn test_format_version_for_dev_package() {
- todo!()
+ let cases: Vec<(&str, bool, &str)> = vec![
+ ("v2.1.0-RC2", true, "PrettyVersion v2.1.0-RC2"),
+ (
+ "bbf527a27356414bfa9bf520f018c5cb7af67c77",
+ true,
+ "PrettyVersion bbf527a",
+ ),
+ ("v1.0.0", false, "PrettyVersion v1.0.0"),
+ (
+ "bbf527a27356414bfa9bf520f018c5cb7af67c77",
+ false,
+ "PrettyVersion bbf527a27356414bfa9bf520f018c5cb7af67c77",
+ ),
+ ];
+
+ for (source_reference, truncate, expected) in cases {
+ let package = PackageHandle::new(
+ "dummy/pkg".to_string(),
+ VersionParser.normalize("dev-master", None).unwrap(),
+ "PrettyVersion".to_string(),
+ );
+ package.__set_source_type(Some("git".to_string()));
+ package.set_source_reference(Some(source_reference.to_string()));
+
+ assert_eq!(
+ expected,
+ package.get_full_pretty_version(truncate, DisplayMode::SourceRefIfDev)
+ );
+ }
}
#[test]
diff --git a/crates/shirabe/tests/package/dumper/array_dumper_test.rs b/crates/shirabe/tests/package/dumper/array_dumper_test.rs
index 64880f1..1e5fd05 100644
--- a/crates/shirabe/tests/package/dumper/array_dumper_test.rs
+++ b/crates/shirabe/tests/package/dumper/array_dumper_test.rs
@@ -79,11 +79,16 @@ fn test_dump_abandoned_replacement() {
);
}
-// PHP drives ~25 heterogeneous setters dynamically (set{ucfirst(method)}) across strings,
-// arrays, a DateTime and Link maps, then checks the corresponding dumped key. Reproducing
-// that dynamic dispatch faithfully would require per-case wiring for every property type.
+// PHP drives 26 heterogeneous setters dynamically (set{ucfirst(method)}) across strings,
+// arrays, a DateTime and Link maps, then checks the corresponding dumped key. Three of the
+// data sets cannot be expressed faithfully because the ported production types were narrowed
+// from PHP's loose `array`: the `authors` set passes plain strings (Rust set_authors wants
+// Vec<IndexMap<String,String>>), `scripts` passes a bare string value (set_scripts wants
+// IndexMap<String,Vec<String>>), and `funding` passes a single map (set_funding wants
+// Vec<IndexMap<String,PhpMixed>>); the dumper additionally re-wraps each of these. Porting the
+// remaining 23 would drop those three cases, so testKeys stays unported (all-or-nothing).
#[test]
-#[ignore = "RootPackageHandle lacks set_type/set_release_date/set_binaries/set_php_ext setters required by the data provider's type/time/bin/php-ext cases"]
+#[ignore = "authors/scripts/funding data sets pass loosely-typed PHP arrays the narrowed Rust set_authors/set_scripts/set_funding types cannot represent, and the dumper re-wraps them; faithful all-or-nothing port blocked without loosening those production types"]
fn test_keys() {
todo!()
}
diff --git a/crates/shirabe/tests/platform/hhvm_detector_test.rs b/crates/shirabe/tests/platform/hhvm_detector_test.rs
index 12a966a..f0a8550 100644
--- a/crates/shirabe/tests/platform/hhvm_detector_test.rs
+++ b/crates/shirabe/tests/platform/hhvm_detector_test.rs
@@ -4,7 +4,7 @@ use shirabe::platform::hhvm_detector::HhvmDetector;
use shirabe::util::Platform;
use shirabe::util::ProcessExecutor;
use shirabe_external_packages::symfony::process::ExecutableFinder;
-use shirabe_php_shim::{PhpMixed, defined};
+use shirabe_php_shim::{PhpMixed, constant, defined};
use shirabe_semver::version_parser::VersionParser;
fn set_up() -> HhvmDetector {
@@ -13,11 +13,16 @@ fn set_up() -> HhvmDetector {
hhvm_detector
}
+#[ignore]
#[test]
-#[ignore = "requires HHVM_VERSION_ID constant, which the shim does not define"]
fn test_hhvm_version_when_executing_in_hhvm() {
- let _hhvm_detector = set_up();
- todo!()
+ let mut hhvm_detector = set_up();
+ if !defined("HHVM_VERSION_ID") {
+ // markTestSkipped('Not running with HHVM')
+ return;
+ }
+ let version = hhvm_detector.get_version();
+ assert_eq!(version_id_to_version(), version);
}
#[ignore]
@@ -66,3 +71,17 @@ fn test_hhvm_version_when_executing_in_php() {
VersionParser.normalize(&detected_version, None).unwrap()
);
}
+
+fn version_id_to_version() -> Option<String> {
+ if !defined("HHVM_VERSION_ID") {
+ return None;
+ }
+
+ let hhvm_version_id = constant("HHVM_VERSION_ID").as_int().unwrap();
+ Some(format!(
+ "{}.{}.{}",
+ hhvm_version_id / 10000,
+ (hhvm_version_id / 100) % 100,
+ hhvm_version_id % 100,
+ ))
+}
diff --git a/crates/shirabe/tests/question/strict_confirmation_question_test.rs b/crates/shirabe/tests/question/strict_confirmation_question_test.rs
index c59a0fd..de8911b 100644
--- a/crates/shirabe/tests/question/strict_confirmation_question_test.rs
+++ b/crates/shirabe/tests/question/strict_confirmation_question_test.rs
@@ -1,23 +1,138 @@
//! ref: composer/tests/Composer/Test/Question/StrictConfirmationQuestionTest.php
-// These drive StrictConfirmationQuestion through Symfony's QuestionHelper::ask using
-// ArrayInput/StreamOutput, none of which are ported. The question's normalizer and
-// validator are private, so they cannot be exercised directly either.
+use std::cell::RefCell;
+use std::rc::Rc;
+
+use shirabe::question::StrictConfirmationQuestion;
+use shirabe_external_packages::symfony::console::helper::question_helper::QuestionHelper;
+use shirabe_external_packages::symfony::console::input::array_input::ArrayInput;
+use shirabe_external_packages::symfony::console::input::streamable_input_interface::StreamableInputInterface;
+use shirabe_external_packages::symfony::console::output::output_interface::OutputInterface;
+use shirabe_external_packages::symfony::console::output::stream_output::StreamOutput;
+use shirabe_php_shim::PhpMixed;
+
+const TRUE_ANSWER_REGEX: &str = "/^y(?:es)?$/i";
+const FALSE_ANSWER_REGEX: &str = "/^no?$/i";
+
+/// @return string[][]
+fn get_ask_confirmation_bad_data() -> Vec<&'static str> {
+ vec!["not correct", "no more", "yes please", "yellow"]
+}
#[test]
-#[ignore = "ArrayInput exposes no public set_stream (its inner Input is pub(crate)); fopen now returns a PhpResource that bridges to StreamOutput::new, but the input-stream wiring is still missing"]
fn test_ask_confirmation_bad_answer() {
- todo!()
+ for answer in get_ask_confirmation_bad_data() {
+ let (mut input, mut dialog) = create_input(&format!("{}\n", answer));
+
+ let mut question = StrictConfirmationQuestion::new(
+ "Do you like French fries?".to_string(),
+ true,
+ TRUE_ANSWER_REGEX.to_string(),
+ FALSE_ANSWER_REGEX.to_string(),
+ );
+ question.inner_mut().set_max_attempts(Some(1)).unwrap();
+
+ let error = dialog
+ .ask(&mut input, create_output_interface(), question.inner())
+ .expect_err("expected an InvalidArgumentException");
+ assert_eq!(error.to_string(), "Please answer yes, y, no, or n.");
+ }
}
#[test]
-#[ignore = "ArrayInput exposes no public set_stream (its inner Input is pub(crate)); fopen now returns a PhpResource that bridges to StreamOutput::new, but the input-stream wiring is still missing"]
fn test_ask_confirmation() {
- todo!()
+ for (question, expected, default) in get_ask_confirmation_data() {
+ let (mut input, mut dialog) = create_input(&format!("{}\n", question));
+
+ let question = StrictConfirmationQuestion::new(
+ "Do you like French fries?".to_string(),
+ default,
+ TRUE_ANSWER_REGEX.to_string(),
+ FALSE_ANSWER_REGEX.to_string(),
+ );
+ assert_eq!(
+ dialog
+ .ask(&mut input, create_output_interface(), question.inner())
+ .unwrap()
+ .unwrap(),
+ PhpMixed::Bool(expected),
+ "confirmation question should {}",
+ if expected { "pass" } else { "cancel" }
+ );
+ }
+}
+
+/// @return mixed[][]
+fn get_ask_confirmation_data() -> Vec<(&'static str, bool, bool)> {
+ vec![
+ ("", true, true),
+ ("", false, false),
+ ("y", true, true),
+ ("yes", true, true),
+ ("n", false, true),
+ ("no", false, true),
+ ]
}
#[test]
-#[ignore = "ArrayInput exposes no public set_stream (its inner Input is pub(crate)); fopen now returns a PhpResource that bridges to StreamOutput::new, but the input-stream wiring is still missing"]
fn test_ask_confirmation_with_custom_true_and_false_answer() {
- todo!()
+ let question = StrictConfirmationQuestion::new(
+ "Do you like French fries?".to_string(),
+ false,
+ "/^ja$/i".to_string(),
+ "/^nein$/i".to_string(),
+ );
+
+ let (mut input, mut dialog) = create_input("ja\n");
+ assert_eq!(
+ dialog
+ .ask(&mut input, create_output_interface(), question.inner())
+ .unwrap()
+ .unwrap(),
+ PhpMixed::Bool(true)
+ );
+
+ let (mut input, mut dialog) = create_input("nein\n");
+ assert_eq!(
+ dialog
+ .ask(&mut input, create_output_interface(), question.inner())
+ .unwrap()
+ .unwrap(),
+ PhpMixed::Bool(false)
+ );
+}
+
+/// @return resource
+fn get_input_stream(input: &str) -> shirabe_php_shim::PhpResource {
+ let stream = shirabe_php_shim::php_fopen_resource("php://memory", "r+");
+
+ shirabe_php_shim::fwrite_resource(&stream, input);
+ shirabe_php_shim::rewind(&stream);
+
+ stream
+}
+
+fn create_output_interface() -> Rc<RefCell<dyn OutputInterface>> {
+ let stream = shirabe_php_shim::php_fopen_resource("php://memory", "r+");
+ let output = StreamOutput::new(stream, None, None, None)
+ .unwrap()
+ .expect("php://memory is a valid stream");
+ Rc::new(RefCell::new(output))
+}
+
+/// @return array{ArrayInput, QuestionHelper}
+fn create_input(entry: &str) -> (ArrayInput, QuestionHelper) {
+ let mut input = ArrayInput::new(
+ vec![(
+ PhpMixed::Int(0),
+ PhpMixed::String("--no-interaction".to_string()),
+ )],
+ None,
+ )
+ .unwrap();
+ input.set_stream(get_input_stream(entry));
+
+ let dialog = QuestionHelper::default();
+
+ (input, dialog)
}
diff --git a/crates/shirabe/tests/repository/artifact_repository_test.rs b/crates/shirabe/tests/repository/artifact_repository_test.rs
index 765bdac..63fd238 100644
--- a/crates/shirabe/tests/repository/artifact_repository_test.rs
+++ b/crates/shirabe/tests/repository/artifact_repository_test.rs
@@ -8,11 +8,14 @@ use shirabe::io::{IOInterface, NullIO};
use shirabe::repository::ArtifactRepository;
use shirabe_php_shim::{PhpMixed, extension_loaded};
-fn set_up() {
+/// Returns true when the test should be skipped because the zip extension is
+/// unavailable, mirroring PHP's markTestSkipped in setUp.
+fn set_up() -> bool {
if !extension_loaded("zip") {
// markTestSkipped('You need the zip extension to run this test.')
- todo!()
+ return true;
}
+ false
}
fn artifacts_dir() -> String {
@@ -34,7 +37,9 @@ fn create_repo(url: &str) -> ArtifactRepository {
#[test]
#[ignore]
fn test_extracts_configs_from_zip_archives() {
- set_up();
+ if set_up() {
+ return;
+ }
let mut expected_packages = vec![
"vendor0/package0-0.0.1".to_string(),
@@ -82,7 +87,9 @@ fn test_extracts_configs_from_zip_archives() {
#[test]
#[ignore]
fn test_absolute_repo_url_creates_absolute_url_packages() {
- set_up();
+ if set_up() {
+ return;
+ }
let absolute_path = artifacts_dir();
let mut repo = create_repo(&absolute_path);
@@ -101,7 +108,9 @@ fn test_absolute_repo_url_creates_absolute_url_packages() {
#[test]
#[ignore]
fn test_relative_repo_url_creates_relative_url_packages() {
- set_up();
+ if set_up() {
+ return;
+ }
let relative_path = "tests/Composer/Test/Repository/Fixtures/artifacts";
let mut repo = create_repo(relative_path);
diff --git a/crates/shirabe/tests/util/filesystem_test.rs b/crates/shirabe/tests/util/filesystem_test.rs
index f5896b0..e0bb46b 100644
--- a/crates/shirabe/tests/util/filesystem_test.rs
+++ b/crates/shirabe/tests/util/filesystem_test.rs
@@ -9,50 +9,9 @@ use shirabe_php_shim::{
dirname, file_exists, file_put_contents, is_dir, is_file, mkdir, symlink, touch,
};
-#[allow(dead_code)]
-struct SetUp {
- fs: Filesystem,
- working_dir: String,
- test_file: String,
-}
-
-#[allow(dead_code)]
-fn set_up() -> SetUp {
- let fs = Filesystem::new(None);
- // getUniqueTmpDirectory is base TestCase infrastructure that is not ported.
- let working_dir: String = todo!();
- #[allow(unreachable_code)]
- let unique_tmp: String = todo!();
- #[allow(unreachable_code)]
- let test_file: String = format!("{unique_tmp}/composer_test_file");
- #[allow(unreachable_code)]
- SetUp {
- fs,
- working_dir,
- test_file,
- }
-}
-
-#[allow(dead_code)]
-fn tear_down(set_up: &mut SetUp) {
- if is_dir(&set_up.working_dir) {
- let _ = set_up.fs.remove_directory(&set_up.working_dir);
- }
- if is_file(&set_up.test_file) {
- let _ = set_up.fs.remove_directory(dirname(&set_up.test_file));
- }
-}
-
-#[allow(dead_code)]
-struct TearDown {
- set_up: SetUp,
-}
-
-impl Drop for TearDown {
- fn drop(&mut self) {
- tear_down(&mut self.set_up);
- }
-}
+// PHP's setUp/tearDown build workingDir/testFile under TestCase::getUniqueTmpDirectory; the
+// on-disk tests below instead create their own tempfile::TempDir inline, so no shared fixture
+// helper is needed.
/// providePathCouplesAsCode: (a, b, directory, expected, static, prefer_relative)
fn provide_path_couples_as_code()
diff --git a/crates/shirabe/tests/util/http/proxy_manager_test.rs b/crates/shirabe/tests/util/http/proxy_manager_test.rs
index dede524..7beafe1 100644
--- a/crates/shirabe/tests/util/http/proxy_manager_test.rs
+++ b/crates/shirabe/tests/util/http/proxy_manager_test.rs
@@ -38,11 +38,32 @@ impl Drop for TearDown {
}
#[test]
-#[ignore = "ProxyManager::get_instance returns a shared &'static Mutex, so instance identity (=== / after reset) cannot be expressed through the public API"]
+#[serial_test::serial]
fn test_instantiation() {
let _tear_down = TearDown;
set_up();
- todo!()
+
+ // PHP compares object identity (===); the value-based Rust singleton exposes a per-instance
+ // generation id instead, which changes only when a new ProxyManager is constructed.
+ let original_instance = {
+ let mutex = ProxyManager::get_instance();
+ let guard = mutex.lock().unwrap();
+ guard.as_ref().unwrap().__generation()
+ };
+ let same_instance = {
+ let mutex = ProxyManager::get_instance();
+ let guard = mutex.lock().unwrap();
+ guard.as_ref().unwrap().__generation()
+ };
+ assert_eq!(original_instance, same_instance);
+
+ ProxyManager::reset();
+ let new_instance = {
+ let mutex = ProxyManager::get_instance();
+ let guard = mutex.lock().unwrap();
+ guard.as_ref().unwrap().__generation()
+ };
+ assert_ne!(same_instance, new_instance);
}
#[test]
diff --git a/crates/shirabe/tests/util/stream_context_factory_test.rs b/crates/shirabe/tests/util/stream_context_factory_test.rs
index 23d4392..6491883 100644
--- a/crates/shirabe/tests/util/stream_context_factory_test.rs
+++ b/crates/shirabe/tests/util/stream_context_factory_test.rs
@@ -7,7 +7,33 @@ use indexmap::IndexMap;
use shirabe::util::http::proxy_manager::ProxyManager;
use shirabe::util::platform::Platform;
use shirabe::util::stream_context_factory::StreamContextFactory;
-use shirabe_php_shim::{PhpMixed, implode, stripos};
+use shirabe_php_shim::{
+ PhpMixed, base64_encode, extension_loaded, implode, stream_context_get_options, stripos,
+};
+
+fn s(value: &str) -> PhpMixed {
+ PhpMixed::String(value.to_string())
+}
+
+fn arr(entries: Vec<(&str, PhpMixed)>) -> PhpMixed {
+ PhpMixed::Array(
+ entries
+ .into_iter()
+ .map(|(k, v)| (k.to_string(), v))
+ .collect(),
+ )
+}
+
+fn list(items: Vec<PhpMixed>) -> PhpMixed {
+ PhpMixed::List(items)
+}
+
+fn map(entries: Vec<(&str, PhpMixed)>) -> IndexMap<String, PhpMixed> {
+ entries
+ .into_iter()
+ .map(|(k, v)| (k.to_string(), v))
+ .collect()
+}
fn set_up() {
Platform::clear_env("HTTP_PROXY");
@@ -37,8 +63,11 @@ impl Drop for TearDown {
}
}
+// PHP's dataGetContext second data set passes a `notification` closure in both the default and
+// expected params; PhpMixed has no closure variant, so that data set (and thus the all-or-nothing
+// testGetContext) cannot be expressed.
#[test]
-#[ignore = "stream_context_get_options/stream_context_get_params shim functions do not exist; cannot read back created context to assert"]
+#[ignore = "dataGetContext passes a notification closure in params; PhpMixed cannot represent a PHP closure, so the data set is unportable"]
fn test_get_context() {
let _tear_down = TearDown;
set_up();
@@ -46,67 +75,299 @@ fn test_get_context() {
}
#[test]
-#[ignore = "stream_context_get_options shim function does not exist; cannot read back created context to assert"]
+#[ignore]
fn test_http_proxy() {
let _tear_down = TearDown;
set_up();
- todo!()
+ Platform::put_env(
+ "http_proxy",
+ "http://username:p%40ssword@proxyserver.net:3128/",
+ );
+ Platform::put_env("HTTP_PROXY", "http://proxyserver/");
+
+ let default_options = map(vec![(
+ "http",
+ arr(vec![("method", s("GET")), ("header", s("User-Agent: foo"))]),
+ )]);
+ let context =
+ StreamContextFactory::get_context("http://example.org", default_options, IndexMap::new())
+ .unwrap();
+ let options = stream_context_get_options(&context);
+
+ let expected = map(vec![(
+ "http",
+ arr(vec![
+ ("proxy", s("tcp://proxyserver.net:3128")),
+ ("request_fulluri", PhpMixed::Bool(true)),
+ ("method", s("GET")),
+ (
+ "header",
+ list(vec![
+ s("User-Agent: foo"),
+ s(&format!(
+ "Proxy-Authorization: Basic {}",
+ base64_encode("username:p@ssword")
+ )),
+ ]),
+ ),
+ ("max_redirects", PhpMixed::Int(20)),
+ ("follow_location", PhpMixed::Int(1)),
+ ]),
+ )]);
+ assert_eq!(expected, options);
}
#[test]
-#[ignore = "stream_context_get_options shim function does not exist; cannot read back created context to assert"]
+#[ignore]
fn test_http_proxy_with_no_proxy() {
let _tear_down = TearDown;
set_up();
- todo!()
+ Platform::put_env(
+ "http_proxy",
+ "http://username:password@proxyserver.net:3128/",
+ );
+ Platform::put_env("no_proxy", "foo,example.org");
+
+ let default_options = map(vec![(
+ "http",
+ arr(vec![("method", s("GET")), ("header", s("User-Agent: foo"))]),
+ )]);
+ let context =
+ StreamContextFactory::get_context("http://example.org", default_options, IndexMap::new())
+ .unwrap();
+ let options = stream_context_get_options(&context);
+
+ let expected = map(vec![(
+ "http",
+ arr(vec![
+ ("method", s("GET")),
+ ("max_redirects", PhpMixed::Int(20)),
+ ("follow_location", PhpMixed::Int(1)),
+ ("header", list(vec![s("User-Agent: foo")])),
+ ]),
+ )]);
+ assert_eq!(expected, options);
}
#[test]
-#[ignore = "stream_context_get_options shim function does not exist; cannot read back created context to assert"]
+#[ignore]
fn test_http_proxy_with_no_proxy_wildcard() {
let _tear_down = TearDown;
set_up();
- todo!()
+ Platform::put_env(
+ "http_proxy",
+ "http://username:password@proxyserver.net:3128/",
+ );
+ Platform::put_env("no_proxy", "*");
+
+ let default_options = map(vec![(
+ "http",
+ arr(vec![("method", s("GET")), ("header", s("User-Agent: foo"))]),
+ )]);
+ let context =
+ StreamContextFactory::get_context("http://example.org", default_options, IndexMap::new())
+ .unwrap();
+ let options = stream_context_get_options(&context);
+
+ let expected = map(vec![(
+ "http",
+ arr(vec![
+ ("method", s("GET")),
+ ("max_redirects", PhpMixed::Int(20)),
+ ("follow_location", PhpMixed::Int(1)),
+ ("header", list(vec![s("User-Agent: foo")])),
+ ]),
+ )]);
+ assert_eq!(expected, options);
}
#[test]
-#[ignore = "stream_context_get_options shim function does not exist; cannot read back created context to assert"]
+#[ignore]
fn test_options_are_preserved() {
let _tear_down = TearDown;
set_up();
- todo!()
+ Platform::put_env(
+ "http_proxy",
+ "http://username:password@proxyserver.net:3128/",
+ );
+
+ let default_options = map(vec![(
+ "http",
+ arr(vec![
+ ("method", s("GET")),
+ ("header", list(vec![s("User-Agent: foo"), s("X-Foo: bar")])),
+ ("request_fulluri", PhpMixed::Bool(false)),
+ ]),
+ )]);
+ let context =
+ StreamContextFactory::get_context("http://example.org", default_options, IndexMap::new())
+ .unwrap();
+ let options = stream_context_get_options(&context);
+
+ let expected = map(vec![(
+ "http",
+ arr(vec![
+ ("proxy", s("tcp://proxyserver.net:3128")),
+ ("request_fulluri", PhpMixed::Bool(false)),
+ ("method", s("GET")),
+ (
+ "header",
+ list(vec![
+ s("User-Agent: foo"),
+ s("X-Foo: bar"),
+ s(&format!(
+ "Proxy-Authorization: Basic {}",
+ base64_encode("username:password")
+ )),
+ ]),
+ ),
+ ("max_redirects", PhpMixed::Int(20)),
+ ("follow_location", PhpMixed::Int(1)),
+ ]),
+ )]);
+ assert_eq!(expected, options);
}
#[test]
-#[ignore = "stream_context_get_options shim function does not exist; cannot read back created context to assert"]
+#[ignore]
fn test_http_proxy_without_port() {
let _tear_down = TearDown;
set_up();
- todo!()
+ Platform::put_env("https_proxy", "http://username:password@proxyserver.net");
+
+ let default_options = map(vec![(
+ "http",
+ arr(vec![("method", s("GET")), ("header", s("User-Agent: foo"))]),
+ )]);
+ let context =
+ StreamContextFactory::get_context("https://example.org", default_options, IndexMap::new())
+ .unwrap();
+ let options = stream_context_get_options(&context);
+
+ let expected = map(vec![(
+ "http",
+ arr(vec![
+ ("proxy", s("tcp://proxyserver.net:80")),
+ ("method", s("GET")),
+ (
+ "header",
+ list(vec![
+ s("User-Agent: foo"),
+ s(&format!(
+ "Proxy-Authorization: Basic {}",
+ base64_encode("username:password")
+ )),
+ ]),
+ ),
+ ("max_redirects", PhpMixed::Int(20)),
+ ("follow_location", PhpMixed::Int(1)),
+ ]),
+ )]);
+ assert_eq!(expected, options);
}
#[test]
-#[ignore = "stream_context_get_options shim function does not exist; cannot read back created context to assert"]
+#[ignore]
fn test_https_proxy_override() {
let _tear_down = TearDown;
set_up();
- todo!()
+ if !extension_loaded("openssl") {
+ // markTestSkipped('Requires openssl')
+ return;
+ }
+
+ Platform::put_env("http_proxy", "http://username:password@proxyserver.net");
+ Platform::put_env("https_proxy", "https://woopproxy.net");
+
+ // Pointless test replaced by ProxyHelperTest.php
+ // expectException('Composer\Downloader\TransportException')
+ let result = StreamContextFactory::get_context(
+ "https://example.org",
+ map(vec![(
+ "http",
+ arr(vec![("method", s("GET")), ("header", s("User-Agent: foo"))]),
+ )]),
+ IndexMap::new(),
+ );
+ assert!(result.is_err());
}
#[test]
-#[ignore = "stream_context_get_options shim function does not exist; cannot read back created context to assert"]
+#[ignore]
fn test_ssl_proxy() {
let _tear_down = TearDown;
- set_up();
- todo!()
+ for (expected, proxy) in [
+ ("ssl://proxyserver:443", "https://proxyserver/"),
+ ("ssl://proxyserver:8443", "https://proxyserver:8443"),
+ ] {
+ set_up();
+ Platform::put_env("http_proxy", proxy);
+
+ if extension_loaded("openssl") {
+ let context = StreamContextFactory::get_context(
+ "http://example.org",
+ map(vec![("http", arr(vec![("header", s("User-Agent: foo"))]))]),
+ IndexMap::new(),
+ )
+ .unwrap();
+ let options = stream_context_get_options(&context);
+
+ let expected_options = map(vec![(
+ "http",
+ arr(vec![
+ ("proxy", s(expected)),
+ ("request_fulluri", PhpMixed::Bool(true)),
+ ("max_redirects", PhpMixed::Int(20)),
+ ("follow_location", PhpMixed::Int(1)),
+ ("header", list(vec![s("User-Agent: foo")])),
+ ]),
+ )]);
+ assert_eq!(expected_options, options);
+ } else {
+ match StreamContextFactory::get_context(
+ "http://example.org",
+ IndexMap::new(),
+ IndexMap::new(),
+ ) {
+ // The catch in PHP asserts the exception is a TransportException; the return type
+ // here already guarantees that.
+ Ok(_) => panic!(),
+ Err(_) => {}
+ }
+ }
+ }
}
#[test]
-#[ignore = "stream_context_get_options shim function does not exist; cannot read back created context to assert"]
+#[ignore]
fn test_ensure_thatfix_http_header_field_moves_content_type_to_end_of_options() {
let _tear_down = TearDown;
set_up();
- todo!()
+ let options = map(vec![(
+ "http",
+ arr(vec![(
+ "header",
+ s(
+ "User-agent: foo\r\nX-Foo: bar\r\nContent-Type: application/json\r\nAuthorization: Basic aW52YWxpZA==",
+ ),
+ )]),
+ )]);
+ let expected_header = vec![
+ s("User-agent: foo"),
+ s("X-Foo: bar"),
+ s("Authorization: Basic aW52YWxpZA=="),
+ s("Content-Type: application/json"),
+ ];
+ let context =
+ StreamContextFactory::get_context("http://example.org", options, IndexMap::new()).unwrap();
+ let ctxoptions = stream_context_get_options(&context);
+ let ctx_header = ctxoptions
+ .get("http")
+ .and_then(|v| v.as_array())
+ .and_then(|a| a.get("header"))
+ .and_then(|v| v.as_list())
+ .unwrap();
+ assert_eq!(expected_header.last().unwrap(), ctx_header.last().unwrap());
}
#[test]