| Age | Commit message (Collapse) | Author |
|
PHP's readTestFile splits sections with a regex that leaves a section's
own trailing newline attached when it is the file's last section
(verified against real PHP); for install-without-lock.test and
update-without-lock.test, --EXPECT-LOCK-- is that last section, so its
raw content is "false\n", not "false". PHP's `$expectLock === 'false'`
check therefore also misses, but PHP falls through to
JsonFile::parseJson("false\n"), which json_decodes to boolean false
anyway, and every downstream check in doTestIntegration branches on
PHP truthiness of $expectLock, so the outcome is unaffected either way.
The Rust port only mirrored the literal string comparison, so the
same "false\n" produced ExpectLock::Json(Value::Bool(false)) instead
of ExpectLock::Never, and do_test_integration's Json arm unconditionally
read composer.lock, panicking because config.lock=false means no lock
file is ever written. Parse EXPECT-LOCK as JSON first and classify by
PHP truthiness of the result, matching what doTestIntegration actually
checks instead of the raw string.
|
|
The b'>' match arm in strip_tags's state machine never pushed the
character to the output buffer when encountered outside a tag (state
0), unlike every other special-character arm (!, ?, -, and the
catch-all) which does push in that state. Every literal '>' not part
of an HTML-like tag was silently dropped.
This corrupted "=>" into "=" in Composer's operation trace strings
(e.g. "Upgrading foo/bar (1.0.0 => 1.1.0)"), which go through
strip_tags to remove the <info>/<comment> markup before comparison,
un-ignoring 82 integration tests that were asserting on that exact
arrow.
|
|
Locker::get_stability_flags returned IndexMap<String, String>, converting
each value via PhpMixed::as_string(), which only matches the String
variant. composer.lock's "stability-flags" values are always JSON
integers (BasePackage::STABILITIES), so every flag silently decoded to
"" and installer.rs's downstream .parse::<i64>() defaulted it to 0
(stable). This made any locked package pinned via a non-stable
stability-flags entry look "unacceptable" during `composer install`,
silently dropping it from the solver's pool instead of fixing/requiring
it — turning a real dependency conflict into a spurious "lock file needs
changes" result. Return i64 directly, matching
RootPackageInterface::get_stability_flags and set_lock_data's existing
convention for this same PHP array shape.
|
|
do_test_integration built the Locker's composer.json content string with
serde_json::to_string, which doesn't escape slashes, while the fixture's
auto-computed lock "hash" field used JsonFile::encode_with_options with
slashes escaped (mirroring PHP's json_encode default). Since virtually
every package name contains a "/", this made Locker::isFresh() spuriously
report every such lock as stale, unignoring 12 fixtures that depended on
the lock being recognized as fresh during `install`.
|
|
Replace the three loop-based test functions (each iterating all
fixtures with a shared SKIP_INSTALLER_* skip list) with macro-generated
per-fixture test functions, so a single failing or panicking fixture
no longer hides the pass/fail status of the rest. Known-failing
fixtures keep their TODO(phase-d) reasons via #[ignore] on their own
test instead of a shared skip list. All generated tests are #[serial]
since they mutate global env vars (COMPOSER_POOL_OPTIMIZER,
COMPOSER_FUND) that would otherwise race across parallel test threads.
This is an interim structure for triaging the ~131 ignored fixtures
one at a time; it should be reverted to a single loop-based test per
group (matching Composer's directory-scanned fixtures) once they are
resolved.
|
|
reload to PHP runtime
Rust has no PHP interpreter, so eval() can never be ported faithfully.
safely_load_installed_versions()'s job of priming Composer\InstalledVersions
before plugins run only matters within a single shared PHP process, which
the RPC-based plugin architecture does not have; the PHP runtime process
can call InstalledVersions::reload() itself instead.
Co-Authored-By: Claude Sonnet 5 <noreply@anthropic.com>
|
|
A plugin can reach ProcessExecutor::executeAsync() through the
rust-proxy stub, which resolves with a real Symfony Process instance
that can't be reconstructed on the Rust side (its state is tied to
whichever process calls proc_open(), and it refuses serialization).
Add execute_async_php() as a todo!() stub, documented as a dual-
instantiation split in plugin-class-classification.md: Rust-internal
callers keep using execute_async(), while the plugin path must forward
spawning to the PHP child once the RPC channel exists.
|
|
advisories
get_security_advisories cloned each package's full metadata blob
(hundreds of versions for popular packages) just to peek at its
"security-advisories" field. Same pattern already fixed in
load_async_packages by 2862d2da; move ownership through pattern
matches and IndexMap::shift_remove instead.
Measured with laravel/laravel create-project: response processing in
the security-advisories metadata branch drops ~80% (236ms -> 48ms),
and the whole run_security_advisory_filter phase drops from ~420-500ms
to ~250-270ms. End-to-end, shirabe now matches or edges out upstream
Composer (6.57s vs 6.85s in this run) instead of trailing by ~1.2s.
|
|
metadata
load_async_packages cloned each response's version metadata up to
three times (spec_list/response, response_arr, versions_mixed, then
every per-version field) before building the versions Vec. Move
ownership through pattern matches and IndexMap::shift_remove instead.
Measured with laravel/laravel create-project: per-batch response
processing time in load_async_packages drops ~39% (1.30s -> 0.80s
cumulative), narrowing the E2E gap vs upstream Composer from 1.37s to
1.14s on average.
|
|
JsonFile::write_with_options() used the caller-supplied JsonEncodeOptions
verbatim, ignoring self.indent (set by read()'s detect_indenting), unlike
PHP's write() which always passes $this->indent to encode() regardless of
the $options argument. Un-ignore test_preserve_indentation_after_read.
|
|
The negated remaining-width subtraction was cast straight to usize,
overflowing whenever it went negative (e.g. an abandoned-package
warning ate the available width) and panicking on slice indexing.
Route through the shim's substr(), which mirrors PHP's negative-length
semantics and clamps instead of overflowing.
|
|
test_pool_builder
build_pool never populated skipped_load for locked packages skipped due
to the update allow list (nor their replace targets), so load_package's
skipped_load-driven transitive-unlock branch never fired.
load_packages_marked_for_loading never recorded loaded packages into
loaded_per_repo, so repositories had no way to dedupe already-loaded
versions when a constraint got re-expanded, producing duplicate pool
entries.
unlock_package looked up a locked package's removal index via the
position in a Vec snapshot of self.packages.values() instead of its
actual IndexMap key, so after earlier removals the wrong entry (or
none) got removed, leaving stale locked packages in the pool.
Fixing all three lets test_pool_builder run un-ignored.
|
|
shirabe_php_shim::chr() returned a Rust String, which lossily
re-encodes bytes >= 0x80 as UTF-8 replacement characters. ip_get_mask,
ip_get_network, and ip_map_to_6 relied on chr() to build raw in_addr
and netmask byte arrays, corrupting IPv4-in-IPv6 mappings and CIDR
netmasks. Build the Vec<u8> byte arrays directly instead of
round-tripping through String, and un-ignore test_ip_address and
test_ip_range now that the underlying bug is fixed.
chr()'s only other caller (http_downloader.rs, an ASCII ESC byte in a
regex pattern) didn't need the indirection either, so remove the shim
function entirely.
Co-Authored-By: Claude Sonnet 5 <noreply@anthropic.com>
|
|
The ignored tests raced on the process-wide HTTP_PROXY/etc env vars
and the ProxyManager::INSTANCE mutex, causing spurious PoisonError
panics when run in parallel. Adding #[serial_test::serial], the same
annotation test_instantiation already used, fixes the race with no
other changes needed.
|
|
Several call sites coerced PhpMixed to bool via `.as_bool()` (which
only matches a literal Bool variant) where the corresponding PHP code
does a plain `(bool)` cast or truthy check (isset()/array_key_exists()
+ implicit bool conversion). This silently dropped truthy non-bool
values (e.g. String("true"), String("1"), Int(1)) to their unwrap_or
default instead of PHP's actual truthy result. Switched these sites to
PhpMixed::to_bool(), which implements PHP's full truthy-cast rules.
|
|
74 tests carried a bare #[ignore] with no explanation. Re-ran each:
25 now pass and had the attribute removed; the remaining 49 got a
concise reason (todo!() stubs, regex-crate PCRE gaps, PhpMixed type
mismatches, config bool-coercion bugs, missing skipped_load wiring
in PoolBuilder, etc.) so future work can find and fix them by grep.
No production code or test logic/assertions were changed.
Co-Authored-By: Claude Sonnet 5 <noreply@anthropic.com>
|
|
parseIgnoreWithApply distinguishes ['CVE-123' => 'reason'] from
[0 => 'CVE-123'] by checking is_int($key) in PHP. AuditConfig only
matched on the value's type, so a canonical-int-keyed string value
was mistaken for an id => reason pair. Expose canonical_int_key from
the php-shim to replicate PHP's key-canonicalization rule and
unignore test_mixed_formats.
|
|
execute() held &config.borrow() across check_http/check_composer_repo/
check_composer_audit, which reach HttpDownloader -> CurlDownloader::
download; that method does self.config.borrow_mut() on the same
Config RefCell, panicking with "RefCell already borrowed" once the
phpinfo panic that previously masked this was fixed.
Switch those three helpers to take the Rc<RefCell<Config>> handle
(matching check_version's existing pattern) and borrow only where a
field is actually read, so no borrow spans the downstream network
call. Un-ignore the now-passing run_diagnose smoke test and
test_cmd_fail; test_cmd_success stays ignored, now for two separate
reasons: it needs real network access (as the PHP original does), and
shirabe_php_shim::OPENSSL_VERSION_NUMBER is a hardcoded stub (0) that
always trips check_platform's TLSv1.1/1.2 support check regardless of
the real linked OpenSSL, forcing a non-zero exit code.
|
|
DiagnoseCommand::check_platform needed phpinfo() output but
shirabe-php-shim's ob_start()/phpinfo()/ob_get_clean() are unmodeled
todo!()s. Add a phpinfo dispatch entry to the PHP worker (capturing
output the same way extension_info already does) and a get_phpinfo()
wrapper, then switch check_platform to call it directly.
This unblocks the phpinfo-related panic in diagnose; the ignored
tests now hit a separate RefCell double-borrow bug in check_http, so
their ignore reasons are updated to point at that instead.
|
|
capture_stderr_separately routed the tester through ConsoleOutput,
which detects color/hyperlink support from the real process STDOUT
before the stream is swapped to the memory buffer. When STDOUT is a
real tty, decoration leaks into the captured output. The PHP original
never passes this option and get_error_output() was unused here, so
drop it to match and keep the assertion deterministic.
|
|
The shim date() renders in UTC only (no timezone database) while PHP's
date() uses the system default timezone, so get_relative_time misses the
"today" match and prints "this week" whenever the local date differs
from the UTC date (daily 00:00-09:00 JST on this machine). Verified by
running the test with and without TZ=UTC at 07:40 JST. Mark the gap with
a TODO(phase-c) in the shim; fixing it needs a timezone database (a new
crate), which is a user decision.
Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
|
|
Measured the actual panic site: DiagnoseCommand::check_platform reaches
the todo!() ob_start()/ob_get_clean() shims while capturing phpinfo(),
the same root cause already recorded in
tests/command/diagnose_command_test.rs.
Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
|
|
The Ref temporaries created by input.borrow() inside the argument
expressions of the determine_requirements call lived until the end of
the whole call statement, so ConsoleIO::ask_question's borrow_mut() on
the same shared input RefCell panicked with "RefCell already borrowed"
when the command prompted for packages. Hoist the argument computations
into locals so no borrow is held across the call, and un-ignore the
run_require CLI test.
Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
|
|
PHP only runs this test when the zip extension is not loaded; the Rust
port has unconditional zip support, so that precondition cannot exist.
Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
|
|
The ignore reason was stale: create_repository_by_class now dispatches
every repository class this test registers, and the test passes.
Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
|
|
PHP's `?:` chain in getComposerInformation short-circuits, so
getBranches() only runs when the tags search is falsy. The eager port
issued an extra git/refs/heads API request that PHP never makes.
Un-ignore test_public_repository_archived, which this fixes.
Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
|
|
The previous ignore reason blamed unmodeled extension info; the actual
blockers are the TODO(plugin) stubs resource_bundle_get (returns Null,
dropping lib-icu-cldr) and imagick_get_version_string (returns "",
dropping lib-imagick-imagemagick), both pending dynamic method dispatch
on PHP objects via the plugin RPC mechanism.
Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
|
|
The ignore reason went stale: AliasPackage::get_source_type is
implemented (delegates to alias_of) and the test passes as-is.
Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
|
|
The regex crate parses PCRE's possessive \d{1,5}+ as a stacked
repetition (?:\d{1,5})+, i.e. \d+, so date versions like 20121020
matched the classical pattern and normalized to 20121020.0.0.0 instead
of falling through to the date(time) pattern like PHP. The plain
\d{1,5} is equivalent to the possessive form here per the
regex-porting rules. Un-ignore test_find_recommended_require_version
which this had blocked.
Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
|
|
Config::get("github-protocols") ported PHP's array_search over the
protocol list via a string-keyed map, but array_search_mixed returns
the matched index as PhpMixed::Int, which the as_string() read never
matched, so the git protocol was never removed (Config.php:447-449
removes it whenever secure-http is on). Search the list directly and
read the index as an Int. Un-ignore
test_update_throws_runtime_exception_if_git_command_fails which this
had blocked.
Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
|
|
PHP's Config::get() applies an (int) cast to cache-files-ttl, cache-ttl,
and the process-timeout env override (Config.php:326,367,398), so string
values like '99999999' become integers. The strict PhpMixed::as_int
returned None for strings, collapsing them to 0. Use the intval shim,
which implements the PHP cast, and un-ignore
test_cache_garbage_collection_is_called which this had blocked.
Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
|
|
PHP's UpdateCommand::getPackagesInteractively passes $autocompleterValues
keyed by package name to $io->select, so the selection resolves to package
names. The port passed only the keys as a list, making select resolve to a
numeric index that the update then treated as an unknown package. The old
ignore reason (non-interactive terminal error) no longer applied.
Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
|
|
PHP's RunScriptCommand::interact passes $options keyed by script name, so
select resolves the entered value to the script name and sets it as the
script argument. The port passed only the keys as a list, which made select
resolve to a numeric index instead of the script name.
Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
|
|
PHP's IOInterface::select accepts an associative choices array whose keys
are the selectable values, but the port narrowed it to Vec<String>, making
key-based selection unrepresentable. Accept PhpMixed (List or Array) like
PHP's array $choices; ConsoleIO already branched on both shapes internally.
Also mirror PHP in the single-select array_search fallback for numeric-keyed
arrays. All call sites keep their previous list-based behavior.
Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
|
|
The PHP test implicitly requires the process cwd to be an ancestor of
the Fixtures dir (phpunit runs inside the composer checkout); cargo
runs tests from the crate manifest dir, which is not. Replicate the
phpunit precondition with a drop-restoring CwdGuard chdir'ing to the
__DIR__ equivalent, and serialize it (together with the only other
cwd-mutating test in the repository binary,
test_repository_writes_installed_php) via #[serial] so the
process-global cwd cannot race.
Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
|
|
Add the __add_installer test seam that registers an installer as a
pre-built Rc handle, so the test can reproduce PHP's object-identity
semantics (assertSame / removeInstaller) via Rc::ptr_eq; add_installer
cannot serve because Rc::from(Box) reallocates, losing the caller's
pointer identity.
Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
|
|
Re-verified the reason: besides the missing as_any seam on
EventInterface (a cross-cutting trait change over every event type),
the mocked dispatchScript call expectation is also inexpressible since
dispatch_script is a concrete method with no call-recording seam.
Record both blockers in the ignore string and TODO(phase-d) comment.
Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
|
|
The ignore reason had rotted: ConsoleOutputInterface::set_error_output
is already ported, so a BufferedOutput error sink can be injected into
a real ConsoleOutput and the error-routed write read back, matching the
file's convention of replacing PHPUnit mock expectations with
BufferedOutput assertions.
Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
|
|
The only missing seam was the PHP test's
ReflectionMethod(getPhpExecCommand) access; add the test-only
__get_php_exec_command wrapper and port the test on the existing
get_listeners override and process-executor mock infrastructure.
Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
|
|
PHP's BufferIO extends ConsoleIO, so $io instanceof ConsoleIO matches
it; the port models that inheritance as composition, making the plain
ConsoleIO downcast reject BufferIO and throw where PHP renders tables.
Also try a BufferIO downcast and unwrap its inner ConsoleIO, which
unblocks the two FORMAT_TABLE cases and un-ignores test_audit.
Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
|
|
Spell out why the existing __new_mock seam cannot serve: it replaces
execute() wholesale (skipping the download step, ref
InstallationManagerMock), while the PHP test runs the real execute() and
spies only on the three per-operation methods it dispatches to. A
per-method spy on the real path would be a production design change.
Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
|
|
The old reason blamed missing mock infrastructure, but a spy against
dyn AutoloadGeneratorInterface is perfectly writable. The actual blocker
is that make_autoloader (PHP makeAutoloader, called from doDispatch's
script branches, where setDevMode is invoked) is an intentional no-op in
the port, so set_dev_mode is never reached and a spy would observe
nothing.
Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
|
|
Beyond the missing CommandCompletionTester harness, the PHP test's
expected suggestions depend on the Composer dev checkout environment
(its own composer.json/lock, installed vendor packages) and live
Packagist queries, which the port cannot reproduce without a fixture
environment.
Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
|
|
The ignore reason went stale: the getListeners override seam and a real
ProcessExecutor wired to the IO already cover the PHP setup, and IOStub
is the PHPUnit IOInterface-mock equivalent. IOStub now records
writeError calls (writeRaw was already recorded) so the
expects(once)->with(...) spies on writeError/writeRaw can be reproduced
as call-list equality assertions.
Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
|
|
The ignore reason went stale: the FilesystemMock seam added for
FileDownloaderTest already intercepts removeDirectoryAsync, and the
sibling fossil/hg testRemove ports use it. The seam now records the
removed directories instead of a bare call count so the PHP
->with($this->equalTo($this->workingDir)) argument assertion can be
reproduced faithfully.
Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
|
|
The tests fail before any network access: DiagnoseCommand::check_platform
captures phpinfo() via ob_start()/ob_get_clean(), which are todo!() in
shirabe-php-shim, so the command panics before producing output. Record
that as the primary blocker alongside the live-network requirement.
Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
|
|
PHP's getRemoteContents calls file_get_contents unconditionally: the
same stream wrapper reads file:// URLs, plain local paths and network
schemes. The Rust port only handled the explicit "file" scheme, so a
scheme-less local packages.json repository failed with "file could not
be downloaded". Extend the local branch to empty schemes; the http(s)
stub is unchanged.
This clears the first blocker of the create-project functional test;
its ignore reason now documents the remaining ones (live network and
Glob::to_regex emitting PCRE lookaheads the regex crate cannot
compile), which need a user decision.
Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
|
|
The ignore reason went stale: HttpDownloader is reqwest-based now, so
building it for HgDriver no longer reaches curl_multi_init() (todo!() in
shirabe-php-shim::curl). Test content unchanged.
Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
|
|
The file:// branch of RemoteFilesystem::get_remote_contents is now
implemented, so the dist download reaches the ZipArchive "is not a zip
archive" path the test asserts. Test content unchanged.
Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
|
|
validate_array judged emptiness via as_array(), which only matches
PhpMixed::Array, so a non-empty PhpMixed::List (e.g. a funding array) was
misjudged as empty and dropped, diverging from PHP's !count() check.
Match both Array and List.
The stale ignore reason on test_fund_command no longer applies:
init_temp_composer injects packagist:false so no network is reached, and
the downloader stack is reqwest-based (no curl shim todo!()).
Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
|