diff --git a/.github/workflows/ci.yml b/.github/workflows/ci.yml index 3d89ea2..6520bd7 100644 --- a/.github/workflows/ci.yml +++ b/.github/workflows/ci.yml @@ -16,5 +16,3 @@ jobs: uses: innmind/github-workflows/.github/workflows/psalm-matrix.yml@main cs: uses: innmind/github-workflows/.github/workflows/cs.yml@main - with: - php-version: '8.2' 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/CHANGELOG.md b/CHANGELOG.md index 0a41e43..f958d74 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -1,5 +1,15 @@ # Changelog +## [Unreleased] + +### Changed + +- 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 +- Requires `innmind/time:~1.0` + ## 6.1.0 - 2025-08-06 ### Added 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 diff --git a/composer.json b/composer.json index 15284f3..f479492 100644 --- a/composer.json +++ b/composer.json @@ -14,14 +14,13 @@ "issues": "http://github.com/Innmind/ServerControl/issues" }, "require": { - "php": "~8.2", - "innmind/immutable": "~5.12", - "innmind/url": "~4.0", + "php": "~8.4", + "innmind/immutable": "~6.0", + "innmind/url": "~5.0", "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": "~1.0", + "innmind/filesystem": "~9.0", + "innmind/io": "~4.0" }, "autoload": { "psr-4": { @@ -34,7 +33,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/Run/Implementation.php b/src/Run/Implementation.php new file mode 100644 index 0000000..f5111fd --- /dev/null +++ b/src/Run/Implementation.php @@ -0,0 +1,21 @@ + + */ + public function __invoke(Command|Command\OverSsh $command): Attempt; +} diff --git a/src/Run/Logger.php b/src/Run/Logger.php new file mode 100644 index 0000000..d2ef634 --- /dev/null +++ b/src/Run/Logger.php @@ -0,0 +1,48 @@ +normalize(); + } + + $this->logger->info('About to execute the {command}', [ + 'command' => $toLog->toString(), + 'workingDirectory' => $toLog->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/Run/Remote.php b/src/Run/Remote.php new file mode 100644 index 0000000..7488556 --- /dev/null +++ b/src/Run/Remote.php @@ -0,0 +1,54 @@ +run)(Command\OverSsh::of( + $this->user, + $this->host, + $this->port, + $command, + )); + } + + /** + * @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/Run/Unix.php b/src/Run/Unix.php new file mode 100644 index 0000000..0168eb2 --- /dev/null +++ b/src/Run/Unix.php @@ -0,0 +1,71 @@ +normalize(); + } + + return Attempt::of(function() use ($command) { + $process = new Process\Unix( + $this->clock, + $this->io, + $this->halt, + $this->grace, + $command, + ); + + if ($command->toBeRunInBackground()) { + return Process::background($process()); + } + + return Process::foreground($process(), $command->outputToBeStreamed()); + }); + } + + /** + * @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/Run/Via.php b/src/Run/Via.php new file mode 100644 index 0000000..5323b3f --- /dev/null +++ b/src/Run/Via.php @@ -0,0 +1,40 @@ + $run + */ + private function __construct( + private \Closure $run, + ) { + } + + #[\Override] + public function __invoke(Command|Command\OverSsh $command): Attempt + { + return ($this->run)($command); + } + + /** + * @internal + * + * @param callable(Command|Command\OverSsh): 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 5cf699b..8a05678 100644 --- a/src/Server.php +++ b/src/Server.php @@ -3,32 +3,113 @@ namespace Innmind\Server\Control; -use Innmind\Server\Control\{ - Server\Processes, - Server\Volumes, +use Innmind\Server\Control\Server\{ + Processes, + Volumes, + Process, + Script, + Command, +}; +use Innmind\IO\IO; +use Innmind\Url\Authority\{ + Host, + Port, + UserInformation\User, +}; +use Innmind\Time\{ + Clock, + Period, + Halt, }; use Innmind\Immutable\{ Attempt, SideEffect, }; +use Psr\Log\LoggerInterface; -interface Server +final class Server { + private function __construct( + private Run\Implementation $run, + ) { + } + + /** + * @internal Use the factory instead + */ + public static function new( + Clock $clock, + IO $io, + Halt $halt, + ?Period $grace = null, + ): self { + return new self(Run\Unix::of( + $clock, + $io, + $halt, + $grace, + )); + } + + public static function remote( + self $server, + User $user, + Host $host, + ?Port $port = null, + ): self { + return new self(Run\Remote::of( + $server->run, + $user, + $host, + $port, + )); + } + + public static function logger(self $server, LoggerInterface $logger): self + { + return new self(Run\Logger::psr( + $server->run, + $logger, + )); + } + + /** + * @internal + * + * @param callable(Command|Command\OverSsh): Attempt $run + */ + public static function via(callable $run): self + { + return new self(Run\Via::of($run)); + } + #[\NoDiscard] - public function processes(): Processes; + public function processes(): Processes + { + return Processes::of($this->run); + } #[\NoDiscard] - public function volumes(): Volumes; + public function volumes(): Volumes + { + return Volumes::of($this->processes()); + } /** * @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/Server/Command.php b/src/Server/Command.php index 40513c6..d2d31ce 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\Time\Period; use Innmind\Filesystem\File\Content; use Innmind\Url\Path; use Innmind\Immutable\{ - Sequence, Map, - Str, Maybe, }; @@ -25,42 +21,9 @@ */ 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 - */ - 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 Implementation $implementation, + ) { } /** @@ -77,7 +40,7 @@ private function __construct(bool $background, string $executable) #[\NoDiscard] public static function background(string $executable): self { - return new self(true, $executable); + return new self(Definition::background($executable)); } /** @@ -91,16 +54,15 @@ public static function background(string $executable): self #[\NoDiscard] public static function foreground(string $executable): self { - return new self(false, $executable); + return new self(Definition::foreground($executable)); } #[\NoDiscard] public function withArgument(string $value): self { - $self = clone $this; - $self->parameters = ($this->parameters)(new Argument($value)); - - return $self; + return new self( + $this->implementation->withArgument($value), + ); } /** @@ -109,10 +71,9 @@ 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->implementation->withOption($key, $value), + ); } /** @@ -121,10 +82,9 @@ 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->implementation->withShortOption($key, $value), + ); } /** @@ -133,10 +93,9 @@ 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->implementation->withEnvironment($key, $value), + ); } /** @@ -145,75 +104,64 @@ 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( + $values->reduce( + $this->implementation, + static fn(Implementation $self, $key, $value) => $self->withEnvironment( + $key, + $value, + ), + ), + ); } #[\NoDiscard] public function withWorkingDirectory(Path $path): self { - $self = clone $this; - $self->workingDirectory = Maybe::just($path); - - return $self; + return new self( + $this->implementation->withWorkingDirectory($path), + ); } #[\NoDiscard] public function withInput(Content $input): self { - $self = clone $this; - $self->input = Maybe::just($input); - - return $self; + return new self( + $this->implementation->withInput($input), + ); } #[\NoDiscard] public function overwrite(Path $path): self { - $self = clone $this; - $self->redirection = Maybe::just(new Overwrite($path)); - - return $self; + return new self( + $this->implementation->overwrite($path), + ); } #[\NoDiscard] public function append(Path $path): self { - $self = clone $this; - $self->redirection = Maybe::just(new Append($path)); - - return $self; + return new self( + $this->implementation->append($path), + ); } #[\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(Pipe::of( + $this->implementation, + $command->implementation, + )); } #[\NoDiscard] public function timeoutAfter(Period $timeout): self { - $self = clone $this; - $self->timeout = Maybe::just($timeout); - - return $self; + return new self( + $this->implementation->timeoutAfter($timeout), + ); } /** @@ -229,10 +177,9 @@ public function timeoutAfter(Period $timeout): self #[\NoDiscard] public function streamOutput(): self { - $self = clone $this; - $self->streamOutput = true; - - return $self; + return new self( + $this->implementation->streamOutput(), + ); } /** @@ -242,7 +189,7 @@ public function streamOutput(): self */ public function environment(): Map { - return $this->environment; + return $this->implementation->environment(); } /** @@ -252,7 +199,7 @@ public function environment(): Map */ public function workingDirectory(): Maybe { - return $this->workingDirectory; + return $this->implementation->workingDirectory(); } /** @@ -262,7 +209,7 @@ public function workingDirectory(): Maybe */ public function input(): Maybe { - return $this->input; + return $this->implementation->input(); } /** @@ -270,7 +217,7 @@ public function input(): Maybe */ public function toBeRunInBackground(): bool { - return $this->background; + return $this->implementation->toBeRunInBackground(); } /** @@ -280,7 +227,7 @@ public function toBeRunInBackground(): bool */ public function timeout(): Maybe { - return $this->timeout; + return $this->implementation->timeout(); } /** @@ -288,7 +235,7 @@ public function timeout(): Maybe */ public function outputToBeStreamed(): bool { - return $this->streamOutput; + return $this->implementation->outputToBeStreamed(); } /** @@ -298,23 +245,16 @@ 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->implementation->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, - ); + /** + * This method is only to be used by innmind/testing + * + * @internal + */ + public function internal(): Implementation + { + return $this->implementation; } } diff --git a/src/Server/Command/Append.php b/src/Server/Command/Append.php deleted file mode 100644 index 188de75..0000000 --- a/src/Server/Command/Append.php +++ /dev/null @@ -1,26 +0,0 @@ -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..8468887 100644 --- a/src/Server/Command/Argument.php +++ b/src/Server/Command/Argument.php @@ -7,18 +7,19 @@ * @psalm-immutable * @internal */ -final class Argument implements Parameter +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; } - #[\Override] public function toString(): string { - return $this->value; + return Str::escape($this->value); } } diff --git a/src/Server/Command/Definition.php b/src/Server/Command/Definition.php new file mode 100644 index 0000000..3b2823c --- /dev/null +++ b/src/Server/Command/Definition.php @@ -0,0 +1,356 @@ + $parameters + * @param Map $environment + * @param Maybe $workingDirectory + * @param Maybe $input + * @param 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 */ + $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 */ + $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(Redirection::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(Redirection::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, + ); + } + + /** + * @return non-empty-string + */ + #[\NoDiscard] + public function executable(): string + { + return $this->executable; + } + + /** + * @return Sequence + */ + #[\NoDiscard] + public function parameters(): Sequence + { + return $this->parameters; + } + + /** + * @return Maybe + */ + #[\NoDiscard] + public function redirection(): Maybe + { + return $this->redirection; + } + + #[\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 + { + /** + * @psalm-suppress InvalidArgument Due to append + * @var non-empty-string + */ + return $this + ->parameters + ->append($this->redirection->toSequence()) + ->map(static fn($parameter) => ' '.$parameter->toString()) + ->map(Str::of(...)) + ->fold(Concat::monoid) + ->prepend($this->executable) + ->toString(); + } +} diff --git a/src/Server/Command/Implementation.php b/src/Server/Command/Implementation.php new file mode 100644 index 0000000..159751e --- /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/Option.php b/src/Server/Command/Option.php index 34f01d5..d91408b 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 @@ -15,7 +15,7 @@ final class Option implements Parameter private function __construct( private bool $long, private string $key, - private ?string $value = null, + private ?string $value, ) { } @@ -37,7 +37,16 @@ public static function short(string $key, ?string $value = null): self return new self(false, $key, $value); } - #[\Override] + public function key(): string + { + return $this->key; + } + + public function value(): ?string + { + return $this->value; + } + public function toString(): string { if ($this->long) { @@ -55,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/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(); + } +} diff --git a/src/Server/Command/Overwrite.php b/src/Server/Command/Overwrite.php deleted file mode 100644 index 7366cef..0000000 --- a/src/Server/Command/Overwrite.php +++ /dev/null @@ -1,26 +0,0 @@ -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 @@ -a; + } + + #[\NoDiscard] + public function b(): Implementation + { + return $this->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/src/Server/Command/Redirection.php b/src/Server/Command/Redirection.php new file mode 100644 index 0000000..06091c6 --- /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 => '>', + }).' '.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/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/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)); } /** diff --git a/src/Server/Process/Started.php b/src/Server/Process/Started.php index 5933876..1e1cb5f 100644 --- a/src/Server/Process/Started.php +++ b/src/Server/Process/Started.php @@ -3,18 +3,18 @@ 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\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; @@ -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); } @@ -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/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/Server/Processes.php b/src/Server/Processes.php index cb7611d..c3a8396 100644 --- a/src/Server/Processes.php +++ b/src/Server/Processes.php @@ -3,23 +3,61 @@ 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() + ->attempt(static fn($e) => new ProcessFailed( + $command, + $process, + $e, + )) + ->map(SideEffect::identity(...)), + ); + } } 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/Server/Processes/Remote.php b/src/Server/Processes/Remote.php deleted file mode 100644 index ca09acc..0000000 --- a/src/Server/Processes/Remote.php +++ /dev/null @@ -1,84 +0,0 @@ -processes = $processes; - $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 execute(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 - ->processes - ->execute( - $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()); - } -} diff --git a/src/Server/Processes/Unix.php b/src/Server/Processes/Unix.php deleted file mode 100644 index 986f7b6..0000000 --- a/src/Server/Processes/Unix.php +++ /dev/null @@ -1,90 +0,0 @@ -clock, - $this->io, - $this->halt, - $this->grace, - $command, - ); - - if ($command->toBeRunInBackground()) { - return Process::background($process()); - } - - return Process::foreground($process(), $command->outputToBeStreamed()); - }); - } - - #[\Override] - 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/Volumes.php b/src/Server/Volumes.php index 557dbc9..34627a8 100644 --- a/src/Server/Volumes.php +++ b/src/Server/Volumes.php @@ -3,24 +3,99 @@ 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() + ->attempt(static fn($e) => new ProcessFailed( + $command, + $process, + $e, + )) + ->map(SideEffect::identity(...)), + ); + } + + 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/ServerFactory.php b/src/ServerFactory.php index 6431c76..f5cb2fb 100644 --- a/src/ServerFactory.php +++ b/src/ServerFactory.php @@ -3,15 +3,12 @@ namespace Innmind\Server\Control; -use Innmind\Server\Control\{ - Servers\Unix, - Exception\UnsupportedOperatingSystem, -}; -use Innmind\TimeContinuum\{ +use Innmind\Server\Control\Exception\UnsupportedOperatingSystem; +use Innmind\Time\{ Clock, Period, + Halt, }; -use Innmind\TimeWarp\Halt; use Innmind\IO\IO; final class ServerFactory @@ -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/Logger.php b/src/Servers/Logger.php deleted file mode 100644 index 3ecb339..0000000 --- a/src/Servers/Logger.php +++ /dev/null @@ -1,59 +0,0 @@ -processes = Processes\Logger::psr( - $server->processes(), - $logger, - ); - $this->volumes = new Volumes\Unix($this->processes); - } - - public static function psr(Server $server, LoggerInterface $logger): self - { - return new self($server, $logger); - } - - #[\Override] - public function processes(): Processes - { - return $this->processes; - } - - #[\Override] - 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/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/src/Servers/Remote.php b/src/Servers/Remote.php deleted file mode 100644 index f004a07..0000000 --- a/src/Servers/Remote.php +++ /dev/null @@ -1,71 +0,0 @@ -processes = new Processes\Remote( - $server->processes(), - $user, - $host, - $port, - ); - $this->volumes = new Volumes\Unix($this->processes); - } - - public static function of( - Server $server, - User $user, - Host $host, - ?Port $port = null, - ): self { - return new self($server, $user, $host, $port); - } - - #[\Override] - public function processes(): Processes - { - return $this->processes; - } - - #[\Override] - 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 deleted file mode 100644 index 973fdb3..0000000 --- a/src/Servers/Unix.php +++ /dev/null @@ -1,75 +0,0 @@ -processes = Processes\Unix::of( - $clock, - $io, - $halt, - $grace, - ); - $this->volumes = new Volumes\Unix($this->processes); - } - - /** - * @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; - } - - #[\Override] - 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/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()); } 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/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 diff --git a/tests/Server/CommandTest.php b/tests/Server/CommandTest.php index fd4bc19..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; @@ -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, @@ -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(), ); } diff --git a/tests/Server/Process/BackgroundTest.php b/tests/Server/Process/BackgroundTest.php index cb997d3..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\Usleep; 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'), ); @@ -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); @@ -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..a2d4dc7 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\Usleep; 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']), @@ -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()); @@ -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); @@ -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,13 +200,13 @@ 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']), ); $process = Process::foreground($slow()); - $process->output()->memoize(); + $_ = $process->output()->memoize(); $this->assertInstanceOf( Success::class, @@ -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']), @@ -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/Process/UnixTest.php b/tests/Server/Process/UnixTest.php index 0506d7b..5d772d0 100644 --- a/tests/Server/Process/UnixTest.php +++ b/tests/Server/Process/UnixTest.php @@ -3,19 +3,19 @@ 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\TimeContinuum\{ +use Innmind\Time\{ Clock, Period, + Halt, }; -use Innmind\TimeWarp\Halt\Usleep; 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'), ); @@ -59,19 +59,19 @@ 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(), - 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..94e3426 100644 --- a/tests/Server/Processes/LoggerTest.php +++ b/tests/Server/Processes/LoggerTest.php @@ -3,17 +3,17 @@ 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\Time\{ + Clock, + Halt, }; -use Innmind\TimeContinuum\Clock; -use Innmind\TimeWarp\Halt\Usleep; use Innmind\IO\IO; use Innmind\Url\Path; use Psr\Log\NullLogger; @@ -22,27 +22,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 +43,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,20 +62,20 @@ 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(), - Usleep::new(), + Halt::new(), ); } } diff --git a/tests/Server/Processes/RemoteTest.php b/tests/Server/Processes/RemoteTest.php index a04bd1a..68512f1 100644 --- a/tests/Server/Processes/RemoteTest.php +++ b/tests/Server/Processes/RemoteTest.php @@ -3,55 +3,39 @@ 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\Time\{ + Clock, + Halt, }; -use Innmind\TimeContinuum\Clock; -use Innmind\TimeWarp\Halt\Usleep; use Innmind\IO\IO; use Innmind\Url\{ Path, Authority\Host, Authority\Port, - Authority\UserInformation\User -}; -use Innmind\Immutable\{ - Attempt, - SideEffect, + Authority\UserInformation\User, }; +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 +49,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 +68,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 +88,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 +107,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 +123,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(), - Usleep::new(), - ); - - 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); - $this->test->assertSame( + Halt::new(), + )->processes(); + + 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 37d9db9..808d4c3 100644 --- a/tests/Server/Processes/UnixTest.php +++ b/tests/Server/Processes/UnixTest.php @@ -4,19 +4,18 @@ namespace Tests\Innmind\Server\Control\Server\Processes; use Innmind\Server\Control\{ - Server\Processes\Unix, - Server\Processes, + Server, Server\Command, Server\Process, Server\Process\TimedOut, Server\Signal, }; use Innmind\Filesystem\File\Content; -use Innmind\TimeContinuum\{ +use Innmind\Time\{ Clock, Period, + Halt, }; -use Innmind\TimeWarp\Halt\Usleep; use Innmind\IO\IO; use Innmind\Immutable\{ SideEffect, @@ -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(), - Usleep::new(), - )); - } - #[Group('ci')] #[Group('local')] public function testExecute() { - $processes = Unix::of( + $processes = Server::new( Clock::live(), IO::fromAmbientAuthority(), - Usleep::new(), - ); + Halt::new(), + )->processes(); $start = \time(); $process = $processes->execute( Command::foreground('php') @@ -55,7 +43,7 @@ public function testExecute() )->unwrap(); $this->assertInstanceOf(Process::class, $process); - $process->wait(); + $_ = $process->wait(); $this->assertTrue((\time() - $start) >= 6); } @@ -63,11 +51,11 @@ public function testExecute() #[Group('local')] public function testExecuteInBackground() { - $processes = Unix::of( + $processes = Server::new( Clock::live(), IO::fromAmbientAuthority(), - Usleep::new(), - ); + 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(), - Usleep::new(), - ); + Halt::new(), + )->processes(); $process = $processes->execute( Command::foreground('cat')->withInput(Content::oneShot( IO::fromAmbientAuthority() @@ -103,7 +91,7 @@ public function testExecuteWithInput() $process ->output() ->map(static fn($chunk) => $chunk->data()) - ->fold(new Concat) + ->fold(Concat::monoid) ->toString(), ); } @@ -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(), - Usleep::new(), - ); + 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(), - Usleep::new(), - ); + Halt::new(), + )->processes(); $start = \time(); $process = $processes->execute( Command::foreground('sleep') @@ -178,12 +166,12 @@ public function testTimeout() public function testStreamOutput() { $called = false; - $processes = Unix::of( + $processes = Server::new( Clock::live(), IO::fromAmbientAuthority(), - Usleep::new(), - ); - $processes + Halt::new(), + )->processes(); + $_ = $processes ->execute( Command::foreground('cat') ->withArgument('fixtures/symfony.log') @@ -203,11 +191,11 @@ public function testStreamOutput() public function testSecondCallToStreamedOutputThrowsAnError() { $called = false; - $processes = Unix::of( + $processes = Server::new( Clock::live(), IO::fromAmbientAuthority(), - Usleep::new(), - ); + Halt::new(), + )->processes(); $process = $processes ->execute( Command::foreground('cat') @@ -215,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')] @@ -227,19 +215,19 @@ public function testSecondCallToStreamedOutputThrowsAnError() public function testOutputIsNotLostByDefault() { $called = false; - $processes = Unix::of( + $processes = Server::new( Clock::live(), IO::fromAmbientAuthority(), - Usleep::new(), - ); + Halt::new(), + )->processes(); $process = $processes ->execute( Command::foreground('cat') ->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; @@ -254,17 +242,17 @@ public function testStopProcessEvenWhenPipesAreStillOpenAfterTheProcessBeingKill { @\unlink('/tmp/test-file'); \touch('/tmp/test-file'); - $processes = Unix::of( + $processes = Server::new( Clock::live(), IO::fromAmbientAuthority(), - Usleep::new(), - ); - $tail = $processes->execute( + Halt::new(), + )->processes(); + $_ = $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(), @@ -272,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); } @@ -281,11 +269,11 @@ public function testStopProcessEvenWhenPipesAreStillOpenAfterTheProcessBeingKill #[Group('local')] public function testRegressionWhenProcessFinishesTooFastItsFlaggedAsFailingEvenThoughItSucceeded() { - $processes = Unix::of( + $processes = Server::new( Clock::live(), IO::fromAmbientAuthority(), - Usleep::new(), - ); + Halt::new(), + )->processes(); $this->assertTrue( $processes diff --git a/tests/Server/ScriptTest.php b/tests/Server/ScriptTest.php index 466a639..9add642 100644 --- a/tests/Server/ScriptTest.php +++ b/tests/Server/ScriptTest.php @@ -4,16 +4,16 @@ namespace Tests\Innmind\Server\Control\Server; use Innmind\Server\Control\{ + Server, Server\Script, Server\Command, - Servers\Unix, Exception\ProcessFailed, }; -use Innmind\TimeContinuum\{ +use Innmind\Time\{ Clock, Period, + Halt, }; -use Innmind\TimeWarp\Halt\Usleep; use Innmind\IO\IO; use Innmind\Immutable\SideEffect; use Innmind\BlackBox\PHPUnit\Framework\TestCase; @@ -74,12 +74,12 @@ 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(), - Usleep::new(), + Halt::new(), ); } } 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()); }); } diff --git a/tests/Server/Volumes/UnixTest.php b/tests/Server/VolumesTest.php similarity index 76% rename from tests/Server/Volumes/UnixTest.php rename to tests/Server/VolumesTest.php index 57c6c08..30d586e 100644 --- a/tests/Server/Volumes/UnixTest.php +++ b/tests/Server/VolumesTest.php @@ -1,48 +1,33 @@ 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 +50,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 +73,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 +93,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 +113,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 +136,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 +159,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 +179,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 +197,27 @@ public function testReturnErrorWhenFailToUnmountLinuxVolume() private function processes(array ...$commands): Processes { - $processes = Processes\Unix::of( + $processes = Server::new( Clock::live(), IO::fromAmbientAuthority(), - Usleep::new(), - ); + 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/ServerFactoryTest.php b/tests/ServerFactoryTest.php index d0aa03f..d652530 100644 --- a/tests/ServerFactoryTest.php +++ b/tests/ServerFactoryTest.php @@ -6,10 +6,12 @@ use Innmind\Server\Control\{ ServerFactory, Server, - Exception\UnsupportedOperatingSystem + Exception\UnsupportedOperatingSystem, +}; +use Innmind\Time\{ + Clock, + Halt, }; -use Innmind\TimeContinuum\Clock; -use Innmind\TimeWarp\Halt\Usleep; use Innmind\IO\IO; use Innmind\BlackBox\PHPUnit\Framework\TestCase; use PHPUnit\Framework\Attributes\Group; @@ -29,7 +31,7 @@ public function testMakeUnix() $this->assertInstanceOf(Server::class, ServerFactory::build( Clock::live(), IO::fromAmbientAuthority(), - Usleep::new(), + Halt::new(), )); } @@ -48,7 +50,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..1e7dbe0 100644 --- a/tests/Servers/LoggerTest.php +++ b/tests/Servers/LoggerTest.php @@ -4,57 +4,39 @@ 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\Usleep; -use Innmind\IO\IO; -use Innmind\Immutable\{ - Attempt, - SideEffect, +use Innmind\Time\{ + Clock, + Halt, }; +use Innmind\IO\IO; +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')); + $_ = $logger->processes()->execute(Command::foreground('ls')); } #[Group('ci')] @@ -63,7 +45,7 @@ public function testVolumes() { $server = $this->server('which diskutil', "diskutil 'unmount' '/dev'"); - $logger = Logger::psr( + $logger = Server::logger( $server, new NullLogger, ); @@ -72,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')] @@ -81,7 +63,7 @@ public function testReboot() { $server = $this->server('sudo shutdown -r now'); - $logger = Logger::psr( + $logger = Server::logger( $server, new NullLogger, ); @@ -101,7 +83,7 @@ public function testShutdown() { $server = $this->server('sudo shutdown -h now'); - $logger = Logger::psr( + $logger = Server::logger( $server, new NullLogger, ); @@ -117,64 +99,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(), - Usleep::new(), + 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/MockTest.php b/tests/Servers/MockTest.php deleted file mode 100644 index 36ccde3..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->assertCount(0, $process->output()); - $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()); - } -} diff --git a/tests/Servers/RemoteTest.php b/tests/Servers/RemoteTest.php index 9902063..1463b8c 100644 --- a/tests/Servers/RemoteTest.php +++ b/tests/Servers/RemoteTest.php @@ -4,63 +4,44 @@ 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\Usleep; +use Innmind\Time\{ + Clock, + Halt, +}; use Innmind\IO\IO; use Innmind\Url\Authority\{ Host, Port, - UserInformation\User -}; -use Innmind\Immutable\{ - Attempt, - SideEffect, + UserInformation\User, }; +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')); + $_ = $remote->processes()->execute(Command::foreground('ls')); } #[Group('ci')] @@ -69,7 +50,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,10 +58,10 @@ public function testProcessesViaSpecificPort() ); $this->assertInstanceOf( - Processes\Remote::class, + Processes::class, $remote->processes(), ); - $remote->processes()->execute(Command::foreground('ls')); + $_ = $remote->processes()->execute(Command::foreground('ls')); } #[Group('ci')] @@ -92,7 +73,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'), @@ -102,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')] @@ -111,7 +92,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 +113,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 +130,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(), - Usleep::new(), + 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 e8844ae..bc25be5 100644 --- a/tests/Servers/UnixTest.php +++ b/tests/Servers/UnixTest.php @@ -4,38 +4,28 @@ namespace Tests\Innmind\Server\Control\Servers; use Innmind\Server\Control\{ - Servers\Unix, Server, Server\Processes, Server\Volumes, }; -use Innmind\TimeContinuum\Clock; -use Innmind\TimeWarp\Halt\Usleep; +use Innmind\Time\{ + Clock, + Halt, +}; use Innmind\IO\IO; use Innmind\BlackBox\PHPUnit\Framework\TestCase; use PHPUnit\Framework\Attributes\Group; class UnixTest extends TestCase { - #[Group('ci')] - #[Group('local')] - public function testInterface() - { - $this->assertInstanceOf(Server::class, Unix::of( - Clock::live(), - IO::fromAmbientAuthority(), - Usleep::new(), - )); - } - #[Group('ci')] #[Group('local')] public function testProcesses() { - $this->assertInstanceOf(Processes::class, Unix::of( + $this->assertInstanceOf(Processes::class, Server::new( Clock::live(), IO::fromAmbientAuthority(), - Usleep::new(), + Halt::new(), )->processes()); } @@ -43,10 +33,10 @@ public function testProcesses() #[Group('local')] public function testVolumes() { - $this->assertInstanceOf(Volumes::class, Unix::of( + $this->assertInstanceOf(Volumes::class, Server::new( Clock::live(), IO::fromAmbientAuthority(), - Usleep::new(), + Halt::new(), )->volumes()); } }