Browse Source

Updated with latest updates from upstream, added tests

main v1.0.1
Dustin Wilson 6 months ago
parent
commit
43a5121123
  1. 3
      .gitignore
  2. 49
      .php-cs-fixer.php
  3. 9
      composer.json
  4. 3193
      composer.lock
  5. 69
      lib/Filesystem.php
  6. 1
      lib/Filesystem/FileNotFoundException.php
  7. 1
      lib/Filesystem/IOException.php
  8. 7
      lib/Filesystem/InvalidArgumentException.php
  9. 13
      lib/Filesystem/RuntimeException.php
  10. 26
      lib/Path.php
  11. 38
      test
  12. 18
      tests/bootstrap.php
  13. 41
      tests/cases/TestExceptions.php
  14. 1731
      tests/cases/TestFilesystem.php
  15. 1010
      tests/cases/TestPath.php
  16. 146
      tests/lib/FilesystemTestCase.php
  17. 38
      tests/lib/MockStream.php
  18. 22
      tests/phpunit.xml

3
.gitignore

@ -1,6 +1,9 @@
# Project-specific
/test*.*
/tests/.phpunit.cache
/build
.php-cs-fixer.cache
/symfony
# General
*.DS_Store

49
.php-cs-fixer.php

@ -0,0 +1,49 @@
<?php
return (new PhpCsFixer\Config())
->setRules([
'@PSR12' => true,
'array_indentation' => true,
'array_syntax' => [ 'syntax' => 'short' ],
'blank_line_after_namespace' => false,
'blank_line_after_opening_tag' => false,
'blank_lines_before_namespace' => false,
'braces' => [
'allow_single_line_closure' => true,
'position_after_functions_and_oop_constructs' => 'same'
],
'braces_position' => [
'classes_opening_brace' => 'same_line',
'functions_opening_brace' => 'same_line'
],
'class_attributes_separation' => [ 'elements' => [ 'method' => 'one' ] ],
'combine_consecutive_unsets' => true,
'concat_space' => ['spacing' => 'one'],
'declare_equal_normalize' => true,
'function_typehint_space' => true,
'general_phpdoc_annotation_remove' => [],
'include' => true,
'lowercase_cast' => true,
'multiline_whitespace_before_semicolons' => false,
'no_blank_lines_before_namespace' => true,
'no_extra_blank_lines' => [
'tokens' => [
'curly_brace_block',
'extra',
'throw',
'use'
]
],
'no_multiline_whitespace_around_double_arrow' => true,
'no_spaces_around_offset' => true,
'no_whitespace_before_comma_in_array' => true,
'no_whitespace_in_blank_line' => true,
'object_operator_without_whitespace' => true,
'single_quote' => true,
'space_after_semicolon' => true,
'ternary_operator_spaces' => true,
'trim_array_spaces' => true,
'unary_operator_spaces' => true,
'whitespace_after_comma_in_array' => true,
])
->setLineEnding("\n")
;

9
composer.json

