aboutsummaryrefslogtreecommitdiffhomepage
path: root/crates/shirabe/tests/repository
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/tests/repository
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/tests/repository')
-rw-r--r--crates/shirabe/tests/repository/path_repository_test.rs16
-rw-r--r--crates/shirabe/tests/repository/vcs/forgejo_driver_test.rs278
-rw-r--r--crates/shirabe/tests/repository/vcs/perforce_driver_test.rs11
-rw-r--r--crates/shirabe/tests/repository/vcs_repository_test.rs220
4 files changed, 439 insertions, 86 deletions
diff --git a/crates/shirabe/tests/repository/path_repository_test.rs b/crates/shirabe/tests/repository/path_repository_test.rs
index 5679c38..bbf4938 100644
--- a/crates/shirabe/tests/repository/path_repository_test.rs
+++ b/crates/shirabe/tests/repository/path_repository_test.rs
@@ -7,6 +7,8 @@ use indexmap::IndexMap;
use shirabe::config::Config;
use shirabe::io::{IOInterface, NullIO};
use shirabe::repository::PathRepository;
+use shirabe::util::http_downloader::HttpDownloader;
+use shirabe::util::r#loop::Loop;
use shirabe::util::{Platform, ProcessExecutor};
use shirabe_php_shim::{
DIRECTORY_SEPARATOR, PhpMixed, file_get_contents, hash, realpath, serialize,
@@ -28,6 +30,16 @@ fn create_path_repo(options: IndexMap<String, PhpMixed>) -> PathRepository {
let config = Rc::new(RefCell::new(Config::new(true, None)));
let proc = Rc::new(RefCell::new(ProcessExecutor::new(None)));
+ // ref: createPathRepo wires the ProcessExecutor through a Loop so the VersionGuesser's async
+ // git calls are permitted; constructing the Loop calls enable_async() on the shared executor.
+ let http_downloader = Rc::new(RefCell::new(HttpDownloader::new(
+ io.clone(),
+ config.clone(),
+ IndexMap::new(),
+ false,
+ )));
+ let _loop = Loop::new(http_downloader, Some(proc.clone()));
+
PathRepository::new(options, io, config, None, None, Some(proc)).unwrap()
}
@@ -68,7 +80,6 @@ fn test_load_package_from_file_system_with_version() {
);
}
-#[ignore = "version guessing for an unversioned package calls VersionGuesser::guess_git_version, which reaches stream_set_blocking (fcntl(2) not implemented, todo!()) and aborts the process"]
#[test]
fn test_load_package_from_file_system_without_version() {
let repository_url = [
@@ -90,7 +101,6 @@ fn test_load_package_from_file_system_without_version() {
assert!(!package_version.is_empty());
}
-#[ignore = "the without-version fixture matched by the wildcard triggers VersionGuesser::guess_git_version, which reaches stream_set_blocking (fcntl(2) not implemented, todo!()) and aborts the process"]
#[test]
fn test_load_package_from_file_system_with_wildcard() {
let repository_url =
@@ -197,7 +207,6 @@ fn test_url_remains_relative() {
assert_eq!(Some(relative_url), package.get_dist_url());
}
-#[ignore = "the wildcard url also matches the without-version fixture, whose version guessing reaches stream_set_blocking (fcntl(2) not implemented, todo!()) and aborts the process"]
#[test]
fn test_reference_none() {
let options = coordinates(vec![("reference", PhpMixed::String("none".to_string()))]);
@@ -216,7 +225,6 @@ fn test_reference_none() {
}
}
-#[ignore = "the wildcard url also matches the without-version fixture, whose version guessing reaches stream_set_blocking (fcntl(2) not implemented, todo!()) and aborts the process"]
#[test]
fn test_reference_config() {
let options = coordinates(vec![
diff --git a/crates/shirabe/tests/repository/vcs/forgejo_driver_test.rs b/crates/shirabe/tests/repository/vcs/forgejo_driver_test.rs
index ea130ea..1f9ae53 100644
--- a/crates/shirabe/tests/repository/vcs/forgejo_driver_test.rs
+++ b/crates/shirabe/tests/repository/vcs/forgejo_driver_test.rs
@@ -9,15 +9,18 @@ use shirabe::io::IOInterface;
use shirabe::io::null_io::NullIO;
use shirabe::repository::vcs::ForgejoDriver;
use shirabe::util::filesystem::Filesystem;
+use shirabe::util::http_downloader::{HttpDownloader, HttpDownloaderMockHandler};
+use shirabe::util::process_executor::{MockHandler, ProcessExecutor};
use shirabe_php_shim::PhpMixed;
use tempfile::TempDir;
+use crate::http_downloader_mock::{HttpDownloaderMockGuard, expect_full, get_http_downloader_mock};
+use crate::process_executor_mock::{ProcessExecutorMockGuard, get_process_executor_mock};
+
struct SetUp {
home: TempDir,
- config: Config,
- // The IOInterface and HttpDownloader mocks are not ported.
- io: (),
- http_downloader: (),
+ config: Rc<RefCell<Config>>,
+ io: Rc<RefCell<dyn IOInterface>>,
}
fn set_up() -> SetUp {
@@ -36,14 +39,14 @@ fn set_up() -> SetUp {
top.insert("config".to_string(), PhpMixed::Array(config_section));
config.merge(&top, Config::SOURCE_UNKNOWN);
- let io = ();
- let http_downloader = ();
+ // PHP mocks IOInterface with isInteractive() => true; on the passing (200) paths
+ // exercised here the interactivity flag is never consulted, so a bare NullIO matches.
+ let io: Rc<RefCell<dyn IOInterface>> = Rc::new(RefCell::new(NullIO::new()));
SetUp {
home,
- config,
+ config: Rc::new(RefCell::new(config)),
io,
- http_downloader,
}
}
@@ -68,82 +71,233 @@ impl Drop for TearDown {
}
}
-fn supports_provider() -> Vec<(bool, &'static str)> {
- vec![
- (false, "https://example.org/acme/repo"),
- (true, "https://codeberg.org/acme/repository"),
- ]
+// ref: ForgejoDriverTest::initializeDriver
+fn initialize_driver(
+ set_up: &SetUp,
+ repo_url: &str,
+ http_downloader: Rc<RefCell<HttpDownloader>>,
+) -> (ForgejoDriver, ProcessExecutorMockGuard) {
+ let (process, process_guard) = get_process_executor_mock(vec![], false, MockHandler::default());
+
+ let mut repo_config: IndexMap<String, PhpMixed> = IndexMap::new();
+ repo_config.insert("url".to_string(), PhpMixed::String(repo_url.to_string()));
+ let mut driver = ForgejoDriver::new(
+ repo_config,
+ set_up.io.clone(),
+ set_up.config.clone(),
+ http_downloader,
+ process,
+ );
+ driver.initialize().unwrap();
+
+ (driver, process_guard)
}
-#[test]
-#[ignore]
-fn test_supports() {
- for (expected, repo_url) in supports_provider() {
- let io: Rc<RefCell<dyn IOInterface>> = Rc::new(RefCell::new(NullIO::new()));
- let config = Rc::new(RefCell::new(Config::new(true, None)));
+fn http_mock(
+ expectations: Vec<shirabe::util::http_downloader::HttpDownloaderMockExpectation>,
+) -> (Rc<RefCell<HttpDownloader>>, HttpDownloaderMockGuard) {
+ get_http_downloader_mock(expectations, true, HttpDownloaderMockHandler::default())
+}
- assert_eq!(
- expected,
- ForgejoDriver::supports(io, config, repo_url, false).unwrap()
+fn repo_body() -> String {
+ shirabe::json::JsonFile::encode(&PhpMixed::Array({
+ let mut m: IndexMap<String, PhpMixed> = IndexMap::new();
+ m.insert(
+ "default_branch".to_string(),
+ PhpMixed::String("main".to_string()),
);
- }
+ m.insert("has_issues".to_string(), PhpMixed::Bool(true));
+ m.insert("archived".to_string(), PhpMixed::Bool(false));
+ m.insert("private".to_string(), PhpMixed::Bool(false));
+ m.insert(
+ "html_url".to_string(),
+ PhpMixed::String("https://codeberg.org/acme/repo".to_string()),
+ );
+ m.insert(
+ "ssh_url".to_string(),
+ PhpMixed::String("git@codeberg.org:acme/repo.git".to_string()),
+ );
+ m.insert(
+ "clone_url".to_string(),
+ PhpMixed::String("https://codeberg.org/acme/repo.git".to_string()),
+ );
+ m
+ }))
}
-// The remaining cases construct a ForgejoDriver and mock the HttpDownloader to return
-// Forgejo API responses; mocking is not available, and a real HttpDownloader reaches
-// curl_multi_init (todo!()).
#[test]
-#[ignore = "HttpDownloaderMock (getHttpDownloaderMock) and the IOInterface MockObject are not ported"]
fn test_public_repository() {
- let SetUp {
- home,
- config,
- io,
+ let set_up = set_up();
+ let _tear_down = TearDown::new(set_up.home.path().to_path_buf());
+
+ let (http_downloader, _http_guard) = http_mock(vec![expect_full(
+ "https://codeberg.org/api/v1/repos/acme/repo",
+ None,
+ 200,
+ repo_body(),
+ vec![String::new()],
+ )]);
+
+ let (mut driver, _process_guard) = initialize_driver(
+ &set_up,
+ "https://codeberg.org/acme/repo.git",
http_downloader,
- } = set_up();
- let _tear_down = TearDown::new(home.path().to_path_buf());
- let _ = (&config, &io, &http_downloader);
- todo!()
+ );
+ assert_eq!("main", driver.get_root_identifier().unwrap());
+
+ let sha = "SOMESHA";
+ let dist = driver.get_dist(sha).unwrap();
+ assert_eq!("zip", dist.get("type").unwrap());
+ assert_eq!(
+ "https://codeberg.org/api/v1/repos/acme/repo/archive/SOMESHA.zip",
+ dist.get("url").unwrap()
+ );
+ assert_eq!(sha, dist.get("reference").unwrap());
+
+ let source = driver.get_source(sha);
+ assert_eq!("git", source.get("type").unwrap());
+ assert_eq!(
+ "https://codeberg.org/acme/repo.git",
+ source.get("url").unwrap()
+ );
+ assert_eq!(sha, source.get("reference").unwrap());
}
#[test]
-#[ignore = "HttpDownloaderMock (getHttpDownloaderMock) and the IOInterface MockObject are not ported"]
fn test_get_branches() {
- let SetUp {
- home,
- config,
- io,
+ let set_up = set_up();
+ let _tear_down = TearDown::new(set_up.home.path().to_path_buf());
+
+ let branches_body = shirabe::json::JsonFile::encode(&PhpMixed::List(vec![PhpMixed::Array({
+ let mut commit: IndexMap<String, PhpMixed> = IndexMap::new();
+ commit.insert("id".to_string(), PhpMixed::String("SOMESHA".to_string()));
+ let mut entry: IndexMap<String, PhpMixed> = IndexMap::new();
+ entry.insert("name".to_string(), PhpMixed::String("main".to_string()));
+ entry.insert("commit".to_string(), PhpMixed::Array(commit));
+ entry
+ })]));
+
+ let (http_downloader, _http_guard) = http_mock(vec![
+ expect_full(
+ "https://codeberg.org/api/v1/repos/acme/repo",
+ None,
+ 200,
+ repo_body(),
+ vec![String::new()],
+ ),
+ expect_full(
+ "https://codeberg.org/api/v1/repos/acme/repo/branches?per_page=100",
+ None,
+ 200,
+ branches_body,
+ vec![String::new()],
+ ),
+ ]);
+
+ let (mut driver, _process_guard) = initialize_driver(
+ &set_up,
+ "https://codeberg.org/acme/repo.git",
http_downloader,
- } = set_up();
- let _tear_down = TearDown::new(home.path().to_path_buf());
- let _ = (&config, &io, &http_downloader);
- todo!()
+ );
+
+ let mut expected: IndexMap<String, String> = IndexMap::new();
+ expected.insert("main".to_string(), "SOMESHA".to_string());
+ assert_eq!(expected, driver.get_branches().unwrap());
}
#[test]
-#[ignore = "HttpDownloaderMock (getHttpDownloaderMock) and the IOInterface MockObject are not ported"]
fn test_get_tags() {
- let SetUp {
- home,
- config,
- io,
+ let set_up = set_up();
+ let _tear_down = TearDown::new(set_up.home.path().to_path_buf());
+
+ let tags_body = shirabe::json::JsonFile::encode(&PhpMixed::List(vec![PhpMixed::Array({
+ let mut commit: IndexMap<String, PhpMixed> = IndexMap::new();
+ commit.insert("sha".to_string(), PhpMixed::String("SOMESHA".to_string()));
+ let mut entry: IndexMap<String, PhpMixed> = IndexMap::new();
+ entry.insert("name".to_string(), PhpMixed::String("1.0".to_string()));
+ entry.insert("commit".to_string(), PhpMixed::Array(commit));
+ entry
+ })]));
+
+ let (http_downloader, _http_guard) = http_mock(vec![
+ expect_full(
+ "https://codeberg.org/api/v1/repos/acme/repo",
+ None,
+ 200,
+ repo_body(),
+ vec![String::new()],
+ ),
+ expect_full(
+ "https://codeberg.org/api/v1/repos/acme/repo/tags?per_page=100",
+ None,
+ 200,
+ tags_body,
+ vec![String::new()],
+ ),
+ ]);
+
+ let (mut driver, _process_guard) = initialize_driver(
+ &set_up,
+ "https://codeberg.org/acme/repo.git",
http_downloader,
- } = set_up();
- let _tear_down = TearDown::new(home.path().to_path_buf());
- let _ = (&config, &io, &http_downloader);
- todo!()
+ );
+
+ let mut expected: IndexMap<String, String> = IndexMap::new();
+ expected.insert("1.0".to_string(), "SOMESHA".to_string());
+ assert_eq!(expected, driver.get_tags().unwrap());
}
#[test]
-#[ignore = "HttpDownloaderMock (getHttpDownloaderMock) and the IOInterface MockObject are not ported"]
fn test_get_empty_file_content() {
- let SetUp {
- home,
- config,
- io,
+ let set_up = set_up();
+ let _tear_down = TearDown::new(set_up.home.path().to_path_buf());
+
+ let (http_downloader, _http_guard) = http_mock(vec![
+ expect_full(
+ "https://codeberg.org/api/v1/repos/acme/repo",
+ None,
+ 200,
+ repo_body(),
+ vec![String::new()],
+ ),
+ expect_full(
+ "https://codeberg.org/api/v1/repos/acme/repo/contents/composer.json?ref=main",
+ None,
+ 200,
+ "{\"encoding\":\"base64\",\"content\":\"\"}",
+ vec![String::new()],
+ ),
+ ]);
+
+ let (mut driver, _process_guard) = initialize_driver(
+ &set_up,
+ "https://codeberg.org/acme/repo.git",
http_downloader,
- } = set_up();
- let _tear_down = TearDown::new(home.path().to_path_buf());
- let _ = (&config, &io, &http_downloader);
- todo!()
+ );
+
+ assert_eq!(
+ Some(String::new()),
+ driver.get_file_content("composer.json", "main").unwrap()
+ );
+}
+
+fn supports_provider() -> Vec<(bool, &'static str)> {
+ vec![
+ (false, "https://example.org/acme/repo"),
+ (true, "https://codeberg.org/acme/repository"),
+ ]
+}
+
+#[test]
+fn test_supports() {
+ let set_up = set_up();
+ let _tear_down = TearDown::new(set_up.home.path().to_path_buf());
+
+ for (expected, repo_url) in supports_provider() {
+ assert_eq!(
+ expected,
+ ForgejoDriver::supports(set_up.io.clone(), set_up.config.clone(), repo_url, false)
+ .unwrap()
+ );
+ }
}
diff --git a/crates/shirabe/tests/repository/vcs/perforce_driver_test.rs b/crates/shirabe/tests/repository/vcs/perforce_driver_test.rs
index d9178dd..5e9471f 100644
--- a/crates/shirabe/tests/repository/vcs/perforce_driver_test.rs
+++ b/crates/shirabe/tests/repository/vcs/perforce_driver_test.rs
@@ -108,7 +108,6 @@ fn test_supports_returns_false_no_deep_check() {
// The remaining cases mock Perforce, the repository config and IO to drive initialization,
// composer-file detection and cleanup; mocking is not available here.
-#[ignore]
#[test]
fn test_initialize_captures_variables_from_repo_config() {
let SetUp {
@@ -132,9 +131,13 @@ fn test_initialize_captures_variables_from_repo_config() {
IndexMap::new(),
false,
)));
- let process = Rc::new(RefCell::new(shirabe::util::ProcessExecutor::new(Some(
- io.clone(),
- ))));
+ // ref: setUp uses getProcessExecutorMock(); the empty, non-strict mock lets Perforce's p4
+ // queries resolve with the default (exit 0) result so initialize() never shells out to p4.
+ let (process, _process_guard) = crate::process_executor_mock::get_process_executor_mock(
+ vec![],
+ false,
+ shirabe::util::process_executor::MockHandler::default(),
+ );
let mut driver = PerforceDriver::new(repo_config, io, config, http_downloader, process);
driver.initialize().unwrap();
diff --git a/crates/shirabe/tests/repository/vcs_repository_test.rs b/crates/shirabe/tests/repository/vcs_repository_test.rs
index d84ed51..279db04 100644
--- a/crates/shirabe/tests/repository/vcs_repository_test.rs
+++ b/crates/shirabe/tests/repository/vcs_repository_test.rs
@@ -1,29 +1,145 @@
//! ref: composer/tests/Composer/Test/Repository/VcsRepositoryTest.php
use std::cell::RefCell;
+use std::collections::HashSet;
+use std::process::Command;
use std::rc::Rc;
use indexmap::IndexMap;
+use serial_test::serial;
use shirabe::config::Config;
use shirabe::io::IOInterface;
use shirabe::io::null_io::NullIO;
-use shirabe::package::dumper::ArrayDumper;
-use shirabe::repository::RepositoryInterface;
use shirabe::repository::VcsRepository;
+use shirabe::util::ProcessExecutor;
use shirabe::util::filesystem::Filesystem;
use shirabe::util::http_downloader::HttpDownloader;
+use shirabe::util::r#loop::Loop;
use shirabe_php_shim::PhpMixed;
+use tempfile::TempDir;
struct SetUp {
- composer_home: std::path::PathBuf,
- git_repo: std::path::PathBuf,
+ composer_home: TempDir,
+ git_repo: TempDir,
}
-fn set_up() -> SetUp {
- // setUp lazily runs initialize(), which shells out to git to build a fixture repository
- // on disk; the ExecutableFinder/ProcessExecutor-driven setup and the markTestSkipped
- // skip path are not ported.
- todo!()
+// ref: VcsRepositoryTest::initialize. Builds a fixture git repository on disk by shelling out to
+// git. Returns None when git is unavailable (mirroring markTestSkipped).
+fn set_up() -> Option<SetUp> {
+ if which_git().is_none() {
+ // 'This test needs a git binary in the PATH to be able to run'
+ return None;
+ }
+
+ let composer_home = TempDir::new().unwrap();
+ let git_repo = TempDir::new().unwrap();
+ let path = git_repo.path();
+
+ let exec = |args: &[&str]| {
+ let status = Command::new("git")
+ .args(args)
+ .current_dir(path)
+ .env("GIT_CONFIG_GLOBAL", "/dev/null")
+ .env("GIT_CONFIG_SYSTEM", "/dev/null")
+ .status()
+ .unwrap_or_else(|e| panic!("Failed to execute git {:?}: {}", args, e));
+ if !status.success() {
+ panic!("Failed to execute git {:?}", args);
+ }
+ };
+ let write_file = |name: &str, contents: &str| {
+ std::fs::write(path.join(name), contents).unwrap();
+ };
+
+ // init
+ exec(&["init", "-q"]);
+ exec(&["checkout", "-b", "master"]);
+ exec(&["config", "user.email", "composertest@example.org"]);
+ exec(&["config", "user.name", "ComposerTest"]);
+ exec(&["config", "commit.gpgsign", "false"]);
+ write_file("foo", "");
+ exec(&["add", "foo"]);
+ exec(&["commit", "-m", "init"]);
+
+ // non-composed tag & branch
+ exec(&["tag", "0.5.0"]);
+ exec(&["branch", "oldbranch"]);
+
+ // add composed tag & master branch
+ write_file(
+ "composer.json",
+ &shirabe::json::JsonFile::encode(&composer(None)),
+ );
+ exec(&["add", "composer.json"]);
+ exec(&["commit", "-m", "addcomposer"]);
+ exec(&["tag", "0.6.0"]);
+
+ // add feature-a branch
+ exec(&["checkout", "-b", "feature/a-1.0-B"]);
+ write_file("foo", "bar feature");
+ exec(&["add", "foo"]);
+ exec(&["commit", "-m", "change-a"]);
+
+ // add foo#bar branch which should result in dev-foo+bar
+ exec(&["branch", "foo#bar"]);
+
+ // add version to composer.json
+ exec(&["checkout", "master"]);
+ write_file(
+ "composer.json",
+ &shirabe::json::JsonFile::encode(&composer(Some("1.0.0"))),
+ );
+ exec(&["add", "composer.json"]);
+ exec(&["commit", "-m", "addversion"]);
+
+ // create tag with wrong version in it
+ exec(&["tag", "0.9.0"]);
+ // create tag with correct version in it
+ exec(&["tag", "1.0.0"]);
+
+ // add feature-b branch
+ exec(&["checkout", "-b", "feature-b"]);
+ write_file("foo", "baz feature");
+ exec(&["add", "foo"]);
+ exec(&["commit", "-m", "change-b"]);
+
+ // add 1.0 branch
+ exec(&["checkout", "master"]);
+ exec(&["branch", "1.0"]);
+
+ // add 1.0.x branch
+ exec(&["branch", "1.1.x"]);
+
+ // update master to 2.0
+ write_file(
+ "composer.json",
+ &shirabe::json::JsonFile::encode(&composer(Some("2.0.0"))),
+ );
+ exec(&["add", "composer.json"]);
+ exec(&["commit", "-m", "bump-version"]);
+
+ Some(SetUp {
+ composer_home,
+ git_repo,
+ })
+}
+
+fn composer(version: Option<&str>) -> PhpMixed {
+ let mut m: IndexMap<String, PhpMixed> = IndexMap::new();
+ m.insert("name".to_string(), PhpMixed::String("a/b".to_string()));
+ if let Some(version) = version {
+ m.insert("version".to_string(), PhpMixed::String(version.to_string()));
+ }
+ PhpMixed::Array(m)
+}
+
+fn which_git() -> Option<()> {
+ Command::new("git")
+ .arg("--version")
+ .output()
+ .ok()
+ .filter(|o| o.status.success())
+ .map(|_| ())
}
fn tear_down(composer_home: &std::path::Path, git_repo: &std::path::Path) {
@@ -52,13 +168,85 @@ impl Drop for TearDown {
}
}
-// testLoadVersions initialises a real git repository on disk and drives a VcsRepository over
-// it, then asserts the loaded package versions; the git fixture setup and constraint parsing
-// (look-around regex) are not ported. In addition, VcsRepository does not implement
-// RepositoryInterface and keeps its inner ArrayRepository pub(crate), so getPackages() (inherited
-// from ArrayRepository in PHP) is not reachable from the test crate.
#[test]
-#[ignore = "VcsRepository does not expose get_packages() to the test crate; git fixture setup not ported"]
+#[serial]
fn test_load_versions() {
- todo!()
+ let Some(set_up) = set_up() else {
+ // git binary unavailable; skip like markTestSkipped.
+ return;
+ };
+ let composer_home = set_up.composer_home.path().to_path_buf();
+ let git_repo = set_up.git_repo.path().to_path_buf();
+
+ let mut expected: HashSet<String> = [
+ "0.6.0",
+ "1.0.0",
+ "1.0.x-dev",
+ "1.1.x-dev",
+ "dev-feature-b",
+ "dev-feature/a-1.0-B",
+ "dev-foo+bar",
+ "dev-master",
+ "9999999-dev", // alias of dev-master
+ ]
+ .iter()
+ .map(|s| s.to_string())
+ .collect();
+
+ let mut config = Config::new(true, None);
+ let mut top: IndexMap<String, PhpMixed> = IndexMap::new();
+ let mut config_section: IndexMap<String, PhpMixed> = IndexMap::new();
+ config_section.insert(
+ "home".to_string(),
+ PhpMixed::String(composer_home.to_string_lossy().into_owned()),
+ );
+ top.insert("config".to_string(), PhpMixed::Array(config_section));
+ config.merge(&top, Config::SOURCE_UNKNOWN);
+ let config = Rc::new(RefCell::new(config));
+
+ let io: Rc<RefCell<dyn IOInterface>> = Rc::new(RefCell::new(NullIO::new()));
+ let http_downloader = Rc::new(RefCell::new(HttpDownloader::new(
+ io.clone(),
+ config.clone(),
+ IndexMap::new(),
+ false,
+ )));
+ let process = Rc::new(RefCell::new(ProcessExecutor::new(Some(io.clone()))));
+ // VcsRepository's git driver / VersionGuesser run async git processes; constructing a Loop
+ // enables async on the shared ProcessExecutor.
+ let _loop = Loop::new(http_downloader.clone(), Some(process.clone()));
+
+ let mut repo_config: IndexMap<String, PhpMixed> = IndexMap::new();
+ repo_config.insert(
+ "url".to_string(),
+ PhpMixed::String(git_repo.to_string_lossy().into_owned()),
+ );
+ repo_config.insert("type".to_string(), PhpMixed::String("vcs".to_string()));
+
+ let mut repo = VcsRepository::new(
+ repo_config,
+ io,
+ config,
+ http_downloader,
+ None,
+ Some(process),
+ None,
+ None,
+ )
+ .unwrap();
+
+ let _tear_down = TearDown::new(composer_home, git_repo);
+
+ let packages = repo.__get_packages().unwrap();
+
+ for package in &packages {
+ let pretty = package.get_pretty_version();
+ assert!(expected.remove(&pretty), "Unexpected version {}", pretty);
+ }
+
+ assert!(
+ expected.is_empty(),
+ "Missing versions: {}",
+ expected.into_iter().collect::<Vec<_>>().join(", ")
+ );
}