aboutsummaryrefslogtreecommitdiffhomepage
path: root/crates/shirabe/tests
diff options
context:
space:
mode:
Diffstat (limited to 'crates/shirabe/tests')
-rw-r--r--crates/shirabe/tests/command/archive_command_test.rs4
-rw-r--r--crates/shirabe/tests/installer_test.rs6
-rw-r--r--crates/shirabe/tests/plugin/fixtures/subscriber-v1/Subscriber/Plugin.php61
-rw-r--r--crates/shirabe/tests/plugin/fixtures/subscriber-v1/composer.json12
-rw-r--r--crates/shirabe/tests/plugin/main.rs1
-rw-r--r--crates/shirabe/tests/plugin/plugin_installer_test.rs45
-rw-r--r--crates/shirabe/tests/plugin/subscriber_test.rs109
7 files changed, 225 insertions, 13 deletions
diff --git a/crates/shirabe/tests/command/archive_command_test.rs b/crates/shirabe/tests/command/archive_command_test.rs
index 322894db..655020e0 100644
--- a/crates/shirabe/tests/command/archive_command_test.rs
+++ b/crates/shirabe/tests/command/archive_command_test.rs
@@ -85,6 +85,10 @@ mockall::mock! {
transaction: Transaction,
) -> anyhow::Result<i64>;
fn add_listener(&mut self, event_name: &str, listener: Callable, priority: i64);
+ fn add_subscriber<'a>(
+ &mut self,
+ subscriber: &'a dyn shirabe::event_dispatcher::EventSubscriberInterface,
+ ) -> anyhow::Result<()>;
fn has_event_listeners(&mut self, event: &dyn EventInterface) -> bool;
}
}
diff --git a/crates/shirabe/tests/installer_test.rs b/crates/shirabe/tests/installer_test.rs
index d0b60ba8..1a8b7ccc 100644
--- a/crates/shirabe/tests/installer_test.rs
+++ b/crates/shirabe/tests/installer_test.rs
@@ -188,6 +188,12 @@ impl EventDispatcherInterface for StubEventDispatcher {
Ok(0)
}
fn add_listener(&mut self, _event_name: &str, _listener: Callable, _priority: i64) {}
+ fn add_subscriber(
+ &mut self,
+ _subscriber: &dyn shirabe::event_dispatcher::EventSubscriberInterface,
+ ) -> anyhow::Result<()> {
+ Ok(())
+ }
fn has_event_listeners(&mut self, _event: &dyn EventInterface) -> bool {
false
}
diff --git a/crates/shirabe/tests/plugin/fixtures/subscriber-v1/Subscriber/Plugin.php b/crates/shirabe/tests/plugin/fixtures/subscriber-v1/Subscriber/Plugin.php
new file mode 100644
index 00000000..f75763ba
--- /dev/null
+++ b/crates/shirabe/tests/plugin/fixtures/subscriber-v1/Subscriber/Plugin.php
@@ -0,0 +1,61 @@
+<?php
+
+namespace Subscriber;
+
+use Composer\Composer;
+use Composer\EventDispatcher\EventSubscriberInterface;
+use Composer\IO\IOInterface;
+use Composer\Plugin\PluginInterface;
+
+class Plugin implements PluginInterface, EventSubscriberInterface
+{
+ public $version = 'subscriber-v1';
+
+ /** @var IOInterface */
+ private $io;
+
+ public function activate(Composer $composer, IOInterface $io)
+ {
+ $this->io = $io;
+ $io->write('activate subscriber-v1');
+ }
+
+ public function deactivate(Composer $composer, IOInterface $io)
+ {
+ }
+
+ public function uninstall(Composer $composer, IOInterface $io)
+ {
+ }
+
+ public static function getSubscribedEvents()
+ {
+ return [
+ 'post-install-cmd' => 'onPostInstall',
+ 'shirabe-priority-event' => [['early', 10], ['late', -10]],
+ 'shirabe-false-event' => ['returnsFalse', 0],
+ ];
+ }
+
+ public function onPostInstall($event)
+ {
+ $this->io->write('subscriber saw ' . $event->getName());
+ }
+
+ public function early($event)
+ {
+ $this->io->write('early listener');
+ }
+
+ public function late($event)
+ {
+ $this->io->write('late listener');
+ }
+
+ public function returnsFalse($event)
+ {
+ $this->io->write('failing listener');
+
+ return false;
+ }
+}
diff --git a/crates/shirabe/tests/plugin/fixtures/subscriber-v1/composer.json b/crates/shirabe/tests/plugin/fixtures/subscriber-v1/composer.json
new file mode 100644
index 00000000..40659606
--- /dev/null
+++ b/crates/shirabe/tests/plugin/fixtures/subscriber-v1/composer.json
@@ -0,0 +1,12 @@
+{
+ "name": "subscriber-v1",
+ "version": "1.0.0",
+ "type": "composer-plugin",
+ "autoload": { "psr-0": { "Subscriber": "" } },
+ "extra": {
+ "class": "Subscriber\\Plugin"
+ },
+ "require": {
+ "composer-plugin-api": "^2.0"
+ }
+}
diff --git a/crates/shirabe/tests/plugin/main.rs b/crates/shirabe/tests/plugin/main.rs
index 62c8cfa8..3a66bb75 100644
--- a/crates/shirabe/tests/plugin/main.rs
+++ b/crates/shirabe/tests/plugin/main.rs
@@ -4,3 +4,4 @@ mod async_runtime;
mod config_stub;
mod plugin_installer_test;
+mod subscriber_test;
diff --git a/crates/shirabe/tests/plugin/plugin_installer_test.rs b/crates/shirabe/tests/plugin/plugin_installer_test.rs
index 5dfb6e06..70548ba6 100644
--- a/crates/shirabe/tests/plugin/plugin_installer_test.rs
+++ b/crates/shirabe/tests/plugin/plugin_installer_test.rs
@@ -35,7 +35,7 @@ use tempfile::TempDir;
/// The register/activate flow runs the plugin in the real PHP worker; without a PHP binary the
/// worker cannot start. Tests exercising it return early, following the convention of the
/// non-mock tests in `shirabe-php-rpc`.
-fn php_runtime_available() -> bool {
+pub(crate) fn php_runtime_available() -> bool {
PhpExecutableFinder::new().find(false).is_some()
}
@@ -45,7 +45,7 @@ fn php_runtime_available() -> bool {
/// race the other's `class_exists` checks, so the worker-touching tests run serialized.
static PHP_WORKER_TESTS: std::sync::Mutex<()> = std::sync::Mutex::new(());
-fn lock_php_worker() -> std::sync::MutexGuard<'static, ()> {
+pub(crate) fn lock_php_worker() -> std::sync::MutexGuard<'static, ()> {
PHP_WORKER_TESTS
.lock()
.unwrap_or_else(|poisoned| poisoned.into_inner())
@@ -62,6 +62,16 @@ fn fixtures_dir() -> String {
.to_string()
}
+/// Shirabe-owned fixtures with no upstream counterpart (see `subscriber_test.rs`).
+pub(crate) fn shirabe_fixtures_dir() -> String {
+ let dir = std::path::Path::new(env!("CARGO_MANIFEST_DIR")).join("tests/plugin/fixtures");
+ dir.canonicalize()
+ .expect("the Shirabe plugin fixtures directory must exist")
+ .to_str()
+ .unwrap()
+ .to_string()
+}
+
// PHP mocks `Composer\Downloader\DownloadManager`; install/update/remove resolve to null and the
// other methods are never reached by these tests.
mockall::mock! {
@@ -184,7 +194,16 @@ impl InstallationManagerInterface for MockInstallationManager {
}
fn get_install_path(&self, package: PackageInterfaceHandle) -> Option<String> {
- Some(format!("{}/{}", fixtures_dir(), package.get_pretty_name()))
+ let upstream = format!("{}/{}", fixtures_dir(), package.get_pretty_name());
+ if std::path::Path::new(&upstream).exists() {
+ return Some(upstream);
+ }
+ // Shirabe-specific fixtures (subscriber_test) live next to this test binary.
+ Some(format!(
+ "{}/{}",
+ shirabe_fixtures_dir(),
+ package.get_pretty_name()
+ ))
}
fn set_output_progress(&mut self, _output_progress: bool) {}
@@ -214,20 +233,20 @@ fn locker_installation_manager(
}
#[derive(Debug)]
-struct SetUp {
- io: std::rc::Rc<std::cell::RefCell<BufferIO>>,
- io_dyn: std::rc::Rc<std::cell::RefCell<dyn IOInterface>>,
- pm: std::rc::Rc<std::cell::RefCell<PluginManager>>,
+pub(crate) struct SetUp {
+ pub(crate) io: std::rc::Rc<std::cell::RefCell<BufferIO>>,
+ pub(crate) io_dyn: std::rc::Rc<std::cell::RefCell<dyn IOInterface>>,
+ pub(crate) pm: std::rc::Rc<std::cell::RefCell<PluginManager>>,
autoload_generator: std::rc::Rc<std::cell::RefCell<AutoloadGenerator>>,
packages: Vec<PackageInterfaceHandle>,
- repository: InstalledRepositoryInterfaceHandle,
+ pub(crate) repository: InstalledRepositoryInterfaceHandle,
// Keeps the Composer alive; PluginManager only holds a weak back-reference to it.
- composer: ComposerHandle,
+ pub(crate) composer: ComposerHandle,
// PHP's tearDown() removes this directory; TempDir does the same on drop.
_directory: TempDir,
}
-fn set_up() -> SetUp {
+pub(crate) fn set_up() -> SetUp {
let loader = JsonLoader::new(Box::new(ArrayLoader::new(None, false)));
let mut packages = vec![];
let directory = TempDir::new().unwrap();
@@ -380,7 +399,7 @@ fn plugin_property(
}
}
-fn new_installer(set_up: &SetUp) -> PluginInstaller {
+pub(crate) fn new_installer(set_up: &SetUp) -> PluginInstaller {
PluginInstaller::new(
set_up.io_dyn.clone(),
set_up.composer.upcast().downgrade(),
@@ -740,9 +759,9 @@ impl PluginInterface for CapablePlugin {
}
impl Capable for CapablePlugin {
- fn get_capabilities(&self) -> IndexMap<String, String> {
+ fn get_capabilities(&self) -> anyhow::Result<IndexMap<String, String>> {
*self.get_capabilities_calls.borrow_mut() += 1;
- IndexMap::new()
+ Ok(IndexMap::new())
}
}
diff --git a/crates/shirabe/tests/plugin/subscriber_test.rs b/crates/shirabe/tests/plugin/subscriber_test.rs
new file mode 100644
index 00000000..8de3d8c2
--- /dev/null
+++ b/crates/shirabe/tests/plugin/subscriber_test.rs
@@ -0,0 +1,109 @@
+//! Shirabe-specific integration tests for the subscriber plugin path: upstream Composer
+//! has no test that exercises `addSubscriber`/`getSubscribedEvents` through a real plugin, so
+//! these tests use a Shirabe-owned fixture (`fixtures/subscriber-v1`) instead of a ported one.
+
+use crate::async_runtime::run;
+use crate::plugin_installer_test::{lock_php_worker, new_installer, php_runtime_available, set_up};
+use shirabe::installer::InstallerInterface;
+use shirabe::package::PackageInterfaceHandle;
+use shirabe::package::loader::{ArrayLoader, JsonLoader, JsonLoaderInput};
+
+fn subscriber_fixture_package() -> PackageInterfaceHandle {
+ let loader = JsonLoader::new(Box::new(ArrayLoader::new(None, false)));
+ let path = std::path::Path::new(env!("CARGO_MANIFEST_DIR"))
+ .join("tests/plugin/fixtures/subscriber-v1/composer.json");
+ loader
+ .load(JsonLoaderInput::String(
+ path.canonicalize().unwrap().to_str().unwrap().to_string(),
+ ))
+ .unwrap()
+}
+
+/// Installs the subscriber fixture plugin: activate runs in the PHP child and
+/// `addSubscriber` registers its listeners with the event dispatcher.
+fn install_subscriber_plugin(set_up: &crate::plugin_installer_test::SetUp) {
+ let installer = new_installer(set_up);
+ set_up.pm.borrow_mut().load_installed_plugins().unwrap();
+ run(installer.install(&set_up.repository, subscriber_fixture_package())).unwrap();
+ assert_eq!("activate subscriber-v1\n", set_up.io.borrow().get_output());
+}
+
+fn dispatch(set_up: &crate::plugin_installer_test::SetUp, event_name: &str) -> i64 {
+ let dispatcher = set_up.composer.borrow().get_event_dispatcher();
+ let result = dispatcher.borrow_mut().dispatch(Some(event_name), None);
+ result.unwrap()
+}
+
+#[test]
+fn test_subscriber_listener_receives_event() {
+ if !php_runtime_available() {
+ return;
+ }
+ let _worker = lock_php_worker();
+ let set_up = set_up();
+ install_subscriber_plugin(&set_up);
+
+ // 'post-install-cmd' => 'onPostInstall' (bare method-name shape); the listener calls
+ // $event->getName() back over RPC.
+ let return_code = dispatch(&set_up, "post-install-cmd");
+
+ assert_eq!(0, return_code);
+ assert_eq!(
+ "activate subscriber-v1\nsubscriber saw post-install-cmd\n",
+ set_up.io.borrow().get_output()
+ );
+}
+
+#[test]
+fn test_subscriber_listeners_run_in_priority_order() {
+ if !php_runtime_available() {
+ return;
+ }
+ let _worker = lock_php_worker();
+ let set_up = set_up();
+ install_subscriber_plugin(&set_up);
+
+ // 'shirabe-priority-event' => [['early', 10], ['late', -10]] (multi-handler shape).
+ let return_code = dispatch(&set_up, "shirabe-priority-event");
+
+ assert_eq!(0, return_code);
+ assert_eq!(
+ "activate subscriber-v1\nearly listener\nlate listener\n",
+ set_up.io.borrow().get_output()
+ );
+}
+
+#[test]
+fn test_subscriber_listener_returning_false_sets_return_code() {
+ if !php_runtime_available() {
+ return;
+ }
+ let _worker = lock_php_worker();
+ let set_up = set_up();
+ install_subscriber_plugin(&set_up);
+
+ // 'shirabe-false-event' => ['returnsFalse', 0] (method+priority shape); PHP maps a false
+ // listener return to exit code 1.
+ let return_code = dispatch(&set_up, "shirabe-false-event");
+
+ assert_eq!(1, return_code);
+ assert_eq!(
+ "activate subscriber-v1\nfailing listener\n",
+ set_up.io.borrow().get_output()
+ );
+}
+
+#[test]
+fn test_unrelated_event_does_not_reach_the_subscriber() {
+ if !php_runtime_available() {
+ return;
+ }
+ let _worker = lock_php_worker();
+ let set_up = set_up();
+ install_subscriber_plugin(&set_up);
+
+ let return_code = dispatch(&set_up, "shirabe-unrelated-event");
+
+ assert_eq!(0, return_code);
+ assert_eq!("activate subscriber-v1\n", set_up.io.borrow().get_output());
+}