aboutsummaryrefslogtreecommitdiffhomepage
path: root/crates/shirabe/tests/plugin
diff options
context:
space:
mode:
authornsfisis <nsfisis@gmail.com>2026-08-31 00:22:02 +0900
committernsfisis <nsfisis@gmail.com>2026-08-31 00:22:02 +0900
commit4a2f024846ca1b0bbdb4f6904bae964756c9f701 (patch)
tree3261da76e9f84e9a07946a7c537a394cf01b29de /crates/shirabe/tests/plugin
parent6a6ec1b8f8a5c70d21f3772ce637b763e8ab21ea (diff)
downloadphp-shirabe-4a2f024846ca1b0bbdb4f6904bae964756c9f701.tar.gz
php-shirabe-4a2f024846ca1b0bbdb4f6904bae964756c9f701.tar.zst
php-shirabe-4a2f024846ca1b0bbdb4f6904bae964756c9f701.zip
feat(plugin): serve HttpDownloader's async surface
add() and addCopy() answer with a promise, and enableAsync(), wait() and countActiveJobs() answer alongside them. The Rust future runs to completion before the promise is handed over, so requests a plugin starts together run one after another rather than overlapping; overlapping them needs a promise representation that crosses the boundary unresolved. Everything else the surface does is preserved. add() still refuses a downloader outside a Loop, and it does so by throwing out of the call the way PHP does, where a failed request instead arrives as a rejection the caller handles — __shirabe_rejected_promise is the failure half of the resolved- promise helper. wait() and countActiveJobs() answer for a downloader with no outstanding job, which, once every request settles before its call returns, it never has. This is where HttpDownloader parts company with ProcessExecutor, whose async surface stays an explicit error: executeAsync() resolves its promise with a Symfony Process, whose state is the proc_open() resource of whichever process called start(), where a request resolves its promise with a Response. Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
Diffstat (limited to 'crates/shirabe/tests/plugin')
-rw-r--r--crates/shirabe/tests/plugin/e2e_http_downloader_test.rs5
-rw-r--r--crates/shirabe/tests/plugin/fixtures/e2e-http-downloader/plugin/src/Plugin.php33
2 files changed, 38 insertions, 0 deletions
diff --git a/crates/shirabe/tests/plugin/e2e_http_downloader_test.rs b/crates/shirabe/tests/plugin/e2e_http_downloader_test.rs
index 42471b61..f5fdfa97 100644
--- a/crates/shirabe/tests/plugin/e2e_http_downloader_test.rs
+++ b/crates/shirabe/tests/plugin/e2e_http_downloader_test.rs
@@ -76,6 +76,11 @@ get=ok
response class=\"Composer\\\\Util\\\\Http\\\\Response\" body=\"{\\\"probe\\\":true,\\\"n\\\":42}\" headers=[]
getHeader=null
copy=ok file=\"{\\\"probe\\\":true,\\\"n\\\":42}\"
+add-before-enable=LogicException: You must use the HttpDownloader instance which is part of a Composer\\Loop instance to be able to run async http requests
+add=ok body=\"{\\\"probe\\\":true,\\\"n\\\":42}\"
+add-missing=ok rejected=\"Composer\\\\Downloader\\\\TransportException\"
+addCopy=ok file=\"{\\\"probe\\\":true,\\\"n\\\":42}\"
+countActiveJobs=0 wait=ok
collect=ok
",
upstream.trace
diff --git a/crates/shirabe/tests/plugin/fixtures/e2e-http-downloader/plugin/src/Plugin.php b/crates/shirabe/tests/plugin/fixtures/e2e-http-downloader/plugin/src/Plugin.php
index 054a47ca..6e91ad00 100644
--- a/crates/shirabe/tests/plugin/fixtures/e2e-http-downloader/plugin/src/Plugin.php
+++ b/crates/shirabe/tests/plugin/fixtures/e2e-http-downloader/plugin/src/Plugin.php
@@ -98,6 +98,39 @@ class Plugin implements PluginInterface, EventSubscriberInterface
$downloader->copy('file://' . $payload, $target);
}) . ' file=' . json_encode(@file_get_contents($target));
+ // A downloader that is not part of a Loop refuses async requests, so the probe records
+ // both sides of the gate.
+ $lines[] = 'add-before-enable=' . $this->describe(static function () use ($downloader, $payload): void {
+ $downloader->add('file://' . $payload);
+ });
+ $downloader->enableAsync();
+
+ $added = null;
+ $lines[] = 'add=' . $this->describe(static function () use ($downloader, $payload, &$added): void {
+ $downloader->add('file://' . $payload)->then(static function ($result) use (&$added): void {
+ $added = $result;
+ });
+ }) . ' body=' . json_encode($added === null ? null : $added->getBody());
+
+ // A path that cannot exist keeps the rejection reason free of the temporary directory.
+ $rejection = null;
+ $lines[] = 'add-missing=' . $this->describe(static function () use ($downloader, &$rejection): void {
+ $downloader->add('file:///shirabe-probe-missing.json')->then(null, static function ($error) use (&$rejection): void {
+ $rejection = $error;
+ });
+ }) . ' rejected=' . json_encode($rejection === null ? null : \get_class($rejection));
+
+ $lines[] = 'addCopy=' . $this->describe(static function () use ($downloader, $payload): void {
+ $downloader->addCopy('file://' . $payload, getcwd() . '/probe-async-copy.json')->then(null, static function ($error): void {
+ throw $error;
+ });
+ }) . ' file=' . json_encode(@file_get_contents(getcwd() . '/probe-async-copy.json'));
+
+ $lines[] = 'countActiveJobs=' . json_encode($downloader->countActiveJobs())
+ . ' wait=' . $this->describe(static function () use ($downloader): void {
+ $downloader->wait();
+ });
+
// collect() unsets the response's own properties, so the object is spent afterwards and
// nothing may read it again.
$lines[] = 'collect=' . $this->describe(static function () use ($response): void {