@ -8,6 +8,11 @@
"MensBeam\\": "lib/"
}
},
"autoload-dev": {
"psr-4": {
"MensBeam\\Filesystem\\Test\\": "tests/lib/"
}
},
"authors": [
{
"name": "Dustin Wilson",
@ -20,7 +25,9 @@
"symfony/polyfill-mbstring": ">=1.8"
},
"require-dev": {
"symfony/filesystem": ">=6.2"
"friendsofphp/php-cs-fixer": "^3.38",
"symfony/filesystem": "*",
"phpunit/phpunit": "^10.4"
},
"suggest": {
"ext-ctype": "For better performance",

3193
composer.lock

File diff suppressed because it is too large

69
lib/Filesystem.php

@ -14,7 +14,6 @@ use MensBeam\Filesystem\{
IOException
};
/**
* Provides basic utility to manipulate the file system.
*
@ -30,6 +29,8 @@ class Filesystem {
* If the target file is newer, it is overwritten only when the
* $overwriteNewerFiles option is set to true.
*
* @return void
*
* @throws FileNotFoundException When originFile doesn't exist
* @throws IOException When copy fails
*/
@ -80,6 +81,8 @@ class Filesystem {
/**
* Creates a directory recursively.
*
* @return void
*
* @throws IOException On any directory creation failure
*/
public static function mkdir(string|iterable $dirs, int $mode = 0777) {
@ -119,6 +122,8 @@ class Filesystem {
* @param int|null $time The touch time as a Unix timestamp, if not supplied the current system time is used
* @param int|null $atime The access time as a Unix timestamp, if not supplied the current system time is used
*
* @return void
*
* @throws IOException When touch fails
*/
public static function touch(string|iterable $files, int $time = null, int $atime = null) {
@ -132,6 +137,8 @@ class Filesystem {
/**
* Removes files or directories.
*
* @return void
*
* @throws IOException When removal fails
*/
public static function remove(string|iterable $files) {
@ -147,6 +154,7 @@ class Filesystem {
private static function doRemove(array $files, bool $isRecursive): void {
$files = array_reverse($files);
foreach ($files as $file) {
$file = (string)$file;
if (is_link($file)) {
// See https://bugs.php.net/52176
if (!(self::box('unlink', $file) || '\\' !== \DIRECTORY_SEPARATOR || self::box('rmdir', $file)) && file_exists($file)) {
@ -154,7 +162,7 @@ class Filesystem {
}
} elseif (is_dir($file)) {
if (!$isRecursive) {
$tmpName = \dirname(realpath($file)).'/.'.strrev(strtr(base64_encode(random_bytes(2)), '/=', '-.'));
$tmpName = \dirname(realpath($file)) . '/.' . strrev(strtr(base64_encode(random_bytes(2)), '/=', '-_'));
if (file_exists($tmpName)) {
try {
@ -196,11 +204,14 @@ class Filesystem {
* @param int $umask The mode mask (octal)
* @param bool $recursive Whether change the mod recursively or not
*
* @return void
*
* @throws IOException When the change fails
*/
public static function chmod(string|iterable $files, int $mode, int $umask = 0000, bool $recursive = false) {
foreach (self::toIterable($files) as $file) {
if (\is_int($mode) && !self::box('chmod', $file, $mode & ~$umask)) {
$file = (string)$file;
if (!self::box('chmod', $file, $mode & ~$umask)) {
throw new IOException(sprintf('Failed to chmod file "%s": ', $file) . self::$lastError, 0, null, $file);
}
if ($recursive && is_dir($file) && !is_link($file)) {
@ -215,10 +226,13 @@ class Filesystem {
* @param string|int $user A user name or number
* @param bool $recursive Whether change the owner recursively or not
*
* @return void
*
* @throws IOException When the change fails
*/
public static function chown(string|iterable $files, string|int $user, bool $recursive = false) {
foreach (self::toIterable($files) as $file) {
$file = (string)$file;
if ($recursive && is_dir($file) && !is_link($file)) {
self::chown(new \FilesystemIterator($file), $user, true);
}
@ -240,10 +254,13 @@ class Filesystem {
* @param string|int $group A group name or number
* @param bool $recursive Whether change the group recursively or not
*
* @return void
*
* @throws IOException When the change fails
*/
public static function chgrp(string|iterable $files, string|int $group, bool $recursive = false) {
foreach (self::toIterable($files) as $file) {
$file = (string)$file;
if ($recursive && is_dir($file) && !is_link($file)) {
self::chgrp(new \FilesystemIterator($file), $group, true);
}
@ -262,6 +279,8 @@ class Filesystem {
/**
* Renames a file or a directory.
*
* @return void
*
* @throws IOException When target file or directory already exists
* @throws IOException When origin cannot be renamed
*/
@ -301,6 +320,8 @@ class Filesystem {
/**
* Creates a symbolic link or copy a directory.
*
* @return void
*
* @throws IOException When symlink fails
*/
public static function symlink(string $originDir, string $targetDir, bool $copyOnWindows = false) {
@ -336,6 +357,8 @@ class Filesystem {
*
* @param string|string[] $targetFiles The target file(s)
*
* @return void
*
* @throws FileNotFoundException When original file is missing or not a file
* @throws IOException When link fails, including if link already exists
*/
@ -367,7 +390,7 @@ class Filesystem {
/**
* @param string $linkType Name of the link type, typically 'symbolic' or 'hard'
*/
private static function linkException(string $origin, string $target, string $linkType) {
private static function linkException(string $origin, string $target, string $linkType): never {
if (self::$lastError) {
if ('\\' === \DIRECTORY_SEPARATOR && str_contains(self::$lastError, 'error code(1314)')) {
throw new IOException(sprintf('Unable to create "%s" link due to error code 1314: \'A required privilege is not held by the client\'. Do you have the required Administrator-rights?', $linkType), 0, null, $target);
@ -421,11 +444,9 @@ class Filesystem {
$startPath = str_replace('\\', '/', $startPath);
}
$splitDriveLetter = function ($path) {
return (\strlen($path) > 2 && ':' === $path[1] && '/' === $path[2] && ctype_alpha($path[0]))
$splitDriveLetter = fn ($path) => (\strlen($path) > 2 && ':' === $path[1] && '/' === $path[2] && ctype_alpha($path[0]))
? [substr($path, 2), strtoupper($path[0])]
: [$path, null];
};
$splitPath = function ($path) {
$result = [];
@ -491,6 +512,8 @@ class Filesystem {
* - $options['copy_on_windows'] Whether to copy files instead of links on Windows (see symlink(), defaults to false)
* - $options['delete'] Whether to delete files that are not in the source directory (defaults to false)
*
* @return void
*
* @throws IOException When file type is unknown
*/
public static function mirror(string $originDir, string $targetDir, \Traversable $iterator = null, array $options = []) {
@ -513,7 +536,7 @@ class Filesystem {
foreach ($deleteIterator as $file) {
$origin = $originDir . substr($file->getPathname(), $targetDirLen);
if (!self::exists($origin)) {
self::remove($file);
self::remove((string)$file);
}
}
}
@ -536,12 +559,12 @@ class Filesystem {
$target = $targetDir . substr($file->getPathname(), $originDirLen);
$filesCreatedWhileMirroring[$target] = true;
if (!$copyOnWindows && is_link($file)) {
if (!$copyOnWindows && is_link((string)$file)) {
self::symlink($file->getLinkTarget(), $target);
} elseif (is_dir($file)) {
} elseif (is_dir((string)$file)) {
self::mkdir($target);
} elseif (is_file($file)) {
self::copy($file, $target, $options['override'] ?? false);
} elseif (is_file((string)$file)) {
self::copy((string)$file, $target, $options['override'] ?? false);
} else {
throw new IOException(sprintf('Unable to guess "%s" file type.', $file), 0, null, $file);
}
@ -552,8 +575,10 @@ class Filesystem {
* Returns whether the file path is an absolute path.
*/
public static function isAbsolutePath(string $file): bool {
return '' !== $file && (strspn($file, '/\\', 0, 1)
|| (\strlen($file) > 3 && ctype_alpha($file[0])
return '' !== $file && (
strspn($file, '/\\', 0, 1)
|| (
\strlen($file) > 3 && ctype_alpha($file[0])
&& ':' === $file[1]
&& strspn($file, '/\\', 2, 1)
)
@ -612,6 +637,8 @@ class Filesystem {
*
* @param string|resource $content The data to write into the file
*
* @return void
*
* @throws IOException if the file cannot be written to
*/
public static function dumpFile(string $filename, $content) {
@ -621,6 +648,12 @@ class Filesystem {
$dir = \dirname($filename);
if (is_link($filename) && $linkTarget = self::readlink($filename)) {
self::dumpFile(Path::makeAbsolute($linkTarget, $dir), $content);
return;
}
if (!is_dir($dir)) {
self::mkdir($dir);
}
@ -650,6 +683,8 @@ class Filesystem {
* @param string|resource $content The content to append
* @param bool $lock Whether the file should be locked when writing to it
*
* @return void
*
* @throws IOException If the file is not writable
*/
public static function appendToFile(string $filename, $content/* , bool $lock = false */) {
@ -704,10 +739,10 @@ class Filesystem {
/**
* @internal
*/
public static function handleError(int $type, string $msg) {
public static function handleError(int $type, string $msg): void {
self::$lastError = $msg;
}
private function __construct() {}
private function __construct() {
}
}

1
lib/Filesystem/FileNotFoundException.php

@ -9,7 +9,6 @@
declare(strict_types=1);
namespace MensBeam\Filesystem;
/**
* Exception class thrown when a file couldn't be found.
*

1
lib/Filesystem/IOException.php

@ -9,7 +9,6 @@
declare(strict_types=1);
namespace MensBeam\Filesystem;
/**
* Exception class thrown when a filesystem operation failure happens.
*

7
lib/Filesystem/InvalidArgumentException.php

@ -9,8 +9,5 @@
declare(strict_types=1);
namespace MensBeam\Filesystem;
/**
* @author Christian Flothmann <christian.flothmann@sensiolabs.de>
*/
class InvalidArgumentException extends \InvalidArgumentException {}
class InvalidArgumentException extends \InvalidArgumentException {
}

13
lib/Filesystem/RuntimeException.php

@ -0,0 +1,13 @@
<?php
/**
* @license MIT
* Copyright 2023 Dustin Wilson, J. King, et al.
* Original copyright 2023 Fabien Potencier
* See LICENSE and AUTHORS files for details
*/
declare(strict_types=1);
namespace MensBeam\Filesystem;
class RuntimeException extends \RuntimeException {
}

26
lib/Path.php

@ -8,7 +8,10 @@
declare(strict_types=1);
namespace MensBeam;
use MensBeam\Filesystem\{
InvalidArgumentException,
RuntimeException
};
/**
* Contains utility methods for handling path strings.
@ -25,12 +28,12 @@ class Path {
/**
* The number of buffer entries that triggers a cleanup operation.
*/
protected const CLEANUP_THRESHOLD = 1250;
private const CLEANUP_THRESHOLD = 1250;
/**
* The buffer size after the cleanup operation.
*/
protected const CLEANUP_SIZE = 1000;
private const CLEANUP_SIZE = 1000;
/**
* Buffers input/output of {@link canonicalize()}.
@ -179,7 +182,7 @@ class Path {
*
* The result is a canonical path.
*
* @throws \RuntimeException If your operating system or environment isn't supported
* @throws RuntimeException If your operating system or environment isn't supported
*/
public static function getHomeDirectory(): string {
// For UNIX support
@ -192,7 +195,7 @@ class Path {
return self::canonicalize(getenv('HOMEDRIVE') . getenv('HOMEPATH'));
}
throw new \RuntimeException("Cannot find the home directory path: Your environment or operating system isn't supported.");
throw new RuntimeException("Cannot find the home directory path: Your environment or operating system isn't supported.");
}
/**
@ -417,17 +420,17 @@ class Path {
*
* @param string $basePath an absolute base path
*
* @throws \InvalidArgumentException if the base path is not absolute or if
* @throws InvalidArgumentException if the base path is not absolute or if
* the given path is an absolute path with
* a different root than the base path
*/
public static function makeAbsolute(string $path, string $basePath): string {
if ('' === $basePath) {
throw new \InvalidArgumentException(sprintf('The base path must be a non-empty string. Got: "%s".', $basePath));
throw new InvalidArgumentException(sprintf('The base path must be a non-empty string. Got: "%s".', $basePath));
}
if (!self::isAbsolute($basePath)) {
throw new \InvalidArgumentException(sprintf('The base path "%s" is not an absolute path.', $basePath));
throw new InvalidArgumentException(sprintf('The base path "%s" is not an absolute path.', $basePath));
}
if (self::isAbsolute($path)) {
@ -516,12 +519,12 @@ class Path {
// If the passed path is absolute, but the base path is not, we
// cannot generate a relative path
if ('' !== $root && '' === $baseRoot) {
throw new \InvalidArgumentException(sprintf('The absolute path "%s" cannot be made relative to the relative path "%s". You should provide an absolute base path instead.', $path, $basePath));
throw new InvalidArgumentException(sprintf('The absolute path "%s" cannot be made relative to the relative path "%s". You should provide an absolute base path instead.', $path, $basePath));
}
// Fail if the roots of the two paths are different
if ($baseRoot && $root !== $baseRoot) {
throw new \InvalidArgumentException(sprintf('The path "%s" cannot be made relative to "%s", because they have different roots ("%s" and "%s").', $path, $basePath, $root, $baseRoot));
throw new InvalidArgumentException(sprintf('The path "%s" cannot be made relative to "%s", because they have different roots ("%s" and "%s").', $path, $basePath, $root, $baseRoot));
}
if ('' === $relativeBasePath) {
@ -788,5 +791,6 @@ class Path {
return strtolower($string);
}
private function __construct() {}
private function __construct() {
}
}

38
test

@ -0,0 +1,38 @@
#!/usr/bin/env php
<?php
/**
* @license MIT
* Copyright 2022 Dustin Wilson, et al.
* See LICENSE and AUTHORS files for details
*/
$dir = ini_get('extension_dir');
$php = escapeshellarg(\PHP_BINARY);
$code = escapeshellarg(__DIR__ . '/lib');
array_shift($argv);
foreach ($argv as $k => $v) {
if (in_array($v, ['--coverage', '--coverage-html'])) {
$argv[$k] = '--coverage-html tests/coverage';
}
}
$cmd = [
$php,
'-d opcache.enable_cli=0',
];
if (!extension_loaded('xdebug')) {
$cmd[] = '-d zend_extension=xdebug.so';
}
$cmd = implode(' ', [
...$cmd,
'-d xdebug.mode=coverage,develop,trace',
escapeshellarg(__DIR__ . '/vendor/bin/phpunit'),
'--configuration tests/phpunit.xml',
...$argv,
'--display-deprecations'
]);
passthru($cmd);

18
tests/bootstrap.php

@ -0,0 +1,18 @@
<?php
declare(strict_types=1);
namespace MensBeam\Catcher\Test;
ini_set('memory_limit', '2G');
ini_set('zend.assertions', '1');
ini_set('assert.exception', 'true');
error_reporting(\E_ALL);
define('CWD', dirname(__DIR__));
require_once CWD . '/vendor/autoload.php';
if (function_exists('xdebug_set_filter')) {
if (defined('XDEBUG_PATH_INCLUDE')) {
xdebug_set_filter(\XDEBUG_FILTER_CODE_COVERAGE, \XDEBUG_PATH_INCLUDE, [CWD . '/lib/']);
} else {
xdebug_set_filter(\XDEBUG_FILTER_CODE_COVERAGE, \XDEBUG_PATH_WHITELIST, [CWD . '/lib/']);
}
}

41
tests/cases/TestExceptions.php

@ -0,0 +1,41 @@
<?php
/**
* @license MIT
* Copyright 2022 Dustin Wilson, et al.
* See LICENSE and AUTHORS files for details
*/
declare(strict_types=1);
namespace MensBeam\Filesystem\Test;
use PHPUnit\Framework\TestCase;
use MensBeam\Filesystem\FileNotFoundException;
use MensBeam\Filesystem\IOException;
/**
* @covers \MensBeam\Filesystem\FileNotFoundException
* @covers \MensBeam\Filesystem\InvalidArgumentException
* @covers \MensBeam\Filesystem\IOException
*/
class TestExceptions extends TestCase {
public function testGetPath() {
$e = new IOException('', 0, null, '/foo');
$this->assertEquals('/foo', $e->getPath(), 'The pass should be returned.');
}
public function testGeneratedMessage() {
$e = new FileNotFoundException(null, 0, null, '/foo');
$this->assertEquals('/foo', $e->getPath());
$this->assertEquals('File "/foo" could not be found.', $e->getMessage(), 'A message should be generated.');
}
public function testGeneratedMessageWithoutPath() {
$e = new FileNotFoundException();
$this->assertEquals('File could not be found.', $e->getMessage(), 'A message should be generated.');
}
public function testCustomMessage() {
$e = new FileNotFoundException('bar', 0, null, '/foo');
$this->assertEquals('bar', $e->getMessage(), 'A custom message should be possible still.');
}
}

1731
tests/cases/TestFilesystem.php

File diff suppressed because it is too large

1010
tests/cases/TestPath.php

File diff suppressed because it is too large

146
tests/lib/FilesystemTestCase.php

@ -0,0 +1,146 @@
<?php
/**
* @license MIT
* Copyright 2022 Dustin Wilson, et al.
* See LICENSE and AUTHORS files for details
*/
declare(strict_types=1);
namespace MensBeam\Filesystem\Test;
use MensBeam\Filesystem as Fs,
PHPUnit\Framework\TestCase;
class FilesystemTestCase extends TestCase {
protected array $longPathNamesWindows = [];
protected string $workspace;
private int $umask;
private static ?bool $linkOnWindows = null;
private static ?bool $symlinkOnWindows = null;
public static function setUpBeforeClass(): void {
if ('\\' === \DIRECTORY_SEPARATOR) {
self::$linkOnWindows = true;
$originFile = tempnam(sys_get_temp_dir(), 'li');
$targetFile = tempnam(sys_get_temp_dir(), 'li');
if (true !== @link($originFile, $targetFile)) {
$report = error_get_last();
if (\is_array($report) && str_contains($report['message'], 'error code(1314)')) {
self::$linkOnWindows = false;
}
} else {
@unlink($targetFile);
}
self::$symlinkOnWindows = true;
$originDir = tempnam(sys_get_temp_dir(), 'sl');
$targetDir = tempnam(sys_get_temp_dir(), 'sl');
if (true !== @symlink($originDir, $targetDir)) {
$report = error_get_last();
if (\is_array($report) && str_contains($report['message'], 'error code(1314)')) {
self::$symlinkOnWindows = false;
}
} else {
@unlink($targetDir);
}
}
}
protected function setUp(): void {
$this->umask = umask(0);
$this->workspace = sys_get_temp_dir() . '/' . microtime(true) . '.' . mt_rand();
mkdir($this->workspace, 0777, true);
$this->workspace = realpath($this->workspace);
}
protected function tearDown(): void {
if (!empty($this->longPathNamesWindows)) {
foreach ($this->longPathNamesWindows as $path) {
exec('DEL ' . $path);
}
$this->longPathNamesWindows = [];
}
Fs::remove($this->workspace);
umask($this->umask);
}
/**
* @param int $expectedFilePerms Expected file permissions as three digits (i.e. 755)
* @param string $filePath
*/
protected function assertFilePermissions($expectedFilePerms, $filePath) {
$actualFilePerms = (int) substr(sprintf('%o', fileperms($filePath)), -3);
$this->assertEquals(
$expectedFilePerms,
$actualFilePerms,
sprintf('File permissions for %s must be %s. Actual %s', $filePath, $expectedFilePerms, $actualFilePerms)
);
}
protected function getFileOwnerId($filepath) {
$this->markAsSkippedIfPosixIsMissing();
$infos = stat($filepath);
return $infos['uid'];
}
protected function getFileOwner($filepath) {
$this->markAsSkippedIfPosixIsMissing();
return ($datas = posix_getpwuid($this->getFileOwnerId($filepath))) ? $datas['name'] : null;
}
protected function getFileGroupId($filepath) {
$this->markAsSkippedIfPosixIsMissing();
$infos = stat($filepath);
return $infos['gid'];
}
protected function getFileGroup($filepath) {
$this->markAsSkippedIfPosixIsMissing();
if ($datas = posix_getgrgid($this->getFileGroupId($filepath))) {
return $datas['name'];
}
$this->markTestSkipped('Unable to retrieve file group name');
}
protected function markAsSkippedIfLinkIsMissing() {
if (!\function_exists('link')) {
$this->markTestSkipped('link is not supported');
}
if ('\\' === \DIRECTORY_SEPARATOR && false === self::$linkOnWindows) {
$this->markTestSkipped('link requires "Create hard links" privilege on windows');
}
}
protected function markAsSkippedIfSymlinkIsMissing($relative = false) {
if ('\\' === \DIRECTORY_SEPARATOR && false === self::$symlinkOnWindows) {
$this->markTestSkipped('symlink requires "Create symbolic links" privilege on Windows');
}
// https://bugs.php.net/69473
if ($relative && '\\' === \DIRECTORY_SEPARATOR && 1 === \PHP_ZTS) {
$this->markTestSkipped('symlink does not support relative paths on thread safe Windows PHP versions');
}
}
protected function markAsSkippedIfChmodIsMissing() {
if ('\\' === \DIRECTORY_SEPARATOR) {
$this->markTestSkipped('chmod is not supported on Windows');
}
}
protected function markAsSkippedIfPosixIsMissing() {
if (!\function_exists('posix_isatty')) {
$this->markTestSkipped('Function posix_isatty is required.');
}
}
}

38
tests/lib/MockStream.php

@ -0,0 +1,38 @@
<?php
/**
* @license MIT
* Copyright 2022 Dustin Wilson, et al.
* See LICENSE and AUTHORS files for details
*/
declare(strict_types=1);
namespace MensBeam\Filesystem\Test;
/**
* Mock stream class to be used with stream_wrapper_register.
* stream_wrapper_register('mock', '\MensBeam\Filesystem\Tests\MockStream').
*/
class MockStream {
public $context;
/**
* Opens file or URL.
*
* @param string $path Specifies the URL that was passed to the original function
* @param string $mode The mode used to open the file, as detailed for fopen()
* @param int $options Holds additional flags set by the streams API
* @param string|null $opened_path If the path is opened successfully, and STREAM_USE_PATH is set in options,
* opened_path should be set to the full path of the file/resource that was actually opened
*/
public function stream_open(string $path, string $mode, int $options, string &$opened_path = null): bool {
return true;
}
/**
* @param string $path The file path or URL to stat
* @param int $flags Holds additional flags set by the streams API
*/
public function url_stat(string $path, int $flags): array {
return [];
}
}

22
tests/phpunit.xml

@ -0,0 +1,22 @@
<?xml version="1.0" encoding="UTF-8"?>
<phpunit xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" xsi:noNamespaceSchemaLocation="https://schema.phpunit.de/10.2/phpunit.xsd"
beStrictAboutOutputDuringTests="true"
beStrictAboutTestsThatDoNotTestAnything="true"
bootstrap="bootstrap.php"
cacheDirectory=".phpunit.cache"
colors="true"
executionOrder="defects"
requireCoverageMetadata="true"
>
<testsuites>
<testsuite name="Main">
<directory prefix="Test" suffix=".php">./cases</directory>
</testsuite>
</testsuites>
<coverage/>
<source>
<include>
<directory suffix=".php">../lib</directory>
</include>
</source>
</phpunit>
Loading…
Cancel
Save