Skip to content

Commit

Permalink
Added tests and test utils for mocking functions.
Browse files Browse the repository at this point in the history
  • Loading branch information
frankdejonge committed Dec 24, 2019
1 parent 8863235 commit 319ac6b
Show file tree
Hide file tree
Showing 8 changed files with 192 additions and 33 deletions.
6 changes: 3 additions & 3 deletions composer.json
Original file line number Diff line number Diff line change
Expand Up @@ -9,11 +9,11 @@
},
"require": {
"php": "^7.2",
"ext-fileinfo": "*"
"ext-fileinfo": "*",
"ext-posix": "*"
},
"require-dev": {
"phpunit/phpunit": "^8.5",
"ext-posix": "*"
"phpunit/phpunit": "^8.5"
},
"license": "MIT",
"authors": [
Expand Down
12 changes: 12 additions & 0 deletions mocked-functions.php
Original file line number Diff line number Diff line change
@@ -0,0 +1,12 @@
<?php

namespace League\Flysystem\Local {
function rmdir(...$arguments)
{
if ( ! is_mocked('rmdir')) {
return \rmdir(...$arguments);
}

return return_mocked_value('rmdir');
}
}
5 changes: 5 additions & 0 deletions phpunit.php
Original file line number Diff line number Diff line change
@@ -0,0 +1,5 @@
<?php

include __DIR__.'/vendor/autoload.php';
include __DIR__.'/test-functions.php';
include __DIR__.'/mocked-functions.php';
2 changes: 1 addition & 1 deletion phpunit.xml.dist
Original file line number Diff line number Diff line change
@@ -1,5 +1,5 @@
<?xml version="1.0" encoding="UTF-8"?>
<phpunit colors="true" bootstrap="vendor/autoload.php">
<phpunit colors="true" bootstrap="phpunit.php">
<testsuites>
<testsuite name="Flysystem">
<directory suffix="Test.php">src/</directory>
Expand Down
38 changes: 18 additions & 20 deletions src/Local/LocalFilesystem.php
Original file line number Diff line number Diff line change
Expand Up @@ -20,7 +20,6 @@
use League\Flysystem\UnableToSetVisibility;
use League\Flysystem\UnableToUpdateFile;
use League\Flysystem\UnableToWriteFile;
use League\Flysystem\UnreadableFileEncountered;
use League\Flysystem\Visibility;
use RecursiveDirectoryIterator;
use RecursiveIteratorIterator;
Expand All @@ -34,8 +33,8 @@
use function file_exists;
use function is_dir;
use function is_file;
use function mkdir;
use function rename;
use function rmdir;
use function stream_copy_to_stream;
use function unlink;

Expand Down Expand Up @@ -177,8 +176,9 @@ public function deleteDirectory(string $prefix): void

/** @var SplFileInfo $file */
foreach ($contents as $file) {
$this->guardAgainstUnreadableFileInfo($file);
$this->deleteFileInfoObject($file);
if ( ! $this->deleteFileInfoObject($file)) {
throw UnableToDeleteDirectory::atLocation($prefix, "Unable to delete file at " . $file->getPathname());
}
}

unset($contents);
Expand All @@ -197,25 +197,15 @@ private function listDirectoryRecursively(
);
}

protected function guardAgainstUnreadableFileInfo(SplFileInfo $file)
{
if ( ! $file->isReadable()) {
$location = $file->getType() === 'link' ? $file->getPathname() : $file->getRealPath();
throw UnreadableFileEncountered::atLocation($location);
}
}

protected function deleteFileInfoObject(SplFileInfo $file): void
protected function deleteFileInfoObject(SplFileInfo $file): bool
{
switch ($file->getType()) {
case 'dir':
rmdir($file->getRealPath());
break;
return @rmdir($file->getRealPath());
case 'link':
unlink($file->getPathname());
break;
return @unlink($file->getPathname());
default:
unlink($file->getRealPath());
return @unlink($file->getRealPath());
}
}

Expand All @@ -232,7 +222,7 @@ public function listContents(string $path, bool $recursive): Generator

