aboutsummaryrefslogtreecommitdiffhomepage
path: root/crates/shirabe/src
diff options
context:
space:
mode:
authornsfisis <nsfisis@gmail.com>2026-06-25 17:49:35 +0900
committernsfisis <nsfisis@gmail.com>2026-06-26 00:20:05 +0900
commitd4ec1dcb6fc04ff029ded35cc2fcc14f3bbc02ad (patch)
tree0a85bd085961fcdfd5570cafc3b44ba5eb8cb152 /crates/shirabe/src
parent22d0b327368e5f39de6f381046c081d08efdba15 (diff)
downloadphp-shirabe-d4ec1dcb6fc04ff029ded35cc2fcc14f3bbc02ad.tar.gz
php-shirabe-d4ec1dcb6fc04ff029ded35cc2fcc14f3bbc02ad.tar.zst
php-shirabe-d4ec1dcb6fc04ff029ded35cc2fcc14f3bbc02ad.zip
test: port 32 command/repository/downloader tests
Add create_installed_json/create_composer_lock test helpers. Port command (8), repository path/forgejo/perforce/vcs (11), and fossil/hg/download_manager (13) tests. Fix production porting bugs: root_package_loader/forgejo_url/version_bumper regex delimiters, repository_manager create_repository_by_class, array_loader isset, licenses_command RefCell borrow; implement disk_free_space and touch2/touch3 via libc. Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
Diffstat (limited to 'crates/shirabe/src')
-rw-r--r--crates/shirabe/src/command/licenses_command.rs10
-rw-r--r--crates/shirabe/src/package/loader/array_loader.rs22
-rw-r--r--crates/shirabe/src/package/loader/root_package_loader.rs14
-rw-r--r--crates/shirabe/src/package/version/version_bumper.rs12
-rw-r--r--crates/shirabe/src/repository/handle.rs8
-rw-r--r--crates/shirabe/src/repository/installed_repository.rs6
-rw-r--r--crates/shirabe/src/repository/repository_manager.rs26
-rw-r--r--crates/shirabe/src/repository/vcs_repository.rs12
-rw-r--r--crates/shirabe/src/util/forgejo_url.rs2
9 files changed, 84 insertions, 28 deletions
diff --git a/crates/shirabe/src/command/licenses_command.rs b/crates/shirabe/src/command/licenses_command.rs
index 3c1745a..3be4df3 100644
--- a/crates/shirabe/src/command/licenses_command.rs
+++ b/crates/shirabe/src/command/licenses_command.rs
@@ -107,8 +107,7 @@ impl Command for LicensesCommand {
input: Rc<RefCell<dyn InputInterface>>,
output: Rc<RefCell<dyn OutputInterface>>,
) -> anyhow::Result<i64> {
- let composer = self.require_composer(None, None)?;
- let mut composer = crate::command::composer_full_mut(&composer);
+ let composer_handle = self.require_composer(None, None)?;
// TODO(plugin): dispatch COMMAND event for plugin hooks
let command_event = CommandEvent::new(
@@ -117,11 +116,14 @@ impl Command for LicensesCommand {
input.clone(),
output.clone(),
);
- composer
- .get_event_dispatcher()
+ // The event dispatcher reads back through the shared Composer handle (script listeners), so the
+ // dispatch must run while no other borrow of that handle is held.
+ let event_dispatcher = composer_handle.borrow_partial().get_event_dispatcher();
+ event_dispatcher
.borrow_mut()
.dispatch(Some(command_event.get_name()), None);
+ let mut composer = crate::command::composer_full_mut(&composer_handle);
let root = composer.get_package();
let packages = if input
diff --git a/crates/shirabe/src/package/loader/array_loader.rs b/crates/shirabe/src/package/loader/array_loader.rs
index 844b3ef..20280e0 100644
--- a/crates/shirabe/src/package/loader/array_loader.rs
+++ b/crates/shirabe/src/package/loader/array_loader.rs
@@ -370,7 +370,7 @@ impl ArrayLoader {
package.package_mut().set_is_default_branch(true);
}
- if let Some(source) = config.get("source").cloned() {
+ if let Some(source) = config.get("source").filter(|v| !v.is_null()).cloned() {
let source_map = match &source {
PhpMixed::Array(m) => Some(m.clone()),
_ => None,
@@ -404,9 +404,12 @@ impl ArrayLoader {
package
.package_mut()
.set_source_url(source_map.get("url").map(|v| strval(v)));
- package
- .package_mut()
- .set_source_reference(source_map.get("reference").map(|v| strval(v)));
+ package.package_mut().set_source_reference(
+ source_map
+ .get("reference")
+ .filter(|v| !v.is_null())
+ .map(|v| strval(v)),
+ );
if let Some(mirrors) = source_map.get("mirrors") {
package
.package_mut()
@@ -414,7 +417,7 @@ impl ArrayLoader {
}
}
- if let Some(dist) = config.get("dist").cloned() {
+ if let Some(dist) = config.get("dist").filter(|v| !v.is_null()).cloned() {
let dist_map = match &dist {
PhpMixed::Array(m) => Some(m.clone()),
_ => None,
@@ -446,9 +449,12 @@ impl ArrayLoader {
package
.package_mut()
.set_dist_url(dist_map.get("url").map(|v| strval(v)));
- package
- .package_mut()
- .set_dist_reference(dist_map.get("reference").map(|v| strval(v)));
+ package.package_mut().set_dist_reference(
+ dist_map
+ .get("reference")
+ .filter(|v| !v.is_null())
+ .map(|v| strval(v)),
+ );
package
.package_mut()
.set_dist_sha1_checksum(dist_map.get("shasum").map(|v| strval(v)));
diff --git a/crates/shirabe/src/package/loader/root_package_loader.rs b/crates/shirabe/src/package/loader/root_package_loader.rs
index 9cd1eac..308706f 100644
--- a/crates/shirabe/src/package/loader/root_package_loader.rs
+++ b/crates/shirabe/src/package/loader/root_package_loader.rs
@@ -266,7 +266,7 @@ impl RootPackageLoader {
for (req_name, req_version) in requires {
let mut m: IndexMap<CaptureKey, String> = IndexMap::new();
if Preg::is_match3(
- r"(?:^|\| *|, *)([^,\s#|]+)(?:#[^ ]+)? +as +([^,\s|]+)(?:$| *\|| *)",
+ r"{(?:^|\| *|, *)([^,\s#|]+)(?:#[^ ]+)? +as +([^,\s|]+)(?:$| *\|| *,)}",
req_version,
Some(&mut m),
) {
@@ -320,7 +320,7 @@ impl RootPackageLoader {
for (req_name, req_version) in requires {
let mut constraints: Vec<String> = vec![];
- let or_split = Preg::split(r"\s*\|\|?\s*", req_version.trim());
+ let or_split = Preg::split(r"{\s*\|\|?\s*}", req_version.trim());
for or_constraint in &or_split {
let and_split = shirabe_semver::split_and_constraints(or_constraint);
for and_constraint in and_split {
@@ -329,7 +329,7 @@ impl RootPackageLoader {
}
let stability_names: Vec<&str> = stabilities.keys().copied().collect();
- let pattern = format!("^[^@]*?@({})$", stability_names.join("|"));
+ let pattern = format!("{{^[^@]*?@({})$}}i", stability_names.join("|"));
let mut matched = false;
for constraint in &constraints {
@@ -353,8 +353,8 @@ impl RootPackageLoader {
}
for constraint in &constraints {
- let req_version_stripped = Preg::replace(r"^([^,\s@]+) as .+$", "$1", constraint);
- if Preg::is_match(r"^[^,\s@]+$", &req_version_stripped) {
+ let req_version_stripped = Preg::replace(r"{^([^,\s@]+) as .+$}", "$1", constraint);
+ if Preg::is_match(r"{^[^,\s@]+$}", &req_version_stripped) {
let stability_name = VersionParser::parse_stability(&req_version_stripped);
if stability_name != "stable" {
let name = strtolower(req_name);
@@ -378,9 +378,9 @@ impl RootPackageLoader {
mut references: IndexMap<String, String>,
) -> IndexMap<String, String> {
for (req_name, req_version) in requires {
- let req_version = Preg::replace(r"^([^,\s@]+) as .+$", "$1", req_version);
+ let req_version = Preg::replace(r"{^([^,\s@]+) as .+$}", "$1", req_version);
let mut m: IndexMap<CaptureKey, String> = IndexMap::new();
- if Preg::is_match3(r"^[^,\s@]+?#([a-f0-9]+)$", &req_version, Some(&mut m))
+ if Preg::is_match3(r"{^[^,\s@]+?#([a-f0-9]+)$}", &req_version, Some(&mut m))
&& VersionParser::parse_stability(&req_version) == "dev"
{
let name = strtolower(req_name);
diff --git a/crates/shirabe/src/package/version/version_bumper.rs b/crates/shirabe/src/package/version/version_bumper.rs
index c029e3b..4deb450 100644
--- a/crates/shirabe/src/package/version/version_bumper.rs
+++ b/crates/shirabe/src/package/version/version_bumper.rs
@@ -54,9 +54,17 @@ impl VersionBumper {
return Ok(pretty_constraint);
}
+ // Regex pattern compatibility:
+ // PHP uses zero-width look-behind `(?<=,|\ |\||^)` and look-ahead `(?=,|$|\ |\||@)` to require
+ // a separator around the constraint. The `regex` crate supports neither, so the separators are
+ // matched as *consuming* groups instead. Only the `constraint` named group's own offset/length
+ // is used for the replacement below (never group 0), so consuming the surrounding separators
+ // does not shift the rewrite. The single-character `|`/space/comma separators are not consumed
+ // by both an adjacent trailing and leading match, but Composer never packs two same-major
+ // constraints around a single such separator (alternatives use `||`, ranges use spaces).
let pattern = format!(
r#"{{
- (?<=,|\ |\||^) # leading separator
+ (?:^|[,\ |]) # leading separator
(?P<constraint>
\^v?{major}(?:\.\d+)* # e.g. ^2.anything
| ~v?{major}(?:\.\d+){{1,3}} # e.g. ~2.2 or ~2.2.2 or ~2.2.2.2
@@ -64,7 +72,7 @@ impl VersionBumper {
| >=v?\d(?:\.\d+)* # e.g. >=2 or >=1.2 etc
| \* # full wildcard
)
- (?=,|$|\ |\||@) # trailing separator
+ (?:$|[,\ |@]) # trailing separator
}}x"#,
major = major
);
diff --git a/crates/shirabe/src/repository/handle.rs b/crates/shirabe/src/repository/handle.rs
index 2e83ef8..90a9090 100644
--- a/crates/shirabe/src/repository/handle.rs
+++ b/crates/shirabe/src/repository/handle.rs
@@ -148,6 +148,14 @@ impl RepositoryInterfaceHandle {
// --- InstalledRepositoryInterface helpers (valid only when the wrapped repository is one) ---
+ /// PHP `$repository instanceof InstalledRepositoryInterface`.
+ pub fn is_installed_repository_interface(&self) -> bool {
+ self.0
+ .borrow()
+ .as_installed_repository_interface()
+ .is_some()
+ }
+
pub fn is_fresh(&self) -> bool {
self.0
.borrow()
diff --git a/crates/shirabe/src/repository/installed_repository.rs b/crates/shirabe/src/repository/installed_repository.rs
index 55ec50f..cfa5a7a 100644
--- a/crates/shirabe/src/repository/installed_repository.rs
+++ b/crates/shirabe/src/repository/installed_repository.rs
@@ -364,12 +364,14 @@ impl InstalledRepository {
}
pub fn add_repository(&mut self, repository: RepositoryInterfaceHandle) {
- // TODO: type guard?
assert!(
repository.is::<LockArrayRepository>()
+ || repository.is_installed_repository_interface()
|| repository.is::<RootPackageRepository>()
|| repository.is::<PlatformRepository>(),
- "An InstalledRepository can contain a repository of type: LockArrayRepository, RootPackageRepository or PlatformRepository"
+ "An InstalledRepository can not contain a repository of type {} ({})",
+ repository.get_repo_name(),
+ repository.get_repo_name()
);
self.inner.add_repository(repository);
diff --git a/crates/shirabe/src/repository/repository_manager.rs b/crates/shirabe/src/repository/repository_manager.rs
index 403b504..247236c 100644
--- a/crates/shirabe/src/repository/repository_manager.rs
+++ b/crates/shirabe/src/repository/repository_manager.rs
@@ -147,10 +147,30 @@ impl RepositoryManager {
fn create_repository_by_class(
&self,
- _class: &str,
- _config: IndexMap<String, PhpMixed>,
+ class: &str,
+ config: IndexMap<String, PhpMixed>,
) -> anyhow::Result<RepositoryInterfaceHandle> {
- todo!("Phase B: dynamic class instantiation by class name")
+ // PHP: `new $class($config, $this->io, $this->config, $this->httpDownloader,
+ // $this->eventDispatcher, $this->process)`. Rust cannot instantiate by string class name, so
+ // dispatch over the classes registered in `createDefaultRepositoryManager`.
+ match class {
+ "Composer\\Repository\\ComposerRepository" => Ok(RepositoryInterfaceHandle::new(
+ crate::repository::ComposerRepository::new(
+ config,
+ self.io.clone(),
+ &self.config.borrow(),
+ self.http_downloader.clone(),
+ self.event_dispatcher.clone(),
+ )?,
+ )),
+ "Composer\\Repository\\PackageRepository" => Ok(RepositoryInterfaceHandle::new(
+ crate::repository::PackageRepository::new(config),
+ )),
+ other => todo!(
+ "Phase B: dynamic class instantiation by class name: {}",
+ other
+ ),
+ }
}
pub fn set_repository_class(&mut self, r#type: &str, class: &str) {
diff --git a/crates/shirabe/src/repository/vcs_repository.rs b/crates/shirabe/src/repository/vcs_repository.rs
index 526aa01..7a143eb 100644
--- a/crates/shirabe/src/repository/vcs_repository.rs
+++ b/crates/shirabe/src/repository/vcs_repository.rs
@@ -264,6 +264,16 @@ impl VcsRepository {
&self.version_transport_exceptions
}
+ /// For testing only: drives `initialize` (which shells out to the VCS driver to discover
+ /// tags/branches and load each package) and returns the packages collected by the inner
+ /// `ArrayRepository`, mirroring the polymorphic `RepositoryInterface::getPackages` dispatch
+ /// in PHP where `VcsRepository` inherits `getPackages` from `ArrayRepository`.
+ pub fn __get_packages(&mut self) -> anyhow::Result<Vec<crate::package::BasePackageHandle>> {
+ self.initialize()?;
+ use crate::repository::RepositoryInterface;
+ self.inner.get_packages()
+ }
+
pub fn initialize(&mut self) -> Result<()> {
self.inner.initialize();
@@ -282,7 +292,7 @@ impl VcsRepository {
self.version_parser = Some(VersionParser::new());
if self.loader.is_none() {
self.loader = Some(Box::new(ArrayLoader::new(
- Some(todo!("phase-b: clone VersionParser")),
+ self.version_parser.clone(),
false,
)));
}
diff --git a/crates/shirabe/src/util/forgejo_url.rs b/crates/shirabe/src/util/forgejo_url.rs
index 85f2238..d336b87 100644
--- a/crates/shirabe/src/util/forgejo_url.rs
+++ b/crates/shirabe/src/util/forgejo_url.rs
@@ -14,7 +14,7 @@ pub struct ForgejoUrl {
impl ForgejoUrl {
pub const URL_REGEX: &'static str =
- r"^(?:(?:https?|git)://([^/]+)/|git@([^:]+):/?)([^/]+)/([^/]+?)(?:\.git|/)?$";
+ r"{^(?:(?:https?|git)://([^/]+)/|git@([^:]+):/?)([^/]+)/([^/]+?)(?:\.git|/)?$}";
fn new(owner: String, repository: String, origin_url: String, api_url: String) -> Self {
Self {