From b566f7f6d49be9be97ec6679642978feb3cb5321 Mon Sep 17 00:00:00 2001 From: Baptiste Langlade Date: Sun, 16 Nov 2025 12:06:12 +0100 Subject: [PATCH 01/27] require php 8.4 --- .github/workflows/ci.yml | 10 ++++------ CHANGELOG.md | 6 ++++++ composer.json | 2 +- 3 files changed, 11 insertions(+), 7 deletions(-) diff --git a/.github/workflows/ci.yml b/.github/workflows/ci.yml index 3d89ea2..05d18ca 100644 --- a/.github/workflows/ci.yml +++ b/.github/workflows/ci.yml @@ -4,17 +4,15 @@ on: [push, pull_request] jobs: blackbox: - uses: innmind/github-workflows/.github/workflows/black-box-matrix.yml@main + uses: innmind/github-workflows/.github/workflows/black-box-matrix.yml@next with: tags: 'ci' coverage: - uses: innmind/github-workflows/.github/workflows/coverage-matrix.yml@main + uses: innmind/github-workflows/.github/workflows/coverage-matrix.yml@next secrets: inherit with: tags: 'ci' psalm: - uses: innmind/github-workflows/.github/workflows/psalm-matrix.yml@main + uses: innmind/github-workflows/.github/workflows/psalm-matrix.yml@next cs: - uses: innmind/github-workflows/.github/workflows/cs.yml@main - with: - php-version: '8.2' + uses: innmind/github-workflows/.github/workflows/cs.yml@next diff --git a/CHANGELOG.md b/CHANGELOG.md index 0a41e43..f212bb1 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -1,5 +1,11 @@ # Changelog +## [Unreleased] + +### Changed + +- Requires PHP `8.4` + ## 6.1.0 - 2025-08-06 ### Added diff --git a/composer.json b/composer.json index 15284f3..930aa7e 100644 --- a/composer.json +++ b/composer.json @@ -14,7 +14,7 @@ "issues": "http://github.com/Innmind/ServerControl/issues" }, "require": { - "php": "~8.2", + "php": "~8.4", "innmind/immutable": "~5.12", "innmind/url": "~4.0", "psr/log": "~3.0", From f9d304b7132b377d088e0db3ed71a92c597363b5 Mon Sep 17 00:00:00 2001 From: Baptiste Langlade Date: Sun, 16 Nov 2025 12:09:08 +0100 Subject: [PATCH 02/27] better integrate tests in blackbox --- tests/Server/Process/UnixTest.php | 6 +++--- tests/Server/Volumes/NameTest.php | 6 +++--- 2 files changed, 6 insertions(+), 6 deletions(-) diff --git a/tests/Server/Process/UnixTest.php b/tests/Server/Process/UnixTest.php index 0506d7b..02285c1 100644 --- a/tests/Server/Process/UnixTest.php +++ b/tests/Server/Process/UnixTest.php @@ -59,15 +59,15 @@ public function testSimpleOutput() #[Group('ci')] #[Group('local')] - public function testOutput() + public function testOutput(): BlackBox\Proof { - $this + return $this ->forAll( Set::strings() ->madeOf(Set::strings()->chars()->ascii()->filter(static fn($char) => $char !== '\\')) ->between(1, 126), ) - ->then(function($echo) { + ->prove(function($echo) { $cat = new Unix( Clock::live(), IO::fromAmbientAuthority(), diff --git a/tests/Server/Volumes/NameTest.php b/tests/Server/Volumes/NameTest.php index 4304384..cab1da5 100644 --- a/tests/Server/Volumes/NameTest.php +++ b/tests/Server/Volumes/NameTest.php @@ -17,11 +17,11 @@ class NameTest extends TestCase #[Group('ci')] #[Group('local')] - public function testInterface() + public function testInterface(): BlackBox\Proof { - $this + return $this ->forAll(Set::strings()->atLeast(1)) - ->then(function($name) { + ->prove(function($name) { $this->assertSame($name, Name::of($name)->toString()); }); } From 27dee273809c99a5a6aeddbe83064835cc253cc4 Mon Sep 17 00:00:00 2001 From: Baptiste Langlade Date: Sun, 16 Nov 2025 12:17:31 +0100 Subject: [PATCH 03/27] update dependencies --- composer.json | 15 +++++++++------ src/Server/Process/Started.php | 2 +- tests/Server/CommandTest.php | 4 ++-- tests/Server/Process/BackgroundTest.php | 10 +++++----- tests/Server/Process/ForegroundTest.php | 18 +++++++++--------- tests/Server/Process/UnixTest.php | 20 ++++++++++---------- tests/Server/Processes/LoggerTest.php | 4 ++-- tests/Server/Processes/RemoteTest.php | 4 ++-- tests/Server/Processes/UnixTest.php | 24 ++++++++++++------------ tests/Server/ScriptTest.php | 4 ++-- tests/Server/Volumes/UnixTest.php | 4 ++-- tests/ServerFactoryTest.php | 6 +++--- tests/Servers/LoggerTest.php | 4 ++-- tests/Servers/MockTest.php | 2 +- tests/Servers/RemoteTest.php | 4 ++-- tests/Servers/UnixTest.php | 8 ++++---- 16 files changed, 68 insertions(+), 65 deletions(-) diff --git a/composer.json b/composer.json index 930aa7e..94de669 100644 --- a/composer.json +++ b/composer.json @@ -15,13 +15,16 @@ }, "require": { "php": "~8.4", - "innmind/immutable": "~5.12", - "innmind/url": "~4.0", + "innmind/immutable": "dev-next", + "innmind/url": "dev-next", "psr/log": "~3.0", - "innmind/time-continuum": "^4.1.1", - "innmind/filesystem": "~8.0", - "innmind/time-warp": "~4.0", - "innmind/io": "~3.1" + "innmind/time-continuum": "dev-next", + "innmind/filesystem": "dev-next", + "innmind/time-warp": "dev-next", + "innmind/io": "dev-next", + "innmind/media-type": "dev-next", + "innmind/validation": "dev-next", + "innmind/ip": "dev-next" }, "autoload": { "psr-4": { diff --git a/src/Server/Process/Started.php b/src/Server/Process/Started.php index 5933876..d994d16 100644 --- a/src/Server/Process/Started.php +++ b/src/Server/Process/Started.php @@ -177,7 +177,7 @@ public function output(): Sequence } /** @var Sequence> */ - yield Sequence::of(Either::right(new SideEffect)); + yield Sequence::of(Either::right(SideEffect::identity)); })->flatMap(static fn($chunks) => $chunks); } diff --git a/tests/Server/CommandTest.php b/tests/Server/CommandTest.php index fd4bc19..1d76895 100644 --- a/tests/Server/CommandTest.php +++ b/tests/Server/CommandTest.php @@ -102,7 +102,7 @@ public function testWithEnvironment() $this->assertInstanceOf(Command::class, $command); $this->assertSame('bin/console', $command->toString()); $this->assertInstanceOf(Map::class, $command->environment()); - $this->assertCount(1, $command->environment()); + $this->assertSame(1, $command->environment()->size()); $this->assertSame('prod', $command->environment()->get('SYMFONY_ENV')->match( static fn($env) => $env, static fn() => null, @@ -120,7 +120,7 @@ public function testWithEnvironments() $this->assertInstanceOf(Command::class, $command); $this->assertSame('bin/console', $command->toString()); $this->assertInstanceOf(Map::class, $command->environment()); - $this->assertCount(3, $command->environment()); + $this->assertSame(3, $command->environment()->size()); $this->assertSame('prod', $command->environment()->get('SYMFONY_ENV')->match( static fn($env) => $env, static fn() => null, diff --git a/tests/Server/Process/BackgroundTest.php b/tests/Server/Process/BackgroundTest.php index cb997d3..e4a6e67 100644 --- a/tests/Server/Process/BackgroundTest.php +++ b/tests/Server/Process/BackgroundTest.php @@ -13,7 +13,7 @@ Clock, Period, }; -use Innmind\TimeWarp\Halt\Usleep; +use Innmind\TimeWarp\Halt; use Innmind\IO\IO; use Innmind\Immutable\Monoid\Concat; use Innmind\BlackBox\PHPUnit\Framework\TestCase; @@ -28,7 +28,7 @@ public function testInterface() $process = new Unix( Clock::live(), IO::fromAmbientAuthority(), - Usleep::new(), + Halt::new(), Period::second(1), Command::background('ps'), ); @@ -46,7 +46,7 @@ public function testPid() $ps = new Unix( Clock::live(), IO::fromAmbientAuthority(), - Usleep::new(), + Halt::new(), Period::second(1), Command::background('ps'), ); @@ -65,7 +65,7 @@ public function testOutput() $slow = new Unix( Clock::live(), IO::fromAmbientAuthority(), - Usleep::new(), + Halt::new(), Period::second(1), Command::background('php fixtures/slow.php'), ); @@ -90,7 +90,7 @@ public function testWait() $slow = new Unix( Clock::live(), IO::fromAmbientAuthority(), - Usleep::new(), + Halt::new(), Period::second(1), Command::background('php fixtures/slow.php'), ); diff --git a/tests/Server/Process/ForegroundTest.php b/tests/Server/Process/ForegroundTest.php index abefbec..c1e505f 100644 --- a/tests/Server/Process/ForegroundTest.php +++ b/tests/Server/Process/ForegroundTest.php @@ -15,7 +15,7 @@ Clock, Period, }; -use Innmind\TimeWarp\Halt\Usleep; +use Innmind\TimeWarp\Halt; use Innmind\IO\IO; use Innmind\Immutable\Monoid\Concat; use Innmind\BlackBox\PHPUnit\Framework\TestCase; @@ -30,7 +30,7 @@ public function testInterface() $ps = new Unix( Clock::live(), IO::fromAmbientAuthority(), - Usleep::new(), + Halt::new(), Period::second(1), Command::foreground('ps'), ); @@ -48,7 +48,7 @@ public function testPid() $ps = new Unix( Clock::live(), IO::fromAmbientAuthority(), - Usleep::new(), + Halt::new(), Period::second(1), Command::foreground('ps'), ); @@ -70,7 +70,7 @@ public function testOutput() $slow = new Unix( Clock::live(), IO::fromAmbientAuthority(), - Usleep::new(), + Halt::new(), Period::second(1), Command::foreground('php fixtures/slow.php') ->withEnvironment('PATH', $_SERVER['PATH']), @@ -108,7 +108,7 @@ public function testExitCodeForFailingProcess() $fail = new Unix( Clock::live(), IO::fromAmbientAuthority(), - Usleep::new(), + Halt::new(), Period::second(1), Command::foreground('php fixtures/fails.php') ->withEnvironment('PATH', $_SERVER['PATH']), @@ -149,7 +149,7 @@ public function testWait() $slow = new Unix( Clock::live(), IO::fromAmbientAuthority(), - Usleep::new(), + Halt::new(), Period::second(1), Command::foreground('php fixtures/slow.php') ->withEnvironment('PATH', $_SERVER['PATH']), @@ -180,7 +180,7 @@ public function testExitStatusIsKeptInMemory() $slow = new Unix( Clock::live(), IO::fromAmbientAuthority(), - Usleep::new(), + Halt::new(), Period::second(1), Command::foreground('php fixtures/slow.php') ->withEnvironment('PATH', $_SERVER['PATH']), @@ -200,7 +200,7 @@ public function testExitStatusIsAvailableAfterIteratingOverTheOutput() $slow = new Unix( Clock::live(), IO::fromAmbientAuthority(), - Usleep::new(), + Halt::new(), Period::second(1), Command::foreground('php fixtures/slow.php') ->withEnvironment('PATH', $_SERVER['PATH']), @@ -226,7 +226,7 @@ public function testOutputIsAvailableAfterWaitingForExitStatus() $slow = new Unix( Clock::live(), IO::fromAmbientAuthority(), - Usleep::new(), + Halt::new(), Period::second(1), Command::foreground('php fixtures/slow.php') ->withEnvironment('PATH', $_SERVER['PATH']), diff --git a/tests/Server/Process/UnixTest.php b/tests/Server/Process/UnixTest.php index 02285c1..ee393df 100644 --- a/tests/Server/Process/UnixTest.php +++ b/tests/Server/Process/UnixTest.php @@ -15,7 +15,7 @@ Clock, Period, }; -use Innmind\TimeWarp\Halt\Usleep; +use Innmind\TimeWarp\Halt; use Innmind\Url\Path; use Innmind\IO\IO; use Innmind\Immutable\{ @@ -40,7 +40,7 @@ public function testSimpleOutput() $cat = new Unix( Clock::live(), IO::fromAmbientAuthority(), - Usleep::new(), + Halt::new(), Period::second(1), Command::foreground('echo')->withArgument('hello'), ); @@ -71,7 +71,7 @@ public function testOutput(): BlackBox\Proof $cat = new Unix( Clock::live(), IO::fromAmbientAuthority(), - Usleep::new(), + Halt::new(), Period::second(1), Command::foreground('echo')->withArgument($echo), ); @@ -94,7 +94,7 @@ public function testSlowOutput() $slow = new Unix( Clock::live(), IO::fromAmbientAuthority(), - Usleep::new(), + Halt::new(), Period::second(1), Command::foreground('php') ->withArgument('fixtures/slow.php') @@ -122,7 +122,7 @@ public function testTimeoutSlowOutput() $slow = new Unix( Clock::live(), IO::fromAmbientAuthority(), - Usleep::new(), + Halt::new(), Period::second(1), Command::foreground('php') ->withArgument('fixtures/slow.php') @@ -161,7 +161,7 @@ public function testTimeoutWaitSlowProcess() $slow = new Unix( Clock::live(), IO::fromAmbientAuthority(), - Usleep::new(), + Halt::new(), Period::second(1), Command::foreground('php') ->withArgument('fixtures/slow.php') @@ -196,7 +196,7 @@ public function testWaitSuccess() $cat = new Unix( Clock::live(), IO::fromAmbientAuthority(), - Usleep::new(), + Halt::new(), Period::second(1), Command::foreground('echo')->withArgument('hello'), ); @@ -221,7 +221,7 @@ public function testWaitFail() $cat = new Unix( Clock::live(), IO::fromAmbientAuthority(), - Usleep::new(), + Halt::new(), Period::second(1), Command::foreground('php') ->withArgument('fixtures/fails.php') @@ -249,7 +249,7 @@ public function testWithInput() $cat = new Unix( Clock::live(), IO::fromAmbientAuthority(), - Usleep::new(), + Halt::new(), Period::second(1), Command::foreground('cat')->withInput(Content::oneShot( IO::fromAmbientAuthority() @@ -277,7 +277,7 @@ public function testOverwrite() $cat = new Unix( Clock::live(), IO::fromAmbientAuthority(), - Usleep::new(), + Halt::new(), Period::second(1), Command::foreground('cat') ->withInput(Content::oneShot( diff --git a/tests/Server/Processes/LoggerTest.php b/tests/Server/Processes/LoggerTest.php index 8d5db1f..ddf78c2 100644 --- a/tests/Server/Processes/LoggerTest.php +++ b/tests/Server/Processes/LoggerTest.php @@ -13,7 +13,7 @@ Process\Pid }; use Innmind\TimeContinuum\Clock; -use Innmind\TimeWarp\Halt\Usleep; +use Innmind\TimeWarp\Halt; use Innmind\IO\IO; use Innmind\Url\Path; use Psr\Log\NullLogger; @@ -88,7 +88,7 @@ private function processes(): Unix return Unix::of( Clock::live(), IO::fromAmbientAuthority(), - Usleep::new(), + Halt::new(), ); } } diff --git a/tests/Server/Processes/RemoteTest.php b/tests/Server/Processes/RemoteTest.php index a04bd1a..e2c2246 100644 --- a/tests/Server/Processes/RemoteTest.php +++ b/tests/Server/Processes/RemoteTest.php @@ -12,7 +12,7 @@ Process\Pid, }; use Innmind\TimeContinuum\Clock; -use Innmind\TimeWarp\Halt\Usleep; +use Innmind\TimeWarp\Halt; use Innmind\IO\IO; use Innmind\Url\{ Path, @@ -144,7 +144,7 @@ private function processes(string ...$commands): Processes $processes = Processes\Unix::of( Clock::live(), IO::fromAmbientAuthority(), - Usleep::new(), + Halt::new(), ); return new class($processes, $this, $commands) implements Processes { diff --git a/tests/Server/Processes/UnixTest.php b/tests/Server/Processes/UnixTest.php index 37d9db9..631701d 100644 --- a/tests/Server/Processes/UnixTest.php +++ b/tests/Server/Processes/UnixTest.php @@ -16,7 +16,7 @@ Clock, Period, }; -use Innmind\TimeWarp\Halt\Usleep; +use Innmind\TimeWarp\Halt; use Innmind\IO\IO; use Innmind\Immutable\{ SideEffect, @@ -34,7 +34,7 @@ public function testInterface() $this->assertInstanceOf(Processes::class, Unix::of( Clock::live(), IO::fromAmbientAuthority(), - Usleep::new(), + Halt::new(), )); } @@ -45,7 +45,7 @@ public function testExecute() $processes = Unix::of( Clock::live(), IO::fromAmbientAuthority(), - Usleep::new(), + Halt::new(), ); $start = \time(); $process = $processes->execute( @@ -66,7 +66,7 @@ public function testExecuteInBackground() $processes = Unix::of( Clock::live(), IO::fromAmbientAuthority(), - Usleep::new(), + Halt::new(), ); $start = \time(); $process = $processes->execute( @@ -88,7 +88,7 @@ public function testExecuteWithInput() $processes = Unix::of( Clock::live(), IO::fromAmbientAuthority(), - Usleep::new(), + Halt::new(), ); $process = $processes->execute( Command::foreground('cat')->withInput(Content::oneShot( @@ -120,7 +120,7 @@ public function testKill() $processes = Unix::of( Clock::live(), IO::fromAmbientAuthority(), - Usleep::new(), + Halt::new(), ); $start = \time(); $process = $processes->execute( @@ -153,7 +153,7 @@ public function testTimeout() $processes = Unix::of( Clock::live(), IO::fromAmbientAuthority(), - Usleep::new(), + Halt::new(), ); $start = \time(); $process = $processes->execute( @@ -181,7 +181,7 @@ public function testStreamOutput() $processes = Unix::of( Clock::live(), IO::fromAmbientAuthority(), - Usleep::new(), + Halt::new(), ); $processes ->execute( @@ -206,7 +206,7 @@ public function testSecondCallToStreamedOutputThrowsAnError() $processes = Unix::of( Clock::live(), IO::fromAmbientAuthority(), - Usleep::new(), + Halt::new(), ); $process = $processes ->execute( @@ -230,7 +230,7 @@ public function testOutputIsNotLostByDefault() $processes = Unix::of( Clock::live(), IO::fromAmbientAuthority(), - Usleep::new(), + Halt::new(), ); $process = $processes ->execute( @@ -257,7 +257,7 @@ public function testStopProcessEvenWhenPipesAreStillOpenAfterTheProcessBeingKill $processes = Unix::of( Clock::live(), IO::fromAmbientAuthority(), - Usleep::new(), + Halt::new(), ); $tail = $processes->execute( Command::foreground('tail') @@ -284,7 +284,7 @@ public function testRegressionWhenProcessFinishesTooFastItsFlaggedAsFailingEvenT $processes = Unix::of( Clock::live(), IO::fromAmbientAuthority(), - Usleep::new(), + Halt::new(), ); $this->assertTrue( diff --git a/tests/Server/ScriptTest.php b/tests/Server/ScriptTest.php index 466a639..61288fe 100644 --- a/tests/Server/ScriptTest.php +++ b/tests/Server/ScriptTest.php @@ -13,7 +13,7 @@ Clock, Period, }; -use Innmind\TimeWarp\Halt\Usleep; +use Innmind\TimeWarp\Halt; use Innmind\IO\IO; use Innmind\Immutable\SideEffect; use Innmind\BlackBox\PHPUnit\Framework\TestCase; @@ -79,7 +79,7 @@ private function server(): Unix return Unix::of( Clock::live(), IO::fromAmbientAuthority(), - Usleep::new(), + Halt::new(), ); } } diff --git a/tests/Server/Volumes/UnixTest.php b/tests/Server/Volumes/UnixTest.php index 57c6c08..af82af1 100644 --- a/tests/Server/Volumes/UnixTest.php +++ b/tests/Server/Volumes/UnixTest.php @@ -14,7 +14,7 @@ Exception\ProcessFailed, }; use Innmind\TimeContinuum\Clock; -use Innmind\TimeWarp\Halt\Usleep; +use Innmind\TimeWarp\Halt; use Innmind\IO\IO; use Innmind\Url\Path; use Innmind\Immutable\{ @@ -215,7 +215,7 @@ private function processes(array ...$commands): Processes $processes = Processes\Unix::of( Clock::live(), IO::fromAmbientAuthority(), - Usleep::new(), + Halt::new(), ); return new class($processes, $this, $commands) implements Processes { diff --git a/tests/ServerFactoryTest.php b/tests/ServerFactoryTest.php index d0aa03f..5d0a9f5 100644 --- a/tests/ServerFactoryTest.php +++ b/tests/ServerFactoryTest.php @@ -9,7 +9,7 @@ Exception\UnsupportedOperatingSystem }; use Innmind\TimeContinuum\Clock; -use Innmind\TimeWarp\Halt\Usleep; +use Innmind\TimeWarp\Halt; use Innmind\IO\IO; use Innmind\BlackBox\PHPUnit\Framework\TestCase; use PHPUnit\Framework\Attributes\Group; @@ -29,7 +29,7 @@ public function testMakeUnix() $this->assertInstanceOf(Server::class, ServerFactory::build( Clock::live(), IO::fromAmbientAuthority(), - Usleep::new(), + Halt::new(), )); } @@ -48,7 +48,7 @@ public function testThrowWhenUnsupportedOperatingSystem() ServerFactory::build( Clock::live(), IO::fromAmbientAuthority(), - Usleep::new(), + Halt::new(), ); } } diff --git a/tests/Servers/LoggerTest.php b/tests/Servers/LoggerTest.php index 51a0920..582b03b 100644 --- a/tests/Servers/LoggerTest.php +++ b/tests/Servers/LoggerTest.php @@ -14,7 +14,7 @@ Server\Signal, }; use Innmind\TimeContinuum\Clock; -use Innmind\TimeWarp\Halt\Usleep; +use Innmind\TimeWarp\Halt; use Innmind\IO\IO; use Innmind\Immutable\{ Attempt, @@ -174,7 +174,7 @@ private function processes(): Unix return Unix::of( Clock::live(), IO::fromAmbientAuthority(), - Usleep::new(), + Halt::new(), ); } } diff --git a/tests/Servers/MockTest.php b/tests/Servers/MockTest.php index 36ccde3..52de8d8 100644 --- a/tests/Servers/MockTest.php +++ b/tests/Servers/MockTest.php @@ -515,7 +515,7 @@ public function testWillExecute() static fn($error) => $error, ), ); - $this->assertCount(0, $process->output()); + $this->assertSame(0, $process->output()->size()); $this ->assert() ->not() diff --git a/tests/Servers/RemoteTest.php b/tests/Servers/RemoteTest.php index 9902063..3e7961a 100644 --- a/tests/Servers/RemoteTest.php +++ b/tests/Servers/RemoteTest.php @@ -14,7 +14,7 @@ Server\Signal, }; use Innmind\TimeContinuum\Clock; -use Innmind\TimeWarp\Halt\Usleep; +use Innmind\TimeWarp\Halt; use Innmind\IO\IO; use Innmind\Url\Authority\{ Host, @@ -206,7 +206,7 @@ private function processes(): Unix return Unix::of( Clock::live(), IO::fromAmbientAuthority(), - Usleep::new(), + Halt::new(), ); } } diff --git a/tests/Servers/UnixTest.php b/tests/Servers/UnixTest.php index e8844ae..6ee2078 100644 --- a/tests/Servers/UnixTest.php +++ b/tests/Servers/UnixTest.php @@ -10,7 +10,7 @@ Server\Volumes, }; use Innmind\TimeContinuum\Clock; -use Innmind\TimeWarp\Halt\Usleep; +use Innmind\TimeWarp\Halt; use Innmind\IO\IO; use Innmind\BlackBox\PHPUnit\Framework\TestCase; use PHPUnit\Framework\Attributes\Group; @@ -24,7 +24,7 @@ public function testInterface() $this->assertInstanceOf(Server::class, Unix::of( Clock::live(), IO::fromAmbientAuthority(), - Usleep::new(), + Halt::new(), )); } @@ -35,7 +35,7 @@ public function testProcesses() $this->assertInstanceOf(Processes::class, Unix::of( Clock::live(), IO::fromAmbientAuthority(), - Usleep::new(), + Halt::new(), )->processes()); } @@ -46,7 +46,7 @@ public function testVolumes() $this->assertInstanceOf(Volumes::class, Unix::of( Clock::live(), IO::fromAmbientAuthority(), - Usleep::new(), + Halt::new(), )->volumes()); } } From d541785973a9501d14926c038c626b9ac9944b15 Mon Sep 17 00:00:00 2001 From: Baptiste Langlade Date: Sun, 16 Nov 2025 12:24:02 +0100 Subject: [PATCH 04/27] remove mock system (it will be rebuilt later on) --- src/Servers/Mock.php | 156 ------ src/Servers/Mock/Actions.php | 66 --- src/Servers/Mock/Execute.php | 43 -- src/Servers/Mock/MountVolume.php | 50 -- src/Servers/Mock/ProcessBuilder.php | 122 ----- src/Servers/Mock/Processes.php | 48 -- src/Servers/Mock/Reboot.php | 41 -- src/Servers/Mock/Shutdown.php | 41 -- src/Servers/Mock/UnmountVolume.php | 47 -- src/Servers/Mock/Volumes.php | 54 --- tests/Servers/MockTest.php | 711 ---------------------------- 11 files changed, 1379 deletions(-) delete mode 100644 src/Servers/Mock.php delete mode 100644 src/Servers/Mock/Actions.php delete mode 100644 src/Servers/Mock/Execute.php delete mode 100644 src/Servers/Mock/MountVolume.php delete mode 100644 src/Servers/Mock/ProcessBuilder.php delete mode 100644 src/Servers/Mock/Processes.php delete mode 100644 src/Servers/Mock/Reboot.php delete mode 100644 src/Servers/Mock/Shutdown.php delete mode 100644 src/Servers/Mock/UnmountVolume.php delete mode 100644 src/Servers/Mock/Volumes.php delete mode 100644 tests/Servers/MockTest.php diff --git a/src/Servers/Mock.php b/src/Servers/Mock.php deleted file mode 100644 index 1fa5af0..0000000 --- a/src/Servers/Mock.php +++ /dev/null @@ -1,156 +0,0 @@ -actions); - } - - #[\Override] - public function volumes(): Volumes - { - return Mock\Volumes::new($this->assert, $this->actions); - } - - #[\Override] - public function reboot(): Attempt - { - return $this - ->actions - ->pull(Mock\Reboot::class, 'No reboot was expected') - ->run(); - } - - #[\Override] - public function shutdown(): Attempt - { - return $this - ->actions - ->pull(Mock\Shutdown::class, 'No shutdown was expected') - ->run(); - } - - #[\NoDiscard] - public function willReboot(): self - { - return new self( - $this->assert, - $this->actions->add(Mock\Reboot::success()), - ); - } - - #[\NoDiscard] - public function willFailToReboot(): self - { - return new self( - $this->assert, - $this->actions->add(Mock\Reboot::fail()), - ); - } - - #[\NoDiscard] - public function willShutdown(): self - { - return new self( - $this->assert, - $this->actions->add(Mock\Shutdown::success()), - ); - } - - #[\NoDiscard] - public function willFailToShutdown(): self - { - return new self( - $this->assert, - $this->actions->add(Mock\Shutdown::fail()), - ); - } - - #[\NoDiscard] - public function willMountVolume(string $name, string $path): self - { - return new self( - $this->assert, - $this->actions->add(Mock\MountVolume::success($name, $path)), - ); - } - - #[\NoDiscard] - public function willFailToMountVolume(string $name, string $path): self - { - return new self( - $this->assert, - $this->actions->add(Mock\MountVolume::fail($name, $path)), - ); - } - - #[\NoDiscard] - public function willUnmountVolume(string $name): self - { - return new self( - $this->assert, - $this->actions->add(Mock\UnmountVolume::success($name)), - ); - } - - #[\NoDiscard] - public function willFailToUnmountVolume(string $name): self - { - return new self( - $this->assert, - $this->actions->add(Mock\UnmountVolume::fail($name)), - ); - } - - /** - * @param callable(Command): void $assert - * @param ?callable(Command, Mock\ProcessBuilder): Mock\ProcessBuilder $build - */ - #[\NoDiscard] - public function willExecute( - callable $assert, - ?callable $build = null, - ): self { - return new self( - $this->assert, - $this->actions->add(Mock\Execute::of( - $assert, - $build ?? static fn(Command $command, Mock\ProcessBuilder $build) => $build, - )), - ); - } - - public function assert(): void - { - $this->assert->count( - 0, - $this->actions, - 'There are untriggered actions', - ); - } -} diff --git a/src/Servers/Mock/Actions.php b/src/Servers/Mock/Actions.php deleted file mode 100644 index 22bdd5a..0000000 --- a/src/Servers/Mock/Actions.php +++ /dev/null @@ -1,66 +0,0 @@ - $actions - */ - private function __construct( - private Assert $assert, - private \SplQueue $actions, - ) { - } - - public static function new(Assert $assert): self - { - /** @var \SplQueue */ - $queue = new \SplQueue; - - return new self($assert, $queue); - } - - #[\Override] - public function count(): int - { - return $this->actions->count(); - } - - public function add(object $action): self - { - $actions = clone $this->actions; - $actions->enqueue($action); - - return new self($this->assert, $actions); - } - - /** - * @template T - * - * @param class-string $class - * @param non-empty-string $message - * - * @return T - */ - public function pull(string $class, string $message): object - { - if ($this->actions->isEmpty()) { - $this->assert->fail($message); - } - - $action = $this->actions->dequeue(); - - if ($action instanceof $class) { - return $action; - } - - $this->assert->fail($message); - } -} diff --git a/src/Servers/Mock/Execute.php b/src/Servers/Mock/Execute.php deleted file mode 100644 index 37bfd39..0000000 --- a/src/Servers/Mock/Execute.php +++ /dev/null @@ -1,43 +0,0 @@ -assert)($command); - - return ($this->build)($command, ProcessBuilder::new())->build(); - } -} diff --git a/src/Servers/Mock/MountVolume.php b/src/Servers/Mock/MountVolume.php deleted file mode 100644 index 0029c72..0000000 --- a/src/Servers/Mock/MountVolume.php +++ /dev/null @@ -1,50 +0,0 @@ - - */ - public function run( - Assert $assert, - string $name, - string $path, - ): Attempt { - $assert->same($this->name, $name); - $assert->same($this->path, $path); - - return match ($this->success) { - true => Attempt::result(SideEffect::identity()), - false => Attempt::error(new \Exception('Failed to mount volume')), - }; - } -} diff --git a/src/Servers/Mock/ProcessBuilder.php b/src/Servers/Mock/ProcessBuilder.php deleted file mode 100644 index aeb2231..0000000 --- a/src/Servers/Mock/ProcessBuilder.php +++ /dev/null @@ -1,122 +0,0 @@ -|list $output - */ - #[\NoDiscard] - public function success(Sequence|array|null $output = null): self - { - return new self(new Success(self::output($output))); - } - - /** - * @param Sequence|list $output - */ - #[\NoDiscard] - public function signaled(Sequence|array|null $output = null): self - { - return new self(new Signaled(self::output($output))); - } - - /** - * @param Sequence|list $output - */ - #[\NoDiscard] - public function timedOut(Sequence|array|null $output = null): self - { - return new self(new TimedOut(self::output($output))); - } - - /** - * @param int<1, 255> $exitCode - * @param Sequence|list $output - */ - #[\NoDiscard] - public function failed(int $exitCode = 1, Sequence|array|null $output = null): self - { - return new self(new Failed( - new ExitCode($exitCode), - self::output($output), - )); - } - - /** - * @internal - */ - #[\NoDiscard] - public function build(): Process - { - $result = $this->result; - - /** - * This a trick to not expose any mock contructor on the Process class. - * - * @psalm-suppress PossiblyNullFunctionCall - * @psalm-suppress MixedReturnStatement - * @psalm-suppress InaccessibleMethod - */ - return (\Closure::bind( - static fn() => new Process(new Mock($result)), - null, - Process::class, - ))(); - } - - /** - * @param Sequence|list $output - * - * @return Sequence - */ - private static function output(Sequence|array|null $output = null): Sequence - { - if (\is_null($output)) { - return Sequence::of(); - } - - if (\is_array($output)) { - return Sequence::of(...$output)->map(static fn($pair) => Chunk::of( - Str::of($pair[0]), - match ($pair[1]) { - 'output' => Type::output, - 'error' => Type::error, - }, - )); - } - - return $output; - } -} diff --git a/src/Servers/Mock/Processes.php b/src/Servers/Mock/Processes.php deleted file mode 100644 index 085c629..0000000 --- a/src/Servers/Mock/Processes.php +++ /dev/null @@ -1,48 +0,0 @@ -actions - ->pull(Execute::class, 'No process expected to be executed') - ->run($command), - ); - } - - #[\Override] - public function kill(Pid $pid, Signal $signal): Attempt - { - return Attempt::result(SideEffect::identity()); - } -} diff --git a/src/Servers/Mock/Reboot.php b/src/Servers/Mock/Reboot.php deleted file mode 100644 index 6da4e55..0000000 --- a/src/Servers/Mock/Reboot.php +++ /dev/null @@ -1,41 +0,0 @@ - - */ - public function run(): Attempt - { - return match ($this->success) { - true => Attempt::result(SideEffect::identity()), - false => Attempt::error(new \Exception('Failed to reboot')), - }; - } -} diff --git a/src/Servers/Mock/Shutdown.php b/src/Servers/Mock/Shutdown.php deleted file mode 100644 index 502c2a5..0000000 --- a/src/Servers/Mock/Shutdown.php +++ /dev/null @@ -1,41 +0,0 @@ - - */ - public function run(): Attempt - { - return match ($this->success) { - true => Attempt::result(SideEffect::identity()), - false => Attempt::error(new \Exception('Failed to shutdown')), - }; - } -} diff --git a/src/Servers/Mock/UnmountVolume.php b/src/Servers/Mock/UnmountVolume.php deleted file mode 100644 index 83a4cdc..0000000 --- a/src/Servers/Mock/UnmountVolume.php +++ /dev/null @@ -1,47 +0,0 @@ - - */ - public function run( - Assert $assert, - string $name, - ): Attempt { - $assert->same($this->name, $name); - - return match ($this->success) { - true => Attempt::result(SideEffect::identity()), - false => Attempt::error(new \Exception('Failed to unmount volume')), - }; - } -} diff --git a/src/Servers/Mock/Volumes.php b/src/Servers/Mock/Volumes.php deleted file mode 100644 index 6dacb8f..0000000 --- a/src/Servers/Mock/Volumes.php +++ /dev/null @@ -1,54 +0,0 @@ -actions - ->pull(MountVolume::class, 'No volume mounting was expected') - ->run( - $this->assert, - $name->toString(), - $mountpoint->toString(), - ); - } - - #[\Override] - public function unmount(Name $name): Attempt - { - return $this - ->actions - ->pull(UnmountVolume::class, 'No volume unmounting was expected') - ->run( - $this->assert, - $name->toString(), - ); - } -} diff --git a/tests/Servers/MockTest.php b/tests/Servers/MockTest.php deleted file mode 100644 index 52de8d8..0000000 --- a/tests/Servers/MockTest.php +++ /dev/null @@ -1,711 +0,0 @@ -assertInstanceOf( - Server::class, - Mock::new($this->assert()), - ); - } - - #[Group('ci')] - #[Group('local')] - public function testWillReboot() - { - $mock = Mock::new($this->assert()) - ->willReboot(); - - $this->assertInstanceOf( - SideEffect::class, - $mock - ->reboot() - ->unwrap(), - ); - $this - ->assert() - ->not() - ->throws(static fn() => $mock->assert()); - } - - #[Group('ci')] - #[Group('local')] - public function testWillFailToReboot() - { - $mock = Mock::new($this->assert()) - ->willFailToReboot(); - - $this->assertFalse( - $mock - ->reboot() - ->match( - static fn() => true, - static fn() => false, - ), - ); - $this - ->assert() - ->not() - ->throws(static fn() => $mock->assert()); - } - - #[Group('ci')] - #[Group('local')] - public function testUnexpectedReboot() - { - $mock = Mock::new($this->assert()); - - try { - $mock->reboot(); - } catch (\Throwable $e) { - $this->assertInstanceOf(Failure::class, $e); - - return; - } - - $this->fail('It should throw'); - } - - #[Group('ci')] - #[Group('local')] - public function testUncalledReboot() - { - $mock = Mock::new($this->assert()) - ->willReboot(); - - try { - $mock->assert(); - } catch (\Throwable $e) { - $this->assertInstanceOf(Failure::class, $e); - - return; - } - - $this->fail('It should throw'); - } - - #[Group('ci')] - #[Group('local')] - public function testWillShutdown() - { - $mock = Mock::new($this->assert()) - ->willShutdown(); - - $this->assertInstanceOf( - SideEffect::class, - $mock - ->shutdown() - ->unwrap(), - ); - $this - ->assert() - ->not() - ->throws(static fn() => $mock->assert()); - } - - #[Group('ci')] - #[Group('local')] - public function testWillFailToShutdown() - { - $mock = Mock::new($this->assert()) - ->willFailToShutdown(); - - $this->assertFalse( - $mock - ->shutdown() - ->match( - static fn() => true, - static fn() => false, - ), - ); - $this - ->assert() - ->not() - ->throws(static fn() => $mock->assert()); - } - - #[Group('ci')] - #[Group('local')] - public function testUnexpectedShutdown() - { - $mock = Mock::new($this->assert()); - - try { - $mock->shutdown(); - } catch (\Throwable $e) { - $this->assertInstanceOf(Failure::class, $e); - - return; - } - - $this->fail('It should throw'); - } - - #[Group('ci')] - #[Group('local')] - public function testUncalledShutdown() - { - $mock = Mock::new($this->assert()) - ->willShutdown(); - - try { - $mock->assert(); - } catch (\Throwable $e) { - $this->assertInstanceOf(Failure::class, $e); - - return; - } - - $this->fail('It should throw'); - } - - #[Group('ci')] - #[Group('local')] - public function testWillMountVolume() - { - $mock = Mock::new($this->assert()) - ->willMountVolume('foo', '/bar'); - - $this->assertInstanceOf( - SideEffect::class, - $mock - ->volumes() - ->mount(Volumes\Name::of('foo'), Path::of('/bar')) - ->unwrap(), - ); - $this - ->assert() - ->not() - ->throws(static fn() => $mock->assert()); - } - - #[Group('ci')] - #[Group('local')] - public function testWillMountVolumeWithWrongName() - { - $mock = Mock::new($this->assert()) - ->willMountVolume('foo', '/bar'); - - try { - $mock - ->volumes() - ->mount(Volumes\Name::of('bar'), Path::of('/bar')); - } catch (\Throwable $e) { - $this->assertInstanceOf(Failure::class, $e); - - return; - } - - $this->fail('It should throw'); - } - - #[Group('ci')] - #[Group('local')] - public function testWillMountVolumeWithWrongPath() - { - $mock = Mock::new($this->assert()) - ->willMountVolume('foo', '/bar'); - - try { - $mock - ->volumes() - ->mount(Volumes\Name::of('foo'), Path::of('/foo')); - } catch (\Throwable $e) { - $this->assertInstanceOf(Failure::class, $e); - - return; - } - - $this->fail('It should throw'); - } - - #[Group('ci')] - #[Group('local')] - public function testWillFailToMountVolume() - { - $mock = Mock::new($this->assert()) - ->willFailToMountVolume('foo', '/bar'); - - $this->assertFalse( - $mock - ->volumes() - ->mount(Volumes\Name::of('foo'), Path::of('/bar')) - ->match( - static fn() => true, - static fn() => false, - ), - ); - $this - ->assert() - ->not() - ->throws(static fn() => $mock->assert()); - } - - #[Group('ci')] - #[Group('local')] - public function testWillFailToMountVolumeWithWrongName() - { - $mock = Mock::new($this->assert()) - ->willFailToMountVolume('foo', '/bar'); - - try { - $mock - ->volumes() - ->mount(Volumes\Name::of('bar'), Path::of('/bar')); - } catch (\Throwable $e) { - $this->assertInstanceOf(Failure::class, $e); - - return; - } - - $this->fail('It should throw'); - } - - #[Group('ci')] - #[Group('local')] - public function testWillFailToMountVolumeWithWrongPath() - { - $mock = Mock::new($this->assert()) - ->willFailToMountVolume('foo', '/bar'); - - try { - $mock - ->volumes() - ->mount(Volumes\Name::of('foo'), Path::of('/foo')); - } catch (\Throwable $e) { - $this->assertInstanceOf(Failure::class, $e); - - return; - } - - $this->fail('It should throw'); - } - - #[Group('ci')] - #[Group('local')] - public function testUnexpectedMountVolume() - { - $mock = Mock::new($this->assert()); - - try { - $mock - ->volumes() - ->mount(Volumes\Name::of('foo'), Path::of('/bar')); - } catch (\Throwable $e) { - $this->assertInstanceOf(Failure::class, $e); - - return; - } - - $this->fail('It should throw'); - } - - #[Group('ci')] - #[Group('local')] - public function testUncalledMountVolume() - { - $mock = Mock::new($this->assert()) - ->willMountVolume('foo', '/bar'); - - try { - $mock->assert(); - } catch (\Throwable $e) { - $this->assertInstanceOf(Failure::class, $e); - - return; - } - - $this->fail('It should throw'); - } - - #[Group('ci')] - #[Group('local')] - public function testWillUnmountVolume() - { - $mock = Mock::new($this->assert()) - ->willUnmountVolume('foo'); - - $this->assertInstanceOf( - SideEffect::class, - $mock - ->volumes() - ->unmount(Volumes\Name::of('foo')) - ->unwrap(), - ); - $this - ->assert() - ->not() - ->throws(static fn() => $mock->assert()); - } - - #[Group('ci')] - #[Group('local')] - public function testWillUnmountVolumeWithWrongName() - { - $mock = Mock::new($this->assert()) - ->willUnmountVolume('foo'); - - try { - $mock - ->volumes() - ->unmount(Volumes\Name::of('bar')); - } catch (\Throwable $e) { - $this->assertInstanceOf(Failure::class, $e); - - return; - } - - $this->fail('It should throw'); - } - - #[Group('ci')] - #[Group('local')] - public function testWillFailToUnmountVolume() - { - $mock = Mock::new($this->assert()) - ->willFailToUnmountVolume('foo'); - - $this->assertFalse( - $mock - ->volumes() - ->unmount(Volumes\Name::of('foo')) - ->match( - static fn() => true, - static fn() => false, - ), - ); - $this - ->assert() - ->not() - ->throws(static fn() => $mock->assert()); - } - - #[Group('ci')] - #[Group('local')] - public function testWillFailToUnmountVolumeWithWrongName() - { - $mock = Mock::new($this->assert()) - ->willFailToUnmountVolume('foo'); - - try { - $mock - ->volumes() - ->unmount(Volumes\Name::of('bar')); - } catch (\Throwable $e) { - $this->assertInstanceOf(Failure::class, $e); - - return; - } - - $this->fail('It should throw'); - } - - #[Group('ci')] - #[Group('local')] - public function testUnexpectedUnmountVolume() - { - $mock = Mock::new($this->assert()); - - try { - $mock - ->volumes() - ->unmount(Volumes\Name::of('foo')); - } catch (\Throwable $e) { - $this->assertInstanceOf(Failure::class, $e); - - return; - } - - $this->fail('It should throw'); - } - - #[Group('ci')] - #[Group('local')] - public function testUncalledUnmountVolume() - { - $mock = Mock::new($this->assert()) - ->willUnmountVolume('foo'); - - try { - $mock->assert(); - } catch (\Throwable $e) { - $this->assertInstanceOf(Failure::class, $e); - - return; - } - - $this->fail('It should throw'); - } - - #[Group('ci')] - #[Group('local')] - public function testProcessKillIsAlwaysSuccessful() - { - $mock = Mock::new($this->assert()); - - $this->assertInstanceOf( - SideEffect::class, - $mock - ->processes() - ->kill( - new Pid(2), - Signal::kill, - ) - ->unwrap(), - ); - } - - #[Group('ci')] - #[Group('local')] - public function testWillExecute() - { - $expected = Command::foreground('echo'); - - $mock = Mock::new($this->assert()) - ->willExecute( - fn($command) => $this->assertSame($expected, $command), - ); - - $process = $mock - ->processes() - ->execute($expected) - ->unwrap(); - - $this->assertSame(2, $process->pid()->match( - static fn($pid) => $pid->toInt(), - static fn() => null, - )); - $this->assertInstanceOf( - Success::class, - $process - ->wait() - ->match( - static fn($success) => $success, - static fn($error) => $error, - ), - ); - $this->assertSame(0, $process->output()->size()); - $this - ->assert() - ->not() - ->throws(static fn() => $mock->assert()); - } - - #[Group('ci')] - #[Group('local')] - public function testWillExecuteSuccess() - { - $expected = Command::foreground('echo'); - $output = Sequence::of(); - - $mock = Mock::new($this->assert()) - ->willExecute( - fn($command) => $this->assertSame($expected, $command), - static fn($_, $build) => $build->success($output), - ); - - $process = $mock - ->processes() - ->execute($expected) - ->unwrap(); - - $this->assertInstanceOf( - Success::class, - $process - ->wait() - ->match( - static fn($success) => $success, - static fn($error) => $error, - ), - ); - $this->assertSame($output, $process->output()); - $this - ->assert() - ->not() - ->throws(static fn() => $mock->assert()); - } - - #[Group('ci')] - #[Group('local')] - public function testWillExecuteSuccessWithCustomOutput() - { - $expected = Command::foreground('echo'); - - $mock = Mock::new($this->assert()) - ->willExecute( - fn($command) => $this->assertSame($expected, $command), - static fn($_, $build) => $build->success([ - ['foo', 'output'], - ['bar', 'error'], - ]), - ); - - $process = $mock - ->processes() - ->execute($expected) - ->unwrap(); - - $this->assertInstanceOf( - Success::class, - $process - ->wait() - ->match( - static fn($success) => $success, - static fn($error) => $error, - ), - ); - $this->assertSame( - 'foobar', - $process - ->output() - ->map(static fn($chunk) => $chunk->data()) - ->fold(new Concat) - ->toString(), - ); - $this->assertSame( - 'outputerror', - $process - ->output() - ->map(static fn($chunk) => $chunk->type()->name) - ->map(Str::of(...)) - ->fold(new Concat) - ->toString(), - ); - $this - ->assert() - ->not() - ->throws(static fn() => $mock->assert()); - } - - #[Group('ci')] - #[Group('local')] - public function testWillExecuteSignaled() - { - $expected = Command::foreground('echo'); - $output = Sequence::of(); - - $mock = Mock::new($this->assert()) - ->willExecute( - fn($command) => $this->assertSame($expected, $command), - static fn($_, $build) => $build->signaled($output), - ); - - $process = $mock - ->processes() - ->execute($expected) - ->unwrap(); - - $this->assertInstanceOf( - Signaled::class, - $process - ->wait() - ->match( - static fn($success) => $success, - static fn($error) => $error, - ), - ); - $this->assertSame($output, $process->output()); - $this - ->assert() - ->not() - ->throws(static fn() => $mock->assert()); - } - - #[Group('ci')] - #[Group('local')] - public function testWillExecuteTimedOut() - { - $expected = Command::foreground('echo'); - $output = Sequence::of(); - - $mock = Mock::new($this->assert()) - ->willExecute( - fn($command) => $this->assertSame($expected, $command), - static fn($_, $build) => $build->timedOut($output), - ); - - $process = $mock - ->processes() - ->execute($expected) - ->unwrap(); - - $this->assertInstanceOf( - TimedOut::class, - $process - ->wait() - ->match( - static fn($success) => $success, - static fn($error) => $error, - ), - ); - $this->assertSame($output, $process->output()); - $this - ->assert() - ->not() - ->throws(static fn() => $mock->assert()); - } - - #[Group('ci')] - #[Group('local')] - public function testWillExecuteFailed() - { - $expected = Command::foreground('echo'); - $output = Sequence::of(); - - $mock = Mock::new($this->assert()) - ->willExecute( - fn($command) => $this->assertSame($expected, $command), - static fn($_, $build) => $build->failed(1, $output), - ); - - $process = $mock - ->processes() - ->execute($expected) - ->unwrap(); - - $result = $process - ->wait() - ->match( - static fn($success) => $success, - static fn($error) => $error, - ); - $this->assertInstanceOf(Failed::class, $result); - $this->assertSame(1, $result->exitCode()->toInt()); - $this->assertSame($output, $process->output()); - $this - ->assert() - ->not() - ->throws(static fn() => $mock->assert()); - } -} From c6714a92a8b0a70e456db77aeb3f098e0e71d1f5 Mon Sep 17 00:00:00 2001 From: Baptiste Langlade Date: Sun, 16 Nov 2025 12:48:22 +0100 Subject: [PATCH 05/27] make Server a final class --- CHANGELOG.md | 1 + src/Server.php | 84 ++++++++++++++++++++++++++++++++-- src/ServerFactory.php | 7 +-- src/Servers/Implementation.php | 21 +++++++++ src/Servers/Logger.php | 24 +++------- src/Servers/Remote.php | 24 +++------- src/Servers/Unix.php | 20 ++------ 7 files changed, 119 insertions(+), 62 deletions(-) create mode 100644 src/Servers/Implementation.php diff --git a/CHANGELOG.md b/CHANGELOG.md index f212bb1..dcf3ed3 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -5,6 +5,7 @@ ### Changed - Requires PHP `8.4` +- `Innmind\Server\Control\Server` is now a final class, all previous implementations are now flagged as internal ## 6.1.0 - 2025-08-06 diff --git a/src/Server.php b/src/Server.php index 5cf699b..4d81c0d 100644 --- a/src/Server.php +++ b/src/Server.php @@ -4,31 +4,105 @@ namespace Innmind\Server\Control; use Innmind\Server\Control\{ + Servers\Implementation, + Servers\Unix, + Servers\Remote, + Servers\Logger, Server\Processes, Server\Volumes, + Server\Script, + Server\Command, +}; +use Innmind\TimeWarp\Halt; +use Innmind\IO\IO; +use Innmind\Url\Authority\{ + Host, + Port, + UserInformation\User, +}; +use Innmind\TimeContinuum\{ + Clock, + Period, }; use Innmind\Immutable\{ Attempt, SideEffect, }; +use Psr\Log\LoggerInterface; -interface Server +final class Server { + private function __construct( + private Implementation $implementation, + ) { + } + + /** + * @internal Use the factory instead + */ + public static function new( + Clock $clock, + IO $io, + Halt $halt, + ?Period $grace = null, + ): self { + return new self(Unix::of( + $clock, + $io, + $halt, + $grace, + )); + } + + public static function remote( + self $server, + User $user, + Host $host, + ?Port $port = null, + ): self { + return new self(Remote::of( + $server->implementation, + $user, + $host, + $port, + )); + } + + public static function logger(self $server, LoggerInterface $logger): self + { + return new self(Logger::psr( + $server->implementation, + $logger, + )); + } + #[\NoDiscard] - public function processes(): Processes; + public function processes(): Processes + { + return $this->implementation->processes(); + } #[\NoDiscard] - public function volumes(): Volumes; + public function volumes(): Volumes + { + return $this->implementation->volumes(); + } /** * @return Attempt */ #[\NoDiscard] - public function reboot(): Attempt; + public function reboot(): Attempt + { + return Script::of(Command::foreground('sudo shutdown -r now'))($this); + } /** * @return Attempt */ #[\NoDiscard] - public function shutdown(): Attempt; + public function shutdown(): Attempt + { + return Script::of(Command::foreground('sudo shutdown -h now'))($this); + } } diff --git a/src/ServerFactory.php b/src/ServerFactory.php index 6431c76..7955625 100644 --- a/src/ServerFactory.php +++ b/src/ServerFactory.php @@ -3,10 +3,7 @@ namespace Innmind\Server\Control; -use Innmind\Server\Control\{ - Servers\Unix, - Exception\UnsupportedOperatingSystem, -}; +use Innmind\Server\Control\Exception\UnsupportedOperatingSystem; use Innmind\TimeContinuum\{ Clock, Period, @@ -29,7 +26,7 @@ public static function build( switch (\PHP_OS) { case 'Darwin': case 'Linux': - return Unix::of($clock, $io, $halt, $grace); + return Server::new($clock, $io, $halt, $grace); } throw new UnsupportedOperatingSystem; diff --git a/src/Servers/Implementation.php b/src/Servers/Implementation.php new file mode 100644 index 0000000..443d9c6 --- /dev/null +++ b/src/Servers/Implementation.php @@ -0,0 +1,21 @@ +processes = Processes\Logger::psr( @@ -28,7 +28,7 @@ private function __construct( $this->volumes = new Volumes\Unix($this->processes); } - public static function psr(Server $server, LoggerInterface $logger): self + public static function psr(Implementation $server, LoggerInterface $logger): self { return new self($server, $logger); } @@ -44,16 +44,4 @@ public function volumes(): Volumes { return $this->volumes; } - - #[\Override] - public function reboot(): Attempt - { - return Server\Script::of(Command::foreground('sudo shutdown -r now'))($this); - } - - #[\Override] - public function shutdown(): Attempt - { - return Server\Script::of(Command::foreground('sudo shutdown -h now'))($this); - } } diff --git a/src/Servers/Remote.php b/src/Servers/Remote.php index f004a07..3a4f6c4 100644 --- a/src/Servers/Remote.php +++ b/src/Servers/Remote.php @@ -4,25 +4,25 @@ namespace Innmind\Server\Control\Servers; use Innmind\Server\Control\{ - Server, Server\Processes, Server\Volumes, - Server\Command, }; use Innmind\Url\Authority\{ Host, Port, UserInformation\User, }; -use Innmind\Immutable\Attempt; -final class Remote implements Server +/** + * @internal + */ +final class Remote implements Implementation { private Processes $processes; private Volumes $volumes; private function __construct( - Server $server, + Implementation $server, User $user, Host $host, ?Port $port = null, @@ -37,7 +37,7 @@ private function __construct( } public static function of( - Server $server, + Implementation $server, User $user, Host $host, ?Port $port = null, @@ -56,16 +56,4 @@ public function volumes(): Volumes { return $this->volumes; } - - #[\Override] - public function reboot(): Attempt - { - return Server\Script::of(Command::foreground('sudo shutdown -r now'))($this); - } - - #[\Override] - public function shutdown(): Attempt - { - return Server\Script::of(Command::foreground('sudo shutdown -h now'))($this); - } } diff --git a/src/Servers/Unix.php b/src/Servers/Unix.php index 973fdb3..fdf180e 100644 --- a/src/Servers/Unix.php +++ b/src/Servers/Unix.php @@ -4,10 +4,8 @@ namespace Innmind\Server\Control\Servers; use Innmind\Server\Control\{ - Server, Server\Processes, Server\Volumes, - Server\Command, }; use Innmind\TimeContinuum\{ Clock, @@ -15,9 +13,11 @@ }; use Innmind\TimeWarp\Halt; use Innmind\IO\IO; -use Innmind\Immutable\Attempt; -final class Unix implements Server +/** + * @internal + */ +final class Unix implements Implementation { private Processes $processes; private Volumes $volumes; @@ -60,16 +60,4 @@ public function volumes(): Volumes { return $this->volumes; } - - #[\Override] - public function reboot(): Attempt - { - return Server\Script::of(Command::foreground('sudo shutdown -r now'))($this); - } - - #[\Override] - public function shutdown(): Attempt - { - return Server\Script::of(Command::foreground('sudo shutdown -h now'))($this); - } } From bb4a5c4d0584c382d3d848b66c196c765614d06e Mon Sep 17 00:00:00 2001 From: Baptiste Langlade Date: Sun, 16 Nov 2025 12:54:30 +0100 Subject: [PATCH 06/27] make Volumes a final class --- CHANGELOG.md | 1 + src/Server.php | 2 +- src/Server/Volumes.php | 81 ++++++++++++++++++++++++++++-- src/Server/Volumes/Unix.php | 90 ---------------------------------- src/Servers/Implementation.php | 8 +-- src/Servers/Logger.php | 13 +---- src/Servers/Remote.php | 13 +---- src/Servers/Unix.php | 13 +---- 8 files changed, 83 insertions(+), 138 deletions(-) delete mode 100644 src/Server/Volumes/Unix.php diff --git a/CHANGELOG.md b/CHANGELOG.md index dcf3ed3..6185447 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -6,6 +6,7 @@ - Requires PHP `8.4` - `Innmind\Server\Control\Server` is now a final class, all previous implementations are now flagged as internal +- `Innmind\Server\Control\Server\Volumes` is now a final class ## 6.1.0 - 2025-08-06 diff --git a/src/Server.php b/src/Server.php index 4d81c0d..d62a07b 100644 --- a/src/Server.php +++ b/src/Server.php @@ -85,7 +85,7 @@ public function processes(): Processes #[\NoDiscard] public function volumes(): Volumes { - return $this->implementation->volumes(); + return Volumes::of($this->processes()); } /** diff --git a/src/Server/Volumes.php b/src/Server/Volumes.php index 557dbc9..8883c99 100644 --- a/src/Server/Volumes.php +++ b/src/Server/Volumes.php @@ -3,24 +3,97 @@ namespace Innmind\Server\Control\Server; -use Innmind\Server\Control\Server\Volumes\Name; +use Innmind\Server\Control\{ + Server\Volumes\Name, + Exception\ProcessFailed, +}; use Innmind\Url\Path; use Innmind\Immutable\{ Attempt, SideEffect, }; -interface Volumes +final class Volumes { + private function __construct( + private Processes $processes, + ) { + } + + /** + * @internal + */ + public static function of(Processes $processes): self + { + return new self($processes); + } + /** * @return Attempt */ #[\NoDiscard] - public function mount(Name $name, Path $mountpoint): Attempt; + public function mount(Name $name, Path $mountpoint): Attempt + { + if ($this->isOSX()) { + return $this->run( + Command::foreground('diskutil') + ->withArgument('mount') + ->withArgument($name->toString()), + ); + } + + return $this->run( + Command::foreground('mount') + ->withArgument($name->toString()) + ->withArgument($mountpoint->toString()), + ); + } /** * @return Attempt */ #[\NoDiscard] - public function unmount(Name $name): Attempt; + public function unmount(Name $name): Attempt + { + if ($this->isOSX()) { + return $this->run( + Command::foreground('diskutil') + ->withArgument('unmount') + ->withArgument($name->toString()), + ); + } + + return $this->run( + Command::foreground('umount') + ->withArgument($name->toString()), + ); + } + + /** + * @return Attempt + */ + private function run(Command $command): Attempt + { + return $this + ->processes + ->execute($command) + ->flatMap(static fn($process) => $process->wait()->match( + static fn() => Attempt::result(SideEffect::identity()), + static fn($e) => Attempt::error(new ProcessFailed( + $command, + $process, + $e, + )), + )); + } + + private function isOSX(): bool + { + return $this + ->run(Command::foreground('which diskutil')) + ->match( + static fn() => true, + static fn() => false, + ); + } } diff --git a/src/Server/Volumes/Unix.php b/src/Server/Volumes/Unix.php deleted file mode 100644 index b33d15f..0000000 --- a/src/Server/Volumes/Unix.php +++ /dev/null @@ -1,90 +0,0 @@ -isOSX()) { - return $this->run( - Command::foreground('diskutil') - ->withArgument('mount') - ->withArgument($name->toString()), - ); - } - - return $this->run( - Command::foreground('mount') - ->withArgument($name->toString()) - ->withArgument($mountpoint->toString()), - ); - } - - #[\Override] - public function unmount(Name $name): Attempt - { - if ($this->isOSX()) { - return $this->run( - Command::foreground('diskutil') - ->withArgument('unmount') - ->withArgument($name->toString()), - ); - } - - return $this->run( - Command::foreground('umount') - ->withArgument($name->toString()), - ); - } - - /** - * @return Attempt - */ - private function run(Command $command): Attempt - { - return $this - ->processes - ->execute($command) - ->flatMap(static fn($process) => $process->wait()->match( - static fn() => Attempt::result(SideEffect::identity()), - static fn($e) => Attempt::error(new ProcessFailed( - $command, - $process, - $e, - )), - )); - } - - private function isOSX(): bool - { - return $this - ->run(Command::foreground('which diskutil')) - ->match( - static fn() => true, - static fn() => false, - ); - } -} diff --git a/src/Servers/Implementation.php b/src/Servers/Implementation.php index 443d9c6..a73bb12 100644 --- a/src/Servers/Implementation.php +++ b/src/Servers/Implementation.php @@ -3,10 +3,7 @@ namespace Innmind\Server\Control\Servers; -use Innmind\Server\Control\Server\{ - Processes, - Volumes, -}; +use Innmind\Server\Control\Server\Processes; /** * @internal @@ -15,7 +12,4 @@ interface Implementation { #[\NoDiscard] public function processes(): Processes; - - #[\NoDiscard] - public function volumes(): Volumes; } diff --git a/src/Servers/Logger.php b/src/Servers/Logger.php index aa8c1e5..9f139ac 100644 --- a/src/Servers/Logger.php +++ b/src/Servers/Logger.php @@ -3,10 +3,7 @@ namespace Innmind\Server\Control\Servers; -use Innmind\Server\Control\{ - Server\Processes, - Server\Volumes, -}; +use Innmind\Server\Control\Server\Processes; use Psr\Log\LoggerInterface; /** @@ -15,7 +12,6 @@ final class Logger implements Implementation { private Processes $processes; - private Volumes $volumes; private function __construct( Implementation $server, @@ -25,7 +21,6 @@ private function __construct( $server->processes(), $logger, ); - $this->volumes = new Volumes\Unix($this->processes); } public static function psr(Implementation $server, LoggerInterface $logger): self @@ -38,10 +33,4 @@ public function processes(): Processes { return $this->processes; } - - #[\Override] - public function volumes(): Volumes - { - return $this->volumes; - } } diff --git a/src/Servers/Remote.php b/src/Servers/Remote.php index 3a4f6c4..3da2fbc 100644 --- a/src/Servers/Remote.php +++ b/src/Servers/Remote.php @@ -3,10 +3,7 @@ namespace Innmind\Server\Control\Servers; -use Innmind\Server\Control\{ - Server\Processes, - Server\Volumes, -}; +use Innmind\Server\Control\Server\Processes; use Innmind\Url\Authority\{ Host, Port, @@ -19,7 +16,6 @@ final class Remote implements Implementation { private Processes $processes; - private Volumes $volumes; private function __construct( Implementation $server, @@ -33,7 +29,6 @@ private function __construct( $host, $port, ); - $this->volumes = new Volumes\Unix($this->processes); } public static function of( @@ -50,10 +45,4 @@ public function processes(): Processes { return $this->processes; } - - #[\Override] - public function volumes(): Volumes - { - return $this->volumes; - } } diff --git a/src/Servers/Unix.php b/src/Servers/Unix.php index fdf180e..5993de6 100644 --- a/src/Servers/Unix.php +++ b/src/Servers/Unix.php @@ -3,10 +3,7 @@ namespace Innmind\Server\Control\Servers; -use Innmind\Server\Control\{ - Server\Processes, - Server\Volumes, -}; +use Innmind\Server\Control\Server\Processes; use Innmind\TimeContinuum\{ Clock, Period, @@ -20,7 +17,6 @@ final class Unix implements Implementation { private Processes $processes; - private Volumes $volumes; private function __construct( Clock $clock, @@ -34,7 +30,6 @@ private function __construct( $halt, $grace, ); - $this->volumes = new Volumes\Unix($this->processes); } /** @@ -54,10 +49,4 @@ public function processes(): Processes { return $this->processes; } - - #[\Override] - public function volumes(): Volumes - { - return $this->volumes; - } } From 0d5cc127a990016ec2079a03e2a263a6534567ea Mon Sep 17 00:00:00 2001 From: Baptiste Langlade Date: Sun, 16 Nov 2025 13:32:51 +0100 Subject: [PATCH 07/27] make Processes a final class --- CHANGELOG.md | 1 + src/Run/Implementation.php | 21 ++++++++ src/Run/Logger.php | 44 ++++++++++++++++ src/{Server/Processes => Run}/Remote.php | 61 ++++++++++----------- src/{Server/Processes => Run}/Unix.php | 67 ++++++++---------------- src/Server.php | 18 +++---- src/Server/Processes.php | 44 ++++++++++++++-- src/Server/Processes/Logger.php | 52 ------------------ src/Servers/Implementation.php | 15 ------ src/Servers/Logger.php | 36 ------------- src/Servers/Remote.php | 48 ----------------- src/Servers/Unix.php | 52 ------------------ 12 files changed, 164 insertions(+), 295 deletions(-) create mode 100644 src/Run/Implementation.php create mode 100644 src/Run/Logger.php rename src/{Server/Processes => Run}/Remote.php (55%) rename src/{Server/Processes => Run}/Unix.php (55%) delete mode 100644 src/Server/Processes/Logger.php delete mode 100644 src/Servers/Implementation.php delete mode 100644 src/Servers/Logger.php delete mode 100644 src/Servers/Remote.php delete mode 100644 src/Servers/Unix.php diff --git a/CHANGELOG.md b/CHANGELOG.md index 6185447..d7a81b9 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -7,6 +7,7 @@ - Requires PHP `8.4` - `Innmind\Server\Control\Server` is now a final class, all previous implementations are now flagged as internal - `Innmind\Server\Control\Server\Volumes` is now a final class +- `Innmind\Server\Control\Server\Processes` is now a final class ## 6.1.0 - 2025-08-06 diff --git a/src/Run/Implementation.php b/src/Run/Implementation.php new file mode 100644 index 0000000..2c28ee5 --- /dev/null +++ b/src/Run/Implementation.php @@ -0,0 +1,21 @@ + + */ + public function __invoke(Command $command): Attempt; +} diff --git a/src/Run/Logger.php b/src/Run/Logger.php new file mode 100644 index 0000000..c013446 --- /dev/null +++ b/src/Run/Logger.php @@ -0,0 +1,44 @@ +logger->info('About to execute the {command}', [ + 'command' => $command->toString(), + 'workingDirectory' => $command->workingDirectory()->match( + static fn($path) => $path->toString(), + static fn() => null, + ), + ]); + + return ($this->run)($command); + } + + /** + * @internal + */ + public static function psr(Implementation $run, LoggerInterface $logger): self + { + return new self($run, $logger); + } +} diff --git a/src/Server/Processes/Remote.php b/src/Run/Remote.php similarity index 55% rename from src/Server/Processes/Remote.php rename to src/Run/Remote.php index ca09acc..1b65f07 100644 --- a/src/Server/Processes/Remote.php +++ b/src/Run/Remote.php @@ -1,39 +1,33 @@ processes = $processes; + $this->run = $run; $command = Command::foreground('ssh'); if ($port instanceof Port) { @@ -48,7 +42,7 @@ public function __construct( } #[\Override] - public function execute(Command $command): Attempt + public function __invoke(Command $command): Attempt { /** @psalm-suppress ArgumentTypeCoercion Due psalm not understing that $bash cannot be empty */ $command = $command @@ -63,22 +57,25 @@ public function execute(Command $command): Attempt static fn() => $command, ); - return $this - ->processes - ->execute( - $this->command->withArgument($command->toString()), - ); + return ($this->run)( + $this->command->withArgument($command->toString()), + ); } - #[\Override] - public function kill(Pid $pid, Signal $signal): Attempt - { - return $this - ->execute( - $command = Command::foreground('kill') - ->withShortOption($signal->toString()) - ->withArgument($pid->toString()), - ) - ->map(static fn() => SideEffect::identity()); + /** + * @internal + */ + public static function of( + Implementation $run, + User $user, + Host $host, + ?Port $port = null, + ): self { + return new self( + $run, + $user, + $host, + $port, + ); } } diff --git a/src/Server/Processes/Unix.php b/src/Run/Unix.php similarity index 55% rename from src/Server/Processes/Unix.php rename to src/Run/Unix.php index 986f7b6..223f59d 100644 --- a/src/Server/Processes/Unix.php +++ b/src/Run/Unix.php @@ -1,15 +1,11 @@ execute( - $command = Command::foreground('kill') - ->withShortOption($signal->toString()) - ->withArgument($pid->toString()), - ) - ->flatMap(static fn($process) => $process->wait()->match( - static fn() => Attempt::result(SideEffect::identity()), - static fn($e) => Attempt::error(new ProcessFailed( - $command, - $process, - $e, - )), - )); + /** + * @internal + */ + public static function of( + Clock $clock, + IO $io, + Halt $halt, + ?Period $grace = null, + ): self { + return new self( + $clock, + $io, + $halt, + $grace ?? Period::second(1), + ); } } diff --git a/src/Server.php b/src/Server.php index d62a07b..6d9f582 100644 --- a/src/Server.php +++ b/src/Server.php @@ -4,10 +4,6 @@ namespace Innmind\Server\Control; use Innmind\Server\Control\{ - Servers\Implementation, - Servers\Unix, - Servers\Remote, - Servers\Logger, Server\Processes, Server\Volumes, Server\Script, @@ -33,7 +29,7 @@ final class Server { private function __construct( - private Implementation $implementation, + private Run\Implementation $run, ) { } @@ -46,7 +42,7 @@ public static function new( Halt $halt, ?Period $grace = null, ): self { - return new self(Unix::of( + return new self(Run\Unix::of( $clock, $io, $halt, @@ -60,8 +56,8 @@ public static function remote( Host $host, ?Port $port = null, ): self { - return new self(Remote::of( - $server->implementation, + return new self(Run\Remote::of( + $server->run, $user, $host, $port, @@ -70,8 +66,8 @@ public static function remote( public static function logger(self $server, LoggerInterface $logger): self { - return new self(Logger::psr( - $server->implementation, + return new self(Run\Logger::psr( + $server->run, $logger, )); } @@ -79,7 +75,7 @@ public static function logger(self $server, LoggerInterface $logger): self #[\NoDiscard] public function processes(): Processes { - return $this->implementation->processes(); + return Processes::of($this->run); } #[\NoDiscard] diff --git a/src/Server/Processes.php b/src/Server/Processes.php index cb7611d..dc924c6 100644 --- a/src/Server/Processes.php +++ b/src/Server/Processes.php @@ -3,23 +3,59 @@ namespace Innmind\Server\Control\Server; -use Innmind\Server\Control\Server\Process\Pid; +use Innmind\Server\Control\{ + Server\Process\Pid, + Run, + Exception\ProcessFailed, +}; use Innmind\Immutable\{ Attempt, SideEffect, }; -interface Processes +final class Processes { + private function __construct( + private Run\Implementation $run, + ) { + } + + /** + * @internal + */ + public static function of(Run\Implementation $run): self + { + return new self($run); + } + /** * @return Attempt */ #[\NoDiscard] - public function execute(Command $command): Attempt; + public function execute(Command $command): Attempt + { + return ($this->run)($command); + } /** * @return Attempt */ #[\NoDiscard] - public function kill(Pid $pid, Signal $signal): Attempt; + public function kill(Pid $pid, Signal $signal): Attempt + { + return $this + ->execute( + $command = Command::foreground('kill') + ->withShortOption($signal->toString()) + ->withArgument($pid->toString()), + ) + ->flatMap(static fn($process) => $process->wait()->match( + static fn() => Attempt::result(SideEffect::identity()), + static fn($e) => Attempt::error(new ProcessFailed( + $command, + $process, + $e, + )), + )); + } } diff --git a/src/Server/Processes/Logger.php b/src/Server/Processes/Logger.php deleted file mode 100644 index 259315a..0000000 --- a/src/Server/Processes/Logger.php +++ /dev/null @@ -1,52 +0,0 @@ -logger->info('About to execute a command', [ - 'command' => $command->toString(), - 'workingDirectory' => $command->workingDirectory()->match( - static fn($path) => $path->toString(), - static fn() => null, - ), - ]); - - return $this->processes->execute($command); - } - - #[\Override] - public function kill(Pid $pid, Signal $signal): Attempt - { - $this->logger->info('About to kill a process', [ - 'pid' => $pid->toInt(), - 'signal' => $signal->toInt(), - ]); - - return $this->processes->kill($pid, $signal); - } -} diff --git a/src/Servers/Implementation.php b/src/Servers/Implementation.php deleted file mode 100644 index a73bb12..0000000 --- a/src/Servers/Implementation.php +++ /dev/null @@ -1,15 +0,0 @@ -processes = Processes\Logger::psr( - $server->processes(), - $logger, - ); - } - - public static function psr(Implementation $server, LoggerInterface $logger): self - { - return new self($server, $logger); - } - - #[\Override] - public function processes(): Processes - { - return $this->processes; - } -} diff --git a/src/Servers/Remote.php b/src/Servers/Remote.php deleted file mode 100644 index 3da2fbc..0000000 --- a/src/Servers/Remote.php +++ /dev/null @@ -1,48 +0,0 @@ -processes = new Processes\Remote( - $server->processes(), - $user, - $host, - $port, - ); - } - - public static function of( - Implementation $server, - User $user, - Host $host, - ?Port $port = null, - ): self { - return new self($server, $user, $host, $port); - } - - #[\Override] - public function processes(): Processes - { - return $this->processes; - } -} diff --git a/src/Servers/Unix.php b/src/Servers/Unix.php deleted file mode 100644 index 5993de6..0000000 --- a/src/Servers/Unix.php +++ /dev/null @@ -1,52 +0,0 @@ -processes = Processes\Unix::of( - $clock, - $io, - $halt, - $grace, - ); - } - - /** - * @internal Use the factory instead - */ - public static function of( - Clock $clock, - IO $io, - Halt $halt, - ?Period $grace = null, - ): self { - return new self($clock, $io, $halt, $grace); - } - - #[\Override] - public function processes(): Processes - { - return $this->processes; - } -} From 59cc564f5b18e57ce60b23d5f354ee5028c40f48 Mon Sep 17 00:00:00 2001 From: Baptiste Langlade Date: Sun, 16 Nov 2025 13:45:21 +0100 Subject: [PATCH 08/27] add Server::via() --- src/Run/Via.php | 40 ++++++++++++++++++++++++++++++++++++++++ src/Server.php | 11 +++++++++++ 2 files changed, 51 insertions(+) create mode 100644 src/Run/Via.php diff --git a/src/Run/Via.php b/src/Run/Via.php new file mode 100644 index 0000000..7744078 --- /dev/null +++ b/src/Run/Via.php @@ -0,0 +1,40 @@ + $run + */ + private function __construct( + private \Closure $run, + ) { + } + + #[\Override] + public function __invoke(Command $command): Attempt + { + return ($this->run)($command); + } + + /** + * @internal + * + * @param callable(Command): Attempt $run + */ + public static function of(callable $run): self + { + return new self(\Closure::fromCallable($run)); + } +} diff --git a/src/Server.php b/src/Server.php index 6d9f582..d05a3b9 100644 --- a/src/Server.php +++ b/src/Server.php @@ -6,6 +6,7 @@ use Innmind\Server\Control\{ Server\Processes, Server\Volumes, + Server\Process, Server\Script, Server\Command, }; @@ -72,6 +73,16 @@ public static function logger(self $server, LoggerInterface $logger): self )); } + /** + * @internal + * + * @param callable(Command): Attempt $run + */ + public static function via(callable $run): self + { + return new self(Run\Via::of($run)); + } + #[\NoDiscard] public function processes(): Processes { From 70eab1422d747ad07bd981665864dcadd48f42ca Mon Sep 17 00:00:00 2001 From: Baptiste Langlade Date: Sun, 16 Nov 2025 14:02:30 +0100 Subject: [PATCH 09/27] fix tests --- tests/Server/Processes/LoggerTest.php | 49 +++----- tests/Server/Processes/RemoteTest.php | 96 ++++++---------- tests/Server/Processes/UnixTest.php | 54 ++++----- tests/Server/ScriptTest.php | 6 +- .../{Volumes/UnixTest.php => VolumesTest.php} | 77 ++++--------- tests/Servers/LoggerTest.php | 101 ++++------------- tests/Servers/RemoteTest.php | 106 ++++-------------- tests/Servers/UnixTest.php | 16 +-- 8 files changed, 142 insertions(+), 363 deletions(-) rename tests/Server/{Volumes/UnixTest.php => VolumesTest.php} (78%) diff --git a/tests/Server/Processes/LoggerTest.php b/tests/Server/Processes/LoggerTest.php index ddf78c2..1af7727 100644 --- a/tests/Server/Processes/LoggerTest.php +++ b/tests/Server/Processes/LoggerTest.php @@ -3,14 +3,12 @@ namespace Tests\Innmind\Server\Control\Server\Processes; -use Innmind\Server\Control\Server\{ - Processes\Logger, - Processes\Unix, - Processes, - Process, - Command, - Signal, - Process\Pid +use Innmind\Server\Control\{ + Server, + Server\Process, + Server\Command, + Server\Signal, + Server\Process\Pid, }; use Innmind\TimeContinuum\Clock; use Innmind\TimeWarp\Halt; @@ -22,27 +20,14 @@ class LoggerTest extends TestCase { - #[Group('ci')] - #[Group('local')] - public function testInterface() - { - $this->assertInstanceOf( - Processes::class, - Logger::psr( - $this->processes(), - new NullLogger, - ), - ); - } - #[Group('ci')] #[Group('local')] public function testExecute() { - $logger = Logger::psr( - $this->processes(), + $logger = Server::logger( + $this->server(), new NullLogger, - ); + )->processes(); $this->assertInstanceOf( Process::class, @@ -56,10 +41,10 @@ public function testExecute() #[Group('local')] public function testExecuteWithWorkingDirectory() { - $logger = Logger::psr( - $this->processes(), + $logger = Server::logger( + $this->server(), new NullLogger, - ); + )->processes(); $this->assertInstanceOf( Process::class, @@ -75,17 +60,17 @@ public function testExecuteWithWorkingDirectory() #[Group('local')] public function testKill() { - $logger = Logger::psr( - $this->processes(), + $logger = Server::logger( + $this->server(), new NullLogger, - ); + )->processes(); $this->assertNotNull($logger->kill(new Pid(42), Signal::kill)); } - private function processes(): Unix + private function server(): Server { - return Unix::of( + return Server::new( Clock::live(), IO::fromAmbientAuthority(), Halt::new(), diff --git a/tests/Server/Processes/RemoteTest.php b/tests/Server/Processes/RemoteTest.php index e2c2246..39b101e 100644 --- a/tests/Server/Processes/RemoteTest.php +++ b/tests/Server/Processes/RemoteTest.php @@ -3,13 +3,12 @@ namespace Tests\Innmind\Server\Control\Server\Processes; -use Innmind\Server\Control\Server\{ - Processes\Remote, - Processes, - Process, - Command, - Signal, - Process\Pid, +use Innmind\Server\Control\{ + Server, + Server\Process, + Server\Command, + Server\Signal, + Server\Process\Pid, }; use Innmind\TimeContinuum\Clock; use Innmind\TimeWarp\Halt; @@ -20,38 +19,21 @@ Authority\Port, Authority\UserInformation\User }; -use Innmind\Immutable\{ - Attempt, - SideEffect, -}; +use Innmind\Immutable\SideEffect; use Innmind\BlackBox\PHPUnit\Framework\TestCase; use PHPUnit\Framework\Attributes\Group; class RemoteTest extends TestCase { - #[Group('ci')] - #[Group('local')] - public function testInterface() - { - $this->assertInstanceOf( - Processes::class, - new Remote( - $this->processes(), - User::none(), - Host::of('example.com'), - ), - ); - } - #[Group('ci')] #[Group('local')] public function testExecute() { - $remote = new Remote( - $this->processes("ssh 'foo@example.com' 'ls '\''-l'\'''"), + $remote = Server::remote( + $this->server("ssh 'foo@example.com' 'ls '\''-l'\'''"), User::of('foo'), Host::of('example.com'), - ); + )->processes(); $this->assertInstanceOf( Process::class, @@ -65,12 +47,12 @@ public function testExecute() #[Group('local')] public function testExecuteViaSpecificPort() { - $remote = new Remote( - $this->processes("ssh '-p' '24' 'foo@example.com' 'ls '\''-l'\'''"), + $remote = Server::remote( + $this->server("ssh '-p' '24' 'foo@example.com' 'ls '\''-l'\'''"), User::of('foo'), Host::of('example.com'), Port::of(24), - ); + )->processes(); $this->assertInstanceOf( Process::class, @@ -84,11 +66,11 @@ public function testExecuteViaSpecificPort() #[Group('local')] public function testExecuteWithWorkingDirectory() { - $remote = new Remote( - $this->processes("ssh 'foo@example.com' 'cd /tmp/foo && ls '\''-l'\'''"), + $remote = Server::remote( + $this->server("ssh 'foo@example.com' 'cd /tmp/foo && ls '\''-l'\'''"), User::of('foo'), Host::of('example.com'), - ); + )->processes(); $this->assertInstanceOf( Process::class, @@ -104,11 +86,11 @@ public function testExecuteWithWorkingDirectory() #[Group('local')] public function testKill() { - $remote = new Remote( - $this->processes("ssh 'foo@example.com' 'kill '\''-9'\'' '\''42'\'''"), + $remote = Server::remote( + $this->server("ssh 'foo@example.com' 'kill '\''-9'\'' '\''42'\'''"), User::of('foo'), Host::of('example.com'), - ); + )->processes(); $this->assertInstanceOf( SideEffect::class, @@ -123,12 +105,12 @@ public function testKill() #[Group('local')] public function testKillViaSpecificPort() { - $remote = new Remote( - $this->processes("ssh '-p' '24' 'foo@example.com' 'kill '\''-9'\'' '\''42'\'''"), + $remote = Server::remote( + $this->server("ssh '-p' '24' 'foo@example.com' 'kill '\''-9'\'' '\''42'\'''"), User::of('foo'), Host::of('example.com'), Port::of(24), - ); + )->processes(); $this->assertInstanceOf( SideEffect::class, @@ -139,37 +121,25 @@ public function testKillViaSpecificPort() ); } - private function processes(string ...$commands): Processes + private function server(string ...$commands): Server { - $processes = Processes\Unix::of( + $processes = Server::new( Clock::live(), IO::fromAmbientAuthority(), Halt::new(), - ); - - return new class($processes, $this, $commands) implements Processes { - public function __construct( - private $processes, - private $test, - private $commands, - ) { - } + )->processes(); - public function execute(Command $command): Attempt - { - $expected = \array_shift($this->commands); - $this->test->assertNotNull($expected); - $this->test->assertSame( + return Server::via( + function($command) use ($processes, &$commands) { + $expected = \array_shift($commands); + $this->assertNotNull($expected); + $this->assertSame( $expected, $command->toString(), ); - return $this->processes->execute(Command::foreground('echo')); - } - - public function kill(Pid $pid, Signal $signal): Attempt - { - } - }; + return $processes->execute(Command::foreground('echo')); + }, + ); } } diff --git a/tests/Server/Processes/UnixTest.php b/tests/Server/Processes/UnixTest.php index 631701d..bc44dbc 100644 --- a/tests/Server/Processes/UnixTest.php +++ b/tests/Server/Processes/UnixTest.php @@ -4,8 +4,7 @@ namespace Tests\Innmind\Server\Control\Server\Processes; use Innmind\Server\Control\{ - Server\Processes\Unix, - Server\Processes, + Server, Server\Command, Server\Process, Server\Process\TimedOut, @@ -27,26 +26,15 @@ class UnixTest extends TestCase { - #[Group('ci')] - #[Group('local')] - public function testInterface() - { - $this->assertInstanceOf(Processes::class, Unix::of( - Clock::live(), - IO::fromAmbientAuthority(), - Halt::new(), - )); - } - #[Group('ci')] #[Group('local')] public function testExecute() { - $processes = Unix::of( + $processes = Server::new( Clock::live(), IO::fromAmbientAuthority(), Halt::new(), - ); + )->processes(); $start = \time(); $process = $processes->execute( Command::foreground('php') @@ -63,11 +51,11 @@ public function testExecute() #[Group('local')] public function testExecuteInBackground() { - $processes = Unix::of( + $processes = Server::new( Clock::live(), IO::fromAmbientAuthority(), Halt::new(), - ); + )->processes(); $start = \time(); $process = $processes->execute( Command::background('php') @@ -85,11 +73,11 @@ public function testExecuteInBackground() #[Group('local')] public function testExecuteWithInput() { - $processes = Unix::of( + $processes = Server::new( Clock::live(), IO::fromAmbientAuthority(), Halt::new(), - ); + )->processes(); $process = $processes->execute( Command::foreground('cat')->withInput(Content::oneShot( IO::fromAmbientAuthority() @@ -117,11 +105,11 @@ public function testKill() // That's why it's never run in the CI // todo investigate more why this is happening only for linux - $processes = Unix::of( + $processes = Server::new( Clock::live(), IO::fromAmbientAuthority(), Halt::new(), - ); + )->processes(); $start = \time(); $process = $processes->execute( Command::foreground('php') @@ -150,11 +138,11 @@ public function testKill() #[Group('local')] public function testTimeout() { - $processes = Unix::of( + $processes = Server::new( Clock::live(), IO::fromAmbientAuthority(), Halt::new(), - ); + )->processes(); $start = \time(); $process = $processes->execute( Command::foreground('sleep') @@ -178,11 +166,11 @@ public function testTimeout() public function testStreamOutput() { $called = false; - $processes = Unix::of( + $processes = Server::new( Clock::live(), IO::fromAmbientAuthority(), Halt::new(), - ); + )->processes(); $processes ->execute( Command::foreground('cat') @@ -203,11 +191,11 @@ public function testStreamOutput() public function testSecondCallToStreamedOutputThrowsAnError() { $called = false; - $processes = Unix::of( + $processes = Server::new( Clock::live(), IO::fromAmbientAuthority(), Halt::new(), - ); + )->processes(); $process = $processes ->execute( Command::foreground('cat') @@ -227,11 +215,11 @@ public function testSecondCallToStreamedOutputThrowsAnError() public function testOutputIsNotLostByDefault() { $called = false; - $processes = Unix::of( + $processes = Server::new( Clock::live(), IO::fromAmbientAuthority(), Halt::new(), - ); + )->processes(); $process = $processes ->execute( Command::foreground('cat') @@ -254,11 +242,11 @@ public function testStopProcessEvenWhenPipesAreStillOpenAfterTheProcessBeingKill { @\unlink('/tmp/test-file'); \touch('/tmp/test-file'); - $processes = Unix::of( + $processes = Server::new( Clock::live(), IO::fromAmbientAuthority(), Halt::new(), - ); + )->processes(); $tail = $processes->execute( Command::foreground('tail') ->withShortOption('f') @@ -281,11 +269,11 @@ public function testStopProcessEvenWhenPipesAreStillOpenAfterTheProcessBeingKill #[Group('local')] public function testRegressionWhenProcessFinishesTooFastItsFlaggedAsFailingEvenThoughItSucceeded() { - $processes = Unix::of( + $processes = Server::new( Clock::live(), IO::fromAmbientAuthority(), Halt::new(), - ); + )->processes(); $this->assertTrue( $processes diff --git a/tests/Server/ScriptTest.php b/tests/Server/ScriptTest.php index 61288fe..483c308 100644 --- a/tests/Server/ScriptTest.php +++ b/tests/Server/ScriptTest.php @@ -4,9 +4,9 @@ namespace Tests\Innmind\Server\Control\Server; use Innmind\Server\Control\{ + Server, Server\Script, Server\Command, - Servers\Unix, Exception\ProcessFailed, }; use Innmind\TimeContinuum\{ @@ -74,9 +74,9 @@ public function testFailDueToTimeout() $this->assertInstanceOf(ProcessFailed::class, $e); } - private function server(): Unix + private function server(): Server { - return Unix::of( + return Server::new( Clock::live(), IO::fromAmbientAuthority(), Halt::new(), diff --git a/tests/Server/Volumes/UnixTest.php b/tests/Server/VolumesTest.php similarity index 78% rename from tests/Server/Volumes/UnixTest.php rename to tests/Server/VolumesTest.php index af82af1..9d02769 100644 --- a/tests/Server/Volumes/UnixTest.php +++ b/tests/Server/VolumesTest.php @@ -1,15 +1,13 @@ assertInstanceOf( - Volumes::class, - new Unix( - $this->processes(), - ), - ); - } - #[Group('ci')] #[Group('local')] public function testMountOSXVolume() { - $volumes = new Unix( + $volumes = Volumes::of( $this->processes( ['which diskutil', true], ["diskutil 'mount' '/dev/disk1s2'", true], @@ -65,7 +48,7 @@ public function testMountOSXVolume() #[Group('local')] public function testThrowWhenFailToMountOSXVolume() { - $volumes = new Unix( + $volumes = Volumes::of( $this->processes( ['which diskutil', true], ["diskutil 'mount' '/dev/disk1s2'", false], @@ -88,7 +71,7 @@ public function testThrowWhenFailToMountOSXVolume() #[Group('local')] public function testUnmountOSXVolume() { - $volumes = new Unix( + $volumes = Volumes::of( $this->processes( ['which diskutil', true], ["diskutil 'unmount' '/dev/disk1s2'", true], @@ -108,7 +91,7 @@ public function testUnmountOSXVolume() #[Group('local')] public function testReturnErrorWhenFailToUnmountOSXVolume() { - $volumes = new Unix( + $volumes = Volumes::of( $this->processes( ['which diskutil', true], ["diskutil 'unmount' '/dev/disk1s2'", false], @@ -128,7 +111,7 @@ public function testReturnErrorWhenFailToUnmountOSXVolume() #[Group('local')] public function testMountLinuxVolume() { - $volumes = new Unix( + $volumes = Volumes::of( $this->processes( ['which diskutil', false], ["mount '/dev/disk1s2' '/somewhere'", true], @@ -151,7 +134,7 @@ public function testMountLinuxVolume() #[Group('local')] public function testReturnErrorWhenFailToMountLinuxVolume() { - $volumes = new Unix( + $volumes = Volumes::of( $this->processes( ['which diskutil', false], ["mount '/dev/disk1s2' '/somewhere'", false], @@ -174,7 +157,7 @@ public function testReturnErrorWhenFailToMountLinuxVolume() #[Group('local')] public function testUnmountLinuxVolume() { - $volumes = new Unix( + $volumes = Volumes::of( $this->processes( ['which diskutil', false], ["umount '/dev/disk1s2'", true], @@ -194,7 +177,7 @@ public function testUnmountLinuxVolume() #[Group('local')] public function testReturnErrorWhenFailToUnmountLinuxVolume() { - $volumes = new Unix( + $volumes = Volumes::of( $this->processes( ['which diskutil', false], ["umount '/dev/disk1s2'", false], @@ -212,39 +195,27 @@ public function testReturnErrorWhenFailToUnmountLinuxVolume() private function processes(array ...$commands): Processes { - $processes = Processes\Unix::of( + $processes = Server::new( Clock::live(), IO::fromAmbientAuthority(), Halt::new(), - ); + )->processes(); - return new class($processes, $this, $commands) implements Processes { - public function __construct( - private $processes, - private $test, - private $commands, - ) { - } - - public function execute(Command $command): Attempt - { - $expected = \array_shift($this->commands); - $this->test->assertNotNull($expected); + return Server::via( + function($command) use ($processes, &$commands) { + $expected = \array_shift($commands); + $this->assertNotNull($expected); [$expected, $success] = $expected; - $this->test->assertSame( + $this->assertSame( $expected, $command->toString(), ); - return $this->processes->execute(Command::foreground(match ($success) { + return $processes->execute(Command::foreground(match ($success) { true => 'echo', false => 'unknown', })); - } - - public function kill(Pid $pid, Signal $signal): Attempt - { - } - }; + }, + )->processes(); } } diff --git a/tests/Servers/LoggerTest.php b/tests/Servers/LoggerTest.php index 582b03b..4d2aab5 100644 --- a/tests/Servers/LoggerTest.php +++ b/tests/Servers/LoggerTest.php @@ -4,54 +4,34 @@ namespace Tests\Innmind\Server\Control\Servers; use Innmind\Server\Control\{ - Servers\Logger, Server, Server\Processes, - Server\Processes\Unix, - Server\Process\Pid, Server\Command, Server\Volumes, - Server\Signal, }; use Innmind\TimeContinuum\Clock; use Innmind\TimeWarp\Halt; use Innmind\IO\IO; -use Innmind\Immutable\{ - Attempt, - SideEffect, -}; +use Innmind\Immutable\SideEffect; use Psr\Log\NullLogger; use Innmind\BlackBox\PHPUnit\Framework\TestCase; use PHPUnit\Framework\Attributes\Group; class LoggerTest extends TestCase { - #[Group('ci')] - #[Group('local')] - public function testInterface() - { - $this->assertInstanceOf( - Server::class, - Logger::psr( - $this->server(), - new NullLogger, - ), - ); - } - #[Group('ci')] #[Group('local')] public function testProcesses() { $server = $this->server('ls'); - $logger = Logger::psr( + $logger = Server::logger( $server, new NullLogger, ); $this->assertInstanceOf( - Processes\Logger::class, + Processes::class, $logger->processes(), ); $logger->processes()->execute(Command::foreground('ls')); @@ -63,7 +43,7 @@ public function testVolumes() { $server = $this->server('which diskutil', "diskutil 'unmount' '/dev'"); - $logger = Logger::psr( + $logger = Server::logger( $server, new NullLogger, ); @@ -81,7 +61,7 @@ public function testReboot() { $server = $this->server('sudo shutdown -r now'); - $logger = Logger::psr( + $logger = Server::logger( $server, new NullLogger, ); @@ -101,7 +81,7 @@ public function testShutdown() { $server = $this->server('sudo shutdown -h now'); - $logger = Logger::psr( + $logger = Server::logger( $server, new NullLogger, ); @@ -117,64 +97,23 @@ public function testShutdown() private function server(string ...$commands): Server { - return new class($this->processes(), $this, $commands) implements Server { - private $inner; - - public function __construct( - private $processes, - private $test, - private $commands, - ) { - } - - public function processes(): Processes - { - return $this->inner ??= new class($this->processes, $this->test, $this->commands) implements Processes { - public function __construct( - private $processes, - private $test, - private $commands, - ) { - } - - public function execute(Command $command): Attempt - { - $expected = \array_shift($this->commands); - $this->test->assertNotNull($expected); - $this->test->assertSame( - $expected, - $command->toString(), - ); - - return $this->processes->execute(Command::foreground('echo')); - } - - public function kill(Pid $pid, Signal $signal): Attempt - { - } - }; - } - - public function volumes(): Volumes - { - } - - public function reboot(): Attempt - { - } - - public function shutdown(): Attempt - { - } - }; - } - - private function processes(): Unix - { - return Unix::of( + $processes = Server::new( Clock::live(), IO::fromAmbientAuthority(), Halt::new(), + )->processes(); + + return Server::via( + function($command) use ($processes, &$commands) { + $expected = \array_shift($commands); + $this->assertNotNull($expected); + $this->assertSame( + $expected, + $command->toString(), + ); + + return $processes->execute(Command::foreground('echo')); + }, ); } } diff --git a/tests/Servers/RemoteTest.php b/tests/Servers/RemoteTest.php index 3e7961a..22cac5b 100644 --- a/tests/Servers/RemoteTest.php +++ b/tests/Servers/RemoteTest.php @@ -4,14 +4,10 @@ namespace Tests\Innmind\Server\Control\Servers; use Innmind\Server\Control\{ - Servers\Remote, Server, Server\Processes, - Server\Processes\Unix, - Server\Process\Pid, Server\Command, Server\Volumes, - Server\Signal, }; use Innmind\TimeContinuum\Clock; use Innmind\TimeWarp\Halt; @@ -21,43 +17,26 @@ Port, UserInformation\User }; -use Innmind\Immutable\{ - Attempt, - SideEffect, -}; +use Innmind\Immutable\SideEffect; use Innmind\BlackBox\PHPUnit\Framework\TestCase; use PHPUnit\Framework\Attributes\Group; class RemoteTest extends TestCase { - #[Group('ci')] - #[Group('local')] - public function testInterface() - { - $this->assertInstanceOf( - Server::class, - Remote::of( - $this->server(), - User::none(), - Host::none(), - ), - ); - } - #[Group('ci')] #[Group('local')] public function testProcesses() { $server = $this->server("ssh 'foo@example.com' 'ls'"); - $remote = Remote::of( + $remote = Server::remote( $server, User::of('foo'), Host::of('example.com'), ); $this->assertInstanceOf( - Processes\Remote::class, + Processes::class, $remote->processes(), ); $remote->processes()->execute(Command::foreground('ls')); @@ -69,7 +48,7 @@ public function testProcessesViaSpecificPort() { $server = $this->server("ssh '-p' '42' 'foo@example.com' 'ls'"); - $remote = Remote::of( + $remote = Server::remote( $server, User::of('foo'), Host::of('example.com'), @@ -77,7 +56,7 @@ public function testProcessesViaSpecificPort() ); $this->assertInstanceOf( - Processes\Remote::class, + Processes::class, $remote->processes(), ); $remote->processes()->execute(Command::foreground('ls')); @@ -92,7 +71,7 @@ public function testVolumes() "ssh 'foo@example.com' 'diskutil '\''unmount'\'' '\''/dev'\'''", ); - $remote = Remote::of( + $remote = Server::remote( $server, User::of('foo'), Host::of('example.com'), @@ -111,7 +90,7 @@ public function testReboot() { $server = $this->server("ssh 'foo@example.com' 'sudo shutdown -r now'"); - $remote = Remote::of( + $remote = Server::remote( $server, User::of('foo'), Host::of('example.com'), @@ -132,7 +111,7 @@ public function testShutdown() { $server = $this->server("ssh 'foo@example.com' 'sudo shutdown -h now'"); - $remote = Remote::of( + $remote = Server::remote( $server, User::of('foo'), Host::of('example.com'), @@ -149,64 +128,23 @@ public function testShutdown() private function server(string ...$commands): Server { - return new class($this->processes(), $this, $commands) implements Server { - private $inner; - - public function __construct( - private $processes, - private $test, - private $commands, - ) { - } - - public function processes(): Processes - { - return $this->inner ??= new class($this->processes, $this->test, $this->commands) implements Processes { - public function __construct( - private $processes, - private $test, - private $commands, - ) { - } - - public function execute(Command $command): Attempt - { - $expected = \array_shift($this->commands); - $this->test->assertNotNull($expected); - $this->test->assertSame( - $expected, - $command->toString(), - ); - - return $this->processes->execute(Command::foreground('echo')); - } - - public function kill(Pid $pid, Signal $signal): Attempt - { - } - }; - } - - public function volumes(): Volumes - { - } - - public function reboot(): Attempt - { - } - - public function shutdown(): Attempt - { - } - }; - } - - private function processes(): Unix - { - return Unix::of( + $processes = Server::new( Clock::live(), IO::fromAmbientAuthority(), Halt::new(), + )->processes(); + + return Server::via( + function($command) use ($processes, &$commands) { + $expected = \array_shift($commands); + $this->assertNotNull($expected); + $this->assertSame( + $expected, + $command->toString(), + ); + + return $processes->execute(Command::foreground('echo')); + }, ); } } diff --git a/tests/Servers/UnixTest.php b/tests/Servers/UnixTest.php index 6ee2078..232f7dd 100644 --- a/tests/Servers/UnixTest.php +++ b/tests/Servers/UnixTest.php @@ -4,7 +4,6 @@ namespace Tests\Innmind\Server\Control\Servers; use Innmind\Server\Control\{ - Servers\Unix, Server, Server\Processes, Server\Volumes, @@ -17,22 +16,11 @@ class UnixTest extends TestCase { - #[Group('ci')] - #[Group('local')] - public function testInterface() - { - $this->assertInstanceOf(Server::class, Unix::of( - Clock::live(), - IO::fromAmbientAuthority(), - Halt::new(), - )); - } - #[Group('ci')] #[Group('local')] public function testProcesses() { - $this->assertInstanceOf(Processes::class, Unix::of( + $this->assertInstanceOf(Processes::class, Server::new( Clock::live(), IO::fromAmbientAuthority(), Halt::new(), @@ -43,7 +31,7 @@ public function testProcesses() #[Group('local')] public function testVolumes() { - $this->assertInstanceOf(Volumes::class, Unix::of( + $this->assertInstanceOf(Volumes::class, Server::new( Clock::live(), IO::fromAmbientAuthority(), Halt::new(), From f359b5c05e754f6ef30746ab1af7e2a8b45a4ac9 Mon Sep 17 00:00:00 2001 From: Baptiste Langlade Date: Sun, 16 Nov 2025 14:04:54 +0100 Subject: [PATCH 10/27] let the caller choose the process pid --- src/Server/Process/Mock.php | 20 ++++++++------------ 1 file changed, 8 insertions(+), 12 deletions(-) diff --git a/src/Server/Process/Mock.php b/src/Server/Process/Mock.php index c62d2c4..3b61235 100644 --- a/src/Server/Process/Mock.php +++ b/src/Server/Process/Mock.php @@ -15,17 +15,13 @@ */ final class Mock { - /** @var int<2, max> */ - private static int $processes = 2; - - /** @var int<2, max> */ - private int $pid; - private Success|Failed|Signaled|TimedOut $status; - - public function __construct(Success|Failed|Signaled|TimedOut $status) - { - $this->pid = self::$processes++; - $this->status = $status; + /** + * @param ?int<2, max> $pid + */ + public function __construct( + private ?int $pid, + private Success|Failed|Signaled|TimedOut $status, + ) { } /** @@ -33,7 +29,7 @@ public function __construct(Success|Failed|Signaled|TimedOut $status) */ public function pid(): Maybe { - return Maybe::just(new Pid($this->pid)); + return Maybe::of($this->pid)->map(static fn($pid) => new Pid($pid)); } /** From 4ffb7a4b95092e744fe1a9afed9bd8f9577adf47 Mon Sep 17 00:00:00 2001 From: Baptiste Langlade Date: Tue, 2 Dec 2025 14:22:11 +0100 Subject: [PATCH 11/27] add Command::overSSH() --- CHANGELOG.md | 4 ++++ src/Run/Remote.php | 42 +++++++++--------------------------------- src/Server/Command.php | 37 ++++++++++++++++++++++++++++++++++++- 3 files changed, 49 insertions(+), 34 deletions(-) diff --git a/CHANGELOG.md b/CHANGELOG.md index d7a81b9..03f282c 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -2,6 +2,10 @@ ## [Unreleased] +### Added + +- `Innmind\Server\Control\Server\Command::overSsh()` + ### Changed - Requires PHP `8.4` diff --git a/src/Run/Remote.php b/src/Run/Remote.php index 1b65f07..68ced98 100644 --- a/src/Run/Remote.php +++ b/src/Run/Remote.php @@ -18,47 +18,23 @@ */ final class Remote implements Implementation { - private Implementation $run; - private Command $command; - private function __construct( - Implementation $run, - User $user, - Host $host, - ?Port $port = null, + private Implementation $run, + private User $user, + private Host $host, + private ?Port $port = null, ) { - $this->run = $run; - $command = Command::foreground('ssh'); - - if ($port instanceof Port) { - $command = $command->withShortOption('p', $port->toString()); - } - - $this->command = $command->withArgument(\sprintf( - '%s@%s', - $user->toString(), - $host->toString(), - )); } #[\Override] public function __invoke(Command $command): Attempt { - /** @psalm-suppress ArgumentTypeCoercion Due psalm not understing that $bash cannot be empty */ - $command = $command - ->workingDirectory() - ->map(static fn($path) => \sprintf( - 'cd %s && %s', - $path->toString(), - $command->toString(), - )) - ->match( - static fn($bash) => Command::foreground($bash), - static fn() => $command, - ); - return ($this->run)( - $this->command->withArgument($command->toString()), + $command->overSsh( + $this->user, + $this->host, + $this->port, + ), ); } diff --git a/src/Server/Command.php b/src/Server/Command.php index 40513c6..ffeae03 100644 --- a/src/Server/Command.php +++ b/src/Server/Command.php @@ -12,7 +12,12 @@ }; use Innmind\TimeContinuum\Period; use Innmind\Filesystem\File\Content; -use Innmind\Url\Path; +use Innmind\Url\{ + Path, + Authority\Host, + Authority\Port, + Authority\UserInformation\User, +}; use Innmind\Immutable\{ Sequence, Map, @@ -235,6 +240,36 @@ public function streamOutput(): self return $self; } + #[\NoDiscard] + public function overSsh(User $user, Host $host, ?Port $port = null): self + { + $ssh = self::foreground('ssh'); + + if ($port instanceof Port) { + $ssh = $ssh->withShortOption('p', $port->toString()); + } + + $ssh = $ssh->withArgument(\sprintf( + '%s@%s', + $user->toString(), + $host->toString(), + )); + + $self = $this + ->workingDirectory() + ->map(fn($path) => \sprintf( + 'cd %s && %s', + $path->toString(), + $this->toString(), + )) + ->match( + static fn($bash) => self::foreground($bash), + fn() => $this, + ); + + return $ssh->withArgument($self->toString()); + } + /** * @internal * From a4f9fbcd7fb738bcd164df1adb506fb84ec78d78 Mon Sep 17 00:00:00 2001 From: Baptiste Langlade Date: Thu, 4 Dec 2025 13:11:00 +0100 Subject: [PATCH 12/27] Revert "add Command::overSSH()" This reverts commit 4ffb7a4b95092e744fe1a9afed9bd8f9577adf47. --- CHANGELOG.md | 4 ---- src/Run/Remote.php | 42 +++++++++++++++++++++++++++++++++--------- src/Server/Command.php | 37 +------------------------------------ 3 files changed, 34 insertions(+), 49 deletions(-) diff --git a/CHANGELOG.md b/CHANGELOG.md index 03f282c..d7a81b9 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -2,10 +2,6 @@ ## [Unreleased] -### Added - -- `Innmind\Server\Control\Server\Command::overSsh()` - ### Changed - Requires PHP `8.4` diff --git a/src/Run/Remote.php b/src/Run/Remote.php index 68ced98..1b65f07 100644 --- a/src/Run/Remote.php +++ b/src/Run/Remote.php @@ -18,23 +18,47 @@ */ final class Remote implements Implementation { + private Implementation $run; + private Command $command; + private function __construct( - private Implementation $run, - private User $user, - private Host $host, - private ?Port $port = null, + Implementation $run, + User $user, + Host $host, + ?Port $port = null, ) { + $this->run = $run; + $command = Command::foreground('ssh'); + + if ($port instanceof Port) { + $command = $command->withShortOption('p', $port->toString()); + } + + $this->command = $command->withArgument(\sprintf( + '%s@%s', + $user->toString(), + $host->toString(), + )); } #[\Override] public function __invoke(Command $command): Attempt { + /** @psalm-suppress ArgumentTypeCoercion Due psalm not understing that $bash cannot be empty */ + $command = $command + ->workingDirectory() + ->map(static fn($path) => \sprintf( + 'cd %s && %s', + $path->toString(), + $command->toString(), + )) + ->match( + static fn($bash) => Command::foreground($bash), + static fn() => $command, + ); + return ($this->run)( - $command->overSsh( - $this->user, - $this->host, - $this->port, - ), + $this->command->withArgument($command->toString()), ); } diff --git a/src/Server/Command.php b/src/Server/Command.php index ffeae03..40513c6 100644 --- a/src/Server/Command.php +++ b/src/Server/Command.php @@ -12,12 +12,7 @@ }; use Innmind\TimeContinuum\Period; use Innmind\Filesystem\File\Content; -use Innmind\Url\{ - Path, - Authority\Host, - Authority\Port, - Authority\UserInformation\User, -}; +use Innmind\Url\Path; use Innmind\Immutable\{ Sequence, Map, @@ -240,36 +235,6 @@ public function streamOutput(): self return $self; } - #[\NoDiscard] - public function overSsh(User $user, Host $host, ?Port $port = null): self - { - $ssh = self::foreground('ssh'); - - if ($port instanceof Port) { - $ssh = $ssh->withShortOption('p', $port->toString()); - } - - $ssh = $ssh->withArgument(\sprintf( - '%s@%s', - $user->toString(), - $host->toString(), - )); - - $self = $this - ->workingDirectory() - ->map(fn($path) => \sprintf( - 'cd %s && %s', - $path->toString(), - $this->toString(), - )) - ->match( - static fn($bash) => self::foreground($bash), - fn() => $this, - ); - - return $ssh->withArgument($self->toString()); - } - /** * @internal * From 6200debe5ea87eae89eee81c36be0478eb8592f8 Mon Sep 17 00:00:00 2001 From: Baptiste Langlade Date: Thu, 4 Dec 2025 13:33:29 +0100 Subject: [PATCH 13/27] extract ssh commands into its own class to let runners inspect them --- src/Run/Implementation.php | 2 +- src/Run/Logger.php | 12 +++- src/Run/Remote.php | 51 ++++----------- src/Run/Unix.php | 6 +- src/Run/Via.php | 6 +- src/Server.php | 2 +- src/Server/Command/OverSsh.php | 109 +++++++++++++++++++++++++++++++++ 7 files changed, 140 insertions(+), 48 deletions(-) create mode 100644 src/Server/Command/OverSsh.php diff --git a/src/Run/Implementation.php b/src/Run/Implementation.php index 2c28ee5..5d05e1d 100644 --- a/src/Run/Implementation.php +++ b/src/Run/Implementation.php @@ -17,5 +17,5 @@ interface Implementation /** * @return Attempt */ - public function __invoke(Command $command): Attempt; + public function __invoke(Command|Command\OverSsh $command): Attempt; } diff --git a/src/Run/Logger.php b/src/Run/Logger.php index c013446..1ea098a 100644 --- a/src/Run/Logger.php +++ b/src/Run/Logger.php @@ -21,11 +21,17 @@ private function __construct( } #[\Override] - public function __invoke(Command $command): Attempt + public function __invoke(Command|Command\OverSsh $command): Attempt { + $toLog = $command; + + if ($toLog instanceof Command\OverSsh) { + $toLog = $toLog->normalize(); + } + $this->logger->info('About to execute the {command}', [ - 'command' => $command->toString(), - 'workingDirectory' => $command->workingDirectory()->match( + 'command' => $toLog->toString(), + 'workingDirectory' => $toLog->workingDirectory()->match( static fn($path) => $path->toString(), static fn() => null, ), diff --git a/src/Run/Remote.php b/src/Run/Remote.php index 1b65f07..7488556 100644 --- a/src/Run/Remote.php +++ b/src/Run/Remote.php @@ -3,9 +3,7 @@ namespace Innmind\Server\Control\Run; -use Innmind\Server\Control\{ - Server\Command, -}; +use Innmind\Server\Control\Server\Command; use Innmind\Url\Authority\{ Host, Port, @@ -18,48 +16,23 @@ */ final class Remote implements Implementation { - private Implementation $run; - private Command $command; - private function __construct( - Implementation $run, - User $user, - Host $host, - ?Port $port = null, + private Implementation $run, + private User $user, + private Host $host, + private ?Port $port, ) { - $this->run = $run; - $command = Command::foreground('ssh'); - - if ($port instanceof Port) { - $command = $command->withShortOption('p', $port->toString()); - } - - $this->command = $command->withArgument(\sprintf( - '%s@%s', - $user->toString(), - $host->toString(), - )); } #[\Override] - public function __invoke(Command $command): Attempt + public function __invoke(Command|Command\OverSsh $command): Attempt { - /** @psalm-suppress ArgumentTypeCoercion Due psalm not understing that $bash cannot be empty */ - $command = $command - ->workingDirectory() - ->map(static fn($path) => \sprintf( - 'cd %s && %s', - $path->toString(), - $command->toString(), - )) - ->match( - static fn($bash) => Command::foreground($bash), - static fn() => $command, - ); - - return ($this->run)( - $this->command->withArgument($command->toString()), - ); + return ($this->run)(Command\OverSsh::of( + $this->user, + $this->host, + $this->port, + $command, + )); } /** diff --git a/src/Run/Unix.php b/src/Run/Unix.php index 223f59d..107507f 100644 --- a/src/Run/Unix.php +++ b/src/Run/Unix.php @@ -29,8 +29,12 @@ private function __construct( } #[\Override] - public function __invoke(Command $command): Attempt + public function __invoke(Command|Command\OverSsh $command): Attempt { + if ($command instanceof Command\OverSsh) { + $command = $command->normalize(); + } + return Attempt::of(function() use ($command) { $process = new Process\Unix( $this->clock, diff --git a/src/Run/Via.php b/src/Run/Via.php index 7744078..5323b3f 100644 --- a/src/Run/Via.php +++ b/src/Run/Via.php @@ -15,7 +15,7 @@ final class Via implements Implementation { /** - * @param \Closure(Command): Attempt $run + * @param \Closure(Command|Command\OverSsh): Attempt $run */ private function __construct( private \Closure $run, @@ -23,7 +23,7 @@ private function __construct( } #[\Override] - public function __invoke(Command $command): Attempt + public function __invoke(Command|Command\OverSsh $command): Attempt { return ($this->run)($command); } @@ -31,7 +31,7 @@ public function __invoke(Command $command): Attempt /** * @internal * - * @param callable(Command): Attempt $run + * @param callable(Command|Command\OverSsh): Attempt $run */ public static function of(callable $run): self { diff --git a/src/Server.php b/src/Server.php index d05a3b9..fa189cc 100644 --- a/src/Server.php +++ b/src/Server.php @@ -76,7 +76,7 @@ public static function logger(self $server, LoggerInterface $logger): self /** * @internal * - * @param callable(Command): Attempt $run + * @param callable(Command|Command\OverSsh): Attempt $run */ public static function via(callable $run): self { diff --git a/src/Server/Command/OverSsh.php b/src/Server/Command/OverSsh.php new file mode 100644 index 0000000..0166b28 --- /dev/null +++ b/src/Server/Command/OverSsh.php @@ -0,0 +1,109 @@ +user; + } + + #[\NoDiscard] + public function host(): Host + { + return $this->host; + } + + #[\NoDiscard] + public function port(): ?Port + { + return $this->port; + } + + #[\NoDiscard] + public function command(): Command|self + { + return $this->command; + } + + #[\NoDiscard] + public function normalize(): Command + { + $command = $this->command; + + if ($command instanceof self) { + $command = $command->normalize(); + } + + $ssh = Command::foreground('ssh'); + + if ($this->port instanceof Port) { + $ssh = $ssh->withShortOption('p', $this->port->toString()); + } + + $ssh = $ssh->withArgument(\sprintf( + '%s@%s', + $this->user->toString(), + $this->host->toString(), + )); + + $self = $command + ->workingDirectory() + ->map(static fn($path) => \sprintf( + 'cd %s && %s', + $path->toString(), + $command->toString(), + )) + ->match( + static fn($bash) => Command::foreground($bash), + static fn() => $command, + ); + + return $ssh->withArgument($self->toString()); + } + + /** + * @return non-empty-string + */ + #[\NoDiscard] + public function toString(): string + { + return $this->normalize()->toString(); + } +} From 02087ca3ddf85fa2e829cc582b6d2d187fcc7e3b Mon Sep 17 00:00:00 2001 From: Baptiste Langlade Date: Thu, 4 Dec 2025 15:20:54 +0100 Subject: [PATCH 14/27] avoid using clone --- src/Server/Command.php | 291 ++++++++++++++++++++++++++++------------- 1 file changed, 197 insertions(+), 94 deletions(-) diff --git a/src/Server/Command.php b/src/Server/Command.php index 40513c6..db19661 100644 --- a/src/Server/Command.php +++ b/src/Server/Command.php @@ -25,42 +25,26 @@ */ final class Command { - /** @var non-empty-string */ - private string $executable; - /** @var Sequence */ - private Sequence $parameters; - /** @var Map */ - private Map $environment; - /** @var Maybe */ - private Maybe $workingDirectory; - /** @var Maybe */ - private Maybe $input; - /** @var Maybe|Maybe */ - private Maybe $redirection; - private bool $background = false; - /** @var Maybe */ - private Maybe $timeout; - private bool $streamOutput = false; - /** * @param non-empty-string $executable + * @param Sequence $parameters + * @param Map $environment + * @param Maybe $workingDirectory + * @param Maybe $input + * @param Maybe|Maybe $redirection + * @param Maybe $timeout */ - private function __construct(bool $background, string $executable) - { - $this->executable = $executable; - $this->background = $background; - /** @var Sequence */ - $this->parameters = Sequence::of(); - /** @var Map */ - $this->environment = Map::of(); - /** @var Maybe */ - $this->workingDirectory = Maybe::nothing(); - /** @var Maybe */ - $this->input = Maybe::nothing(); - /** @var Maybe|Maybe */ - $this->redirection = Maybe::nothing(); - /** @var Maybe */ - $this->timeout = Maybe::nothing(); + private function __construct( + private bool $background, + private string $executable, + private Sequence $parameters, + private Map $environment, + private Maybe $workingDirectory, + private Maybe $input, + private Maybe $redirection, + private Maybe $timeout, + private bool $streamOutput, + ) { } /** @@ -77,7 +61,26 @@ private function __construct(bool $background, string $executable) #[\NoDiscard] public static function background(string $executable): self { - return new self(true, $executable); + /** @var Maybe */ + $workingDirectory = Maybe::nothing(); + /** @var Maybe */ + $input = Maybe::nothing(); + /** @var Maybe|Maybe */ + $redirection = Maybe::nothing(); + /** @var Maybe */ + $timeout = Maybe::nothing(); + + return new self( + true, + $executable, + Sequence::of(), + Map::of(), + $workingDirectory, + $input, + $redirection, + $timeout, + false, + ); } /** @@ -91,16 +94,42 @@ public static function background(string $executable): self #[\NoDiscard] public static function foreground(string $executable): self { - return new self(false, $executable); + /** @var Maybe */ + $workingDirectory = Maybe::nothing(); + /** @var Maybe */ + $input = Maybe::nothing(); + /** @var Maybe|Maybe */ + $redirection = Maybe::nothing(); + /** @var Maybe */ + $timeout = Maybe::nothing(); + + return new self( + false, + $executable, + Sequence::of(), + Map::of(), + $workingDirectory, + $input, + $redirection, + $timeout, + false, + ); } #[\NoDiscard] public function withArgument(string $value): self { - $self = clone $this; - $self->parameters = ($this->parameters)(new Argument($value)); - - return $self; + return new self( + $this->background, + $this->executable, + ($this->parameters)(new Argument($value)), + $this->environment, + $this->workingDirectory, + $this->input, + $this->redirection, + $this->timeout, + $this->streamOutput, + ); } /** @@ -109,10 +138,17 @@ public function withArgument(string $value): self #[\NoDiscard] public function withOption(string $key, ?string $value = null): self { - $self = clone $this; - $self->parameters = ($this->parameters)(Option::long($key, $value)); - - return $self; + return new self( + $this->background, + $this->executable, + ($this->parameters)(Option::long($key, $value)), + $this->environment, + $this->workingDirectory, + $this->input, + $this->redirection, + $this->timeout, + $this->streamOutput, + ); } /** @@ -121,10 +157,17 @@ public function withOption(string $key, ?string $value = null): self #[\NoDiscard] public function withShortOption(string $key, ?string $value = null): self { - $self = clone $this; - $self->parameters = ($this->parameters)(Option::short($key, $value)); - - return $self; + return new self( + $this->background, + $this->executable, + ($this->parameters)(Option::short($key, $value)), + $this->environment, + $this->workingDirectory, + $this->input, + $this->redirection, + $this->timeout, + $this->streamOutput, + ); } /** @@ -133,10 +176,17 @@ public function withShortOption(string $key, ?string $value = null): self #[\NoDiscard] public function withEnvironment(string $key, string $value): self { - $self = clone $this; - $self->environment = ($this->environment)($key, $value); - - return $self; + return new self( + $this->background, + $this->executable, + $this->parameters, + ($this->environment)($key, $value), + $this->workingDirectory, + $this->input, + $this->redirection, + $this->timeout, + $this->streamOutput, + ); } /** @@ -145,75 +195,121 @@ public function withEnvironment(string $key, string $value): self #[\NoDiscard] public function withEnvironments(Map $values): self { - $self = clone $this; - $self->environment = $this->environment->merge($values); - - return $self; + return new self( + $this->background, + $this->executable, + $this->parameters, + $this->environment->merge($values), + $this->workingDirectory, + $this->input, + $this->redirection, + $this->timeout, + $this->streamOutput, + ); } #[\NoDiscard] public function withWorkingDirectory(Path $path): self { - $self = clone $this; - $self->workingDirectory = Maybe::just($path); - - return $self; + return new self( + $this->background, + $this->executable, + $this->parameters, + $this->environment, + Maybe::just($path), + $this->input, + $this->redirection, + $this->timeout, + $this->streamOutput, + ); } #[\NoDiscard] public function withInput(Content $input): self { - $self = clone $this; - $self->input = Maybe::just($input); - - return $self; + return new self( + $this->background, + $this->executable, + $this->parameters, + $this->environment, + $this->workingDirectory, + Maybe::just($input), + $this->redirection, + $this->timeout, + $this->streamOutput, + ); } #[\NoDiscard] public function overwrite(Path $path): self { - $self = clone $this; - $self->redirection = Maybe::just(new Overwrite($path)); - - return $self; + return new self( + $this->background, + $this->executable, + $this->parameters, + $this->environment, + $this->workingDirectory, + $this->input, + Maybe::just(new Overwrite($path)), + $this->timeout, + $this->streamOutput, + ); } #[\NoDiscard] public function append(Path $path): self { - $self = clone $this; - $self->redirection = Maybe::just(new Append($path)); - - return $self; + return new self( + $this->background, + $this->executable, + $this->parameters, + $this->environment, + $this->workingDirectory, + $this->input, + Maybe::just(new Append($path)), + $this->timeout, + $this->streamOutput, + ); } #[\NoDiscard] public function pipe(self $command): self { - $self = clone $this; - - $self->parameters = $this - ->redirection - ->match( - fn($redirection) => ($this->parameters)($redirection), - fn() => $this->parameters, - ) - ->add(new Pipe) - ->add(new Argument($command->executable)) - ->append($command->parameters); - $self->environment = $this->environment->merge($command->environment); - $self->redirection = $command->redirection; - - return $self; + return new self( + $this->background, + $this->executable, + $this + ->redirection + ->match( + $this->parameters, + fn() => $this->parameters, + ) + ->add(new Pipe) + ->add(new Argument($command->executable)) + ->append($command->parameters), + $this->environment->merge($command->environment), + $this->workingDirectory, + $this->input, + $command->redirection, + $this->timeout, + $this->streamOutput, + ); } #[\NoDiscard] public function timeoutAfter(Period $timeout): self { - $self = clone $this; - $self->timeout = Maybe::just($timeout); - - return $self; + return new self( + $this->background, + $this->executable, + $this->parameters, + $this->environment, + $this->workingDirectory, + $this->input, + $this->redirection, + Maybe::just($timeout), + $this->streamOutput, + ); } /** @@ -229,10 +325,17 @@ public function timeoutAfter(Period $timeout): self #[\NoDiscard] public function streamOutput(): self { - $self = clone $this; - $self->streamOutput = true; - - return $self; + return new self( + $this->background, + $this->executable, + $this->parameters, + $this->environment, + $this->workingDirectory, + $this->input, + $this->redirection, + $this->timeout, + true, + ); } /** From c6e05a31bdb5153ad248c717ac07f1fda07d9ebb Mon Sep 17 00:00:00 2001 From: Baptiste Langlade Date: Thu, 4 Dec 2025 16:24:41 +0100 Subject: [PATCH 15/27] decouple commands definitions from piping them --- src/Server/Command.php | 243 +++---------------- src/Server/Command/Definition.php | 326 ++++++++++++++++++++++++++ src/Server/Command/Implementation.php | 86 +++++++ src/Server/Command/Pipe.php | 168 ++++++++++++- tests/Server/Command/PipeTest.php | 18 -- tests/Server/CommandTest.php | 2 +- 6 files changed, 614 insertions(+), 229 deletions(-) create mode 100644 src/Server/Command/Definition.php create mode 100644 src/Server/Command/Implementation.php delete mode 100644 tests/Server/Command/PipeTest.php diff --git a/src/Server/Command.php b/src/Server/Command.php index db19661..6b50b2c 100644 --- a/src/Server/Command.php +++ b/src/Server/Command.php @@ -3,20 +3,16 @@ namespace Innmind\Server\Control\Server; -use Innmind\Server\Control\{ - Server\Command\Argument, - Server\Command\Option, - Server\Command\Overwrite, - Server\Command\Append, - Server\Command\Pipe, +use Innmind\Server\Control\Server\Command\{ + Implementation, + Definition, + Pipe, }; use Innmind\TimeContinuum\Period; use Innmind\Filesystem\File\Content; use Innmind\Url\Path; use Innmind\Immutable\{ - Sequence, Map, - Str, Maybe, }; @@ -25,25 +21,8 @@ */ final class Command { - /** - * @param non-empty-string $executable - * @param Sequence $parameters - * @param Map $environment - * @param Maybe $workingDirectory - * @param Maybe $input - * @param Maybe|Maybe $redirection - * @param Maybe $timeout - */ private function __construct( - private bool $background, - private string $executable, - private Sequence $parameters, - private Map $environment, - private Maybe $workingDirectory, - private Maybe $input, - private Maybe $redirection, - private Maybe $timeout, - private bool $streamOutput, + private Implementation $implementation, ) { } @@ -61,26 +40,7 @@ private function __construct( #[\NoDiscard] public static function background(string $executable): self { - /** @var Maybe */ - $workingDirectory = Maybe::nothing(); - /** @var Maybe */ - $input = Maybe::nothing(); - /** @var Maybe|Maybe */ - $redirection = Maybe::nothing(); - /** @var Maybe */ - $timeout = Maybe::nothing(); - - return new self( - true, - $executable, - Sequence::of(), - Map::of(), - $workingDirectory, - $input, - $redirection, - $timeout, - false, - ); + return new self(Definition::background($executable)); } /** @@ -94,41 +54,14 @@ public static function background(string $executable): self #[\NoDiscard] public static function foreground(string $executable): self { - /** @var Maybe */ - $workingDirectory = Maybe::nothing(); - /** @var Maybe */ - $input = Maybe::nothing(); - /** @var Maybe|Maybe */ - $redirection = Maybe::nothing(); - /** @var Maybe */ - $timeout = Maybe::nothing(); - - return new self( - false, - $executable, - Sequence::of(), - Map::of(), - $workingDirectory, - $input, - $redirection, - $timeout, - false, - ); + return new self(Definition::foreground($executable)); } #[\NoDiscard] public function withArgument(string $value): self { return new self( - $this->background, - $this->executable, - ($this->parameters)(new Argument($value)), - $this->environment, - $this->workingDirectory, - $this->input, - $this->redirection, - $this->timeout, - $this->streamOutput, + $this->implementation->withArgument($value), ); } @@ -139,15 +72,7 @@ public function withArgument(string $value): self public function withOption(string $key, ?string $value = null): self { return new self( - $this->background, - $this->executable, - ($this->parameters)(Option::long($key, $value)), - $this->environment, - $this->workingDirectory, - $this->input, - $this->redirection, - $this->timeout, - $this->streamOutput, + $this->implementation->withOption($key, $value), ); } @@ -158,15 +83,7 @@ public function withOption(string $key, ?string $value = null): self public function withShortOption(string $key, ?string $value = null): self { return new self( - $this->background, - $this->executable, - ($this->parameters)(Option::short($key, $value)), - $this->environment, - $this->workingDirectory, - $this->input, - $this->redirection, - $this->timeout, - $this->streamOutput, + $this->implementation->withShortOption($key, $value), ); } @@ -177,15 +94,7 @@ public function withShortOption(string $key, ?string $value = null): self public function withEnvironment(string $key, string $value): self { return new self( - $this->background, - $this->executable, - $this->parameters, - ($this->environment)($key, $value), - $this->workingDirectory, - $this->input, - $this->redirection, - $this->timeout, - $this->streamOutput, + $this->implementation->withEnvironment($key, $value), ); } @@ -196,15 +105,13 @@ public function withEnvironment(string $key, string $value): self public function withEnvironments(Map $values): self { return new self( - $this->background, - $this->executable, - $this->parameters, - $this->environment->merge($values), - $this->workingDirectory, - $this->input, - $this->redirection, - $this->timeout, - $this->streamOutput, + $values->reduce( + $this->implementation, + static fn(Implementation $self, $key, $value) => $self->withEnvironment( + $key, + $value, + ), + ), ); } @@ -212,15 +119,7 @@ public function withEnvironments(Map $values): self public function withWorkingDirectory(Path $path): self { return new self( - $this->background, - $this->executable, - $this->parameters, - $this->environment, - Maybe::just($path), - $this->input, - $this->redirection, - $this->timeout, - $this->streamOutput, + $this->implementation->withWorkingDirectory($path), ); } @@ -228,15 +127,7 @@ public function withWorkingDirectory(Path $path): self public function withInput(Content $input): self { return new self( - $this->background, - $this->executable, - $this->parameters, - $this->environment, - $this->workingDirectory, - Maybe::just($input), - $this->redirection, - $this->timeout, - $this->streamOutput, + $this->implementation->withInput($input), ); } @@ -244,15 +135,7 @@ public function withInput(Content $input): self public function overwrite(Path $path): self { return new self( - $this->background, - $this->executable, - $this->parameters, - $this->environment, - $this->workingDirectory, - $this->input, - Maybe::just(new Overwrite($path)), - $this->timeout, - $this->streamOutput, + $this->implementation->overwrite($path), ); } @@ -260,55 +143,24 @@ public function overwrite(Path $path): self public function append(Path $path): self { return new self( - $this->background, - $this->executable, - $this->parameters, - $this->environment, - $this->workingDirectory, - $this->input, - Maybe::just(new Append($path)), - $this->timeout, - $this->streamOutput, + $this->implementation->append($path), ); } #[\NoDiscard] public function pipe(self $command): self { - return new self( - $this->background, - $this->executable, - $this - ->redirection - ->match( - $this->parameters, - fn() => $this->parameters, - ) - ->add(new Pipe) - ->add(new Argument($command->executable)) - ->append($command->parameters), - $this->environment->merge($command->environment), - $this->workingDirectory, - $this->input, - $command->redirection, - $this->timeout, - $this->streamOutput, - ); + return new self(Pipe::of( + $this->implementation, + $command->implementation, + )); } #[\NoDiscard] public function timeoutAfter(Period $timeout): self { return new self( - $this->background, - $this->executable, - $this->parameters, - $this->environment, - $this->workingDirectory, - $this->input, - $this->redirection, - Maybe::just($timeout), - $this->streamOutput, + $this->implementation->timeoutAfter($timeout), ); } @@ -326,15 +178,7 @@ public function timeoutAfter(Period $timeout): self public function streamOutput(): self { return new self( - $this->background, - $this->executable, - $this->parameters, - $this->environment, - $this->workingDirectory, - $this->input, - $this->redirection, - $this->timeout, - true, + $this->implementation->streamOutput(), ); } @@ -345,7 +189,7 @@ public function streamOutput(): self */ public function environment(): Map { - return $this->environment; + return $this->implementation->environment(); } /** @@ -355,7 +199,7 @@ public function environment(): Map */ public function workingDirectory(): Maybe { - return $this->workingDirectory; + return $this->implementation->workingDirectory(); } /** @@ -365,7 +209,7 @@ public function workingDirectory(): Maybe */ public function input(): Maybe { - return $this->input; + return $this->implementation->input(); } /** @@ -373,7 +217,7 @@ public function input(): Maybe */ public function toBeRunInBackground(): bool { - return $this->background; + return $this->implementation->toBeRunInBackground(); } /** @@ -383,7 +227,7 @@ public function toBeRunInBackground(): bool */ public function timeout(): Maybe { - return $this->timeout; + return $this->implementation->timeout(); } /** @@ -391,7 +235,7 @@ public function timeout(): Maybe */ public function outputToBeStreamed(): bool { - return $this->streamOutput; + return $this->implementation->outputToBeStreamed(); } /** @@ -401,23 +245,6 @@ public function outputToBeStreamed(): bool */ public function toString(): string { - $string = $this->executable; - - if ($this->parameters->size() > 0) { - $parameters = $this->parameters->map( - static fn($parameter): string => $parameter->toString(), - ); - $string .= ' '.Str::of(' ')->join($parameters)->toString(); - } - - return $this - ->redirection - ->map(static fn($redirection) => $redirection->toString()) - ->map(static fn($redirection) => ' '.$redirection) - ->map(static fn($redirection) => $string.$redirection) - ->match( - static fn($string) => $string, - static fn() => $string, - ); + return $this->implementation->toString(); } } diff --git a/src/Server/Command/Definition.php b/src/Server/Command/Definition.php new file mode 100644 index 0000000..fbfae75 --- /dev/null +++ b/src/Server/Command/Definition.php @@ -0,0 +1,326 @@ + $parameters + * @param Map $environment + * @param Maybe $workingDirectory + * @param Maybe $input + * @param Maybe|Maybe $redirection + * @param Maybe $timeout + */ + private function __construct( + private bool $background, + private string $executable, + private Sequence $parameters, + private Map $environment, + private Maybe $workingDirectory, + private Maybe $input, + private Maybe $redirection, + private Maybe $timeout, + private bool $streamOutput, + ) { + } + + /** + * @psalm-pure + * @internal + * + * @param non-empty-string $executable + */ + #[\NoDiscard] + public static function background(string $executable): self + { + /** @var Maybe */ + $workingDirectory = Maybe::nothing(); + /** @var Maybe */ + $input = Maybe::nothing(); + /** @var Maybe|Maybe */ + $redirection = Maybe::nothing(); + /** @var Maybe */ + $timeout = Maybe::nothing(); + + return new self( + true, + $executable, + Sequence::of(), + Map::of(), + $workingDirectory, + $input, + $redirection, + $timeout, + false, + ); + } + + /** + * @psalm-pure + * @internal + * + * @param non-empty-string $executable + */ + #[\NoDiscard] + public static function foreground(string $executable): self + { + /** @var Maybe */ + $workingDirectory = Maybe::nothing(); + /** @var Maybe */ + $input = Maybe::nothing(); + /** @var Maybe|Maybe */ + $redirection = Maybe::nothing(); + /** @var Maybe */ + $timeout = Maybe::nothing(); + + return new self( + false, + $executable, + Sequence::of(), + Map::of(), + $workingDirectory, + $input, + $redirection, + $timeout, + false, + ); + } + + #[\NoDiscard] + #[\Override] + public function withArgument(string $value): self + { + return new self( + $this->background, + $this->executable, + ($this->parameters)(new Argument($value)), + $this->environment, + $this->workingDirectory, + $this->input, + $this->redirection, + $this->timeout, + $this->streamOutput, + ); + } + + #[\NoDiscard] + #[\Override] + public function withOption(string $key, ?string $value = null): self + { + return new self( + $this->background, + $this->executable, + ($this->parameters)(Option::long($key, $value)), + $this->environment, + $this->workingDirectory, + $this->input, + $this->redirection, + $this->timeout, + $this->streamOutput, + ); + } + + #[\NoDiscard] + #[\Override] + public function withShortOption(string $key, ?string $value = null): self + { + return new self( + $this->background, + $this->executable, + ($this->parameters)(Option::short($key, $value)), + $this->environment, + $this->workingDirectory, + $this->input, + $this->redirection, + $this->timeout, + $this->streamOutput, + ); + } + + #[\NoDiscard] + #[\Override] + public function withEnvironment(string $key, string $value): self + { + return new self( + $this->background, + $this->executable, + $this->parameters, + ($this->environment)($key, $value), + $this->workingDirectory, + $this->input, + $this->redirection, + $this->timeout, + $this->streamOutput, + ); + } + + #[\NoDiscard] + #[\Override] + public function withWorkingDirectory(Path $path): self + { + return new self( + $this->background, + $this->executable, + $this->parameters, + $this->environment, + Maybe::just($path), + $this->input, + $this->redirection, + $this->timeout, + $this->streamOutput, + ); + } + + #[\NoDiscard] + #[\Override] + public function withInput(Content $input): self + { + return new self( + $this->background, + $this->executable, + $this->parameters, + $this->environment, + $this->workingDirectory, + Maybe::just($input), + $this->redirection, + $this->timeout, + $this->streamOutput, + ); + } + + #[\NoDiscard] + #[\Override] + public function overwrite(Path $path): self + { + return new self( + $this->background, + $this->executable, + $this->parameters, + $this->environment, + $this->workingDirectory, + $this->input, + Maybe::just(new Overwrite($path)), + $this->timeout, + $this->streamOutput, + ); + } + + #[\NoDiscard] + #[\Override] + public function append(Path $path): self + { + return new self( + $this->background, + $this->executable, + $this->parameters, + $this->environment, + $this->workingDirectory, + $this->input, + Maybe::just(new Append($path)), + $this->timeout, + $this->streamOutput, + ); + } + + #[\NoDiscard] + #[\Override] + public function timeoutAfter(Period $timeout): self + { + return new self( + $this->background, + $this->executable, + $this->parameters, + $this->environment, + $this->workingDirectory, + $this->input, + $this->redirection, + Maybe::just($timeout), + $this->streamOutput, + ); + } + + #[\NoDiscard] + #[\Override] + public function streamOutput(): self + { + return new self( + $this->background, + $this->executable, + $this->parameters, + $this->environment, + $this->workingDirectory, + $this->input, + $this->redirection, + $this->timeout, + true, + ); + } + + #[\Override] + public function environment(): Map + { + return $this->environment; + } + + #[\Override] + public function workingDirectory(): Maybe + { + return $this->workingDirectory; + } + + #[\Override] + public function input(): Maybe + { + return $this->input; + } + + #[\Override] + public function toBeRunInBackground(): bool + { + return $this->background; + } + + #[\Override] + public function timeout(): Maybe + { + return $this->timeout; + } + + #[\Override] + public function outputToBeStreamed(): bool + { + return $this->streamOutput; + } + + #[\Override] + public function toString(): string + { + /** @var non-empty-string */ + return $this + ->parameters + ->append($this->redirection->toSequence()) + ->map(static fn($parameter) => ' '.$parameter->toString()) + ->map(Str::of(...)) + ->fold(new Concat) + ->prepend($this->executable) + ->toString(); + } +} diff --git a/src/Server/Command/Implementation.php b/src/Server/Command/Implementation.php new file mode 100644 index 0000000..dcddbe4 --- /dev/null +++ b/src/Server/Command/Implementation.php @@ -0,0 +1,86 @@ + + */ + #[\NoDiscard] + public function environment(): Map; + + /** + * @return Maybe + */ + #[\NoDiscard] + public function workingDirectory(): Maybe; + + /** + * @return Maybe + */ + #[\NoDiscard] + public function input(): Maybe; + #[\NoDiscard] + public function toBeRunInBackground(): bool; + + /** + * @return Maybe + */ + #[\NoDiscard] + public function timeout(): Maybe; + #[\NoDiscard] + public function outputToBeStreamed(): bool; + + /** + * @return non-empty-string + */ + #[\NoDiscard] + public function toString(): string; +} diff --git a/src/Server/Command/Pipe.php b/src/Server/Command/Pipe.php index e30e43d..81ac5e9 100644 --- a/src/Server/Command/Pipe.php +++ b/src/Server/Command/Pipe.php @@ -3,15 +3,179 @@ namespace Innmind\Server\Control\Server\Command; +use Innmind\TimeContinuum\Period; +use Innmind\Filesystem\File\Content; +use Innmind\Url\Path; +use Innmind\Immutable\{ + Map, + Maybe, +}; + /** * @psalm-immutable * @internal */ -final class Pipe implements Parameter +final class Pipe implements Implementation { + private function __construct( + private Implementation $a, + private Implementation $b, + ) { + } + + /** + * @psalm-pure + * @internal + */ + public static function of(Implementation $a, Implementation $b): self + { + return new self($a, $b); + } + + #[\Override] + #[\NoDiscard] + public function withArgument(string $value): self + { + return new self( + $this->a, + $this->b->withArgument($value), + ); + } + + #[\Override] + #[\NoDiscard] + public function withOption(string $key, ?string $value = null): self + { + return new self( + $this->a, + $this->b->withOption($key, $value), + ); + } + + #[\Override] + #[\NoDiscard] + public function withShortOption(string $key, ?string $value = null): self + { + return new self( + $this->a, + $this->b->withShortOption($key, $value), + ); + } + + #[\Override] + #[\NoDiscard] + public function withEnvironment(string $key, string $value): self + { + return new self( + $this->a, + $this->b->withEnvironment($key, $value), + ); + } + + #[\Override] + #[\NoDiscard] + public function withWorkingDirectory(Path $path): self + { + return new self( + $this->a->withWorkingDirectory($path), + $this->b, + ); + } + + #[\Override] + #[\NoDiscard] + public function withInput(Content $input): self + { + return new self( + $this->a->withInput($input), + $this->b, + ); + } + + #[\Override] + #[\NoDiscard] + public function overwrite(Path $path): self + { + return new self( + $this->a, + $this->b->overwrite($path), + ); + } + + #[\Override] + #[\NoDiscard] + public function append(Path $path): self + { + return new self( + $this->a, + $this->b->append($path), + ); + } + + #[\Override] + #[\NoDiscard] + public function timeoutAfter(Period $timeout): self + { + return new self( + $this->a->timeoutAfter($timeout), + $this->b, + ); + } + + #[\Override] + #[\NoDiscard] + public function streamOutput(): self + { + return new self( + $this->a->streamOutput(), + $this->b, + ); + } + + #[\Override] + public function environment(): Map + { + return $this->a->environment()->merge($this->b->environment()); + } + + #[\Override] + public function workingDirectory(): Maybe + { + return $this->a->workingDirectory(); + } + + #[\Override] + public function input(): Maybe + { + return $this->a->input(); + } + + #[\Override] + public function toBeRunInBackground(): bool + { + // todo should it be 'a || b' ? + return $this->a->toBeRunInBackground(); + } + + #[\Override] + public function timeout(): Maybe + { + return $this->a->timeout(); + } + + #[\Override] + public function outputToBeStreamed(): bool + { + return $this->a->outputToBeStreamed(); + } + #[\Override] public function toString(): string { - return '|'; + return \sprintf( + '%s | %s', + $this->a->toString(), + $this->b->toString(), + ); } } diff --git a/tests/Server/Command/PipeTest.php b/tests/Server/Command/PipeTest.php deleted file mode 100644 index 993796d..0000000 --- a/tests/Server/Command/PipeTest.php +++ /dev/null @@ -1,18 +0,0 @@ -assertSame('|', (new Pipe)->toString()); - } -} diff --git a/tests/Server/CommandTest.php b/tests/Server/CommandTest.php index 1d76895..76f22d6 100644 --- a/tests/Server/CommandTest.php +++ b/tests/Server/CommandTest.php @@ -207,7 +207,7 @@ public function testPipe() $this->assertSame("cat 'foo.txt'", $commandB->toString()); $this->assertSame("wc > 'count.txt'", $commandC->toString()); $this->assertSame( - "echo 'bar' >> 'foo.txt' | 'cat' 'foo.txt' | 'wc' > 'count.txt'", + "echo 'bar' >> 'foo.txt' | cat 'foo.txt' | wc > 'count.txt'", $command->toString(), ); } From 8f4ffa1406572c147f1cfc35f0e61e5399c56ec2 Mon Sep 17 00:00:00 2001 From: Baptiste Langlade Date: Thu, 4 Dec 2025 16:39:28 +0100 Subject: [PATCH 16/27] expose command definition details --- src/Server/Command.php | 10 ++++++++++ src/Server/Command/Definition.php | 27 +++++++++++++++++++++++++++ 2 files changed, 37 insertions(+) diff --git a/src/Server/Command.php b/src/Server/Command.php index 6b50b2c..9b63abb 100644 --- a/src/Server/Command.php +++ b/src/Server/Command.php @@ -247,4 +247,14 @@ public function toString(): string { return $this->implementation->toString(); } + + /** + * This method is only to be used by innmind/testing + * + * @internal + */ + public function internal(): Implementation + { + return $this->implementation; + } } diff --git a/src/Server/Command/Definition.php b/src/Server/Command/Definition.php index fbfae75..3f5fd4d 100644 --- a/src/Server/Command/Definition.php +++ b/src/Server/Command/Definition.php @@ -274,6 +274,33 @@ public function streamOutput(): self ); } + /** + * @return non-empty-string + */ + #[\NoDiscard] + public function executable(): string + { + return $this->executable; + } + + /** + * @return Sequence + */ + #[\NoDiscard] + public function parameters(): Sequence + { + return $this->parameters; + } + + /** + * @return Maybe|Maybe + */ + #[\NoDiscard] + public function redirection(): Maybe + { + return $this->redirection; + } + #[\Override] public function environment(): Map { From 5801269de7232adbfb77e0e8ff8437a27a88fc68 Mon Sep 17 00:00:00 2001 From: Baptiste Langlade Date: Thu, 4 Dec 2025 16:50:49 +0100 Subject: [PATCH 17/27] remove Parameter interface --- src/Server/Command/Append.php | 3 +-- src/Server/Command/Argument.php | 3 +-- src/Server/Command/Definition.php | 9 ++++++--- src/Server/Command/Option.php | 3 +-- src/Server/Command/Overwrite.php | 3 +-- src/Server/Command/Parameter.php | 13 ------------- 6 files changed, 10 insertions(+), 24 deletions(-) delete mode 100644 src/Server/Command/Parameter.php diff --git a/src/Server/Command/Append.php b/src/Server/Command/Append.php index 188de75..2a6b852 100644 --- a/src/Server/Command/Append.php +++ b/src/Server/Command/Append.php @@ -9,7 +9,7 @@ * @psalm-immutable * @internal */ -final class Append implements Parameter +final class Append { private string $value; @@ -18,7 +18,6 @@ public function __construct(Path $path) $this->value = '>> '.(new Argument($path->toString()))->toString(); } - #[\Override] public function toString(): string { return $this->value; diff --git a/src/Server/Command/Argument.php b/src/Server/Command/Argument.php index c51dd0a..4badf76 100644 --- a/src/Server/Command/Argument.php +++ b/src/Server/Command/Argument.php @@ -7,7 +7,7 @@ * @psalm-immutable * @internal */ -final class Argument implements Parameter +final class Argument { private string $value; @@ -16,7 +16,6 @@ public function __construct(string $value) $this->value = (new Str($value))->toString(); } - #[\Override] public function toString(): string { return $this->value; diff --git a/src/Server/Command/Definition.php b/src/Server/Command/Definition.php index 3f5fd4d..3912f6b 100644 --- a/src/Server/Command/Definition.php +++ b/src/Server/Command/Definition.php @@ -22,7 +22,7 @@ final class Definition implements Implementation { /** * @param non-empty-string $executable - * @param Sequence $parameters + * @param Sequence $parameters * @param Map $environment * @param Maybe $workingDirectory * @param Maybe $input @@ -284,7 +284,7 @@ public function executable(): string } /** - * @return Sequence + * @return Sequence */ #[\NoDiscard] public function parameters(): Sequence @@ -340,7 +340,10 @@ public function outputToBeStreamed(): bool #[\Override] public function toString(): string { - /** @var non-empty-string */ + /** + * @psalm-suppress InvalidArgument Due to append + * @var non-empty-string + */ return $this ->parameters ->append($this->redirection->toSequence()) diff --git a/src/Server/Command/Option.php b/src/Server/Command/Option.php index 34f01d5..1bec1ac 100644 --- a/src/Server/Command/Option.php +++ b/src/Server/Command/Option.php @@ -7,7 +7,7 @@ * @psalm-immutable * @internal */ -final class Option implements Parameter +final class Option { /** * @param non-empty-string $key @@ -37,7 +37,6 @@ public static function short(string $key, ?string $value = null): self return new self(false, $key, $value); } - #[\Override] public function toString(): string { if ($this->long) { diff --git a/src/Server/Command/Overwrite.php b/src/Server/Command/Overwrite.php index 7366cef..f4ac08c 100644 --- a/src/Server/Command/Overwrite.php +++ b/src/Server/Command/Overwrite.php @@ -9,7 +9,7 @@ * @psalm-immutable * @internal */ -final class Overwrite implements Parameter +final class Overwrite { private string $value; @@ -18,7 +18,6 @@ public function __construct(Path $path) $this->value = '> '.(new Argument($path->toString()))->toString(); } - #[\Override] public function toString(): string { return $this->value; diff --git a/src/Server/Command/Parameter.php b/src/Server/Command/Parameter.php deleted file mode 100644 index ff2d7cf..0000000 --- a/src/Server/Command/Parameter.php +++ /dev/null @@ -1,13 +0,0 @@ - Date: Thu, 4 Dec 2025 16:59:18 +0100 Subject: [PATCH 18/27] expose the raw values of parameters --- src/Server/Command/Append.php | 10 ++++++---- src/Server/Command/Argument.php | 10 ++++++---- src/Server/Command/Option.php | 12 +++++++++++- src/Server/Command/Overwrite.php | 10 ++++++---- 4 files changed, 29 insertions(+), 13 deletions(-) diff --git a/src/Server/Command/Append.php b/src/Server/Command/Append.php index 2a6b852..4e05446 100644 --- a/src/Server/Command/Append.php +++ b/src/Server/Command/Append.php @@ -11,15 +11,17 @@ */ final class Append { - private string $value; + public function __construct(private Path $path) + { + } - public function __construct(Path $path) + public function path(): Path { - $this->value = '>> '.(new Argument($path->toString()))->toString(); + return $this->path; } public function toString(): string { - return $this->value; + return '>> '.(new Argument($this->path->toString()))->toString(); } } diff --git a/src/Server/Command/Argument.php b/src/Server/Command/Argument.php index 4badf76..e9cf4fc 100644 --- a/src/Server/Command/Argument.php +++ b/src/Server/Command/Argument.php @@ -9,15 +9,17 @@ */ final class Argument { - private string $value; + public function __construct(private string $value) + { + } - public function __construct(string $value) + public function unescaped(): string { - $this->value = (new Str($value))->toString(); + return $this->value; } public function toString(): string { - return $this->value; + return (new Str($this->value))->toString(); } } diff --git a/src/Server/Command/Option.php b/src/Server/Command/Option.php index 1bec1ac..077ff8f 100644 --- a/src/Server/Command/Option.php +++ b/src/Server/Command/Option.php @@ -15,7 +15,7 @@ final class Option private function __construct( private bool $long, private string $key, - private ?string $value = null, + private ?string $value, ) { } @@ -37,6 +37,16 @@ public static function short(string $key, ?string $value = null): self return new self(false, $key, $value); } + public function key(): string + { + return $this->key; + } + + public function value(): ?string + { + return $this->value; + } + public function toString(): string { if ($this->long) { diff --git a/src/Server/Command/Overwrite.php b/src/Server/Command/Overwrite.php index f4ac08c..a741ab9 100644 --- a/src/Server/Command/Overwrite.php +++ b/src/Server/Command/Overwrite.php @@ -11,15 +11,17 @@ */ final class Overwrite { - private string $value; + public function __construct(private Path $path) + { + } - public function __construct(Path $path) + public function path(): Path { - $this->value = '> '.(new Argument($path->toString()))->toString(); + return $this->path; } public function toString(): string { - return $this->value; + return '> '.(new Argument($this->path->toString()))->toString(); } } From a3c8dadbb46e97f42ffc9acd98f2ce9d1ef5c2e6 Mon Sep 17 00:00:00 2001 From: Baptiste Langlade Date: Thu, 4 Dec 2025 17:21:13 +0100 Subject: [PATCH 19/27] regroup append and overwrite in the same class --- src/Server/Command/Append.php | 27 ----------- src/Server/Command/Definition.php | 12 ++--- src/Server/Command/Overwrite.php | 27 ----------- src/Server/Command/Redirection.php | 62 ++++++++++++++++++++++++++ tests/Server/Command/AppendTest.php | 4 +- tests/Server/Command/OverwriteTest.php | 4 +- 6 files changed, 72 insertions(+), 64 deletions(-) delete mode 100644 src/Server/Command/Append.php delete mode 100644 src/Server/Command/Overwrite.php create mode 100644 src/Server/Command/Redirection.php diff --git a/src/Server/Command/Append.php b/src/Server/Command/Append.php deleted file mode 100644 index 4e05446..0000000 --- a/src/Server/Command/Append.php +++ /dev/null @@ -1,27 +0,0 @@ -path; - } - - public function toString(): string - { - return '>> '.(new Argument($this->path->toString()))->toString(); - } -} diff --git a/src/Server/Command/Definition.php b/src/Server/Command/Definition.php index 3912f6b..a8b65aa 100644 --- a/src/Server/Command/Definition.php +++ b/src/Server/Command/Definition.php @@ -26,7 +26,7 @@ final class Definition implements Implementation * @param Map $environment * @param Maybe $workingDirectory * @param Maybe $input - * @param Maybe|Maybe $redirection + * @param Maybe $redirection * @param Maybe $timeout */ private function __construct( @@ -55,7 +55,7 @@ public static function background(string $executable): self $workingDirectory = Maybe::nothing(); /** @var Maybe */ $input = Maybe::nothing(); - /** @var Maybe|Maybe */ + /** @var Maybe */ $redirection = Maybe::nothing(); /** @var Maybe */ $timeout = Maybe::nothing(); @@ -86,7 +86,7 @@ public static function foreground(string $executable): self $workingDirectory = Maybe::nothing(); /** @var Maybe */ $input = Maybe::nothing(); - /** @var Maybe|Maybe */ + /** @var Maybe */ $redirection = Maybe::nothing(); /** @var Maybe */ $timeout = Maybe::nothing(); @@ -217,7 +217,7 @@ public function overwrite(Path $path): self $this->environment, $this->workingDirectory, $this->input, - Maybe::just(new Overwrite($path)), + Maybe::just(Redirection::overwrite($path)), $this->timeout, $this->streamOutput, ); @@ -234,7 +234,7 @@ public function append(Path $path): self $this->environment, $this->workingDirectory, $this->input, - Maybe::just(new Append($path)), + Maybe::just(Redirection::append($path)), $this->timeout, $this->streamOutput, ); @@ -293,7 +293,7 @@ public function parameters(): Sequence } /** - * @return Maybe|Maybe + * @return Maybe */ #[\NoDiscard] public function redirection(): Maybe diff --git a/src/Server/Command/Overwrite.php b/src/Server/Command/Overwrite.php deleted file mode 100644 index a741ab9..0000000 --- a/src/Server/Command/Overwrite.php +++ /dev/null @@ -1,27 +0,0 @@ -path; - } - - public function toString(): string - { - return '> '.(new Argument($this->path->toString()))->toString(); - } -} diff --git a/src/Server/Command/Redirection.php b/src/Server/Command/Redirection.php new file mode 100644 index 0000000..c503ad2 --- /dev/null +++ b/src/Server/Command/Redirection.php @@ -0,0 +1,62 @@ +append) { + true => $append($this->path), + false => $overwrite($this->path), + }; + } + + public function toString(): string + { + return (match ($this->append) { + true => '>>', + false => '>', + }).' '.(new Str($this->path->toString()))->toString(); + } +} diff --git a/tests/Server/Command/AppendTest.php b/tests/Server/Command/AppendTest.php index 2d0305b..08adf0b 100644 --- a/tests/Server/Command/AppendTest.php +++ b/tests/Server/Command/AppendTest.php @@ -3,7 +3,7 @@ namespace Tests\Innmind\Server\Control\Server\Command; -use Innmind\Server\Control\Server\Command\Append; +use Innmind\Server\Control\Server\Command\Redirection; use Innmind\Url\Path; use Innmind\BlackBox\PHPUnit\Framework\TestCase; use PHPUnit\Framework\Attributes\Group; @@ -14,7 +14,7 @@ class AppendTest extends TestCase #[Group('local')] public function testInterface() { - $argument = new Append(Path::of('some-value')); + $argument = Redirection::append(Path::of('some-value')); $this->assertSame(">> 'some-value'", $argument->toString()); } diff --git a/tests/Server/Command/OverwriteTest.php b/tests/Server/Command/OverwriteTest.php index 0c4afcf..ed1bd0b 100644 --- a/tests/Server/Command/OverwriteTest.php +++ b/tests/Server/Command/OverwriteTest.php @@ -3,7 +3,7 @@ namespace Tests\Innmind\Server\Control\Server\Command; -use Innmind\Server\Control\Server\Command\Overwrite; +use Innmind\Server\Control\Server\Command\Redirection; use Innmind\Url\Path; use Innmind\BlackBox\PHPUnit\Framework\TestCase; use PHPUnit\Framework\Attributes\Group; @@ -14,7 +14,7 @@ class OverwriteTest extends TestCase #[Group('local')] public function testInterface() { - $argument = new Overwrite(Path::of('some-value')); + $argument = Redirection::overwrite(Path::of('some-value')); $this->assertSame("> 'some-value'", $argument->toString()); } From 835c2101f5ccc4eacbd38c59618874002dc86f08 Mon Sep 17 00:00:00 2001 From: Baptiste Langlade Date: Thu, 4 Dec 2025 17:25:26 +0100 Subject: [PATCH 20/27] simplify string escaping api --- src/Server/Command/Argument.php | 2 +- src/Server/Command/Option.php | 6 +++--- src/Server/Command/Redirection.php | 2 +- src/Server/Command/Str.php | 22 ++++++---------------- tests/Server/Command/StrTest.php | 2 +- 5 files changed, 12 insertions(+), 22 deletions(-) diff --git a/src/Server/Command/Argument.php b/src/Server/Command/Argument.php index e9cf4fc..8468887 100644 --- a/src/Server/Command/Argument.php +++ b/src/Server/Command/Argument.php @@ -20,6 +20,6 @@ public function unescaped(): string public function toString(): string { - return (new Str($this->value))->toString(); + return Str::escape($this->value); } } diff --git a/src/Server/Command/Option.php b/src/Server/Command/Option.php index 077ff8f..d91408b 100644 --- a/src/Server/Command/Option.php +++ b/src/Server/Command/Option.php @@ -64,15 +64,15 @@ private function longString(): string $string .= '='.$this->value; } - return (new Str($string))->toString(); + return Str::escape($string); } private function shortString(): string { - $string = (new Str('-'.$this->key))->toString(); + $string = Str::escape('-'.$this->key); if (\is_string($this->value)) { - $string .= ' '.(new Str($this->value))->toString(); + $string .= ' '.Str::escape($this->value); } return $string; diff --git a/src/Server/Command/Redirection.php b/src/Server/Command/Redirection.php index c503ad2..06091c6 100644 --- a/src/Server/Command/Redirection.php +++ b/src/Server/Command/Redirection.php @@ -57,6 +57,6 @@ public function toString(): string return (match ($this->append) { true => '>>', false => '>', - }).' '.(new Str($this->path->toString()))->toString(); + }).' '.Str::escape($this->path->toString()); } } diff --git a/src/Server/Command/Str.php b/src/Server/Command/Str.php index 5030034..718d35d 100644 --- a/src/Server/Command/Str.php +++ b/src/Server/Command/Str.php @@ -6,31 +6,21 @@ use Innmind\Immutable\Str as S; /** - * @psalm-immutable * @internal */ final class Str { - private string $value; - - public function __construct(string $string) - { - $this->value = $this->escape(S::of($string))->toString(); - } - - public function toString(): string - { - return $this->value; - } - /** + * @psalm-pure + * @internal * @see Symfony\Component\Process\Process::escapeArgument() */ - private function escape(S $string): S + public static function escape(string $string): string { - return $string + return S::of($string) ->replace("'", "'\\''") ->prepend("'") - ->append("'"); + ->append("'") + ->toString(); } } diff --git a/tests/Server/Command/StrTest.php b/tests/Server/Command/StrTest.php index 7688132..c28ddfd 100644 --- a/tests/Server/Command/StrTest.php +++ b/tests/Server/Command/StrTest.php @@ -17,7 +17,7 @@ class StrTest extends TestCase #[Group('local')] public function testInterface(string $str, string $expected) { - $this->assertSame($expected, (new Str($str))->toString()); + $this->assertSame($expected, Str::escape($str)); } public static function cases(): array From 32b5494f161db6d47ae726564dcbebf26a547bd4 Mon Sep 17 00:00:00 2001 From: Baptiste Langlade Date: Thu, 4 Dec 2025 17:33:45 +0100 Subject: [PATCH 21/27] expose pipe sub commands --- src/Server/Command/Pipe.php | 12 ++++++++++++ 1 file changed, 12 insertions(+) diff --git a/src/Server/Command/Pipe.php b/src/Server/Command/Pipe.php index 81ac5e9..db6fbc2 100644 --- a/src/Server/Command/Pipe.php +++ b/src/Server/Command/Pipe.php @@ -32,6 +32,18 @@ public static function of(Implementation $a, Implementation $b): self return new self($a, $b); } + #[\NoDiscard] + public function a(): Implementation + { + return $this->a; + } + + #[\NoDiscard] + public function b(): Implementation + { + return $this->b; + } + #[\Override] #[\NoDiscard] public function withArgument(string $value): self From ce14479e9a622a4a6976af75aed0581a8f73ce64 Mon Sep 17 00:00:00 2001 From: Baptiste Langlade Date: Sat, 31 Jan 2026 18:32:35 +0100 Subject: [PATCH 22/27] tag dependencies --- .github/workflows/ci.yml | 8 ++++---- composer.json | 17 +++++++---------- src/Server/Command/Definition.php | 2 +- tests/Server/Process/BackgroundTest.php | 2 +- tests/Server/Process/ForegroundTest.php | 4 ++-- tests/Server/Processes/UnixTest.php | 2 +- 6 files changed, 16 insertions(+), 19 deletions(-) diff --git a/.github/workflows/ci.yml b/.github/workflows/ci.yml index 05d18ca..6520bd7 100644 --- a/.github/workflows/ci.yml +++ b/.github/workflows/ci.yml @@ -4,15 +4,15 @@ on: [push, pull_request] jobs: blackbox: - uses: innmind/github-workflows/.github/workflows/black-box-matrix.yml@next + uses: innmind/github-workflows/.github/workflows/black-box-matrix.yml@main with: tags: 'ci' coverage: - uses: innmind/github-workflows/.github/workflows/coverage-matrix.yml@next + uses: innmind/github-workflows/.github/workflows/coverage-matrix.yml@main secrets: inherit with: tags: 'ci' psalm: - uses: innmind/github-workflows/.github/workflows/psalm-matrix.yml@next + uses: innmind/github-workflows/.github/workflows/psalm-matrix.yml@main cs: - uses: innmind/github-workflows/.github/workflows/cs.yml@next + uses: innmind/github-workflows/.github/workflows/cs.yml@main diff --git a/composer.json b/composer.json index 94de669..e23a75c 100644 --- a/composer.json +++ b/composer.json @@ -15,16 +15,13 @@ }, "require": { "php": "~8.4", - "innmind/immutable": "dev-next", - "innmind/url": "dev-next", + "innmind/immutable": "~6.0", + "innmind/url": "~5.0", "psr/log": "~3.0", - "innmind/time-continuum": "dev-next", - "innmind/filesystem": "dev-next", - "innmind/time-warp": "dev-next", - "innmind/io": "dev-next", - "innmind/media-type": "dev-next", - "innmind/validation": "dev-next", - "innmind/ip": "dev-next" + "innmind/time-continuum": "~5.0", + "innmind/filesystem": "~9.0", + "innmind/time-warp": "~5.0", + "innmind/io": "~4.0" }, "autoload": { "psr-4": { @@ -37,7 +34,7 @@ } }, "require-dev": { - "innmind/static-analysis": "^1.2.1", + "innmind/static-analysis": "~1.3", "innmind/black-box": "~6.5", "innmind/coding-standard": "~2.0" } diff --git a/src/Server/Command/Definition.php b/src/Server/Command/Definition.php index a8b65aa..b939db0 100644 --- a/src/Server/Command/Definition.php +++ b/src/Server/Command/Definition.php @@ -349,7 +349,7 @@ public function toString(): string ->append($this->redirection->toSequence()) ->map(static fn($parameter) => ' '.$parameter->toString()) ->map(Str::of(...)) - ->fold(new Concat) + ->fold(Concat::monoid) ->prepend($this->executable) ->toString(); } diff --git a/tests/Server/Process/BackgroundTest.php b/tests/Server/Process/BackgroundTest.php index e4a6e67..afacacd 100644 --- a/tests/Server/Process/BackgroundTest.php +++ b/tests/Server/Process/BackgroundTest.php @@ -77,7 +77,7 @@ public function testOutput() $process ->output() ->map(static fn($chunk) => $chunk->data()) - ->fold(new Concat) + ->fold(Concat::monoid) ->toString(), ); $this->assertTrue((\time() - $start) < 1); diff --git a/tests/Server/Process/ForegroundTest.php b/tests/Server/Process/ForegroundTest.php index c1e505f..ad3a728 100644 --- a/tests/Server/Process/ForegroundTest.php +++ b/tests/Server/Process/ForegroundTest.php @@ -95,7 +95,7 @@ public function testOutput() $process ->output() ->map(static fn($chunk) => $chunk->data()) - ->fold(new Concat) + ->fold(Concat::monoid) ->toString(), ); $this->assertSame(6, $count); @@ -246,7 +246,7 @@ public function testOutputIsAvailableAfterWaitingForExitStatus() $process ->output() ->map(static fn($chunk) => $chunk->data()) - ->fold(new Concat) + ->fold(Concat::monoid) ->toString(), ); } diff --git a/tests/Server/Processes/UnixTest.php b/tests/Server/Processes/UnixTest.php index bc44dbc..a944edc 100644 --- a/tests/Server/Processes/UnixTest.php +++ b/tests/Server/Processes/UnixTest.php @@ -91,7 +91,7 @@ public function testExecuteWithInput() $process ->output() ->map(static fn($chunk) => $chunk->data()) - ->fold(new Concat) + ->fold(Concat::monoid) ->toString(), ); } From 45084ad773a339f6ee3e514d909cc543c7ff037e Mon Sep 17 00:00:00 2001 From: Baptiste Langlade Date: Sat, 31 Jan 2026 18:42:58 +0100 Subject: [PATCH 23/27] replace innmind/time-continuum and innmind/time-warp by innmind/time --- CHANGELOG.md | 1 + composer.json | 3 +-- src/Run/Unix.php | 4 ++-- src/Server.php | 4 ++-- src/Server/Command.php | 2 +- src/Server/Command/Definition.php | 2 +- src/Server/Command/Implementation.php | 2 +- src/Server/Command/Pipe.php | 2 +- src/Server/Process/Started.php | 8 ++++---- src/Server/Process/Unix.php | 4 ++-- src/ServerFactory.php | 4 ++-- tests/Server/CommandTest.php | 2 +- tests/Server/Process/BackgroundTest.php | 4 ++-- tests/Server/Process/ForegroundTest.php | 4 ++-- tests/Server/Process/UnixTest.php | 4 ++-- tests/Server/Processes/LoggerTest.php | 6 ++++-- tests/Server/Processes/RemoteTest.php | 6 ++++-- tests/Server/Processes/UnixTest.php | 4 ++-- tests/Server/ScriptTest.php | 4 ++-- tests/Server/VolumesTest.php | 6 ++++-- tests/ServerFactoryTest.php | 6 ++++-- tests/Servers/LoggerTest.php | 6 ++++-- tests/Servers/RemoteTest.php | 6 ++++-- tests/Servers/UnixTest.php | 6 ++++-- 24 files changed, 57 insertions(+), 43 deletions(-) diff --git a/CHANGELOG.md b/CHANGELOG.md index d7a81b9..f958d74 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -8,6 +8,7 @@ - `Innmind\Server\Control\Server` is now a final class, all previous implementations are now flagged as internal - `Innmind\Server\Control\Server\Volumes` is now a final class - `Innmind\Server\Control\Server\Processes` is now a final class +- Requires `innmind/time:~1.0` ## 6.1.0 - 2025-08-06 diff --git a/composer.json b/composer.json index e23a75c..f479492 100644 --- a/composer.json +++ b/composer.json @@ -18,9 +18,8 @@ "innmind/immutable": "~6.0", "innmind/url": "~5.0", "psr/log": "~3.0", - "innmind/time-continuum": "~5.0", + "innmind/time": "~1.0", "innmind/filesystem": "~9.0", - "innmind/time-warp": "~5.0", "innmind/io": "~4.0" }, "autoload": { diff --git a/src/Run/Unix.php b/src/Run/Unix.php index 107507f..bcc1849 100644 --- a/src/Run/Unix.php +++ b/src/Run/Unix.php @@ -7,11 +7,11 @@ Server\Command, Server\Process, }; -use Innmind\TimeContinuum\{ +use Innmind\Time\{ Clock, Period, + Halt, }; -use Innmind\TimeWarp\Halt; use Innmind\IO\IO; use Innmind\Immutable\Attempt; diff --git a/src/Server.php b/src/Server.php index fa189cc..95019e3 100644 --- a/src/Server.php +++ b/src/Server.php @@ -10,16 +10,16 @@ Server\Script, Server\Command, }; -use Innmind\TimeWarp\Halt; use Innmind\IO\IO; use Innmind\Url\Authority\{ Host, Port, UserInformation\User, }; -use Innmind\TimeContinuum\{ +use Innmind\Time\{ Clock, Period, + Halt, }; use Innmind\Immutable\{ Attempt, diff --git a/src/Server/Command.php b/src/Server/Command.php index 9b63abb..d2d31ce 100644 --- a/src/Server/Command.php +++ b/src/Server/Command.php @@ -8,7 +8,7 @@ Definition, Pipe, }; -use Innmind\TimeContinuum\Period; +use Innmind\Time\Period; use Innmind\Filesystem\File\Content; use Innmind\Url\Path; use Innmind\Immutable\{ diff --git a/src/Server/Command/Definition.php b/src/Server/Command/Definition.php index b939db0..3b2823c 100644 --- a/src/Server/Command/Definition.php +++ b/src/Server/Command/Definition.php @@ -3,7 +3,7 @@ namespace Innmind\Server\Control\Server\Command; -use Innmind\TimeContinuum\Period; +use Innmind\Time\Period; use Innmind\Filesystem\File\Content; use Innmind\Url\Path; use Innmind\Immutable\{ diff --git a/src/Server/Command/Implementation.php b/src/Server/Command/Implementation.php index dcddbe4..159751e 100644 --- a/src/Server/Command/Implementation.php +++ b/src/Server/Command/Implementation.php @@ -3,7 +3,7 @@ namespace Innmind\Server\Control\Server\Command; -use Innmind\TimeContinuum\Period; +use Innmind\Time\Period; use Innmind\Filesystem\File\Content; use Innmind\Url\Path; use Innmind\Immutable\{ diff --git a/src/Server/Command/Pipe.php b/src/Server/Command/Pipe.php index db6fbc2..626c7b8 100644 --- a/src/Server/Command/Pipe.php +++ b/src/Server/Command/Pipe.php @@ -3,7 +3,7 @@ namespace Innmind\Server\Control\Server\Command; -use Innmind\TimeContinuum\Period; +use Innmind\Time\Period; use Innmind\Filesystem\File\Content; use Innmind\Url\Path; use Innmind\Immutable\{ diff --git a/src/Server/Process/Started.php b/src/Server/Process/Started.php index d994d16..87ed03e 100644 --- a/src/Server/Process/Started.php +++ b/src/Server/Process/Started.php @@ -9,12 +9,12 @@ Server\Signal, }; use Innmind\Filesystem\File\Content; -use Innmind\TimeContinuum\{ +use Innmind\Time\{ Clock, - PointInTime, + Point, Period, + Halt, }; -use Innmind\TimeWarp\Halt; use Innmind\IO\{ IO, Streams\Stream, @@ -44,7 +44,7 @@ final class Started private Halt $halt; private Period $grace; private bool $background; - private PointInTime $startedAt; + private Point $startedAt; /** @var resource */ private $process; private Stream $output; diff --git a/src/Server/Process/Unix.php b/src/Server/Process/Unix.php index ff543d9..eca2e09 100644 --- a/src/Server/Process/Unix.php +++ b/src/Server/Process/Unix.php @@ -7,11 +7,11 @@ Server\Command, Exception\RuntimeException, }; -use Innmind\TimeContinuum\{ +use Innmind\Time\{ Clock, Period, + Halt, }; -use Innmind\TimeWarp\Halt; use Innmind\IO\IO; /** diff --git a/src/ServerFactory.php b/src/ServerFactory.php index 7955625..f5cb2fb 100644 --- a/src/ServerFactory.php +++ b/src/ServerFactory.php @@ -4,11 +4,11 @@ namespace Innmind\Server\Control; use Innmind\Server\Control\Exception\UnsupportedOperatingSystem; -use Innmind\TimeContinuum\{ +use Innmind\Time\{ Clock, Period, + Halt, }; -use Innmind\TimeWarp\Halt; use Innmind\IO\IO; final class ServerFactory diff --git a/tests/Server/CommandTest.php b/tests/Server/CommandTest.php index 76f22d6..6a15d80 100644 --- a/tests/Server/CommandTest.php +++ b/tests/Server/CommandTest.php @@ -4,7 +4,7 @@ namespace Tests\Innmind\Server\Control\Server; use Innmind\Server\Control\Server\Command; -use Innmind\TimeContinuum\Period; +use Innmind\Time\Period; use Innmind\Filesystem\File\Content; use Innmind\Url\Path; use Innmind\Immutable\Map; diff --git a/tests/Server/Process/BackgroundTest.php b/tests/Server/Process/BackgroundTest.php index afacacd..c31641b 100644 --- a/tests/Server/Process/BackgroundTest.php +++ b/tests/Server/Process/BackgroundTest.php @@ -9,11 +9,11 @@ Process\Success, Command, }; -use Innmind\TimeContinuum\{ +use Innmind\Time\{ Clock, Period, + Halt, }; -use Innmind\TimeWarp\Halt; use Innmind\IO\IO; use Innmind\Immutable\Monoid\Concat; use Innmind\BlackBox\PHPUnit\Framework\TestCase; diff --git a/tests/Server/Process/ForegroundTest.php b/tests/Server/Process/ForegroundTest.php index ad3a728..c52c979 100644 --- a/tests/Server/Process/ForegroundTest.php +++ b/tests/Server/Process/ForegroundTest.php @@ -11,11 +11,11 @@ Process\Success, Command, }; -use Innmind\TimeContinuum\{ +use Innmind\Time\{ Clock, Period, + Halt, }; -use Innmind\TimeWarp\Halt; use Innmind\IO\IO; use Innmind\Immutable\Monoid\Concat; use Innmind\BlackBox\PHPUnit\Framework\TestCase; diff --git a/tests/Server/Process/UnixTest.php b/tests/Server/Process/UnixTest.php index ee393df..d1aaf20 100644 --- a/tests/Server/Process/UnixTest.php +++ b/tests/Server/Process/UnixTest.php @@ -11,11 +11,11 @@ Server\Command, }; use Innmind\Filesystem\File\Content; -use Innmind\TimeContinuum\{ +use Innmind\Time\{ Clock, Period, + Halt, }; -use Innmind\TimeWarp\Halt; use Innmind\Url\Path; use Innmind\IO\IO; use Innmind\Immutable\{ diff --git a/tests/Server/Processes/LoggerTest.php b/tests/Server/Processes/LoggerTest.php index 1af7727..94e3426 100644 --- a/tests/Server/Processes/LoggerTest.php +++ b/tests/Server/Processes/LoggerTest.php @@ -10,8 +10,10 @@ Server\Signal, Server\Process\Pid, }; -use Innmind\TimeContinuum\Clock; -use Innmind\TimeWarp\Halt; +use Innmind\Time\{ + Clock, + Halt, +}; use Innmind\IO\IO; use Innmind\Url\Path; use Psr\Log\NullLogger; diff --git a/tests/Server/Processes/RemoteTest.php b/tests/Server/Processes/RemoteTest.php index 39b101e..0e7d865 100644 --- a/tests/Server/Processes/RemoteTest.php +++ b/tests/Server/Processes/RemoteTest.php @@ -10,8 +10,10 @@ Server\Signal, Server\Process\Pid, }; -use Innmind\TimeContinuum\Clock; -use Innmind\TimeWarp\Halt; +use Innmind\Time\{ + Clock, + Halt, +}; use Innmind\IO\IO; use Innmind\Url\{ Path, diff --git a/tests/Server/Processes/UnixTest.php b/tests/Server/Processes/UnixTest.php index a944edc..0210903 100644 --- a/tests/Server/Processes/UnixTest.php +++ b/tests/Server/Processes/UnixTest.php @@ -11,11 +11,11 @@ Server\Signal, }; use Innmind\Filesystem\File\Content; -use Innmind\TimeContinuum\{ +use Innmind\Time\{ Clock, Period, + Halt, }; -use Innmind\TimeWarp\Halt; use Innmind\IO\IO; use Innmind\Immutable\{ SideEffect, diff --git a/tests/Server/ScriptTest.php b/tests/Server/ScriptTest.php index 483c308..9add642 100644 --- a/tests/Server/ScriptTest.php +++ b/tests/Server/ScriptTest.php @@ -9,11 +9,11 @@ Server\Command, Exception\ProcessFailed, }; -use Innmind\TimeContinuum\{ +use Innmind\Time\{ Clock, Period, + Halt, }; -use Innmind\TimeWarp\Halt; use Innmind\IO\IO; use Innmind\Immutable\SideEffect; use Innmind\BlackBox\PHPUnit\Framework\TestCase; diff --git a/tests/Server/VolumesTest.php b/tests/Server/VolumesTest.php index 9d02769..30d586e 100644 --- a/tests/Server/VolumesTest.php +++ b/tests/Server/VolumesTest.php @@ -11,8 +11,10 @@ Server\Command, Exception\ProcessFailed, }; -use Innmind\TimeContinuum\Clock; -use Innmind\TimeWarp\Halt; +use Innmind\Time\{ + Clock, + Halt, +}; use Innmind\IO\IO; use Innmind\Url\Path; use Innmind\Immutable\SideEffect; diff --git a/tests/ServerFactoryTest.php b/tests/ServerFactoryTest.php index 5d0a9f5..d049e91 100644 --- a/tests/ServerFactoryTest.php +++ b/tests/ServerFactoryTest.php @@ -8,8 +8,10 @@ Server, Exception\UnsupportedOperatingSystem }; -use Innmind\TimeContinuum\Clock; -use Innmind\TimeWarp\Halt; +use Innmind\Time\{ + Clock, + Halt, +}; use Innmind\IO\IO; use Innmind\BlackBox\PHPUnit\Framework\TestCase; use PHPUnit\Framework\Attributes\Group; diff --git a/tests/Servers/LoggerTest.php b/tests/Servers/LoggerTest.php index 4d2aab5..e480c67 100644 --- a/tests/Servers/LoggerTest.php +++ b/tests/Servers/LoggerTest.php @@ -9,8 +9,10 @@ Server\Command, Server\Volumes, }; -use Innmind\TimeContinuum\Clock; -use Innmind\TimeWarp\Halt; +use Innmind\Time\{ + Clock, + Halt, +}; use Innmind\IO\IO; use Innmind\Immutable\SideEffect; use Psr\Log\NullLogger; diff --git a/tests/Servers/RemoteTest.php b/tests/Servers/RemoteTest.php index 22cac5b..5f06ccc 100644 --- a/tests/Servers/RemoteTest.php +++ b/tests/Servers/RemoteTest.php @@ -9,8 +9,10 @@ Server\Command, Server\Volumes, }; -use Innmind\TimeContinuum\Clock; -use Innmind\TimeWarp\Halt; +use Innmind\Time\{ + Clock, + Halt, +}; use Innmind\IO\IO; use Innmind\Url\Authority\{ Host, diff --git a/tests/Servers/UnixTest.php b/tests/Servers/UnixTest.php index 232f7dd..bc25be5 100644 --- a/tests/Servers/UnixTest.php +++ b/tests/Servers/UnixTest.php @@ -8,8 +8,10 @@ Server\Processes, Server\Volumes, }; -use Innmind\TimeContinuum\Clock; -use Innmind\TimeWarp\Halt; +use Innmind\Time\{ + Clock, + Halt, +}; use Innmind\IO\IO; use Innmind\BlackBox\PHPUnit\Framework\TestCase; use PHPUnit\Framework\Attributes\Group; From 009c0caf39aa3eb3b8ad8b12b649050fb83cf59d Mon Sep 17 00:00:00 2001 From: Baptiste Langlade Date: Sat, 31 Jan 2026 18:50:41 +0100 Subject: [PATCH 24/27] fix warnings --- src/Server/Process/Background.php | 2 +- src/Server/Process/Started.php | 2 +- tests/Server/Process/ForegroundTest.php | 4 ++-- tests/Server/Processes/UnixTest.php | 18 +++++++++--------- tests/Servers/LoggerTest.php | 4 ++-- tests/Servers/RemoteTest.php | 6 +++--- 6 files changed, 18 insertions(+), 18 deletions(-) diff --git a/src/Server/Process/Background.php b/src/Server/Process/Background.php index 4719f7c..4ea2a96 100644 --- a/src/Server/Process/Background.php +++ b/src/Server/Process/Background.php @@ -23,7 +23,7 @@ public function __construct(Started $process) // wait for the process to be started in the background otherwise the // process will be killed // this also allows to send any input to the stream - $process->output()->memoize(); + $_ = $process->output()->memoize(); $this->output = Sequence::of(); // the pid returned by `$process->pid()` is the one for the "foreground" diff --git a/src/Server/Process/Started.php b/src/Server/Process/Started.php index 87ed03e..e265744 100644 --- a/src/Server/Process/Started.php +++ b/src/Server/Process/Started.php @@ -330,7 +330,7 @@ private function checkTimeout(): Maybe private function abort(): string { @\proc_terminate($this->process); - ($this->halt)($this->grace); + $_ = ($this->halt)($this->grace)->memoize(); if ($this->status()['running']) { @\proc_terminate($this->process, Signal::kill->toInt()); diff --git a/tests/Server/Process/ForegroundTest.php b/tests/Server/Process/ForegroundTest.php index c52c979..a2d4dc7 100644 --- a/tests/Server/Process/ForegroundTest.php +++ b/tests/Server/Process/ForegroundTest.php @@ -79,7 +79,7 @@ public function testOutput() $start = \time(); $count = 0; - $process + $_ = $process ->output() ->foreach(function($chunk) use ($start, &$count) { $this->assertSame($count."\n", $chunk->data()->toString()); @@ -206,7 +206,7 @@ public function testExitStatusIsAvailableAfterIteratingOverTheOutput() ->withEnvironment('PATH', $_SERVER['PATH']), ); $process = Process::foreground($slow()); - $process->output()->memoize(); + $_ = $process->output()->memoize(); $this->assertInstanceOf( Success::class, diff --git a/tests/Server/Processes/UnixTest.php b/tests/Server/Processes/UnixTest.php index 0210903..808d4c3 100644 --- a/tests/Server/Processes/UnixTest.php +++ b/tests/Server/Processes/UnixTest.php @@ -43,7 +43,7 @@ public function testExecute() )->unwrap(); $this->assertInstanceOf(Process::class, $process); - $process->wait(); + $_ = $process->wait(); $this->assertTrue((\time() - $start) >= 6); } @@ -171,7 +171,7 @@ public function testStreamOutput() IO::fromAmbientAuthority(), Halt::new(), )->processes(); - $processes + $_ = $processes ->execute( Command::foreground('cat') ->withArgument('fixtures/symfony.log') @@ -203,11 +203,11 @@ public function testSecondCallToStreamedOutputThrowsAnError() ->streamOutput(), ) ->unwrap(); - $process->output()->foreach(static fn() => null); + $_ = $process->output()->foreach(static fn() => null); $this->expectException(\LogicException::class); - $process->output()->foreach(static fn() => null); + $_ = $process->output()->foreach(static fn() => null); } #[Group('ci')] @@ -226,8 +226,8 @@ public function testOutputIsNotLostByDefault() ->withArgument('fixtures/symfony.log'), ) ->unwrap(); - $process->output()->foreach(static fn() => null); - $process + $_ = $process->output()->foreach(static fn() => null); + $_ = $process ->output() ->foreach(static function() use (&$called) { $called = true; @@ -247,12 +247,12 @@ public function testStopProcessEvenWhenPipesAreStillOpenAfterTheProcessBeingKill IO::fromAmbientAuthority(), Halt::new(), )->processes(); - $tail = $processes->execute( + $_ = $tail = $processes->execute( Command::foreground('tail') ->withShortOption('f') ->withArgument('/tmp/test-file'), )->unwrap(); - $processes->execute( + $_ = $processes->execute( Command::background('sleep 2 && kill') ->withArgument($tail->pid()->match( static fn($pid) => $pid->toString(), @@ -260,7 +260,7 @@ public function testStopProcessEvenWhenPipesAreStillOpenAfterTheProcessBeingKill )), )->unwrap(); - $tail->output()->foreach(static fn() => null); + $_ = $tail->output()->foreach(static fn() => null); // when done correctly then the foreach above would run forever $this->assertTrue(true); } diff --git a/tests/Servers/LoggerTest.php b/tests/Servers/LoggerTest.php index e480c67..1e7dbe0 100644 --- a/tests/Servers/LoggerTest.php +++ b/tests/Servers/LoggerTest.php @@ -36,7 +36,7 @@ public function testProcesses() Processes::class, $logger->processes(), ); - $logger->processes()->execute(Command::foreground('ls')); + $_ = $logger->processes()->execute(Command::foreground('ls')); } #[Group('ci')] @@ -54,7 +54,7 @@ public function testVolumes() Volumes::class, $logger->volumes(), ); - $logger->volumes()->unmount(Volumes\Name::of('/dev')); + $_ = $logger->volumes()->unmount(Volumes\Name::of('/dev')); } #[Group('ci')] diff --git a/tests/Servers/RemoteTest.php b/tests/Servers/RemoteTest.php index 5f06ccc..c69e6dd 100644 --- a/tests/Servers/RemoteTest.php +++ b/tests/Servers/RemoteTest.php @@ -41,7 +41,7 @@ public function testProcesses() Processes::class, $remote->processes(), ); - $remote->processes()->execute(Command::foreground('ls')); + $_ = $remote->processes()->execute(Command::foreground('ls')); } #[Group('ci')] @@ -61,7 +61,7 @@ public function testProcessesViaSpecificPort() Processes::class, $remote->processes(), ); - $remote->processes()->execute(Command::foreground('ls')); + $_ = $remote->processes()->execute(Command::foreground('ls')); } #[Group('ci')] @@ -83,7 +83,7 @@ public function testVolumes() Volumes::class, $remote->volumes(), ); - $remote->volumes()->unmount(Volumes\Name::of('/dev')); + $_ = $remote->volumes()->unmount(Volumes\Name::of('/dev')); } #[Group('ci')] From 94f14f4ee0ab7bae4fdcdcadf7279e7065a18945 Mon Sep 17 00:00:00 2001 From: Baptiste Langlade Date: Sat, 31 Jan 2026 18:52:24 +0100 Subject: [PATCH 25/27] add extensive CI --- .github/workflows/extensive.yml | 12 ++++++++++++ blackbox.php | 4 ++++ 2 files changed, 16 insertions(+) create mode 100644 .github/workflows/extensive.yml diff --git a/.github/workflows/extensive.yml b/.github/workflows/extensive.yml new file mode 100644 index 0000000..257f139 --- /dev/null +++ b/.github/workflows/extensive.yml @@ -0,0 +1,12 @@ +name: Extensive CI + +on: + push: + tags: + - '*' + paths: + - '.github/workflows/extensive.yml' + +jobs: + blackbox: + uses: innmind/github-workflows/.github/workflows/extensive.yml@main diff --git a/blackbox.php b/blackbox.php index 50022e2..67a2468 100644 --- a/blackbox.php +++ b/blackbox.php @@ -10,6 +10,10 @@ }; Application::new($argv) + ->when( + \getenv('BLACKBOX_SET_SIZE') !== false, + static fn(Application $app) => $app->scenariiPerProof((int) \getenv('BLACKBOX_SET_SIZE')), + ) ->when( \getenv('ENABLE_COVERAGE') !== false, static fn(Application $app) => $app From 913de65dbef3b91f2f7aa5c6825a3640af177ebe Mon Sep 17 00:00:00 2001 From: Baptiste Langlade Date: Sat, 31 Jan 2026 18:59:14 +0100 Subject: [PATCH 26/27] CS --- src/Run/Implementation.php | 6 +++--- src/Run/Logger.php | 4 +--- src/Run/Unix.php | 6 +++--- src/Server.php | 12 ++++++------ src/Server/Process/Started.php | 8 ++++---- tests/Server/Process/UnixTest.php | 12 ++++++------ tests/Server/Processes/RemoteTest.php | 2 +- tests/ServerFactoryTest.php | 2 +- tests/Servers/RemoteTest.php | 2 +- 9 files changed, 26 insertions(+), 28 deletions(-) diff --git a/src/Run/Implementation.php b/src/Run/Implementation.php index 5d05e1d..f5111fd 100644 --- a/src/Run/Implementation.php +++ b/src/Run/Implementation.php @@ -3,9 +3,9 @@ namespace Innmind\Server\Control\Run; -use Innmind\Server\Control\{ - Server\Command, - Server\Process, +use Innmind\Server\Control\Server\{ + Command, + Process, }; use Innmind\Immutable\Attempt; diff --git a/src/Run/Logger.php b/src/Run/Logger.php index 1ea098a..d2ef634 100644 --- a/src/Run/Logger.php +++ b/src/Run/Logger.php @@ -3,9 +3,7 @@ namespace Innmind\Server\Control\Run; -use Innmind\Server\Control\Server\{ - Command, -}; +use Innmind\Server\Control\Server\Command; use Innmind\Immutable\Attempt; use Psr\Log\LoggerInterface; diff --git a/src/Run/Unix.php b/src/Run/Unix.php index bcc1849..0168eb2 100644 --- a/src/Run/Unix.php +++ b/src/Run/Unix.php @@ -3,9 +3,9 @@ namespace Innmind\Server\Control\Run; -use Innmind\Server\Control\{ - Server\Command, - Server\Process, +use Innmind\Server\Control\Server\{ + Command, + Process, }; use Innmind\Time\{ Clock, diff --git a/src/Server.php b/src/Server.php index 95019e3..8a05678 100644 --- a/src/Server.php +++ b/src/Server.php @@ -3,12 +3,12 @@ namespace Innmind\Server\Control; -use Innmind\Server\Control\{ - Server\Processes, - Server\Volumes, - Server\Process, - Server\Script, - Server\Command, +use Innmind\Server\Control\Server\{ + Processes, + Volumes, + Process, + Script, + Command, }; use Innmind\IO\IO; use Innmind\Url\Authority\{ diff --git a/src/Server/Process/Started.php b/src/Server/Process/Started.php index e265744..1e1cb5f 100644 --- a/src/Server/Process/Started.php +++ b/src/Server/Process/Started.php @@ -3,10 +3,10 @@ namespace Innmind\Server\Control\Server\Process; -use Innmind\Server\Control\{ - Server\Process\Output\Chunk, - Server\Process\Output\Type, - Server\Signal, +use Innmind\Server\Control\Server\{ + Process\Output\Chunk, + Process\Output\Type, + Signal, }; use Innmind\Filesystem\File\Content; use Innmind\Time\{ diff --git a/tests/Server/Process/UnixTest.php b/tests/Server/Process/UnixTest.php index d1aaf20..5d772d0 100644 --- a/tests/Server/Process/UnixTest.php +++ b/tests/Server/Process/UnixTest.php @@ -3,12 +3,12 @@ namespace Tests\Innmind\Server\Control\Server\Process; -use Innmind\Server\Control\{ - Server\Process\Unix, - Server\Process\Output\Chunk, - Server\Process\Output\Type, - Server\Process\ExitCode, - Server\Command, +use Innmind\Server\Control\Server\{ + Process\Unix, + Process\Output\Chunk, + Process\Output\Type, + Process\ExitCode, + Command, }; use Innmind\Filesystem\File\Content; use Innmind\Time\{ diff --git a/tests/Server/Processes/RemoteTest.php b/tests/Server/Processes/RemoteTest.php index 0e7d865..68512f1 100644 --- a/tests/Server/Processes/RemoteTest.php +++ b/tests/Server/Processes/RemoteTest.php @@ -19,7 +19,7 @@ Path, Authority\Host, Authority\Port, - Authority\UserInformation\User + Authority\UserInformation\User, }; use Innmind\Immutable\SideEffect; use Innmind\BlackBox\PHPUnit\Framework\TestCase; diff --git a/tests/ServerFactoryTest.php b/tests/ServerFactoryTest.php index d049e91..d652530 100644 --- a/tests/ServerFactoryTest.php +++ b/tests/ServerFactoryTest.php @@ -6,7 +6,7 @@ use Innmind\Server\Control\{ ServerFactory, Server, - Exception\UnsupportedOperatingSystem + Exception\UnsupportedOperatingSystem, }; use Innmind\Time\{ Clock, diff --git a/tests/Servers/RemoteTest.php b/tests/Servers/RemoteTest.php index c69e6dd..1463b8c 100644 --- a/tests/Servers/RemoteTest.php +++ b/tests/Servers/RemoteTest.php @@ -17,7 +17,7 @@ use Innmind\Url\Authority\{ Host, Port, - UserInformation\User + UserInformation\User, }; use Innmind\Immutable\SideEffect; use Innmind\BlackBox\PHPUnit\Framework\TestCase; From 83bb281ade4b56e710c891de0a49ccb9f539bf41 Mon Sep 17 00:00:00 2001 From: Baptiste Langlade Date: Sat, 31 Jan 2026 19:15:53 +0100 Subject: [PATCH 27/27] avoid force unwrapping of Eithers --- src/Server/Processes.php | 18 ++++++++++-------- src/Server/Volumes.php | 18 ++++++++++-------- 2 files changed, 20 insertions(+), 16 deletions(-) diff --git a/src/Server/Processes.php b/src/Server/Processes.php index dc924c6..c3a8396 100644 --- a/src/Server/Processes.php +++ b/src/Server/Processes.php @@ -49,13 +49,15 @@ public function kill(Pid $pid, Signal $signal): Attempt ->withShortOption($signal->toString()) ->withArgument($pid->toString()), ) - ->flatMap(static fn($process) => $process->wait()->match( - static fn() => Attempt::result(SideEffect::identity()), - static fn($e) => Attempt::error(new ProcessFailed( - $command, - $process, - $e, - )), - )); + ->flatMap( + static fn($process) => $process + ->wait() + ->attempt(static fn($e) => new ProcessFailed( + $command, + $process, + $e, + )) + ->map(SideEffect::identity(...)), + ); } } diff --git a/src/Server/Volumes.php b/src/Server/Volumes.php index 8883c99..34627a8 100644 --- a/src/Server/Volumes.php +++ b/src/Server/Volumes.php @@ -77,14 +77,16 @@ private function run(Command $command): Attempt return $this ->processes ->execute($command) - ->flatMap(static fn($process) => $process->wait()->match( - static fn() => Attempt::result(SideEffect::identity()), - static fn($e) => Attempt::error(new ProcessFailed( - $command, - $process, - $e, - )), - )); + ->flatMap( + static fn($process) => $process + ->wait() + ->attempt(static fn($e) => new ProcessFailed( + $command, + $process, + $e, + )) + ->map(SideEffect::identity(...)), + ); } private function isOSX(): bool