Skip to content

Commit

Permalink
ci(unit-tests): test on windows
Browse files Browse the repository at this point in the history
Signed-off-by: azjezz <azjezz@protonmail.com>
  • Loading branch information
azjezz committed Nov 10, 2021
1 parent f896bcf commit 515e7cd
Show file tree
Hide file tree
Showing 22 changed files with 199 additions and 58 deletions.
1 change: 1 addition & 0 deletions .github/workflows/unit-tests.yml
Original file line number Diff line number Diff line change
Expand Up @@ -17,6 +17,7 @@ jobs:
operating-system:
- "macos-latest"
- "ubuntu-latest"
- "windows-latest"

steps:
- name: "checkout"
Expand Down
4 changes: 2 additions & 2 deletions docs/component/io.md
Original file line number Diff line number Diff line change
Expand Up @@ -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`

Expand All @@ -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`

Expand Down
4 changes: 2 additions & 2 deletions docs/component/shell.md
Original file line number Diff line number Diff line change
Expand Up @@ -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)


2 changes: 1 addition & 1 deletion docs/component/unix.md
Original file line number Diff line number Diff line change
Expand Up @@ -20,6 +20,6 @@

#### `Classes`

- [Server](./../../src/Psl/Unix/Server.php#L15)
- [Server](./../../src/Psl/Unix/Server.php#L16)


2 changes: 1 addition & 1 deletion src/Psl/Filesystem/create_temporary_file.php
Original file line number Diff line number Diff line change
Expand Up @@ -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);
Expand Down
9 changes: 6 additions & 3 deletions src/Psl/IO/Internal/ResourceHandle.php
Original file line number Diff line number Diff line change
Expand Up @@ -24,6 +24,7 @@
use function stream_set_read_buffer;
use function strlen;
use function substr;
use const PHP_OS_FAMILY;

/**
* @internal
Expand Down Expand Up @@ -68,19 +69,21 @@ public function __construct(mixed $resource, bool $read, bool $write, bool $seek
Type\object(),
)->assert($resource);

$meta = stream_get_meta_data($resource);
$this->blocks = ($meta['wrapper_type'] ?? '') === 'plainfile';

/** @psalm-suppress UnusedFunctionCall */
stream_set_read_buffer($resource, 0);
$result = stream_set_blocking($resource, false);
if ($result === false) {
// plainfile is always blocking on windows.
if ($result === false && !($this->blocks && PHP_OS_FAMILY === 'Windows')) {
$error = error_get_last();

throw new Exception\BlockingException(
$error['message'] ?? 'Unable to set the handle resource to non-blocking mode'
);
}

$meta = stream_get_meta_data($resource);
$this->blocks = ($meta['wrapper_type'] ?? '') === 'plainfile';
if ($seek) {
$seekable = (bool)$meta['seekable'];

Expand Down
4 changes: 3 additions & 1 deletion src/Psl/IO/Reader.php
Original file line number Diff line number Diff line change
Expand Up @@ -12,6 +12,8 @@
use function strpos;
use function substr;

use const PHP_EOL;

final class Reader implements ReadHandleInterface
{
use ReadHandleConvenienceMethodsTrait;
Expand Down Expand Up @@ -143,7 +145,7 @@ public function readByte(?float $timeout = null): string
*/
public function readLine(): ?string
{
$line = $this->readUntil("\n");
$line = $this->readUntil(PHP_EOL);
if (null !== $line) {
return $line;
}
Expand Down
5 changes: 4 additions & 1 deletion src/Psl/IO/pipe.php
Original file line number Diff line number Diff line change
Expand Up @@ -10,7 +10,9 @@
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;

Expand All @@ -28,7 +30,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();
Expand Down
34 changes: 12 additions & 22 deletions src/Psl/Shell/escape_argument.php
Original file line number Diff line number Diff line change
Expand Up @@ -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;

Expand All @@ -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
}
82 changes: 73 additions & 9 deletions src/Psl/Shell/execute.php
Original file line number Diff line number Diff line change
Expand Up @@ -9,13 +9,20 @@
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;

use function is_dir;
use function is_resource;
use function proc_close;
use function proc_open;
use function strpbrk;

use const PHP_OS_FAMILY;

/**
* Execute an external program.
Expand Down Expand Up @@ -66,27 +73,84 @@ 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 = Filesystem\create_temporary_file(prefix: 'psl_stdout'), 'w'],
2 => [$stderr_file = Filesystem\create_temporary_file(prefix: 'psl_stderr'), '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)) {
throw new Exception\RuntimeException('Failed to open a new process.');
}
// @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([
Expand Down
11 changes: 9 additions & 2 deletions src/Psl/Unix/Server.php
Original file line number Diff line number Diff line change
Expand Up @@ -4,14 +4,15 @@

namespace Psl\Unix;

use Psl;
use Psl\Network;
use Revolt\EventLoop;

use function error_get_last;
use function fclose;
use function stream_socket_accept;

use const PHP_OS_FAMILY;

final class Server implements Network\ServerInterface
{
/**
Expand Down Expand Up @@ -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);
Expand Down
6 changes: 6 additions & 0 deletions src/Psl/Unix/connect.php
Original file line number Diff line number Diff line change
Expand Up @@ -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 */
Expand Down
9 changes: 8 additions & 1 deletion tests/unit/Async/AwaitReadableTest.php
Original file line number Diff line number Diff line change
Expand Up @@ -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];

Expand Down
8 changes: 7 additions & 1 deletion tests/unit/Env/CurrentExecTest.php
Original file line number Diff line number Diff line change
Expand Up @@ -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);
}

Expand Down
Loading

0 comments on commit 515e7cd

Please sign in to comment.