aboutsummaryrefslogtreecommitdiffhomepage
path: root/crates/shirabe/tests/plugin
AgeCommit message (Collapse)Author
2026-08-07feat(plugin): cross alias packages over RPC as proxy stubsnsfisis
AliasPackage, CompleteAliasPackage and RootAliasPackage now have generated proxy stubs, so a package handed to a plugin no longer has to be a real package: it crosses as the stub matching its concrete variant, answers getAliasOf / setRootPackageAlias / isRootPackageAlias / hasSelfVersionRequires, and can be constructed from plugin code. The setters RootPackageInterface declares are routed through that interface for every root package instead of through the base Package state. Only the alias variant needs it -- RootAliasPackage overrides all nine to write through to the package it aliases -- but a real RootPackage delegates to the same base state either way, so both take one path. An alias of an alias has no representation here, so narrowing the constructor argument to a real package is an explicit error rather than a silent demotion. Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
2026-08-06feat(plugin): let links and release dates cross the RPC boundarynsfisis
The link getters and setters and the release date accessors were explicit errors for every non-empty value, because an immutable value has no entity to point a handle at. They now cross as materialized values: the descriptor names the real class and the constructor arguments, and each side builds a genuine instance of its own. The semver constraint a link holds is encoded structurally rather than re-parsed from its string form, so the pretty strings and the conjunctive flag survive the crossing. Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
2026-08-06test(plugin): cover composer/installers beyond a fresh installnsfisis
A single install leaves the installer contract half tested: it never reaches `update`, never runs over an already-installed tree, and never reaches the plugin's own `uninstall()` override — the one chaining onto the promise `LibraryInstaller::uninstall` returns. Nor does it touch the configuration surface real projects use, `installer-paths` and `installer-name`, which the plugin reads back through the package proxy. All three now compare command output as well as the resulting tree against upstream Composer. Progress bar frames are dropped from that comparison: Shirabe renders them differently for every install, including projects with no plugin at all, so they say nothing about the plugin under test. With those covered the plugin joins the verified list in the README.
2026-08-06test(plugin): compare a composer/installers run against upstream Composernsfisis
The fixture project pins composer/installers 2.3.0 and requires two packages of framework-specific types, so the plugin's LibraryInstaller subclass decides where they land. Upstream Composer and Shirabe install the same project and the whole resulting tree is compared. The plugin tarball is fetched by `fixtures/e2e-installers/fetch` into a git-ignored directory and pinned by a digest over the extracted files, so the third-party source never enters this repository and the test skips itself while the directory is absent. The tree is staged and only moved into place once verified, so an unverified tree is never observable under the name the test looks for.
2026-08-06test(plugin): compare a plugin-provided installer against upstream Composernsfisis
The fixture plugin registers an InstallerInterface implementation of its own and installs a package of a custom type with it, recording every contract call it receives. A fresh install produces a byte-identical project tree on both implementations, trace included. A second test pins the divergence a re-run and a `remove` expose: the two implementations consult getInstaller at different points, so the trace order — and its length once `remove` re-creates the Composer instance — differs. It is written in full and marked ignored rather than trimmed. Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
2026-08-06feat(plugin): run plugin-provided installers through the RPC workernsfisis
A plugin can now hand an InstallerInterface implementation to InstallationManager::addInstaller across the wire, and a legacy composer-installer package is loaded as one; both are backed by a PhpInstallerProxy forwarding the whole installer contract to the entity in the PHP worker. An installer returning a real promise is an explicit error until promises can cross the boundary. InstallationManager takes installers as shared handles instead of boxes, so the object identity removeInstaller and PluginManager's registeredPlugins compare against survives registration, and holds them in a RefCell: Installer::run keeps a shared borrow of the manager for the whole run, and a plugin activated inside it registers its installer from there. The type cache keys on the installer itself, like upstream, so re-entrant registration cannot leave a stale index behind. InstallerInterface::supports is fallible for the same reason getCapabilities and getCommands are: it answers over RPC. Cloning a proxy stub clones the Rust-side entity and rebinds the copy to the fresh handle. Previously only the classes declaring __clone got a throwing body, and the rest let two stubs share (and twice release) one handle. Package entities answer with AnyPackage::dup, which already carries BasePackage::__clone and the RootAliasPackage override; the others are an explicit error. The package proxy covers the whole PackageInterface surface; only the link maps and the release date still lack a wire image for their value objects. PluginManager gains a test-only seam for the reported Plugin API version, and the three PluginInstallerTest cases that need it are ported. Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
2026-08-05test(plugin): pin a composer-normalize fixture and compare its listingnsfisis
The pinned plugin and its real dependency tree (nine packages, fetched by commit and verified by tree hash into the git-ignored ext/) install under both implementations, and the list/help renderings of the plugin-provided normalize command must match upstream byte for byte. The execution comparison is written but ignored: NormalizeCommand builds a second, in-process Composer instance, and the worker's proxy stubs reject native construction of the classes that path instantiates. Stale comments about the missing worker-side application are updated to the current facts. Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
2026-08-05test(plugin): compare plugin-provided command execution against upstreamnsfisis
A Shirabe-authored CommandProvider fixture plugin (greet) runs under both implementations: exit codes, the command's own output, help/list rendering, alias resolution, a validation failure, and a file recording the shared object graph the command observed (root package, strict application FQCN, and the exit code of the built-in about command it invoked) must match. Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
2026-08-05feat(plugin): discover and list plugin-provided commandsnsfisis
Port Application::getPluginCommands: resolve the local composer with plugins force-enabled, fall back to Factory::createGlobal, and collect commands from CommandProvider capability adapters. PhpCommandProxy now mirrors name/description/aliases/hidden over RPC so `list` output matches upstream; input definitions remain TODO(plugin). PhpClass::php_class_name returns an owned String because PHP-backed proxies only know their class at runtime (the override-skip warning prints get_class). CommandProvider::getCommands hands out shared Rc<RefCell> handles since the commands are stored in the application. Factory::createGlobal now propagates createConfig errors instead of swallowing them, and Application::getComposer only catches the exception classes upstream catches, so a ParsingException reaches doRun's GithubActionError path as in Composer. Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
2026-08-04feat(plugin): instantiate plugin capabilities through the PHP RPC workernsfisis
getPluginCapability was a no-op returning None. Now it runs the real flow: class_exists in the worker, new $capabilityClass($ctorArgs) with the plugin's own phandle spliced in as $ctorArgs['plugin'], both instanceof checks answered by is_a in the child, and a per-interface Rust adapter over the resulting entity (CommandProvider and the plain Capability marker; anything else is an explicit error). getPluginCapabilities now propagates errors instead of swallowing them. Capable::get_capabilities widens from IndexMap<String, String> to IndexMap<String, PhpMixed>: upstream casts the return with (array) and validates only the queried key, so the narrow type both rejected maps Composer accepts and made the invalidImplementationClassNames data provider unrepresentable. The old code also returned ' 0 ' (trimmed to the falsy '0') as a valid class name where upstream throws; the rewritten validation follows upstream's empty/is_string/trim sequence. CommandProvider::get_commands returns BaseCommand adapters whose name is read back over RPC after the PHP constructor ran configure(); executing one still needs the PHP-side Symfony Application and stays an explicit error. The is_array / instanceof BaseCommand checks that upstream's Application::getPluginCommands performs on the raw getCommands value live in the adapter, because Vec<Box<dyn BaseCommand>> asserts every element up front. Ports testCommandProviderCapability (plugin-v8 end to end against the real worker) and testQueryingWithInvalidCapabilityClassNameThrows (all eight provider cases); the two tests that pass a PHPUnit mock plugin into PHP stay ignored — a Rust-native mock has no PHP-side entity to cross the boundary as $ctorArgs['plugin']. Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
2026-08-04test(plugin): compare a real-plugin install against upstream Composernsfisis
Run upstream Composer and Shirabe over pristine copies of a fixture project at the same path and require composer.lock, the whole vendor tree (including the plugin-generated GeneratedConfig.php), the plugin's IO lines and the exit code to match byte for byte. The plugin under test (phpstan/extension-installer 1.4.3) is downloaded by fixtures/e2e/fetch — pinned to an upstream commit and hash-verified — into a git-ignored directory rather than committed; the test skips while it is absent, like the other real-PHP prerequisites. Its dependencies are minimal stand-ins resolved from local repositories, so test runs stay offline. A zip dist would exercise the known lossy-string byte-precision debt in RemoteFilesystem, so the fixture serves the plugin through a path dist until that is resolved. Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
2026-08-04test(plugin): tolerate the _composer_tmp rename in the removal testnsfisis
The PHP worker is shared across the test binary, so whichever subscriber test runs first owns the bare class name and every later install registers the plugin under a _composer_tmpN rename (as upstream does). The exact-name lookup made the removal test depend on lock acquisition order. Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
2026-08-04feat(plugin): remove subscribed listeners when a plugin is removednsfisis
Wire removePlugin to EventDispatcher::removeListener now that subscriber listeners carry the plugin's P-table handle: PHP's $candidate[0] === $listener identity check maps to phandle equality on Callable::PhpMethod. Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
2026-08-04feat(plugin): dispatch plugin event subscribers through the RPC workernsfisis
Wires the addPlugin subscriber branch end to end: EventSubscriberInterface and Capable become fallible and dyn-compatible (their sole implementor is the PHP plugin proxy, which answers getSubscribedEvents over RPC), listeners register as Callable::PhpMethod and are invoked with a per-call event handle, and the R table now drops entries when a child-side stub destructs. The R table keeps its IndexMap with monotonically increasing handles, so released handles are never reused and no generation counter is needed. Upstream has no subscriber-plugin test, so the path is covered by a Shirabe-owned fixture exercising all three getSubscribedEvents shapes. Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
2026-08-04fix(plugin): resolve review findings in the plugin activation flownsfisis
InstallationManager::execute now takes &self (the mock recorder moved into a RefCell) and its callers hold only shared borrows: plugin registration inside a batch re-enters the same manager handle through Composer::getInstallationManager()->getInstallPath(), which panicked on the RefCell re-borrow under the &mut shape — the same re-entrancy the repository side already fixed, unreachable from the ported tests because they call PluginInstaller::install directly like PHPUnit does. The worker-side InstalledVersions mirror now matches the full tail of FilesystemRepository::write: unconditional reload plus the reflection-based selfDir/installedIsLocalDir restore. The previous class_exists(false) guard rested on a lazy-load assumption that does not hold in the worker (its real ClassLoader only knows the Composer checkout's vendor dir, so a later lazy load would read the checkout's installed.php, not the project's); the mirror is now skipped only when the class is not autoloadable at all, i.e. no plugin runtime and hence no observer code. Boot-time seeding stays TODO(plugin). Also from the review: registered_plugins entries are removed only after the deactivate/uninstall loop (PHP unsets last, and a throw must leave the entry observable); extra.class keeps associative-array values and fails loudly on non-strings instead of silently dropping them; the two discarded write() results now propagate (they carry the reload-push failure); register_package's allow-plugins skip message is DEBUG like the addPlugin side; the loader-eviction divergence of REGISTERED_LOADERS and the lossy UTF-8 spots carry searchable markers; the test-only proxy downcast follows the __ naming rule; the R-table dispatch clones the entity out instead of holding the table borrow across the handler; the IO/PartialComposer stubs turn a plugin-side `new NullIO()` into an explicit error instead of an ArgumentCountError; and the empty() emulation covers float 0.0. Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
2026-08-04feat(plugin): activate plugins through the PHP RPC workernsfisis
Implement the remainder of PluginManager::registerPackage: the plugin autoload map is built by the ported createLoader/parseAutoloads and served to the worker over the existing reverse-RPC autoloader, files entries go through a composerRequire-equivalent glue call, and already-defined classes take the upstream _composer_tmp rename/eval path. Instantiation uses the new NewObject/CallPhpMethod lanes backed by a P table in the worker; PhpPluginProxy adapts the resulting handle to PluginInterface, with $composer/$io exposed to plugin callbacks via an R table (unsupported methods stay explicit errors). Hand-written proxy stubs cover Composer, PartialComposer and the IO hierarchy, and the stub autoloader is re-prepended after loading the Composer PHP runtime so its vendor autoloader cannot shadow proxied FQCNs. FilesystemRepository::write now mirrors InstalledVersions::reload into a running worker (class_exists-guarded, so an unloaded class keeps its upstream lazy-load behavior), removing the previously undefined observation window. The installer pipeline passes the installed repository as a shared handle instead of a long-lived `&mut dyn`: plugin registration runs inside InstallationManager::execute and re-enters the same local repository through the RepositoryManager, which would panic on the RefCell re-borrow under the old shape. PluginInterface lifecycle methods now take an owned ComposerHandle (plugins retain $composer past the call) and return anyhow::Result (PHP plugin code may throw); the plugin list uses shared ownership so the identity comparison of removePlugin survives the dual storage in registeredPlugins, matching PHP reference semantics. Ports the activate/upgrade/uninstall tests of PluginInstallerTest, serialized across the shared worker process whose persistent class table is exactly what exercises the rename path. Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
2026-07-16test(plugin): port capability-query tests, record remaining TODO(phase-d) ↵nsfisis
reasons Implement test_incapable_plugin_is_correctly_detected and test_querying_non_provided_capability_returns_null_safely against a real PluginManager, using hand-written PluginInterface/Capable stubs in place of PHPUnit's ad-hoc mocks. Wire the shared config_stub test helper into the plugin test binary, and derive Debug on the SetUp struct per this project's convention. The remaining plugin_installer_test.rs cases stay #[ignore]d: install/update/uninstall wiring in PluginInstaller and class instantiation in PluginManager::register_package are still TODO(plugin) stubs, so no plugin ever actually gets registered. Each now records its blocking TODO(plugin) site via // TODO(phase-d). Co-Authored-By: Claude Sonnet 5 <noreply@anthropic.com>
2026-06-22test: port more test casesnsfisis
Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
2026-06-21test(tests): expand stub macros into plain test functionsnsfisis
The per-file stub!/encode_stub!/etc. macros generated #[ignore]d test functions but obscured the individual test bodies. Expanding them inline removes the macro indirection so future ports can fill in each function directly. Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
2026-06-21test(tests): port setUp/tearDown as set_up/tear_down with TearDownnsfisis
Port PHP setUp/tearDown across the ported integration tests using same-named set_up()/tear_down() functions and a TearDown struct whose Drop runs tear_down(). Fixture-init setUp returns its fixtures; tmpdir-style setUp/tearDown carry state in TearDown fields. Parts that depend on unported infrastructure (PHPUnit mocks, Config::merge, the PHP error handler) stay todo!() and are only wired into ignored stubs to avoid breaking live tests. Also fix shirabe-php-shim putenv to handle the no-'=' form (PHP unsets the variable), which Platform::clear_env relies on for the env-clearing tearDowns. Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
2026-06-21test: port package archiver/loader/version/locker and PluginInstaller stubsnsfisis
All ignored: ArrayLoader/ValidatingArrayLoader/VersionSelector parse constraints via a look-around regex; ArchiveManager uses PharData (todo!()); ArchivableFilesFinder/VersionGuesser/Locker mock git/process/IO; the plugin API is not implemented (TODO(plugin)). Wires up the plugin test target. Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
2026-06-21test: add empty test filesnsfisis