diff options
| author | nsfisis <nsfisis@gmail.com> | 2026-06-25 15:12:06 +0900 |
|---|---|---|
| committer | nsfisis <nsfisis@gmail.com> | 2026-06-26 00:20:05 +0900 |
| commit | 8117e5450693d19726da877bce8aacf5c7aa53af (patch) | |
| tree | 4b06e6f6924599f3402d29cdcdeb0f8a88354d89 /crates/shirabe/tests/package | |
| parent | 8dd3d67884a204ca40e3364206868fea77a312be (diff) | |
| download | php-shirabe-8117e5450693d19726da877bce8aacf5c7aa53af.tar.gz php-shirabe-8117e5450693d19726da877bce8aacf5c7aa53af.tar.zst php-shirabe-8117e5450693d19726da877bce8aacf5c7aa53af.zip | |
test: port 44 vcs/downloader/version tests using mock infra
Port git, version_guesser, gitlab_driver, github_driver, and git_downloader
tests using the ProcessExecutor/HttpDownloader mocks and IO/Config stubs.
Fix production regex-porting bugs surfaced by the now-reachable paths:
Url::sanitize and Response::find_header_value had non-delimited PCRE patterns;
implement array_search_mixed non-strict branch and a datetime format mapping.
Add HttpDownloader::__new_mock so mocked downloaders skip curl construction.
Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
Diffstat (limited to 'crates/shirabe/tests/package')
| -rw-r--r-- | crates/shirabe/tests/package/main.rs | 6 | ||||
| -rw-r--r-- | crates/shirabe/tests/package/version/version_guesser_test.rs | 571 |
2 files changed, 535 insertions, 42 deletions
diff --git a/crates/shirabe/tests/package/main.rs b/crates/shirabe/tests/package/main.rs index 57d7cd3..7d841b4 100644 --- a/crates/shirabe/tests/package/main.rs +++ b/crates/shirabe/tests/package/main.rs @@ -1,3 +1,9 @@ +#[path = "../common/config_stub.rs"] +mod config_stub; +#[path = "../common/io_stub.rs"] +mod io_stub; +#[path = "../common/process_executor_mock.rs"] +mod process_executor_mock; #[path = "../common/test_case.rs"] mod test_case; diff --git a/crates/shirabe/tests/package/version/version_guesser_test.rs b/crates/shirabe/tests/package/version/version_guesser_test.rs index 1ec0ed4..6bfe6c1 100644 --- a/crates/shirabe/tests/package/version/version_guesser_test.rs +++ b/crates/shirabe/tests/package/version/version_guesser_test.rs @@ -3,121 +3,608 @@ use std::cell::RefCell; use std::rc::Rc; +use indexmap::IndexMap; +use serial_test::serial; use shirabe::config::Config; use shirabe::package::version::{VersionGuesser, VersionParser}; +use shirabe::util::Git as GitUtil; use shirabe::util::platform::Platform; -use shirabe::util::process_executor::ProcessExecutor; +use shirabe::util::process_executor::{MockExpectation, MockHandler, ProcessExecutor}; +use shirabe_php_shim::PhpMixed; -#[allow(dead_code)] -fn set_up() { - // Resets GitUtil's cached `version` static via ReflectionProperty; the static is not - // exposed here and reflection-based mutation has no ported equivalent. - todo!() -} +use crate::process_executor_mock::{cmd, cmd_full, get_process_executor_mock}; -#[allow(dead_code)] -fn tear_down() { - todo!() +// Mirrors VersionGuesserTest::setUp/tearDown: reset GitUtil's cached `version` +// static so each test re-runs `git --version` against its own mock. +fn set_up() { + GitUtil::__reset_version(); } -#[allow(dead_code)] struct TearDown; impl Drop for TearDown { fn drop(&mut self) { - tear_down(); + GitUtil::__reset_version(); } } -// These drive VersionGuesser with a mocked ProcessExecutor feeding git/hg command output; -// mocking is not available here. -#[ignore = "requires getProcessExecutorMock with expects() command expectations; no ProcessExecutorMock mocking infrastructure exists"] +// `$config = new Config; $config->merge(['repositories' => ['packagist' => false]]);` +fn make_config() -> Rc<RefCell<Config>> { + let mut config = Config::new(true, None); + let mut repositories: IndexMap<String, PhpMixed> = IndexMap::new(); + repositories.insert("packagist".to_string(), PhpMixed::Bool(false)); + let mut merge: IndexMap<String, PhpMixed> = IndexMap::new(); + merge.insert("repositories".to_string(), PhpMixed::Array(repositories)); + config.merge(&merge, Config::SOURCE_UNKNOWN); + Rc::new(RefCell::new(config)) +} + +fn make_guesser( + config: Rc<RefCell<Config>>, + process: Rc<RefCell<ProcessExecutor>>, +) -> VersionGuesser { + VersionGuesser::new(config, process, VersionParser::new(), None) +} + +// Helper to build a `['key' => 'value']` package config used in the tests. +fn package_config(entries: &[(&str, PhpMixed)]) -> IndexMap<String, PhpMixed> { + let mut map: IndexMap<String, PhpMixed> = IndexMap::new(); + for (k, v) in entries { + map.insert(k.to_string(), v.clone()); + } + map +} + +fn string_list(items: &[&str]) -> PhpMixed { + PhpMixed::List( + items + .iter() + .map(|s| PhpMixed::String(s.to_string())) + .collect(), + ) +} + +#[ignore = "guess_hg_version builds an HttpDownloader for HgDriver, which calls curl_multi_init() (todo!() in shirabe-php-shim::curl)"] #[test] +#[serial] fn test_hg_guess_version_returns_data() { - todo!() + set_up(); + let _td = TearDown; + let branch = "default"; + + let expectations: Vec<MockExpectation> = vec![ + cmd_full(["git", "--version"], 0, "git version 2.33.0", ""), + cmd_full( + ["git", "branch", "-a", "--no-color", "--no-abbrev", "-v"], + 128, + "", + "", + ), + cmd_full(["git", "describe", "--exact-match", "--tags"], 128, "", ""), + cmd_full( + [ + "git", + "rev-list", + "--no-commit-header", + "--format=%H", + "-n1", + "HEAD", + "--no-show-signature", + ], + 128, + "", + "", + ), + cmd_full(["hg", "branch"], 0, branch, ""), + cmd_full(["hg", "branches"], 0, "", ""), + cmd_full(["hg", "bookmarks"], 0, "", ""), + ]; + let (process, _guard) = get_process_executor_mock(expectations, true, MockHandler::default()); + + let config = make_config(); + let mut guesser = make_guesser(config, process); + let version_data = guesser + .guess_version(&IndexMap::new(), "dummy/path") + .unwrap() + .expect("expected version data"); + + assert_eq!(format!("dev-{}", branch), version_data.version.unwrap()); + assert_eq!( + format!("dev-{}", branch), + version_data.pretty_version.unwrap() + ); + assert!(version_data.commit.as_deref().unwrap_or("").is_empty()); } -#[ignore = "requires getProcessExecutorMock with expects() command expectations; no ProcessExecutorMock mocking infrastructure exists"] #[test] +#[serial] fn test_guess_version_returns_data() { - todo!() + set_up(); + let _td = TearDown; + let commit_hash = "03a15d220da53c52eddd5f32ffca64a7b3801bea"; + let another_commit_hash = "03a15d220da53c52eddd5f32ffca64a7b3801bea"; + + let expectations: Vec<MockExpectation> = vec![ + cmd_full(["git", "--version"], 0, "git version 2.52.0", ""), + cmd_full( + ["git", "branch", "-a", "--no-color", "--no-abbrev", "-v"], + 0, + format!( + "* master {} Commit message\n(no branch) {} Commit message\n", + commit_hash, another_commit_hash + ), + "", + ), + ]; + let (process, _guard) = get_process_executor_mock(expectations, true, MockHandler::default()); + + let config = make_config(); + let mut guesser = make_guesser(config, process); + let version_data = guesser + .guess_version(&IndexMap::new(), "dummy/path") + .unwrap() + .expect("expected version data"); + + assert_eq!("dev-master", version_data.version.unwrap()); + assert_eq!("dev-master", version_data.pretty_version.unwrap()); + assert!(version_data.feature_version.is_none()); + assert!(version_data.feature_pretty_version.is_none()); + assert_eq!(commit_hash, version_data.commit.unwrap()); } -#[ignore = "requires getProcessExecutorMock with expects() command expectations; no ProcessExecutorMock mocking infrastructure exists"] #[test] +#[serial] fn test_guess_version_does_not_see_custom_default_branch_as_non_feature_branch() { - todo!() + set_up(); + let _td = TearDown; + let commit_hash = "03a15d220da53c52eddd5f32ffca64a7b3801bea"; + let another_commit_hash = "13a15d220da53c52eddd5f32ffca64a7b3801bea"; + + let expectations: Vec<MockExpectation> = vec![ + cmd_full(["git", "--version"], 0, "git version 2.52.0", ""), + cmd_full( + ["git", "branch", "-a", "--no-color", "--no-abbrev", "-v"], + 0, + // Assumption here is that arbitrary would be the default branch + format!( + " arbitrary {} Commit message\n* current {} Another message\n", + commit_hash, another_commit_hash + ), + "", + ), + ]; + let (process, _guard) = get_process_executor_mock(expectations, true, MockHandler::default()); + + let config = make_config(); + let mut guesser = make_guesser(config, process); + let version_data = guesser + .guess_version( + &package_config(&[("version", PhpMixed::String("self.version".to_string()))]), + "dummy/path", + ) + .unwrap() + .expect("expected version data"); + + assert_eq!("dev-current", version_data.version.unwrap()); + assert_eq!(another_commit_hash, version_data.commit.unwrap()); } -#[ignore = "requires getProcessExecutorMock with expects() command expectations; no ProcessExecutorMock mocking infrastructure exists"] +#[ignore = "feature-branch guessing calls ProcessExecutor::execute_async, whose mock path is todo!()"] #[test] +#[serial] fn test_guess_version_reads_and_respects_non_feature_branches_configuration_for_arbitrary_naming() { - todo!() + set_up(); + let _td = TearDown; + let commit_hash = "03a15d220da53c52eddd5f32ffca64a7b3801bea"; + let another_commit_hash = "13a15d220da53c52eddd5f32ffca64a7b3801bea"; + + let expectations: Vec<MockExpectation> = vec![ + cmd_full(["git", "--version"], 0, "git version 2.52.0", ""), + cmd_full( + ["git", "branch", "-a", "--no-color", "--no-abbrev", "-v"], + 0, + format!( + " arbitrary {} Commit message\n* feature {} Another message\n", + commit_hash, another_commit_hash + ), + "", + ), + cmd_full( + ["git", "rev-list", "arbitrary..feature"], + 0, + format!("{}\n", another_commit_hash), + "", + ), + ]; + let (process, _guard) = get_process_executor_mock(expectations, true, MockHandler::default()); + + let config = make_config(); + let mut guesser = make_guesser(config, process); + let version_data = guesser + .guess_version( + &package_config(&[ + ("version", PhpMixed::String("self.version".to_string())), + ("non-feature-branches", string_list(&["arbitrary"])), + ]), + "dummy/path", + ) + .unwrap() + .expect("expected version data"); + + assert_eq!("dev-arbitrary", version_data.version.unwrap()); + assert_eq!(another_commit_hash, version_data.commit.unwrap()); + assert_eq!( + "dev-feature", + version_data.feature_version.as_deref().unwrap() + ); + assert_eq!( + "dev-feature", + version_data.feature_pretty_version.as_deref().unwrap() + ); } -#[ignore = "requires getProcessExecutorMock with expects() command expectations; no ProcessExecutorMock mocking infrastructure exists"] +#[ignore = "feature-branch guessing calls ProcessExecutor::execute_async, whose mock path is todo!()"] #[test] +#[serial] fn test_guess_version_reads_and_respects_non_feature_branches_configuration_for_arbitrary_naming_regex() { - todo!() + set_up(); + let _td = TearDown; + let commit_hash = "03a15d220da53c52eddd5f32ffca64a7b3801bea"; + let another_commit_hash = "13a15d220da53c52eddd5f32ffca64a7b3801bea"; + + let expectations: Vec<MockExpectation> = vec![ + cmd_full(["git", "--version"], 0, "git version 2.52.0", ""), + cmd_full( + ["git", "branch", "-a", "--no-color", "--no-abbrev", "-v"], + 0, + format!( + " latest-testing {} Commit message\n* feature {} Another message\n", + commit_hash, another_commit_hash + ), + "", + ), + cmd_full( + ["git", "rev-list", "latest-testing..feature"], + 0, + format!("{}\n", another_commit_hash), + "", + ), + ]; + let (process, _guard) = get_process_executor_mock(expectations, true, MockHandler::default()); + + let config = make_config(); + let mut guesser = make_guesser(config, process); + let version_data = guesser + .guess_version( + &package_config(&[ + ("version", PhpMixed::String("self.version".to_string())), + ("non-feature-branches", string_list(&["latest-.*"])), + ]), + "dummy/path", + ) + .unwrap() + .expect("expected version data"); + + assert_eq!("dev-latest-testing", version_data.version.unwrap()); + assert_eq!(another_commit_hash, version_data.commit.unwrap()); + assert_eq!( + "dev-feature", + version_data.feature_version.as_deref().unwrap() + ); + assert_eq!( + "dev-feature", + version_data.feature_pretty_version.as_deref().unwrap() + ); } -#[ignore = "requires getProcessExecutorMock with expects() command expectations; no ProcessExecutorMock mocking infrastructure exists"] #[test] +#[serial] fn test_guess_version_reads_and_respects_non_feature_branches_configuration_for_arbitrary_naming_when_on_non_feature_branch() { - todo!() + set_up(); + let _td = TearDown; + let commit_hash = "03a15d220da53c52eddd5f32ffca64a7b3801bea"; + let another_commit_hash = "13a15d220da53c52eddd5f32ffca64a7b3801bea"; + + let expectations: Vec<MockExpectation> = vec![ + cmd_full(["git", "--version"], 0, "git version 2.52.0", ""), + cmd_full( + ["git", "branch", "-a", "--no-color", "--no-abbrev", "-v"], + 0, + format!( + "* latest-testing {} Commit message\n current {} Another message\n master {} Another message\n", + commit_hash, another_commit_hash, another_commit_hash + ), + "", + ), + ]; + let (process, _guard) = get_process_executor_mock(expectations, true, MockHandler::default()); + + let config = make_config(); + let mut guesser = make_guesser(config, process); + let version_data = guesser + .guess_version( + &package_config(&[ + ("version", PhpMixed::String("self.version".to_string())), + ("non-feature-branches", string_list(&["latest-.*"])), + ]), + "dummy/path", + ) + .unwrap() + .expect("expected version data"); + + assert_eq!("dev-latest-testing", version_data.version.unwrap()); + assert_eq!(commit_hash, version_data.commit.unwrap()); + assert!(version_data.feature_version.is_none()); + assert!(version_data.feature_pretty_version.is_none()); } -#[ignore = "requires getProcessExecutorMock with expects() command expectations; no ProcessExecutorMock mocking infrastructure exists"] #[test] +#[serial] fn test_detached_head_becomes_dev_hash() { - todo!() + set_up(); + let _td = TearDown; + let commit_hash = "03a15d220da53c52eddd5f32ffca64a7b3801bea"; + + let expectations: Vec<MockExpectation> = vec![ + cmd_full(["git", "--version"], 0, "git version 2.52.0", ""), + cmd_full( + ["git", "branch", "-a", "--no-color", "--no-abbrev", "-v"], + 0, + format!("* (no branch) {} Commit message\n", commit_hash), + "", + ), + cmd(["git", "describe", "--exact-match", "--tags"]), + ]; + let (process, _guard) = get_process_executor_mock(expectations, true, MockHandler::default()); + + let config = make_config(); + let mut guesser = make_guesser(config, process); + let version_data = guesser + .guess_version(&IndexMap::new(), "dummy/path") + .unwrap() + .expect("expected version data"); + + assert_eq!( + format!("dev-{}", commit_hash), + version_data.version.unwrap() + ); } -#[ignore = "requires getProcessExecutorMock with expects() command expectations; no ProcessExecutorMock mocking infrastructure exists"] #[test] +#[serial] fn test_detached_fetch_head_becomes_dev_hash_git2() { - todo!() + set_up(); + let _td = TearDown; + let commit_hash = "03a15d220da53c52eddd5f32ffca64a7b3801bea"; + + let expectations: Vec<MockExpectation> = vec![ + cmd_full(["git", "--version"], 0, "git version 2.52.0", ""), + cmd_full( + ["git", "branch", "-a", "--no-color", "--no-abbrev", "-v"], + 0, + format!( + "* (HEAD detached at FETCH_HEAD) {} Commit message\n", + commit_hash + ), + "", + ), + cmd(["git", "describe", "--exact-match", "--tags"]), + ]; + let (process, _guard) = get_process_executor_mock(expectations, true, MockHandler::default()); + + let config = make_config(); + let mut guesser = make_guesser(config, process); + let version_data = guesser + .guess_version(&IndexMap::new(), "dummy/path") + .unwrap() + .expect("expected version data"); + + assert_eq!( + format!("dev-{}", commit_hash), + version_data.version.unwrap() + ); } -#[ignore = "requires getProcessExecutorMock with expects() command expectations; no ProcessExecutorMock mocking infrastructure exists"] #[test] +#[serial] fn test_detached_commit_head_becomes_dev_hash_git2() { - todo!() + set_up(); + let _td = TearDown; + let commit_hash = "03a15d220da53c52eddd5f32ffca64a7b3801bea"; + + let expectations: Vec<MockExpectation> = vec![ + cmd_full(["git", "--version"], 0, "git version 2.52.0", ""), + cmd_full( + ["git", "branch", "-a", "--no-color", "--no-abbrev", "-v"], + 0, + format!( + "* (HEAD detached at 03a15d220) {} Commit message\n", + commit_hash + ), + "", + ), + cmd(["git", "describe", "--exact-match", "--tags"]), + ]; + let (process, _guard) = get_process_executor_mock(expectations, true, MockHandler::default()); + + let config = make_config(); + let mut guesser = make_guesser(config, process); + let version_data = guesser + .guess_version(&IndexMap::new(), "dummy/path") + .unwrap() + .expect("expected version data"); + + assert_eq!( + format!("dev-{}", commit_hash), + version_data.version.unwrap() + ); } -#[ignore = "requires getProcessExecutorMock with expects() command expectations; no ProcessExecutorMock mocking infrastructure exists"] #[test] +#[serial] fn test_tag_becomes_version() { - todo!() + set_up(); + let _td = TearDown; + + let expectations: Vec<MockExpectation> = vec![ + cmd_full(["git", "--version"], 0, "git version 2.52.0", ""), + cmd_full( + ["git", "branch", "-a", "--no-color", "--no-abbrev", "-v"], + 0, + "* (HEAD detached at v2.0.5-alpha2) 433b98d4218c181bae01865901aac045585e8a1a Commit message\n", + "", + ), + cmd_full( + ["git", "describe", "--exact-match", "--tags"], + 0, + "v2.0.5-alpha2", + "", + ), + ]; + let (process, _guard) = get_process_executor_mock(expectations, true, MockHandler::default()); + + let config = make_config(); + let mut guesser = make_guesser(config, process); + let version_data = guesser + .guess_version(&IndexMap::new(), "dummy/path") + .unwrap() + .expect("expected version data"); + + assert_eq!("2.0.5.0-alpha2", version_data.version.unwrap()); } -#[ignore = "requires getProcessExecutorMock with expects() command expectations; no ProcessExecutorMock mocking infrastructure exists"] #[test] +#[serial] fn test_tag_becomes_pretty_version() { - todo!() + set_up(); + let _td = TearDown; + + let expectations: Vec<MockExpectation> = vec![ + cmd_full(["git", "--version"], 0, "git version 2.52.0", ""), + cmd_full( + ["git", "branch", "-a", "--no-color", "--no-abbrev", "-v"], + 0, + "* (HEAD detached at 1.0.0) c006f0c12bbbf197b5c071ffb1c0e9812bb14a4d Commit message\n", + "", + ), + cmd_full( + ["git", "describe", "--exact-match", "--tags"], + 0, + "1.0.0", + "", + ), + ]; + let (process, _guard) = get_process_executor_mock(expectations, true, MockHandler::default()); + + let config = make_config(); + let mut guesser = make_guesser(config, process); + let version_data = guesser + .guess_version(&IndexMap::new(), "dummy/path") + .unwrap() + .expect("expected version data"); + + assert_eq!("1.0.0.0", version_data.version.unwrap()); + assert_eq!("1.0.0", version_data.pretty_version.unwrap()); } -#[ignore = "requires getProcessExecutorMock with expects() command expectations; no ProcessExecutorMock mocking infrastructure exists"] #[test] +#[serial] fn test_invalid_tag_becomes_version() { - todo!() + set_up(); + let _td = TearDown; + + let expectations: Vec<MockExpectation> = vec![ + cmd_full(["git", "--version"], 0, "git version 2.52.0", ""), + cmd_full( + ["git", "branch", "-a", "--no-color", "--no-abbrev", "-v"], + 0, + "* foo 03a15d220da53c52eddd5f32ffca64a7b3801bea Commit message\n", + "", + ), + ]; + let (process, _guard) = get_process_executor_mock(expectations, true, MockHandler::default()); + + let config = make_config(); + let mut guesser = make_guesser(config, process); + let version_data = guesser + .guess_version(&IndexMap::new(), "dummy/path") + .unwrap() + .expect("expected version data"); + + assert_eq!("dev-foo", version_data.version.unwrap()); } -#[ignore = "requires getProcessExecutorMock with expects() command expectations; no ProcessExecutorMock mocking infrastructure exists"] #[test] +#[serial] fn test_numeric_branches_show_nicely() { - todo!() + set_up(); + let _td = TearDown; + + let expectations: Vec<MockExpectation> = vec![ + cmd_full(["git", "--version"], 0, "git version 2.52.0", ""), + cmd_full( + ["git", "branch", "-a", "--no-color", "--no-abbrev", "-v"], + 0, + "* 1.5 03a15d220da53c52eddd5f32ffca64a7b3801bea Commit message\n", + "", + ), + ]; + let (process, _guard) = get_process_executor_mock(expectations, true, MockHandler::default()); + + let config = make_config(); + let mut guesser = make_guesser(config, process); + let version_data = guesser + .guess_version(&IndexMap::new(), "dummy/path") + .unwrap() + .expect("expected version data"); + + assert_eq!("1.5.x-dev", version_data.pretty_version.unwrap()); + assert_eq!("1.5.9999999.9999999-dev", version_data.version.unwrap()); } -#[ignore = "requires getProcessExecutorMock with expects() command expectations; no ProcessExecutorMock mocking infrastructure exists"] +#[ignore = "remote-branch feature guessing calls ProcessExecutor::execute_async, whose mock path is todo!()"] #[test] +#[serial] fn test_remote_branches_are_selected() { - todo!() + set_up(); + let _td = TearDown; + + let expectations: Vec<MockExpectation> = vec![ + cmd_full(["git", "--version"], 0, "git version 2.52.0", ""), + cmd_full( + ["git", "branch", "-a", "--no-color", "--no-abbrev", "-v"], + 0, + "* feature-branch 03a15d220da53c52eddd5f32ffca64a7b3801bea Commit message\n\ + remotes/origin/1.5 03a15d220da53c52eddd5f32ffca64a7b3801bea Commit message\n", + "", + ), + cmd_full( + ["git", "rev-list", "remotes/origin/1.5..feature-branch"], + 0, + "\n", + "", + ), + ]; + let (process, _guard) = get_process_executor_mock(expectations, true, MockHandler::default()); + + let config = make_config(); + let mut guesser = make_guesser(config, process); + let version_data = guesser + .guess_version( + &package_config(&[("version", PhpMixed::String("self.version".to_string()))]), + "dummy/path", + ) + .unwrap() + .expect("expected version data"); + + assert_eq!("1.5.x-dev", version_data.pretty_version.unwrap()); + assert_eq!("1.5.9999999.9999999-dev", version_data.version.unwrap()); } #[test] +#[serial] fn test_get_root_version_from_env() { // @dataProvider rootEnvVersionsProvider let root_env_versions: Vec<(&str, &str)> = vec