foreach ($iterator as $fileInfo) {
if ($fileInfo->isLink()) {
if ($this->linkHandling & self::DISALLOW_LINKS) {
if ($this->linkHandling & self::SKIP_LINKS) {
continue;
}
throw SymbolicLinkEncountered::atLocation($fileInfo->getPathname());
Expand Down Expand Up @@ -296,8 +286,16 @@ public function fileExists(string $location): bool
return is_file($location);
}

public function createDirectory(string $location, Config $config): void
public function createDirectory(string $path, Config $config): void
{
$location = $this->prefixer->prefixPath($path);
$visibility = $config->get('visibility', $config->get('directory_visibilty'));
$permissions = $this->resolveDirectoryVisibility($visibility);
error_clear_last();

if ( ! @mkdir($location, $permissions, true)) {
throw UnableToCreateDirectory::atLocation($path, error_get_last()['message'] ?? '');
}
}

public function setVisibility(string $location, $visibility): void
Expand Down
132 changes: 124 additions & 8 deletions src/Local/LocalFilesystemTest.php
Original file line number Diff line number Diff line change
Expand Up @@ -6,34 +6,45 @@

use League\Flysystem\Config;
use League\Flysystem\StorageAttributes;
use League\Flysystem\SymbolicLinkEncountered;
use League\Flysystem\UnableToCreateDirectory;
use League\Flysystem\UnableToDeleteDirectory;
use League\Flysystem\UnableToDeleteFile;
use League\Flysystem\UnableToSetVisibility;
use League\Flysystem\UnableToUpdateFile;
use League\Flysystem\UnableToWriteFile;
use League\Flysystem\Visibility;
use PHPUnit\Framework\TestCase;

use function array_shift;
use function file_get_contents;
use function file_put_contents;
use function fileperms;
use function fwrite;
use function getenv;
use function is_dir;
use function is_string;
use function iterator_to_array;
use function mkdir;
use function rewind;
use function substr;
use function symlink;

use const LOCK_EX;

class LocalFilesystemTest extends TestCase
{
private const ROOT = __DIR__ . '/test-root';

protected function setUp(): void
{
reset_function_mocks();
$this->deleteDirectory(static::ROOT);
}

protected function tearDown(): void
{
reset_function_mocks();
$this->deleteDirectory(static::ROOT);
}

Expand Down Expand Up @@ -150,8 +161,7 @@ public function writing_a_file_with_a_stream_and_visibility()
public function writing_a_file_with_visibility()
{
$adapter = new LocalFilesystem(
static::ROOT,
new PublicAndPrivateVisibilityInterpreting()
static::ROOT, new PublicAndPrivateVisibilityInterpreting()
);
$adapter->write('/file.txt', 'contents', new Config(['visibility' => 'private']));
$this->assertFileContains(static::ROOT . '/file.txt', 'contents');
Expand Down Expand Up @@ -253,10 +263,7 @@ public function deleting_a_file_that_does_not_exist()
*/
public function deleting_a_file_that_cannot_be_deleted()
{
if (posix_getuid() === 0 || getenv('FLYSYSTEM_TEST_DELETE_FAILURE') !== 'yes') {
$this->markTestSkipped('Skipping this out of precaution.');
}

$this->maybeSkipDangerousTests();
$this->expectException(UnableToDeleteFile::class);
$adapter = new LocalFilesystem('/');
$adapter->delete('/etc/hosts');
Expand Down Expand Up @@ -290,7 +297,7 @@ public function listing_contents()
{
$adapter = new LocalFilesystem(static::ROOT);
$adapter->write('directory/filename.txt', 'content', new Config());
$adapter->write('filename.txt', 'content' , new Config());
$adapter->write('filename.txt', 'content', new Config());
$contents = iterator_to_array($adapter->listContents('/', false));

$this->assertCount(2, $contents);
Expand All @@ -304,7 +311,7 @@ public function listing_contents_recursively()
{
$adapter = new LocalFilesystem(static::ROOT);
$adapter->write('directory/filename.txt', 'content', new Config());
$adapter->write('filename.txt', 'content' , new Config());
$adapter->write('filename.txt', 'content', new Config());
$contents = iterator_to_array($adapter->listContents('/', true));

$this->assertCount(3, $contents);
Expand All @@ -322,6 +329,106 @@ public function listing_a_non_existing_directory()
$this->assertCount(0, $contents);
}

/**
* @test
*/
public function listing_directory_contents_with_link_skipping()
{
$adapter = new LocalFilesystem(static::ROOT, null, LOCK_EX, LocalFilesystem::SKIP_LINKS);
file_put_contents(static::ROOT . '/file.txt', 'content');
symlink(static::ROOT . '/file.txt', static::ROOT . '/link.txt');

$contents = iterator_to_array($adapter->listContents('/', true));

$this->assertCount(1, $contents);
}

/**
* @test
*/
public function listing_directory_contents_with_disallowing_links()
{
$this->expectException(SymbolicLinkEncountered::class);
$adapter = new LocalFilesystem(static::ROOT, null, LOCK_EX, LocalFilesystem::DISALLOW_LINKS);
file_put_contents(static::ROOT . '/file.txt', 'content');
symlink(static::ROOT . '/file.txt', static::ROOT . '/link.txt');

$adapter->listContents('/', true)->next();
}

/**
* @test
*/
public function deleting_a_directory()
{
$adapter = new LocalFilesystem(static::ROOT);
mkdir(static::ROOT . '/directory/subdir/', 0744, true);
$this->assertDirectoryExists(static::ROOT . '/directory/subdir/');
file_put_contents(static::ROOT . '/directory/subdir/file.txt', 'content');
symlink(static::ROOT . '/directory/subdir/file.txt', static::ROOT . '/directory/subdir/link.txt');
$adapter->deleteDirectory('directory/subdir');
$this->assertDirectoryNotExists(static::ROOT . '/directory/subdir/');
$adapter->deleteDirectory('directory');
$this->assertDirectoryNotExists(static::ROOT . '/directory/');
}

/**
* @test
*/
public function deleting_a_non_existing_directory()
{
$adapter = new LocalFilesystem(static::ROOT);
$adapter->deleteDirectory('/non-existing-directory/');
$this->assertTrue(true);
}

/**
* @test
*/
public function not_being_able_to_delete_a_directory()
{
$this->expectException(UnableToDeleteDirectory::class);

mock_function('rmdir', false);

$adapter = new LocalFilesystem(static::ROOT);
$adapter->createDirectory('/etc/', new Config());
$adapter->deleteDirectory('/etc/');
}

/**
* @test
*/
public function not_being_able_to_delete_a_sub_directory()
{
$this->expectException(UnableToDeleteDirectory::class);

mock_function('rmdir', false);

$adapter = new LocalFilesystem(static::ROOT);
$adapter->createDirectory('/etc/subdirectory/', new Config());
$adapter->deleteDirectory('/etc/');
}

/**
* @test
*/
public function creating_a_directory()
{
$adapter = new LocalFilesystem(static::ROOT);
$adapter->createDirectory('public', new Config(['visibility' => 'public']));
$this->assertDirectoryExists(static::ROOT . '/public');
$this->assertFileHasPermissions(static::ROOT . '/public', 0755);

$adapter->createDirectory('private', new Config(['visibility' => 'private']));
$this->assertDirectoryExists(static::ROOT . '/private');
$this->assertFileHasPermissions(static::ROOT . '/private', 0700);

$adapter->createDirectory('also_private', new Config(['directory_visibility' => 'private']));
$this->assertDirectoryExists(static::ROOT . '/also_private');
$this->assertFileHasPermissions(static::ROOT . '/also_private', 0700);
}

private function streamWithContents(string $contents)
{
$stream = fopen('php://temp', 'w+b');
Expand Down Expand Up @@ -352,4 +459,13 @@ private function assertFileContains(string $file, string $expectedContents): voi
$contents = file_get_contents($file);
$this->assertEquals($expectedContents, $contents);
}

private function maybeSkipDangerousTests(): void
{
if (posix_getuid() === 0 || getenv('FLYSYSTEM_TEST_DANGEROUS_THINGS') !== 'yes') {
$this->markTestSkipped(
'Skipping this out of precaution. Use FLYSYSTEM_TEST_DANGEROUS_THINGS=yes to test them'
);
}
}
}
2 changes: 1 addition & 1 deletion src/Local/PublicAndPrivateVisibilityInterpreting.php
Original file line number Diff line number Diff line change
Expand Up @@ -38,7 +38,7 @@ public function __construct(
int $filePublic = 0644,
int $filePrivate = 0600,
int $directoryPublic = 0755,
int $directoryPrivate = 0744,
int $directoryPrivate = 0700,
string $defaultForDirectories = Visibility::PRIVATE
) {
$this->filePublic = $filePublic;
Expand Down
28 changes: 28 additions & 0 deletions test-functions.php
Original file line number Diff line number Diff line change
@@ -0,0 +1,28 @@
<?php

declare(strict_types=1);

function return_mocked_value(string $name)
{
return array_shift($GLOBALS['__FM:RETURNS:' . $name]);
}

function reset_function_mocks()
{
foreach ($GLOBALS as $name) {
if (is_string($name) && substr($name, 0, 5) === '__FM:') {
unset($GLOBALS[$name]);
}
}
}

function mock_function(string $name, ...$returns)
{
$GLOBALS['__FM:FUNC_IS_MOCKED:' . $name] = 'yes';
$GLOBALS['__FM:RETURNS:' . $name] = $returns;
}

function is_mocked(string $name)
{
return ($GLOBALS['__FM:FUNC_IS_MOCKED:' . $name] ?? 'no') === 'yes';
}

0 comments on commit 319ac6b

Please sign in to comment.