Commit 856d6e14 authored by alexpott's avatar alexpott

Issue #1376122 by sun: Stream wrappers of parent site are leaking into all tests.

parent 2d476a5f
......@@ -202,12 +202,16 @@ function file_get_stream_wrappers($filter = STREAM_WRAPPERS_ALL) {
$wrappers_storage = &drupal_static(__FUNCTION__);
if (!isset($wrappers_storage)) {
$wrappers = \Drupal::moduleHandler()->invokeAll('stream_wrappers');
foreach ($wrappers as $scheme => $info) {
// Add defaults.
$wrappers[$scheme] += array('type' => STREAM_WRAPPERS_NORMAL);
$wrappers = array();
$container = \Drupal::getContainer();
if (is_object($container) && $container->has('module_handler')) {
$wrappers = \Drupal::moduleHandler()->invokeAll('stream_wrappers');
foreach ($wrappers as $scheme => $info) {
// Add defaults.
$wrappers[$scheme] += array('type' => STREAM_WRAPPERS_NORMAL);
}
\Drupal::moduleHandler()->alter('stream_wrappers', $wrappers);
}
drupal_alter('stream_wrappers', $wrappers);
$existing = stream_get_wrappers();
foreach ($wrappers as $scheme => $info) {
// We only register classes that implement our interface.
......
......@@ -15,7 +15,7 @@ public static function getInfo() {
return array(
'name' => 'File copying',
'description' => 'Tests the file copy function.',
'group' => 'File API',
'group' => 'File Managed API',
);
}
......
......@@ -15,7 +15,7 @@ public static function getInfo() {
return array(
'name' => 'File delete',
'description' => 'Tests the file delete function.',
'group' => 'File API',
'group' => 'File Managed API',
);
}
......
......@@ -17,7 +17,7 @@ public static function getInfo() {
return array(
'name' => 'File download',
'description' => 'Tests for file download/transfer functions.',
'group' => 'File API',
'group' => 'File Managed API',
);
}
......
......@@ -7,14 +7,14 @@
namespace Drupal\file\Tests;
use Drupal\system\Tests\File\FileTestBase;
use \stdClass;
use Drupal\file\FileInterface;
use Drupal\simpletest\WebTestBase;
/**
* Base class for file tests that use the file_test module to test uploads and
* hooks.
*/
abstract class FileManagedTestBase extends FileTestBase {
abstract class FileManagedTestBase extends WebTestBase {
/**
* Modules to enable.
......@@ -89,6 +89,50 @@ function assertFileHookCalled($hook, $expected_count = 1, $message = NULL) {
$this->assertEqual($actual_count, $expected_count, $message);
}
/**
* Asserts that two files have the same values (except timestamp).
*
* @param \Drupal\file\FileInterface $before
* File object to compare.
* @param \Drupal\file\FileInterface $after
* File object to compare.
*/
function assertFileUnchanged(FileInterface $before, FileInterface $after) {
$this->assertEqual($before->id(), $after->id(), t('File id is the same: %file1 == %file2.', array('%file1' => $before->id(), '%file2' => $after->id())), 'File unchanged');
$this->assertEqual($before->getOwner()->id(), $after->getOwner()->id(), t('File owner is the same: %file1 == %file2.', array('%file1' => $before->getOwner()->id(), '%file2' => $after->getOwner()->id())), 'File unchanged');
$this->assertEqual($before->getFilename(), $after->getFilename(), t('File name is the same: %file1 == %file2.', array('%file1' => $before->getFilename(), '%file2' => $after->getFilename())), 'File unchanged');
$this->assertEqual($before->getFileUri(), $after->getFileUri(), t('File path is the same: %file1 == %file2.', array('%file1' => $before->getFileUri(), '%file2' => $after->getFileUri())), 'File unchanged');
$this->assertEqual($before->getMimeType(), $after->getMimeType(), t('File MIME type is the same: %file1 == %file2.', array('%file1' => $before->getMimeType(), '%file2' => $after->getMimeType())), 'File unchanged');
$this->assertEqual($before->getSize(), $after->getSize(), t('File size is the same: %file1 == %file2.', array('%file1' => $before->getSize(), '%file2' => $after->getSize())), 'File unchanged');
$this->assertEqual($before->isPermanent(), $after->isPermanent(), t('File status is the same: %file1 == %file2.', array('%file1' => $before->isPermanent(), '%file2' => $after->isPermanent())), 'File unchanged');
}
/**
* Asserts that two files are not the same by comparing the fid and filepath.
*
* @param \Drupal\file\FileInterface $file1
* File object to compare.
* @param \Drupal\file\FileInterface $file2
* File object to compare.
*/
function assertDifferentFile(FileInterface $file1, FileInterface $file2) {
$this->assertNotEqual($file1->id(), $file2->id(), t('Files have different ids: %file1 != %file2.', array('%file1' => $file1->id(), '%file2' => $file2->id())), 'Different file');
$this->assertNotEqual($file1->getFileUri(), $file2->getFileUri(), t('Files have different paths: %file1 != %file2.', array('%file1' => $file1->getFileUri(), '%file2' => $file2->getFileUri())), 'Different file');
}
/**
* Asserts that two files are the same by comparing the fid and filepath.
*
* @param \Drupal\file\FileInterface $file1
* File object to compare.
* @param \Drupal\file\FileInterface $file2
* File object to compare.
*/
function assertSameFile(FileInterface $file1, FileInterface $file2) {
$this->assertEqual($file1->id(), $file2->id(), t('Files have the same ids: %file1 == %file2.', array('%file1' => $file1->id(), '%file2-fid' => $file2->id())), 'Same file');
$this->assertEqual($file1->getFileUri(), $file2->getFileUri(), t('Files have the same path: %file1 == %file2.', array('%file1' => $file1->getFileUri(), '%file2' => $file2->getFileUri())), 'Same file');
}
/**
* Create a file and save it to the files table and assert that it occurs
* correctly.
......@@ -106,7 +150,7 @@ function assertFileHookCalled($hook, $expected_count = 1, $message = NULL) {
* File entity.
*/
function createFile($filepath = NULL, $contents = NULL, $scheme = NULL) {
$file = new stdClass();
$file = new \stdClass();
$file->uri = $this->createUri($filepath, $contents, $scheme);
$file->filename = drupal_basename($file->uri);
$file->filemime = 'text/plain';
......@@ -121,4 +165,41 @@ function createFile($filepath = NULL, $contents = NULL, $scheme = NULL) {
return entity_create('file', (array) $file);
}
/**
* Creates a file and returns its URI.
*
* @param string $filepath
* Optional string specifying the file path. If none is provided then a
* randomly named file will be created in the site's files directory.
* @param string $contents
* Optional contents to save into the file. If a NULL value is provided an
* arbitrary string will be used.
* @param string $scheme
* Optional string indicating the stream scheme to use. Drupal core includes
* public, private, and temporary. The public wrapper is the default.
*
* @return string
* File URI.
*/
function createUri($filepath = NULL, $contents = NULL, $scheme = NULL) {
if (!isset($filepath)) {
// Prefix with non-latin characters to ensure that all file-related
// tests work with international filenames.
$filepath = 'Файл для тестирования ' . $this->randomName();
}
if (!isset($scheme)) {
$scheme = file_default_scheme();
}
$filepath = $scheme . '://' . $filepath;
if (!isset($contents)) {
$contents = "file_put_contents() doesn't seem to appreciate empty strings so let's put in some data.";
}
file_put_contents($filepath, $contents);
$this->assertTrue(is_file($filepath), t('The test file exists on the disk.'), 'Create test file');
return $filepath;
}
}
......@@ -15,7 +15,7 @@ public static function getInfo() {
return array(
'name' => 'File loading',
'description' => 'Tests the file_load() function.',
'group' => 'File API',
'group' => 'File Managed API',
);
}
......
......@@ -15,7 +15,7 @@ public static function getInfo() {
return array(
'name' => 'File moving',
'description' => 'Tests the file move function.',
'group' => 'File API',
'group' => 'File Managed API',
);
}
......
......@@ -21,7 +21,7 @@ class RemoteFileSaveUploadTest extends SaveUploadTest {
public static function getInfo() {
$info = parent::getInfo();
$info['group'] = 'File API (remote)';
$info['group'] = 'File Managed API (remote)';
return $info;
}
......
......@@ -15,7 +15,7 @@ public static function getInfo() {
return array(
'name' => 'File save data',
'description' => 'Tests the file save data function.',
'group' => 'File API',
'group' => 'File Managed API',
);
}
......
......@@ -17,7 +17,7 @@ public static function getInfo() {
return array(
'name' => 'File saving',
'description' => 'File saving tests',
'group' => 'File API',
'group' => 'File Managed API',
);
}
......
......@@ -30,7 +30,7 @@ public static function getInfo() {
return array(
'name' => 'File uploading',
'description' => 'Tests the file uploading functions.',
'group' => 'File API',
'group' => 'File Managed API',
);
}
......
......@@ -15,7 +15,7 @@ public static function getInfo() {
return array(
'name' => 'File space used tests',
'description' => 'Tests the spaceUsed() function.',
'group' => 'File API',
'group' => 'File Managed API',
);
}
......
......@@ -15,7 +15,7 @@ public static function getInfo() {
return array(
'name' => 'File usage',
'description' => 'Tests the file usage functions.',
'group' => 'File API',
'group' => 'File Managed API',
);
}
......
......@@ -15,7 +15,7 @@ public static function getInfo() {
return array(
'name' => 'File validate',
'description' => 'Tests the file_validate() function.',
'group' => 'File API',
'group' => 'File Managed API',
);
}
......
......@@ -15,7 +15,7 @@ public static function getInfo() {
return array(
'name' => 'File validator tests',
'description' => 'Tests the functions used to validate uploaded files.',
'group' => 'File API',
'group' => 'File Managed API',
);
}
......
......@@ -64,6 +64,15 @@ abstract class DrupalUnitTestBase extends UnitTestBase {
*/
protected $keyValueFactory;
/**
* A list of stream wrappers that have been registered for this test.
*
* @see \Drupal\simpletest\DrupalUnitTestBase::registerStreamWrapper()
*
* @var array
*/
private $streamWrappers = array();
/**
* Overrides \Drupal\simpletest\UnitTestBase::__construct().
*/
......@@ -91,6 +100,7 @@ protected function setUp() {
$this->keyValueFactory = new KeyValueMemoryFactory();
parent::setUp();
// Build a minimal, partially mocked environment for unit tests.
$this->containerBuild(\Drupal::getContainer());
// Make sure it survives kernel rebuilds.
......@@ -133,10 +143,30 @@ protected function setUp() {
$this->enableModules($modules, FALSE);
// In order to use theme functions default theme config needs to exist.
\Drupal::config('system.theme')->set('default', 'stark');
// Tests based on this class are entitled to use Drupal's File and
// StreamWrapper APIs.
// @todo Move StreamWrapper management into DrupalKernel.
// @see https://drupal.org/node/2028109
// The public stream wrapper only depends on the file_public_path setting,
// which is provided by UnitTestBase::setUp().
$this->registerStreamWrapper('public', 'Drupal\Core\StreamWrapper\PublicStream');
// The temporary stream wrapper is able to operate both with and without
// configuration.
$this->registerStreamWrapper('temporary', 'Drupal\Core\StreamWrapper\TemporaryStream');
}
protected function tearDown() {
$this->kernel->shutdown();
// Before tearing down the test environment, ensure that no stream wrapper
// of this test leaks into the parent environment. Unlike all other global
// state variables in Drupal, stream wrappers are a global state construct
// of PHP core, which has to be maintained manually.
// @todo Move StreamWrapper management into DrupalKernel.
// @see https://drupal.org/node/2028109
foreach ($this->streamWrappers as $scheme) {
$this->unregisterStreamWrapper($scheme);
}
parent::tearDown();
}
......@@ -340,4 +370,55 @@ protected function disableModules(array $modules) {
)));
}
/**
* Registers a stream wrapper for this test.
*
* @param string $scheme
* The scheme to register.
* @param string $class
* The fully qualified class name to register.
* @param int $type
* The Drupal Stream Wrapper API type. Defaults to
* STREAM_WRAPPERS_LOCAL_NORMAL.
*/
protected function registerStreamWrapper($scheme, $class, $type = STREAM_WRAPPERS_LOCAL_NORMAL) {
if (isset($this->streamWrappers[$scheme])) {
$this->unregisterStreamWrapper($scheme);
}
$this->streamWrappers[$scheme] = $scheme;
if (($type & STREAM_WRAPPERS_LOCAL) == STREAM_WRAPPERS_LOCAL) {
stream_wrapper_register($scheme, $class);
}
else {
stream_wrapper_register($scheme, $class, STREAM_IS_URL);
}
// @todo Revamp Drupal's stream wrapper API for D8.
// @see https://drupal.org/node/2028109
$wrappers = &drupal_static('file_get_stream_wrappers');
$wrappers[$scheme] = array(
'type' => $type,
'class' => $class,
);
$wrappers[STREAM_WRAPPERS_ALL] = $wrappers;
}
/**
* Unregisters a stream wrapper previously registered by this test.
*
* DrupalUnitTestBase::tearDown() automatically cleans up all registered
* stream wrappers, so this usually does not have to be called manually.
*
* @param string $scheme
* The scheme to unregister.
*/
protected function unregisterStreamWrapper($scheme) {
stream_wrapper_unregister($scheme);
unset($this->streamWrappers[$scheme]);
// @todo Revamp Drupal's stream wrapper API for D8.
// @see https://drupal.org/node/2028109
$wrappers = &drupal_static('file_get_stream_wrappers');
unset($wrappers[$scheme]);
unset($wrappers[STREAM_WRAPPERS_ALL][$scheme]);
}
}
......@@ -991,6 +991,19 @@ private function prepareEnvironment() {
// Create and set new configuration directories.
$this->prepareConfigDirectories();
// Unregister all custom stream wrappers of the parent site.
// Availability of Drupal stream wrappers varies by test base class:
// - UnitTestBase operates in a completely empty environment.
// - DrupalUnitTestBase supports and maintains stream wrappers in a custom
// way.
// - WebTestBase re-initializes Drupal stream wrappers after installation.
// The original stream wrappers are restored after the test run.
// @see TestBase::tearDown()
$wrappers = file_get_stream_wrappers();
foreach ($wrappers as $scheme => $info) {
stream_wrapper_unregister($scheme);
}
// Reset statics before the old container is replaced so that objects with a
// __destruct() method still have access to it.
// All static variables need to be reset before the database prefix is
......@@ -1150,10 +1163,6 @@ private function restoreEnvironment() {
}
}
// In case a fatal error occurred that was not in the test process read the
// log to pick up any fatal errors.
simpletest_log_read($this->testId, $this->databasePrefix, get_class($this), TRUE);
// Delete temporary files directory.
file_unmanaged_delete_recursive($this->originalFileDirectory . '/simpletest/' . substr($this->databasePrefix, 10), array($this, 'filePreDeleteCallback'));
......@@ -1183,6 +1192,17 @@ private function restoreEnvironment() {
// Restore original statics and globals.
\Drupal::setContainer($this->originalContainer);
$GLOBALS['config_directories'] = $this->originalConfigDirectories;
// Re-initialize original stream wrappers of the parent site.
// This must happen after static variables have been reset and the original
// container and $config_directories are restored, as simpletest_log_read()
// uses the public stream wrapper to locate the error.log.
file_get_stream_wrappers();
// In case a fatal error occurred that was not in the test process read the
// log to pick up any fatal errors.
simpletest_log_read($this->testId, $this->databasePrefix, get_class($this), TRUE);
if (isset($this->originalPrefix)) {
drupal_valid_test_ua($this->originalPrefix);
}
......
......@@ -7,10 +7,12 @@
namespace Drupal\system\Tests\File;
use Drupal\simpletest\WebTestBase;
/**
* File system configuration related tests.
*/
class ConfigTest extends FileTestBase {
class ConfigTest extends WebTestBase {
public static function getInfo() {
return array(
......
......@@ -7,68 +7,43 @@
namespace Drupal\system\Tests\File;
use Drupal\file\FileInterface;
use Drupal\simpletest\WebTestBase;
use Drupal\simpletest\DrupalUnitTestBase;
/**
* Base class for file tests that adds some additional file specific
* assertions and helper functions.
*/
abstract class FileTestBase extends WebTestBase {
function setUp() {
parent::setUp();
// Make sure that custom stream wrappers are registered.
// @todo This has the potential to be a major bug deeply buried in File API;
// file_unmanaged_*() API functions and test functions are invoking native
// PHP functions directly, whereas Drupal's custom stream wrappers are not
// registered yet.
file_get_stream_wrappers();
}
abstract class FileTestBase extends DrupalUnitTestBase {
/**
* Check that two files have the same values for all fields other than the
* timestamp.
* Modules to enable.
*
* @param \Drupal\file\FileInterface $before
* File object to compare.
* @param \Drupal\file\FileInterface $after
* File object to compare.
* @var array
*/
function assertFileUnchanged(FileInterface $before, FileInterface $after) {
$this->assertEqual($before->id(), $after->id(), t('File id is the same: %file1 == %file2.', array('%file1' => $before->id(), '%file2' => $after->id())), 'File unchanged');
$this->assertEqual($before->getOwner()->id(), $after->getOwner()->id(), t('File owner is the same: %file1 == %file2.', array('%file1' => $before->getOwner()->id(), '%file2' => $after->getOwner()->id())), 'File unchanged');
$this->assertEqual($before->getFilename(), $after->getFilename(), t('File name is the same: %file1 == %file2.', array('%file1' => $before->getFilename(), '%file2' => $after->getFilename())), 'File unchanged');
$this->assertEqual($before->getFileUri(), $after->getFileUri(), t('File path is the same: %file1 == %file2.', array('%file1' => $before->getFileUri(), '%file2' => $after->getFileUri())), 'File unchanged');
$this->assertEqual($before->getMimeType(), $after->getMimeType(), t('File MIME type is the same: %file1 == %file2.', array('%file1' => $before->getMimeType(), '%file2' => $after->getMimeType())), 'File unchanged');
$this->assertEqual($before->getSize(), $after->getSize(), t('File size is the same: %file1 == %file2.', array('%file1' => $before->getSize(), '%file2' => $after->getSize())), 'File unchanged');
$this->assertEqual($before->isPermanent(), $after->isPermanent(), t('File status is the same: %file1 == %file2.', array('%file1' => $before->isPermanent(), '%file2' => $after->isPermanent())), 'File unchanged');
}
public static $modules = array('system');
/**
* Check that two files are not the same by comparing the fid and filepath.
* A stream wrapper scheme to register for the test.
*
* @param \Drupal\file\FileInterface $file1
* File object to compare.
* @param \Drupal\file\FileInterface $file2
* File object to compare.
* @var string
*/
function assertDifferentFile(FileInterface $file1, FileInterface $file2) {
$this->assertNotEqual($file1->id(), $file2->id(), t('Files have different ids: %file1 != %file2.', array('%file1' => $file1->id(), '%file2' => $file2->id())), 'Different file');
$this->assertNotEqual($file1->getFileUri(), $file2->getFileUri(), t('Files have different paths: %file1 != %file2.', array('%file1' => $file1->getFileUri(), '%file2' => $file2->getFileUri())), 'Different file');
}
protected $scheme;
/**
* Check that two files are the same by comparing the fid and filepath.
* A fully-qualified stream wrapper class name to register for the test.
*
* @param \Drupal\file\FileInterface $file1
* File object to compare.
* @param \Drupal\file\FileInterface $file2
* File object to compare.
* @var string
*/
function assertSameFile(FileInterface $file1, FileInterface $file2) {
$this->assertEqual($file1->id(), $file2->id(), t('Files have the same ids: %file1 == %file2.', array('%file1' => $file1->id(), '%file2-fid' => $file2->id())), 'Same file');
$this->assertEqual($file1->getFileUri(), $file2->getFileUri(), t('Files have the same path: %file1 == %file2.', array('%file1' => $file1->getFileUri(), '%file2' => $file2->getFileUri())), 'Same file');
protected $classname;
function setUp() {
parent::setUp();
$this->installConfig(array('system'));
$this->registerStreamWrapper('private', 'Drupal\Core\StreamWrapper\PrivateStream');
if (isset($this->scheme)) {
$this->registerStreamWrapper($this->scheme, $this->classname);
}
}
/**
......@@ -170,7 +145,7 @@ function createDirectory($path = NULL) {
return $path;
}
/**
/**
* Create a file and return the URI of it.
*
* @param $filepath
......
......@@ -7,12 +7,10 @@
namespace Drupal\system\Tests\File;
use Drupal\simpletest\WebTestBase;
/**
* Tests for file_get_mimetype().
*/
class MimeTypeTest extends WebTestBase {
class MimeTypeTest extends FileTestBase {
/**
* Modules to enable.
......
......@@ -13,13 +13,17 @@
class ReadOnlyStreamWrapperTest extends FileTestBase {
/**
* Modules to enable.
* A stream wrapper scheme to register for the test.
*
* @var array
* @var string
*/
public static $modules = array('file_test');
protected $scheme = 'dummy-readonly';
/**
* A fully-qualified stream wrapper class name to register for the test.
*
* @var string
*/
protected $classname = 'Drupal\file_test\DummyReadOnlyStreamWrapper';
public static function getInfo() {
......@@ -30,16 +34,6 @@ public static function getInfo() {
);
}