From 100ad37705a8b3d470c45f5f48b59021cfa77fe6 Mon Sep 17 00:00:00 2001 From: azjezz Date: Wed, 10 Nov 2021 16:25:20 +0100 Subject: [PATCH] ci(unit-tests): test on windows Signed-off-by: azjezz --- .github/workflows/unit-tests.yml | 1 + docs/component/io.md | 4 +- docs/component/shell.md | 4 +- docs/component/unix.md | 2 +- src/Psl/File/Internal/ResourceHandle.php | 2 - src/Psl/File/Internal/open.php | 2 - src/Psl/File/ReadHandle.php | 1 - src/Psl/File/ReadWriteHandle.php | 1 - src/Psl/File/WriteHandle.php | 1 - src/Psl/File/open_read_only.php | 1 - src/Psl/File/open_read_write.php | 1 - src/Psl/File/open_write_only.php | 1 - src/Psl/File/temporary.php | 1 - src/Psl/Filesystem/create_temporary_file.php | 2 +- src/Psl/IO/Exception/BlockingException.php | 9 -- src/Psl/IO/Internal/ResourceHandle.php | 15 +--- src/Psl/IO/Reader.php | 14 +--- src/Psl/IO/Stream/CloseHandle.php | 2 - src/Psl/IO/Stream/CloseReadHandle.php | 2 - src/Psl/IO/Stream/CloseReadWriteHandle.php | 2 - src/Psl/IO/Stream/CloseSeekHandle.php | 2 - src/Psl/IO/Stream/CloseSeekReadHandle.php | 2 - .../IO/Stream/CloseSeekReadWriteHandle.php | 2 - src/Psl/IO/Stream/CloseSeekWriteHandle.php | 2 - src/Psl/IO/Stream/CloseWriteHandle.php | 2 - src/Psl/IO/Stream/ReadHandle.php | 2 - src/Psl/IO/Stream/ReadWriteHandle.php | 2 - src/Psl/IO/Stream/SeekHandle.php | 2 - src/Psl/IO/Stream/SeekReadHandle.php | 2 - src/Psl/IO/Stream/SeekReadWriteHandle.php | 2 - src/Psl/IO/Stream/SeekWriteHandle.php | 2 - src/Psl/IO/Stream/WriteHandle.php | 2 - .../IO/WriteHandleConvenienceMethodsTrait.php | 1 - src/Psl/IO/WriteHandleInterface.php | 3 - src/Psl/IO/pipe.php | 7 +- src/Psl/Internal/Loader.php | 1 - src/Psl/Shell/escape_argument.php | 34 +++----- src/Psl/Shell/execute.php | 83 ++++++++++++++++--- src/Psl/TCP/Internal/Socket.php | 2 - src/Psl/Unix/Internal/Socket.php | 2 - src/Psl/Unix/Server.php | 11 ++- src/Psl/Unix/connect.php | 6 ++ tests/unit/Async/AwaitReadableTest.php | 9 +- tests/unit/Env/CurrentExecTest.php | 8 +- tests/unit/Filesystem/CopyTest.php | 6 ++ tests/unit/Filesystem/FileTest.php | 11 ++- tests/unit/Filesystem/PermissionsTest.php | 7 ++ tests/unit/Filesystem/ReadDirectoryTest.php | 7 ++ tests/unit/Shell/EscapeCommandTest.php | 1 - tests/unit/Shell/ExecuteTest.php | 22 +++-- tests/unit/Unix/ConnectTest.php | 8 +- tests/unit/Unix/ServerTest.php | 14 ++++ 52 files changed, 197 insertions(+), 138 deletions(-) delete mode 100644 src/Psl/IO/Exception/BlockingException.php diff --git a/.github/workflows/unit-tests.yml b/.github/workflows/unit-tests.yml index 78ca40afe..caf89e6ad 100644 --- a/.github/workflows/unit-tests.yml +++ b/.github/workflows/unit-tests.yml @@ -17,6 +17,7 @@ jobs: operating-system: - "macos-latest" - "ubuntu-latest" + - "windows-latest" steps: - name: "checkout" diff --git a/docs/component/io.md b/docs/component/io.md index addba07f7..4f230c859 100644 --- a/docs/component/io.md +++ b/docs/component/io.md @@ -15,7 +15,7 @@ - [error_handle](./../../src/Psl/IO/error_handle.php#L17) - [input_handle](./../../src/Psl/IO/input_handle.php#L17) - [output_handle](./../../src/Psl/IO/output_handle.php#L17) -- [pipe](./../../src/Psl/IO/pipe.php#L24) +- [pipe](./../../src/Psl/IO/pipe.php#L26) #### `Interfaces` @@ -39,7 +39,7 @@ #### `Classes` - [MemoryHandle](./../../src/Psl/IO/MemoryHandle.php#L15) -- [Reader](./../../src/Psl/IO/Reader.php#L15) +- [Reader](./../../src/Psl/IO/Reader.php#L17) #### `Traits` diff --git a/docs/component/shell.md b/docs/component/shell.md index 7572dd563..2bef82cba 100644 --- a/docs/component/shell.md +++ b/docs/component/shell.md @@ -12,8 +12,8 @@ #### `Functions` -- [escape_argument](./../../src/Psl/Shell/escape_argument.php#L17) +- [escape_argument](./../../src/Psl/Shell/escape_argument.php#L19) - [escape_command](./../../src/Psl/Shell/escape_command.php#L14) -- [execute](./../../src/Psl/Shell/execute.php#L40) +- [execute](./../../src/Psl/Shell/execute.php#L45) diff --git a/docs/component/unix.md b/docs/component/unix.md index d300e6a07..36a68d69e 100644 --- a/docs/component/unix.md +++ b/docs/component/unix.md @@ -20,6 +20,6 @@ #### `Classes` -- [Server](./../../src/Psl/Unix/Server.php#L15) +- [Server](./../../src/Psl/Unix/Server.php#L16) diff --git a/src/Psl/File/Internal/ResourceHandle.php b/src/Psl/File/Internal/ResourceHandle.php index d7f62d9df..1edbba43e 100644 --- a/src/Psl/File/Internal/ResourceHandle.php +++ b/src/Psl/File/Internal/ResourceHandle.php @@ -31,8 +31,6 @@ final class ResourceHandle extends IO\Internal\ResourceHandle implements File\Re /** * @param resource|object $resource - * - * @throws Exception\BlockingException If unable to set the handle resource to non-blocking mode. */ public function __construct(string $path, mixed $resource, bool $read, bool $write) { diff --git a/src/Psl/File/Internal/open.php b/src/Psl/File/Internal/open.php index e444be105..ebbf7cca9 100644 --- a/src/Psl/File/Internal/open.php +++ b/src/Psl/File/Internal/open.php @@ -11,8 +11,6 @@ * @internal * * @codeCoverageIgnore - * - * @throws IO\Exception\BlockingException If unable to set the handle resource to non-blocking mode. */ function open(string $filename, string $mode, bool $read, bool $write): ReadWriteHandleInterface { diff --git a/src/Psl/File/ReadHandle.php b/src/Psl/File/ReadHandle.php index d04a85843..e5f6a08fc 100644 --- a/src/Psl/File/ReadHandle.php +++ b/src/Psl/File/ReadHandle.php @@ -17,7 +17,6 @@ final class ReadHandle extends Internal\AbstractHandleWrapper implements ReadHan /** * @param resource|object $stream * - * @throws IO\Exception\BlockingException If unable to set the stream to non-blocking mode. * @throws Psl\Exception\InvariantViolationException If $path does not point to a file, or is not readable. */ public function __construct(string $path) diff --git a/src/Psl/File/ReadWriteHandle.php b/src/Psl/File/ReadWriteHandle.php index 39f03cc74..799e9893f 100644 --- a/src/Psl/File/ReadWriteHandle.php +++ b/src/Psl/File/ReadWriteHandle.php @@ -18,7 +18,6 @@ final class ReadWriteHandle extends Internal\AbstractHandleWrapper implements Re /** * @param resource|object $stream * - * @throws IO\Exception\BlockingException If unable to set the stream to non-blocking mode. * @throws Psl\Exception\InvariantViolationException If $path points to a non-file node, or it not writeable. * @throws Filesystem\Exception\RuntimeException If unable to create $path when it does not exist. */ diff --git a/src/Psl/File/WriteHandle.php b/src/Psl/File/WriteHandle.php index 1faae9a5b..42a9500c9 100644 --- a/src/Psl/File/WriteHandle.php +++ b/src/Psl/File/WriteHandle.php @@ -17,7 +17,6 @@ final class WriteHandle extends Internal\AbstractHandleWrapper implements WriteH /** * @param resource|object $stream * - * @throws IO\Exception\BlockingException If unable to set the stream to non-blocking mode. * @throws Psl\Exception\InvariantViolationException If $filename points to a non-file node, or it not writeable. * @throws Filesystem\Exception\RuntimeException If unable to create $path when it does not exist. */ diff --git a/src/Psl/File/open_read_only.php b/src/Psl/File/open_read_only.php index 95fd4ea09..93a181881 100644 --- a/src/Psl/File/open_read_only.php +++ b/src/Psl/File/open_read_only.php @@ -10,7 +10,6 @@ /** * Open a file handle for read only. * - * @throws IO\Exception\BlockingException If unable to set the stream to non-blocking mode. * @throws Psl\Exception\InvariantViolationException If $path does not point to a file, or is not readable. * */ function open_read_only(string $path): ReadHandleInterface diff --git a/src/Psl/File/open_read_write.php b/src/Psl/File/open_read_write.php index 9c13b9542..1e72102fd 100644 --- a/src/Psl/File/open_read_write.php +++ b/src/Psl/File/open_read_write.php @@ -11,7 +11,6 @@ /** * Open a file handle for read and write. * - * @throws IO\Exception\BlockingException If unable to set the stream to non-blocking mode. * @throws Psl\Exception\InvariantViolationException If $path points to a non-file node, or it not writeable. * @throws Filesystem\Exception\RuntimeException If unable to create $path when it does not exist. */ diff --git a/src/Psl/File/open_write_only.php b/src/Psl/File/open_write_only.php index 7ccd1d3a0..ca71bd1ca 100644 --- a/src/Psl/File/open_write_only.php +++ b/src/Psl/File/open_write_only.php @@ -11,7 +11,6 @@ /** * Open a file handle for write only. * - * @throws IO\Exception\BlockingException If unable to set the stream to non-blocking mode. * @throws Psl\Exception\InvariantViolationException If $filename points to a non-file node, or it not writeable. * @throws Filesystem\Exception\RuntimeException If unable to create $path when it does not exist. */ diff --git a/src/Psl/File/temporary.php b/src/Psl/File/temporary.php index 341ce9152..e1dc322e3 100644 --- a/src/Psl/File/temporary.php +++ b/src/Psl/File/temporary.php @@ -14,7 +14,6 @@ * @throws Psl\Exception\InvariantViolationException If $directory doesn't exist or is not writable. * @throws Psl\Exception\InvariantViolationException If $prefix contains a directory separator. * @throws Filesystem\Exception\RuntimeException If unable to create the file. - * @throws IO\Exception\BlockingException If unable to set the handle to non-blocking mode. */ function temporary(?string $directory = null, ?string $prefix = null): ReadWriteHandleInterface { diff --git a/src/Psl/Filesystem/create_temporary_file.php b/src/Psl/Filesystem/create_temporary_file.php index 62e81dda6..f559581b9 100644 --- a/src/Psl/Filesystem/create_temporary_file.php +++ b/src/Psl/Filesystem/create_temporary_file.php @@ -43,7 +43,7 @@ function create_temporary_file(?string $directory = null, ?string $prefix = null } try { - $filename = $directory . '/' . $prefix . SecureRandom\string(8); + $filename = $directory . SEPARATOR . $prefix . SecureRandom\string(8); // @codeCoverageIgnoreStart } catch (SecureRandom\Exception\InsufficientEntropyException $e) { throw new Exception\RuntimeException('Unable to gather enough entropy to generate filename.', 0, $e); diff --git a/src/Psl/IO/Exception/BlockingException.php b/src/Psl/IO/Exception/BlockingException.php deleted file mode 100644 index 4a446f672..000000000 --- a/src/Psl/IO/Exception/BlockingException.php +++ /dev/null @@ -1,9 +0,0 @@ -blocks = ($meta['wrapper_type'] ?? '') === 'plainfile'; + $this->blocks = $meta['blocked'] || ($meta['wrapper_type'] ?? '') === 'plainfile'; + if ($seek) { $seekable = (bool)$meta['seekable']; diff --git a/src/Psl/IO/Reader.php b/src/Psl/IO/Reader.php index 81cec9511..32088078f 100644 --- a/src/Psl/IO/Reader.php +++ b/src/Psl/IO/Reader.php @@ -12,6 +12,8 @@ use function strpos; use function substr; +use const PHP_EOL; + final class Reader implements ReadHandleInterface { use ReadHandleConvenienceMethodsTrait; @@ -32,7 +34,6 @@ public function __construct(ReadHandleInterface $handle) * @param int $size The number of bytes to read. * * @throws Exception\AlreadyClosedException If the handle has been already closed. - * @throws Exception\BlockingException If the handle is a socket or similar, and the read would block. * @throws Exception\RuntimeException If an error occurred during the operation, * or reached end of file before requested size. * @throws InvariantViolationException If $size is not positive. @@ -79,7 +80,6 @@ function (): void { /** * @throws Exception\AlreadyClosedException If the handle has been already closed. - * @throws Exception\BlockingException If the handle is a socket or similar, and the read would block. * @throws Exception\RuntimeException If an error occurred during the operation. * @throws Exception\TimeoutException If $timeout is reached before being able to read from the handle. */ @@ -97,7 +97,6 @@ private function fillBuffer(?int $desired_bytes, ?float $timeout): void * Read a single byte from the handle. * * @throws Exception\AlreadyClosedException If the handle has been already closed. - * @throws Exception\BlockingException If the handle is a socket or similar, and the read would block. * @throws Exception\RuntimeException If an error occurred during the operation, or reached end of file. * @throws Psl\Exception\InvariantViolationException If $timeout is negative. */ @@ -138,12 +137,11 @@ public function readByte(?float $timeout = null): string * or null if the end of file is reached before finding the current line terminator. * * @throws Exception\AlreadyClosedException If the handle has been already closed. - * @throws Exception\BlockingException If the handle is a socket or similar, and the read would block. * @throws Exception\RuntimeException If an error occurred during the operation. */ public function readLine(): ?string { - $line = $this->readUntil("\n"); + $line = $this->readUntil(PHP_EOL); if (null !== $line) { return $line; } @@ -163,7 +161,6 @@ public function readLine(): ?string * data. * * @throws Exception\AlreadyClosedException If the handle has been already closed. - * @throws Exception\BlockingException If the handle is a socket or similar, and the read would block. * @throws Exception\RuntimeException If an error occurred during the operation. * * @psalm-suppress MissingThrowsDocblock @@ -206,7 +203,6 @@ public function readUntil(string $suffix): ?string * @throws Exception\TimeoutException If $timeout is reached before being able to read from the handle. * @throws InvariantViolationException If $max_bytes is 0. * @throws Exception\AlreadyClosedException If the handle has been already closed. - * @throws Exception\BlockingException If the handle is a socket or similar, and the read would block. * * @return string the read data on success, or an empty string if the end of file is reached. * @@ -242,7 +238,6 @@ public function read(?int $max_bytes = null, ?float $timeout = null): string * @throws Exception\RuntimeException If an error occurred during the operation. * @throws InvariantViolationException If $max_bytes is 0. * @throws Exception\AlreadyClosedException If the handle has been already closed. - * @throws Exception\BlockingException If the handle is a socket or similar, and the read would block. * * @return string the read data on success, or an empty string if the end of file is reached. * @@ -289,7 +284,6 @@ public function getHandle(): ReadHandleInterface /** * @throws Exception\RuntimeException If an error occurred during the operation. * @throws Exception\AlreadyClosedException If the handle has been already closed. - * @throws Exception\BlockingException If the handle is a socket or similar, and the read would block. * * @return bool true if EOL has been reached, false otherwise. */ @@ -313,8 +307,6 @@ public function isEndOfFile(): bool $this->eof = true; return true; } - } catch (Exception\BlockingException) { - return false; } catch (Exception\ExceptionInterface) { // ignore; it'll be thrown again when attempting a real read. } diff --git a/src/Psl/IO/Stream/CloseHandle.php b/src/Psl/IO/Stream/CloseHandle.php index 171327fbc..69764e393 100644 --- a/src/Psl/IO/Stream/CloseHandle.php +++ b/src/Psl/IO/Stream/CloseHandle.php @@ -16,8 +16,6 @@ final class CloseHandle implements IO\CloseHandleInterface /** * @param resource|object $stream - * - * @throws IO\Exception\BlockingException If unable to set the stream to non-blocking mode. */ public function __construct(mixed $stream) { diff --git a/src/Psl/IO/Stream/CloseReadHandle.php b/src/Psl/IO/Stream/CloseReadHandle.php index 5216b8c80..3b7561b55 100644 --- a/src/Psl/IO/Stream/CloseReadHandle.php +++ b/src/Psl/IO/Stream/CloseReadHandle.php @@ -18,8 +18,6 @@ final class CloseReadHandle implements IO\CloseReadHandleInterface /** * @param resource|object $stream - * - * @throws IO\Exception\BlockingException If unable to set the stream to non-blocking mode. */ public function __construct(mixed $stream) { diff --git a/src/Psl/IO/Stream/CloseReadWriteHandle.php b/src/Psl/IO/Stream/CloseReadWriteHandle.php index 1becf097e..0fccd3364 100644 --- a/src/Psl/IO/Stream/CloseReadWriteHandle.php +++ b/src/Psl/IO/Stream/CloseReadWriteHandle.php @@ -19,8 +19,6 @@ final class CloseReadWriteHandle implements IO\CloseReadWriteHandleInterface /** * @param resource|object $stream - * - * @throws IO\Exception\BlockingException If unable to set the stream to non-blocking mode. */ public function __construct(mixed $stream) { diff --git a/src/Psl/IO/Stream/CloseSeekHandle.php b/src/Psl/IO/Stream/CloseSeekHandle.php index 4353e51a4..5f111aa16 100644 --- a/src/Psl/IO/Stream/CloseSeekHandle.php +++ b/src/Psl/IO/Stream/CloseSeekHandle.php @@ -16,8 +16,6 @@ final class CloseSeekHandle implements IO\CloseSeekHandleInterface /** * @param resource|object $stream - * - * @throws IO\Exception\BlockingException If unable to set the stream to non-blocking mode. */ public function __construct(mixed $stream) { diff --git a/src/Psl/IO/Stream/CloseSeekReadHandle.php b/src/Psl/IO/Stream/CloseSeekReadHandle.php index ba1f97d7c..47a7d9217 100644 --- a/src/Psl/IO/Stream/CloseSeekReadHandle.php +++ b/src/Psl/IO/Stream/CloseSeekReadHandle.php @@ -18,8 +18,6 @@ final class CloseSeekReadHandle implements IO\CloseSeekReadHandleInterface /** * @param resource|object $stream - * - * @throws IO\Exception\BlockingException If unable to set the stream to non-blocking mode. */ public function __construct(mixed $stream) { diff --git a/src/Psl/IO/Stream/CloseSeekReadWriteHandle.php b/src/Psl/IO/Stream/CloseSeekReadWriteHandle.php index 7bac6c978..b1a307bf1 100644 --- a/src/Psl/IO/Stream/CloseSeekReadWriteHandle.php +++ b/src/Psl/IO/Stream/CloseSeekReadWriteHandle.php @@ -19,8 +19,6 @@ final class CloseSeekReadWriteHandle implements IO\CloseSeekReadWriteHandleInter /** * @param resource|object $stream - * - * @throws IO\Exception\BlockingException If unable to set the stream to non-blocking mode. */ public function __construct(mixed $stream) { diff --git a/src/Psl/IO/Stream/CloseSeekWriteHandle.php b/src/Psl/IO/Stream/CloseSeekWriteHandle.php index 339e93d19..5f9beec1d 100644 --- a/src/Psl/IO/Stream/CloseSeekWriteHandle.php +++ b/src/Psl/IO/Stream/CloseSeekWriteHandle.php @@ -18,8 +18,6 @@ final class CloseSeekWriteHandle implements IO\CloseSeekWriteHandleInterface /** * @param resource|object $stream - * - * @throws IO\Exception\BlockingException If unable to set the stream to non-blocking mode. */ public function __construct(mixed $stream) { diff --git a/src/Psl/IO/Stream/CloseWriteHandle.php b/src/Psl/IO/Stream/CloseWriteHandle.php index 15bcbadb1..473b766a2 100644 --- a/src/Psl/IO/Stream/CloseWriteHandle.php +++ b/src/Psl/IO/Stream/CloseWriteHandle.php @@ -18,8 +18,6 @@ final class CloseWriteHandle implements IO\CloseWriteHandleInterface /** * @param resource|object $stream - * - * @throws IO\Exception\BlockingException If unable to set the stream to non-blocking mode. */ public function __construct(mixed $stream) { diff --git a/src/Psl/IO/Stream/ReadHandle.php b/src/Psl/IO/Stream/ReadHandle.php index bd374d1d7..842055ff0 100644 --- a/src/Psl/IO/Stream/ReadHandle.php +++ b/src/Psl/IO/Stream/ReadHandle.php @@ -18,8 +18,6 @@ final class ReadHandle implements IO\ReadHandleInterface /** * @param resource|object $stream - * - * @throws IO\Exception\BlockingException If unable to set the stream to non-blocking mode. */ public function __construct(mixed $stream) { diff --git a/src/Psl/IO/Stream/ReadWriteHandle.php b/src/Psl/IO/Stream/ReadWriteHandle.php index c06da1b11..0a77c2acd 100644 --- a/src/Psl/IO/Stream/ReadWriteHandle.php +++ b/src/Psl/IO/Stream/ReadWriteHandle.php @@ -19,8 +19,6 @@ final class ReadWriteHandle implements IO\ReadWriteHandleInterface /** * @param resource|object $stream - * - * @throws IO\Exception\BlockingException If unable to set the stream to non-blocking mode. */ public function __construct(mixed $stream) { diff --git a/src/Psl/IO/Stream/SeekHandle.php b/src/Psl/IO/Stream/SeekHandle.php index d8e88666d..ecc3737f1 100644 --- a/src/Psl/IO/Stream/SeekHandle.php +++ b/src/Psl/IO/Stream/SeekHandle.php @@ -16,8 +16,6 @@ final class SeekHandle implements IO\SeekHandleInterface /** * @param resource|object $stream - * - * @throws IO\Exception\BlockingException If unable to set the stream to non-blocking mode. */ public function __construct(mixed $stream) { diff --git a/src/Psl/IO/Stream/SeekReadHandle.php b/src/Psl/IO/Stream/SeekReadHandle.php index 4fc19eec0..e74358f67 100644 --- a/src/Psl/IO/Stream/SeekReadHandle.php +++ b/src/Psl/IO/Stream/SeekReadHandle.php @@ -18,8 +18,6 @@ final class SeekReadHandle implements IO\SeekReadHandleInterface /** * @param resource|object $stream - * - * @throws IO\Exception\BlockingException If unable to set the stream to non-blocking mode. */ public function __construct(mixed $stream) { diff --git a/src/Psl/IO/Stream/SeekReadWriteHandle.php b/src/Psl/IO/Stream/SeekReadWriteHandle.php index 4e8814d04..517d993f9 100644 --- a/src/Psl/IO/Stream/SeekReadWriteHandle.php +++ b/src/Psl/IO/Stream/SeekReadWriteHandle.php @@ -19,8 +19,6 @@ final class SeekReadWriteHandle implements IO\SeekReadWriteHandleInterface /** * @param resource|object $stream - * - * @throws IO\Exception\BlockingException If unable to set the stream to non-blocking mode. */ public function __construct(mixed $stream) { diff --git a/src/Psl/IO/Stream/SeekWriteHandle.php b/src/Psl/IO/Stream/SeekWriteHandle.php index faaef1786..17b9ea56a 100644 --- a/src/Psl/IO/Stream/SeekWriteHandle.php +++ b/src/Psl/IO/Stream/SeekWriteHandle.php @@ -18,8 +18,6 @@ final class SeekWriteHandle implements IO\SeekWriteHandleInterface /** * @param resource|object $stream - * - * @throws IO\Exception\BlockingException If unable to set the stream to non-blocking mode. */ public function __construct(mixed $stream) { diff --git a/src/Psl/IO/Stream/WriteHandle.php b/src/Psl/IO/Stream/WriteHandle.php index b1a1d891d..58b318e2d 100644 --- a/src/Psl/IO/Stream/WriteHandle.php +++ b/src/Psl/IO/Stream/WriteHandle.php @@ -18,8 +18,6 @@ final class WriteHandle implements IO\WriteHandleInterface /** * @param resource|object $stream - * - * @throws IO\Exception\BlockingException If unable to set the stream to non-blocking mode. */ public function __construct(mixed $stream) { diff --git a/src/Psl/IO/WriteHandleConvenienceMethodsTrait.php b/src/Psl/IO/WriteHandleConvenienceMethodsTrait.php index 41c6f200f..19cceb7e2 100644 --- a/src/Psl/IO/WriteHandleConvenienceMethodsTrait.php +++ b/src/Psl/IO/WriteHandleConvenienceMethodsTrait.php @@ -27,7 +27,6 @@ trait WriteHandleConvenienceMethodsTrait * do not want this to happen. * * @throws Exception\AlreadyClosedException If the handle has been already closed. - * @throws Exception\BlockingException If the handle is a socket or similar, and the write would block. * @throws Exception\RuntimeException If an error occurred during the operation. * @throws Exception\TimeoutException If reached timeout before completing the operation. */ diff --git a/src/Psl/IO/WriteHandleInterface.php b/src/Psl/IO/WriteHandleInterface.php index d8fbe14ea..83c7a712c 100644 --- a/src/Psl/IO/WriteHandleInterface.php +++ b/src/Psl/IO/WriteHandleInterface.php @@ -13,7 +13,6 @@ interface WriteHandleInterface extends HandleInterface * An immediate, unordered write. * * @throws Exception\AlreadyClosedException If the handle has been already closed. - * @throws Exception\BlockingException If the handle is a socket or similar, and the write would block. * @throws Exception\RuntimeException If an error occurred during the operation. * * @return int the number of bytes written on success, which may be 0. @@ -30,7 +29,6 @@ public function writeImmediately(string $bytes): int; * value and call again if needed. * * @throws Exception\AlreadyClosedException If the handle has been already closed. - * @throws Exception\BlockingException If the handle is a socket or similar, and the write would block. * @throws Exception\RuntimeException If an error occurred during the operation. * @throws Exception\TimeoutException If reached timeout before completing the operation. * @@ -50,7 +48,6 @@ public function write(string $bytes, ?float $timeout = null): int; * do not want this to happen. * * @throws Exception\AlreadyClosedException If the handle has been already closed. - * @throws Exception\BlockingException If the handle is a socket or similar, and the write would block. * @throws Exception\RuntimeException If an error occurred during the operation. * @throws Exception\TimeoutException If reached timeout before completing the operation. */ diff --git a/src/Psl/IO/pipe.php b/src/Psl/IO/pipe.php index cf875065d..4bf8fd960 100644 --- a/src/Psl/IO/pipe.php +++ b/src/Psl/IO/pipe.php @@ -10,15 +10,15 @@ use function error_get_last; use function stream_socket_pair; +use const PHP_OS_FAMILY; use const STREAM_IPPROTO_IP; +use const STREAM_PF_INET; use const STREAM_PF_UNIX; use const STREAM_SOCK_STREAM; /** * Create a pair of handles, where writes to the WriteHandle can be read from the ReadHandle. * - * @throws Exception\BlockingException If unable to set one of the handles to non-blocking mode. - * * @return array{0: CloseReadHandleInterface, 1: CloseWriteHandleInterface} */ function pipe(): array @@ -28,7 +28,8 @@ function pipe(): array * @return array{0: resource, 1: resource} */ static function (): array { - $sockets = stream_socket_pair(STREAM_PF_UNIX, STREAM_SOCK_STREAM, STREAM_IPPROTO_IP); + $domain = PHP_OS_FAMILY === 'Windows' ? STREAM_PF_INET : STREAM_PF_UNIX; + $sockets = stream_socket_pair($domain, STREAM_SOCK_STREAM, STREAM_IPPROTO_IP); // @codeCoverageIgnoreStart if ($sockets === false) { $error = error_get_last(); diff --git a/src/Psl/Internal/Loader.php b/src/Psl/Internal/Loader.php index 4e2d49a41..52f73deab 100644 --- a/src/Psl/Internal/Loader.php +++ b/src/Psl/Internal/Loader.php @@ -610,7 +610,6 @@ final class Loader 'Psl\Filesystem\Exception\RuntimeException', 'Psl\IO\Exception\AlreadyClosedException', 'Psl\IO\Exception\RuntimeException', - 'Psl\IO\Exception\BlockingException', 'Psl\IO\Exception\TimeoutException', 'Psl\IO\Internal\ResourceHandle', 'Psl\IO\Reader', diff --git a/src/Psl/Shell/escape_argument.php b/src/Psl/Shell/escape_argument.php index 304d9f128..68c9bc53f 100644 --- a/src/Psl/Shell/escape_argument.php +++ b/src/Psl/Shell/escape_argument.php @@ -4,8 +4,10 @@ namespace Psl\Shell; -use Psl\Regex; -use Psl\Str\Byte; +use function preg_match; +use function preg_replace; +use function str_contains; +use function str_replace; use const DIRECTORY_SEPARATOR; @@ -32,32 +34,20 @@ function escape_argument(string $argument): string } if ('\\' !== DIRECTORY_SEPARATOR) { - $argument = Byte\replace($argument, "'", "'\\''"); - - return "'" . $argument . "'"; + return "'" . str_replace("'", "'\\''", $argument) . "'"; } - // @codeCoverageIgnoreStart - /** @psalm-suppress MissingThrowsDocblock - safe ( $offset is within-of-bounds ) */ - if (Byte\contains($argument, "\0")) { - $argument = Byte\replace($argument, "\0", '?'); + if (str_contains($argument, "\0")) { + $argument = str_replace("\0", '?', $argument); } - /** @psalm-suppress MissingThrowsDocblock - safe ( $pattern is valid ) */ - if (!Regex\matches($argument, '/[\/()%!^"<>&|\s]/')) { + if (!preg_match('/[\/()%!^"<>&|\s]/', $argument)) { return $argument; } - /** @psalm-suppress MissingThrowsDocblock - safe ( $pattern is valid ) */ - $argument = Regex\replace($argument, '/(\\\\+)$/', '$1$1'); - $argument = Byte\replace_every($argument, [ - '"' => '""', - '^' => '"^^"', - '%' => '"^%"', - '!' => '"^!"', - "\n" => '!LF!' - ]); - - return '"' . $argument . '"'; + /** @var string $argument */ + $argument = preg_replace('/(\\\\+)$/', '$1$1', $argument); + + return '"' . str_replace(['"', '^', '%', '!', "\n"], ['""', '"^^"', '"^%"', '"^!"', '!LF!'], $argument) . '"'; // @codeCoverageIgnoreEnd } diff --git a/src/Psl/Shell/execute.php b/src/Psl/Shell/execute.php index 2c9106a8f..0976598a5 100644 --- a/src/Psl/Shell/execute.php +++ b/src/Psl/Shell/execute.php @@ -9,6 +9,10 @@ use Psl\Env; use Psl\IO; use Psl\IO\Stream; +use Psl\Regex; +use Psl\File; +use Psl\Filesystem; +use Psl\SecureRandom; use Psl\Str; use Psl\Vec; @@ -16,6 +20,9 @@ use function is_resource; use function proc_close; use function proc_open; +use function strpbrk; + +use const PHP_OS_FAMILY; /** * Execute an external program. @@ -35,7 +42,6 @@ * @throws Exception\PossibleAttackException In case the command being run is suspicious ( e.g: contains NULL byte ). * @throws Exception\RuntimeException In case $working_directory doesn't exist, or unable to create a new process. * @throws Exception\TimeoutException If $timeout is reached before being able to read the process stream. - * @throws IO\Exception\BlockingException If unable to set the process stream to non-blocking mode. */ function execute( string $command, @@ -66,18 +72,75 @@ function execute( throw new Exception\PossibleAttackException('NULL byte detected.'); } - $descriptor = [ - 1 => ['pipe', 'w'], - 2 => ['pipe', 'w'], - ]; - $environment = Dict\merge(Env\get_vars(), $environment); - $working_directory = $working_directory ?? Env\current_dir(); + $working_directory ??= Env\current_dir(); if (!is_dir($working_directory)) { throw new Exception\RuntimeException('$working_directory does not exist.'); } - $process = proc_open($commandline, $descriptor, $pipes, $working_directory, $environment); + $options = []; + // @codeCoverageIgnoreStart + if (PHP_OS_FAMILY === 'Windows') { + $variable_cache = []; + $variable_count = 0; + $identifier = 'PHP_STANDARD_LIBRARY_TMP_ENV_' . SecureRandom\string(6); + $commandline = Regex\replace_with($commandline, '/"(?:([^"%!^]*+(?:(?:!LF!|"(?:\^[%!^])?+")[^"%!^]*+)++)|[^"]*+ )"/x', static function (array $m) use ( + &$environment, + &$variable_cache, + &$variable_count, + $identifier + ): string { + if (!isset($m[1])) { + return $m[0]; + } + + if (isset($variable_cache[$m[0]])) { + return $variable_cache[$m[0]]; + } + + if (Str\Byte\contains($value = $m[1], "\0")) { + $value = Str\Byte\replace($value, "\0", '?'); + } + if (false === strpbrk($value, "\"%!\n")) { + return '"' . $value . '"'; + } + + $value = Str\Byte\replace_every($value, ['!LF!' => "\n", '"^!"' => '!', '"^%"' => '%', '"^^"' => '^', '""' => '"']); + $value = '"' . Regex\replace($value, '/(\\\\*)"/', '$1$1\\"') . '"'; + $var = $identifier . ++$variable_count; + + $environment[$var] = $value; + + return $variable_cache[$m[0]] = '!' . $var . '!'; + }); + + $commandline = 'cmd /V:ON /E:ON /D /C (' . Str\Byte\replace($commandline, "\n", ' ') . ')'; + $options = [ + 'bypass_shell' => true, + 'blocking_pipes' => false, + ]; + + $descriptor = [ + 1 => [$stdout_file = 'tcp://127.0.0.1:90001', 'w'], + 2 => [$stderr_file = 'tcp://127.0.0.1:90002', 'w'], + ]; + + $get_handle = static function(int $type, array $_pipes) use($stdout_file, $stderr_file): IO\CloseReadHandleInterface { + return 1 === $type ? File\open_read_only($stdout_file) : File\open_read_only($stderr_file); + }; + } else { + $descriptor = [ + 1 => ['pipe', 'w'], + 2 => ['pipe', 'w'], + ]; + + $get_handle = static function(int $type, array $pipes): IO\CloseReadHandleInterface { + return new Stream\CloseReadHandle($pipes[$type]); + }; + } + // @codeCoverageIgnoreEnd + + $process = proc_open($commandline, $descriptor, $pipes, $working_directory, $environment, $options); // @codeCoverageIgnoreStart // not sure how to replicate this, but it can happen \_o.o_/ if (!is_resource($process)) { @@ -85,8 +148,8 @@ function execute( } // @codeCoverageIgnoreEnd - $stdout = new Stream\CloseReadHandle($pipes[1]); - $stderr = new Stream\CloseReadHandle($pipes[2]); + $stdout = $get_handle(1, $pipes); + $stderr = $get_handle(2, $pipes); try { [$stdout_content, $stderr_content] = Async\concurrent([ diff --git a/src/Psl/TCP/Internal/Socket.php b/src/Psl/TCP/Internal/Socket.php index 233f4dc83..b0d29e378 100644 --- a/src/Psl/TCP/Internal/Socket.php +++ b/src/Psl/TCP/Internal/Socket.php @@ -19,8 +19,6 @@ final class Socket extends Internal\ResourceHandle implements TCP\SocketInterfac { /** * @param resource $resource - * - * @throws Exception\BlockingException If unable to set the socket resource to non-blocking mode. */ public function __construct($resource) { diff --git a/src/Psl/Unix/Internal/Socket.php b/src/Psl/Unix/Internal/Socket.php index 4f1e8b93c..ea55aca3b 100644 --- a/src/Psl/Unix/Internal/Socket.php +++ b/src/Psl/Unix/Internal/Socket.php @@ -19,8 +19,6 @@ final class Socket extends Internal\ResourceHandle implements Unix\SocketInterfa { /** * @param resource $resource - * - * @throws Exception\BlockingException If unable to set the socket resource to non-blocking mode. */ public function __construct($resource) { diff --git a/src/Psl/Unix/Server.php b/src/Psl/Unix/Server.php index a5daff385..1277680bf 100644 --- a/src/Psl/Unix/Server.php +++ b/src/Psl/Unix/Server.php @@ -4,7 +4,6 @@ namespace Psl\Unix; -use Psl; use Psl\Network; use Revolt\EventLoop; @@ -12,6 +11,8 @@ use function fclose; use function stream_socket_accept; +use const PHP_OS_FAMILY; + final class Server implements Network\ServerInterface { /** @@ -62,10 +63,16 @@ static function (string $_watcher, mixed $resource) use (&$suspension): void { * * @param non-empty-string $file * - * @throws Psl\Network\Exception\RuntimeException In case failed to listen to on given address. + * @throws Network\Exception\RuntimeException In case failed to listen to on given address. */ public static function create(string $file): self { + // @codeCoverageIgnoreStart + if (PHP_OS_FAMILY === 'Windows') { + throw new Network\Exception\RuntimeException('Unix Server is not supported on Windows platform.'); + } + // @codeCoverageIgnoreEnd + $socket = Network\Internal\server_listen("unix://{$file}"); return new self($socket); diff --git a/src/Psl/Unix/connect.php b/src/Psl/Unix/connect.php index e756fa4b5..e8791eb9e 100644 --- a/src/Psl/Unix/connect.php +++ b/src/Psl/Unix/connect.php @@ -16,6 +16,12 @@ */ function connect(string $path, ?float $timeout = null): SocketInterface { + // @codeCoverageIgnoreStart + if (PHP_OS_FAMILY === 'Windows') { + throw new Network\Exception\RuntimeException('Unix Server is not supported on Windows platform.'); + } + // @codeCoverageIgnoreEnd + $socket = Network\Internal\socket_connect("unix://{$path}", timeout: $timeout); /** @psalm-suppress MissingThrowsDocblock */ diff --git a/tests/unit/Async/AwaitReadableTest.php b/tests/unit/Async/AwaitReadableTest.php index c084a9846..7d53088fa 100644 --- a/tests/unit/Async/AwaitReadableTest.php +++ b/tests/unit/Async/AwaitReadableTest.php @@ -11,11 +11,18 @@ use function fwrite; use function stream_socket_pair; +use const PHP_OS_FAMILY; +use const STREAM_IPPROTO_IP; +use const STREAM_PF_INET; +use const STREAM_PF_UNIX; +use const STREAM_SOCK_STREAM; + final class AwaitReadableTest extends TestCase { public function testAwaitReadable(): void { - $sockets = stream_socket_pair(STREAM_PF_UNIX, STREAM_SOCK_STREAM, STREAM_IPPROTO_IP); + $domain = PHP_OS_FAMILY === 'Windows' ? STREAM_PF_INET : STREAM_PF_UNIX; + $sockets = stream_socket_pair($domain, STREAM_SOCK_STREAM, STREAM_IPPROTO_IP); $write_socket = $sockets[0]; $read_socket = $sockets[1]; diff --git a/tests/unit/Env/CurrentExecTest.php b/tests/unit/Env/CurrentExecTest.php index 4ed9ebecf..8b509ea81 100644 --- a/tests/unit/Env/CurrentExecTest.php +++ b/tests/unit/Env/CurrentExecTest.php @@ -8,13 +8,19 @@ use Psl\Env; use Psl\Filesystem; +use const PHP_OS_FAMILY; + final class CurrentExecTest extends TestCase { public function testCurrentExe(): void { + if ('Windows' === PHP_OS_FAMILY) { + static::markTestSkipped('I do not want to bother :)'); + } + $phpunit = __DIR__ . '/../../../vendor/bin/phpunit'; $phpunit = Filesystem\canonicalize($phpunit); - if (PHP_OS_FAMILY !== 'Windows' && Filesystem\is_symbolic_link($phpunit)) { + if (Filesystem\is_symbolic_link($phpunit)) { $phpunit = Filesystem\read_symbolic_link($phpunit); } diff --git a/tests/unit/Filesystem/CopyTest.php b/tests/unit/Filesystem/CopyTest.php index f7810cb09..0adf299d0 100644 --- a/tests/unit/Filesystem/CopyTest.php +++ b/tests/unit/Filesystem/CopyTest.php @@ -7,6 +7,8 @@ use Psl\Filesystem; use Psl\Str; +use const PHP_OS_FAMILY; + final class CopyTest extends AbstractFilesystemTest { protected string $function = 'copy'; @@ -42,6 +44,10 @@ public function testCopyOverwrite(): void public function testCopyExecutableBits(): void { + if (PHP_OS_FAMILY === 'Windows') { + static::markTestSkipped('Test can only be executed under *nix OS.'); + } + $shell_file = Str\join([$this->directory, 'hello.sh'], Filesystem\SEPARATOR); Filesystem\create_file($shell_file); diff --git a/tests/unit/Filesystem/FileTest.php b/tests/unit/Filesystem/FileTest.php index c16cea9b2..f8e53ed35 100644 --- a/tests/unit/Filesystem/FileTest.php +++ b/tests/unit/Filesystem/FileTest.php @@ -138,12 +138,17 @@ public function testWriteFileThrowsForNonWritableFiles(): void { $file = Str\join([$this->directory, 'write.txt'], Filesystem\SEPARATOR); Filesystem\create_file($file); + $permissions = Filesystem\get_permissions($file) & 0777; Filesystem\change_permissions($file, 0111); - $this->expectException(InvariantViolationException::class); - $this->expectExceptionMessage('File "' . $file . '" is not writable.'); + try { + $this->expectException(InvariantViolationException::class); + $this->expectExceptionMessage('File "' . $file . '" is not writable.'); - Filesystem\write_file($file, 'hello'); + Filesystem\write_file($file, 'hello'); + } finally { + Filesystem\change_permissions($file, $permissions); + } } public function testReadFile(): void diff --git a/tests/unit/Filesystem/PermissionsTest.php b/tests/unit/Filesystem/PermissionsTest.php index 45c72da74..64c834a32 100644 --- a/tests/unit/Filesystem/PermissionsTest.php +++ b/tests/unit/Filesystem/PermissionsTest.php @@ -7,12 +7,19 @@ use Psl\Filesystem; use Psl\Str; +use const PHP_OS_FAMILY; + final class PermissionsTest extends AbstractFilesystemTest { protected string $function = 'permissions'; public function testChangePermissions(): void { + if (PHP_OS_FAMILY === 'Windows') { + // executable bit on windows. + static::markTestSkipped('Test can only be executed under *nix OS.'); + } + $filename = Str\join([$this->directory, 'foo.txt'], Filesystem\SEPARATOR); Filesystem\create_file($filename); diff --git a/tests/unit/Filesystem/ReadDirectoryTest.php b/tests/unit/Filesystem/ReadDirectoryTest.php index 9a7c97aec..00c4aa813 100644 --- a/tests/unit/Filesystem/ReadDirectoryTest.php +++ b/tests/unit/Filesystem/ReadDirectoryTest.php @@ -10,6 +10,8 @@ use Psl\Str; use Psl\Vec; +use const PHP_OS_FAMILY; + final class ReadDirectoryTest extends AbstractFilesystemTest { protected string $function = 'read_directory'; @@ -57,6 +59,11 @@ public function testReadDirectoryThrowsIfNotDirectory(): void public function testReadDirectoryThrowsIfNotReadable(): void { + if (PHP_OS_FAMILY === 'Windows') { + // executable bit on windows. + static::markTestSkipped('Test can only be executed under *nix OS.'); + } + Filesystem\change_permissions($this->directory, 0077); $this->expectException(InvariantViolationException::class); diff --git a/tests/unit/Shell/EscapeCommandTest.php b/tests/unit/Shell/EscapeCommandTest.php index ce1ec35e5..09e4b714a 100644 --- a/tests/unit/Shell/EscapeCommandTest.php +++ b/tests/unit/Shell/EscapeCommandTest.php @@ -11,7 +11,6 @@ final class EscapeCommandTest extends TestCase { public function testEscapeCommand(): void { - static::assertSame( "Hello, World!", Shell\execute(Shell\escape_command(PHP_BINARY), ['-r', 'echo "Hello, World!";']) diff --git a/tests/unit/Shell/ExecuteTest.php b/tests/unit/Shell/ExecuteTest.php index c4c099f65..380cd8222 100644 --- a/tests/unit/Shell/ExecuteTest.php +++ b/tests/unit/Shell/ExecuteTest.php @@ -34,6 +34,10 @@ public function testFailedExecution(): void public function testItThrowsForNULLByte(): void { + if (PHP_OS_FAMILY === 'Windows') { + static::markTestSkipped('Test can only be executed under *nix OS.'); + } + $this->expectException(Shell\Exception\PossibleAttackException::class); Shell\execute('php', ["\0"]); @@ -49,17 +53,21 @@ public function testEnvironmentIsPassedDownToTheProcess(): void public function testCurrentEnvironmentVariablesArePassedDownToTheProcess(): void { - Env\set_var('FOO', 'BAR'); - - static::assertSame( - 'BAR', - Shell\execute(PHP_BINARY, ['-r', 'echo getenv("FOO");']) - ); + try { + Env\set_var('FOO', 'BAR'); + + static::assertSame( + 'BAR', + Shell\execute(PHP_BINARY, ['-r', 'echo getenv("FOO");']) + ); + } finally { + Env\remove_var('FOO'); + } } public function testWorkingDirectoryIsUsed(): void { - if ('Darwin' === PHP_OS_FAMILY) { + if ('Darwin' === PHP_OS_FAMILY || PHP_OS_FAMILY === 'Windows') { static::markTestSkipped(); } diff --git a/tests/unit/Unix/ConnectTest.php b/tests/unit/Unix/ConnectTest.php index 9960ce210..149114cb8 100644 --- a/tests/unit/Unix/ConnectTest.php +++ b/tests/unit/Unix/ConnectTest.php @@ -10,10 +10,16 @@ use Psl\Str; use Psl\Unix; +use const PHP_OS_FAMILY; + final class ConnectTest extends TestCase { public function testConnect(): void { + if (PHP_OS_FAMILY === 'Windows') { + static::markTestSkipped('Unix Server is not supported on Windows platform.'); + } + $sock = Filesystem\create_temporary_file(prefix: 'psl-examples') . ".sock"; Async\concurrent([ @@ -29,7 +35,7 @@ public function testConnect(): void }, 'client' => static function () use ($sock): void { $client = Unix\connect($sock); - self::assertSame("unix://" . $sock, $client->getPeerAddress()->toString()); + self::assertSame("unix://" . $sock, $client->getPeerAddress()->toString()); $client->writeAll('Hello, World!'); $response = $client->readAll(); self::assertSame('!dlroW ,olleH', $response); diff --git a/tests/unit/Unix/ServerTest.php b/tests/unit/Unix/ServerTest.php index ca824d26b..a17cdbc46 100644 --- a/tests/unit/Unix/ServerTest.php +++ b/tests/unit/Unix/ServerTest.php @@ -11,10 +11,16 @@ use Psl\Unix; use Throwable; +use const PHP_OS_FAMILY; + final class ServerTest extends TestCase { public function testNextConnectionOnStoppedServer(): void { + if (PHP_OS_FAMILY === 'Windows') { + static::markTestSkipped('Unix Server is not supported on Windows platform.'); + } + $sock = Filesystem\create_temporary_file(prefix: 'psl-examples') . ".sock"; $server = Unix\Server::create($sock); $server->stopListening(); @@ -27,6 +33,10 @@ public function testNextConnectionOnStoppedServer(): void public function testGetLocalAddressOnStoppedServer(): void { + if (PHP_OS_FAMILY === 'Windows') { + static::markTestSkipped('Unix Server is not supported on Windows platform.'); + } + $sock = Filesystem\create_temporary_file(prefix: 'psl-examples') . ".sock"; $server = Unix\Server::create($sock); $server->stopListening(); @@ -39,6 +49,10 @@ public function testGetLocalAddressOnStoppedServer(): void public function testThrowsForPendingOperation(): void { + if (PHP_OS_FAMILY === 'Windows') { + static::markTestSkipped('Unix Server is not supported on Windows platform.'); + } + $sock = Filesystem\create_temporary_file(prefix: 'psl-examples') . ".sock"; $server = Unix\Server::create($sock);