Update to Drupal 8.2.0. For more information, see https://www.drupal.org/project/drupal/releases/8.2.0

This commit is contained in:
Pantheon Automation 2016-10-06 15:16:20 -07:00 committed by Greg Anderson
parent 2f563ab520
commit f1c8716f57
1732 changed files with 52334 additions and 11780 deletions

View file

@ -0,0 +1,45 @@
<?php
namespace Drupal\FunctionalJavascriptTests\Ajax;
use Drupal\FunctionalJavascriptTests\JavascriptTestBase;
/**
* Tests that AJAX responses use the current theme.
*
* @group Ajax
*/
class AjaxThemeTest extends JavascriptTestBase {
/**
* {@inheritdoc}
*/
public static $modules = ['ajax_test'];
public function testAjaxWithAdminRoute() {
\Drupal::service('theme_installer')->install(['stable', 'seven']);
$theme_config = \Drupal::configFactory()->getEditable('system.theme');
$theme_config->set('admin', 'seven');
$theme_config->set('default', 'stable');
$theme_config->save();
$account = $this->drupalCreateUser(['view the administration theme']);
$this->drupalLogin($account);
// First visit the site directly via the URL. This should render it in the
// admin theme.
$this->drupalGet('admin/ajax-test/theme');
$assert = $this->assertSession();
$assert->pageTextContains('Current theme: seven');
// Now click the modal, which should also use the admin theme.
$this->drupalGet('ajax-test/dialog');
$assert->pageTextNotContains('Current theme: stable');
$this->clickLink('Link 8 (ajax)');
$assert->assertWaitOnAjaxRequest();
$assert->pageTextContains('Current theme: stable');
$assert->pageTextNotContains('Current theme: seven');
}
}

View file

@ -0,0 +1,61 @@
<?php
namespace Drupal\FunctionalJavascriptTests\Core\Session;
use Drupal\FunctionalJavascriptTests\JavascriptTestBase;
use Drupal\menu_link_content\Entity\MenuLinkContent;
/**
* Tests that sessions don't expire.
*
* @group session
*/
class SessionTest extends JavascriptTestBase {
/**
* {@inheritdoc}
*/
public static $modules = ['menu_link_content', 'block'];
/**
* {@inheritdoc}
*/
protected function setUp() {
parent::setUp();
$account = $this->drupalCreateUser();
$this->drupalLogin($account);
$menu_link_content = MenuLinkContent::create([
'title' => 'Link to front page',
'menu_name' => 'tools',
'link' => ['uri' => 'route:<front>'],
]);
$menu_link_content->save();
$this->drupalPlaceBlock('system_menu_block:tools');
}
/**
* Tests that the session doesn't expire.
*
* Makes sure that drupal_valid_test_ua() works for multiple requests
* performed by the Mink browser. The SIMPLETEST_USER_AGENT cookie must always
* be valid.
*/
public function testSessionExpiration() {
// Visit the front page and click the link back to the front page a large
// number of times.
$this->drupalGet('<front>');
$session_assert = $this->assertSession();
$page = $this->getSession()->getPage();
for ($i = 0; $i < 25; $i++) {
$page->clickLink('Link to front page');
$session_assert->statusCodeEquals(200);
}
}
}

View file

@ -38,7 +38,7 @@ abstract class JavascriptTestBase extends BrowserTestBase {
catch (DeadClient $e) {
$this->markTestSkipped('PhantomJS is either not installed or not running. Start it via phantomjs --ssl-protocol=any --ignore-ssl-errors=true vendor/jcalderonzumba/gastonjs/src/Client/main.js 8510 1024 768&');
}
catch (Exception $e) {
catch (\Exception $e) {
$this->markTestSkipped('An unexpected error occurred while starting Mink: ' . $e->getMessage());
}
}

View file

@ -2,6 +2,8 @@
namespace Drupal\FunctionalTests;
use Drupal\Component\Render\FormattableMarkup;
use Drupal\Component\Utility\Xss;
use Drupal\KernelTests\AssertLegacyTrait as BaseAssertLegacyTrait;
/**
@ -53,10 +55,17 @@ trait AssertLegacyTrait {
* Plain text to look for.
*
* @deprecated Scheduled for removal in Drupal 9.0.0.
* Use $this->assertSession()->pageTextContains() or
* $this->assertSession()->responseContains() instead.
* Use instead:
* - $this->assertSession()->responseContains() for non-HTML responses,
* like XML or Json.
* - $this->assertSession()->pageTextContains() for HTML responses. Unlike
* the deprecated assertText(), the passed text should be HTML decoded,
* exactly as a human sees it in the browser.
*/
protected function assertText($text) {
// Cast MarkupInterface to string.
$text = (string) $text;
$content_type = $this->getSession()->getResponseHeader('Content-type');
// In case of a Non-HTML response (example: XML) check the original
// response.
@ -64,7 +73,7 @@ trait AssertLegacyTrait {
$this->assertSession()->responseContains($text);
}
else {
$this->assertSession()->pageTextContains($text);
$this->assertTextHelper($text, FALSE);
}
}
@ -78,10 +87,17 @@ trait AssertLegacyTrait {
* Plain text to look for.
*
* @deprecated Scheduled for removal in Drupal 9.0.0.
* Use $this->assertSession()->pageTextNotContains() or
* $this->assertSession()->responseNotContains() instead.
* Use instead:
* - $this->assertSession()->responseNotContains() for non-HTML responses,
* like XML or Json.
* - $this->assertSession()->pageTextNotContains() for HTML responses.
* Unlike the deprecated assertNoText(), the passed text should be HTML
* decoded, exactly as a human sees it in the browser.
*/
protected function assertNoText($text) {
// Cast MarkupInterface to string.
$text = (string) $text;
$content_type = $this->getSession()->getResponseHeader('Content-type');
// In case of a Non-HTML response (example: XML) check the original
// response.
@ -89,10 +105,40 @@ trait AssertLegacyTrait {
$this->assertSession()->responseNotContains($text);
}
else {
$this->assertSession()->pageTextNotContains($text);
$this->assertTextHelper($text);
}
}
/**
* Helper for assertText and assertNoText.
*
* @param string $text
* Plain text to look for.
* @param bool $not_exists
* (optional) TRUE if this text should not exist, FALSE if it should.
* Defaults to TRUE.
*
* @return bool
* TRUE on pass, FALSE on fail.
*/
protected function assertTextHelper($text, $not_exists = TRUE) {
$args = ['@text' => $text];
$message = $not_exists ? new FormattableMarkup('"@text" not found', $args) : new FormattableMarkup('"@text" found', $args);
$raw_content = $this->getSession()->getPage()->getContent();
// Trying to simulate what the user sees, given that it removes all text
// inside the head tags, removes inline Javascript, fix all HTML entities,
// removes dangerous protocols and filtering out all HTML tags, as they are
// not visible in a normal browser.
$raw_content = preg_replace('@<head>(.+?)</head>@si', '', $raw_content);
$page_text = Xss::filter($raw_content, []);
$actual = $not_exists == (strpos($page_text, (string) $text) === FALSE);
$this->assertTrue($actual, $message);
return $actual;
}
/**
* Passes if the text is found ONLY ONCE on the text version of the page.
*

View file

@ -0,0 +1,116 @@
<?php
namespace Drupal\FunctionalTests;
use Drupal\Component\Utility\Html;
use Drupal\Core\Url;
use Drupal\Tests\BrowserTestBase;
/**
* Tests BrowserTestBase functionality.
*
* @group browsertestbase
*/
class BrowserTestBaseTest extends BrowserTestBase {
/**
* Modules to enable.
*
* @var array
*/
public static $modules = array('test_page_test', 'form_test', 'system_test');
/**
* Tests basic page test.
*/
public function testGoTo() {
$account = $this->drupalCreateUser();
$this->drupalLogin($account);
// Visit a Drupal page that requires login.
$this->drupalGet('test-page');
$this->assertSession()->statusCodeEquals(200);
// Test page contains some text.
$this->assertSession()->pageTextContains('Test page text.');
// Check that returned plain text is correct.
$text = $this->getTextContent();
$this->assertContains('Test page text.', $text);
$this->assertNotContains('</html>', $text);
// Response includes cache tags that we can assert.
$this->assertSession()->responseHeaderEquals('X-Drupal-Cache-Tags', 'rendered');
// Test that we can read the JS settings.
$js_settings = $this->getDrupalSettings();
$this->assertSame('azAZ09();.,\\\/-_{}', $js_settings['test-setting']);
// Test drupalGet with a url object.
$url = Url::fromRoute('test_page_test.render_title');
$this->drupalGet($url);
$this->assertSession()->statusCodeEquals(200);
// Test page contains some text.
$this->assertSession()->pageTextContains('Hello Drupal');
// Test that setting headers with drupalGet() works.
$this->drupalGet('system-test/header', array(), array(
'Test-Header' => 'header value',
));
$returned_header = $this->getSession()->getResponseHeader('Test-Header');
$this->assertSame('header value', $returned_header);
}
/**
* Tests basic form functionality.
*/
public function testForm() {
// Ensure the proper response code for a _form route.
$this->drupalGet('form-test/object-builder');
$this->assertSession()->statusCodeEquals(200);
// Ensure the form and text field exist.
$this->assertSession()->elementExists('css', 'form#form-test-form-test-object');
$this->assertSession()->fieldExists('bananas');
$edit = ['bananas' => 'green'];
$this->submitForm($edit, 'Save', 'form-test-form-test-object');
$config_factory = $this->container->get('config.factory');
$value = $config_factory->get('form_test.object')->get('bananas');
$this->assertSame('green', $value);
}
/**
* Tests clickLink() functionality.
*/
public function testClickLink() {
$this->drupalGet('test-page');
$this->clickLink('Visually identical test links');
$this->assertContains('user/login', $this->getSession()->getCurrentUrl());
$this->drupalGet('test-page');
$this->clickLink('Visually identical test links', 0);
$this->assertContains('user/login', $this->getSession()->getCurrentUrl());
$this->drupalGet('test-page');
$this->clickLink('Visually identical test links', 1);
$this->assertContains('user/register', $this->getSession()->getCurrentUrl());
}
public function testError() {
$this->setExpectedException('\Exception', 'User notice: foo');
$this->drupalGet('test-error');
}
/**
* Tests legacy asserts.
*/
public function testLegacyAsserts() {
$this->drupalGet('test-encoded');
$dangerous = 'Bad html <script>alert(123);</script>';
$sanitized = Html::escape($dangerous);
$this->assertNoText($dangerous);
$this->assertText($sanitized);
}
}

View file

@ -0,0 +1,22 @@
<?php
namespace Drupal\FunctionalTests\Core\Config;
use Drupal\Tests\BrowserTestBase;
use Drupal\Tests\Traits\Core\Config\SchemaConfigListenerTestTrait;
/**
* Tests the functionality of ConfigSchemaChecker in KernelTestBase tests.
*
* @group config
*/
class SchemaConfigListenerTest extends BrowserTestBase {
use SchemaConfigListenerTestTrait;
/**
* {@inheritdoc}
*/
public static $modules = array('config_test');
}

View file

@ -83,7 +83,6 @@ trait AssertConfigTrait {
break;
default:
throw new \Exception($config_name . ': ' . var_export($op, TRUE));
break;
}
}
}

View file

@ -27,6 +27,21 @@ class DefaultConfigTest extends KernelTestBase {
*/
public static $modules = ['system', 'user'];
/**
* The following config entries are changed on module install.
*
* Compare them does not make sense.
*
* @todo Figure out why simpletest.settings is not installed.
*
* @var array
*/
public static $skippedConfig = [
'locale.settings' => ['path: '],
'syslog.settings' => ['facility: '],
'simpletest.settings' => TRUE,
];
/**
* {@inheritdoc}
*/
@ -49,20 +64,6 @@ class DefaultConfigTest extends KernelTestBase {
* @dataProvider providerTestModuleConfig
*/
public function testModuleConfig($module) {
/** @var \Drupal\Core\Extension\ModuleInstallerInterface $module_installer */
$module_installer = $this->container->get('module_installer');
/** @var \Drupal\Core\Config\StorageInterface $active_config_storage */
$active_config_storage = $this->container->get('config.storage');
/** @var \Drupal\Core\Config\ConfigManagerInterface $config_manager */
$config_manager = $this->container->get('config.manager');
// @todo https://www.drupal.org/node/2308745 Rest has an implicit dependency
// on the Node module remove once solved.
if (in_array($module, ['rest', 'hal'])) {
$module_installer->install(['node']);
}
$module_installer->install([$module]);
// System and user are required in order to be able to install some of the
// other modules. Therefore they are put into static::$modules, which though
// doesn't install config files, so import those config files explicitly.
@ -73,21 +74,71 @@ class DefaultConfigTest extends KernelTestBase {
break;
}
$default_install_path = drupal_get_path('module', $module) . '/' . InstallStorage::CONFIG_INSTALL_DIRECTORY;
$module_config_storage = new FileStorage($default_install_path, StorageInterface::DEFAULT_COLLECTION);
$module_path = drupal_get_path('module', $module) . '/';
// The following config entries are changed on module install, so compare
// them doesn't make sense.
$skipped_config = [];
$skipped_config['locale.settings'][] = 'path: ';
$skipped_config['syslog.settings'][] = 'facility: ';
// @todo Figure out why simpletest.settings is not installed.
$skipped_config['simpletest.settings'] = TRUE;
/** @var \Drupal\Core\Extension\ModuleInstallerInterface $module_installer */
$module_installer = $this->container->get('module_installer');
// Compare the installed config with the one in the module directory.
foreach ($module_config_storage->listAll() as $config_name) {
$result = $config_manager->diff($module_config_storage, $active_config_storage, $config_name);
$this->assertConfigDiff($result, $config_name, $skipped_config);
// @todo https://www.drupal.org/node/2308745 Rest has an implicit dependency
// on the Node module remove once solved.
if (in_array($module, ['rest', 'hal'])) {
$module_installer->install(['node']);
}
// Work out any additional modules and themes that need installing to create
// and optional config.
$optional_config_storage = new FileStorage($module_path . InstallStorage::CONFIG_OPTIONAL_DIRECTORY, StorageInterface::DEFAULT_COLLECTION);
$modules_to_install = [$module];
$themes_to_install = [];
foreach ($optional_config_storage->listAll() as $config_name) {
$data = $optional_config_storage->read($config_name);
if (isset($data['dependencies']['module'])) {
$modules_to_install = array_merge($modules_to_install, $data['dependencies']['module']);
}
if (isset($data['dependencies']['theme'])) {
$themes_to_install = array_merge($themes_to_install, $data['dependencies']['theme']);
}
}
$module_installer->install(array_unique($modules_to_install));
$this->container->get('theme_installer')->install($themes_to_install);
// Test configuration in the module's config/install directory.
$module_config_storage = new FileStorage($module_path . InstallStorage::CONFIG_INSTALL_DIRECTORY, StorageInterface::DEFAULT_COLLECTION);
$this->doTestsOnConfigStorage($module_config_storage);
// Test configuration in the module's config/optional directory.
$this->doTestsOnConfigStorage($optional_config_storage);
}
/**
* Tests that default config matches the installed config.
*
* @param \Drupal\Core\Config\StorageInterface $default_config_storage
* The default config storage to test.
*/
protected function doTestsOnConfigStorage(StorageInterface $default_config_storage) {
/** @var \Drupal\Core\Config\ConfigManagerInterface $config_manager */
$config_manager = $this->container->get('config.manager');
// Just connect directly to the config table so we don't need to worry about
// the cache layer.
$active_config_storage = $this->container->get('config.storage');
foreach ($default_config_storage->listAll() as $config_name) {
if ($active_config_storage->exists($config_name)) {
// If it is a config entity re-save it. This ensures that any
// recalculation of dependencies does not cause config change.
if ($entity_type = $config_manager->getEntityTypeIdByName($config_name)) {
$entity_storage = $config_manager
->getEntityManager()
->getStorage($entity_type);
$id = $entity_storage->getIDFromConfigName($config_name, $entity_storage->getEntityType()
->getConfigPrefix());
$entity_storage->load($id)->calculateDependencies()->save();
}
$result = $config_manager->diff($default_config_storage, $active_config_storage, $config_name);
$this->assertConfigDiff($result, $config_name, static::$skippedConfig);
}
}
}

View file

@ -0,0 +1,480 @@
<?php
namespace Drupal\KernelTests\Core\Asset;
use Drupal\Component\Serialization\Json;
use Drupal\Component\Utility\Unicode;
use Drupal\Core\Asset\AttachedAssets;
use Drupal\KernelTests\KernelTestBase;
/**
* Tests #attached assets: attached asset libraries and JavaScript settings.
*
* i.e. tests:
*
* @code
* $build['#attached']['library'] =
* $build['#attached']['drupalSettings'] =
* @endcode
*
* @group Common
* @group Asset
*/
class AttachedAssetsTest extends KernelTestBase {
/**
* The asset resolver service.
*
* @var \Drupal\Core\Asset\AssetResolverInterface
*/
protected $assetResolver;
/**
* The renderer service.
*
* @var \Drupal\Core\Render\RendererInterface
*/
protected $renderer;
/**
* {@inheritdoc}
*/
public static $modules = array('language', 'simpletest', 'common_test', 'system');
/**
* {@inheritdoc}
*/
protected function setUp() {
parent::setUp();
$this->container->get('router.builder')->rebuild();
$this->assetResolver = $this->container->get('asset.resolver');
$this->renderer = $this->container->get('renderer');
}
/**
* Tests that default CSS and JavaScript is empty.
*/
function testDefault() {
$assets = new AttachedAssets();
$this->assertEqual(array(), $this->assetResolver->getCssAssets($assets, FALSE), 'Default CSS is empty.');
list($js_assets_header, $js_assets_footer) = $this->assetResolver->getJsAssets($assets, FALSE);
$this->assertEqual(array(), $js_assets_header, 'Default header JavaScript is empty.');
$this->assertEqual(array(), $js_assets_footer, 'Default footer JavaScript is empty.');
}
/**
* Tests non-existing libraries.
*/
function testLibraryUnknown() {
$build['#attached']['library'][] = 'core/unknown';
$assets = AttachedAssets::createFromRenderArray($build);
$this->assertIdentical([], $this->assetResolver->getJsAssets($assets, FALSE)[0], 'Unknown library was not added to the page.');
}
/**
* Tests adding a CSS and a JavaScript file.
*/
function testAddFiles() {
$build['#attached']['library'][] = 'common_test/files';
$assets = AttachedAssets::createFromRenderArray($build);
$css = $this->assetResolver->getCssAssets($assets, FALSE);
$js = $this->assetResolver->getJsAssets($assets, FALSE)[1];
$this->assertTrue(array_key_exists('core/modules/system/tests/modules/common_test/bar.css', $css), 'CSS files are correctly added.');
$this->assertTrue(array_key_exists('core/modules/system/tests/modules/common_test/foo.js', $js), 'JavaScript files are correctly added.');
$css_render_array = \Drupal::service('asset.css.collection_renderer')->render($css);
$js_render_array = \Drupal::service('asset.js.collection_renderer')->render($js);
$rendered_css = $this->renderer->renderPlain($css_render_array);
$rendered_js = $this->renderer->renderPlain($js_render_array);
$query_string = $this->container->get('state')->get('system.css_js_query_string') ?: '0';
$this->assertNotIdentical(strpos($rendered_css, '<link rel="stylesheet" href="' . file_url_transform_relative(file_create_url('core/modules/system/tests/modules/common_test/bar.css')) . '?' . $query_string . '" media="all" />'), FALSE, 'Rendering an external CSS file.');
$this->assertNotIdentical(strpos($rendered_js, '<script src="' . file_url_transform_relative(file_create_url('core/modules/system/tests/modules/common_test/foo.js')) . '?' . $query_string . '"></script>'), FALSE, 'Rendering an external JavaScript file.');
}
/**
* Tests adding JavaScript settings.
*/
function testAddJsSettings() {
// Add a file in order to test default settings.
$build['#attached']['library'][] = 'core/drupalSettings';
$assets = AttachedAssets::createFromRenderArray($build);
$this->assertEqual([], $assets->getSettings(), 'JavaScript settings on $assets are empty.');
$javascript = $this->assetResolver->getJsAssets($assets, FALSE)[1];
$this->assertTrue(array_key_exists('currentPath', $javascript['drupalSettings']['data']['path']), 'The current path JavaScript setting is set correctly.');
$this->assertTrue(array_key_exists('currentPath', $assets->getSettings()['path']), 'JavaScript settings on $assets are resolved after retrieving JavaScript assets, and are equal to the returned JavaScript settings.');
$assets->setSettings(['drupal' => 'rocks', 'dries' => 280342800]);
$javascript = $this->assetResolver->getJsAssets($assets, FALSE)[1];
$this->assertEqual(280342800, $javascript['drupalSettings']['data']['dries'], 'JavaScript setting is set correctly.');
$this->assertEqual('rocks', $javascript['drupalSettings']['data']['drupal'], 'The other JavaScript setting is set correctly.');
}
/**
* Tests adding external CSS and JavaScript files.
*/
function testAddExternalFiles() {
$build['#attached']['library'][] = 'common_test/external';
$assets = AttachedAssets::createFromRenderArray($build);
$css = $this->assetResolver->getCssAssets($assets, FALSE);
$js = $this->assetResolver->getJsAssets($assets, FALSE)[1];
$this->assertTrue(array_key_exists('http://example.com/stylesheet.css', $css), 'External CSS files are correctly added.');
$this->assertTrue(array_key_exists('http://example.com/script.js', $js), 'External JavaScript files are correctly added.');
$css_render_array = \Drupal::service('asset.css.collection_renderer')->render($css);
$js_render_array = \Drupal::service('asset.js.collection_renderer')->render($js);
$rendered_css = $this->renderer->renderPlain($css_render_array);
$rendered_js = $this->renderer->renderPlain($js_render_array);
$this->assertNotIdentical(strpos($rendered_css, '<link rel="stylesheet" href="http://example.com/stylesheet.css" media="all" />'), FALSE, 'Rendering an external CSS file.');
$this->assertNotIdentical(strpos($rendered_js, '<script src="http://example.com/script.js"></script>'), FALSE, 'Rendering an external JavaScript file.');
}
/**
* Tests adding JavaScript files with additional attributes.
*/
function testAttributes() {
$build['#attached']['library'][] = 'common_test/js-attributes';
$assets = AttachedAssets::createFromRenderArray($build);
$js = $this->assetResolver->getJsAssets($assets, FALSE)[1];
$js_render_array = \Drupal::service('asset.js.collection_renderer')->render($js);
$rendered_js = $this->renderer->renderPlain($js_render_array);
$expected_1 = '<script src="http://example.com/deferred-external.js" foo="bar" defer></script>';
$expected_2 = '<script src="' . file_url_transform_relative(file_create_url('core/modules/system/tests/modules/common_test/deferred-internal.js')) . '?v=1" defer bar="foo"></script>';
$this->assertNotIdentical(strpos($rendered_js, $expected_1), FALSE, 'Rendered external JavaScript with correct defer and random attributes.');
$this->assertNotIdentical(strpos($rendered_js, $expected_2), FALSE, 'Rendered internal JavaScript with correct defer and random attributes.');
}
/**
* Tests that attributes are maintained when JS aggregation is enabled.
*/
function testAggregatedAttributes() {
$build['#attached']['library'][] = 'common_test/js-attributes';
$assets = AttachedAssets::createFromRenderArray($build);
$js = $this->assetResolver->getJsAssets($assets, TRUE)[1];
$js_render_array = \Drupal::service('asset.js.collection_renderer')->render($js);
$rendered_js = $this->renderer->renderPlain($js_render_array);
$expected_1 = '<script src="http://example.com/deferred-external.js" foo="bar" defer></script>';
$expected_2 = '<script src="' . file_url_transform_relative(file_create_url('core/modules/system/tests/modules/common_test/deferred-internal.js')) . '?v=1" defer bar="foo"></script>';
$this->assertNotIdentical(strpos($rendered_js, $expected_1), FALSE, 'Rendered external JavaScript with correct defer and random attributes.');
$this->assertNotIdentical(strpos($rendered_js, $expected_2), FALSE, 'Rendered internal JavaScript with correct defer and random attributes.');
}
/**
* Integration test for CSS/JS aggregation.
*/
function testAggregation() {
$build['#attached']['library'][] = 'core/drupal.timezone';
$build['#attached']['library'][] = 'core/drupal.vertical-tabs';
$assets = AttachedAssets::createFromRenderArray($build);
$this->assertEqual(1, count($this->assetResolver->getCssAssets($assets, TRUE)), 'There is a sole aggregated CSS asset.');
list($header_js, $footer_js) = $this->assetResolver->getJsAssets($assets, TRUE);
$this->assertEqual([], \Drupal::service('asset.js.collection_renderer')->render($header_js), 'There are 0 JavaScript assets in the header.');
$rendered_footer_js = \Drupal::service('asset.js.collection_renderer')->render($footer_js);
$this->assertEqual(2, count($rendered_footer_js), 'There are 2 JavaScript assets in the footer.');
$this->assertEqual('drupal-settings-json', $rendered_footer_js[0]['#attributes']['data-drupal-selector'], 'The first of the two JavaScript assets in the footer has drupal settings.');
$this->assertEqual(0, strpos($rendered_footer_js[1]['#attributes']['src'], base_path()), 'The second of the two JavaScript assets in the footer has the sole aggregated JavaScript asset.');
}
/**
* Tests JavaScript settings.
*/
function testSettings() {
$build = array();
$build['#attached']['library'][] = 'core/drupalSettings';
// Nonsensical value to verify if it's possible to override path settings.
$build['#attached']['drupalSettings']['path']['pathPrefix'] = 'yarhar';
$assets = AttachedAssets::createFromRenderArray($build);
$js = $this->assetResolver->getJsAssets($assets, FALSE)[1];
$js_render_array = \Drupal::service('asset.js.collection_renderer')->render($js);
// Cast to string since this returns a \Drupal\Core\Render\Markup object.
$rendered_js = (string) $this->renderer->renderPlain($js_render_array);
// Parse the generated drupalSettings <script> back to a PHP representation.
$startToken = '{';
$endToken = '}';
$start = strpos($rendered_js, $startToken);
$end = strrpos($rendered_js, $endToken);
// Convert to a string, as $renderer_js is a \Drupal\Core\Render\Markup
// object.
$json = Unicode::substr($rendered_js, $start, $end - $start + 1);
$parsed_settings = Json::decode($json);
// Test whether the settings for core/drupalSettings are available.
$this->assertTrue(isset($parsed_settings['path']['baseUrl']), 'drupalSettings.path.baseUrl is present.');
$this->assertIdentical($parsed_settings['path']['pathPrefix'], 'yarhar', 'drupalSettings.path.pathPrefix is present and has the correct (overridden) value.');
$this->assertIdentical($parsed_settings['path']['currentPath'], '', 'drupalSettings.path.currentPath is present and has the correct value.');
$this->assertIdentical($parsed_settings['path']['currentPathIsAdmin'], FALSE, 'drupalSettings.path.currentPathIsAdmin is present and has the correct value.');
$this->assertIdentical($parsed_settings['path']['isFront'], FALSE, 'drupalSettings.path.isFront is present and has the correct value.');
$this->assertIdentical($parsed_settings['path']['currentLanguage'], 'en', 'drupalSettings.path.currentLanguage is present and has the correct value.');
// Tests whether altering JavaScript settings via hook_js_settings_alter()
// is working as expected.
// @see common_test_js_settings_alter()
$this->assertIdentical($parsed_settings['pluralDelimiter'], '☃');
$this->assertIdentical($parsed_settings['foo'], 'bar');
}
/**
* Tests JS assets depending on the 'core/<head>' virtual library.
*/
function testHeaderHTML() {
$build['#attached']['library'][] = 'common_test/js-header';
$assets = AttachedAssets::createFromRenderArray($build);
$js = $this->assetResolver->getJsAssets($assets, FALSE)[0];
$js_render_array = \Drupal::service('asset.js.collection_renderer')->render($js);
$rendered_js = $this->renderer->renderPlain($js_render_array);
$query_string = $this->container->get('state')->get('system.css_js_query_string') ?: '0';
$this->assertNotIdentical(strpos($rendered_js, '<script src="' . file_url_transform_relative(file_create_url('core/modules/system/tests/modules/common_test/header.js')) . '?' . $query_string . '"></script>'), FALSE, 'The JS asset in common_test/js-header appears in the header.');
$this->assertNotIdentical(strpos($rendered_js, '<script src="' . file_url_transform_relative(file_create_url('core/misc/drupal.js'))), FALSE, 'The JS asset of the direct dependency (core/drupal) of common_test/js-header appears in the header.');
$this->assertNotIdentical(strpos($rendered_js, '<script src="' . file_url_transform_relative(file_create_url('core/assets/vendor/domready/ready.min.js'))), FALSE, 'The JS asset of the indirect dependency (core/domready) of common_test/js-header appears in the header.');
}
/**
* Tests that for assets with cache = FALSE, Drupal sets preprocess = FALSE.
*/
function testNoCache() {
$build['#attached']['library'][] = 'common_test/no-cache';
$assets = AttachedAssets::createFromRenderArray($build);
$js = $this->assetResolver->getJsAssets($assets, FALSE)[1];
$this->assertFalse($js['core/modules/system/tests/modules/common_test/nocache.js']['preprocess'], 'Setting cache to FALSE sets preprocess to FALSE when adding JavaScript.');
}
/**
* Tests adding JavaScript within conditional comments.
*
* @see \Drupal\Core\Render\Element\HtmlTag::preRenderConditionalComments()
*/
function testBrowserConditionalComments() {
$default_query_string = $this->container->get('state')->get('system.css_js_query_string') ?: '0';
$build['#attached']['library'][] = 'common_test/browsers';
$assets = AttachedAssets::createFromRenderArray($build);
$js = $this->assetResolver->getJsAssets($assets, FALSE)[1];
$js_render_array = \Drupal::service('asset.js.collection_renderer')->render($js);
$rendered_js = $this->renderer->renderPlain($js_render_array);
$expected_1 = "<!--[if lte IE 8]>\n" . '<script src="' . file_url_transform_relative(file_create_url('core/modules/system/tests/modules/common_test/old-ie.js')) . '?' . $default_query_string . '"></script>' . "\n<![endif]-->";
$expected_2 = "<!--[if !IE]><!-->\n" . '<script src="' . file_url_transform_relative(file_create_url('core/modules/system/tests/modules/common_test/no-ie.js')) . '?' . $default_query_string . '"></script>' . "\n<!--<![endif]-->";
$this->assertNotIdentical(strpos($rendered_js, $expected_1), FALSE, 'Rendered JavaScript within downlevel-hidden conditional comments.');
$this->assertNotIdentical(strpos($rendered_js, $expected_2), FALSE, 'Rendered JavaScript within downlevel-revealed conditional comments.');
}
/**
* Tests JavaScript versioning.
*/
function testVersionQueryString() {
$build['#attached']['library'][] = 'core/backbone';
$build['#attached']['library'][] = 'core/domready';
$assets = AttachedAssets::createFromRenderArray($build);
$js = $this->assetResolver->getJsAssets($assets, FALSE)[1];
$js_render_array = \Drupal::service('asset.js.collection_renderer')->render($js);
$rendered_js = $this->renderer->renderPlain($js_render_array);
$this->assertTrue(strpos($rendered_js, 'core/assets/vendor/backbone/backbone-min.js?v=1.2.3') > 0 && strpos($rendered_js, 'core/assets/vendor/domready/ready.min.js?v=1.0.8') > 0, 'JavaScript version identifiers correctly appended to URLs');
}
/**
* Tests JavaScript and CSS asset ordering.
*/
function testRenderOrder() {
$build['#attached']['library'][] = 'common_test/order';
$assets = AttachedAssets::createFromRenderArray($build);
// Construct the expected result from the regex.
$expected_order_js = [
"-8_1",
"-8_2",
"-8_3",
"-8_4",
"-5_1", // The external script.
"-3_1",
"-3_2",
"0_1",
"0_2",
"0_3",
];
// Retrieve the rendered JavaScript and test against the regex.
$js = $this->assetResolver->getJsAssets($assets, FALSE)[1];
$js_render_array = \Drupal::service('asset.js.collection_renderer')->render($js);
$rendered_js = $this->renderer->renderPlain($js_render_array);
$matches = array();
if (preg_match_all('/weight_([-0-9]+_[0-9]+)/', $rendered_js, $matches)) {
$result = $matches[1];
}
else {
$result = array();
}
$this->assertIdentical($result, $expected_order_js, 'JavaScript is added in the expected weight order.');
// Construct the expected result from the regex.
$expected_order_css = [
// Base.
'base_weight_-101_1',
'base_weight_-8_1',
'layout_weight_-101_1',
'base_weight_0_1',
'base_weight_0_2',
// Layout.
'layout_weight_-8_1',
'component_weight_-101_1',
'layout_weight_0_1',
'layout_weight_0_2',
// Component.
'component_weight_-8_1',
'state_weight_-101_1',
'component_weight_0_1',
'component_weight_0_2',
// State.
'state_weight_-8_1',
'theme_weight_-101_1',
'state_weight_0_1',
'state_weight_0_2',
// Theme.
'theme_weight_-8_1',
'theme_weight_0_1',
'theme_weight_0_2',
];
// Retrieve the rendered CSS and test against the regex.
$css = $this->assetResolver->getCssAssets($assets, FALSE);
$css_render_array = \Drupal::service('asset.css.collection_renderer')->render($css);
$rendered_css = $this->renderer->renderPlain($css_render_array);
$matches = array();
if (preg_match_all('/([a-z]+)_weight_([-0-9]+_[0-9]+)/', $rendered_css, $matches)) {
$result = $matches[0];
}
else {
$result = array();
}
$this->assertIdentical($result, $expected_order_css, 'CSS is added in the expected weight order.');
}
/**
* Tests rendering the JavaScript with a file's weight above jQuery's.
*/
function testRenderDifferentWeight() {
// If a library contains assets A and B, and A is listed first, then B can
// still make itself appear first by defining a lower weight.
$build['#attached']['library'][] = 'core/jquery';
$build['#attached']['library'][] = 'common_test/weight';
$assets = AttachedAssets::createFromRenderArray($build);
$js = $this->assetResolver->getJsAssets($assets, FALSE)[1];
$js_render_array = \Drupal::service('asset.js.collection_renderer')->render($js);
$rendered_js = $this->renderer->renderPlain($js_render_array);
$this->assertTrue(strpos($rendered_js, 'lighter.css') < strpos($rendered_js, 'first.js'), 'Lighter CSS assets are rendered first.');
$this->assertTrue(strpos($rendered_js, 'lighter.js') < strpos($rendered_js, 'first.js'), 'Lighter JavaScript assets are rendered first.');
$this->assertTrue(strpos($rendered_js, 'before-jquery.js') < strpos($rendered_js, 'core/assets/vendor/jquery/jquery.min.js'), 'Rendering a JavaScript file above jQuery.');
}
/**
* Tests altering a JavaScript's weight via hook_js_alter().
*
* @see simpletest_js_alter()
*/
function testAlter() {
// Add both tableselect.js and simpletest.js.
$build['#attached']['library'][] = 'core/drupal.tableselect';
$build['#attached']['library'][] = 'simpletest/drupal.simpletest';
$assets = AttachedAssets::createFromRenderArray($build);
// Render the JavaScript, testing if simpletest.js was altered to be before
// tableselect.js. See simpletest_js_alter() to see where this alteration
// takes place.
$js = $this->assetResolver->getJsAssets($assets, FALSE)[1];
$js_render_array = \Drupal::service('asset.js.collection_renderer')->render($js);
$rendered_js = $this->renderer->renderPlain($js_render_array);
$this->assertTrue(strpos($rendered_js, 'simpletest.js') < strpos($rendered_js, 'core/misc/tableselect.js'), 'Altering JavaScript weight through the alter hook.');
}
/**
* Adds a JavaScript library to the page and alters it.
*
* @see common_test_library_info_alter()
*/
function testLibraryAlter() {
// Verify that common_test altered the title of Farbtastic.
/** @var \Drupal\Core\Asset\LibraryDiscoveryInterface $library_discovery */
$library_discovery = \Drupal::service('library.discovery');
$library = $library_discovery->getLibraryByName('core', 'jquery.farbtastic');
$this->assertEqual($library['version'], '0.0', 'Registered libraries were altered.');
// common_test_library_info_alter() also added a dependency on jQuery Form.
$build['#attached']['library'][] = 'core/jquery.farbtastic';
$assets = AttachedAssets::createFromRenderArray($build);
$js = $this->assetResolver->getJsAssets($assets, FALSE)[1];
$js_render_array = \Drupal::service('asset.js.collection_renderer')->render($js);
$rendered_js = $this->renderer->renderPlain($js_render_array);
$this->assertTrue(strpos($rendered_js, 'core/assets/vendor/jquery-form/jquery.form.min.js'), 'Altered library dependencies are added to the page.');
}
/**
* Dynamically defines an asset library and alters it.
*/
function testDynamicLibrary() {
/** @var \Drupal\Core\Asset\LibraryDiscoveryInterface $library_discovery */
$library_discovery = \Drupal::service('library.discovery');
// Retrieve a dynamic library definition.
// @see common_test_library_info_build()
\Drupal::state()->set('common_test.library_info_build_test', TRUE);
$library_discovery->clearCachedDefinitions();
$dynamic_library = $library_discovery->getLibraryByName('common_test', 'dynamic_library');
$this->assertTrue(is_array($dynamic_library));
if ($this->assertTrue(isset($dynamic_library['version']))) {
$this->assertIdentical('1.0', $dynamic_library['version']);
}
// Make sure the dynamic library definition could be altered.
// @see common_test_library_info_alter()
if ($this->assertTrue(isset($dynamic_library['dependencies']))) {
$this->assertIdentical(['core/jquery'], $dynamic_library['dependencies']);
}
}
/**
* Tests that multiple modules can implement libraries with the same name.
*
* @see common_test.library.yml
*/
function testLibraryNameConflicts() {
/** @var \Drupal\Core\Asset\LibraryDiscoveryInterface $library_discovery */
$library_discovery = \Drupal::service('library.discovery');
$farbtastic = $library_discovery->getLibraryByName('common_test', 'jquery.farbtastic');
$this->assertEqual($farbtastic['version'], '0.1', 'Alternative libraries can be added to the page.');
}
/**
* Tests JavaScript files that have querystrings attached get added right.
*/
function testAddJsFileWithQueryString() {
$build['#attached']['library'][] = 'common_test/querystring';
$assets = AttachedAssets::createFromRenderArray($build);
$css = $this->assetResolver->getCssAssets($assets, FALSE);
$js = $this->assetResolver->getJsAssets($assets, FALSE)[1];
$this->assertTrue(array_key_exists('core/modules/system/tests/modules/common_test/querystring.css?arg1=value1&arg2=value2', $css), 'CSS file with query string is correctly added.');
$this->assertTrue(array_key_exists('core/modules/system/tests/modules/common_test/querystring.js?arg1=value1&arg2=value2', $js), 'JavaScript file with query string is correctly added.');
$css_render_array = \Drupal::service('asset.css.collection_renderer')->render($css);
$rendered_css = $this->renderer->renderPlain($css_render_array);
$js_render_array = \Drupal::service('asset.js.collection_renderer')->render($js);
$rendered_js = $this->renderer->renderPlain($js_render_array);
$query_string = $this->container->get('state')->get('system.css_js_query_string') ?: '0';
$this->assertNotIdentical(strpos($rendered_css, '<link rel="stylesheet" href="' . str_replace('&', '&amp;', file_url_transform_relative(file_create_url('core/modules/system/tests/modules/common_test/querystring.css?arg1=value1&arg2=value2'))) . '&amp;' . $query_string . '" media="all" />'), FALSE, 'CSS file with query string gets version query string correctly appended..');
$this->assertNotIdentical(strpos($rendered_js, '<script src="' . str_replace('&', '&amp;', file_url_transform_relative(file_create_url('core/modules/system/tests/modules/common_test/querystring.js?arg1=value1&arg2=value2'))) . '&amp;' . $query_string . '"></script>'), FALSE, 'JavaScript file with query string gets version query string correctly appended.');
}
}

View file

@ -0,0 +1,296 @@
<?php
namespace Drupal\KernelTests\Core\Asset;
use Drupal\Core\Asset\Exception\InvalidLibrariesExtendSpecificationException;
use Drupal\Core\Asset\Exception\InvalidLibrariesOverrideSpecificationException;
use Drupal\KernelTests\KernelTestBase;
/**
* Tests the library discovery and library discovery parser.
*
* @group Render
*/
class LibraryDiscoveryIntegrationTest extends KernelTestBase {
/**
* The library discovery service.
*
* @var \Drupal\Core\Asset\LibraryDiscoveryInterface
*/
protected $libraryDiscovery;
/**
* {@inheritdoc}
*/
protected function setUp() {
parent::setUp();
$this->container->get('theme_installer')->install(['test_theme', 'classy']);
$this->libraryDiscovery = $this->container->get('library.discovery');
}
/**
* Tests that hook_library_info is invoked and the cache is cleared.
*/
public function testHookLibraryInfoByTheme() {
// Activate test_theme and verify that the library 'kitten' is added using
// hook_library_info_alter().
$this->activateTheme('test_theme');
$this->assertTrue($this->libraryDiscovery->getLibraryByName('test_theme', 'kitten'));
// Now make classy the active theme and assert that library is not added.
$this->activateTheme('classy');
$this->assertFalse($this->libraryDiscovery->getLibraryByName('test_theme', 'kitten'));
}
/**
* Tests that libraries-override are applied to library definitions.
*/
public function testLibrariesOverride() {
// Assert some classy libraries that will be overridden or removed.
$this->activateTheme('classy');
$this->assertAssetInLibrary('core/themes/classy/css/components/button.css', 'classy', 'base', 'css');
$this->assertAssetInLibrary('core/themes/classy/css/components/collapse-processed.css', 'classy', 'base', 'css');
$this->assertAssetInLibrary('core/themes/classy/css/components/container-inline.css', 'classy', 'base', 'css');
$this->assertAssetInLibrary('core/themes/classy/css/components/details.css', 'classy', 'base', 'css');
$this->assertAssetInLibrary('core/themes/classy/css/components/dialog.css', 'classy', 'dialog', 'css');
// Confirmatory assert on core library to be removed.
$this->assertTrue($this->libraryDiscovery->getLibraryByName('core', 'drupal.progress'), 'Confirmatory test on "core/drupal.progress"');
// Activate test theme that defines libraries overrides.
$this->activateTheme('test_theme');
// Assert that entire library was correctly overridden.
$this->assertEqual($this->libraryDiscovery->getLibraryByName('core', 'drupal.collapse'), $this->libraryDiscovery->getLibraryByName('test_theme', 'collapse'), 'Entire library correctly overridden.');
// Assert that classy library assets were correctly overridden or removed.
$this->assertNoAssetInLibrary('core/themes/classy/css/components/button.css', 'classy', 'base', 'css');
$this->assertNoAssetInLibrary('core/themes/classy/css/components/collapse-processed.css', 'classy', 'base', 'css');
$this->assertNoAssetInLibrary('core/themes/classy/css/components/container-inline.css', 'classy', 'base', 'css');
$this->assertNoAssetInLibrary('core/themes/classy/css/components/details.css', 'classy', 'base', 'css');
$this->assertNoAssetInLibrary('core/themes/classy/css/components/dialog.css', 'classy', 'dialog', 'css');
$this->assertAssetInLibrary('core/modules/system/tests/themes/test_theme/css/my-button.css', 'classy', 'base', 'css');
$this->assertAssetInLibrary('core/modules/system/tests/themes/test_theme/css/my-collapse-processed.css', 'classy', 'base', 'css');
$this->assertAssetInLibrary('themes/my_theme/css/my-container-inline.css', 'classy', 'base', 'css');
$this->assertAssetInLibrary('themes/my_theme/css/my-details.css', 'classy', 'base', 'css');
// Assert that entire library was correctly removed.
$this->assertFalse($this->libraryDiscovery->getLibraryByName('core', 'drupal.progress'), 'Entire library correctly removed.');
// Assert that overridden library asset still retains attributes.
$library = $this->libraryDiscovery->getLibraryByName('core', 'jquery');
foreach ($library['js'] as $definition) {
if ($definition['data'] == 'core/modules/system/tests/themes/test_theme/js/collapse.js') {
$this->assertTrue($definition['minified'] && $definition['weight'] == -20, 'Previous attributes retained');
break;
}
}
}
/**
* Tests libraries-override on drupalSettings.
*/
public function testLibrariesOverrideDrupalSettings() {
// Activate test theme that attempts to override drupalSettings.
$this->activateTheme('test_theme_libraries_override_with_drupal_settings');
// Assert that drupalSettings cannot be overridden and throws an exception.
try {
$this->libraryDiscovery->getLibraryByName('core', 'drupal.ajax');
$this->fail('Throw Exception when trying to override drupalSettings');
}
catch (InvalidLibrariesOverrideSpecificationException $e) {
$expected_message = 'drupalSettings may not be overridden in libraries-override. Trying to override core/drupal.ajax/drupalSettings. Use hook_library_info_alter() instead.';
$this->assertEqual($e->getMessage(), $expected_message, 'Throw Exception when trying to override drupalSettings');
}
}
/**
* Tests libraries-override on malformed assets.
*/
public function testLibrariesOverrideMalformedAsset() {
// Activate test theme that overrides with a malformed asset.
$this->activateTheme('test_theme_libraries_override_with_invalid_asset');
// Assert that improperly formed asset "specs" throw an exception.
try {
$this->libraryDiscovery->getLibraryByName('core', 'drupal.dialog');
$this->fail('Throw Exception when specifying invalid override');
}
catch (InvalidLibrariesOverrideSpecificationException $e) {
$expected_message = 'Library asset core/drupal.dialog/css is not correctly specified. It should be in the form "extension/library_name/sub_key/path/to/asset.js".';
$this->assertEqual($e->getMessage(), $expected_message, 'Throw Exception when specifying invalid override');
}
}
/**
* Tests library assets with other ways for specifying paths.
*/
public function testLibrariesOverrideOtherAssetLibraryNames() {
// Activate a test theme that defines libraries overrides on other types of
// assets.
$this->activateTheme('test_theme');
// Assert Drupal-relative paths.
$this->assertAssetInLibrary('themes/my_theme/css/dropbutton.css', 'core', 'drupal.dropbutton', 'css');
// Assert stream wrapper paths.
$this->assertAssetInLibrary('public://my_css/vertical-tabs.css', 'core', 'drupal.vertical-tabs', 'css');
// Assert a protocol-relative URI.
$this->assertAssetInLibrary('//my-server/my_theme/css/jquery_ui.css', 'core', 'jquery.ui', 'css');
// Assert an absolute URI.
$this->assertAssetInLibrary('http://example.com/my_theme/css/farbtastic.css', 'core', 'jquery.farbtastic', 'css');
}
/**
* Tests that base theme libraries-override still apply in sub themes.
*/
public function testBaseThemeLibrariesOverrideInSubTheme() {
// Activate a test theme that has subthemes.
$this->activateTheme('test_subtheme');
// Assert that libraries-override specified in the base theme still applies
// in the sub theme.
$this->assertNoAssetInLibrary('core/misc/dialog/dialog.js', 'core', 'drupal.dialog', 'js');
$this->assertAssetInLibrary('core/modules/system/tests/themes/test_basetheme/css/farbtastic.css', 'core', 'jquery.farbtastic', 'css');
}
/**
* Tests libraries-extend.
*/
public function testLibrariesExtend() {
// Activate classy themes and verify the libraries are not extended.
$this->activateTheme('classy');
$this->assertNoAssetInLibrary('core/modules/system/tests/themes/test_theme_libraries_extend/css/extend_1.css', 'classy', 'book-navigation', 'css');
$this->assertNoAssetInLibrary('core/modules/system/tests/themes/test_theme_libraries_extend/js/extend_1.js', 'classy', 'book-navigation', 'js');
$this->assertNoAssetInLibrary('core/modules/system/tests/themes/test_theme_libraries_extend/css/extend_2.css', 'classy', 'book-navigation', 'css');
// Activate the theme that extends the book-navigation library in classy.
$this->activateTheme('test_theme_libraries_extend');
$this->assertAssetInLibrary('core/modules/system/tests/themes/test_theme_libraries_extend/css/extend_1.css', 'classy', 'book-navigation', 'css');
$this->assertAssetInLibrary('core/modules/system/tests/themes/test_theme_libraries_extend/js/extend_1.js', 'classy', 'book-navigation', 'js');
$this->assertAssetInLibrary('core/modules/system/tests/themes/test_theme_libraries_extend/css/extend_2.css', 'classy', 'book-navigation', 'css');
// Activate a sub theme and confirm that it inherits the library assets
// extended in the base theme as well as its own.
$this->assertNoAssetInLibrary('core/modules/system/tests/themes/test_basetheme/css/base-libraries-extend.css', 'classy', 'base', 'css');
$this->assertNoAssetInLibrary('core/modules/system/tests/themes/test_subtheme/css/sub-libraries-extend.css', 'classy', 'base', 'css');
$this->activateTheme('test_subtheme');
$this->assertAssetInLibrary('core/modules/system/tests/themes/test_basetheme/css/base-libraries-extend.css', 'classy', 'base', 'css');
$this->assertAssetInLibrary('core/modules/system/tests/themes/test_subtheme/css/sub-libraries-extend.css', 'classy', 'base', 'css');
// Activate test theme that extends with a non-existent library. An
// exception should be thrown.
$this->activateTheme('test_theme_libraries_extend');
try {
$this->libraryDiscovery->getLibraryByName('core', 'drupal.dialog');
$this->fail('Throw Exception when specifying non-existent libraries-extend.');
}
catch (InvalidLibrariesExtendSpecificationException $e) {
$expected_message = 'The specified library "test_theme_libraries_extend/non_existent_library" does not exist.';
$this->assertEqual($e->getMessage(), $expected_message, 'Throw Exception when specifying non-existent libraries-extend.');
}
// Also, test non-string libraries-extend. An exception should be thrown.
$this->container->get('theme_installer')->install(['test_theme']);
try {
$this->libraryDiscovery->getLibraryByName('test_theme', 'collapse');
$this->fail('Throw Exception when specifying non-string libraries-extend.');
}
catch (InvalidLibrariesExtendSpecificationException $e) {
$expected_message = 'The libraries-extend specification for each library must be a list of strings.';
$this->assertEqual($e->getMessage(), $expected_message, 'Throw Exception when specifying non-string libraries-extend.');
}
}
/**
* Activates a specified theme.
*
* Installs the theme if not already installed and makes it the active theme.
*
* @param string $theme_name
* The name of the theme to be activated.
*/
protected function activateTheme($theme_name) {
$this->container->get('theme_installer')->install([$theme_name]);
/** @var \Drupal\Core\Theme\ThemeInitializationInterface $theme_initializer */
$theme_initializer = $this->container->get('theme.initialization');
/** @var \Drupal\Core\Theme\ThemeManagerInterface $theme_manager */
$theme_manager = $this->container->get('theme.manager');
$theme_manager->setActiveTheme($theme_initializer->getActiveThemeByName($theme_name));
$this->libraryDiscovery->clearCachedDefinitions();
// Assert message.
$this->pass(sprintf('Activated theme "%s"', $theme_name));
}
/**
* Asserts that the specified asset is in the given library.
*
* @param string $asset
* The asset file with the path for the file.
* @param string $extension
* The extension in which the $library is defined.
* @param string $library_name
* Name of the library.
* @param mixed $sub_key
* The library sub key where the given asset is defined.
* @param string $message
* (optional) A message to display with the assertion.
*
* @return bool
* TRUE if the specified asset is found in the library.
*/
protected function assertAssetInLibrary($asset, $extension, $library_name, $sub_key, $message = NULL) {
if (!isset($message)) {
$message = sprintf('Asset %s found in library "%s/%s"', $asset, $extension, $library_name);
}
$library = $this->libraryDiscovery->getLibraryByName($extension, $library_name);
foreach ($library[$sub_key] as $definition) {
if ($asset == $definition['data']) {
return $this->pass($message);
}
}
return $this->fail($message);
}
/**
* Asserts that the specified asset is not in the given library.
*
* @param string $asset
* The asset file with the path for the file.
* @param string $extension
* The extension in which the $library_name is defined.
* @param string $library_name
* Name of the library.
* @param mixed $sub_key
* The library sub key where the given asset is defined.
* @param string $message
* (optional) A message to display with the assertion.
*
* @return bool
* TRUE if the specified asset is not found in the library.
*/
protected function assertNoAssetInLibrary($asset, $extension, $library_name, $sub_key, $message = NULL) {
if (!isset($message)) {
$message = sprintf('Asset %s not found in library "%s/%s"', $asset, $extension, $library_name);
}
$library = $this->libraryDiscovery->getLibraryByName($extension, $library_name);
foreach ($library[$sub_key] as $definition) {
if ($asset == $definition['data']) {
return $this->fail($message);
}
}
return $this->pass($message);
}
}

View file

@ -0,0 +1,201 @@
<?php
namespace Drupal\KernelTests\Core\Asset;
use Drupal\KernelTests\KernelTestBase;
/**
* Tests that the asset files for all core libraries exist.
*
* This test also changes the active theme to each core theme to verify
* the libraries after theme-level libraries-override and libraries-extend are
* applied.
*
* @group Asset
*/
class ResolvedLibraryDefinitionsFilesMatchTest extends KernelTestBase {
/**
* The theme handler.
*
* @var \Drupal\Core\Extension\ThemeHandlerInterface
*/
protected $themeHandler;
/**
* The theme initialization.
*
* @var \Drupal\Core\Theme\ThemeInitializationInterface
*/
protected $themeInitialization;
/**
* The theme manager.
*
* @var \Drupal\Core\Theme\ThemeManagerInterface
*/
protected $themeManager;
/**
* The library discovery service.
*
* @var \Drupal\Core\Asset\LibraryDiscoveryInterface
*/
protected $libraryDiscovery;
/**
* A list of all core modules.
*
* @var string[]
*/
protected $allModules;
/**
* A list of all core themes.
*
* We hardcode this because test themes don't use a 'package' or 'hidden' key
* so we don't have a good way of filtering to only get "real" themes.
*
* @var string[]
*/
protected $allThemes = [
'bartik',
'classy',
'seven',
'stable',
'stark',
];
/**
* A list of libraries to skip checking, in the format extension/library_name.
*
* @var string[]
*/
protected $librariesToSkip = [
// Locale has a "dummy" library that does not actually exist.
'locale/translations',
];
/**
* A list of all paths that have been checked.
*
* @var array[]
*/
protected $pathsChecked;
/**
* {@inheritdoc}
*/
public static $modules = ['system'];
/**
* {@inheritdoc}
*/
protected function setUp() {
parent::setUp();
// Install all core themes.
sort($this->allThemes);
$this->container->get('theme_installer')->install($this->allThemes);
// Enable all core modules.
$all_modules = system_rebuild_module_data();
$all_modules = array_filter($all_modules, function ($module) {
// Filter contrib, hidden, already enabled modules and modules in the
// Testing package.
if ($module->origin !== 'core' || !empty($module->info['hidden']) || $module->status == TRUE || $module->info['package'] == 'Testing') {
return FALSE;
}
return TRUE;
});
$this->allModules = array_keys($all_modules);
$this->allModules[] = 'system';
sort($this->allModules);
$this->container->get('module_installer')->install($this->allModules);
$this->themeHandler = $this->container->get('theme_handler');
$this->themeInitialization = $this->container->get('theme.initialization');
$this->themeManager = $this->container->get('theme.manager');
$this->libraryDiscovery = $this->container->get('library.discovery');
}
/**
* Ensures that all core module and theme library files exist.
*/
public function testCoreLibraryCompleteness() {
// First verify all libraries with no active theme.
$this->verifyLibraryFilesExist($this->getAllLibraries());
// Then verify all libraries for each core theme. This may seem like
// overkill but themes can override and extend other extensions' libraries
// and these changes are only applied for the active theme.
foreach ($this->allThemes as $theme) {
$this->themeManager->setActiveTheme($this->themeInitialization->getActiveThemeByName($theme));
$this->libraryDiscovery->clearCachedDefinitions();
$this->verifyLibraryFilesExist($this->getAllLibraries());
}
}
/**
* Checks that all the library files exist.
*
* @param array[] $library_definitions
* An array of library definitions, keyed by extension, then by library, and
* so on.
*/
protected function verifyLibraryFilesExist($library_definitions) {
$root = \Drupal::root();
foreach ($library_definitions as $extension => $libraries) {
foreach ($libraries as $library_name => $library) {
if (in_array("$extension/$library_name", $this->librariesToSkip)) {
continue;
}
// Check that all the assets exist.
foreach (['css', 'js'] as $asset_type) {
foreach ($library[$asset_type] as $asset) {
$file = $asset['data'];
$path = $root . '/' . $file;
// Only check and assert each file path once.
if (!isset($this->pathsChecked[$path])) {
$this->assertTrue(is_file($path), "$file file referenced from the $extension/$library_name library exists.");
$this->pathsChecked[$path] = TRUE;
}
}
}
}
}
}
/**
* Gets all libraries for core and all installed modules.
*
* @return \Drupal\Core\Extension\Extension[]
*/
protected function getAllLibraries() {
$modules = \Drupal::moduleHandler()->getModuleList();
$extensions = $modules;
$module_list = array_keys($modules);
sort($module_list);
$this->assertEqual($this->allModules, $module_list, 'All core modules are installed.');
$themes = $this->themeHandler->listInfo();
$extensions += $themes;
$theme_list = array_keys($themes);
sort($theme_list);
$this->assertEqual($this->allThemes, $theme_list, 'All core themes are installed.');
$libraries['core'] = $this->libraryDiscovery->getLibrariesByExtension('core');
$root = \Drupal::root();
foreach ($extensions as $extension_name => $extension) {
$library_file = $extension->getPath() . '/' . $extension_name . '.libraries.yml';
if (is_file($root . '/' . $library_file)) {
$libraries[$extension_name] = $this->libraryDiscovery->getLibrariesByExtension($extension_name);
}
}
return $libraries;
}
}

View file

@ -0,0 +1,38 @@
<?php
namespace Drupal\KernelTests\Core\Block;
use Drupal\block_test\PluginForm\EmptyBlockForm;
use Drupal\KernelTests\KernelTestBase;
/**
* Tests that blocks can have multiple forms.
*
* @group block
*/
class MultipleBlockFormTest extends KernelTestBase {
/**
* {@inheritdoc}
*/
public static $modules = ['system', 'block', 'block_test'];
/**
* Tests that blocks can have multiple forms.
*/
public function testMultipleForms() {
$configuration = ['label' => 'A very cool block'];
$block = \Drupal::service('plugin.manager.block')->createInstance('test_multiple_forms_block', $configuration);
$form_object1 = \Drupal::service('plugin_form.factory')->createInstance($block, 'configure');
$form_object2 = \Drupal::service('plugin_form.factory')->createInstance($block, 'secondary');
// Assert that the block itself is used for the default form.
$this->assertSame($block, $form_object1);
// Ensure that EmptyBlockForm is used and the plugin is set.
$this->assertInstanceOf(EmptyBlockForm::class, $form_object2);
$this->assertAttributeEquals($block, 'plugin', $form_object2);
}
}

View file

@ -0,0 +1,66 @@
<?php
namespace Drupal\KernelTests\Core\Bootstrap;
use Drupal\KernelTests\KernelTestBase;
/**
* Tests that drupal_get_filename() works correctly.
*
* @group Bootstrap
*/
class GetFilenameTest extends KernelTestBase {
/**
* {@inheritdoc}
*/
public static $modules = ['system'];
/**
* Tests that drupal_get_filename() works when the file is not in database.
*/
function testDrupalGetFilename() {
// drupal_get_profile() is using obtaining the profile from state if the
// install_state global is not set.
global $install_state;
$install_state['parameters']['profile'] = 'testing';
// Rebuild system.module.files state data.
// @todo Remove as part of https://www.drupal.org/node/2186491
drupal_static_reset('system_rebuild_module_data');
system_rebuild_module_data();
// Retrieving the location of a module.
$this->assertIdentical(drupal_get_filename('module', 'system'), 'core/modules/system/system.info.yml');
// Retrieving the location of a theme.
\Drupal::service('theme_handler')->install(array('stark'));
$this->assertIdentical(drupal_get_filename('theme', 'stark'), 'core/themes/stark/stark.info.yml');
// Retrieving the location of a theme engine.
$this->assertIdentical(drupal_get_filename('theme_engine', 'twig'), 'core/themes/engines/twig/twig.info.yml');
// Retrieving the location of a profile. Profiles are a special case with
// a fixed location and naming.
$this->assertIdentical(drupal_get_filename('profile', 'testing'), 'core/profiles/testing/testing.info.yml');
// Generate a non-existing module name.
$non_existing_module = uniqid("", TRUE);
// Set a custom error handler so we can ignore the file not found error.
set_error_handler(function($severity, $message, $file, $line) {
// Skip error handling if this is a "file not found" error.
if (strstr($message, 'is missing from the file system:')) {
\Drupal::state()->set('get_filename_test_triggered_error', TRUE);
return;
}
throw new \ErrorException($message, 0, $severity, $file, $line);
});
$this->assertNull(drupal_get_filename('module', $non_existing_module), 'Searching for an item that does not exist returns NULL.');
$this->assertTrue(\Drupal::state()->get('get_filename_test_triggered_error'), 'Searching for an item that does not exist triggers an error.');
// Restore the original error handler.
restore_error_handler();
}
}

View file

@ -0,0 +1,41 @@
<?php
namespace Drupal\KernelTests\Core\Bootstrap;
use Drupal\KernelTests\KernelTestBase;
/**
* Tests that drupal_static() and drupal_static_reset() work.
*
* @group Bootstrap
*/
class ResettableStaticTest extends KernelTestBase {
/**
* Tests drupal_static() function.
*
* Tests that a variable reference returned by drupal_static() gets reset when
* drupal_static_reset() is called.
*/
function testDrupalStatic() {
$name = __CLASS__ . '_' . __METHOD__;
$var = &drupal_static($name, 'foo');
$this->assertEqual($var, 'foo', 'Variable returned by drupal_static() was set to its default.');
// Call the specific reset and the global reset each twice to ensure that
// multiple resets can be issued without odd side effects.
$var = 'bar';
drupal_static_reset($name);
$this->assertEqual($var, 'foo', 'Variable was reset after first invocation of name-specific reset.');
$var = 'bar';
drupal_static_reset($name);
$this->assertEqual($var, 'foo', 'Variable was reset after second invocation of name-specific reset.');
$var = 'bar';
drupal_static_reset();
$this->assertEqual($var, 'foo', 'Variable was reset after first invocation of global reset.');
$var = 'bar';
drupal_static_reset();
$this->assertEqual($var, 'foo', 'Variable was reset after second invocation of global reset.');
}
}

View file

@ -0,0 +1,207 @@
<?php
namespace Drupal\KernelTests\Core\Cache;
use Drupal\Core\Cache\Apcu4Backend;
use Drupal\Core\Cache\ApcuBackend;
/**
* Tests the APCu cache backend.
*
* @group Cache
* @requires extension apcu
*/
class ApcuBackendTest extends GenericCacheBackendUnitTestBase {
/**
* Get a list of failed requirements.
*
* This specifically bypasses checkRequirements because it fails tests. PHP 7
* does not have APCu and simpletest does not have a explicit "skip"
* functionality so to emulate it we override all test methods and explicitly
* pass when requirements are not met.
*
* @return array
*/
protected function getRequirements() {
$requirements = [];
if (!extension_loaded('apcu')) {
$requirements[] = 'APCu extension not found.';
}
else {
if (PHP_SAPI === 'cli' && !ini_get('apc.enable_cli')) {
$requirements[] = 'apc.enable_cli must be enabled to run this test.';
}
}
return $requirements;
}
/**
* Check if requirements fail.
*
* If the requirements fail the test method should return immediately instead
* of running any tests. Messages will be output to display why the test was
* skipped.
*/
protected function requirementsFail() {
$requirements = $this->getRequirements();
if (!empty($requirements)) {
foreach ($requirements as $message) {
$this->pass($message);
}
return TRUE;
}
return FALSE;
}
/**
* {@inheritdoc}
*/
protected function createCacheBackend($bin) {
if (version_compare(phpversion('apcu'), '5.0.0', '>=')) {
return new ApcuBackend($bin, $this->databasePrefix, \Drupal::service('cache_tags.invalidator.checksum'));
}
else {
return new Apcu4Backend($bin, $this->databasePrefix, \Drupal::service('cache_tags.invalidator.checksum'));
}
}
/**
* {@inheritdoc}
*/
protected function tearDown() {
foreach ($this->cachebackends as $bin => $cachebackend) {
$this->cachebackends[$bin]->removeBin();
}
parent::tearDown();
}
/**
* {@inheritdoc}
*/
public function testSetGet() {
if ($this->requirementsFail()) {
return;
}
parent::testSetGet();
// Make sure entries are permanent (i.e. no TTL).
$backend = $this->getCacheBackend($this->getTestBin());
$key = $backend->getApcuKey('TEST8');
if (class_exists('\APCUIterator')) {
$iterator = new \APCUIterator('/^' . $key . '/');
}
else {
$iterator = new \APCIterator('user', '/^' . $key . '/');
}
foreach ($iterator as $item) {
$this->assertEqual(0, $item['ttl']);
$found = TRUE;
}
$this->assertTrue($found);
}
/**
* {@inheritdoc}
*/
public function testDelete() {
if ($this->requirementsFail()) {
return;
}
parent::testDelete();
}
/**
* {@inheritdoc}
*/
public function testValueTypeIsKept() {
if ($this->requirementsFail()) {
return;
}
parent::testValueTypeIsKept();
}
/**
* {@inheritdoc}
*/
public function testGetMultiple() {
if ($this->requirementsFail()) {
return;
}
parent::testGetMultiple();
}
/**
* {@inheritdoc}
*/
public function testSetMultiple() {
if ($this->requirementsFail()) {
return;
}
parent::testSetMultiple();
}
/**
* {@inheritdoc}
*/
public function testDeleteMultiple() {
if ($this->requirementsFail()) {
return;
}
parent::testDeleteMultiple();
}
/**
* {@inheritdoc}
*/
public function testDeleteAll() {
if ($this->requirementsFail()) {
return;
}
parent::testDeleteAll();
}
/**
* {@inheritdoc}
*/
public function testInvalidate() {
if ($this->requirementsFail()) {
return;
}
parent::testInvalidate();
}
/**
* {@inheritdoc}
*/
public function testInvalidateTags() {
if ($this->requirementsFail()) {
return;
}
parent::testInvalidateTags();
}
/**
* {@inheritdoc}
*/
public function testInvalidateAll() {
if ($this->requirementsFail()) {
return;
}
parent::testInvalidateAll();
}
/**
* {@inheritdoc}
*/
public function testRemoveBin() {
if ($this->requirementsFail()) {
return;
}
parent::testRemoveBin();
}
}

View file

@ -0,0 +1,29 @@
<?php
namespace Drupal\KernelTests\Core\Cache;
use Drupal\Core\Cache\BackendChain;
use Drupal\Core\Cache\MemoryBackend;
/**
* Unit test of the backend chain using the generic cache unit test base.
*
* @group Cache
*/
class BackendChainTest extends GenericCacheBackendUnitTestBase {
protected function createCacheBackend($bin) {
$chain = new BackendChain($bin);
// We need to create some various backends in the chain.
$chain
->appendBackend(new MemoryBackend('foo'))
->prependBackend(new MemoryBackend('bar'))
->appendBackend(new MemoryBackend('baz'));
\Drupal::service('cache_tags.invalidator')->addInvalidator($chain);
return $chain;
}
}

View file

@ -0,0 +1,119 @@
<?php
namespace Drupal\KernelTests\Core\Cache;
use Drupal\KernelTests\KernelTestBase;
use Drupal\simpletest\UserCreationTrait;
use Drupal\user\Entity\Role;
/**
* Tests the cache context optimization.
*
* @group Render
*/
class CacheContextOptimizationTest extends KernelTestBase {
use UserCreationTrait;
/**
* Modules to enable.
*
* @var string[]
*/
public static $modules = ['user', 'system'];
/**
* {@inheritdoc}
*/
protected function setUp() {
parent::setUp();
$this->installEntitySchema('user');
$this->installConfig(['user']);
$this->installSchema('system', ['sequences']);
}
/**
* Ensures that 'user.permissions' cache context is able to define cache tags.
*/
public function testUserPermissionCacheContextOptimization() {
$user1 = $this->createUser();
$this->assertEqual($user1->id(), 1);
$authenticated_user = $this->createUser(['administer permissions']);
$role = $authenticated_user->getRoles()[1];
$test_element = [
'#cache' => [
'keys' => ['test'],
'contexts' => ['user', 'user.permissions'],
],
];
\Drupal::service('account_switcher')->switchTo($authenticated_user);
$element = $test_element;
$element['#markup'] = 'content for authenticated users';
$output = \Drupal::service('renderer')->renderRoot($element);
$this->assertEqual($output, 'content for authenticated users');
// Verify that the render caching is working so that other tests can be
// trusted.
$element = $test_element;
$element['#markup'] = 'this should not be visible';
$output = \Drupal::service('renderer')->renderRoot($element);
$this->assertEqual($output, 'content for authenticated users');
// Even though the cache contexts have been optimized to only include 'user'
// cache context, the element should have been changed because
// 'user.permissions' cache context defined a cache tags for permission
// changes, which should have bubbled up for the element when it was
// optimized away.
Role::load($role)
->revokePermission('administer permissions')
->save();
$element = $test_element;
$element['#markup'] = 'this should be visible';
$output = \Drupal::service('renderer')->renderRoot($element);
$this->assertEqual($output, 'this should be visible');
}
/**
* Ensures that 'user.roles' still works when it is optimized away.
*/
public function testUserRolesCacheContextOptimization() {
$root_user = $this->createUser();
$this->assertEqual($root_user->id(), 1);
$authenticated_user = $this->createUser(['administer permissions']);
$role = $authenticated_user->getRoles()[1];
$test_element = [
'#cache' => [
'keys' => ['test'],
'contexts' => ['user', 'user.roles'],
],
];
\Drupal::service('account_switcher')->switchTo($authenticated_user);
$element = $test_element;
$element['#markup'] = 'content for authenticated users';
$output = \Drupal::service('renderer')->renderRoot($element);
$this->assertEqual($output, 'content for authenticated users');
// Verify that the render caching is working so that other tests can be
// trusted.
$element = $test_element;
$element['#markup'] = 'this should not be visible';
$output = \Drupal::service('renderer')->renderRoot($element);
$this->assertEqual($output, 'content for authenticated users');
// Even though the cache contexts have been optimized to only include 'user'
// cache context, the element should have been changed because 'user.roles'
// cache context defined a cache tag for user entity changes, which should
// have bubbled up for the element when it was optimized away.
$authenticated_user->removeRole($role);
$authenticated_user->save();
$element = $test_element;
$element['#markup'] = 'this should be visible';
$output = \Drupal::service('renderer')->renderRoot($element);
$this->assertEqual($output, 'this should be visible');
}
}

View file

@ -0,0 +1,32 @@
<?php
namespace Drupal\KernelTests\Core\Cache;
use Drupal\Core\Cache\ChainedFastBackend;
use Drupal\Core\Cache\DatabaseBackend;
use Drupal\Core\Cache\PhpBackend;
/**
* Unit test of the fast chained backend using the generic cache unit test base.
*
* @group Cache
*/
class ChainedFastBackendTest extends GenericCacheBackendUnitTestBase {
/**
* Creates a new instance of ChainedFastBackend.
*
* @return \Drupal\Core\Cache\ChainedFastBackend
* A new ChainedFastBackend object.
*/
protected function createCacheBackend($bin) {
$consistent_backend = new DatabaseBackend(\Drupal::service('database'), \Drupal::service('cache_tags.invalidator.checksum'), $bin);
$fast_backend = new PhpBackend($bin, \Drupal::service('cache_tags.invalidator.checksum'));
$backend = new ChainedFastBackend($consistent_backend, $fast_backend, $bin);
// Explicitly register the cache bin as it can not work through the
// cache bin list in the container.
\Drupal::service('cache_tags.invalidator')->addInvalidator($backend);
return $backend;
}
}

View file

@ -0,0 +1,60 @@
<?php
namespace Drupal\KernelTests\Core\Cache;
use Drupal\Core\Cache\Cache;
use Drupal\Core\DependencyInjection\ContainerBuilder;
use Drupal\KernelTests\KernelTestBase;
use Symfony\Component\DependencyInjection\Reference;
/**
* Tests DatabaseBackend cache tag implementation.
*
* @group Cache
*/
class DatabaseBackendTagTest extends KernelTestBase {
/**
* Modules to enable.
*
* @var array
*/
public static $modules = array('system');
/**
* {@inheritdoc}
*/
public function register(ContainerBuilder $container) {
parent::register($container);
// Change container to database cache backends.
$container
->register('cache_factory', 'Drupal\Core\Cache\CacheFactory')
->addArgument(new Reference('settings'))
->addMethodCall('setContainer', array(new Reference('service_container')));
}
public function testTagInvalidations() {
// Create cache entry in multiple bins.
$tags = array('test_tag:1', 'test_tag:2', 'test_tag:3');
$bins = array('data', 'bootstrap', 'render');
foreach ($bins as $bin) {
$bin = \Drupal::cache($bin);
$bin->set('test', 'value', Cache::PERMANENT, $tags);
$this->assertTrue($bin->get('test'), 'Cache item was set in bin.');
}
$invalidations_before = intval(db_select('cachetags')->fields('cachetags', array('invalidations'))->condition('tag', 'test_tag:2')->execute()->fetchField());
Cache::invalidateTags(array('test_tag:2'));
// Test that cache entry has been invalidated in multiple bins.
foreach ($bins as $bin) {
$bin = \Drupal::cache($bin);
$this->assertFalse($bin->get('test'), 'Tag invalidation affected item in bin.');
}
// Test that only one tag invalidation has occurred.
$invalidations_after = intval(db_select('cachetags')->fields('cachetags', array('invalidations'))->condition('tag', 'test_tag:2')->execute()->fetchField());
$this->assertEqual($invalidations_after, $invalidations_before + 1, 'Only one addition cache tag invalidation has occurred after invalidating a tag used in multiple bins.');
}
}

View file

@ -0,0 +1,51 @@
<?php
namespace Drupal\KernelTests\Core\Cache;
use Drupal\Core\Cache\DatabaseBackend;
/**
* Unit test of the database backend using the generic cache unit test base.
*
* @group Cache
*/
class DatabaseBackendTest extends GenericCacheBackendUnitTestBase {
/**
* Modules to enable.
*
* @var array
*/
public static $modules = array('system');
/**
* Creates a new instance of DatabaseBackend.
*
* @return
* A new DatabaseBackend object.
*/
protected function createCacheBackend($bin) {
return new DatabaseBackend($this->container->get('database'), $this->container->get('cache_tags.invalidator.checksum'), $bin);
}
/**
* {@inheritdoc}
*/
public function testSetGet() {
parent::testSetGet();
$backend = $this->getCacheBackend();
// Set up a cache ID that is not ASCII and longer than 255 characters so we
// can test cache ID normalization.
$cid_long = str_repeat('愛€', 500);
$cached_value_long = $this->randomMachineName();
$backend->set($cid_long, $cached_value_long);
$this->assertIdentical($cached_value_long, $backend->get($cid_long)->data, "Backend contains the correct value for long, non-ASCII cache id.");
$cid_short = '愛1€';
$cached_value_short = $this->randomMachineName();
$backend->set($cid_short, $cached_value_short);
$this->assertIdentical($cached_value_short, $backend->get($cid_short)->data, "Backend contains the correct value for short, non-ASCII cache id.");
}
}

View file

@ -0,0 +1,620 @@
<?php
namespace Drupal\KernelTests\Core\Cache;
use Drupal\Core\Cache\Cache;
use Drupal\Core\Cache\CacheBackendInterface;
use Drupal\KernelTests\KernelTestBase;
/**
* Tests any cache backend.
*
* Full generic unit test suite for any cache backend. In order to use it for a
* cache backend implementation, extend this class and override the
* createBackendInstance() method to return an object.
*
* @see DatabaseBackendUnitTestCase
* For a full working implementation.
*/
abstract class GenericCacheBackendUnitTestBase extends KernelTestBase {
/**
* Array of objects implementing Drupal\Core\Cache\CacheBackendInterface.
*
* @var array
*/
protected $cachebackends;
/**
* Cache bin to use for testing.
*
* @var string
*/
protected $testBin;
/**
* Random value to use in tests.
*
* @var string
*/
protected $defaultValue;
/**
* Gets the testing bin.
*
* Override this method if you want to work on a different bin than the
* default one.
*
* @return string
* Bin name.
*/
protected function getTestBin() {
if (!isset($this->testBin)) {
$this->testBin = 'page';
}
return $this->testBin;
}
/**
* Creates a cache backend to test.
*
* Override this method to test a CacheBackend.
*
* @param string $bin
* Bin name to use for this backend instance.
*
* @return \Drupal\Core\Cache\CacheBackendInterface
* Cache backend to test.
*/
protected abstract function createCacheBackend($bin);
/**
* Allows specific implementation to change the environment before a test run.
*/
public function setUpCacheBackend() {
}
/**
* Allows alteration of environment after a test run but before tear down.
*
* Used before the real tear down because the tear down will change things
* such as the database prefix.
*/
public function tearDownCacheBackend() {
}
/**
* Gets a backend to test; this will get a shared instance set in the object.
*
* @return \Drupal\Core\Cache\CacheBackendInterface
* Cache backend to test.
*/
protected function getCacheBackend($bin = NULL) {
if (!isset($bin)) {
$bin = $this->getTestBin();
}
if (!isset($this->cachebackends[$bin])) {
$this->cachebackends[$bin] = $this->createCacheBackend($bin);
// Ensure the backend is empty.
$this->cachebackends[$bin]->deleteAll();
}
return $this->cachebackends[$bin];
}
protected function setUp() {
$this->cachebackends = array();
$this->defaultValue = $this->randomMachineName(10);
parent::setUp();
$this->setUpCacheBackend();
}
protected function tearDown() {
// Destruct the registered backend, each test will get a fresh instance,
// properly emptying it here ensure that on persistent data backends they
// will come up empty the next test.
foreach ($this->cachebackends as $bin => $cachebackend) {
$this->cachebackends[$bin]->deleteAll();
}
unset($this->cachebackends);
$this->tearDownCacheBackend();
parent::tearDown();
}
/**
* Tests the get and set methods of Drupal\Core\Cache\CacheBackendInterface.
*/
public function testSetGet() {
$backend = $this->getCacheBackend();
$this->assertIdentical(FALSE, $backend->get('test1'), "Backend does not contain data for cache id test1.");
$with_backslash = array('foo' => '\Drupal\foo\Bar');
$backend->set('test1', $with_backslash);
$cached = $backend->get('test1');
$this->assert(is_object($cached), "Backend returned an object for cache id test1.");
$this->assertIdentical($with_backslash, $cached->data);
$this->assertTrue($cached->valid, 'Item is marked as valid.');
// We need to round because microtime may be rounded up in the backend.
$this->assertTrue($cached->created >= REQUEST_TIME && $cached->created <= round(microtime(TRUE), 3), 'Created time is correct.');
$this->assertEqual($cached->expire, Cache::PERMANENT, 'Expire time is correct.');
$this->assertIdentical(FALSE, $backend->get('test2'), "Backend does not contain data for cache id test2.");
$backend->set('test2', array('value' => 3), REQUEST_TIME + 3);
$cached = $backend->get('test2');
$this->assert(is_object($cached), "Backend returned an object for cache id test2.");
$this->assertIdentical(array('value' => 3), $cached->data);
$this->assertTrue($cached->valid, 'Item is marked as valid.');
$this->assertTrue($cached->created >= REQUEST_TIME && $cached->created <= round(microtime(TRUE), 3), 'Created time is correct.');
$this->assertEqual($cached->expire, REQUEST_TIME + 3, 'Expire time is correct.');
$backend->set('test3', 'foobar', REQUEST_TIME - 3);
$this->assertFalse($backend->get('test3'), 'Invalid item not returned.');
$cached = $backend->get('test3', TRUE);
$this->assert(is_object($cached), 'Backend returned an object for cache id test3.');
$this->assertFalse($cached->valid, 'Item is marked as valid.');
$this->assertTrue($cached->created >= REQUEST_TIME && $cached->created <= round(microtime(TRUE), 3), 'Created time is correct.');
$this->assertEqual($cached->expire, REQUEST_TIME - 3, 'Expire time is correct.');
$this->assertIdentical(FALSE, $backend->get('test4'), "Backend does not contain data for cache id test4.");
$with_eof = array('foo' => "\nEOF\ndata");
$backend->set('test4', $with_eof);
$cached = $backend->get('test4');
$this->assert(is_object($cached), "Backend returned an object for cache id test4.");
$this->assertIdentical($with_eof, $cached->data);
$this->assertTrue($cached->valid, 'Item is marked as valid.');
$this->assertTrue($cached->created >= REQUEST_TIME && $cached->created <= round(microtime(TRUE), 3), 'Created time is correct.');
$this->assertEqual($cached->expire, Cache::PERMANENT, 'Expire time is correct.');
$this->assertIdentical(FALSE, $backend->get('test5'), "Backend does not contain data for cache id test5.");
$with_eof_and_semicolon = array('foo' => "\nEOF;\ndata");
$backend->set('test5', $with_eof_and_semicolon);
$cached = $backend->get('test5');
$this->assert(is_object($cached), "Backend returned an object for cache id test5.");
$this->assertIdentical($with_eof_and_semicolon, $cached->data);
$this->assertTrue($cached->valid, 'Item is marked as valid.');
$this->assertTrue($cached->created >= REQUEST_TIME && $cached->created <= round(microtime(TRUE), 3), 'Created time is correct.');
$this->assertEqual($cached->expire, Cache::PERMANENT, 'Expire time is correct.');
$with_variable = array('foo' => '$bar');
$backend->set('test6', $with_variable);
$cached = $backend->get('test6');
$this->assert(is_object($cached), "Backend returned an object for cache id test6.");
$this->assertIdentical($with_variable, $cached->data);
// Make sure that a cached object is not affected by changing the original.
$data = new \stdClass();
$data->value = 1;
$data->obj = new \stdClass();
$data->obj->value = 2;
$backend->set('test7', $data);
$expected_data = clone $data;
// Add a property to the original. It should not appear in the cached data.
$data->this_should_not_be_in_the_cache = TRUE;
$cached = $backend->get('test7');
$this->assert(is_object($cached), "Backend returned an object for cache id test7.");
$this->assertEqual($expected_data, $cached->data);
$this->assertFalse(isset($cached->data->this_should_not_be_in_the_cache));
// Add a property to the cache data. It should not appear when we fetch
// the data from cache again.
$cached->data->this_should_not_be_in_the_cache = TRUE;
$fresh_cached = $backend->get('test7');
$this->assertFalse(isset($fresh_cached->data->this_should_not_be_in_the_cache));
// Check with a long key.
$cid = str_repeat('a', 300);
$backend->set($cid, 'test');
$this->assertEqual('test', $backend->get($cid)->data);
// Check that the cache key is case sensitive.
$backend->set('TEST8', 'value');
$this->assertEqual('value', $backend->get('TEST8')->data);
$this->assertFalse($backend->get('test8'));
// Calling ::set() with invalid cache tags. This should fail an assertion.
try {
$backend->set('assertion_test', 'value', Cache::PERMANENT, ['node' => [3, 5, 7]]);
$this->fail('::set() was called with invalid cache tags, runtime assertion did not fail.');
}
catch (\AssertionError $e) {
$this->pass('::set() was called with invalid cache tags, runtime assertion failed.');
}
}
/**
* Tests Drupal\Core\Cache\CacheBackendInterface::delete().
*/
public function testDelete() {
$backend = $this->getCacheBackend();
$this->assertIdentical(FALSE, $backend->get('test1'), "Backend does not contain data for cache id test1.");
$backend->set('test1', 7);
$this->assert(is_object($backend->get('test1')), "Backend returned an object for cache id test1.");
$this->assertIdentical(FALSE, $backend->get('test2'), "Backend does not contain data for cache id test2.");
$backend->set('test2', 3);
$this->assert(is_object($backend->get('test2')), "Backend returned an object for cache id %cid.");
$backend->delete('test1');
$this->assertIdentical(FALSE, $backend->get('test1'), "Backend does not contain data for cache id test1 after deletion.");
$this->assert(is_object($backend->get('test2')), "Backend still has an object for cache id test2.");
$backend->delete('test2');
$this->assertIdentical(FALSE, $backend->get('test2'), "Backend does not contain data for cache id test2 after deletion.");
$long_cid = str_repeat('a', 300);
$backend->set($long_cid, 'test');
$backend->delete($long_cid);
$this->assertIdentical(FALSE, $backend->get($long_cid), "Backend does not contain data for long cache id after deletion.");
}
/**
* Tests data type preservation.
*/
public function testValueTypeIsKept() {
$backend = $this->getCacheBackend();
$variables = array(
'test1' => 1,
'test2' => '0',
'test3' => '',
'test4' => 12.64,
'test5' => FALSE,
'test6' => array(1, 2, 3),
);
// Create cache entries.
foreach ($variables as $cid => $data) {
$backend->set($cid, $data);
}
// Retrieve and test cache objects.
foreach ($variables as $cid => $value) {
$object = $backend->get($cid);
$this->assert(is_object($object), sprintf("Backend returned an object for cache id %s.", $cid));
$this->assertIdentical($value, $object->data, sprintf("Data of cached id %s kept is identical in type and value", $cid));
}
}
/**
* Tests Drupal\Core\Cache\CacheBackendInterface::getMultiple().
*/
public function testGetMultiple() {
$backend = $this->getCacheBackend();
// Set numerous testing keys.
$long_cid = str_repeat('a', 300);
$backend->set('test1', 1);
$backend->set('test2', 3);
$backend->set('test3', 5);
$backend->set('test4', 7);
$backend->set('test5', 11);
$backend->set('test6', 13);
$backend->set('test7', 17);
$backend->set($long_cid, 300);
// Mismatch order for harder testing.
$reference = array(
'test3',
'test7',
'test21', // Cid does not exist.
'test6',
'test19', // Cid does not exist until added before second getMultiple().
'test2',
);
$cids = $reference;
$ret = $backend->getMultiple($cids);
// Test return - ensure it contains existing cache ids.
$this->assert(isset($ret['test2']), "Existing cache id test2 is set.");
$this->assert(isset($ret['test3']), "Existing cache id test3 is set.");
$this->assert(isset($ret['test6']), "Existing cache id test6 is set.");
$this->assert(isset($ret['test7']), "Existing cache id test7 is set.");
// Test return - ensure that objects has expected properties.
$this->assertTrue($ret['test2']->valid, 'Item is marked as valid.');
$this->assertTrue($ret['test2']->created >= REQUEST_TIME && $ret['test2']->created <= round(microtime(TRUE), 3), 'Created time is correct.');
$this->assertEqual($ret['test2']->expire, Cache::PERMANENT, 'Expire time is correct.');
// Test return - ensure it does not contain nonexistent cache ids.
$this->assertFalse(isset($ret['test19']), "Nonexistent cache id test19 is not set.");
$this->assertFalse(isset($ret['test21']), "Nonexistent cache id test21 is not set.");
// Test values.
$this->assertIdentical($ret['test2']->data, 3, "Existing cache id test2 has the correct value.");
$this->assertIdentical($ret['test3']->data, 5, "Existing cache id test3 has the correct value.");
$this->assertIdentical($ret['test6']->data, 13, "Existing cache id test6 has the correct value.");
$this->assertIdentical($ret['test7']->data, 17, "Existing cache id test7 has the correct value.");
// Test $cids array - ensure it contains cache id's that do not exist.
$this->assert(in_array('test19', $cids), "Nonexistent cache id test19 is in cids array.");
$this->assert(in_array('test21', $cids), "Nonexistent cache id test21 is in cids array.");
// Test $cids array - ensure it does not contain cache id's that exist.
$this->assertFalse(in_array('test2', $cids), "Existing cache id test2 is not in cids array.");
$this->assertFalse(in_array('test3', $cids), "Existing cache id test3 is not in cids array.");
$this->assertFalse(in_array('test6', $cids), "Existing cache id test6 is not in cids array.");
$this->assertFalse(in_array('test7', $cids), "Existing cache id test7 is not in cids array.");
// Test a second time after deleting and setting new keys which ensures that
// if the backend uses statics it does not cause unexpected results.
$backend->delete('test3');
$backend->delete('test6');
$backend->set('test19', 57);
$cids = $reference;
$ret = $backend->getMultiple($cids);
// Test return - ensure it contains existing cache ids.
$this->assert(isset($ret['test2']), "Existing cache id test2 is set");
$this->assert(isset($ret['test7']), "Existing cache id test7 is set");
$this->assert(isset($ret['test19']), "Added cache id test19 is set");
// Test return - ensure it does not contain nonexistent cache ids.
$this->assertFalse(isset($ret['test3']), "Deleted cache id test3 is not set");
$this->assertFalse(isset($ret['test6']), "Deleted cache id test6 is not set");
$this->assertFalse(isset($ret['test21']), "Nonexistent cache id test21 is not set");
// Test values.
$this->assertIdentical($ret['test2']->data, 3, "Existing cache id test2 has the correct value.");
$this->assertIdentical($ret['test7']->data, 17, "Existing cache id test7 has the correct value.");
$this->assertIdentical($ret['test19']->data, 57, "Added cache id test19 has the correct value.");
// Test $cids array - ensure it contains cache id's that do not exist.
$this->assert(in_array('test3', $cids), "Deleted cache id test3 is in cids array.");
$this->assert(in_array('test6', $cids), "Deleted cache id test6 is in cids array.");
$this->assert(in_array('test21', $cids), "Nonexistent cache id test21 is in cids array.");
// Test $cids array - ensure it does not contain cache id's that exist.
$this->assertFalse(in_array('test2', $cids), "Existing cache id test2 is not in cids array.");
$this->assertFalse(in_array('test7', $cids), "Existing cache id test7 is not in cids array.");
$this->assertFalse(in_array('test19', $cids), "Added cache id test19 is not in cids array.");
// Test with a long $cid and non-numeric array key.
$cids = array('key:key' => $long_cid);
$return = $backend->getMultiple($cids);
$this->assertEqual(300, $return[$long_cid]->data);
$this->assertTrue(empty($cids));
}
/**
* Tests \Drupal\Core\Cache\CacheBackendInterface::setMultiple().
*/
public function testSetMultiple() {
$backend = $this->getCacheBackend();
$future_expiration = REQUEST_TIME + 100;
// Set multiple testing keys.
$backend->set('cid_1', 'Some other value');
$items = array(
'cid_1' => array('data' => 1),
'cid_2' => array('data' => 2),
'cid_3' => array('data' => array(1, 2)),
'cid_4' => array('data' => 1, 'expire' => $future_expiration),
'cid_5' => array('data' => 1, 'tags' => array('test:a', 'test:b')),
);
$backend->setMultiple($items);
$cids = array_keys($items);
$cached = $backend->getMultiple($cids);
$this->assertEqual($cached['cid_1']->data, $items['cid_1']['data'], 'Over-written cache item set correctly.');
$this->assertTrue($cached['cid_1']->valid, 'Item is marked as valid.');
$this->assertTrue($cached['cid_1']->created >= REQUEST_TIME && $cached['cid_1']->created <= round(microtime(TRUE), 3), 'Created time is correct.');
$this->assertEqual($cached['cid_1']->expire, CacheBackendInterface::CACHE_PERMANENT, 'Cache expiration defaults to permanent.');
$this->assertEqual($cached['cid_2']->data, $items['cid_2']['data'], 'New cache item set correctly.');
$this->assertEqual($cached['cid_2']->expire, CacheBackendInterface::CACHE_PERMANENT, 'Cache expiration defaults to permanent.');
$this->assertEqual($cached['cid_3']->data, $items['cid_3']['data'], 'New cache item with serialized data set correctly.');
$this->assertEqual($cached['cid_3']->expire, CacheBackendInterface::CACHE_PERMANENT, 'Cache expiration defaults to permanent.');
$this->assertEqual($cached['cid_4']->data, $items['cid_4']['data'], 'New cache item set correctly.');
$this->assertEqual($cached['cid_4']->expire, $future_expiration, 'Cache expiration has been correctly set.');
$this->assertEqual($cached['cid_5']->data, $items['cid_5']['data'], 'New cache item set correctly.');
// Calling ::setMultiple() with invalid cache tags. This should fail an
// assertion.
try {
$items = [
'exception_test_1' => array('data' => 1, 'tags' => []),
'exception_test_2' => array('data' => 2, 'tags' => ['valid']),
'exception_test_3' => array('data' => 3, 'tags' => ['node' => [3, 5, 7]]),
];
$backend->setMultiple($items);
$this->fail('::setMultiple() was called with invalid cache tags, runtime assertion did not fail.');
}
catch (\AssertionError $e) {
$this->pass('::setMultiple() was called with invalid cache tags, runtime assertion failed.');
}
}
/**
* Test Drupal\Core\Cache\CacheBackendInterface::delete() and
* Drupal\Core\Cache\CacheBackendInterface::deleteMultiple().
*/
public function testDeleteMultiple() {
$backend = $this->getCacheBackend();
// Set numerous testing keys.
$backend->set('test1', 1);
$backend->set('test2', 3);
$backend->set('test3', 5);
$backend->set('test4', 7);
$backend->set('test5', 11);
$backend->set('test6', 13);
$backend->set('test7', 17);
$backend->delete('test1');
$backend->delete('test23'); // Nonexistent key should not cause an error.
$backend->deleteMultiple(array(
'test3',
'test5',
'test7',
'test19', // Nonexistent key should not cause an error.
'test21', // Nonexistent key should not cause an error.
));
// Test if expected keys have been deleted.
$this->assertIdentical(FALSE, $backend->get('test1'), "Cache id test1 deleted.");
$this->assertIdentical(FALSE, $backend->get('test3'), "Cache id test3 deleted.");
$this->assertIdentical(FALSE, $backend->get('test5'), "Cache id test5 deleted.");
$this->assertIdentical(FALSE, $backend->get('test7'), "Cache id test7 deleted.");
// Test if expected keys exist.
$this->assertNotIdentical(FALSE, $backend->get('test2'), "Cache id test2 exists.");
$this->assertNotIdentical(FALSE, $backend->get('test4'), "Cache id test4 exists.");
$this->assertNotIdentical(FALSE, $backend->get('test6'), "Cache id test6 exists.");
// Test if that expected keys do not exist.
$this->assertIdentical(FALSE, $backend->get('test19'), "Cache id test19 does not exist.");
$this->assertIdentical(FALSE, $backend->get('test21'), "Cache id test21 does not exist.");
// Calling deleteMultiple() with an empty array should not cause an error.
$this->assertFalse($backend->deleteMultiple(array()));
}
/**
* Test Drupal\Core\Cache\CacheBackendInterface::deleteAll().
*/
public function testDeleteAll() {
$backend_a = $this->getCacheBackend();
$backend_b = $this->getCacheBackend('bootstrap');
// Set both expiring and permanent keys.
$backend_a->set('test1', 1, Cache::PERMANENT);
$backend_a->set('test2', 3, time() + 1000);
$backend_b->set('test3', 4, Cache::PERMANENT);
$backend_a->deleteAll();
$this->assertFalse($backend_a->get('test1'), 'First key has been deleted.');
$this->assertFalse($backend_a->get('test2'), 'Second key has been deleted.');
$this->assertTrue($backend_b->get('test3'), 'Item in other bin is preserved.');
}
/**
* Test Drupal\Core\Cache\CacheBackendInterface::invalidate() and
* Drupal\Core\Cache\CacheBackendInterface::invalidateMultiple().
*/
function testInvalidate() {
$backend = $this->getCacheBackend();
$backend->set('test1', 1);
$backend->set('test2', 2);
$backend->set('test3', 2);
$backend->set('test4', 2);
$reference = array('test1', 'test2', 'test3', 'test4');
$cids = $reference;
$ret = $backend->getMultiple($cids);
$this->assertEqual(count($ret), 4, 'Four items returned.');
$backend->invalidate('test1');
$backend->invalidateMultiple(array('test2', 'test3'));
$cids = $reference;
$ret = $backend->getMultiple($cids);
$this->assertEqual(count($ret), 1, 'Only one item element returned.');
$cids = $reference;
$ret = $backend->getMultiple($cids, TRUE);
$this->assertEqual(count($ret), 4, 'Four items returned.');
// Calling invalidateMultiple() with an empty array should not cause an
// error.
$this->assertFalse($backend->invalidateMultiple(array()));
}
/**
* Tests Drupal\Core\Cache\CacheBackendInterface::invalidateTags().
*/
function testInvalidateTags() {
$backend = $this->getCacheBackend();
// Create two cache entries with the same tag and tag value.
$backend->set('test_cid_invalidate1', $this->defaultValue, Cache::PERMANENT, array('test_tag:2'));
$backend->set('test_cid_invalidate2', $this->defaultValue, Cache::PERMANENT, array('test_tag:2'));
$this->assertTrue($backend->get('test_cid_invalidate1') && $backend->get('test_cid_invalidate2'), 'Two cache items were created.');
// Invalidate test_tag of value 1. This should invalidate both entries.
Cache::invalidateTags(array('test_tag:2'));
$this->assertFalse($backend->get('test_cid_invalidate1') || $backend->get('test_cid_invalidate2'), 'Two cache items invalidated after invalidating a cache tag.');
$this->assertTrue($backend->get('test_cid_invalidate1', TRUE) && $backend->get('test_cid_invalidate2', TRUE), 'Cache items not deleted after invalidating a cache tag.');
// Create two cache entries with the same tag and an array tag value.
$backend->set('test_cid_invalidate1', $this->defaultValue, Cache::PERMANENT, array('test_tag:1'));
$backend->set('test_cid_invalidate2', $this->defaultValue, Cache::PERMANENT, array('test_tag:1'));
$this->assertTrue($backend->get('test_cid_invalidate1') && $backend->get('test_cid_invalidate2'), 'Two cache items were created.');
// Invalidate test_tag of value 1. This should invalidate both entries.
Cache::invalidateTags(array('test_tag:1'));
$this->assertFalse($backend->get('test_cid_invalidate1') || $backend->get('test_cid_invalidate2'), 'Two caches removed after invalidating a cache tag.');
$this->assertTrue($backend->get('test_cid_invalidate1', TRUE) && $backend->get('test_cid_invalidate2', TRUE), 'Cache items not deleted after invalidating a cache tag.');
// Create three cache entries with a mix of tags and tag values.
$backend->set('test_cid_invalidate1', $this->defaultValue, Cache::PERMANENT, array('test_tag:1'));
$backend->set('test_cid_invalidate2', $this->defaultValue, Cache::PERMANENT, array('test_tag:2'));
$backend->set('test_cid_invalidate3', $this->defaultValue, Cache::PERMANENT, array('test_tag_foo:3'));
$this->assertTrue($backend->get('test_cid_invalidate1') && $backend->get('test_cid_invalidate2') && $backend->get('test_cid_invalidate3'), 'Three cached items were created.');
Cache::invalidateTags(array('test_tag_foo:3'));
$this->assertTrue($backend->get('test_cid_invalidate1') && $backend->get('test_cid_invalidate2'), 'Cache items not matching the tag were not invalidated.');
$this->assertFalse($backend->get('test_cid_invalidated3'), 'Cached item matching the tag was removed.');
// Create cache entry in multiple bins. Two cache entries
// (test_cid_invalidate1 and test_cid_invalidate2) still exist from previous
// tests.
$tags = array('test_tag:1', 'test_tag:2', 'test_tag:3');
$bins = array('path', 'bootstrap', 'page');
foreach ($bins as $bin) {
$this->getCacheBackend($bin)->set('test', $this->defaultValue, Cache::PERMANENT, $tags);
$this->assertTrue($this->getCacheBackend($bin)->get('test'), 'Cache item was set in bin.');
}
Cache::invalidateTags(array('test_tag:2'));
// Test that the cache entry has been invalidated in multiple bins.
foreach ($bins as $bin) {
$this->assertFalse($this->getCacheBackend($bin)->get('test'), 'Tag invalidation affected item in bin.');
}
// Test that the cache entry with a matching tag has been invalidated.
$this->assertFalse($this->getCacheBackend($bin)->get('test_cid_invalidate2'), 'Cache items matching tag were invalidated.');
// Test that the cache entry with without a matching tag still exists.
$this->assertTrue($this->getCacheBackend($bin)->get('test_cid_invalidate1'), 'Cache items not matching tag were not invalidated.');
}
/**
* Test Drupal\Core\Cache\CacheBackendInterface::invalidateAll().
*/
public function testInvalidateAll() {
$backend_a = $this->getCacheBackend();
$backend_b = $this->getCacheBackend('bootstrap');
// Set both expiring and permanent keys.
$backend_a->set('test1', 1, Cache::PERMANENT);
$backend_a->set('test2', 3, time() + 1000);
$backend_b->set('test3', 4, Cache::PERMANENT);
$backend_a->invalidateAll();
$this->assertFalse($backend_a->get('test1'), 'First key has been invalidated.');
$this->assertFalse($backend_a->get('test2'), 'Second key has been invalidated.');
$this->assertTrue($backend_b->get('test3'), 'Item in other bin is preserved.');
$this->assertTrue($backend_a->get('test1', TRUE), 'First key has not been deleted.');
$this->assertTrue($backend_a->get('test2', TRUE), 'Second key has not been deleted.');
}
/**
* Tests Drupal\Core\Cache\CacheBackendInterface::removeBin().
*/
public function testRemoveBin() {
$backend_a = $this->getCacheBackend();
$backend_b = $this->getCacheBackend('bootstrap');
// Set both expiring and permanent keys.
$backend_a->set('test1', 1, Cache::PERMANENT);
$backend_a->set('test2', 3, time() + 1000);
$backend_b->set('test3', 4, Cache::PERMANENT);
$backend_a->removeBin();
$this->assertFalse($backend_a->get('test1'), 'First key has been deleted.');
$this->assertFalse($backend_a->get('test2', TRUE), 'Second key has been deleted.');
$this->assertTrue($backend_b->get('test3'), 'Item in other bin is preserved.');
}
}

View file

@ -0,0 +1,26 @@
<?php
namespace Drupal\KernelTests\Core\Cache;
use Drupal\Core\Cache\MemoryBackend;
/**
* Unit test of the memory cache backend using the generic cache unit test base.
*
* @group Cache
*/
class MemoryBackendTest extends GenericCacheBackendUnitTestBase {
/**
* Creates a new instance of MemoryBackend.
*
* @return
* A new MemoryBackend object.
*/
protected function createCacheBackend($bin) {
$backend = new MemoryBackend($bin);
\Drupal::service('cache_tags.invalidator')->addInvalidator($backend);
return $backend;
}
}

View file

@ -0,0 +1,25 @@
<?php
namespace Drupal\KernelTests\Core\Cache;
use Drupal\Core\Cache\PhpBackend;
/**
* Unit test of the PHP cache backend using the generic cache unit test base.
*
* @group Cache
*/
class PhpBackendTest extends GenericCacheBackendUnitTestBase {
/**
* Creates a new instance of MemoryBackend.
*
* @return
* A new MemoryBackend object.
*/
protected function createCacheBackend($bin) {
$backend = new PhpBackend($bin, \Drupal::service('cache_tags.invalidator.checksum'));
return $backend;
}
}

View file

@ -0,0 +1,277 @@
<?php
namespace Drupal\KernelTests\Core\Command;
use Drupal\Component\Utility\SafeMarkup;
use Drupal\Core\Command\DbDumpApplication;
use Drupal\Core\Config\DatabaseStorage;
use Drupal\Core\Database\Database;
use Drupal\Core\DependencyInjection\ContainerBuilder;
use Drupal\KernelTests\KernelTestBase;
use Drupal\user\Entity\User;
use Symfony\Component\Console\Tester\CommandTester;
use Symfony\Component\DependencyInjection\Reference;
/**
* Tests for the database dump commands.
*
* @group Update
*/
class DbDumpTest extends KernelTestBase {
/**
* {@inheritdoc}
*/
public static $modules = ['system', 'config', 'dblog', 'menu_link_content', 'link', 'block_content', 'file', 'user'];
/**
* Test data to write into config.
*
* @var array
*/
protected $data;
/**
* Flag to skip these tests, which are database-backend dependent (MySQL).
*
* @see \Drupal\Core\Command\DbDumpCommand
*
* @var bool
*/
protected $skipTests = FALSE;
/**
* An array of original table schemas.
*
* @var array
*/
protected $originalTableSchemas = [];
/**
* An array of original table indexes (including primary and unique keys).
*
* @var array
*/
protected $originalTableIndexes = [];
/**
* Tables that should be part of the exported script.
*
* @var array
*/
protected $tables;
/**
* {@inheritdoc}
*
* Register a database cache backend rather than memory-based.
*/
public function register(ContainerBuilder $container) {
parent::register($container);
$container->register('cache_factory', 'Drupal\Core\Cache\DatabaseBackendFactory')
->addArgument(new Reference('database'))
->addArgument(new Reference('cache_tags.invalidator.checksum'));
}
/**
* {@inheritdoc}
*/
protected function setUp() {
parent::setUp();
// Determine what database backend is running, and set the skip flag.
$this->skipTests = Database::getConnection()->databaseType() !== 'mysql';
// Create some schemas so our export contains tables.
$this->installSchema('system', [
'key_value_expire',
'sessions',
]);
$this->installSchema('dblog', ['watchdog']);
$this->installEntitySchema('block_content');
$this->installEntitySchema('user');
$this->installEntitySchema('file');
$this->installEntitySchema('menu_link_content');
$this->installSchema('system', 'sequences');
// Place some sample config to test for in the export.
$this->data = [
'foo' => $this->randomMachineName(),
'bar' => $this->randomMachineName(),
];
$storage = new DatabaseStorage(Database::getConnection(), 'config');
$storage->write('test_config', $this->data);
// Create user account with some potential syntax issues.
$account = User::create(['mail' => 'q\'uote$dollar@example.com', 'name' => '$dollar']);
$account->save();
// Create url_alias (this will create 'url_alias').
$this->container->get('path.alias_storage')->save('/user/' . $account->id(), '/user/example');
// Create a cache table (this will create 'cache_discovery').
\Drupal::cache('discovery')->set('test', $this->data);
// These are all the tables that should now be in place.
$this->tables = [
'block_content',
'block_content_field_data',
'block_content_field_revision',
'block_content_revision',
'cachetags',
'config',
'cache_bootstrap',
'cache_config',
'cache_data',
'cache_discovery',
'cache_entity',
'file_managed',
'key_value_expire',
'menu_link_content',
'menu_link_content_data',
'sequences',
'sessions',
'url_alias',
'user__roles',
'users',
'users_field_data',
'watchdog',
];
}
/**
* Test the command directly.
*/
public function testDbDumpCommand() {
if ($this->skipTests) {
$this->pass("Skipping test since the DbDumpCommand is currently only compatible with MySql");
return;
}
$application = new DbDumpApplication();
$command = $application->find('dump-database-d8-mysql');
$command_tester = new CommandTester($command);
$command_tester->execute([]);
// Tables that are schema-only should not have data exported.
$pattern = preg_quote("\$connection->insert('sessions')");
$this->assertFalse(preg_match('/' . $pattern . '/', $command_tester->getDisplay()), 'Tables defined as schema-only do not have data exported to the script.');
// Table data is exported.
$pattern = preg_quote("\$connection->insert('config')");
$this->assertTrue(preg_match('/' . $pattern . '/', $command_tester->getDisplay()), 'Table data is properly exported to the script.');
// The test data are in the dump (serialized).
$pattern = preg_quote(serialize($this->data));
$this->assertTrue(preg_match('/' . $pattern . '/', $command_tester->getDisplay()), 'Generated data is found in the exported script.');
// Check that the user account name and email address was properly escaped.
$pattern = preg_quote('"q\'uote\$dollar@example.com"');
$this->assertTrue(preg_match('/' . $pattern . '/', $command_tester->getDisplay()), 'The user account email address was properly escaped in the exported script.');
$pattern = preg_quote('\'$dollar\'');
$this->assertTrue(preg_match('/' . $pattern . '/', $command_tester->getDisplay()), 'The user account name was properly escaped in the exported script.');
}
/**
* Test loading the script back into the database.
*/
public function testScriptLoad() {
if ($this->skipTests) {
$this->pass("Skipping test since the DbDumpCommand is currently only compatible with MySql");
return;
}
// Generate the script.
$application = new DbDumpApplication();
$command = $application->find('dump-database-d8-mysql');
$command_tester = new CommandTester($command);
$command_tester->execute([]);
$script = $command_tester->getDisplay();
// Store original schemas and drop tables to avoid errors.
foreach ($this->tables as $table) {
$this->originalTableSchemas[$table] = $this->getTableSchema($table);
$this->originalTableIndexes[$table] = $this->getTableIndexes($table);
Database::getConnection()->schema()->dropTable($table);
}
// This will load the data.
$file = sys_get_temp_dir() . '/' . $this->randomMachineName();
file_put_contents($file, $script);
require_once $file;
// The tables should now exist and the schemas should match the originals.
foreach ($this->tables as $table) {
$this->assertTrue(Database::getConnection()
->schema()
->tableExists($table), SafeMarkup::format('Table @table created by the database script.', ['@table' => $table]));
$this->assertIdentical($this->originalTableSchemas[$table], $this->getTableSchema($table), SafeMarkup::format('The schema for @table was properly restored.', ['@table' => $table]));
$this->assertIdentical($this->originalTableIndexes[$table], $this->getTableIndexes($table), SafeMarkup::format('The indexes for @table were properly restored.', ['@table' => $table]));
}
// Ensure the test config has been replaced.
$config = unserialize(db_query("SELECT data FROM {config} WHERE name = 'test_config'")->fetchField());
$this->assertIdentical($config, $this->data, 'Script has properly restored the config table data.');
// Ensure the cache data was not exported.
$this->assertFalse(\Drupal::cache('discovery')
->get('test'), 'Cache data was not exported to the script.');
}
/**
* Helper function to get a simplified schema for a given table.
*
* @param string $table
*
* @return array
* Array keyed by field name, with the values being the field type.
*/
protected function getTableSchema($table) {
// Verify the field type on the data column in the cache table.
// @todo this is MySQL specific.
$query = db_query("SHOW COLUMNS FROM {" . $table . "}");
$definition = [];
while ($row = $query->fetchAssoc()) {
$definition[$row['Field']] = $row['Type'];
}
return $definition;
}
/**
* Returns indexes for a given table.
*
* @param string $table
* The table to find indexes for.
*
* @return array
* The 'primary key', 'unique keys', and 'indexes' portion of the Drupal
* table schema.
*/
protected function getTableIndexes($table) {
$query = db_query("SHOW INDEX FROM {" . $table . "}");
$definition = [];
while ($row = $query->fetchAssoc()) {
$index_name = $row['Key_name'];
$column = $row['Column_name'];
// Key the arrays by the index sequence for proper ordering (start at 0).
$order = $row['Seq_in_index'] - 1;
// If specified, add length to the index.
if ($row['Sub_part']) {
$column = [$column, $row['Sub_part']];
}
if ($index_name === 'PRIMARY') {
$definition['primary key'][$order] = $column;
}
elseif ($row['Non_unique'] == 0) {
$definition['unique keys'][$index_name][$order] = $column;
}
else {
$definition['indexes'][$index_name][$order] = $column;
}
}
return $definition;
}
}

View file

@ -0,0 +1,69 @@
<?php
namespace Drupal\KernelTests\Core\Common;
use Drupal\Component\Utility\Bytes;
use Drupal\KernelTests\KernelTestBase;
/**
* Parse a predefined amount of bytes and compare the output with the expected
* value.
*
* @group Common
*/
class SizeTest extends KernelTestBase {
protected $exactTestCases;
protected $roundedTestCases;
protected function setUp() {
parent::setUp();
$kb = Bytes::KILOBYTE;
$this->exactTestCases = array(
'1 byte' => 1,
'1 KB' => $kb,
'1 MB' => $kb * $kb,
'1 GB' => $kb * $kb * $kb,
'1 TB' => $kb * $kb * $kb * $kb,
'1 PB' => $kb * $kb * $kb * $kb * $kb,
'1 EB' => $kb * $kb * $kb * $kb * $kb * $kb,
'1 ZB' => $kb * $kb * $kb * $kb * $kb * $kb * $kb,
'1 YB' => $kb * $kb * $kb * $kb * $kb * $kb * $kb * $kb,
);
$this->roundedTestCases = array(
'2 bytes' => 2,
'1 MB' => ($kb * $kb) - 1, // rounded to 1 MB (not 1000 or 1024 kilobyte!)
round(3623651 / ($this->exactTestCases['1 MB']), 2) . ' MB' => 3623651, // megabytes
round(67234178751368124 / ($this->exactTestCases['1 PB']), 2) . ' PB' => 67234178751368124, // petabytes
round(235346823821125814962843827 / ($this->exactTestCases['1 YB']), 2) . ' YB' => 235346823821125814962843827, // yottabytes
);
}
/**
* Checks that format_size() returns the expected string.
*/
function testCommonFormatSize() {
foreach (array($this->exactTestCases, $this->roundedTestCases) as $test_cases) {
foreach ($test_cases as $expected => $input) {
$this->assertEqual(
($result = format_size($input, NULL)),
$expected,
$expected . ' == ' . $result . ' (' . $input . ' bytes)'
);
}
}
}
/**
* Cross-tests Bytes::toInt() and format_size().
*/
function testCommonParseSizeFormatSize() {
foreach ($this->exactTestCases as $size) {
$this->assertEqual(
$size,
($parsed_size = Bytes::toInt($string = format_size($size, NULL))),
$size . ' == ' . $parsed_size . ' (' . $string . ')'
);
}
}
}

View file

@ -0,0 +1,57 @@
<?php
namespace Drupal\KernelTests\Core\Common;
use Drupal\Component\Utility\UrlHelper;
use Drupal\KernelTests\KernelTestBase;
/**
* Confirm that \Drupal\Component\Utility\Xss::filter() and check_url() work
* correctly, including invalid multi-byte sequences.
*
* @group Common
*/
class XssUnitTest extends KernelTestBase {
/**
* Modules to enable.
*
* @var array
*/
public static $modules = array('filter', 'system');
protected function setUp() {
parent::setUp();
$this->installConfig(array('system'));
}
/**
* Tests t() functionality.
*/
function testT() {
$text = t('Simple text');
$this->assertEqual($text, 'Simple text', 't leaves simple text alone.');
$text = t('Escaped text: @value', array('@value' => '<script>'));
$this->assertEqual($text, 'Escaped text: &lt;script&gt;', 't replaces and escapes string.');
$text = t('Placeholder text: %value', array('%value' => '<script>'));
$this->assertEqual($text, 'Placeholder text: <em class="placeholder">&lt;script&gt;</em>', 't replaces, escapes and themes string.');
}
/**
* Checks that harmful protocols are stripped.
*/
function testBadProtocolStripping() {
// Ensure that check_url() strips out harmful protocols, and encodes for
// HTML.
// Ensure \Drupal\Component\Utility\UrlHelper::stripDangerousProtocols() can
// be used to return a plain-text string stripped of harmful protocols.
$url = 'javascript:http://www.example.com/?x=1&y=2';
$expected_plain = 'http://www.example.com/?x=1&y=2';
$expected_html = 'http://www.example.com/?x=1&amp;y=2';
$this->assertIdentical(check_url($url), $expected_html, 'check_url() filters a URL and encodes it for HTML.');
$this->assertIdentical(UrlHelper::filterBadProtocol($url), $expected_html, '\Drupal\Component\Utility\UrlHelper::filterBadProtocol() filters a URL and encodes it for HTML.');
$this->assertIdentical(UrlHelper::stripDangerousProtocols($url), $expected_plain, '\Drupal\Component\Utility\UrlHelper::stripDangerousProtocols() filters a URL and returns plain text.');
}
}

View file

@ -21,6 +21,7 @@ class CacheabilityMetadataConfigOverrideTest extends KernelTestBase {
'config',
'config_override_test',
'system',
'user'
];
/**

View file

@ -44,9 +44,9 @@ class ConfigDiffTest extends KernelTestBase {
// Verify that the diff reflects a change.
$diff = \Drupal::service('config.manager')->diff($active, $sync, $config_name);
$edits = $diff->getEdits();
$this->assertEqual($edits[0]->type, 'change', 'The first item in the diff is a change.');
$this->assertEqual($edits[0]->orig[0], $change_key . ': ' . $original_data[$change_key], format_string("The active value for key '%change_key' is '%original_data'.", array('%change_key' => $change_key, '%original_data' => $original_data[$change_key])));
$this->assertEqual($edits[0]->closing[0], $change_key . ': ' . $change_data, format_string("The sync value for key '%change_key' is '%change_data'.", array('%change_key' => $change_key, '%change_data' => $change_data)));
$this->assertYamlEdit($edits, $change_key, 'change',
[$change_key . ': ' . $original_data[$change_key]],
[$change_key . ': ' . $change_data]);
// Reset data back to original, and remove a key
$sync_data = $original_data;
@ -56,10 +56,11 @@ class ConfigDiffTest extends KernelTestBase {
// Verify that the diff reflects a removed key.
$diff = \Drupal::service('config.manager')->diff($active, $sync, $config_name);
$edits = $diff->getEdits();
$this->assertEqual($edits[0]->type, 'copy', 'The first item in the diff is a copy.');
$this->assertEqual($edits[1]->type, 'delete', 'The second item in the diff is a delete.');
$this->assertEqual($edits[1]->orig[0], $remove_key . ': ' . $original_data[$remove_key], format_string("The active value for key '%remove_key' is '%original_data'.", array('%remove_key' => $remove_key, '%original_data' => $original_data[$remove_key])));
$this->assertFalse($edits[1]->closing, format_string("The key '%remove_key' does not exist in sync.", array('%remove_key' => $remove_key)));
$this->assertYamlEdit($edits, $change_key, 'copy');
$this->assertYamlEdit($edits, $remove_key, 'delete',
[$remove_key . ': ' . $original_data[$remove_key]],
FALSE
);
// Reset data back to original and add a key
$sync_data = $original_data;
@ -69,10 +70,8 @@ class ConfigDiffTest extends KernelTestBase {
// Verify that the diff reflects an added key.
$diff = \Drupal::service('config.manager')->diff($active, $sync, $config_name);
$edits = $diff->getEdits();
$this->assertEqual($edits[0]->type, 'copy', 'The first item in the diff is a copy.');
$this->assertEqual($edits[1]->type, 'add', 'The second item in the diff is an add.');
$this->assertFalse($edits[1]->orig, format_string("The key '%add_key' does not exist in active.", array('%add_key' => $add_key)));
$this->assertEqual($edits[1]->closing[0], $add_key . ': ' . $add_data, format_string("The sync value for key '%add_key' is '%add_data'.", array('%add_key' => $add_key, '%add_data' => $add_data)));
$this->assertYamlEdit($edits, $change_key, 'copy');
$this->assertYamlEdit($edits, $add_key, 'add', FALSE, [$add_key . ': ' . $add_data]);
// Test diffing a renamed config entity.
$test_entity_id = $this->randomMachineName();
@ -97,10 +96,11 @@ class ConfigDiffTest extends KernelTestBase {
$diff = \Drupal::service('config.manager')->diff($active, $sync, 'config_test.dynamic.' . $new_test_entity_id, $config_name);
$edits = $diff->getEdits();
$this->assertEqual($edits[0]->type, 'copy', 'The first item in the diff is a copy.');
$this->assertEqual($edits[1]->type, 'change', 'The second item in the diff is a change.');
$this->assertEqual($edits[1]->orig, array('id: ' . $new_test_entity_id));
$this->assertEqual($edits[1]->closing, array('id: ' . $test_entity_id));
$this->assertYamlEdit($edits, 'uuid', 'copy');
$this->assertYamlEdit($edits, 'id', 'change',
['id: ' . $new_test_entity_id],
['id: ' . $test_entity_id]);
$this->assertYamlEdit($edits, 'label', 'copy');
$this->assertEqual($edits[2]->type, 'copy', 'The third item in the diff is a copy.');
$this->assertEqual(count($edits), 3, 'There are three items in the diff.');
}
@ -133,10 +133,56 @@ class ConfigDiffTest extends KernelTestBase {
// Test that the differences are detected when diffing the collection.
$diff = \Drupal::service('config.manager')->diff($active, $sync, $config_name, NULL, 'test');
$edits = $diff->getEdits();
$this->assertEqual($edits[0]->type, 'change', 'The second item in the diff is a copy.');
$this->assertEqual($edits[0]->orig, array('foo: bar'));
$this->assertEqual($edits[0]->closing, array('foo: baz'));
$this->assertEqual($edits[1]->type, 'copy', 'The second item in the diff is a copy.');
$this->assertYamlEdit($edits, 'foo', 'change', ['foo: bar'], ['foo: baz']);
}
/**
* Helper method to test that an edit is found in a diff'd YAML file.
*
* @param array $edits
* A list of edits.
* @param string $field
* The field key that is being asserted.
* @param string $type
* The type of edit that is being asserted.
* @param mixed $orig
* (optional) The original value of of the edit. If not supplied, assertion
* is skipped.
* @param mixed $closing
* (optional) The closing value of of the edit. If not supplied, assertion
* is skipped.
*/
protected function assertYamlEdit(array $edits, $field, $type, $orig = NULL, $closing = NULL) {
$match = FALSE;
foreach ($edits as $edit) {
// Choose which section to search for the field.
$haystack = $type == 'add' ? $edit->closing : $edit->orig;
// Look through each line and try and find the key.
if (is_array($haystack)) {
foreach ($haystack as $item) {
if (strpos($item, $field . ':') === 0) {
$match = TRUE;
// Assert that the edit is of the type specified.
$this->assertEqual($edit->type, $type, "The $field item in the diff is a $type");
// If an original value was given, assert that it matches.
if (isset($orig)) {
$this->assertIdentical($edit->orig, $orig, "The original value for key '$field' is correct.");
}
// If a closing value was given, assert that it matches.
if (isset($closing)) {
$this->assertIdentical($edit->closing, $closing, "The closing value for key '$field' is correct.");
}
// Break out of the search entirely.
break 2;
}
}
}
}
// If we didn't match anything, fail.
if (!$match) {
$this->fail("$field edit was not matched");
}
}
}

View file

@ -50,8 +50,10 @@ class ConfigEntityStaticCacheTest extends KernelTestBase {
* Tests that the static cache is working.
*/
public function testCacheHit() {
$entity_1 = entity_load($this->entityTypeId, $this->entityId);
$entity_2 = entity_load($this->entityTypeId, $this->entityId);
$storage = $this->container->get('entity_type.manager')
->getStorage($this->entityTypeId);
$entity_1 = $storage->load($this->entityId);
$entity_2 = $storage->load($this->entityId);
// config_entity_static_cache_test_config_test_load() sets _loadStamp to a
// random string. If they match, it means $entity_2 was retrieved from the
// static cache rather than going through a separate load sequence.
@ -62,19 +64,21 @@ class ConfigEntityStaticCacheTest extends KernelTestBase {
* Tests that the static cache is reset on entity save and delete.
*/
public function testReset() {
$entity = entity_load($this->entityTypeId, $this->entityId);
$storage = $this->container->get('entity_type.manager')
->getStorage($this->entityTypeId);
$entity = $storage->load($this->entityId);
// Ensure loading after a save retrieves the updated entity rather than an
// obsolete cached one.
$entity->label = 'New label';
$entity->save();
$entity = entity_load($this->entityTypeId, $this->entityId);
$entity = $storage->load($this->entityId);
$this->assertIdentical($entity->label, 'New label');
// Ensure loading after a delete retrieves NULL rather than an obsolete
// cached one.
$entity->delete();
$this->assertNull(entity_load($this->entityTypeId, $this->entityId));
$this->assertNull($storage->load($this->entityId));
}
/**

View file

@ -27,11 +27,10 @@ class ConfigEntityStorageTest extends KernelTestBase {
$entity_type = 'config_test';
$id = 'test_1';
// Load the original configuration entity.
$this->container->get('entity_type.manager')
->getStorage($entity_type)
->create(array('id' => $id))
->save();
$entity = entity_load($entity_type, $id);
$storage = $this->container->get('entity_type.manager')
->getStorage($entity_type);
$storage->create(['id' => $id])->save();
$entity = $storage->load($id);
$original_properties = $entity->toArray();

View file

@ -199,8 +199,8 @@ class ConfigInstallTest extends KernelTestBase {
}
catch (UnmetDependenciesException $e) {
$this->assertEqual($e->getExtension(), 'config_install_dependency_test');
$this->assertEqual($e->getConfigObjects(), ['config_test.dynamic.other_module_test_with_dependency']);
$this->assertEqual($e->getMessage(), 'Configuration objects (config_test.dynamic.other_module_test_with_dependency) provided by config_install_dependency_test have unmet dependencies');
$this->assertEqual($e->getConfigObjects(), ['config_other_module_config_test.weird_simple_config', 'config_test.dynamic.other_module_test_with_dependency']);
$this->assertEqual($e->getMessage(), 'Configuration objects (config_other_module_config_test.weird_simple_config, config_test.dynamic.other_module_test_with_dependency) provided by config_install_dependency_test have unmet dependencies');
}
$this->installModules(['config_other_module_config_test']);
$this->installModules(['config_install_dependency_test']);

View file

@ -2,8 +2,8 @@
namespace Drupal\KernelTests\Core\Config;
use Drupal\Core\Config\Schema\SchemaIncompleteException;
use Drupal\KernelTests\KernelTestBase;
use Drupal\Tests\Traits\Core\Config\SchemaConfigListenerTestTrait;
/**
* Tests the functionality of ConfigSchemaChecker in KernelTestBase tests.
@ -12,50 +12,22 @@ use Drupal\KernelTests\KernelTestBase;
*/
class SchemaConfigListenerTest extends KernelTestBase {
use SchemaConfigListenerTestTrait;
/**
* {@inheritdoc}
*/
public static $modules = array('config_test');
/**
* Tests \Drupal\Core\Config\Testing\ConfigSchemaChecker.
* {@inheritdoc}
*/
public function testConfigSchemaChecker() {
// Test a non-existing schema.
$message = 'Expected SchemaIncompleteException thrown';
try {
$this->config('config_schema_test.schemaless')->set('foo', 'bar')->save();
$this->fail($message);
}
catch (SchemaIncompleteException $e) {
$this->pass($message);
$this->assertEqual('No schema for config_schema_test.schemaless', $e->getMessage());
}
// Test a valid schema.
$message = 'Unexpected SchemaIncompleteException thrown';
$config = $this->config('config_test.types')->set('int', 10);
try {
$config->save();
$this->pass($message);
}
catch (SchemaIncompleteException $e) {
$this->fail($message);
}
// Test an invalid schema.
$message = 'Expected SchemaIncompleteException thrown';
$config = $this->config('config_test.types')
->set('foo', 'bar')
->set('array', 1);
try {
$config->save();
$this->fail($message);
}
catch (SchemaIncompleteException $e) {
$this->pass($message);
$this->assertEqual('Schema errors for config_test.types with the following errors: config_test.types:foo missing schema, config_test.types:array variable type is integer but applied schema class is Drupal\Core\Config\Schema\Sequence', $e->getMessage());
}
protected function setUp() {
parent::setUp();
// Install configuration provided by the module so that the order of the
// config keys is the same as
// \Drupal\FunctionalTests\Core\Config\SchemaConfigListenerTest.
$this->installConfig(['config_test']);
}
}

View file

@ -2,9 +2,9 @@
namespace Drupal\KernelTests\Core\Config\Storage;
use Drupal\Component\Serialization\Yaml;
use Drupal\Core\Config\FileStorage;
use Drupal\Core\Config\UnsupportedDataTypeConfigException;
use Drupal\Core\Serialization\Yaml;
use Drupal\Core\StreamWrapper\PublicStream;
/**

View file

@ -36,20 +36,20 @@ abstract class DatabaseTestBase extends KernelTestBase {
*/
function ensureSampleDataNull() {
db_insert('test_null')
->fields(array('name', 'age'))
->values(array(
->fields(array('name', 'age'))
->values(array(
'name' => 'Kermit',
'age' => 25,
))
->values(array(
->values(array(
'name' => 'Fozzie',
'age' => NULL,
))
->values(array(
->values(array(
'name' => 'Gonzo',
'age' => 27,
))
->execute();
->execute();
}
/**

View file

@ -126,4 +126,13 @@ class LoggingTest extends DatabaseTestBase {
$this->assertEqual(count($queries2), 1, 'Correct number of queries recorded for second connection.');
}
/**
* Tests that getLog with a wrong key return an empty array.
*/
function testGetLoggingWrongKey() {
$result = Database::getLog('wrong');
$this->assertEqual($result, [], 'The function getLog with a wrong key returns an empty array.');
}
}

View file

@ -505,6 +505,7 @@ class SchemaTest extends KernelTestBase {
array('not null' => FALSE, 'default' => 7),
array('not null' => TRUE, 'initial' => 1),
array('not null' => TRUE, 'initial' => 1, 'default' => 7),
array('not null' => TRUE, 'initial_from_field' => 'serial_column'),
);
foreach ($variations as $variation) {
@ -532,6 +533,7 @@ class SchemaTest extends KernelTestBase {
array('not null' => FALSE, 'default' => 7),
array('not null' => TRUE, 'initial' => 1),
array('not null' => TRUE, 'initial' => 1, 'default' => 7),
array('not null' => TRUE, 'initial_from_field' => 'serial_column'),
);
foreach ($variations as $variation) {
@ -620,6 +622,19 @@ class SchemaTest extends KernelTestBase {
$this->assertEqual($count, 0, 'Initial values filled out.');
}
// Check that the initial value from another field has been registered.
if (isset($field_spec['initial_from_field'])) {
// There should be no row with a value different than
// $field_spec['initial_from_field'].
$count = db_select($table_name)
->fields($table_name, array('serial_column'))
->where($table_name . '.' . $field_spec['initial_from_field'] . ' <> ' . $table_name . '.' . $field_name)
->countQuery()
->execute()
->fetchField();
$this->assertEqual($count, 0, 'Initial values from another field filled out.');
}
// Check that the default value has been registered.
if (isset($field_spec['default'])) {
// Try inserting a row, and check the resulting value of the new column.

View file

@ -0,0 +1,47 @@
<?php
namespace Drupal\KernelTests\Core\DrupalKernel;
use Drupal\Core\Site\Settings;
use Drupal\KernelTests\KernelTestBase;
/**
* Tests site-specific service overrides.
*
* @group DrupalKernel
*/
class DrupalKernelSiteTest extends KernelTestBase {
/**
* Tests services.yml in site directory.
*/
public function testServicesYml() {
$container_yamls = Settings::get('container_yamls');
$container_yamls[] = $this->siteDirectory . '/services.yml';
$this->setSetting('container_yamls', $container_yamls);
$this->assertFalse($this->container->has('site.service.yml'));
// A service provider class always has precedence over services.yml files.
// KernelTestBase::buildContainer() swaps out many services with in-memory
// implementations already, so those cannot be tested.
$this->assertIdentical(get_class($this->container->get('cache.backend.database')), 'Drupal\Core\Cache\DatabaseBackendFactory');
$class = __CLASS__;
$doc = <<<EOD
services:
# Add a new service.
site.service.yml:
class: $class
# Swap out a core service.
cache.backend.database:
class: Drupal\Core\Cache\MemoryBackendFactory
EOD;
file_put_contents($this->siteDirectory . '/services.yml', $doc);
// Rebuild the container.
$this->container->get('kernel')->rebuildContainer();
$this->assertTrue($this->container->has('site.service.yml'));
$this->assertIdentical(get_class($this->container->get('cache.backend.database')), 'Drupal\Core\Cache\MemoryBackendFactory');
}
}

View file

@ -133,6 +133,11 @@ class DrupalKernelTest extends KernelTestBase {
'pathname' => drupal_get_filename('module', 'service_provider_test'),
'filename' => NULL,
));
// Check that the container itself is not among the persist IDs because it
// does not make sense to persist the container itself.
$persist_ids = $container->getParameter('persist_ids');
$this->assertIdentical(FALSE, array_search('service_container', $persist_ids));
}
/**

View file

@ -0,0 +1,58 @@
<?php
namespace Drupal\KernelTests\Core\DrupalKernel;
use Drupal\KernelTests\KernelTestBase;
use Symfony\Component\HttpFoundation\Response;
/**
* Tests that services are correctly destructed.
*
* @group DrupalKernel
*/
class ServiceDestructionTest extends KernelTestBase {
/**
* Verifies that services are destructed when used.
*/
public function testDestructionUsed() {
// Enable the test module to add it to the container.
$this->enableModules(array('service_provider_test'));
$request = $this->container->get('request_stack')->getCurrentRequest();
$kernel = $this->container->get('kernel');
$kernel->preHandle($request);
// The service has not been destructed yet.
$this->assertNull(\Drupal::state()->get('service_provider_test.destructed'));
// Call the class and then terminate the kernel
$this->container->get('service_provider_test_class');
$response = new Response();
$kernel->terminate($request, $response);
$this->assertTrue(\Drupal::state()->get('service_provider_test.destructed'));
}
/**
* Verifies that services are not unnecessarily destructed when not used.
*/
public function testDestructionUnused() {
// Enable the test module to add it to the container.
$this->enableModules(array('service_provider_test'));
$request = $this->container->get('request_stack')->getCurrentRequest();
$kernel = $this->container->get('kernel');
$kernel->preHandle($request);
// The service has not been destructed yet.
$this->assertNull(\Drupal::state()->get('service_provider_test.destructed'));
// Terminate the kernel. The test class has not been called, so it should not
// be destructed.
$response = new Response();
$kernel->terminate($request, $response);
$this->assertNull(\Drupal::state()->get('service_provider_test.destructed'));
}
}

View file

@ -0,0 +1,205 @@
<?php
namespace Drupal\KernelTests\Core\Element;
use Drupal\Core\Form\FormInterface;
use Drupal\Core\Form\FormState;
use Drupal\Core\Form\FormStateInterface;
use Drupal\Core\Render\Element\PathElement;
use Drupal\Core\Url;
use Drupal\KernelTests\KernelTestBase;
use Drupal\user\Entity\Role;
use Drupal\user\Entity\User;
/**
* Tests PathElement validation and conversion functionality.
*
* @group Form
*/
class PathElementFormTest extends KernelTestBase implements FormInterface {
/**
* User for testing.
*
* @var \Drupal\user\UserInterface
*/
protected $testUser;
/**
* Modules to enable.
*
* @var array
*/
public static $modules = array('system', 'user');
/**
* Sets up the test.
*/
protected function setUp() {
parent::setUp();
$this->installSchema('system', ['sequences', 'key_value_expire']);
$this->installEntitySchema('user');
\Drupal::service('router.builder')->rebuild();
/** @var \Drupal\user\RoleInterface $role */
$role = Role::create(array(
'id' => 'admin',
'label' => 'admin',
));
$role->grantPermission('link to any page');
$role->save();
$this->testUser = User::create(array(
'name' => 'foobar',
'mail' => 'foobar@example.com',
));
$this->testUser->addRole($role->id());
$this->testUser->save();
\Drupal::service('current_user')->setAccount($this->testUser);
}
/**
* {@inheritdoc}
*/
public function getFormId() {
return 'test_path_element';
}
/**
* {@inheritdoc}
*/
public function buildForm(array $form, FormStateInterface $form_state) {
// A required validated path.
$form['required_validate'] = array(
'#type' => 'path',
'#required' => TRUE,
'#title' => 'required_validate',
'#convert_path' => PathElement::CONVERT_NONE,
);
// A non validated required path.
$form['required_non_validate'] = array(
'#type' => 'path',
'#required' => TRUE,
'#title' => 'required_non_validate',
'#convert_path' => PathElement::CONVERT_NONE,
'#validate_path' => FALSE,
);
// A non required validated path.
$form['optional_validate'] = array(
'#type' => 'path',
'#required' => FALSE,
'#title' => 'optional_validate',
'#convert_path' => PathElement::CONVERT_NONE,
);
// A non required converted path.
$form['optional_validate'] = array(
'#type' => 'path',
'#required' => FALSE,
'#title' => 'optional_validate',
'#convert_path' => PathElement::CONVERT_ROUTE,
);
// A converted required validated path.
$form['required_validate_route'] = array(
'#type' => 'path',
'#required' => TRUE,
'#title' => 'required_validate_route',
);
// A converted required validated path.
$form['required_validate_url'] = array(
'#type' => 'path',
'#required' => TRUE,
'#title' => 'required_validate_url',
'#convert_path' => PathElement::CONVERT_URL,
);
$form['submit'] = array(
'#type' => 'submit',
'#value' => t('Submit'),
);
return $form;
}
/**
* {@inheritdoc}
*/
public function submitForm(array &$form, FormStateInterface $form_state) {}
/**
* Form validation handler.
*
* @param array $form
* An associative array containing the structure of the form.
* @param \Drupal\Core\Form\FormStateInterface $form_state
* The current state of the form.
*/
public function validateForm(array &$form, FormStateInterface $form_state) {}
/**
* Tests that default handlers are added even if custom are specified.
*/
public function testPathElement() {
$form_state = (new FormState())
->setValues([
'required_validate' => 'user/' . $this->testUser->id(),
'required_non_validate' => 'magic-ponies',
'required_validate_route' => 'user/' . $this->testUser->id(),
'required_validate_url' => 'user/' . $this->testUser->id(),
]);
$form_builder = $this->container->get('form_builder');
$form_builder->submitForm($this, $form_state);
// Valid form state.
$this->assertEqual(count($form_state->getErrors()), 0);
$this->assertEqual($form_state->getValue('required_validate_route'), array(
'route_name' => 'entity.user.canonical',
'route_parameters' => array(
'user' => $this->testUser->id(),
),
));
/** @var \Drupal\Core\Url $url */
$url = $form_state->getValue('required_validate_url');
$this->assertTrue($url instanceof Url);
$this->assertEqual($url->getRouteName(), 'entity.user.canonical');
$this->assertEqual($url->getRouteParameters(), array(
'user' => $this->testUser->id(),
));
// Test #required.
$form_state = (new FormState())
->setValues([
'required_non_validate' => 'magic-ponies',
'required_validate_route' => 'user/' . $this->testUser->id(),
'required_validate_url' => 'user/' . $this->testUser->id(),
]);
$form_builder->submitForm($this, $form_state);
$errors = $form_state->getErrors();
// Should be missing 'required_validate' field.
$this->assertEqual(count($errors), 1);
$this->assertEqual($errors, array('required_validate' => t('@name field is required.', array('@name' => 'required_validate'))));
// Test invalid parameters.
$form_state = (new FormState())
->setValues([
'required_validate' => 'user/74',
'required_non_validate' => 'magic-ponies',
'required_validate_route' => 'user/74',
'required_validate_url' => 'user/74',
]);
$form_builder = $this->container->get('form_builder');
$form_builder->submitForm($this, $form_state);
// Valid form state.
$errors = $form_state->getErrors();
$this->assertEqual(count($errors), 3);
$this->assertEqual($errors, array(
'required_validate' => t('This path does not exist or you do not have permission to link to %path.', array('%path' => 'user/74')),
'required_validate_route' => t('This path does not exist or you do not have permission to link to %path.', array('%path' => 'user/74')),
'required_validate_url' => t('This path does not exist or you do not have permission to link to %path.', array('%path' => 'user/74')),
));
}
}

View file

@ -0,0 +1,75 @@
<?php
namespace Drupal\KernelTests\Core\Entity;
use Drupal\language\Entity\ConfigurableLanguage;
/**
* Tests correct field method invocation order.
*
* @group Entity
*/
class ContentEntityFieldMethodInvocationOrderTest extends EntityKernelTestBase {
/**
* Modules to enable.
*
* @var array
*/
public static $modules = ['language', 'system', 'entity_test'];
/**
* The EntityTest entity type storage.
*
* @var \Drupal\Core\Entity\ContentEntityStorageInterface
*/
protected $entityTestFieldMethodsStorage;
/**
* {@inheritdoc}
*/
protected function setUp() {
parent::setUp();
// Enable an additional language.
ConfigurableLanguage::createFromLangcode('de')->save();
ConfigurableLanguage::createFromLangcode('fr')->save();
$this->installEntitySchema('entity_test_field_methods');
$this->entityTestFieldMethodsStorage = $this->entityManager->getStorage('entity_test_field_methods');
}
/**
* Tests correct field method invocation order.
*/
public function testFieldMethodInvocationOrder() {
// Create a test entity.
$entity = $this->entityTestFieldMethodsStorage->create([
'name' => $this->randomString(),
'langcode' => 'de',
]);
$entity->save();
$entity->addTranslation('fr')
->save();
// Reset the current value of the test field.
foreach (['de', 'fr'] as $langcode) {
$entity->getTranslation($langcode)->test_invocation_order->value = 0;
}
$entity->getTranslation('de')
->save();
$this->assertTrue($entity->getTranslation('fr')->test_invocation_order->value > $entity->getTranslation('de')->test_invocation_order->value, 'The field presave method has been invoked in the correct entity translation order.');
// Reset the current value of the test field.
foreach (['de', 'fr'] as $langcode) {
$entity->getTranslation($langcode)->test_invocation_order->value = 0;
}
$entity->getTranslation('fr')
->save();
$this->assertTrue($entity->getTranslation('de')->test_invocation_order->value > $entity->getTranslation('fr')->test_invocation_order->value, 'The field presave method has been invoked in the correct entity translation order.');
}
}

View file

@ -60,32 +60,36 @@ class EntityApiTest extends EntityKernelTestBase {
->create(array('name' => 'test', 'user_id' => NULL));
$entity->save();
$entities = array_values(entity_load_multiple_by_properties($entity_type, array('name' => 'test')));
/** @var \Drupal\Core\Entity\EntityStorageInterface $storage */
$storage = $this->container->get('entity_type.manager')
->getStorage($entity_type);
$entities = array_values($storage->loadByProperties(['name' => 'test']));
$this->assertEqual($entities[0]->name->value, 'test', format_string('%entity_type: Created and loaded entity', array('%entity_type' => $entity_type)));
$this->assertEqual($entities[1]->name->value, 'test', format_string('%entity_type: Created and loaded entity', array('%entity_type' => $entity_type)));
// Test loading a single entity.
$loaded_entity = entity_load($entity_type, $entity->id());
$loaded_entity = $storage->load($entity->id());
$this->assertEqual($loaded_entity->id(), $entity->id(), format_string('%entity_type: Loaded a single entity by id.', array('%entity_type' => $entity_type)));
// Test deleting an entity.
$entities = array_values(entity_load_multiple_by_properties($entity_type, array('name' => 'test2')));
$entities = array_values($storage->loadByProperties(['name' => 'test2']));
$entities[0]->delete();
$entities = array_values(entity_load_multiple_by_properties($entity_type, array('name' => 'test2')));
$entities = array_values($storage->loadByProperties(['name' => 'test2']));
$this->assertEqual($entities, array(), format_string('%entity_type: Entity deleted.', array('%entity_type' => $entity_type)));
// Test updating an entity.
$entities = array_values(entity_load_multiple_by_properties($entity_type, array('name' => 'test')));
$entities = array_values($storage->loadByProperties(['name' => 'test']));
$entities[0]->name->value = 'test3';
$entities[0]->save();
$entity = entity_load($entity_type, $entities[0]->id());
$entity = $storage->load($entities[0]->id());
$this->assertEqual($entity->name->value, 'test3', format_string('%entity_type: Entity updated.', array('%entity_type' => $entity_type)));
// Try deleting multiple test entities by deleting all.
$ids = array_keys(entity_load_multiple($entity_type));
$ids = array_keys($storage->loadMultiple());
entity_delete_multiple($entity_type, $ids);
$all = entity_load_multiple($entity_type);
$all = $storage->loadMultiple();
$this->assertTrue(empty($all), format_string('%entity_type: Deleted all entities.', array('%entity_type' => $entity_type)));
// Verify that all data got deleted.
@ -110,7 +114,7 @@ class EntityApiTest extends EntityKernelTestBase {
$controller->delete($entities);
// Verify that entities got deleted.
$all = entity_load_multiple($entity_type);
$all = $storage->loadMultiple();
$this->assertTrue(empty($all), format_string('%entity_type: Deleted all entities.', array('%entity_type' => $entity_type)));
// Verify that all data got deleted from the tables.

View file

@ -377,7 +377,9 @@ class EntityFieldTest extends EntityKernelTestBase {
$entity->save();
$this->assertTrue((bool) $entity->id(), format_string('%entity_type: Entity has received an id.', array('%entity_type' => $entity_type)));
$entity = entity_load($entity_type, $entity->id());
$entity = $this->container->get('entity_type.manager')
->getStorage($entity_type)
->load($entity->id());
$this->assertTrue((bool) $entity->id(), format_string('%entity_type: Entity loaded.', array('%entity_type' => $entity_type)));
// Access the name field.
@ -744,7 +746,9 @@ class EntityFieldTest extends EntityKernelTestBase {
// Save and load entity and make sure it still works.
$entity->save();
$entity = entity_load($entity_type, $entity->id());
$entity = $this->container->get('entity_type.manager')
->getStorage($entity_type)
->load($entity->id());
$this->assertEqual($entity->field_test_text->processed, $target, format_string('%entity_type: Text is processed with the default filter.', array('%entity_type' => $entity_type)));
}

View file

@ -0,0 +1,47 @@
<?php
namespace Drupal\KernelTests\Core\Entity;
use Drupal\entity_test\Entity\EntityTestMulRev;
use Drupal\language\Entity\ConfigurableLanguage;
/**
* Tests translating a non-revisionable field.
*
* @group Entity
*/
class EntityNonRevisionableTranslatableFieldTest extends EntityKernelTestBase {
/**
* {@inheritdoc}
*/
public static $modules = array('entity_test', 'language', 'content_translation');
protected function setUp() {
parent::setUp();
$this->installEntitySchema('entity_test_mulrev');
$this->installEntitySchema('configurable_language');
ConfigurableLanguage::createFromLangcode('es')->save();
}
/**
* Tests translating a non-revisionable field.
*/
function testTranslatingNonRevisionableField() {
/** @var \Drupal\Core\Entity\ContentEntityBase $entity */
$entity = EntityTestMulRev::create();
$entity->set('non_rev_field', 'Hello');
$entity->save();
$translation = $entity->addTranslation('es');
$translation->set('non_rev_field', 'Hola');
$translation->save();
$reloaded = EntityTestMulRev::load($entity->id());
$this->assertEquals('Hello', $reloaded->getTranslation('en')->get('non_rev_field')->value);
$this->assertEquals('Hola', $reloaded->getTranslation('es')->get('non_rev_field')->value);
}
}

View file

@ -247,7 +247,7 @@ class EntityQueryTest extends EntityKernelTestBase {
->condition("$greetings.value", 'merhaba')
->sort('id')
->execute();
$entities = entity_load_multiple('entity_test_mulrev', $ids);
$entities = EntityTestMulRev::loadMultiple($ids);
$first_entity = reset($entities);
$old_name = $first_entity->name->value;
foreach ($entities as $entity) {
@ -481,7 +481,7 @@ class EntityQueryTest extends EntityKernelTestBase {
->exists("$field_name.color")
->count()
->execute();
$this->assertFalse($count);
$this->assertFalse($count);
}
/**

View file

@ -164,11 +164,11 @@ class EntityTranslationTest extends EntityLanguageTestBase {
// Create a language neutral entity and check that properties are stored
// as language neutral.
$entity = $this->container->get('entity_type.manager')
->getStorage($entity_type)
->create(array('name' => $name, 'user_id' => $uid, $langcode_key => LanguageInterface::LANGCODE_NOT_SPECIFIED));
$storage = $this->container->get('entity_type.manager')
->getStorage($entity_type);
$entity = $storage->create(['name' => $name, 'user_id' => $uid, $langcode_key => LanguageInterface::LANGCODE_NOT_SPECIFIED]);
$entity->save();
$entity = entity_load($entity_type, $entity->id());
$entity = $storage->load($entity->id());
$default_langcode = $entity->language()->getId();
$this->assertEqual($default_langcode, LanguageInterface::LANGCODE_NOT_SPECIFIED, format_string('%entity_type: Entity created as language neutral.', array('%entity_type' => $entity_type)));
$field = $entity->getTranslation(LanguageInterface::LANGCODE_DEFAULT)->get('name');
@ -192,7 +192,7 @@ class EntityTranslationTest extends EntityLanguageTestBase {
->getStorage($entity_type)
->create(array('name' => $name, 'user_id' => $uid, $langcode_key => $langcode));
$entity->save();
$entity = entity_load($entity_type, $entity->id());
$entity = $storage->load($entity->id());
$default_langcode = $entity->language()->getId();
$this->assertEqual($default_langcode, $langcode, format_string('%entity_type: Entity created as language specific.', array('%entity_type' => $entity_type)));
$field = $entity->getTranslation($langcode)->get('name');
@ -224,7 +224,7 @@ class EntityTranslationTest extends EntityLanguageTestBase {
$entity->save();
// Check that property translation were correctly stored.
$entity = entity_load($entity_type, $entity->id());
$entity = $storage->load($entity->id());
foreach ($this->langcodes as $langcode) {
$args = array(
'%entity_type' => $entity_type,
@ -242,33 +242,34 @@ class EntityTranslationTest extends EntityLanguageTestBase {
// Create an additional entity with only the uid set. The uid for the
// original language is the same of one used for a translation.
$langcode = $this->langcodes[1];
$this->container->get('entity_type.manager')
->getStorage($entity_type)
->create(array(
/** @var \Drupal\Core\Entity\EntityStorageInterface $storage */
$storage = $this->container->get('entity_type.manager')
->getStorage($entity_type);
$storage->create([
'user_id' => $properties[$langcode]['user_id'],
'name' => 'some name',
$langcode_key => LanguageInterface::LANGCODE_NOT_SPECIFIED,
))
])
->save();
$entities = entity_load_multiple($entity_type);
$entities = $storage->loadMultiple();
$this->assertEqual(count($entities), 3, format_string('%entity_type: Three entities were created.', array('%entity_type' => $entity_type)));
$entities = entity_load_multiple($entity_type, array($translated_id));
$entities = $storage->loadMultiple([$translated_id]);
$this->assertEqual(count($entities), 1, format_string('%entity_type: One entity correctly loaded by id.', array('%entity_type' => $entity_type)));
$entities = entity_load_multiple_by_properties($entity_type, array('name' => $name));
$entities = $storage->loadByProperties(['name' => $name]);
$this->assertEqual(count($entities), 2, format_string('%entity_type: Two entities correctly loaded by name.', array('%entity_type' => $entity_type)));
// @todo The default language condition should go away in favor of an
// explicit parameter.
$entities = entity_load_multiple_by_properties($entity_type, array('name' => $properties[$langcode]['name'][0], $default_langcode_key => 0));
$entities = $storage->loadByProperties(['name' => $properties[$langcode]['name'][0], $default_langcode_key => 0]);
$this->assertEqual(count($entities), 1, format_string('%entity_type: One entity correctly loaded by name translation.', array('%entity_type' => $entity_type)));
$entities = entity_load_multiple_by_properties($entity_type, array($langcode_key => $default_langcode, 'name' => $name));
$entities = $storage->loadByProperties([$langcode_key => $default_langcode, 'name' => $name]);
$this->assertEqual(count($entities), 1, format_string('%entity_type: One entity correctly loaded by name and language.', array('%entity_type' => $entity_type)));
$entities = entity_load_multiple_by_properties($entity_type, array($langcode_key => $langcode, 'name' => $properties[$langcode]['name'][0]));
$entities = $storage->loadByProperties([$langcode_key => $langcode, 'name' => $properties[$langcode]['name'][0]]);
$this->assertEqual(count($entities), 0, format_string('%entity_type: No entity loaded by name translation specifying the translation language.', array('%entity_type' => $entity_type)));
$entities = entity_load_multiple_by_properties($entity_type, array($langcode_key => $langcode, 'name' => $properties[$langcode]['name'][0], $default_langcode_key => 0));
$entities = $storage->loadByProperties([$langcode_key => $langcode, 'name' => $properties[$langcode]['name'][0], $default_langcode_key => 0]);
$this->assertEqual(count($entities), 1, format_string('%entity_type: One entity loaded by name translation and language specifying to look for translations.', array('%entity_type' => $entity_type)));
$entities = entity_load_multiple_by_properties($entity_type, array('user_id' => $properties[$langcode]['user_id'][0], $default_langcode_key => NULL));
$entities = $storage->loadByProperties(['user_id' => $properties[$langcode]['user_id'][0], $default_langcode_key => NULL]);
$this->assertEqual(count($entities), 2, format_string('%entity_type: Two entities loaded by uid without caring about property translatability.', array('%entity_type' => $entity_type)));
// Test property conditions and orders with multiple languages in the same
@ -284,7 +285,8 @@ class EntityTranslationTest extends EntityLanguageTestBase {
$this->assertEqual(count($result), 1, format_string('%entity_type: One entity loaded by name and uid using different language meta conditions.', array('%entity_type' => $entity_type)));
// Test mixed property and field conditions.
$entity = entity_load($entity_type, reset($result), TRUE);
$storage->resetCache($result);
$entity = $storage->load(reset($result));
$field_value = $this->randomString();
$entity->getTranslation($langcode)->set($this->fieldName, array(array('value' => $field_value)));
$entity->save();

View file

@ -65,7 +65,11 @@ class EntityUUIDTest extends EntityKernelTestBase {
$this->assertIdentical($entity->uuid(), $uuid);
// Verify that the UUID is retained upon loading.
$entity_loaded = entity_load($entity_type, $entity->id(), TRUE);
/** @var \Drupal\Core\Entity\EntityStorageInterface $storage */
$storage = $this->container->get('entity_type.manager')
->getStorage($entity_type);
$storage->resetCache([$entity->id()]);
$entity_loaded = $storage->load($entity->id());
$this->assertIdentical($entity_loaded->uuid(), $uuid);
// Verify that \Drupal::entityManager()->loadEntityByUuid() loads the same entity.

View file

@ -0,0 +1,43 @@
<?php
namespace Drupal\KernelTests\Core\EventSubscriber;
use Drupal\Core\Database\Database;
use Drupal\Core\EventSubscriber\ReplicaDatabaseIgnoreSubscriber;
use Drupal\Core\DrupalKernel;
use Drupal\KernelTests\KernelTestBase;
use Symfony\Component\HttpFoundation\Request;
use Symfony\Component\HttpKernel\HttpKernelInterface;
use Symfony\Component\HttpKernel\Event\GetResponseEvent;
/**
* Tests that ReplicaDatabaseIgnoreSubscriber functions correctly.
*
* @group system
*/
class IgnoreReplicaSubscriberTest extends KernelTestBase {
/**
* Tests \Drupal\Core\EventSubscriber\ReplicaDatabaseIgnoreSubscriber::checkReplicaServer().
*/
function testSystemInitIgnoresSecondaries() {
// Clone the master credentials to a replica connection.
// Note this will result in two independent connection objects that happen
// to point to the same place.
$connection_info = Database::getConnectionInfo('default');
Database::addConnectionInfo('default', 'replica', $connection_info['default']);
db_ignore_replica();
$class_loader = require \Drupal::root() . '/autoload.php';
$kernel = new DrupalKernel('testing', $class_loader, FALSE);
$event = new GetResponseEvent($kernel, Request::create('http://example.com'), HttpKernelInterface::MASTER_REQUEST);
$subscriber = new ReplicaDatabaseIgnoreSubscriber();
$subscriber->checkReplicaServer($event);
$db1 = Database::getConnection('default', 'default');
$db2 = Database::getConnection('replica', 'default');
$this->assertIdentical($db1, $db2, 'System Init ignores secondaries when requested.');
}
}

View file

@ -0,0 +1,92 @@
<?php
namespace Drupal\KernelTests\Core\Extension;
use Drupal\KernelTests\KernelTestBase;
/**
* Tests hook_module_implements_alter().
*
* @group Module
*/
class ModuleImplementsAlterTest extends KernelTestBase {
/**
* {@inheritdoc}
*/
public static $modules = ['system'];
/**
* Tests hook_module_implements_alter() adding an implementation.
*
* @see \Drupal\Core\Extension\ModuleHandler::buildImplementationInfo()
* @see module_test_module_implements_alter()
*/
function testModuleImplementsAlter() {
// Get an instance of the module handler, to observe how it is going to be
// replaced.
$module_handler = \Drupal::moduleHandler();
$this->assertTrue($module_handler === \Drupal::moduleHandler(),
'Module handler instance is still the same.');
// Install the module_test module.
\Drupal::service('module_installer')->install(array('module_test'));
// Assert that the \Drupal::moduleHandler() instance has been replaced.
$this->assertFalse($module_handler === \Drupal::moduleHandler(),
'The \Drupal::moduleHandler() instance has been replaced during \Drupal::moduleHandler()->install().');
// Assert that module_test.module is now included.
$this->assertTrue(function_exists('module_test_modules_installed'),
'The file module_test.module was successfully included.');
$this->assertTrue(array_key_exists('module_test', \Drupal::moduleHandler()->getModuleList()),
'module_test is in the module list.');
$this->assertTrue(in_array('module_test', \Drupal::moduleHandler()->getImplementations('modules_installed')),
'module_test implements hook_modules_installed().');
$this->assertTrue(in_array('module_test', \Drupal::moduleHandler()->getImplementations('module_implements_alter')),
'module_test implements hook_module_implements_alter().');
// Assert that module_test.implementations.inc is not included yet.
$this->assertFalse(function_exists('module_test_altered_test_hook'),
'The file module_test.implementations.inc is not included yet.');
// Trigger hook discovery for hook_altered_test_hook().
// Assert that module_test_module_implements_alter(*, 'altered_test_hook')
// has added an implementation.
$this->assertTrue(in_array('module_test', \Drupal::moduleHandler()->getImplementations('altered_test_hook')),
'module_test implements hook_altered_test_hook().');
// Assert that module_test.implementations.inc was included as part of the process.
$this->assertTrue(function_exists('module_test_altered_test_hook'),
'The file module_test.implementations.inc was included.');
}
/**
* Tests what happens if hook_module_implements_alter() adds a non-existing
* function to the implementations.
*
* @see \Drupal\Core\Extension\ModuleHandler::buildImplementationInfo()
* @see module_test_module_implements_alter()
*/
function testModuleImplementsAlterNonExistingImplementation() {
// Install the module_test module.
\Drupal::service('module_installer')->install(array('module_test'));
try {
// Trigger hook discovery.
\Drupal::moduleHandler()->getImplementations('unimplemented_test_hook');
$this->fail('An exception should be thrown for the non-existing implementation.');
}
catch (\RuntimeException $e) {
$this->pass('An exception should be thrown for the non-existing implementation.');
$this->assertEqual($e->getMessage(), 'An invalid implementation module_test_unimplemented_test_hook was added by hook_module_implements_alter()');
}
}
}

View file

@ -0,0 +1,164 @@
<?php
namespace Drupal\KernelTests\Core\File;
use Drupal\Component\PhpStorage\FileStorage;
/**
* Tests operations dealing with directories.
*
* @group File
*/
class DirectoryTest extends FileTestBase {
/**
* Test local directory handling functions.
*/
function testFileCheckLocalDirectoryHandling() {
$site_path = $this->container->get('site.path');
$directory = $site_path . '/files';
// Check a new recursively created local directory for correct file system
// permissions.
$parent = $this->randomMachineName();
$child = $this->randomMachineName();
// Files directory already exists.
$this->assertTrue(is_dir($directory), t('Files directory already exists.'), 'File');
// Make files directory writable only.
$old_mode = fileperms($directory);
// Create the directories.
$parent_path = $directory . DIRECTORY_SEPARATOR . $parent;
$child_path = $parent_path . DIRECTORY_SEPARATOR . $child;
$this->assertTrue(drupal_mkdir($child_path, 0775, TRUE), t('No error reported when creating new local directories.'), 'File');
// Ensure new directories also exist.
$this->assertTrue(is_dir($parent_path), t('New parent directory actually exists.'), 'File');
$this->assertTrue(is_dir($child_path), t('New child directory actually exists.'), 'File');
// Check that new directory permissions were set properly.
$this->assertDirectoryPermissions($parent_path, 0775);
$this->assertDirectoryPermissions($child_path, 0775);
// Check that existing directory permissions were not modified.
$this->assertDirectoryPermissions($directory, $old_mode);
// Check creating a directory using an absolute path.
$absolute_path = drupal_realpath($directory) . DIRECTORY_SEPARATOR . $this->randomMachineName() . DIRECTORY_SEPARATOR . $this->randomMachineName();
$this->assertTrue(drupal_mkdir($absolute_path, 0775, TRUE), 'No error reported when creating new absolute directories.', 'File');
$this->assertDirectoryPermissions($absolute_path, 0775);
}
/**
* Test directory handling functions.
*/
function testFileCheckDirectoryHandling() {
// A directory to operate on.
$directory = file_default_scheme() . '://' . $this->randomMachineName() . '/' . $this->randomMachineName();
$this->assertFalse(is_dir($directory), 'Directory does not exist prior to testing.');
// Non-existent directory.
$this->assertFalse(file_prepare_directory($directory, 0), 'Error reported for non-existing directory.', 'File');
// Make a directory.
$this->assertTrue(file_prepare_directory($directory, FILE_CREATE_DIRECTORY), 'No error reported when creating a new directory.', 'File');
// Make sure directory actually exists.
$this->assertTrue(is_dir($directory), 'Directory actually exists.', 'File');
if (substr(PHP_OS, 0, 3) != 'WIN') {
// PHP on Windows doesn't support any kind of useful read-only mode for
// directories. When executing a chmod() on a directory, PHP only sets the
// read-only flag, which doesn't prevent files to actually be written
// in the directory on any recent version of Windows.
// Make directory read only.
@drupal_chmod($directory, 0444);
$this->assertFalse(file_prepare_directory($directory, 0), 'Error reported for a non-writeable directory.', 'File');
// Test directory permission modification.
$this->setSetting('file_chmod_directory', 0777);
$this->assertTrue(file_prepare_directory($directory, FILE_MODIFY_PERMISSIONS), 'No error reported when making directory writeable.', 'File');
}
// Test that the directory has the correct permissions.
$this->assertDirectoryPermissions($directory, 0777, 'file_chmod_directory setting is respected.');
// Remove .htaccess file to then test that it gets re-created.
@drupal_unlink(file_default_scheme() . '://.htaccess');
$this->assertFalse(is_file(file_default_scheme() . '://.htaccess'), 'Successfully removed the .htaccess file in the files directory.', 'File');
file_ensure_htaccess();
$this->assertTrue(is_file(file_default_scheme() . '://.htaccess'), 'Successfully re-created the .htaccess file in the files directory.', 'File');
// Verify contents of .htaccess file.
$file = file_get_contents(file_default_scheme() . '://.htaccess');
$this->assertEqual($file, FileStorage::htaccessLines(FALSE), 'The .htaccess file contains the proper content.', 'File');
}
/**
* This will take a directory and path, and find a valid filepath that is not
* taken by another file.
*/
function testFileCreateNewFilepath() {
// First we test against an imaginary file that does not exist in a
// directory.
$basename = 'xyz.txt';
$directory = 'core/misc';
$original = $directory . '/' . $basename;
$path = file_create_filename($basename, $directory);
$this->assertEqual($path, $original, format_string('New filepath %new equals %original.', array('%new' => $path, '%original' => $original)), 'File');
// Then we test against a file that already exists within that directory.
$basename = 'druplicon.png';
$original = $directory . '/' . $basename;
$expected = $directory . '/druplicon_0.png';
$path = file_create_filename($basename, $directory);
$this->assertEqual($path, $expected, format_string('Creating a new filepath from %original equals %new (expected %expected).', array('%new' => $path, '%original' => $original, '%expected' => $expected)), 'File');
// @TODO: Finally we copy a file into a directory several times, to ensure a properly iterating filename suffix.
}
/**
* This will test the filepath for a destination based on passed flags and
* whether or not the file exists.
*
* If a file exists, file_destination($destination, $replace) will either
* return:
* - the existing filepath, if $replace is FILE_EXISTS_REPLACE
* - a new filepath if FILE_EXISTS_RENAME
* - an error (returning FALSE) if FILE_EXISTS_ERROR.
* If the file doesn't currently exist, then it will simply return the
* filepath.
*/
function testFileDestination() {
// First test for non-existent file.
$destination = 'core/misc/xyz.txt';
$path = file_destination($destination, FILE_EXISTS_REPLACE);
$this->assertEqual($path, $destination, 'Non-existing filepath destination is correct with FILE_EXISTS_REPLACE.', 'File');
$path = file_destination($destination, FILE_EXISTS_RENAME);
$this->assertEqual($path, $destination, 'Non-existing filepath destination is correct with FILE_EXISTS_RENAME.', 'File');
$path = file_destination($destination, FILE_EXISTS_ERROR);
$this->assertEqual($path, $destination, 'Non-existing filepath destination is correct with FILE_EXISTS_ERROR.', 'File');
$destination = 'core/misc/druplicon.png';
$path = file_destination($destination, FILE_EXISTS_REPLACE);
$this->assertEqual($path, $destination, 'Existing filepath destination remains the same with FILE_EXISTS_REPLACE.', 'File');
$path = file_destination($destination, FILE_EXISTS_RENAME);
$this->assertNotEqual($path, $destination, 'A new filepath destination is created when filepath destination already exists with FILE_EXISTS_RENAME.', 'File');
$path = file_destination($destination, FILE_EXISTS_ERROR);
$this->assertEqual($path, FALSE, 'An error is returned when filepath destination already exists with FILE_EXISTS_ERROR.', 'File');
}
/**
* Ensure that the file_directory_temp() function always returns a value.
*/
function testFileDirectoryTemp() {
// Start with an empty variable to ensure we have a clean slate.
$config = $this->config('system.file');
$config->set('path.temporary', '')->save();
$tmp_directory = file_directory_temp();
$this->assertEqual(empty($tmp_directory), FALSE, 'file_directory_temp() returned a non-empty value.');
$this->assertEqual($config->get('path.temporary'), $tmp_directory);
}
}

View file

@ -0,0 +1,202 @@
<?php
namespace Drupal\KernelTests\Core\File;
use Drupal\Core\DependencyInjection\ContainerBuilder;
use Drupal\KernelTests\KernelTestBase;
/**
* Base class for file tests that adds some additional file specific
* assertions and helper functions.
*/
abstract class FileTestBase extends KernelTestBase {
/**
* Modules to enable.
*
* @var array
*/
public static $modules = array('system');
/**
* A stream wrapper scheme to register for the test.
*
* @var string
*/
protected $scheme;
/**
* A fully-qualified stream wrapper class name to register for the test.
*
* @var string
*/
protected $classname;
/**
* {@inheritdoc}
*/
protected function setUp() {
parent::setUp();
\Drupal::configFactory()->getEditable('system.file')->set('default_scheme', 'public')->save();
}
/**
* {@inheritdoc}
*/
public function register(ContainerBuilder $container) {
parent::register($container);
$container->register('stream_wrapper.private', 'Drupal\Core\StreamWrapper\PrivateStream')
->addTag('stream_wrapper', ['scheme' => 'private']);
if (isset($this->scheme)) {
$container->register('stream_wrapper.' . $this->scheme, $this->classname)
->addTag('stream_wrapper', ['scheme' => $this->scheme]);
}
}
/**
* {@inheritdoc}
*/
protected function setUpFilesystem() {
$public_file_directory = $this->siteDirectory . '/files';
require_once 'core/includes/file.inc';
mkdir($this->siteDirectory, 0775);
mkdir($this->siteDirectory . '/files', 0775);
mkdir($this->siteDirectory . '/files/config/' . CONFIG_SYNC_DIRECTORY, 0775, TRUE);
$this->setSetting('file_public_path', $public_file_directory);
$GLOBALS['config_directories'] = array(
CONFIG_SYNC_DIRECTORY => $this->siteDirectory . '/files/config/sync',
);
}
/**
* Helper function to test the permissions of a file.
*
* @param $filepath
* String file path.
* @param $expected_mode
* Octal integer like 0664 or 0777.
* @param $message
* Optional message.
*/
function assertFilePermissions($filepath, $expected_mode, $message = NULL) {
// Clear out PHP's file stat cache to be sure we see the current value.
clearstatcache(TRUE, $filepath);
// Mask out all but the last three octets.
$actual_mode = fileperms($filepath) & 0777;
// PHP on Windows has limited support for file permissions. Usually each of
// "user", "group" and "other" use one octal digit (3 bits) to represent the
// read/write/execute bits. On Windows, chmod() ignores the "group" and
// "other" bits, and fileperms() returns the "user" bits in all three
// positions. $expected_mode is updated to reflect this.
if (substr(PHP_OS, 0, 3) == 'WIN') {
// Reset the "group" and "other" bits.
$expected_mode = $expected_mode & 0700;
// Shift the "user" bits to the "group" and "other" positions also.
$expected_mode = $expected_mode | $expected_mode >> 3 | $expected_mode >> 6;
}
if (!isset($message)) {
$message = t('Expected file permission to be %expected, actually were %actual.', array('%actual' => decoct($actual_mode), '%expected' => decoct($expected_mode)));
}
$this->assertEqual($actual_mode, $expected_mode, $message);
}
/**
* Helper function to test the permissions of a directory.
*
* @param $directory
* String directory path.
* @param $expected_mode
* Octal integer like 0664 or 0777.
* @param $message
* Optional message.
*/
function assertDirectoryPermissions($directory, $expected_mode, $message = NULL) {
// Clear out PHP's file stat cache to be sure we see the current value.
clearstatcache(TRUE, $directory);
// Mask out all but the last three octets.
$actual_mode = fileperms($directory) & 0777;
$expected_mode = $expected_mode & 0777;
// PHP on Windows has limited support for file permissions. Usually each of
// "user", "group" and "other" use one octal digit (3 bits) to represent the
// read/write/execute bits. On Windows, chmod() ignores the "group" and
// "other" bits, and fileperms() returns the "user" bits in all three
// positions. $expected_mode is updated to reflect this.
if (substr(PHP_OS, 0, 3) == 'WIN') {
// Reset the "group" and "other" bits.
$expected_mode = $expected_mode & 0700;
// Shift the "user" bits to the "group" and "other" positions also.
$expected_mode = $expected_mode | $expected_mode >> 3 | $expected_mode >> 6;
}
if (!isset($message)) {
$message = t('Expected directory permission to be %expected, actually were %actual.', array('%actual' => decoct($actual_mode), '%expected' => decoct($expected_mode)));
}
$this->assertEqual($actual_mode, $expected_mode, $message);
}
/**
* Create a directory and assert it exists.
*
* @param $path
* Optional string with a directory path. If none is provided, a random
* name in the site's files directory will be used.
* @return
* The path to the directory.
*/
function createDirectory($path = NULL) {
// A directory to operate on.
if (!isset($path)) {
$path = file_default_scheme() . '://' . $this->randomMachineName();
}
$this->assertTrue(drupal_mkdir($path) && is_dir($path), 'Directory was created successfully.');
return $path;
}
/**
* Create a file and return the URI of it.
*
* @param $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 $contents
* Optional contents to save into the file. If a NULL value is provided an
* arbitrary string will be used.
* @param $scheme
* Optional string indicating the stream scheme to use. Drupal core includes
* public, private, and temporary. The public wrapper is the default.
* @return
* 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->randomMachineName();
}
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;
}
}

View file

@ -0,0 +1,92 @@
<?php
namespace Drupal\KernelTests\Core\File;
use Drupal\Component\Utility\SafeMarkup;
use Drupal\Core\Site\Settings;
use Drupal\KernelTests\KernelTestBase;
/**
* Tests .htaccess file saving.
*
* @group File
*/
class HtaccessTest extends KernelTestBase {
/**
* Tests file_save_htaccess().
*/
function testHtaccessSave() {
// Prepare test directories.
$public = Settings::get('file_public_path') . '/test/public';
$private = Settings::get('file_public_path') . '/test/private';
$stream = 'public://test/stream';
// Verify that file_save_htaccess() returns FALSE if .htaccess cannot be
// written.
// Note: We cannot test the condition of a directory lacking write
// permissions, since at least on Windows file_save_htaccess() succeeds
// even when changing directory permissions to 0000.
$this->assertFalse(file_save_htaccess($public, FALSE));
// Create public .htaccess file.
mkdir($public, 0777, TRUE);
$this->assertTrue(file_save_htaccess($public, FALSE));
$content = file_get_contents($public . '/.htaccess');
$this->assertTrue(strpos($content, "SetHandler Drupal_Security_Do_Not_Remove_See_SA_2006_006") !== FALSE);
$this->assertFalse(strpos($content, "Require all denied") !== FALSE);
$this->assertFalse(strpos($content, "Deny from all") !== FALSE);
$this->assertTrue(strpos($content, "Options -Indexes -ExecCGI -Includes -MultiViews") !== FALSE);
$this->assertTrue(strpos($content, "SetHandler Drupal_Security_Do_Not_Remove_See_SA_2013_003") !== FALSE);
$this->assertFilePermissions($public . '/.htaccess', 0444);
$this->assertTrue(file_save_htaccess($public, FALSE));
// Create private .htaccess file.
mkdir($private, 0777, TRUE);
$this->assertTrue(file_save_htaccess($private));
$content = file_get_contents($private . '/.htaccess');
$this->assertTrue(strpos($content, "SetHandler Drupal_Security_Do_Not_Remove_See_SA_2006_006") !== FALSE);
$this->assertTrue(strpos($content, "Require all denied") !== FALSE);
$this->assertTrue(strpos($content, "Deny from all") !== FALSE);
$this->assertTrue(strpos($content, "Options -Indexes -ExecCGI -Includes -MultiViews") !== FALSE);
$this->assertTrue(strpos($content, "SetHandler Drupal_Security_Do_Not_Remove_See_SA_2013_003") !== FALSE);
$this->assertFilePermissions($private . '/.htaccess', 0444);
$this->assertTrue(file_save_htaccess($private));
// Create an .htaccess file using a stream URI.
mkdir($stream, 0777, TRUE);
$this->assertTrue(file_save_htaccess($stream));
$content = file_get_contents($stream . '/.htaccess');
$this->assertTrue(strpos($content, "SetHandler Drupal_Security_Do_Not_Remove_See_SA_2006_006") !== FALSE);
$this->assertTrue(strpos($content, "Require all denied") !== FALSE);
$this->assertTrue(strpos($content, "Deny from all") !== FALSE);
$this->assertTrue(strpos($content, "Options -Indexes -ExecCGI -Includes -MultiViews") !== FALSE);
$this->assertTrue(strpos($content, "SetHandler Drupal_Security_Do_Not_Remove_See_SA_2013_003") !== FALSE);
$this->assertFilePermissions($stream . '/.htaccess', 0444);
$this->assertTrue(file_save_htaccess($stream));
}
/**
* Asserts expected file permissions for a given file.
*
* @param string $uri
* The URI of the file to check.
* @param int $expected
* The expected file permissions; e.g., 0444.
*
* @return bool
* Whether the actual file permissions match the expected.
*/
protected function assertFilePermissions($uri, $expected) {
$actual = fileperms($uri) & 0777;
return $this->assertIdentical($actual, $expected, SafeMarkup::format('@uri file permissions @actual are identical to @expected.', array(
'@uri' => $uri,
'@actual' => 0 . decoct($actual),
'@expected' => 0 . decoct($expected),
)));
}
}

View file

@ -0,0 +1,92 @@
<?php
namespace Drupal\KernelTests\Core\File;
/**
* Tests filename mimetype detection.
*
* @group File
*/
class MimeTypeTest extends FileTestBase {
/**
* Modules to enable.
*
* @var array
*/
public static $modules = array('file_test');
/**
* Test mapping of mimetypes from filenames.
*/
public function testFileMimeTypeDetection() {
$prefixes = ['public://', 'private://', 'temporary://', 'dummy-remote://'];
$test_case = array(
'test.jar' => 'application/java-archive',
'test.jpeg' => 'image/jpeg',
'test.JPEG' => 'image/jpeg',
'test.jpg' => 'image/jpeg',
'test.jar.jpg' => 'image/jpeg',
'test.jpg.jar' => 'application/java-archive',
'test.pcf.Z' => 'application/x-font',
'pcf.z' => 'application/octet-stream',
'jar' => 'application/octet-stream',
'some.junk' => 'application/octet-stream',
'foo.file_test_1' => 'madeup/file_test_1',
'foo.file_test_2' => 'madeup/file_test_2',
'foo.doc' => 'madeup/doc',
'test.ogg' => 'audio/ogg',
);
$guesser = $this->container->get('file.mime_type.guesser');
// Test using default mappings.
foreach ($test_case as $input => $expected) {
// Test stream [URI].
foreach ($prefixes as $prefix) {
$output = $guesser->guess($prefix . $input);
$this->assertIdentical($output, $expected, format_string('Mimetype for %input is %output (expected: %expected).', array('%input' => $prefix . $input, '%output' => $output, '%expected' => $expected)));
}
// Test normal path equivalent
$output = $guesser->guess($input);
$this->assertIdentical($output, $expected, format_string('Mimetype (using default mappings) for %input is %output (expected: %expected).', array('%input' => $input, '%output' => $output, '%expected' => $expected)));
}
// Now test the extension gusser by passing in a custom mapping.
$mapping = array(
'mimetypes' => array(
0 => 'application/java-archive',
1 => 'image/jpeg',
),
'extensions' => array(
'jar' => 0,
'jpg' => 1,
)
);
$test_case = array(
'test.jar' => 'application/java-archive',
'test.jpeg' => 'application/octet-stream',
'test.jpg' => 'image/jpeg',
'test.jar.jpg' => 'image/jpeg',
'test.jpg.jar' => 'application/java-archive',
'test.pcf.z' => 'application/octet-stream',
'pcf.z' => 'application/octet-stream',
'jar' => 'application/octet-stream',
'some.junk' => 'application/octet-stream',
'foo.file_test_1' => 'application/octet-stream',
'foo.file_test_2' => 'application/octet-stream',
'foo.doc' => 'application/octet-stream',
'test.ogg' => 'application/octet-stream',
);
$extension_guesser = $this->container->get('file.mime_type.guesser.extension');
$extension_guesser->setMapping($mapping);
foreach ($test_case as $input => $expected) {
$output = $extension_guesser->guess($input);
$this->assertIdentical($output, $expected, format_string('Mimetype (using passed-in mappings) for %input is %output (expected: %expected).', array('%input' => $input, '%output' => $output, '%expected' => $expected)));
}
}
}

View file

@ -0,0 +1,87 @@
<?php
namespace Drupal\KernelTests\Core\File;
/**
* Tests filename munging and unmunging.
*
* @group File
*/
class NameMungingTest extends FileTestBase {
/**
* @var string
*/
protected $badExtension;
/**
* @var string
*/
protected $name;
/**
* @var string
*/
protected $nameWithUcExt;
protected function setUp() {
parent::setUp();
$this->badExtension = 'php';
$this->name = $this->randomMachineName() . '.' . $this->badExtension . '.txt';
$this->nameWithUcExt = $this->randomMachineName() . '.' . strtoupper($this->badExtension) . '.txt';
}
/**
* Create a file and munge/unmunge the name.
*/
function testMunging() {
// Disable insecure uploads.
$this->config('system.file')->set('allow_insecure_uploads', 0)->save();
$munged_name = file_munge_filename($this->name, '', TRUE);
$messages = drupal_get_messages();
$this->assertTrue(in_array(strtr('For security reasons, your upload has been renamed to <em class="placeholder">%filename</em>.', array('%filename' => $munged_name)), $messages['status']), 'Alert properly set when a file is renamed.');
$this->assertNotEqual($munged_name, $this->name, format_string('The new filename (%munged) has been modified from the original (%original)', array('%munged' => $munged_name, '%original' => $this->name)));
}
/**
* Tests munging with a null byte in the filename.
*/
function testMungeNullByte() {
$prefix = $this->randomMachineName();
$filename = $prefix . '.' . $this->badExtension . "\0.txt";
$this->assertEqual(file_munge_filename($filename, ''), $prefix . '.' . $this->badExtension . '_.txt', 'A filename with a null byte is correctly munged to remove the null byte.');
}
/**
* If the system.file.allow_insecure_uploads setting evaluates to true, the file should
* come out untouched, no matter how evil the filename.
*/
function testMungeIgnoreInsecure() {
$this->config('system.file')->set('allow_insecure_uploads', 1)->save();
$munged_name = file_munge_filename($this->name, '');
$this->assertIdentical($munged_name, $this->name, format_string('The original filename (%original) matches the munged filename (%munged) when insecure uploads are enabled.', array('%munged' => $munged_name, '%original' => $this->name)));
}
/**
* White listed extensions are ignored by file_munge_filename().
*/
function testMungeIgnoreWhitelisted() {
// Declare our extension as whitelisted. The declared extensions should
// be case insensitive so test using one with a different case.
$munged_name = file_munge_filename($this->nameWithUcExt, $this->badExtension);
$this->assertIdentical($munged_name, $this->nameWithUcExt, format_string('The new filename (%munged) matches the original (%original) once the extension has been whitelisted.', array('%munged' => $munged_name, '%original' => $this->nameWithUcExt)));
// The allowed extensions should also be normalized.
$munged_name = file_munge_filename($this->name, strtoupper($this->badExtension));
$this->assertIdentical($munged_name, $this->name, format_string('The new filename (%munged) matches the original (%original) also when the whitelisted extension is in uppercase.', array('%munged' => $munged_name, '%original' => $this->name)));
}
/**
* Ensure that unmunge gets your name back.
*/
function testUnMunge() {
$munged_name = file_munge_filename($this->name, '', FALSE);
$unmunged_name = file_unmunge_filename($munged_name);
$this->assertIdentical($unmunged_name, $this->name, format_string('The unmunged (%unmunged) filename matches the original (%original)', array('%unmunged' => $unmunged_name, '%original' => $this->name)));
}
}

View file

@ -0,0 +1,98 @@
<?php
namespace Drupal\KernelTests\Core\File;
use Drupal\Core\StreamWrapper\StreamWrapperInterface;
use Drupal\file_test\StreamWrapper\DummyReadOnlyStreamWrapper;
/**
* Tests the read-only stream wrapper write functions.
*
* @group File
*/
class ReadOnlyStreamWrapperTest extends FileTestBase {
/**
* A stream wrapper scheme to register for the test.
*
* @var string
*/
protected $scheme = 'dummy-readonly';
/**
* A fully-qualified stream wrapper class name to register for the test.
*
* @var string
*/
protected $classname = 'Drupal\file_test\StreamWrapper\DummyReadOnlyStreamWrapper';
/**
* Test read-only specific behavior.
*/
function testReadOnlyBehavior() {
$type = DummyReadOnlyStreamWrapper::getType();
// Checks that the stream wrapper type is not declared as writable.
$this->assertSame(0, $type & StreamWrapperInterface::WRITE);
// Checks that the stream wrapper type is declared as local.
$this->assertSame(1, $type & StreamWrapperInterface::LOCAL);
// Generate a test file
$filename = $this->randomMachineName();
$site_path = $this->container->get('site.path');
$filepath = $site_path . '/files/' . $filename;
file_put_contents($filepath, $filename);
// Generate a read-only stream wrapper instance
$uri = $this->scheme . '://' . $filename;
\Drupal::service('stream_wrapper_manager')->getViaScheme($this->scheme);
// Attempt to open a file in read/write mode
$handle = @fopen($uri, 'r+');
$this->assertFalse($handle, 'Unable to open a file for reading and writing with the read-only stream wrapper.');
// Attempt to open a file in binary read mode
$handle = fopen($uri, 'rb');
$this->assertTrue($handle, 'Able to open a file for reading in binary mode with the read-only stream wrapper.');
$this->assertTrue(fclose($handle), 'Able to close file opened in binary mode using the read_only stream wrapper.');
// Attempt to open a file in text read mode
$handle = fopen($uri, 'rt');
$this->assertTrue($handle, 'Able to open a file for reading in text mode with the read-only stream wrapper.');
$this->assertTrue(fclose($handle), 'Able to close file opened in text mode using the read_only stream wrapper.');
// Attempt to open a file in read mode
$handle = fopen($uri, 'r');
$this->assertTrue($handle, 'Able to open a file for reading with the read-only stream wrapper.');
// Attempt to change file permissions
$this->assertFalse(@chmod($uri, 0777), 'Unable to change file permissions when using read-only stream wrapper.');
// Attempt to acquire an exclusive lock for writing
$this->assertFalse(@flock($handle, LOCK_EX | LOCK_NB), 'Unable to acquire an exclusive lock using the read-only stream wrapper.');
// Attempt to obtain a shared lock
$this->assertTrue(flock($handle, LOCK_SH | LOCK_NB), 'Able to acquire a shared lock using the read-only stream wrapper.');
// Attempt to release a shared lock
$this->assertTrue(flock($handle, LOCK_UN | LOCK_NB), 'Able to release a shared lock using the read-only stream wrapper.');
// Attempt to truncate the file
$this->assertFalse(@ftruncate($handle, 0), 'Unable to truncate using the read-only stream wrapper.');
// Attempt to write to the file
$this->assertFalse(@fwrite($handle, $this->randomMachineName()), 'Unable to write to file using the read-only stream wrapper.');
// Attempt to flush output to the file
$this->assertFalse(@fflush($handle), 'Unable to flush output to file using the read-only stream wrapper.');
// Attempt to close the stream. (Suppress errors, as fclose triggers fflush.)
$this->assertTrue(fclose($handle), 'Able to close file using the read_only stream wrapper.');
// Test the rename() function
$this->assertFalse(@rename($uri, $this->scheme . '://newname.txt'), 'Unable to rename files using the read-only stream wrapper.');
// Test the unlink() function
$this->assertTrue(@drupal_unlink($uri), 'Able to unlink file using read-only stream wrapper.');
$this->assertTrue(file_exists($filepath), 'Unlink File was not actually deleted.');
// Test the mkdir() function by attempting to create a directory.
$dirname = $this->randomMachineName();
$dir = $site_path . '/files/' . $dirname;
$readonlydir = $this->scheme . '://' . $dirname;
$this->assertFalse(@drupal_mkdir($readonlydir, 0775, 0), 'Unable to create directory with read-only stream wrapper.');
// Create a temporary directory for testing purposes
$this->assertTrue(drupal_mkdir($dir), 'Test directory created.');
// Test the rmdir() function by attempting to remove the directory.
$this->assertFalse(@drupal_rmdir($readonlydir), 'Unable to delete directory with read-only stream wrapper.');
// Remove the temporary directory.
drupal_rmdir($dir);
}
}

View file

@ -0,0 +1,38 @@
<?php
namespace Drupal\KernelTests\Core\File;
/**
* Tests operations dealing with directories.
*
* @group File
*/
class RemoteFileDirectoryTest extends DirectoryTest {
/**
* Modules to enable.
*
* @var array
*/
public static $modules = array('file_test');
/**
* A stream wrapper scheme to register for the test.
*
* @var string
*/
protected $scheme = 'dummy-remote';
/**
* A fully-qualified stream wrapper class name to register for the test.
*
* @var string
*/
protected $classname = 'Drupal\file_test\StreamWrapper\DummyRemoteStreamWrapper';
protected function setUp() {
parent::setUp();
$this->config('system.file')->set('default_scheme', 'dummy-remote')->save();
}
}

View file

@ -0,0 +1,38 @@
<?php
namespace Drupal\KernelTests\Core\File;
/**
* Tests the file_scan_directory() function.
*
* @group File
*/
class RemoteFileScanDirectoryTest extends ScanDirectoryTest {
/**
* Modules to enable.
*
* @var array
*/
public static $modules = array('file_test');
/**
* A stream wrapper scheme to register for the test.
*
* @var string
*/
protected $scheme = 'dummy-remote';
/**
* A fully-qualified stream wrapper class name to register for the test.
*
* @var string
*/
protected $classname = 'Drupal\file_test\StreamWrapper\DummyRemoteStreamWrapper';
protected function setUp() {
parent::setUp();
$this->config('system.file')->set('default_scheme', 'dummy-remote')->save();
}
}

View file

@ -0,0 +1,38 @@
<?php
namespace Drupal\KernelTests\Core\File;
/**
* Tests the unmanaged file copy function.
*
* @group File
*/
class RemoteFileUnmanagedCopyTest extends UnmanagedCopyTest {
/**
* Modules to enable.
*
* @var array
*/
public static $modules = array('file_test');
/**
* A stream wrapper scheme to register for the test.
*
* @var string
*/
protected $scheme = 'dummy-remote';
/**
* A fully-qualified stream wrapper class name to register for the test.
*
* @var string
*/
protected $classname = 'Drupal\file_test\StreamWrapper\DummyRemoteStreamWrapper';
protected function setUp() {
parent::setUp();
$this->config('system.file')->set('default_scheme', 'dummy-remote')->save();
}
}

View file

@ -0,0 +1,38 @@
<?php
namespace Drupal\KernelTests\Core\File;
/**
* Tests the unmanaged file delete recursive function.
*
* @group File
*/
class RemoteFileUnmanagedDeleteRecursiveTest extends UnmanagedDeleteRecursiveTest {
/**
* Modules to enable.
*
* @var array
*/
public static $modules = array('file_test');
/**
* A stream wrapper scheme to register for the test.
*
* @var string
*/
protected $scheme = 'dummy-remote';
/**
* A fully-qualified stream wrapper class name to register for the test.
*
* @var string
*/
protected $classname = 'Drupal\file_test\StreamWrapper\DummyRemoteStreamWrapper';
protected function setUp() {
parent::setUp();
$this->config('system.file')->set('default_scheme', 'dummy-remote')->save();
}
}

View file

@ -0,0 +1,38 @@
<?php
namespace Drupal\KernelTests\Core\File;
/**
* Tests the unmanaged file delete function.
*
* @group File
*/
class RemoteFileUnmanagedDeleteTest extends UnmanagedDeleteTest {
/**
* Modules to enable.
*
* @var array
*/
public static $modules = array('file_test');
/**
* A stream wrapper scheme to register for the test.
*
* @var string
*/
protected $scheme = 'dummy-remote';
/**
* A fully-qualified stream wrapper class name to register for the test.
*
* @var string
*/
protected $classname = 'Drupal\file_test\StreamWrapper\DummyRemoteStreamWrapper';
protected function setUp() {
parent::setUp();
$this->config('system.file')->set('default_scheme', 'dummy-remote')->save();
}
}

View file

@ -0,0 +1,38 @@
<?php
namespace Drupal\KernelTests\Core\File;
/**
* Tests the unmanaged file move function.
*
* @group File
*/
class RemoteFileUnmanagedMoveTest extends UnmanagedMoveTest {
/**
* Modules to enable.
*
* @var array
*/
public static $modules = array('file_test');
/**
* A stream wrapper scheme to register for the test.
*
* @var string
*/
protected $scheme = 'dummy-remote';
/**
* A fully-qualified stream wrapper class name to register for the test.
*
* @var string
*/
protected $classname = 'Drupal\file_test\StreamWrapper\DummyRemoteStreamWrapper';
protected function setUp() {
parent::setUp();
$this->config('system.file')->set('default_scheme', 'dummy-remote')->save();
}
}

View file

@ -0,0 +1,38 @@
<?php
namespace Drupal\KernelTests\Core\File;
/**
* Tests the unmanaged file save data function.
*
* @group File
*/
class RemoteFileUnmanagedSaveDataTest extends UnmanagedSaveDataTest {
/**
* Modules to enable.
*
* @var array
*/
public static $modules = array('file_test');
/**
* A stream wrapper scheme to register for the test.
*
* @var string
*/
protected $scheme = 'dummy-remote';
/**
* A fully-qualified stream wrapper class name to register for the test.
*
* @var string
*/
protected $classname = 'Drupal\file_test\StreamWrapper\DummyRemoteStreamWrapper';
protected function setUp() {
parent::setUp();
$this->config('system.file')->set('default_scheme', 'dummy-remote')->save();
}
}

View file

@ -0,0 +1,166 @@
<?php
namespace Drupal\KernelTests\Core\File;
/**
* Tests the file_scan_directory() function.
*
* @group File
*/
class ScanDirectoryTest extends FileTestBase {
/**
* Modules to enable.
*
* @var array
*/
public static $modules = array('file_test');
/**
* @var string
*/
protected $path;
protected function setUp() {
parent::setUp();
// Hardcode the location of the simpletest files as it is already known
// and shouldn't change, and we don't yet have a way to retrieve their
// location from drupal_get_filename() in a cached way.
// @todo Remove as part of https://www.drupal.org/node/2186491
$this->path = 'core/modules/simpletest/files';
}
/**
* Check the format of the returned values.
*/
function testReturn() {
// Grab a listing of all the JavaScript files and check that they're
// passed to the callback.
$all_files = file_scan_directory($this->path, '/^javascript-/');
ksort($all_files);
$this->assertEqual(2, count($all_files), 'Found two, expected javascript files.');
// Check the first file.
$file = reset($all_files);
$this->assertEqual(key($all_files), $file->uri, 'Correct array key was used for the first returned file.');
$this->assertEqual($file->uri, $this->path . '/javascript-1.txt', 'First file name was set correctly.');
$this->assertEqual($file->filename, 'javascript-1.txt', 'First basename was set correctly');
$this->assertEqual($file->name, 'javascript-1', 'First name was set correctly.');
// Check the second file.
$file = next($all_files);
$this->assertEqual(key($all_files), $file->uri, 'Correct array key was used for the second returned file.');
$this->assertEqual($file->uri, $this->path . '/javascript-2.script', 'Second file name was set correctly.');
$this->assertEqual($file->filename, 'javascript-2.script', 'Second basename was set correctly');
$this->assertEqual($file->name, 'javascript-2', 'Second name was set correctly.');
}
/**
* Check that the callback function is called correctly.
*/
function testOptionCallback() {
// When nothing is matched nothing should be passed to the callback.
$all_files = file_scan_directory($this->path, '/^NONEXISTINGFILENAME/', array('callback' => 'file_test_file_scan_callback'));
$this->assertEqual(0, count($all_files), 'No files were found.');
$results = file_test_file_scan_callback();
file_test_file_scan_callback_reset();
$this->assertEqual(0, count($results), 'No files were passed to the callback.');
// Grab a listing of all the JavaScript files and check that they're
// passed to the callback.
$all_files = file_scan_directory($this->path, '/^javascript-/', array('callback' => 'file_test_file_scan_callback'));
$this->assertEqual(2, count($all_files), 'Found two, expected javascript files.');
$results = file_test_file_scan_callback();
file_test_file_scan_callback_reset();
$this->assertEqual(2, count($results), 'Files were passed to the callback.');
}
/**
* Check that the no-mask parameter is honored.
*/
function testOptionNoMask() {
// Grab a listing of all the JavaScript files.
$all_files = file_scan_directory($this->path, '/^javascript-/');
$this->assertEqual(2, count($all_files), 'Found two, expected javascript files.');
// Now use the nomask parameter to filter out the .script file.
$filtered_files = file_scan_directory($this->path, '/^javascript-/', array('nomask' => '/.script$/'));
$this->assertEqual(1, count($filtered_files), 'Filtered correctly.');
}
/**
* Check that key parameter sets the return value's key.
*/
function testOptionKey() {
// "filename", for the path starting with $dir.
$expected = array($this->path . '/javascript-1.txt', $this->path . '/javascript-2.script');
$actual = array_keys(file_scan_directory($this->path, '/^javascript-/', array('key' => 'filepath')));
sort($actual);
$this->assertEqual($expected, $actual, 'Returned the correct values for the filename key.');
// "basename", for the basename of the file.
$expected = array('javascript-1.txt', 'javascript-2.script');
$actual = array_keys(file_scan_directory($this->path, '/^javascript-/', array('key' => 'filename')));
sort($actual);
$this->assertEqual($expected, $actual, 'Returned the correct values for the basename key.');
// "name" for the name of the file without an extension.
$expected = array('javascript-1', 'javascript-2');
$actual = array_keys(file_scan_directory($this->path, '/^javascript-/', array('key' => 'name')));
sort($actual);
$this->assertEqual($expected, $actual, 'Returned the correct values for the name key.');
// Invalid option that should default back to "filename".
$expected = array($this->path . '/javascript-1.txt', $this->path . '/javascript-2.script');
$actual = array_keys(file_scan_directory($this->path, '/^javascript-/', array('key' => 'INVALID')));
sort($actual);
$this->assertEqual($expected, $actual, 'An invalid key defaulted back to the default.');
}
/**
* Check that the recurse option descends into subdirectories.
*/
function testOptionRecurse() {
$files = file_scan_directory($this->path . '/..', '/^javascript-/', array('recurse' => FALSE));
$this->assertTrue(empty($files), "Without recursion couldn't find javascript files.");
$files = file_scan_directory($this->path . '/..', '/^javascript-/', array('recurse' => TRUE));
$this->assertEqual(2, count($files), 'With recursion we found the expected javascript files.');
}
/**
* Check that the min_depth options lets us ignore files in the starting
* directory.
*/
function testOptionMinDepth() {
$files = file_scan_directory($this->path, '/^javascript-/', array('min_depth' => 0));
$this->assertEqual(2, count($files), 'No minimum-depth gets files in current directory.');
$files = file_scan_directory($this->path, '/^javascript-/', array('min_depth' => 1));
$this->assertTrue(empty($files), 'Minimum-depth of 1 successfully excludes files from current directory.');
}
/**
* Tests file_scan_directory() obeys 'file_scan_ignore_directories' setting.
*/
public function testIgnoreDirectories() {
$files = file_scan_directory('core/modules/system/tests/fixtures/IgnoreDirectories', '/\.txt$/');
$this->assertCount(2, $files, '2 text files found when not ignoring directories.');
$this->setSetting('file_scan_ignore_directories', ['frontend_framework']);
$files = file_scan_directory('core/modules/system/tests/fixtures/IgnoreDirectories', '/\.txt$/');
$this->assertCount(1, $files, '1 text files found when ignoring directories called "frontend_framework".');
// Ensure that the directories in file_scan_ignore_directories are escaped
// using preg_quote.
$this->setSetting('file_scan_ignore_directories', ['frontend.*']);
$files = file_scan_directory('core/modules/system/tests/fixtures/IgnoreDirectories', '/\.txt$/');
$this->assertCount(2, $files, '2 text files found when ignoring a directory that is not there.');
$files = file_scan_directory('core/modules/system/tests/fixtures/IgnoreDirectories', '/\.txt$/', ['nomask' => '/^something_thing_else$/']);
$this->assertCount(2, $files, '2 text files found when an "nomask" option is passed in.');
}
}

View file

@ -0,0 +1,147 @@
<?php
namespace Drupal\KernelTests\Core\File;
use Drupal\Core\DrupalKernel;
use Drupal\Core\Site\Settings;
use Drupal\Core\StreamWrapper\PublicStream;
use Symfony\Component\HttpFoundation\Request;
/**
* Tests stream wrapper functions.
*
* @group File
*/
class StreamWrapperTest extends FileTestBase {
/**
* Modules to enable.
*
* @var array
*/
public static $modules = array('file_test');
/**
* A stream wrapper scheme to register for the test.
*
* @var string
*/
protected $scheme = 'dummy';
/**
* A fully-qualified stream wrapper class name to register for the test.
*
* @var string
*/
protected $classname = 'Drupal\file_test\StreamWrapper\DummyStreamWrapper';
function setUp() {
parent::setUp();
// Add file_private_path setting.
$request = Request::create('/');;
$site_path = DrupalKernel::findSitePath($request);
$this->setSetting('file_private_path', $site_path . '/private');
}
/**
* Test the getClassName() function.
*/
function testGetClassName() {
// Check the dummy scheme.
$this->assertEqual($this->classname, \Drupal::service('stream_wrapper_manager')->getClass($this->scheme), 'Got correct class name for dummy scheme.');
// Check core's scheme.
$this->assertEqual('Drupal\Core\StreamWrapper\PublicStream', \Drupal::service('stream_wrapper_manager')->getClass('public'), 'Got correct class name for public scheme.');
}
/**
* Test the getViaScheme() method.
*/
function testGetInstanceByScheme() {
$instance = \Drupal::service('stream_wrapper_manager')->getViaScheme($this->scheme);
$this->assertEqual($this->classname, get_class($instance), 'Got correct class type for dummy scheme.');
$instance = \Drupal::service('stream_wrapper_manager')->getViaScheme('public');
$this->assertEqual('Drupal\Core\StreamWrapper\PublicStream', get_class($instance), 'Got correct class type for public scheme.');
}
/**
* Test the getViaUri() and getViaScheme() methods and target functions.
*/
function testUriFunctions() {
$config = $this->config('system.file');
$instance = \Drupal::service('stream_wrapper_manager')->getViaUri($this->scheme . '://foo');
$this->assertEqual($this->classname, get_class($instance), 'Got correct class type for dummy URI.');
$instance = \Drupal::service('stream_wrapper_manager')->getViaUri('public://foo');
$this->assertEqual('Drupal\Core\StreamWrapper\PublicStream', get_class($instance), 'Got correct class type for public URI.');
// Test file_uri_target().
$this->assertEqual(file_uri_target('public://foo/bar.txt'), 'foo/bar.txt', 'Got a valid stream target from public://foo/bar.txt.');
$this->assertEqual(file_uri_target('data:image/png;base64,iVBORw0KGgoAAAANSUhEUgAAAAUAAAAFCAYAAACNbyblAAAAHElEQVQI12P4//8/w38GIAXDIBKE0DHxgljNBAAO9TXL0Y4OHwAAAABJRU5ErkJggg=='), 'image/png;base64,iVBORw0KGgoAAAANSUhEUgAAAAUAAAAFCAYAAACNbyblAAAAHElEQVQI12P4//8/w38GIAXDIBKE0DHxgljNBAAO9TXL0Y4OHwAAAABJRU5ErkJggg==', t('Got a valid stream target from a data URI.'));
$this->assertFalse(file_uri_target('foo/bar.txt'), 'foo/bar.txt is not a valid stream.');
$this->assertFalse(file_uri_target('public://'), 'public:// has no target.');
$this->assertFalse(file_uri_target('data:'), 'data: has no target.');
// Test file_build_uri() and
// Drupal\Core\StreamWrapper\LocalStream::getDirectoryPath().
$this->assertEqual(file_build_uri('foo/bar.txt'), 'public://foo/bar.txt', 'Expected scheme was added.');
$this->assertEqual(\Drupal::service('stream_wrapper_manager')->getViaScheme('public')->getDirectoryPath(), PublicStream::basePath(), 'Expected default directory path was returned.');
$this->assertEqual(\Drupal::service('stream_wrapper_manager')->getViaScheme('temporary')->getDirectoryPath(), $config->get('path.temporary'), 'Expected temporary directory path was returned.');
$config->set('default_scheme', 'private')->save();
$this->assertEqual(file_build_uri('foo/bar.txt'), 'private://foo/bar.txt', 'Got a valid URI from foo/bar.txt.');
// Test file_create_url()
// TemporaryStream::getExternalUrl() uses Url::fromRoute(), which needs
// route information to work.
$this->container->get('router.builder')->rebuild();
$this->assertTrue(strpos(file_create_url('temporary://test.txt'), 'system/temporary?file=test.txt'), 'Temporary external URL correctly built.');
$this->assertTrue(strpos(file_create_url('public://test.txt'), Settings::get('file_public_path') . '/test.txt'), 'Public external URL correctly built.');
$this->assertTrue(strpos(file_create_url('private://test.txt'), 'system/files/test.txt'), 'Private external URL correctly built.');
}
/**
* Test some file handle functions.
*/
function testFileFunctions() {
$filename = 'public://' . $this->randomMachineName();
file_put_contents($filename, str_repeat('d', 1000));
// Open for rw and place pointer at beginning of file so select will return.
$handle = fopen($filename, 'c+');
$this->assertTrue($handle, 'Able to open a file for appending, reading and writing.');
// Attempt to change options on the file stream: should all fail.
$this->assertFalse(@stream_set_blocking($handle, 0), 'Unable to set to non blocking using a local stream wrapper.');
$this->assertFalse(@stream_set_blocking($handle, 1), 'Unable to set to blocking using a local stream wrapper.');
$this->assertFalse(@stream_set_timeout($handle, 1), 'Unable to set read time out using a local stream wrapper.');
$this->assertEqual(-1 /*EOF*/, @stream_set_write_buffer($handle, 512), 'Unable to set write buffer using a local stream wrapper.');
// This will test stream_cast().
$read = array($handle);
$write = NULL;
$except = NULL;
$this->assertEqual(1, stream_select($read, $write, $except, 0), 'Able to cast a stream via stream_select.');
// This will test stream_truncate().
$this->assertEqual(1, ftruncate($handle, 0), 'Able to truncate a stream via ftruncate().');
fclose($handle);
$this->assertEqual(0, filesize($filename), 'Able to truncate a stream.');
// Cleanup.
unlink($filename);
}
/**
* Test the scheme functions.
*/
function testGetValidStreamScheme() {
$this->assertEqual('foo', file_uri_scheme('foo://pork//chops'), 'Got the correct scheme from foo://asdf');
$this->assertEqual('data', file_uri_scheme('data:image/png;base64,iVBORw0KGgoAAAANSUhEUgAAAAUAAAAFCAYAAACNbyblAAAAHElEQVQI12P4//8/w38GIAXDIBKE0DHxgljNBAAO9TXL0Y4OHwAAAABJRU5ErkJggg=='), 'Got the correct scheme from a data URI.');
$this->assertFalse(file_uri_scheme('foo/bar.txt'), 'foo/bar.txt is not a valid stream.');
$this->assertTrue(file_stream_wrapper_valid_scheme(file_uri_scheme('public://asdf')), 'Got a valid stream scheme from public://asdf');
$this->assertFalse(file_stream_wrapper_valid_scheme(file_uri_scheme('foo://asdf')), 'Did not get a valid stream scheme from foo://asdf');
}
}

View file

@ -0,0 +1,89 @@
<?php
namespace Drupal\KernelTests\Core\File;
use Drupal\Core\Site\Settings;
use Drupal\Core\File\FileSystem;
/**
* Tests the unmanaged file copy function.
*
* @group File
*/
class UnmanagedCopyTest extends FileTestBase {
/**
* Copy a normal file.
*/
function testNormal() {
// Create a file for testing
$uri = $this->createUri();
// Copying to a new name.
$desired_filepath = 'public://' . $this->randomMachineName();
$new_filepath = file_unmanaged_copy($uri, $desired_filepath, FILE_EXISTS_ERROR);
$this->assertTrue($new_filepath, 'Copy was successful.');
$this->assertEqual($new_filepath, $desired_filepath, 'Returned expected filepath.');
$this->assertTrue(file_exists($uri), 'Original file remains.');
$this->assertTrue(file_exists($new_filepath), 'New file exists.');
$this->assertFilePermissions($new_filepath, Settings::get('file_chmod_file', FileSystem::CHMOD_FILE));
// Copying with rename.
$desired_filepath = 'public://' . $this->randomMachineName();
$this->assertTrue(file_put_contents($desired_filepath, ' '), 'Created a file so a rename will have to happen.');
$newer_filepath = file_unmanaged_copy($uri, $desired_filepath, FILE_EXISTS_RENAME);
$this->assertTrue($newer_filepath, 'Copy was successful.');
$this->assertNotEqual($newer_filepath, $desired_filepath, 'Returned expected filepath.');
$this->assertTrue(file_exists($uri), 'Original file remains.');
$this->assertTrue(file_exists($newer_filepath), 'New file exists.');
$this->assertFilePermissions($newer_filepath, Settings::get('file_chmod_file', FileSystem::CHMOD_FILE));
// TODO: test copying to a directory (rather than full directory/file path)
// TODO: test copying normal files using normal paths (rather than only streams)
}
/**
* Copy a non-existent file.
*/
function testNonExistent() {
// Copy non-existent file
$desired_filepath = $this->randomMachineName();
$this->assertFalse(file_exists($desired_filepath), "Randomly named file doesn't exists.");
$new_filepath = file_unmanaged_copy($desired_filepath, $this->randomMachineName());
$this->assertFalse($new_filepath, 'Copying a missing file fails.');
}
/**
* Copy a file onto itself.
*/
function testOverwriteSelf() {
// Create a file for testing
$uri = $this->createUri();
// Copy the file onto itself with renaming works.
$new_filepath = file_unmanaged_copy($uri, $uri, FILE_EXISTS_RENAME);
$this->assertTrue($new_filepath, 'Copying onto itself with renaming works.');
$this->assertNotEqual($new_filepath, $uri, 'Copied file has a new name.');
$this->assertTrue(file_exists($uri), 'Original file exists after copying onto itself.');
$this->assertTrue(file_exists($new_filepath), 'Copied file exists after copying onto itself.');
$this->assertFilePermissions($new_filepath, Settings::get('file_chmod_file', FileSystem::CHMOD_FILE));
// Copy the file onto itself without renaming fails.
$new_filepath = file_unmanaged_copy($uri, $uri, FILE_EXISTS_ERROR);
$this->assertFalse($new_filepath, 'Copying onto itself without renaming fails.');
$this->assertTrue(file_exists($uri), 'File exists after copying onto itself.');
// Copy the file into same directory without renaming fails.
$new_filepath = file_unmanaged_copy($uri, drupal_dirname($uri), FILE_EXISTS_ERROR);
$this->assertFalse($new_filepath, 'Copying onto itself fails.');
$this->assertTrue(file_exists($uri), 'File exists after copying onto itself.');
// Copy the file into same directory with renaming works.
$new_filepath = file_unmanaged_copy($uri, drupal_dirname($uri), FILE_EXISTS_RENAME);
$this->assertTrue($new_filepath, 'Copying into same directory works.');
$this->assertNotEqual($new_filepath, $uri, 'Copied file has a new name.');
$this->assertTrue(file_exists($uri), 'Original file exists after copying onto itself.');
$this->assertTrue(file_exists($new_filepath), 'Copied file exists after copying onto itself.');
$this->assertFilePermissions($new_filepath, Settings::get('file_chmod_file', FileSystem::CHMOD_FILE));
}
}

View file

@ -0,0 +1,74 @@
<?php
namespace Drupal\KernelTests\Core\File;
/**
* Tests the unmanaged file delete recursive function.
*
* @group File
*/
class UnmanagedDeleteRecursiveTest extends FileTestBase {
/**
* Delete a normal file.
*/
function testSingleFile() {
// Create a file for testing
$filepath = file_default_scheme() . '://' . $this->randomMachineName();
file_put_contents($filepath, '');
// Delete the file.
$this->assertTrue(file_unmanaged_delete_recursive($filepath), 'Function reported success.');
$this->assertFalse(file_exists($filepath), 'Test file has been deleted.');
}
/**
* Try deleting an empty directory.
*/
function testEmptyDirectory() {
// A directory to operate on.
$directory = $this->createDirectory();
// Delete the directory.
$this->assertTrue(file_unmanaged_delete_recursive($directory), 'Function reported success.');
$this->assertFalse(file_exists($directory), 'Directory has been deleted.');
}
/**
* Try deleting a directory with some files.
*/
function testDirectory() {
// A directory to operate on.
$directory = $this->createDirectory();
$filepathA = $directory . '/A';
$filepathB = $directory . '/B';
file_put_contents($filepathA, '');
file_put_contents($filepathB, '');
// Delete the directory.
$this->assertTrue(file_unmanaged_delete_recursive($directory), 'Function reported success.');
$this->assertFalse(file_exists($filepathA), 'Test file A has been deleted.');
$this->assertFalse(file_exists($filepathB), 'Test file B has been deleted.');
$this->assertFalse(file_exists($directory), 'Directory has been deleted.');
}
/**
* Try deleting subdirectories with some files.
*/
function testSubDirectory() {
// A directory to operate on.
$directory = $this->createDirectory();
$subdirectory = $this->createDirectory($directory . '/sub');
$filepathA = $directory . '/A';
$filepathB = $subdirectory . '/B';
file_put_contents($filepathA, '');
file_put_contents($filepathB, '');
// Delete the directory.
$this->assertTrue(file_unmanaged_delete_recursive($directory), 'Function reported success.');
$this->assertFalse(file_exists($filepathA), 'Test file A has been deleted.');
$this->assertFalse(file_exists($filepathB), 'Test file B has been deleted.');
$this->assertFalse(file_exists($subdirectory), 'Subdirectory has been deleted.');
$this->assertFalse(file_exists($directory), 'Directory has been deleted.');
}
}

View file

@ -0,0 +1,43 @@
<?php
namespace Drupal\KernelTests\Core\File;
/**
* Tests the unmanaged file delete function.
*
* @group File
*/
class UnmanagedDeleteTest extends FileTestBase {
/**
* Delete a normal file.
*/
function testNormal() {
// Create a file for testing
$uri = $this->createUri();
// Delete a regular file
$this->assertTrue(file_unmanaged_delete($uri), 'Deleted worked.');
$this->assertFalse(file_exists($uri), 'Test file has actually been deleted.');
}
/**
* Try deleting a missing file.
*/
function testMissing() {
// Try to delete a non-existing file
$this->assertTrue(file_unmanaged_delete(file_default_scheme() . '/' . $this->randomMachineName()), 'Returns true when deleting a non-existent file.');
}
/**
* Try deleting a directory.
*/
function testDirectory() {
// A directory to operate on.
$directory = $this->createDirectory();
// Try to delete a directory
$this->assertFalse(file_unmanaged_delete($directory), 'Could not delete the delete directory.');
$this->assertTrue(file_exists($directory), 'Directory has not been deleted.');
}
}

View file

@ -0,0 +1,73 @@
<?php
namespace Drupal\KernelTests\Core\File;
use Drupal\Core\Site\Settings;
use Drupal\Core\File\FileSystem;
/**
* Tests the unmanaged file move function.
*
* @group File
*/
class UnmanagedMoveTest extends FileTestBase {
/**
* Move a normal file.
*/
function testNormal() {
// Create a file for testing
$uri = $this->createUri();
// Moving to a new name.
$desired_filepath = 'public://' . $this->randomMachineName();
$new_filepath = file_unmanaged_move($uri, $desired_filepath, FILE_EXISTS_ERROR);
$this->assertTrue($new_filepath, 'Move was successful.');
$this->assertEqual($new_filepath, $desired_filepath, 'Returned expected filepath.');
$this->assertTrue(file_exists($new_filepath), 'File exists at the new location.');
$this->assertFalse(file_exists($uri), 'No file remains at the old location.');
$this->assertFilePermissions($new_filepath, Settings::get('file_chmod_file', FileSystem::CHMOD_FILE));
// Moving with rename.
$desired_filepath = 'public://' . $this->randomMachineName();
$this->assertTrue(file_exists($new_filepath), 'File exists before moving.');
$this->assertTrue(file_put_contents($desired_filepath, ' '), 'Created a file so a rename will have to happen.');
$newer_filepath = file_unmanaged_move($new_filepath, $desired_filepath, FILE_EXISTS_RENAME);
$this->assertTrue($newer_filepath, 'Move was successful.');
$this->assertNotEqual($newer_filepath, $desired_filepath, 'Returned expected filepath.');
$this->assertTrue(file_exists($newer_filepath), 'File exists at the new location.');
$this->assertFalse(file_exists($new_filepath), 'No file remains at the old location.');
$this->assertFilePermissions($newer_filepath, Settings::get('file_chmod_file', FileSystem::CHMOD_FILE));
// TODO: test moving to a directory (rather than full directory/file path)
// TODO: test creating and moving normal files (rather than streams)
}
/**
* Try to move a missing file.
*/
function testMissing() {
// Move non-existent file.
$new_filepath = file_unmanaged_move($this->randomMachineName(), $this->randomMachineName());
$this->assertFalse($new_filepath, 'Moving a missing file fails.');
}
/**
* Try to move a file onto itself.
*/
function testOverwriteSelf() {
// Create a file for testing.
$uri = $this->createUri();
// Move the file onto itself without renaming shouldn't make changes.
$new_filepath = file_unmanaged_move($uri, $uri, FILE_EXISTS_REPLACE);
$this->assertFalse($new_filepath, 'Moving onto itself without renaming fails.');
$this->assertTrue(file_exists($uri), 'File exists after moving onto itself.');
// Move the file onto itself with renaming will result in a new filename.
$new_filepath = file_unmanaged_move($uri, $uri, FILE_EXISTS_RENAME);
$this->assertTrue($new_filepath, 'Moving onto itself with renaming works.');
$this->assertFalse(file_exists($uri), 'Original file has been removed.');
$this->assertTrue(file_exists($new_filepath), 'File exists after moving onto itself.');
}
}

View file

@ -0,0 +1,32 @@
<?php
namespace Drupal\KernelTests\Core\File;
/**
* Tests the file_unmanaged_save_data() function.
*
* @group File
*/
class UnmanagedSaveDataTest extends FileTestBase {
/**
* Test the file_unmanaged_save_data() function.
*/
function testFileSaveData() {
$contents = $this->randomMachineName(8);
$this->setSetting('file_chmod_file', 0777);
// No filename.
$filepath = file_unmanaged_save_data($contents);
$this->assertTrue($filepath, 'Unnamed file saved correctly.');
$this->assertEqual(file_uri_scheme($filepath), file_default_scheme(), "File was placed in Drupal's files directory.");
$this->assertEqual($contents, file_get_contents($filepath), 'Contents of the file are correct.');
// Provide a filename.
$filepath = file_unmanaged_save_data($contents, 'public://asdf.txt', FILE_EXISTS_REPLACE);
$this->assertTrue($filepath, 'Unnamed file saved correctly.');
$this->assertEqual('asdf.txt', drupal_basename($filepath), 'File was named correctly.');
$this->assertEqual($contents, file_get_contents($filepath), 'Contents of the file are correct.');
$this->assertFilePermissions($filepath, 0777);
}
}

View file

@ -0,0 +1,118 @@
<?php
namespace Drupal\KernelTests\Core\File;
use Symfony\Component\HttpFoundation\Request;
/**
* Tests for file URL rewriting.
*
* @group File
*/
class UrlRewritingTest extends FileTestBase {
/**
* Modules to enable.
*
* @var array
*/
public static $modules = array('file_test');
/**
* Tests the rewriting of shipped file URLs by hook_file_url_alter().
*/
function testShippedFileURL() {
// Test generating a URL to a shipped file (i.e. a file that is part of
// Drupal core, a module or a theme, for example a JavaScript file).
// Test alteration of file URLs to use a CDN.
\Drupal::state()->set('file_test.hook_file_url_alter', 'cdn');
$filepath = 'core/assets/vendor/jquery/jquery.min.js';
$url = file_create_url($filepath);
$this->assertEqual(FILE_URL_TEST_CDN_1 . '/' . $filepath, $url, 'Correctly generated a CDN URL for a shipped file.');
$filepath = 'core/misc/favicon.ico';
$url = file_create_url($filepath);
$this->assertEqual(FILE_URL_TEST_CDN_2 . '/' . $filepath, $url, 'Correctly generated a CDN URL for a shipped file.');
// Test alteration of file URLs to use root-relative URLs.
\Drupal::state()->set('file_test.hook_file_url_alter', 'root-relative');
$filepath = 'core/assets/vendor/jquery/jquery.min.js';
$url = file_create_url($filepath);
$this->assertEqual(base_path() . '/' . $filepath, $url, 'Correctly generated a root-relative URL for a shipped file.');
$filepath = 'core/misc/favicon.ico';
$url = file_create_url($filepath);
$this->assertEqual(base_path() . '/' . $filepath, $url, 'Correctly generated a root-relative URL for a shipped file.');
// Test alteration of file URLs to use protocol-relative URLs.
\Drupal::state()->set('file_test.hook_file_url_alter', 'protocol-relative');
$filepath = 'core/assets/vendor/jquery/jquery.min.js';
$url = file_create_url($filepath);
$this->assertEqual('/' . base_path() . '/' . $filepath, $url, 'Correctly generated a protocol-relative URL for a shipped file.');
$filepath = 'core/misc/favicon.ico';
$url = file_create_url($filepath);
$this->assertEqual('/' . base_path() . '/' . $filepath, $url, 'Correctly generated a protocol-relative URL for a shipped file.');
// Test alteration of file URLs with query strings and/or fragment.
\Drupal::state()->delete('file_test.hook_file_url_alter');
$filepath = 'core/misc/favicon.ico';
$url = file_create_url($filepath . '?foo');
$this->assertEqual($GLOBALS['base_url'] . '/' . $filepath . '?foo=', $url, 'Correctly generated URL. The query string is present.');
$url = file_create_url($filepath . '?foo=bar');
$this->assertEqual($GLOBALS['base_url'] . '/' . $filepath . '?foo=bar', $url, 'Correctly generated URL. The query string is present.');
$url = file_create_url($filepath . '#v1.2');
$this->assertEqual($GLOBALS['base_url'] . '/' . $filepath . '#v1.2', $url, 'Correctly generated URL. The fragment is present.');
$url = file_create_url($filepath . '?foo=bar#v1.2');
$this->assertEqual($GLOBALS['base_url'] . '/' . $filepath . '?foo=bar#v1.2', $url, 'Correctly generated URL. The query string amd fragment is present.');
}
/**
* Tests the rewriting of public managed file URLs by hook_file_url_alter().
*/
function testPublicManagedFileURL() {
// Test generating a URL to a managed file.
// Test alteration of file URLs to use a CDN.
\Drupal::state()->set('file_test.hook_file_url_alter', 'cdn');
$uri = $this->createUri();
$url = file_create_url($uri);
$public_directory_path = \Drupal::service('stream_wrapper_manager')->getViaScheme('public')->getDirectoryPath();
$this->assertEqual(FILE_URL_TEST_CDN_2 . '/' . $public_directory_path . '/' . drupal_basename($uri), $url, 'Correctly generated a CDN URL for a created file.');
// Test alteration of file URLs to use root-relative URLs.
\Drupal::state()->set('file_test.hook_file_url_alter', 'root-relative');
$uri = $this->createUri();
$url = file_create_url($uri);
$this->assertEqual(base_path() . '/' . $public_directory_path . '/' . drupal_basename($uri), $url, 'Correctly generated a root-relative URL for a created file.');
// Test alteration of file URLs to use a protocol-relative URLs.
\Drupal::state()->set('file_test.hook_file_url_alter', 'protocol-relative');
$uri = $this->createUri();
$url = file_create_url($uri);
$this->assertEqual('/' . base_path() . '/' . $public_directory_path . '/' . drupal_basename($uri), $url, 'Correctly generated a protocol-relative URL for a created file.');
}
/**
* Test file_url_transform_relative().
*/
function testRelativeFileURL() {
// Disable file_test.module's hook_file_url_alter() implementation.
\Drupal::state()->set('file_test.hook_file_url_alter', NULL);
// Create a mock Request for file_url_transform_relative().
$request = Request::create($GLOBALS['base_url']);
$this->container->get('request_stack')->push($request);
\Drupal::setContainer($this->container);
// Shipped file.
$filepath = 'core/assets/vendor/jquery/jquery.min.js';
$url = file_create_url($filepath);
$this->assertIdentical(base_path() . $filepath, file_url_transform_relative($url));
// Managed file.
$uri = $this->createUri();
$url = file_create_url($uri);
$public_directory_path = \Drupal::service('stream_wrapper_manager')->getViaScheme('public')->getDirectoryPath();
$this->assertIdentical(base_path() . $public_directory_path . '/' . rawurlencode(drupal_basename($uri)), file_url_transform_relative($url));
}
}

View file

@ -0,0 +1,106 @@
<?php
namespace Drupal\KernelTests\Core\Form;
use Drupal\Core\Form\FormInterface;
use Drupal\Core\Form\FormStateInterface;
use Drupal\KernelTests\KernelTestBase;
use Drupal\user\Entity\User;
use Symfony\Component\HttpFoundation\Request;
/**
* Ensures that form actions can't be tricked into sending to external URLs.
*
* @group system
*/
class ExternalFormUrlTest extends KernelTestBase implements FormInterface {
/**
* {@inheritdoc}
*/
public static $modules = ['user', 'system'];
/**
* {@inheritdoc}
*/
public function getFormId() {
return 'external_form_url_test';
}
/**
* {@inheritdoc}
*/
public function buildForm(array $form, FormStateInterface $form_state) {
$form['something'] = [
'#type' => 'textfield',
'#title' => 'What do you think?',
];
return $form;
}
/**
* {@inheritdoc}
*/
public function validateForm(array &$form, FormStateInterface $form_state) {}
/**
* {@inheritdoc}
*/
public function submitForm(array &$form, FormStateInterface $form_state) {}
/**
* {@inheritdoc}
*/
protected function setUp() {
parent::setUp();
$this->installSchema('system', ['key_value_expire', 'sequences']);
$this->installEntitySchema('user');
$test_user = User::create([
'name' => 'foobar',
'mail' => 'foobar@example.com',
]);
$test_user->save();
\Drupal::service('current_user')->setAccount($test_user);
}
/**
* Tests form behaviour.
*/
public function testActionUrlBehavior() {
// Create a new request which has a request uri with multiple leading
// slashes and make it the master request.
$request_stack = \Drupal::service('request_stack');
/** @var \Symfony\Component\HttpFoundation\RequestStack $original_request */
$original_request = $request_stack->pop();
// Just request some more so there is no request left.
$request_stack->pop();
$request_stack->pop();
$request = Request::create($original_request->getSchemeAndHttpHost() . '//example.org');
$request_stack->push($request);
$form = \Drupal::formBuilder()->getForm($this);
$markup = \Drupal::service('renderer')->renderRoot($form);
$this->setRawContent($markup);
$elements = $this->xpath('//form/@action');
$action = (string) $elements[0];
$this->assertEqual($original_request->getSchemeAndHttpHost() . '//example.org', $action);
// Create a new request which has a request uri with a single leading slash
// and make it the master request.
$request_stack = \Drupal::service('request_stack');
$original_request = $request_stack->pop();
$request = Request::create($original_request->getSchemeAndHttpHost() . '/example.org');
$request_stack->push($request);
$form = \Drupal::formBuilder()->getForm($this);
$markup = \Drupal::service('renderer')->renderRoot($form);
$this->setRawContent($markup);
$elements = $this->xpath('//form/@action');
$action = (string) $elements[0];
$this->assertEqual('/example.org', $action);
}
}

View file

@ -0,0 +1,104 @@
<?php
namespace Drupal\KernelTests\Core\Form;
use Drupal\Core\Form\FormState;
use Drupal\Core\Session\AnonymousUserSession;
use Drupal\Core\Session\UserSession;
use Drupal\KernelTests\KernelTestBase;
/**
* Tests \Drupal::formBuilder()->setCache() and
* \Drupal::formBuilder()->getCache().
*
* @group Form
*/
class FormCacheTest extends KernelTestBase {
/**
* Modules to enable.
*
* @var array
*/
public static $modules = array('system', 'user');
/**
* @var string
*/
protected $formBuildId;
/**
* @var array
*/
protected $form;
/**
* @var array
*/
protected $formState;
protected function setUp() {
parent::setUp();
$this->installSchema('system', array('key_value_expire'));
$this->formBuildId = $this->randomMachineName();
$this->form = array(
'#property' => $this->randomMachineName(),
);
$this->formState = new FormState();
$this->formState->set('example', $this->randomMachineName());
}
/**
* Tests the form cache with a logged-in user.
*/
function testCacheToken() {
\Drupal::currentUser()->setAccount(new UserSession(array('uid' => 1)));
\Drupal::formBuilder()->setCache($this->formBuildId, $this->form, $this->formState);
$cached_form_state = new FormState();
$cached_form = \Drupal::formBuilder()->getCache($this->formBuildId, $cached_form_state);
$this->assertEqual($this->form['#property'], $cached_form['#property']);
$this->assertTrue(!empty($cached_form['#cache_token']), 'Form has a cache token');
$this->assertEqual($this->formState->get('example'), $cached_form_state->get('example'));
// Test that the form cache isn't loaded when the session/token has changed.
// Change the private key. (We cannot change the session ID because this
// will break the parent site test runner batch.)
\Drupal::state()->set('system.private_key', 'invalid');
$cached_form_state = new FormState();
$cached_form = \Drupal::formBuilder()->getCache($this->formBuildId, $cached_form_state);
$this->assertFalse($cached_form, 'No form returned from cache');
$cached_form_state_example = $cached_form_state->get('example');
$this->assertTrue(empty($cached_form_state_example));
// Test that loading the cache with a different form_id fails.
$wrong_form_build_id = $this->randomMachineName(9);
$cached_form_state = new FormState();
$this->assertFalse(\Drupal::formBuilder()->getCache($wrong_form_build_id, $cached_form_state), 'No form returned from cache');
$cached_form_state_example = $cached_form_state->get('example');
$this->assertTrue(empty($cached_form_state_example), 'Cached form state was not loaded');
}
/**
* Tests the form cache without a logged-in user.
*/
function testNoCacheToken() {
// Switch to a anonymous user account.
$account_switcher = \Drupal::service('account_switcher');
$account_switcher->switchTo(new AnonymousUserSession());
$this->formState->set('example', $this->randomMachineName());
\Drupal::formBuilder()->setCache($this->formBuildId, $this->form, $this->formState);
$cached_form_state = new FormState();
$cached_form = \Drupal::formBuilder()->getCache($this->formBuildId, $cached_form_state);
$this->assertEqual($this->form['#property'], $cached_form['#property']);
$this->assertTrue(empty($cached_form['#cache_token']), 'Form has no cache token');
$this->assertEqual($this->formState->get('example'), $cached_form_state->get('example'));
// Restore user account.
$account_switcher->switchBack();
}
}

View file

@ -0,0 +1,104 @@
<?php
namespace Drupal\KernelTests\Core\Form;
use Drupal\Core\Form\FormInterface;
use Drupal\Core\Form\FormState;
use Drupal\Core\Form\FormStateInterface;
use Drupal\KernelTests\KernelTestBase;
/**
* Tests automatically added form handlers.
*
* @group Form
*/
class FormDefaultHandlersTest extends KernelTestBase implements FormInterface {
/**
* Modules to enable.
*
* @var array
*/
public static $modules = array('system');
/**
* {@inheritdoc}
*/
protected function setUp() {
parent::setUp();
$this->installSchema('system', ['key_value_expire']);
}
/**
* {@inheritdoc}
*/
public function getFormId() {
return 'test_form_handlers';
}
/**
* {@inheritdoc}
*/
public function buildForm(array $form, FormStateInterface $form_state) {
$form['#validate'][] = '::customValidateForm';
$form['#submit'][] = '::customSubmitForm';
$form['submit'] = array('#type' => 'submit', '#value' => 'Save');
return $form;
}
/**
* {@inheritdoc}
*/
public function customValidateForm(array &$form, FormStateInterface $form_state) {
$test_handlers = $form_state->get('test_handlers');
$test_handlers['validate'][] = __FUNCTION__;
$form_state->set('test_handlers', $test_handlers);
}
/**
* {@inheritdoc}
*/
public function validateForm(array &$form, FormStateInterface $form_state) {
$test_handlers = $form_state->get('test_handlers');
$test_handlers['validate'][] = __FUNCTION__;
$form_state->set('test_handlers', $test_handlers);
}
/**
* {@inheritdoc}
*/
public function customSubmitForm(array &$form, FormStateInterface $form_state) {
$test_handlers = $form_state->get('test_handlers');
$test_handlers['submit'][] = __FUNCTION__;
$form_state->set('test_handlers', $test_handlers);
}
/**
* {@inheritdoc}
*/
public function submitForm(array &$form, FormStateInterface $form_state) {
$test_handlers = $form_state->get('test_handlers');
$test_handlers['submit'][] = __FUNCTION__;
$form_state->set('test_handlers', $test_handlers);
}
/**
* Tests that default handlers are added even if custom are specified.
*/
function testDefaultAndCustomHandlers() {
$form_state = new FormState();
$form_builder = $this->container->get('form_builder');
$form_builder->submitForm($this, $form_state);
$handlers = $form_state->get('test_handlers');
$this->assertIdentical(count($handlers['validate']), 2);
$this->assertIdentical($handlers['validate'][0], 'customValidateForm');
$this->assertIdentical($handlers['validate'][1], 'validateForm');
$this->assertIdentical(count($handlers['submit']), 2);
$this->assertIdentical($handlers['submit'][0], 'customSubmitForm');
$this->assertIdentical($handlers['submit'][1], 'submitForm');
}
}

View file

@ -0,0 +1,86 @@
<?php
namespace Drupal\KernelTests\Core\Form;
use Drupal\Core\Form\FormInterface;
use Drupal\Core\Form\FormState;
use Drupal\Core\Form\FormStateInterface;
use Drupal\KernelTests\KernelTestBase;
/**
* Tests detection of triggering_element for programmed form submissions.
*
* @group Form
*/
class TriggeringElementProgrammedTest extends KernelTestBase implements FormInterface {
/**
* {@inheritdoc}
*/
public function getFormId() {
return 'triggering_element_programmed_form';
}
/**
* {@inheritdoc}
*/
public function buildForm(array $form, FormStateInterface $form_state) {
$form['one'] = array(
'#type' => 'textfield',
'#title' => 'One',
'#required' => TRUE,
);
$form['two'] = array(
'#type' => 'textfield',
'#title' => 'Two',
'#required' => TRUE,
);
$form['actions'] = array('#type' => 'actions');
$user_input = $form_state->getUserInput();
$form['actions']['submit'] = array(
'#type' => 'submit',
'#value' => 'Save',
'#limit_validation_errors' => array(
array($user_input['section']),
),
// Required for #limit_validation_errors.
'#submit' => array(array($this, 'submitForm')),
);
return $form;
}
/**
* {@inheritdoc}
*/
public function validateForm(array &$form, FormStateInterface $form_state) {
// Verify that the only submit button was recognized as triggering_element.
$this->assertEqual($form['actions']['submit']['#array_parents'], $form_state->getTriggeringElement()['#array_parents']);
}
/**
* {@inheritdoc}
*/
public function submitForm(array &$form, FormStateInterface $form_state) {
}
/**
* Tests that #limit_validation_errors of the only submit button takes effect.
*/
function testLimitValidationErrors() {
// Programmatically submit the form.
$form_state = new FormState();
$form_state->setValue('section', 'one');
$form_builder = $this->container->get('form_builder');
$form_builder->submitForm($this, $form_state);
// Verify that only the specified section was validated.
$errors = $form_state->getErrors();
$this->assertTrue(isset($errors['one']), "Section 'one' was validated.");
$this->assertFalse(isset($errors['two']), "Section 'two' was not validated.");
// Verify that there are only values for the specified section.
$this->assertTrue($form_state->hasValue('one'), "Values for section 'one' found.");
$this->assertFalse($form_state->hasValue('two'), "Values for section 'two' not found.");
}
}

View file

@ -0,0 +1,84 @@
<?php
namespace Drupal\KernelTests\Core\HttpKernel;
use Drupal\Core\DependencyInjection\ContainerBuilder;
use Drupal\Core\DependencyInjection\ServiceModifierInterface;
use Drupal\KernelTests\KernelTestBase;
use Symfony\Component\HttpFoundation\Request;
use Symfony\Component\HttpFoundation\Response;
/**
* Tests CORS provided by Drupal.
*
* @see sites/default/default.services.yml
* @see \Asm89\Stack\Cors
* @see \Asm89\Stack\CorsService
*
* @group Http
*/
class CorsIntegrationTest extends KernelTestBase implements ServiceModifierInterface {
/**
* The cors container configuration.
*
* @var null|array
*/
protected $corsConfig = NULL;
/**
* {@inheritdoc}
*/
public static $modules = ['system', 'test_page_test'];
protected function setUp() {
parent::setUp();
$this->installSchema('system', 'router');
\Drupal::service('router.builder')->rebuild();
}
public function testCrossSiteRequest() {
// Test default parameters.
$cors_config = $this->container->getParameter('cors.config');
$this->assertSame(FALSE, $cors_config['enabled']);
$this->assertSame([], $cors_config['allowedHeaders']);
$this->assertSame([], $cors_config['allowedMethods']);
$this->assertSame(['*'], $cors_config['allowedOrigins']);
$this->assertSame(FALSE, $cors_config['exposedHeaders']);
$this->assertSame(FALSE, $cors_config['maxAge']);
$this->assertSame(FALSE, $cors_config['supportsCredentials']);
// Configure the CORS stack to allow a specific set of origins, but don't
// specify an origin header.
$request = Request::create('/test-page');
$request->headers->set('Origin', '');
$cors_config['enabled'] = TRUE;
$cors_config['allowedOrigins'] = ['http://example.com'];
$this->corsConfig = $cors_config;
$this->container->get('kernel')->rebuildContainer();
/** @var \Symfony\Component\HttpFoundation\Response $response */
$response = $this->container->get('http_kernel')->handle($request);
$this->assertEquals(Response::HTTP_FORBIDDEN, $response->getStatusCode());
$this->assertEquals('Not allowed.', $response->getContent());
// Specify a valid origin.
$request->headers->set('Origin', 'http://example.com');
$response = $this->container->get('http_kernel')->handle($request);
$this->assertEquals(Response::HTTP_OK, $response->getStatusCode());
}
/**
* {@inheritdoc}
*/
public function alter(ContainerBuilder $container) {
if (isset($this->corsConfig)) {
$container->setParameter('cors.config', $this->corsConfig);
}
}
}

View file

@ -0,0 +1,64 @@
<?php
namespace Drupal\KernelTests\Core\HttpKernel;
use Drupal\Core\Url;
use Drupal\KernelTests\KernelTestBase;
use Symfony\Component\HttpFoundation\Request;
use Symfony\Component\HttpKernel\HttpKernelInterface;
/**
* Tests the stacked kernel functionality.
*
* @group Routing
*/
class StackKernelIntegrationTest extends KernelTestBase {
/**
* Modules to enable.
*
* @var array
*/
public static $modules = array('httpkernel_test', 'system');
/**
* {@inheritdoc}
*/
protected function setUp() {
parent::setUp();
\Drupal::service('router.builder')->rebuild();
}
/**
* Tests a request.
*/
public function testRequest() {
$request = Request::create((new Url('httpkernel_test.empty'))->toString());
/** @var \Symfony\Component\HttpKernel\HttpKernelInterface $http_kernel */
$http_kernel = \Drupal::service('http_kernel');
$http_kernel->handle($request, HttpKernelInterface::MASTER_REQUEST, FALSE);
$this->assertEqual($request->attributes->get('_hello'), 'world');
$this->assertEqual($request->attributes->get('_previous_optional_argument'), 'test_argument');
}
/**
* Tests that late middlewares are automatically flagged lazy.
*/
public function testLazyLateMiddlewares() {
$this->assertFalse($this->container->getDefinition('http_middleware.reverse_proxy')->isLazy(), 'lazy flag on http_middleware.reverse_proxy definition is not set');
$this->assertFalse($this->container->getDefinition('http_middleware.kernel_pre_handle')->isLazy(), 'lazy flag on http_middleware.kernel_pre_handle definition is not set');
$this->assertFalse($this->container->getDefinition('http_middleware.session')->isLazy(), 'lazy flag on http_middleware.session definition is not set');
$this->assertFalse($this->container->getDefinition('http_kernel.basic')->isLazy(), 'lazy flag on http_kernel.basic definition is not set');
\Drupal::service('module_installer')->install(['page_cache']);
$this->container = \Drupal::service('kernel')->rebuildContainer();
$this->assertFalse($this->container->getDefinition('http_middleware.reverse_proxy')->isLazy(), 'lazy flag on http_middleware.reverse_proxy definition is not set');
$this->assertFalse($this->container->getDefinition('http_middleware.page_cache')->isLazy(), 'lazy flag on http_middleware.page_cache definition is not set');
$this->assertTrue($this->container->getDefinition('http_middleware.kernel_pre_handle')->isLazy(), 'lazy flag on http_middleware.kernel_pre_handle definition is automatically set if page_cache is enabled.');
$this->assertTrue($this->container->getDefinition('http_middleware.session')->isLazy(), 'lazy flag on http_middleware.session definition is automatically set if page_cache is enabled.');
$this->assertTrue($this->container->getDefinition('http_kernel.basic')->isLazy(), 'lazy flag on http_kernel.basic definition is automatically set if page_cache is enabled.');
}
}

View file

@ -0,0 +1,529 @@
<?php
namespace Drupal\KernelTests\Core\Image;
use Drupal\Core\Image\ImageInterface;
use Drupal\Component\Utility\SafeMarkup;
use Drupal\Core\Site\Settings;
use Drupal\KernelTests\KernelTestBase;
/**
* Tests that core image manipulations work properly: scale, resize, rotate,
* crop, scale and crop, and desaturate.
*
* @group Image
*/
class ToolkitGdTest extends KernelTestBase {
/**
* The image factory service.
*
* @var \Drupal\Core\Image\ImageFactory
*/
protected $imageFactory;
// Colors that are used in testing.
protected $black = array(0, 0, 0, 0);
protected $red = array(255, 0, 0, 0);
protected $green = array(0, 255, 0, 0);
protected $blue = array(0, 0, 255, 0);
protected $yellow = array(255, 255, 0, 0);
protected $white = array(255, 255, 255, 0);
protected $transparent = array(0, 0, 0, 127);
// Used as rotate background colors.
protected $fuchsia = array(255, 0, 255, 0);
protected $rotateTransparent = array(255, 255, 255, 127);
protected $width = 40;
protected $height = 20;
/**
* Modules to enable.
*
* @var array
*/
public static $modules = array('system', 'simpletest');
/**
* {@inheritdoc}
*/
protected function setUp() {
parent::setUp();
// Set the image factory service.
$this->imageFactory = $this->container->get('image.factory');
}
protected function checkRequirements() {
// GD2 support is available.
if (!function_exists('imagegd2')) {
return array(
'Image manipulations for the GD toolkit cannot run because the GD toolkit is not available.',
);
}
return parent::checkRequirements();
}
/**
* Function to compare two colors by RGBa.
*/
function colorsAreEqual($color_a, $color_b) {
// Fully transparent pixels are equal, regardless of RGB.
if ($color_a[3] == 127 && $color_b[3] == 127) {
return TRUE;
}
foreach ($color_a as $key => $value) {
if ($color_b[$key] != $value) {
return FALSE;
}
}
return TRUE;
}
/**
* Function for finding a pixel's RGBa values.
*/
function getPixelColor(ImageInterface $image, $x, $y) {
$toolkit = $image->getToolkit();
$color_index = imagecolorat($toolkit->getResource(), $x, $y);
$transparent_index = imagecolortransparent($toolkit->getResource());
if ($color_index == $transparent_index) {
return array(0, 0, 0, 127);
}
return array_values(imagecolorsforindex($toolkit->getResource(), $color_index));
}
/**
* Since PHP can't visually check that our images have been manipulated
* properly, build a list of expected color values for each of the corners and
* the expected height and widths for the final images.
*/
function testManipulations() {
// Test that the image factory is set to use the GD toolkit.
$this->assertEqual($this->imageFactory->getToolkitId(), 'gd', 'The image factory is set to use the \'gd\' image toolkit.');
// Test the list of supported extensions.
$expected_extensions = ['png', 'gif', 'jpeg', 'jpg', 'jpe'];
$supported_extensions = $this->imageFactory->getSupportedExtensions();
$this->assertEqual($expected_extensions, array_intersect($expected_extensions, $supported_extensions));
// Test that the supported extensions map to correct internal GD image
// types.
$expected_image_types = [
'png' => IMAGETYPE_PNG,
'gif' => IMAGETYPE_GIF,
'jpeg' => IMAGETYPE_JPEG,
'jpg' => IMAGETYPE_JPEG,
'jpe' => IMAGETYPE_JPEG
];
$image = $this->imageFactory->get();
foreach ($expected_image_types as $extension => $expected_image_type) {
$image_type = $image->getToolkit()->extensionToImageType($extension);
$this->assertSame($expected_image_type, $image_type);
}
// Typically the corner colors will be unchanged. These colors are in the
// order of top-left, top-right, bottom-right, bottom-left.
$default_corners = array($this->red, $this->green, $this->blue, $this->transparent);
// A list of files that will be tested.
$files = array(
'image-test.png',
'image-test.gif',
'image-test-no-transparency.gif',
'image-test.jpg',
);
// Setup a list of tests to perform on each type.
$operations = array(
'resize' => array(
'function' => 'resize',
'arguments' => array('width' => 20, 'height' => 10),
'width' => 20,
'height' => 10,
'corners' => $default_corners,
),
'scale_x' => array(
'function' => 'scale',
'arguments' => array('width' => 20),
'width' => 20,
'height' => 10,
'corners' => $default_corners,
),
'scale_y' => array(
'function' => 'scale',
'arguments' => array('height' => 10),
'width' => 20,
'height' => 10,
'corners' => $default_corners,
),
'upscale_x' => array(
'function' => 'scale',
'arguments' => array('width' => 80, 'upscale' => TRUE),
'width' => 80,
'height' => 40,
'corners' => $default_corners,
),
'upscale_y' => array(
'function' => 'scale',
'arguments' => array('height' => 40, 'upscale' => TRUE),
'width' => 80,
'height' => 40,
'corners' => $default_corners,
),
'crop' => array(
'function' => 'crop',
'arguments' => array('x' => 12, 'y' => 4, 'width' => 16, 'height' => 12),
'width' => 16,
'height' => 12,
'corners' => array_fill(0, 4, $this->white),
),
'scale_and_crop' => array(
'function' => 'scale_and_crop',
'arguments' => array('width' => 10, 'height' => 8),
'width' => 10,
'height' => 8,
'corners' => array_fill(0, 4, $this->black),
),
'convert_jpg' => array(
'function' => 'convert',
'width' => 40,
'height' => 20,
'arguments' => array('extension' => 'jpeg'),
'corners' => $default_corners,
),
'convert_gif' => array(
'function' => 'convert',
'width' => 40,
'height' => 20,
'arguments' => array('extension' => 'gif'),
'corners' => $default_corners,
),
'convert_png' => array(
'function' => 'convert',
'width' => 40,
'height' => 20,
'arguments' => array('extension' => 'png'),
'corners' => $default_corners,
),
);
// Systems using non-bundled GD2 don't have imagerotate. Test if available.
if (function_exists('imagerotate')) {
$operations += array(
'rotate_5' => array(
'function' => 'rotate',
'arguments' => array('degrees' => 5, 'background' => '#FF00FF'), // Fuchsia background.
'width' => 41,
'height' => 23,
'corners' => array_fill(0, 4, $this->fuchsia),
),
'rotate_90' => array(
'function' => 'rotate',
'arguments' => array('degrees' => 90, 'background' => '#FF00FF'), // Fuchsia background.
'width' => 20,
'height' => 40,
'corners' => array($this->transparent, $this->red, $this->green, $this->blue),
),
'rotate_transparent_5' => array(
'function' => 'rotate',
'arguments' => array('degrees' => 5),
'width' => 41,
'height' => 23,
'corners' => array_fill(0, 4, $this->rotateTransparent),
),
'rotate_transparent_90' => array(
'function' => 'rotate',
'arguments' => array('degrees' => 90),
'width' => 20,
'height' => 40,
'corners' => array($this->transparent, $this->red, $this->green, $this->blue),
),
);
}
// Systems using non-bundled GD2 don't have imagefilter. Test if available.
if (function_exists('imagefilter')) {
$operations += array(
'desaturate' => array(
'function' => 'desaturate',
'arguments' => array(),
'height' => 20,
'width' => 40,
// Grayscale corners are a bit funky. Each of the corners are a shade of
// gray. The values of these were determined simply by looking at the
// final image to see what desaturated colors end up being.
'corners' => array(
array_fill(0, 3, 76) + array(3 => 0),
array_fill(0, 3, 149) + array(3 => 0),
array_fill(0, 3, 29) + array(3 => 0),
array_fill(0, 3, 225) + array(3 => 127)
),
),
);
}
// Prepare a directory for test file results.
$directory = Settings::get('file_public_path') . '/imagetest';
file_prepare_directory($directory, FILE_CREATE_DIRECTORY);
foreach ($files as $file) {
foreach ($operations as $op => $values) {
// Load up a fresh image.
$image = $this->imageFactory->get(drupal_get_path('module', 'simpletest') . '/files/' . $file);
$toolkit = $image->getToolkit();
if (!$image->isValid()) {
$this->fail(SafeMarkup::format('Could not load image %file.', array('%file' => $file)));
continue 2;
}
$image_original_type = $image->getToolkit()->getType();
// All images should be converted to truecolor when loaded.
$image_truecolor = imageistruecolor($toolkit->getResource());
$this->assertTrue($image_truecolor, SafeMarkup::format('Image %file after load is a truecolor image.', array('%file' => $file)));
// Store the original GD resource.
$old_res = $toolkit->getResource();
// Perform our operation.
$image->apply($values['function'], $values['arguments']);
// If the operation replaced the resource, check that the old one has
// been destroyed.
$new_res = $toolkit->getResource();
if ($new_res !== $old_res) {
$this->assertFalse(is_resource($old_res), SafeMarkup::format("'%operation' destroyed the original resource.", ['%operation' => $values['function']]));
}
// To keep from flooding the test with assert values, make a general
// value for whether each group of values fail.
$correct_dimensions_real = TRUE;
$correct_dimensions_object = TRUE;
if (imagesy($toolkit->getResource()) != $values['height'] || imagesx($toolkit->getResource()) != $values['width']) {
$correct_dimensions_real = FALSE;
}
// Check that the image object has an accurate record of the dimensions.
if ($image->getWidth() != $values['width'] || $image->getHeight() != $values['height']) {
$correct_dimensions_object = FALSE;
}
$file_path = $directory . '/' . $op . image_type_to_extension($image->getToolkit()->getType());
$image->save($file_path);
$this->assertTrue($correct_dimensions_real, SafeMarkup::format('Image %file after %action action has proper dimensions.', array('%file' => $file, '%action' => $op)));
$this->assertTrue($correct_dimensions_object, SafeMarkup::format('Image %file object after %action action is reporting the proper height and width values.', array('%file' => $file, '%action' => $op)));
// JPEG colors will always be messed up due to compression. So we skip
// these tests if the original or the result is in jpeg format.
if ($image->getToolkit()->getType() != IMAGETYPE_JPEG && $image_original_type != IMAGETYPE_JPEG) {
// Now check each of the corners to ensure color correctness.
foreach ($values['corners'] as $key => $corner) {
// The test gif that does not have transparency color set is a
// special case.
if ($file === 'image-test-no-transparency.gif') {
if ($op == 'desaturate') {
// For desaturating, keep the expected color from the test
// data, but set alpha channel to fully opaque.
$corner[3] = 0;
}
elseif ($corner === $this->transparent) {
// Set expected pixel to yellow where the others have
// transparent.
$corner = $this->yellow;
}
}
// Get the location of the corner.
switch ($key) {
case 0:
$x = 0;
$y = 0;
break;
case 1:
$x = $image->getWidth() - 1;
$y = 0;
break;
case 2:
$x = $image->getWidth() - 1;
$y = $image->getHeight() - 1;
break;
case 3:
$x = 0;
$y = $image->getHeight() - 1;
break;
}
$color = $this->getPixelColor($image, $x, $y);
// We also skip the color test for transparency for gif <-> png
// conversion. The convert operation cannot handle that correctly.
if ($image->getToolkit()->getType() == $image_original_type || $corner != $this->transparent) {
$correct_colors = $this->colorsAreEqual($color, $corner);
$this->assertTrue($correct_colors, SafeMarkup::format('Image %file object after %action action has the correct color placement at corner %corner.',
array('%file' => $file, '%action' => $op, '%corner' => $key)));
}
}
}
// Check that saved image reloads without raising PHP errors.
$image_reloaded = $this->imageFactory->get($file_path);
$resource = $image_reloaded->getToolkit()->getResource();
}
}
// Test creation of image from scratch, and saving to storage.
foreach (array(IMAGETYPE_PNG, IMAGETYPE_GIF, IMAGETYPE_JPEG) as $type) {
$image = $this->imageFactory->get();
$image->createNew(50, 20, image_type_to_extension($type, FALSE), '#ffff00');
$file = 'from_null' . image_type_to_extension($type);
$file_path = $directory . '/' . $file ;
$this->assertEqual(50, $image->getWidth(), SafeMarkup::format('Image file %file has the correct width.', array('%file' => $file)));
$this->assertEqual(20, $image->getHeight(), SafeMarkup::format('Image file %file has the correct height.', array('%file' => $file)));
$this->assertEqual(image_type_to_mime_type($type), $image->getMimeType(), SafeMarkup::format('Image file %file has the correct MIME type.', array('%file' => $file)));
$this->assertTrue($image->save($file_path), SafeMarkup::format('Image %file created anew from a null image was saved.', array('%file' => $file)));
// Reload saved image.
$image_reloaded = $this->imageFactory->get($file_path);
if (!$image_reloaded->isValid()) {
$this->fail(SafeMarkup::format('Could not load image %file.', array('%file' => $file)));
continue;
}
$this->assertEqual(50, $image_reloaded->getWidth(), SafeMarkup::format('Image file %file has the correct width.', array('%file' => $file)));
$this->assertEqual(20, $image_reloaded->getHeight(), SafeMarkup::format('Image file %file has the correct height.', array('%file' => $file)));
$this->assertEqual(image_type_to_mime_type($type), $image_reloaded->getMimeType(), SafeMarkup::format('Image file %file has the correct MIME type.', array('%file' => $file)));
if ($image_reloaded->getToolkit()->getType() == IMAGETYPE_GIF) {
$this->assertEqual('#ffff00', $image_reloaded->getToolkit()->getTransparentColor(), SafeMarkup::format('Image file %file has the correct transparent color channel set.', array('%file' => $file)));
}
else {
$this->assertEqual(NULL, $image_reloaded->getToolkit()->getTransparentColor(), SafeMarkup::format('Image file %file has no color channel set.', array('%file' => $file)));
}
}
// Test failures of the 'create_new' operation.
$image = $this->imageFactory->get();
$image->createNew(-50, 20);
$this->assertFalse($image->isValid(), 'CreateNew with negative width fails.');
$image->createNew(50, 20, 'foo');
$this->assertFalse($image->isValid(), 'CreateNew with invalid extension fails.');
$image->createNew(50, 20, 'gif', '#foo');
$this->assertFalse($image->isValid(), 'CreateNew with invalid color hex string fails.');
$image->createNew(50, 20, 'gif', '#ff0000');
$this->assertTrue($image->isValid(), 'CreateNew with valid arguments validates the Image.');
}
/**
* Tests that GD resources are freed from memory.
*/
public function testResourceDestruction() {
// Test that an Image object going out of scope releases its GD resource.
$image = $this->imageFactory->get(drupal_get_path('module', 'simpletest') . '/files/image-test.png');
$res = $image->getToolkit()->getResource();
$this->assertTrue(is_resource($res), 'Successfully loaded image resource.');
$image = NULL;
$this->assertFalse(is_resource($res), 'Image resource was destroyed after losing scope.');
// Test that 'create_new' operation does not leave orphaned GD resources.
$image = $this->imageFactory->get(drupal_get_path('module', 'simpletest') . '/files/image-test.png');
$old_res = $image->getToolkit()->getResource();
// Check if resource has been created successfully.
$this->assertTrue(is_resource($old_res));
$image->createNew(20, 20);
$new_res = $image->getToolkit()->getResource();
// Check if the original resource has been destroyed.
$this->assertFalse(is_resource($old_res));
// Check if a new resource has been created successfully.
$this->assertTrue(is_resource($new_res));
}
/**
* Tests for GIF images with transparency.
*/
function testGifTransparentImages() {
// Prepare a directory for test file results.
$directory = Settings::get('file_public_path') . '/imagetest';
file_prepare_directory($directory, FILE_CREATE_DIRECTORY);
// Test loading an indexed GIF image with transparent color set.
// Color at top-right pixel should be fully transparent.
$file = 'image-test-transparent-indexed.gif';
$image = $this->imageFactory->get(drupal_get_path('module', 'simpletest') . '/files/' . $file);
$resource = $image->getToolkit()->getResource();
$color_index = imagecolorat($resource, $image->getWidth() - 1, 0);
$color = array_values(imagecolorsforindex($resource, $color_index));
$this->assertEqual($this->rotateTransparent, $color, "Image {$file} after load has full transparent color at corner 1.");
// Test deliberately creating a GIF image with no transparent color set.
// Color at top-right pixel should be fully transparent while in memory,
// fully opaque after flushing image to file.
$file = 'image-test-no-transparent-color-set.gif';
$file_path = $directory . '/' . $file ;
// Create image.
$image = $this->imageFactory->get();
$image->createNew(50, 20, 'gif', NULL);
$resource = $image->getToolkit()->getResource();
$color_index = imagecolorat($resource, $image->getWidth() - 1, 0);
$color = array_values(imagecolorsforindex($resource, $color_index));
$this->assertEqual($this->rotateTransparent, $color, "New GIF image with no transparent color set after creation has full transparent color at corner 1.");
// Save image.
$this->assertTrue($image->save($file_path), "New GIF image {$file} was saved.");
// Reload image.
$image_reloaded = $this->imageFactory->get($file_path);
$resource = $image_reloaded->getToolkit()->getResource();
$color_index = imagecolorat($resource, $image_reloaded->getWidth() - 1, 0);
$color = array_values(imagecolorsforindex($resource, $color_index));
// Check explicitly for alpha == 0 as the rest of the color has been
// compressed and may have slight difference from full white.
$this->assertEqual(0, $color[3], "New GIF image {$file} after reload has no transparent color at corner 1.");
// Test loading an image whose transparent color index is out of range.
// This image was generated by taking an initial image with a palette size
// of 6 colors, and setting the transparent color index to 6 (one higher
// than the largest allowed index), as follows:
// @code
// $image = imagecreatefromgif('core/modules/simpletest/files/image-test.gif');
// imagecolortransparent($image, 6);
// imagegif($image, 'core/modules/simpletest/files/image-test-transparent-out-of-range.gif');
// @endcode
// This allows us to test that an image with an out-of-range color index
// can be loaded correctly.
$file = 'image-test-transparent-out-of-range.gif';
$image = $this->imageFactory->get(drupal_get_path('module', 'simpletest') . '/files/' . $file);
$toolkit = $image->getToolkit();
if (!$image->isValid()) {
$this->fail(SafeMarkup::format('Could not load image %file.', array('%file' => $file)));
}
else {
// All images should be converted to truecolor when loaded.
$image_truecolor = imageistruecolor($toolkit->getResource());
$this->assertTrue($image_truecolor, SafeMarkup::format('Image %file after load is a truecolor image.', array('%file' => $file)));
}
}
/**
* Tests calling a missing image operation plugin.
*/
function testMissingOperation() {
// Test that the image factory is set to use the GD toolkit.
$this->assertEqual($this->imageFactory->getToolkitId(), 'gd', 'The image factory is set to use the \'gd\' image toolkit.');
// An image file that will be tested.
$file = 'image-test.png';
// Load up a fresh image.
$image = $this->imageFactory->get(drupal_get_path('module', 'simpletest') . '/files/' . $file);
if (!$image->isValid()) {
$this->fail(SafeMarkup::format('Could not load image %file.', array('%file' => $file)));
}
// Try perform a missing toolkit operation.
$this->assertFalse($image->apply('missing_op', array()), 'Calling a missing image toolkit operation plugin fails.');
}
}

View file

@ -0,0 +1,59 @@
<?php
namespace Drupal\KernelTests\Core\Installer;
use Drupal\Core\StringTranslation\Translator\FileTranslation;
use Drupal\KernelTests\KernelTestBase;
/**
* Tests for installer language support.
*
* @group Installer
*/
class InstallerLanguageTest extends KernelTestBase {
/**
* Tests that the installer can find translation files.
*/
function testInstallerTranslationFiles() {
// Different translation files would be found depending on which language
// we are looking for.
$expected_translation_files = array(
NULL => array('drupal-8.0.0-beta2.hu.po', 'drupal-8.0.0.de.po'),
'de' => array('drupal-8.0.0.de.po'),
'hu' => array('drupal-8.0.0-beta2.hu.po'),
'it' => array(),
);
// Hardcode the simpletest module location as we don't yet know where it is.
// @todo Remove as part of https://www.drupal.org/node/2186491
$file_translation = new FileTranslation('core/modules/simpletest/files/translations');
foreach ($expected_translation_files as $langcode => $files_expected) {
$files_found = $file_translation->findTranslationFiles($langcode);
$this->assertTrue(count($files_found) == count($files_expected), format_string('@count installer languages found.', array('@count' => count($files_expected))));
foreach ($files_found as $file) {
$this->assertTrue(in_array($file->filename, $files_expected), format_string('@file found.', array('@file' => $file->filename)));
}
}
}
/**
* Tests profile info caching in non-English languages.
*/
function testInstallerTranslationCache() {
require_once 'core/includes/install.inc';
// Prime the drupal_get_filename() static cache with the location of the
// testing profile as it is not the currently active profile and we don't
// yet have any cached way to retrieve its location.
// @todo Remove as part of https://www.drupal.org/node/2186491
drupal_get_filename('profile', 'testing', 'core/profiles/testing/testing.info.yml');
$info_en = install_profile_info('testing', 'en');
$info_nl = install_profile_info('testing', 'nl');
$this->assertFalse(in_array('locale', $info_en['dependencies']), 'Locale is not set when installing in English.');
$this->assertTrue(in_array('locale', $info_nl['dependencies']), 'Locale is set when installing in Dutch.');
}
}

View file

@ -0,0 +1,158 @@
<?php
namespace Drupal\KernelTests\Core\KeyValueStore;
use Drupal\Core\DependencyInjection\ContainerBuilder;
use Drupal\Core\KeyValueStore\KeyValueFactory;
/**
* Tests the key-value database storage.
*
* @group KeyValueStore
*/
class DatabaseStorageExpirableTest extends StorageTestBase {
/**
* Modules to enable.
*
* @var array
*/
public static $modules = array('system');
protected function setUp() {
parent::setUp();
$this->factory = 'keyvalue.expirable';
$this->installSchema('system', array('key_value_expire'));
}
/**
* {@inheritdoc}
*/
public function register(ContainerBuilder $container) {
parent::register($container);
$parameter[KeyValueFactory::DEFAULT_SETTING] = 'keyvalue.expirable.database';
$container->setParameter('factory.keyvalue.expirable', $parameter);
}
/**
* Tests CRUD functionality with expiration.
*/
public function testCRUDWithExpiration() {
$stores = $this->createStorage();
// Verify that an item can be stored with setWithExpire().
// Use a random expiration in each test.
$stores[0]->setWithExpire('foo', $this->objects[0], rand(500, 100000));
$this->assertIdenticalObject($this->objects[0], $stores[0]->get('foo'));
// Verify that the other collection is not affected.
$this->assertFalse($stores[1]->get('foo'));
// Verify that an item can be updated with setWithExpire().
$stores[0]->setWithExpire('foo', $this->objects[1], rand(500, 100000));
$this->assertIdenticalObject($this->objects[1], $stores[0]->get('foo'));
// Verify that the other collection is still not affected.
$this->assertFalse($stores[1]->get('foo'));
// Verify that the expirable data key is unique.
$stores[1]->setWithExpire('foo', $this->objects[2], rand(500, 100000));
$this->assertIdenticalObject($this->objects[1], $stores[0]->get('foo'));
$this->assertIdenticalObject($this->objects[2], $stores[1]->get('foo'));
// Verify that multiple items can be stored with setMultipleWithExpire().
$values = array(
'foo' => $this->objects[3],
'bar' => $this->objects[4],
);
$stores[0]->setMultipleWithExpire($values, rand(500, 100000));
$result = $stores[0]->getMultiple(array('foo', 'bar'));
foreach ($values as $j => $value) {
$this->assertIdenticalObject($value, $result[$j]);
}
// Verify that the other collection was not affected.
$this->assertIdenticalObject($stores[1]->get('foo'), $this->objects[2]);
$this->assertFalse($stores[1]->get('bar'));
// Verify that all items in a collection can be retrieved.
// Ensure that an item with the same name exists in the other collection.
$stores[1]->set('foo', $this->objects[5]);
$result = $stores[0]->getAll();
// Not using assertIdentical(), since the order is not defined for getAll().
$this->assertEqual(count($result), count($values));
foreach ($result as $key => $value) {
$this->assertEqual($values[$key], $value);
}
// Verify that all items in the other collection are different.
$result = $stores[1]->getAll();
$this->assertEqual($result, array('foo' => $this->objects[5]));
// Verify that multiple items can be deleted.
$stores[0]->deleteMultiple(array_keys($values));
$this->assertFalse($stores[0]->get('foo'));
$this->assertFalse($stores[0]->get('bar'));
$this->assertFalse($stores[0]->getMultiple(array('foo', 'bar')));
// Verify that the item in the other collection still exists.
$this->assertIdenticalObject($this->objects[5], $stores[1]->get('foo'));
// Test that setWithExpireIfNotExists() succeeds only the first time.
$key = $this->randomMachineName();
for ($i = 0; $i <= 1; $i++) {
// setWithExpireIfNotExists() should be TRUE the first time (when $i is
// 0) and FALSE the second time (when $i is 1).
$this->assertEqual(!$i, $stores[0]->setWithExpireIfNotExists($key, $this->objects[$i], rand(500, 100000)));
$this->assertIdenticalObject($this->objects[0], $stores[0]->get($key));
// Verify that the other collection is not affected.
$this->assertFalse($stores[1]->get($key));
}
// Remove the item and try to set it again.
$stores[0]->delete($key);
$stores[0]->setWithExpireIfNotExists($key, $this->objects[1], rand(500, 100000));
// This time it should succeed.
$this->assertIdenticalObject($this->objects[1], $stores[0]->get($key));
// Verify that the other collection is still not affected.
$this->assertFalse($stores[1]->get($key));
}
/**
* Tests data expiration.
*/
public function testExpiration() {
$stores = $this->createStorage();
$day = 604800;
// Set an item to expire in the past and another without an expiration.
$stores[0]->setWithExpire('yesterday', 'all my troubles seemed so far away', -1 * $day);
$stores[0]->set('troubles', 'here to stay');
// Only the non-expired item should be returned.
$this->assertFalse($stores[0]->has('yesterday'));
$this->assertFalse($stores[0]->get('yesterday'));
$this->assertTrue($stores[0]->has('troubles'));
$this->assertIdentical($stores[0]->get('troubles'), 'here to stay');
$this->assertIdentical(count($stores[0]->getMultiple(array('yesterday', 'troubles'))), 1);
// Store items set to expire in the past in various ways.
$stores[0]->setWithExpire($this->randomMachineName(), $this->objects[0], -7 * $day);
$stores[0]->setWithExpireIfNotExists($this->randomMachineName(), $this->objects[1], -5 * $day);
$stores[0]->setMultipleWithExpire(
array(
$this->randomMachineName() => $this->objects[2],
$this->randomMachineName() => $this->objects[3],
),
-3 * $day
);
$stores[0]->setWithExpireIfNotExists('yesterday', "you'd forgiven me", -1 * $day);
$stores[0]->setWithExpire('still', "'til we say we're sorry", 2 * $day);
// Ensure only non-expired items are retrieved.
$all = $stores[0]->getAll();
$this->assertIdentical(count($all), 2);
foreach (array('troubles', 'still') as $key) {
$this->assertTrue(!empty($all[$key]));
}
}
}

View file

@ -0,0 +1,37 @@
<?php
namespace Drupal\KernelTests\Core\KeyValueStore;
use Drupal\Core\DependencyInjection\ContainerBuilder;
use Drupal\Core\KeyValueStore\KeyValueFactory;
/**
* Tests the key-value database storage.
*
* @group KeyValueStore
*/
class DatabaseStorageTest extends StorageTestBase {
/**
* Modules to enable.
*
* @var array
*/
public static $modules = array('system');
protected function setUp() {
parent::setUp();
$this->installSchema('system', array('key_value'));
}
/**
* {@inheritdoc}
*/
public function register(ContainerBuilder $container) {
parent::register($container);
$parameter[KeyValueFactory::DEFAULT_SETTING] = 'keyvalue.database';
$container->setParameter('factory.keyvalue', $parameter);
}
}

View file

@ -0,0 +1,72 @@
<?php
namespace Drupal\KernelTests\Core\KeyValueStore;
use Drupal\Component\Serialization\PhpSerialize;
use Drupal\Core\Database\Database;
use Drupal\Core\KeyValueStore\DatabaseStorageExpirable;
use Drupal\KernelTests\KernelTestBase;
/**
* Tests garbage collection for the expirable key-value database storage.
*
* @group KeyValueStore
*/
class GarbageCollectionTest extends KernelTestBase {
/**
* Modules to enable.
*
* @var array
*/
public static $modules = array('system');
protected function setUp() {
parent::setUp();
// These additional tables are necessary due to the call to system_cron().
$this->installSchema('system', array('key_value_expire'));
}
/**
* Tests garbage collection.
*/
public function testGarbageCollection() {
$collection = $this->randomMachineName();
$store = new DatabaseStorageExpirable($collection, new PhpSerialize(), Database::getConnection());
// Insert some items and confirm that they're set.
for ($i = 0; $i <= 3; $i++) {
$store->setWithExpire('key_' . $i, $this->randomObject(), rand(500, 100000));
}
$this->assertIdentical(sizeof($store->getAll()), 4, 'Four items were written to the storage.');
// Manually expire the data.
for ($i = 0; $i <= 3; $i++) {
db_merge('key_value_expire')
->keys(array(
'name' => 'key_' . $i,
'collection' => $collection,
))
->fields(array(
'expire' => REQUEST_TIME - 1,
))
->execute();
}
// Perform a new set operation and then trigger garbage collection.
$store->setWithExpire('autumn', 'winter', rand(500, 1000000));
system_cron();
// Query the database and confirm that the stale records were deleted.
$result = db_query(
'SELECT name, value FROM {key_value_expire} WHERE collection = :collection',
array(
':collection' => $collection,
))->fetchAll();
$this->assertIdentical(count($result), 1, 'Only one item remains after garbage collection');
}
}

View file

@ -0,0 +1,160 @@
<?php
namespace Drupal\KernelTests\Core\KeyValueStore;
use Drupal\Core\Entity\EntityMalformedException;
use Drupal\Core\Entity\EntityStorageException;
use Drupal\KernelTests\KernelTestBase;
use Drupal\entity_test\Entity\EntityTestLabel;
/**
* Tests KeyValueEntityStorage for content entities.
*
* @group KeyValueStore
*/
class KeyValueContentEntityStorageTest extends KernelTestBase {
/**
* Modules to enable.
*
* @var array
*/
public static $modules = array('user', 'entity_test', 'keyvalue_test');
/**
* {@inheritdoc}
*/
protected function setUp() {
parent::setUp();
$this->installEntitySchema('user');
}
/**
* Tests CRUD operations.
*/
function testCRUD() {
$default_langcode = \Drupal::languageManager()->getDefaultLanguage()->getId();
// Verify default properties on a newly created empty entity.
$empty = EntityTestLabel::create();
$this->assertIdentical($empty->id->value, NULL);
$this->assertIdentical($empty->name->value, NULL);
$this->assertTrue($empty->uuid->value);
$this->assertIdentical($empty->langcode->value, $default_langcode);
// Verify ConfigEntity properties/methods on the newly created empty entity.
$this->assertIdentical($empty->isNew(), TRUE);
$this->assertIdentical($empty->bundle(), 'entity_test_label');
$this->assertIdentical($empty->id(), NULL);
$this->assertTrue($empty->uuid());
$this->assertIdentical($empty->label(), NULL);
// Verify Entity properties/methods on the newly created empty entity.
$this->assertIdentical($empty->getEntityTypeId(), 'entity_test_label');
// The URI can only be checked after saving.
try {
$empty->urlInfo();
$this->fail('EntityMalformedException was thrown.');
}
catch (EntityMalformedException $e) {
$this->pass('EntityMalformedException was thrown.');
}
// Verify that an empty entity cannot be saved.
try {
$empty->save();
$this->fail('EntityMalformedException was thrown.');
}
catch (EntityMalformedException $e) {
$this->pass('EntityMalformedException was thrown.');
}
// Verify that an entity with an empty ID string is considered empty, too.
$empty_id = EntityTestLabel::create(array(
'id' => '',
));
$this->assertIdentical($empty_id->isNew(), TRUE);
try {
$empty_id->save();
$this->fail('EntityMalformedException was thrown.');
}
catch (EntityMalformedException $e) {
$this->pass('EntityMalformedException was thrown.');
}
// Verify properties on a newly created entity.
$entity_test = EntityTestLabel::create($expected = array(
'id' => $this->randomMachineName(),
'name' => $this->randomString(),
));
$this->assertIdentical($entity_test->id->value, $expected['id']);
$this->assertTrue($entity_test->uuid->value);
$this->assertNotEqual($entity_test->uuid->value, $empty->uuid->value);
$this->assertIdentical($entity_test->name->value, $expected['name']);
$this->assertIdentical($entity_test->langcode->value, $default_langcode);
// Verify methods on the newly created entity.
$this->assertIdentical($entity_test->isNew(), TRUE);
$this->assertIdentical($entity_test->id(), $expected['id']);
$this->assertTrue($entity_test->uuid());
$expected['uuid'] = $entity_test->uuid();
$this->assertIdentical($entity_test->label(), $expected['name']);
// Verify that the entity can be saved.
try {
$status = $entity_test->save();
$this->pass('EntityMalformedException was not thrown.');
}
catch (EntityMalformedException $e) {
$this->fail('EntityMalformedException was not thrown.');
}
// Verify that the correct status is returned and properties did not change.
$this->assertIdentical($status, SAVED_NEW);
$this->assertIdentical($entity_test->id(), $expected['id']);
$this->assertIdentical($entity_test->uuid(), $expected['uuid']);
$this->assertIdentical($entity_test->label(), $expected['name']);
$this->assertIdentical($entity_test->isNew(), FALSE);
// Save again, and verify correct status and properties again.
$status = $entity_test->save();
$this->assertIdentical($status, SAVED_UPDATED);
$this->assertIdentical($entity_test->id(), $expected['id']);
$this->assertIdentical($entity_test->uuid(), $expected['uuid']);
$this->assertIdentical($entity_test->label(), $expected['name']);
$this->assertIdentical($entity_test->isNew(), FALSE);
// Ensure that creating an entity with the same id as an existing one is not
// possible.
$same_id = EntityTestLabel::create(array(
'id' => $entity_test->id(),
));
$this->assertIdentical($same_id->isNew(), TRUE);
try {
$same_id->save();
$this->fail('Not possible to overwrite an entity entity.');
}
catch (EntityStorageException $e) {
$this->pass('Not possible to overwrite an entity entity.');
}
// Verify that renaming the ID returns correct status and properties.
$ids = array($expected['id'], 'second_' . $this->randomMachineName(4), 'third_' . $this->randomMachineName(4));
for ($i = 1; $i < 3; $i++) {
$old_id = $ids[$i - 1];
$new_id = $ids[$i];
// Before renaming, everything should point to the current ID.
$this->assertIdentical($entity_test->id(), $old_id);
// Rename.
$entity_test->id = $new_id;
$this->assertIdentical($entity_test->id(), $new_id);
$status = $entity_test->save();
$this->assertIdentical($status, SAVED_UPDATED);
$this->assertIdentical($entity_test->isNew(), FALSE);
// Verify that originalID points to new ID directly after renaming.
$this->assertIdentical($entity_test->id(), $new_id);
}
}
}

View file

@ -0,0 +1,26 @@
<?php
namespace Drupal\KernelTests\Core\KeyValueStore;
use Drupal\Core\DependencyInjection\ContainerBuilder;
use Drupal\Core\KeyValueStore\KeyValueFactory;
/**
* Tests the key-value memory storage.
*
* @group KeyValueStore
*/
class MemoryStorageTest extends StorageTestBase {
/**
* {@inheritdoc}
*/
public function register(ContainerBuilder $container) {
parent::register($container);
$container->register('keyvalue.memory', 'Drupal\Core\KeyValueStore\KeyValueMemoryFactory');
$parameter[KeyValueFactory::DEFAULT_SETTING] = 'keyvalue.memory';
$container->setParameter('factory.keyvalue', $parameter);
}
}

View file

@ -0,0 +1,215 @@
<?php
namespace Drupal\KernelTests\Core\KeyValueStore;
use Drupal\KernelTests\KernelTestBase;
/**
* Base class for testing key-value storages.
*/
abstract class StorageTestBase extends KernelTestBase {
/**
* An array of random stdClass objects.
*
* @var array
*/
protected $objects = array();
/**
* An array of data collection labels.
*
* @var array
*/
protected $collections = array();
/**
* Whether we are using an expirable key/value store.
*
* @var bool
*/
protected $factory = 'keyvalue';
protected function setUp() {
parent::setUp();
// Define two data collections,
$this->collections = array(0 => 'zero', 1 => 'one');
// Create several objects for testing.
for ($i = 0; $i <= 5; $i++) {
$this->objects[$i] = $this->randomObject();
}
}
/**
* Tests CRUD operations.
*/
public function testCRUD() {
$stores = $this->createStorage();
// Verify that each store returns its own collection name.
$this->assertIdentical($stores[0]->getCollectionName(), $this->collections[0]);
$this->assertIdentical($stores[1]->getCollectionName(), $this->collections[1]);
// Verify that an item can be stored.
$stores[0]->set('foo', $this->objects[0]);
$this->assertTrue($stores[0]->has('foo'));
$this->assertIdenticalObject($this->objects[0], $stores[0]->get('foo'));
// Verify that the other collection is not affected.
$this->assertFalse($stores[1]->has('foo'));
$this->assertFalse($stores[1]->get('foo'));
// Verify that an item can be updated.
$stores[0]->set('foo', $this->objects[1]);
$this->assertIdenticalObject($this->objects[1], $stores[0]->get('foo'));
// Verify that the other collection is still not affected.
$this->assertFalse($stores[1]->get('foo'));
// Verify that a collection/name pair is unique.
$stores[1]->set('foo', $this->objects[2]);
$this->assertIdenticalObject($this->objects[1], $stores[0]->get('foo'));
$this->assertIdenticalObject($this->objects[2], $stores[1]->get('foo'));
// Verify that an item can be deleted.
$stores[0]->delete('foo');
$this->assertFalse($stores[0]->has('foo'));
$this->assertFalse($stores[0]->get('foo'));
// Verify that the other collection is not affected.
$this->assertTrue($stores[1]->has('foo'));
$this->assertIdenticalObject($this->objects[2], $stores[1]->get('foo'));
$stores[1]->delete('foo');
$this->assertFalse($stores[1]->get('foo'));
// Verify that multiple items can be stored.
$values = array(
'foo' => $this->objects[3],
'bar' => $this->objects[4],
);
$stores[0]->setMultiple($values);
// Verify that multiple items can be retrieved.
$result = $stores[0]->getMultiple(array('foo', 'bar'));
foreach ($values as $j => $value) {
$this->assertIdenticalObject($value, $result[$j]);
}
// Verify that the other collection was not affected.
$this->assertFalse($stores[1]->get('foo'));
$this->assertFalse($stores[1]->get('bar'));
// Verify that all items in a collection can be retrieved.
// Ensure that an item with the same name exists in the other collection.
$stores[1]->set('foo', $this->objects[5]);
$result = $stores[0]->getAll();
// Not using assertIdentical(), since the order is not defined for getAll().
$this->assertEqual(count($result), count($values));
foreach ($result as $key => $value) {
$this->assertEqual($values[$key], $value);
}
// Verify that all items in the other collection are different.
$result = $stores[1]->getAll();
$this->assertEqual($result, array('foo' => $this->objects[5]));
// Verify that multiple items can be deleted.
$stores[0]->deleteMultiple(array_keys($values));
$this->assertFalse($stores[0]->get('foo'));
$this->assertFalse($stores[0]->get('bar'));
$this->assertFalse($stores[0]->getMultiple(array('foo', 'bar')));
// Verify that deleting no items does not cause an error.
$stores[0]->deleteMultiple(array());
// Verify that the item in the other collection still exists.
$this->assertIdenticalObject($this->objects[5], $stores[1]->get('foo'));
}
/**
* Tests expected behavior for non-existing keys.
*/
public function testNonExistingKeys() {
$stores = $this->createStorage();
// Verify that a non-existing key returns NULL as value.
$this->assertNull($stores[0]->get('foo'));
// Verify that a non-existing key with a default returns the default.
$this->assertIdentical($stores[0]->get('foo', 'bar'), 'bar');
// Verify that a FALSE value can be stored.
$stores[0]->set('foo', FALSE);
$this->assertIdentical($stores[0]->get('foo'), FALSE);
// Verify that a deleted key returns NULL as value.
$stores[0]->delete('foo');
$this->assertNull($stores[0]->get('foo'));
// Verify that a non-existing key is not returned when getting multiple keys.
$stores[0]->set('bar', 'baz');
$values = $stores[0]->getMultiple(array('foo', 'bar'));
$this->assertFalse(isset($values['foo']), "Key 'foo' not found.");
$this->assertIdentical($values['bar'], 'baz');
}
/**
* Tests the setIfNotExists() method.
*/
public function testSetIfNotExists() {
$stores = $this->createStorage();
$key = $this->randomMachineName();
// Test that setIfNotExists() succeeds only the first time.
for ($i = 0; $i <= 1; $i++) {
// setIfNotExists() should be TRUE the first time (when $i is 0) and
// FALSE the second time (when $i is 1).
$this->assertEqual(!$i, $stores[0]->setIfNotExists($key, $this->objects[$i]));
$this->assertIdenticalObject($this->objects[0], $stores[0]->get($key));
// Verify that the other collection is not affected.
$this->assertFalse($stores[1]->get($key));
}
// Remove the item and try to set it again.
$stores[0]->delete($key);
$stores[0]->setIfNotExists($key, $this->objects[1]);
// This time it should succeed.
$this->assertIdenticalObject($this->objects[1], $stores[0]->get($key));
// Verify that the other collection is still not affected.
$this->assertFalse($stores[1]->get($key));
}
/**
* Tests the rename operation.
*/
public function testRename() {
$stores = $this->createStorage();
$store = $stores[0];
$store->set('old', 'thing');
$this->assertIdentical($store->get('old'), 'thing');
$store->rename('old', 'new');
$this->assertIdentical($store->get('new'), 'thing');
$this->assertNull($store->get('old'));
}
/**
* Creates storage objects for each collection defined for this class.
*
* Storing the storage objects in a class member variable causes a fatal
* exception in DatabaseStorageExpirableTest, because in that situation
* garbage collection is not triggered until the test class itself is
* destructed, after tearDown() has deleted the database tables. Instead,
* create the storage objects locally in each test using this method.
*
* @see \Drupal\system\Tests\KeyValueStore\DatabaseStorageExpirable
* @see \Drupal\Core\KeyValueStore\DatabaseStorageExpirable::garbageCollection()
*/
protected function createStorage() {
$stores = array();
foreach ($this->collections as $i => $collection) {
$stores[$i] = $this->container->get($this->factory)->get($collection);
}
return $stores;
}
}

View file

@ -0,0 +1,71 @@
<?php
namespace Drupal\KernelTests\Core\Lock;
use Drupal\Core\Lock\DatabaseLockBackend;
use Drupal\KernelTests\KernelTestBase;
/**
* Tests the Database lock backend.
*
* @group Lock
*/
class LockTest extends KernelTestBase {
/**
* Database lock backend to test.
*
* @var \Drupal\Core\Lock\DatabaseLockBackend
*/
protected $lock;
protected function setUp() {
parent::setUp();
$this->lock = new DatabaseLockBackend($this->container->get('database'));
}
/**
* Tests backend release functionality.
*/
public function testBackendLockRelease() {
$success = $this->lock->acquire('lock_a');
$this->assertTrue($success, 'Could acquire first lock.');
// This function is not part of the backend, but the default database
// backend implement it, we can here use it safely.
$is_free = $this->lock->lockMayBeAvailable('lock_a');
$this->assertFalse($is_free, 'First lock is unavailable.');
$this->lock->release('lock_a');
$is_free = $this->lock->lockMayBeAvailable('lock_a');
$this->assertTrue($is_free, 'First lock has been released.');
$success = $this->lock->acquire('lock_b');
$this->assertTrue($success, 'Could acquire second lock.');
$success = $this->lock->acquire('lock_b');
$this->assertTrue($success, 'Could acquire second lock a second time within the same request.');
$this->lock->release('lock_b');
}
/**
* Tests backend release functionality.
*/
public function testBackendLockReleaseAll() {
$success = $this->lock->acquire('lock_a');
$this->assertTrue($success, 'Could acquire first lock.');
$success = $this->lock->acquire('lock_b');
$this->assertTrue($success, 'Could acquire second lock.');
$this->lock->releaseAll();
$is_free = $this->lock->lockMayBeAvailable('lock_a');
$this->assertTrue($is_free, 'First lock has been released.');
$is_free = $this->lock->lockMayBeAvailable('lock_b');
$this->assertTrue($is_free, 'Second lock has been released.');
}
}

View file

@ -0,0 +1,56 @@
<?php
namespace Drupal\KernelTests\Core\Menu;
use Drupal\Core\Menu\MenuTreeParameters;
use Drupal\KernelTests\KernelTestBase;
/**
* Tests integration of static menu links.
*
* @group Menu
*/
class MenuLinkDefaultIntegrationTest extends KernelTestBase {
/**
* Modules to enable.
*
* @var array
*/
public static $modules = array(
'menu_test',
);
/**
* Tests moving a static menu link without a specified menu to the root.
*/
public function testMoveToRoot() {
/** @var \Drupal\Core\Menu\MenuLinkManagerInterface $menu_link_manager */
$menu_link_manager = \Drupal::service('plugin.manager.menu.link');
$menu_link_manager->rebuild();
$menu_link = $menu_link_manager->getDefinition('menu_test.child');
$this->assertEqual($menu_link['parent'], 'menu_test.parent');
$this->assertEqual($menu_link['menu_name'], 'test');
$tree = \Drupal::menuTree()->load('test', new MenuTreeParameters());
$this->assertEqual(count($tree), 1);
$this->assertEqual($tree['menu_test.parent']->link->getPluginId(), 'menu_test.parent');
$this->assertEqual($tree['menu_test.parent']->subtree['menu_test.child']->link->getPluginId(), 'menu_test.child');
// Ensure that the menu name is not forgotten.
$menu_link_manager->updateDefinition('menu_test.child', array('parent' => ''));
$menu_link = $menu_link_manager->getDefinition('menu_test.child');
$this->assertEqual($menu_link['parent'], '');
$this->assertEqual($menu_link['menu_name'], 'test');
$tree = \Drupal::menuTree()->load('test', new MenuTreeParameters());
$this->assertEqual(count($tree), 2);
$this->assertEqual($tree['menu_test.parent']->link->getPluginId(), 'menu_test.parent');
$this->assertEqual($tree['menu_test.child']->link->getPluginId(), 'menu_test.child');
$this->assertTrue(TRUE);
}
}

View file

@ -0,0 +1,133 @@
<?php
namespace Drupal\KernelTests\Core\Menu;
use Drupal\Core\Menu\MenuLinkTreeElement;
use Drupal\Core\Menu\MenuTreeParameters;
use Drupal\KernelTests\KernelTestBase;
use Drupal\Tests\Core\Menu\MenuLinkMock;
/**
* Tests the menu link tree.
*
* @group Menu
*
* @see \Drupal\Core\Menu\MenuLinkTree
*/
class MenuLinkTreeTest extends KernelTestBase {
/**
* The tested menu link tree.
*
* @var \Drupal\Core\Menu\MenuLinkTree
*/
protected $linkTree;
/**
* The menu link plugin manager.
*
* @var \Drupal\Core\Menu\MenuLinkManagerInterface $menuLinkManager
*/
protected $menuLinkManager;
/**
* Modules to enable.
*
* @var array
*/
public static $modules = array(
'system',
'menu_test',
'menu_link_content',
'field',
'link',
);
/**
* {@inheritdoc}
*/
protected function setUp() {
parent::setUp();
\Drupal::service('router.builder')->rebuild();
$this->installEntitySchema('menu_link_content');
$this->linkTree = $this->container->get('menu.link_tree');
$this->menuLinkManager = $this->container->get('plugin.manager.menu.link');
}
/**
* Tests deleting all the links in a menu.
*/
public function testDeleteLinksInMenu() {
\Drupal::entityManager()->getStorage('menu')->create(array('id' => 'menu1'))->save();
\Drupal::entityManager()->getStorage('menu')->create(array('id' => 'menu2'))->save();
\Drupal::entityManager()->getStorage('menu_link_content')->create(array('link' => ['uri' => 'internal:/menu_name_test'], 'menu_name' => 'menu1', 'bundle' => 'menu_link_content'))->save();
\Drupal::entityManager()->getStorage('menu_link_content')->create(array('link' => ['uri' => 'internal:/menu_name_test'], 'menu_name' => 'menu1', 'bundle' => 'menu_link_content'))->save();
\Drupal::entityManager()->getStorage('menu_link_content')->create(array('link' => ['uri' => 'internal:/menu_name_test'], 'menu_name' => 'menu2', 'bundle' => 'menu_link_content'))->save();
$output = $this->linkTree->load('menu1', new MenuTreeParameters());
$this->assertEqual(count($output), 2);
$output = $this->linkTree->load('menu2', new MenuTreeParameters());
$this->assertEqual(count($output), 1);
$this->menuLinkManager->deleteLinksInMenu('menu1');
$output = $this->linkTree->load('menu1', new MenuTreeParameters());
$this->assertEqual(count($output), 0);
$output = $this->linkTree->load('menu2', new MenuTreeParameters());
$this->assertEqual(count($output), 1);
}
/**
* Tests creating links with an expected tree structure.
*/
public function testCreateLinksInMenu() {
// This creates a tree with the following structure:
// - 1
// - 2
// - 3
// - 4
// - 5
// - 7
// - 6
// - 8
// With link 6 being the only external link.
$links = array(
1 => MenuLinkMock::create(array('id' => 'test.example1', 'route_name' => 'example1', 'title' => 'foo', 'parent' => '')),
2 => MenuLinkMock::create(array('id' => 'test.example2', 'route_name' => 'example2', 'title' => 'bar', 'parent' => 'test.example1', 'route_parameters' => array('foo' => 'bar'))),
3 => MenuLinkMock::create(array('id' => 'test.example3', 'route_name' => 'example3', 'title' => 'baz', 'parent' => 'test.example2', 'route_parameters' => array('baz' => 'qux'))),
4 => MenuLinkMock::create(array('id' => 'test.example4', 'route_name' => 'example4', 'title' => 'qux', 'parent' => 'test.example3')),
5 => MenuLinkMock::create(array('id' => 'test.example5', 'route_name' => 'example5', 'title' => 'foofoo', 'parent' => '')),
6 => MenuLinkMock::create(array('id' => 'test.example6', 'route_name' => '', 'url' => 'https://www.drupal.org/', 'title' => 'barbar', 'parent' => '')),
7 => MenuLinkMock::create(array('id' => 'test.example7', 'route_name' => 'example7', 'title' => 'bazbaz', 'parent' => '')),
8 => MenuLinkMock::create(array('id' => 'test.example8', 'route_name' => 'example8', 'title' => 'quxqux', 'parent' => '')),
);
foreach ($links as $instance) {
$this->menuLinkManager->addDefinition($instance->getPluginId(), $instance->getPluginDefinition());
}
$parameters = new MenuTreeParameters();
$tree = $this->linkTree->load('mock', $parameters);
$count = function(array $tree) {
$sum = function ($carry, MenuLinkTreeElement $item) {
return $carry + $item->count();
};
return array_reduce($tree, $sum);
};
$this->assertEqual($count($tree), 8);
$parameters = new MenuTreeParameters();
$parameters->setRoot('test.example2');
$tree = $this->linkTree->load($instance->getMenuName(), $parameters);
$top_link = reset($tree);
$this->assertEqual(count($top_link->subtree), 1);
$child = reset($top_link->subtree);
$this->assertEqual($child->link->getPluginId(), $links[3]->getPluginId());
$height = $this->linkTree->getSubtreeHeight('test.example2');
$this->assertEqual($height, 3);
}
}

View file

@ -0,0 +1,453 @@
<?php
namespace Drupal\KernelTests\Core\Menu;
use Drupal\Component\Plugin\Exception\PluginException;
use Drupal\Core\Menu\MenuTreeParameters;
use Drupal\Core\Menu\MenuTreeStorage;
use Drupal\KernelTests\KernelTestBase;
/**
* Tests the menu tree storage.
*
* @group Menu
*
* @see \Drupal\Core\Menu\MenuTreeStorage
*/
class MenuTreeStorageTest extends KernelTestBase {
/**
* The tested tree storage.
*
* @var \Drupal\Core\Menu\MenuTreeStorage
*/
protected $treeStorage;
/**
* The database connection.
*
* @var \Drupal\Core\Database\Connection
*/
protected $connection;
/**
* {@inheritdoc}
*/
protected function setUp() {
parent::setUp();
$this->treeStorage = new MenuTreeStorage($this->container->get('database'), $this->container->get('cache.menu'), $this->container->get('cache_tags.invalidator'), 'menu_tree');
$this->connection = $this->container->get('database');
}
/**
* Tests the tree storage when no tree was built yet.
*/
public function testBasicMethods() {
$this->doTestEmptyStorage();
$this->doTestTable();
}
/**
* Ensures that there are no menu links by default.
*/
protected function doTestEmptyStorage() {
$this->assertEqual(0, $this->treeStorage->countMenuLinks());
}
/**
* Ensures that table gets created on the fly.
*/
protected function doTestTable() {
// Test that we can create a tree storage with an arbitrary table name and
// that selecting from the storage creates the table.
$tree_storage = new MenuTreeStorage($this->container->get('database'), $this->container->get('cache.menu'), $this->container->get('cache_tags.invalidator'), 'test_menu_tree');
$this->assertFalse($this->connection->schema()->tableExists('test_menu_tree'), 'Test table is not yet created');
$tree_storage->countMenuLinks();
$this->assertTrue($this->connection->schema()->tableExists('test_menu_tree'), 'Test table was created');
}
/**
* Tests with a simple linear hierarchy.
*/
public function testSimpleHierarchy() {
// Add some links with parent on the previous one and test some values.
// <tools>
// - test1
// -- test2
// --- test3
$this->addMenuLink('test1', '');
$this->assertMenuLink('test1', array('has_children' => 0, 'depth' => 1));
$this->addMenuLink('test2', 'test1');
$this->assertMenuLink('test1', array('has_children' => 1, 'depth' => 1), array(), array('test2'));
$this->assertMenuLink('test2', array('has_children' => 0, 'depth' => 2), array('test1'));
$this->addMenuLink('test3', 'test2');
$this->assertMenuLink('test1', array('has_children' => 1, 'depth' => 1), array(), array('test2', 'test3'));
$this->assertMenuLink('test2', array('has_children' => 1, 'depth' => 2), array('test1'), array('test3'));
$this->assertMenuLink('test3', array('has_children' => 0, 'depth' => 3), array('test2', 'test1'));
}
/**
* Tests the tree with moving links inside the hierarchy.
*/
public function testMenuLinkMoving() {
// Before the move.
// <tools>
// - test1
// -- test2
// --- test3
// - test4
// -- test5
// --- test6
$this->addMenuLink('test1', '');
$this->addMenuLink('test2', 'test1');
$this->addMenuLink('test3', 'test2');
$this->addMenuLink('test4', '');
$this->addMenuLink('test5', 'test4');
$this->addMenuLink('test6', 'test5');
$this->assertMenuLink('test1', array('has_children' => 1, 'depth' => 1), array(), array('test2', 'test3'));
$this->assertMenuLink('test2', array('has_children' => 1, 'depth' => 2), array('test1'), array('test3'));
$this->assertMenuLink('test4', array('has_children' => 1, 'depth' => 1), array(), array('test5', 'test6'));
$this->assertMenuLink('test5', array('has_children' => 1, 'depth' => 2), array('test4'), array('test6'));
$this->assertMenuLink('test6', array('has_children' => 0, 'depth' => 3), array('test5', 'test4'));
$this->moveMenuLink('test2', 'test5');
// After the 1st move.
// <tools>
// - test1
// - test4
// -- test5
// --- test2
// ---- test3
// --- test6
$this->assertMenuLink('test1', array('has_children' => 0, 'depth' => 1));
$this->assertMenuLink('test2', array('has_children' => 1, 'depth' => 3), array('test5', 'test4'), array('test3'));
$this->assertMenuLink('test3', array('has_children' => 0, 'depth' => 4), array('test2', 'test5', 'test4'));
$this->assertMenuLink('test4', array('has_children' => 1, 'depth' => 1), array(), array('test5', 'test2', 'test3', 'test6'));
$this->assertMenuLink('test5', array('has_children' => 1, 'depth' => 2), array('test4'), array('test2', 'test3', 'test6'));
$this->assertMenuLink('test6', array('has_children' => 0, 'depth' => 3), array('test5', 'test4'));
$this->moveMenuLink('test4', 'test1');
$this->moveMenuLink('test3', 'test1');
// After the next 2 moves.
// <tools>
// - test1
// -- test3
// -- test4
// --- test5
// ---- test2
// ---- test6
$this->assertMenuLink('test1', array('has_children' => 1, 'depth' => 1), array(), array('test4', 'test5', 'test2', 'test3', 'test6'));
$this->assertMenuLink('test2', array('has_children' => 0, 'depth' => 4), array('test5', 'test4', 'test1'));
$this->assertMenuLink('test3', array('has_children' => 0, 'depth' => 2), array('test1'));
$this->assertMenuLink('test4', array('has_children' => 1, 'depth' => 2), array('test1'), array('test2', 'test5', 'test6'));
$this->assertMenuLink('test5', array('has_children' => 1, 'depth' => 3), array('test4', 'test1'), array('test2', 'test6'));
$this->assertMenuLink('test6', array('has_children' => 0, 'depth' => 4), array('test5', 'test4', 'test1'));
// Deleting a link in the middle should re-attach child links to the parent.
$this->treeStorage->delete('test4');
// After the delete.
// <tools>
// - test1
// -- test3
// -- test5
// --- test2
// --- test6
$this->assertMenuLink('test1', array('has_children' => 1, 'depth' => 1), array(), array('test5', 'test2', 'test3', 'test6'));
$this->assertMenuLink('test2', array('has_children' => 0, 'depth' => 3), array('test5', 'test1'));
$this->assertMenuLink('test3', array('has_children' => 0, 'depth' => 2), array('test1'));
$this->assertFalse($this->treeStorage->load('test4'));
$this->assertMenuLink('test5', array('has_children' => 1, 'depth' => 2), array('test1'), array('test2', 'test6'));
$this->assertMenuLink('test6', array('has_children' => 0, 'depth' => 3), array('test5', 'test1'));
}
/**
* Tests with disabled child links.
*/
public function testMenuDisabledChildLinks() {
// Add some links with parent on the previous one and test some values.
// <tools>
// - test1
// -- test2 (disabled)
$this->addMenuLink('test1', '');
$this->assertMenuLink('test1', array('has_children' => 0, 'depth' => 1));
$this->addMenuLink('test2', 'test1', '<front>', array(), 'tools', array('enabled' => 0));
// The 1st link does not have any visible children, so has_children is 0.
$this->assertMenuLink('test1', array('has_children' => 0, 'depth' => 1));
$this->assertMenuLink('test2', array('has_children' => 0, 'depth' => 2, 'enabled' => 0), array('test1'));
// Add more links with parent on the previous one.
// <footer>
// - footerA
// ===============
// <tools>
// - test1
// -- test2 (disabled)
// --- test3
// ---- test4
// ----- test5
// ------ test6
// ------- test7
// -------- test8
// --------- test9
$this->addMenuLink('footerA', '', '<front>', array(), 'footer');
$visible_children = array();
for ($i = 3; $i <= $this->treeStorage->maxDepth(); $i++) {
$parent = $i - 1;
$this->addMenuLink("test$i", "test$parent");
$visible_children[] = "test$i";
}
// The 1st link does not have any visible children, so has_children is still
// 0. However, it has visible links below it that will be found.
$this->assertMenuLink('test1', array('has_children' => 0, 'depth' => 1), array(), $visible_children);
// This should fail since test9 would end up at greater than max depth.
try {
$this->moveMenuLink('test1', 'footerA');
$this->fail('Exception was not thrown');
}
catch (PluginException $e) {
$this->pass($e->getMessage());
}
// The opposite move should work, and change the has_children flag.
$this->moveMenuLink('footerA', 'test1');
$visible_children[] = 'footerA';
$this->assertMenuLink('test1', array('has_children' => 1, 'depth' => 1), array(), $visible_children);
}
/**
* Tests the loadTreeData method.
*/
public function testLoadTree() {
$this->addMenuLink('test1', '');
$this->addMenuLink('test2', 'test1');
$this->addMenuLink('test3', 'test2');
$this->addMenuLink('test4');
$this->addMenuLink('test5', 'test4');
$data = $this->treeStorage->loadTreeData('tools', new MenuTreeParameters());
$tree = $data['tree'];
$this->assertEqual(count($tree['test1']['subtree']), 1);
$this->assertEqual(count($tree['test1']['subtree']['test2']['subtree']), 1);
$this->assertEqual(count($tree['test1']['subtree']['test2']['subtree']['test3']['subtree']), 0);
$this->assertEqual(count($tree['test4']['subtree']), 1);
$this->assertEqual(count($tree['test4']['subtree']['test5']['subtree']), 0);
$parameters = new MenuTreeParameters();
$parameters->setActiveTrail(array('test4', 'test5'));
$data = $this->treeStorage->loadTreeData('tools', $parameters);
$tree = $data['tree'];
$this->assertEqual(count($tree['test1']['subtree']), 1);
$this->assertFalse($tree['test1']['in_active_trail']);
$this->assertEqual(count($tree['test1']['subtree']['test2']['subtree']), 1);
$this->assertFalse($tree['test1']['subtree']['test2']['in_active_trail']);
$this->assertEqual(count($tree['test1']['subtree']['test2']['subtree']['test3']['subtree']), 0);
$this->assertFalse($tree['test1']['subtree']['test2']['subtree']['test3']['in_active_trail']);
$this->assertEqual(count($tree['test4']['subtree']), 1);
$this->assertTrue($tree['test4']['in_active_trail']);
$this->assertEqual(count($tree['test4']['subtree']['test5']['subtree']), 0);
$this->assertTrue($tree['test4']['subtree']['test5']['in_active_trail']);
// Add some conditions to ensure that conditions work as expected.
$parameters = new MenuTreeParameters();
$parameters->addCondition('parent', 'test1');
$data = $this->treeStorage->loadTreeData('tools', $parameters);
$this->assertEqual(count($data['tree']), 1);
$this->assertEqual($data['tree']['test2']['definition']['id'], 'test2');
$this->assertEqual($data['tree']['test2']['subtree'], []);
// Test for only enabled links.
$link = $this->treeStorage->load('test3');
$link['enabled'] = FALSE;
$this->treeStorage->save($link);
$link = $this->treeStorage->load('test4');
$link['enabled'] = FALSE;
$this->treeStorage->save($link);
$link = $this->treeStorage->load('test5');
$link['enabled'] = FALSE;
$this->treeStorage->save($link);
$parameters = new MenuTreeParameters();
$parameters->onlyEnabledLinks();
$data = $this->treeStorage->loadTreeData('tools', $parameters);
$this->assertEqual(count($data['tree']), 1);
$this->assertEqual($data['tree']['test1']['definition']['id'], 'test1');
$this->assertEqual(count($data['tree']['test1']['subtree']), 1);
$this->assertEqual($data['tree']['test1']['subtree']['test2']['definition']['id'], 'test2');
$this->assertEqual($data['tree']['test1']['subtree']['test2']['subtree'], []);
}
/**
* Tests finding the subtree height with content menu links.
*/
public function testSubtreeHeight() {
// root
// - child1
// -- child2
// --- child3
// ---- child4
$this->addMenuLink('root');
$this->addMenuLink('child1', 'root');
$this->addMenuLink('child2', 'child1');
$this->addMenuLink('child3', 'child2');
$this->addMenuLink('child4', 'child3');
$this->assertEqual($this->treeStorage->getSubtreeHeight('root'), 5);
$this->assertEqual($this->treeStorage->getSubtreeHeight('child1'), 4);
$this->assertEqual($this->treeStorage->getSubtreeHeight('child2'), 3);
$this->assertEqual($this->treeStorage->getSubtreeHeight('child3'), 2);
$this->assertEqual($this->treeStorage->getSubtreeHeight('child4'), 1);
}
/**
* Ensure hierarchy persists after a menu rebuild.
*/
public function testMenuRebuild() {
// root
// - child1
// -- child2
// --- child3
// ---- child4
$this->addMenuLink('root');
$this->addMenuLink('child1', 'root');
$this->addMenuLink('child2', 'child1');
$this->addMenuLink('child3', 'child2');
$this->addMenuLink('child4', 'child3');
$this->assertEqual($this->treeStorage->getSubtreeHeight('root'), 5);
$this->assertEqual($this->treeStorage->getSubtreeHeight('child1'), 4);
$this->assertEqual($this->treeStorage->getSubtreeHeight('child2'), 3);
$this->assertEqual($this->treeStorage->getSubtreeHeight('child3'), 2);
$this->assertEqual($this->treeStorage->getSubtreeHeight('child4'), 1);
// Intentionally leave child3 out to mimic static or external links.
$definitions = $this->treeStorage->loadMultiple(['root', 'child1', 'child2', 'child4']);
$this->treeStorage->rebuild($definitions);
$this->assertEqual($this->treeStorage->getSubtreeHeight('root'), 5);
$this->assertEqual($this->treeStorage->getSubtreeHeight('child1'), 4);
$this->assertEqual($this->treeStorage->getSubtreeHeight('child2'), 3);
$this->assertEqual($this->treeStorage->getSubtreeHeight('child3'), 2);
$this->assertEqual($this->treeStorage->getSubtreeHeight('child4'), 1);
}
/**
* Tests MenuTreeStorage::loadByProperties().
*/
public function testLoadByProperties() {
$tests = array(
array('foo' => 'bar'),
array(0 => 'wrong'),
);
$message = 'An invalid property name throws an exception.';
foreach ($tests as $properties) {
try {
$this->treeStorage->loadByProperties($properties);
$this->fail($message);
}
catch (\InvalidArgumentException $e) {
$this->assertTrue(preg_match('/^An invalid property name, .+ was specified. Allowed property names are:/', $e->getMessage()), 'Found expected exception message.');
$this->pass($message);
}
}
$this->addMenuLink('test_link.1', '', 'test', array(), 'menu1');
$properties = array('menu_name' => 'menu1');
$links = $this->treeStorage->loadByProperties($properties);
$this->assertEqual('menu1', $links['test_link.1']['menu_name']);
$this->assertEqual('test', $links['test_link.1']['route_name']);
}
/**
* Adds a link with the given ID and supply defaults.
*/
protected function addMenuLink($id, $parent = '', $route_name = 'test', $route_parameters = array(), $menu_name = 'tools', $extra = array()) {
$link = array(
'id' => $id,
'menu_name' => $menu_name,
'route_name' => $route_name,
'route_parameters' => $route_parameters,
'title' => 'test',
'parent' => $parent,
'options' => array(),
'metadata' => array(),
) + $extra;
$this->treeStorage->save($link);
}
/**
* Moves the link with the given ID so it's under a new parent.
*
* @param string $id
* The ID of the menu link to move.
* @param string $new_parent
* The ID of the new parent link.
*/
protected function moveMenuLink($id, $new_parent) {
$menu_link = $this->treeStorage->load($id);
$menu_link['parent'] = $new_parent;
$this->treeStorage->save($menu_link);
}
/**
* Tests that a link's stored representation matches the expected values.
*
* @param string $id
* The ID of the menu link to test
* @param array $expected_properties
* A keyed array of column names and values like has_children and depth.
* @param array $parents
* An ordered array of the IDs of the menu links that are the parents.
* @param array $children
* Array of child IDs that are visible (enabled == 1).
*/
protected function assertMenuLink($id, array $expected_properties, array $parents = array(), array $children = array()) {
$query = $this->connection->select('menu_tree');
$query->fields('menu_tree');
$query->condition('id', $id);
foreach ($expected_properties as $field => $value) {
$query->condition($field, $value);
}
$all = $query->execute()->fetchAll(\PDO::FETCH_ASSOC);
$this->assertEqual(count($all), 1, "Found link $id matching all the expected properties");
$raw = reset($all);
// Put the current link onto the front.
array_unshift($parents, $raw['id']);
$query = $this->connection->select('menu_tree');
$query->fields('menu_tree', array('id', 'mlid'));
$query->condition('id', $parents, 'IN');
$found_parents = $query->execute()->fetchAllKeyed(0, 1);
$this->assertEqual(count($parents), count($found_parents), 'Found expected number of parents');
$this->assertEqual($raw['depth'], count($found_parents), 'Number of parents is the same as the depth');
$materialized_path = $this->treeStorage->getRootPathIds($id);
$this->assertEqual(array_values($materialized_path), array_values($parents), 'Parents match the materialized path');
// Check that the selected mlid values of the parents are in the correct
// column, including the link's own.
for ($i = $raw['depth']; $i >= 1; $i--) {
$parent_id = array_shift($parents);
$this->assertEqual($raw["p$i"], $found_parents[$parent_id], "mlid of parent matches at column p$i");
}
for ($i = $raw['depth'] + 1; $i <= $this->treeStorage->maxDepth(); $i++) {
$this->assertEqual($raw["p$i"], 0, "parent is 0 at column p$i greater than depth");
}
if ($parents) {
$this->assertEqual($raw['parent'], end($parents), 'Ensure that the parent field is set properly');
}
$found_children = array_keys($this->treeStorage->loadAllChildren($id));
// We need both these checks since the 2nd will pass if there are extra
// IDs loaded in $found_children.
$this->assertEqual(count($children), count($found_children), "Found expected number of children for $id");
$this->assertEqual(array_intersect($children, $found_children), $children, 'Child IDs match');
}
}

View file

@ -0,0 +1,219 @@
<?php
namespace Drupal\KernelTests\Core\Path;
use Drupal\Core\Cache\MemoryCounterBackend;
use Drupal\Core\Path\AliasStorage;
use Drupal\Core\Database\Database;
use Drupal\Core\Path\AliasManager;
use Drupal\Core\Path\AliasWhitelist;
/**
* Tests path alias CRUD and lookup functionality.
*
* @group Path
*/
class AliasTest extends PathUnitTestBase {
function testCRUD() {
//Prepare database table.
$connection = Database::getConnection();
$this->fixtures->createTables($connection);
//Create Path object.
$aliasStorage = new AliasStorage($connection, $this->container->get('module_handler'));
$aliases = $this->fixtures->sampleUrlAliases();
//Create a few aliases
foreach ($aliases as $idx => $alias) {
$aliasStorage->save($alias['source'], $alias['alias'], $alias['langcode']);
$result = $connection->query('SELECT * FROM {url_alias} WHERE source = :source AND alias= :alias AND langcode = :langcode', array(':source' => $alias['source'], ':alias' => $alias['alias'], ':langcode' => $alias['langcode']));
$rows = $result->fetchAll();
$this->assertEqual(count($rows), 1, format_string('Created an entry for %alias.', array('%alias' => $alias['alias'])));
//Cache the pid for further tests.
$aliases[$idx]['pid'] = $rows[0]->pid;
}
//Load a few aliases
foreach ($aliases as $alias) {
$pid = $alias['pid'];
$loadedAlias = $aliasStorage->load(array('pid' => $pid));
$this->assertEqual($loadedAlias, $alias, format_string('Loaded the expected path with pid %pid.', array('%pid' => $pid)));
}
// Load alias by source path.
$loadedAlias = $aliasStorage->load(array('source' => '/node/1'));
$this->assertEqual($loadedAlias['alias'], '/alias_for_node_1_und', 'The last created alias loaded by default.');
//Update a few aliases
foreach ($aliases as $alias) {
$fields = $aliasStorage->save($alias['source'], $alias['alias'] . '_updated', $alias['langcode'], $alias['pid']);
$this->assertEqual($alias['alias'], $fields['original']['alias']);
$result = $connection->query('SELECT pid FROM {url_alias} WHERE source = :source AND alias= :alias AND langcode = :langcode', array(':source' => $alias['source'], ':alias' => $alias['alias'] . '_updated', ':langcode' => $alias['langcode']));
$pid = $result->fetchField();
$this->assertEqual($pid, $alias['pid'], format_string('Updated entry for pid %pid.', array('%pid' => $pid)));
}
//Delete a few aliases
foreach ($aliases as $alias) {
$pid = $alias['pid'];
$aliasStorage->delete(array('pid' => $pid));
$result = $connection->query('SELECT * FROM {url_alias} WHERE pid = :pid', array(':pid' => $pid));
$rows = $result->fetchAll();
$this->assertEqual(count($rows), 0, format_string('Deleted entry with pid %pid.', array('%pid' => $pid)));
}
}
function testLookupPath() {
//Prepare database table.
$connection = Database::getConnection();
$this->fixtures->createTables($connection);
//Create AliasManager and Path object.
$aliasManager = $this->container->get('path.alias_manager');
$aliasStorage = new AliasStorage($connection, $this->container->get('module_handler'));
// Test the situation where the source is the same for multiple aliases.
// Start with a language-neutral alias, which we will override.
$path = array(
'source' => "/user/1",
'alias' => '/foo',
);
$aliasStorage->save($path['source'], $path['alias']);
$this->assertEqual($aliasManager->getAliasByPath($path['source']), $path['alias'], 'Basic alias lookup works.');
$this->assertEqual($aliasManager->getPathByAlias($path['alias']), $path['source'], 'Basic source lookup works.');
// Create a language specific alias for the default language (English).
$path = array(
'source' => "/user/1",
'alias' => "/users/Dries",
'langcode' => 'en',
);
$aliasStorage->save($path['source'], $path['alias'], $path['langcode']);
// Hook that clears cache is not executed with unit tests.
\Drupal::service('path.alias_manager')->cacheClear();
$this->assertEqual($aliasManager->getAliasByPath($path['source']), $path['alias'], 'English alias overrides language-neutral alias.');
$this->assertEqual($aliasManager->getPathByAlias($path['alias']), $path['source'], 'English source overrides language-neutral source.');
// Create a language-neutral alias for the same path, again.
$path = array(
'source' => "/user/1",
'alias' => '/bar',
);
$aliasStorage->save($path['source'], $path['alias']);
$this->assertEqual($aliasManager->getAliasByPath($path['source']), "/users/Dries", 'English alias still returned after entering a language-neutral alias.');
// Create a language-specific (xx-lolspeak) alias for the same path.
$path = array(
'source' => "/user/1",
'alias' => '/LOL',
'langcode' => 'xx-lolspeak',
);
$aliasStorage->save($path['source'], $path['alias'], $path['langcode']);
$this->assertEqual($aliasManager->getAliasByPath($path['source']), "/users/Dries", 'English alias still returned after entering a LOLspeak alias.');
// The LOLspeak alias should be returned if we really want LOLspeak.
$this->assertEqual($aliasManager->getAliasByPath($path['source'], 'xx-lolspeak'), '/LOL', 'LOLspeak alias returned if we specify xx-lolspeak to the alias manager.');
// Create a new alias for this path in English, which should override the
// previous alias for "user/1".
$path = array(
'source' => "/user/1",
'alias' => '/users/my-new-path',
'langcode' => 'en',
);
$aliasStorage->save($path['source'], $path['alias'], $path['langcode']);
// Hook that clears cache is not executed with unit tests.
$aliasManager->cacheClear();
$this->assertEqual($aliasManager->getAliasByPath($path['source']), $path['alias'], 'Recently created English alias returned.');
$this->assertEqual($aliasManager->getPathByAlias($path['alias']), $path['source'], 'Recently created English source returned.');
// Remove the English aliases, which should cause a fallback to the most
// recently created language-neutral alias, 'bar'.
$aliasStorage->delete(array('langcode' => 'en'));
// Hook that clears cache is not executed with unit tests.
$aliasManager->cacheClear();
$this->assertEqual($aliasManager->getAliasByPath($path['source']), '/bar', 'Path lookup falls back to recently created language-neutral alias.');
// Test the situation where the alias and language are the same, but
// the source differs. The newer alias record should be returned.
$aliasStorage->save('/user/2', '/bar');
// Hook that clears cache is not executed with unit tests.
$aliasManager->cacheClear();
$this->assertEqual($aliasManager->getPathByAlias('/bar'), '/user/2', 'Newer alias record is returned when comparing two LanguageInterface::LANGCODE_NOT_SPECIFIED paths with the same alias.');
}
/**
* Tests the alias whitelist.
*/
function testWhitelist() {
// Prepare database table.
$connection = Database::getConnection();
$this->fixtures->createTables($connection);
$memoryCounterBackend = new MemoryCounterBackend('default');
// Create AliasManager and Path object.
$aliasStorage = new AliasStorage($connection, $this->container->get('module_handler'));
$whitelist = new AliasWhitelist('path_alias_whitelist', $memoryCounterBackend, $this->container->get('lock'), $this->container->get('state'), $aliasStorage);
$aliasManager = new AliasManager($aliasStorage, $whitelist, $this->container->get('language_manager'), $memoryCounterBackend);
// No alias for user and admin yet, so should be NULL.
$this->assertNull($whitelist->get('user'));
$this->assertNull($whitelist->get('admin'));
// Non-existing path roots should be NULL too. Use a length of 7 to avoid
// possible conflict with random aliases below.
$this->assertNull($whitelist->get($this->randomMachineName()));
// Add an alias for user/1, user should get whitelisted now.
$aliasStorage->save('/user/1', '/' . $this->randomMachineName());
$aliasManager->cacheClear();
$this->assertTrue($whitelist->get('user'));
$this->assertNull($whitelist->get('admin'));
$this->assertNull($whitelist->get($this->randomMachineName()));
// Add an alias for admin, both should get whitelisted now.
$aliasStorage->save('/admin/something', '/' . $this->randomMachineName());
$aliasManager->cacheClear();
$this->assertTrue($whitelist->get('user'));
$this->assertTrue($whitelist->get('admin'));
$this->assertNull($whitelist->get($this->randomMachineName()));
// Remove the user alias again, whitelist entry should be removed.
$aliasStorage->delete(array('source' => '/user/1'));
$aliasManager->cacheClear();
$this->assertNull($whitelist->get('user'));
$this->assertTrue($whitelist->get('admin'));
$this->assertNull($whitelist->get($this->randomMachineName()));
// Destruct the whitelist so that the caches are written.
$whitelist->destruct();
$this->assertEqual($memoryCounterBackend->getCounter('set', 'path_alias_whitelist'), 1);
$memoryCounterBackend->resetCounter();
// Re-initialize the whitelist using the same cache backend, should load
// from cache.
$whitelist = new AliasWhitelist('path_alias_whitelist', $memoryCounterBackend, $this->container->get('lock'), $this->container->get('state'), $aliasStorage);
$this->assertNull($whitelist->get('user'));
$this->assertTrue($whitelist->get('admin'));
$this->assertNull($whitelist->get($this->randomMachineName()));
$this->assertEqual($memoryCounterBackend->getCounter('get', 'path_alias_whitelist'), 1);
$this->assertEqual($memoryCounterBackend->getCounter('set', 'path_alias_whitelist'), 0);
// Destruct the whitelist, should not attempt to write the cache again.
$whitelist->destruct();
$this->assertEqual($memoryCounterBackend->getCounter('get', 'path_alias_whitelist'), 1);
$this->assertEqual($memoryCounterBackend->getCounter('set', 'path_alias_whitelist'), 0);
}
}

View file

@ -0,0 +1,33 @@
<?php
namespace Drupal\KernelTests\Core\Path;
use Drupal\Core\Database\Database;
use Drupal\KernelTests\KernelTestBase;
use Drupal\system\Tests\Path\UrlAliasFixtures;
/**
* Base class for Path/URL alias integration tests.
*/
abstract class PathUnitTestBase extends KernelTestBase {
/**
* @var \Drupal\system\Tests\Path\UrlAliasFixtures
*/
protected $fixtures;
protected function setUp() {
parent::setUp();
$this->fixtures = new UrlAliasFixtures();
// The alias whitelist expects that the menu path roots are set by a
// menu router rebuild.
\Drupal::state()->set('router.path_roots', array('user', 'admin'));
}
protected function tearDown() {
$this->fixtures->dropTables(Database::getConnection());
parent::tearDown();
}
}

View file

@ -0,0 +1,93 @@
<?php
namespace Drupal\KernelTests\Core\Plugin\Condition;
use Drupal\Core\Plugin\Context\Context;
use Drupal\Core\Plugin\Context\ContextDefinition;
use Drupal\KernelTests\KernelTestBase;
use Drupal\user\Entity\User;
/**
* Tests a condition that requires two users.
*
* @group condition_test
*/
class ConditionTestDualUserTest extends KernelTestBase {
/**
* An anonymous user for testing purposes.
*
* @var \Drupal\user\Entity\User
*/
protected $anonymous;
/**
* An authenticated user for testing purposes.
*
* @var \Drupal\user\Entity\User
*/
protected $authenticated;
/**
* {@inheritdoc}
*/
public static $modules = ['system', 'user', 'condition_test'];
/**
* {@inheritdoc}
*/
protected function setUp() {
parent::setUp();
$this->installSchema('system', 'sequences');
$this->installEntitySchema('user');
$this->anonymous = User::create(['uid' => 0]);
$this->authenticated = User::create(['uid' => 1]);
}
/**
* Tests the dual user condition.
*/
public function testConditions() {
$this->doTestIdenticalUser();
$this->doTestDifferentUser();
}
/**
* Tests with both contexts mapped to the same user.
*/
protected function doTestIdenticalUser() {
/** @var \Drupal\Core\Condition\ConditionPluginBase $condition */
$condition = \Drupal::service('plugin.manager.condition')
->createInstance('condition_test_dual_user')
// Map the anonymous user to both contexts.
->setContextMapping([
'user1' => 'anonymous',
'user2' => 'anonymous',
]);
$definition = new ContextDefinition('entity:user');
$contexts['anonymous'] = new Context($definition, $this->anonymous);
\Drupal::service('context.handler')->applyContextMapping($condition, $contexts);
$this->assertTrue($condition->execute());
}
/**
* Tests with each context mapped to different users.
*/
protected function doTestDifferentUser() {
/** @var \Drupal\Core\Condition\ConditionPluginBase $condition */
$condition = \Drupal::service('plugin.manager.condition')
->createInstance('condition_test_dual_user')
->setContextMapping([
'user1' => 'anonymous',
'user2' => 'authenticated',
]);
$definition = new ContextDefinition('entity:user');
$contexts['anonymous'] = new Context($definition, $this->anonymous);
$contexts['authenticated'] = new Context($definition, $this->authenticated);
\Drupal::service('context.handler')->applyContextMapping($condition, $contexts);
$this->assertFalse($condition->execute());
}
}

View file

@ -0,0 +1,49 @@
<?php
namespace Drupal\KernelTests\Core\Plugin\Condition;
use Drupal\Component\Utility\SafeMarkup;
use Drupal\KernelTests\KernelTestBase;
/**
* Tests the CurrentThemeCondition plugin.
*
* @group Condition
*/
class CurrentThemeConditionTest extends KernelTestBase {
/**
* {@inheritdoc}
*/
public static $modules = array('system', 'theme_test');
/**
* Tests the current theme condition.
*/
public function testCurrentTheme() {
\Drupal::service('theme_handler')->install(array('test_theme'));
$manager = \Drupal::service('plugin.manager.condition');
/** @var $condition \Drupal\Core\Condition\ConditionInterface */
$condition = $manager->createInstance('current_theme');
$condition->setConfiguration(array('theme' => 'test_theme'));
/** @var $condition_negated \Drupal\Core\Condition\ConditionInterface */
$condition_negated = $manager->createInstance('current_theme');
$condition_negated->setConfiguration(array('theme' => 'test_theme', 'negate' => TRUE));
$this->assertEqual($condition->summary(), SafeMarkup::format('The current theme is @theme', array('@theme' => 'test_theme')));
$this->assertEqual($condition_negated->summary(), SafeMarkup::format('The current theme is not @theme', array('@theme' => 'test_theme')));
// The expected theme has not been set up yet.
$this->assertFalse($condition->execute());
$this->assertTrue($condition_negated->execute());
// Set the expected theme to be used.
\Drupal::service('theme_handler')->setDefault('test_theme');
\Drupal::theme()->resetActiveTheme();
$this->assertTrue($condition->execute());
$this->assertFalse($condition_negated->execute());
}
}

View file

@ -0,0 +1,71 @@
<?php
namespace Drupal\KernelTests\Core\Plugin\Condition;
use Drupal\Core\Plugin\Context\Context;
use Drupal\Core\Plugin\Context\ContextDefinition;
use Drupal\KernelTests\KernelTestBase;
use Drupal\node\Entity\Node;
use Drupal\node\Entity\NodeType;
/**
* Tests a condition with optional context.
*
* @group condition_test
*/
class OptionalContextConditionTest extends KernelTestBase {
/**
* {@inheritdoc}
*/
public static $modules = ['system', 'user', 'condition_test', 'node'];
/**
* Tests with both contexts mapped to the same user.
*/
public function testContextMissing() {
/** @var \Drupal\Core\Condition\ConditionPluginBase $condition */
$condition = \Drupal::service('plugin.manager.condition')
->createInstance('condition_test_optional_context')
->setContextMapping([
'node' => 'node',
]);
\Drupal::service('context.handler')->applyContextMapping($condition, []);
$this->assertTrue($condition->execute());
}
/**
* Tests with both contexts mapped to the same user.
*/
public function testContextNoValue() {
/** @var \Drupal\Core\Condition\ConditionPluginBase $condition */
$condition = \Drupal::service('plugin.manager.condition')
->createInstance('condition_test_optional_context')
->setContextMapping([
'node' => 'node',
]);
$definition = new ContextDefinition('entity:node');
$contexts['node'] = (new Context($definition));
\Drupal::service('context.handler')->applyContextMapping($condition, $contexts);
$this->assertTrue($condition->execute());
}
/**
* Tests with both contexts mapped to the same user.
*/
public function testContextAvailable() {
NodeType::create(['type' => 'example', 'name' => 'Example'])->save();
/** @var \Drupal\Core\Condition\ConditionPluginBase $condition */
$condition = \Drupal::service('plugin.manager.condition')
->createInstance('condition_test_optional_context')
->setContextMapping([
'node' => 'node',
]);
$definition = new ContextDefinition('entity:node');
$node = Node::create(['type' => 'example']);
$contexts['node'] = new Context($definition, $node);
\Drupal::service('context.handler')->applyContextMapping($condition, $contexts);
$this->assertFalse($condition->execute());
}
}

View file

@ -0,0 +1,141 @@
<?php
namespace Drupal\KernelTests\Core\Plugin\Condition;
use Drupal\Core\Path\CurrentPathStack;
use Drupal\KernelTests\KernelTestBase;
use Drupal\system\Tests\Routing\MockAliasManager;
use Symfony\Component\HttpFoundation\Request;
use Symfony\Component\HttpFoundation\RequestStack;
/**
* Tests that the Request Path Condition, provided by the system module, is
* working properly.
*
* @group Plugin
*/
class RequestPathTest extends KernelTestBase {
/**
* The condition plugin manager under test.
*
* @var \Drupal\Core\Condition\ConditionManager
*/
protected $pluginManager;
/**
* The path alias manager used for testing.
*
* @var \Drupal\system\Tests\Routing\MockAliasManager
*/
protected $aliasManager;
/**
* The request stack used for testing.
*
* @var \Symfony\Component\HttpFoundation\RequestStack
*/
protected $requestStack;
/**
* Modules to enable.
*
* @var array
*/
public static $modules = array('system', 'user', 'field', 'path');
/**
* The current path.
*
* @var \Drupal\Core\Path\CurrentPathStack|\PHPUnit_Framework_MockObject_MockObject
*/
protected $currentPath;
/**
* {@inheritdoc}
*/
protected function setUp() {
parent::setUp();
$this->installSchema('system', array('sequences'));
$this->pluginManager = $this->container->get('plugin.manager.condition');
// Set a mock alias manager in the container.
$this->aliasManager = new MockAliasManager();
$this->container->set('path.alias_manager', $this->aliasManager);
// Set the test request stack in the container.
$this->requestStack = new RequestStack();
$this->container->set('request_stack', $this->requestStack);
$this->currentPath = new CurrentPathStack($this->requestStack);
$this->container->set('path.current', $this->currentPath);
}
/**
* Tests the request path condition.
*/
public function testConditions() {
// Get the request path condition and test and configure it to check against
// different patterns and requests.
$pages = "/my/pass/page\r\n/my/pass/page2\r\n/foo";
$request = Request::create('/my/pass/page2');
$this->requestStack->push($request);
/* @var \Drupal\system\Plugin\Condition\RequestPath $condition */
$condition = $this->pluginManager->createInstance('request_path');
$condition->setConfig('pages', $pages);
$this->aliasManager->addAlias('/my/pass/page2', '/my/pass/page2');
$this->assertTrue($condition->execute(), 'The request path matches a standard path');
$this->assertEqual($condition->summary(), 'Return true on the following pages: /my/pass/page, /my/pass/page2, /foo', 'The condition summary matches for a standard path');
// Test an aliased path.
$this->currentPath->setPath('/my/aliased/page', $request);
$this->requestStack->pop();
$this->requestStack->push($request);
$this->aliasManager->addAlias('/my/aliased/page', '/my/pass/page');
$this->assertTrue($condition->execute(), 'The request path matches an aliased path');
$this->assertEqual($condition->summary(), 'Return true on the following pages: /my/pass/page, /my/pass/page2, /foo', 'The condition summary matches for an aliased path');
// Test a wildcard path.
$this->aliasManager->addAlias('/my/pass/page3', '/my/pass/page3');
$this->currentPath->setPath('/my/pass/page3', $request);
$this->requestStack->pop();
$this->requestStack->push($request);
$condition->setConfig('pages', '/my/pass/*');
$this->assertTrue($condition->evaluate(), 'The system_path my/pass/page3 passes for wildcard paths.');
$this->assertEqual($condition->summary(), 'Return true on the following pages: /my/pass/*', 'The condition summary matches for a wildcard path');
// Test a missing path.
$this->requestStack->pop();
$this->requestStack->push($request);
$this->currentPath->setPath('/my/fail/page4', $request);
$condition->setConfig('pages', '/my/pass/*');
$this->aliasManager->addAlias('/my/fail/page4', '/my/fail/page4');
$this->assertFalse($condition->evaluate(), 'The system_path /my/pass/page4 fails for a missing path.');
// Test a path of '/'.
$this->aliasManager->addAlias('/', '/my/pass/page3');
$this->currentPath->setPath('/', $request);
$this->requestStack->pop();
$this->requestStack->push($request);
$this->assertTrue($condition->evaluate(), 'The system_path my/pass/page3 passes for wildcard paths.');
$this->assertEqual($condition->summary(), 'Return true on the following pages: /my/pass/*', 'The condition summary matches for a wildcard path');
}
}

View file

@ -0,0 +1,106 @@
<?php
namespace Drupal\KernelTests\Core\Plugin;
use Drupal\Component\Plugin\Exception\ContextException;
use Drupal\Core\Plugin\Context\ContextDefinition;
use Drupal\KernelTests\KernelTestBase;
use Drupal\node\Entity\Node;
use Drupal\node\Entity\NodeType;
use Drupal\plugin_test\Plugin\MockBlockManager;
use Drupal\user\Entity\User;
/**
* Tests that contexts are properly set and working within plugins.
*
* @group Plugin
*/
class ContextPluginTest extends KernelTestBase {
public static $modules = array('system', 'user', 'node', 'field', 'filter', 'text');
/**
* Tests basic context definition and value getters and setters.
*/
function testContext() {
$this->installEntitySchema('user');
$this->installEntitySchema('node');
$this->installEntitySchema('node_type');
$type = NodeType::create(['type' => 'page', 'name' => 'Page']);
$type->save();
$name = $this->randomMachineName();
$manager = new MockBlockManager();
$plugin = $manager->createInstance('user_name');
// Create a node, add it as context, catch the exception.
$node = Node::create(['type' => 'page', 'title' => $name]);
// Try to get context that is missing its definition.
try {
$plugin->getContextDefinition('not_exists');
$this->fail('The user context should not yet be set.');
}
catch (ContextException $e) {
$this->assertEqual($e->getMessage(), 'The not_exists context is not a valid context.');
}
// Test the getContextDefinitions() method.
$user_context_definition = ContextDefinition::create('entity:user')->setLabel(t('User'));
$this->assertEqual($plugin->getContextDefinitions()['user']->getLabel(), $user_context_definition->getLabel());
// Test the getContextDefinition() method for a valid context.
$this->assertEqual($plugin->getContextDefinition('user')->getLabel(), $user_context_definition->getLabel());
// Try to get a context with valid definition.
$this->assertNotNull($plugin->getContext('user'), 'Succeeded to get a context with a valid definition.');
// Try to get a value of a valid context, while this value has not been set.
try {
$plugin->getContextValue('user');
}
catch (ContextException $e) {
$this->assertIdentical("The 'entity:user' context is required and not present.", $e->getMessage(), 'Requesting a non-set value of a required context should throw a context exception.');
}
// Try to pass the wrong class type as a context value.
$plugin->setContextValue('user', $node);
$violations = $plugin->validateContexts();
$this->assertTrue(!empty($violations), 'The provided context value does not pass validation.');
// Set an appropriate context value and check to make sure its methods work
// as expected.
$user = User::create(['name' => $name]);
$plugin->setContextValue('user', $user);
$this->assertEqual($plugin->getContextValue('user')->getUsername(), $user->getUsername());
$this->assertEqual($user->label(), $plugin->getTitle());
// Test Optional context handling.
$plugin = $manager->createInstance('user_name_optional');
$this->assertNull($plugin->getContextValue('user'), 'Requesting a non-set value of a valid context should return NULL.');
// Test Complex compound context handling.
$complex_plugin = $manager->createInstance('complex_context');
$complex_plugin->setContextValue('user', $user);
// With only the user context set, try to get the context values.
$values = $complex_plugin->getContextValues();
$this->assertNull($values['node'], 'The node context is not yet set.');
$this->assertNotNull($values['user'], 'The user context is set');
$complex_plugin->setContextValue('node', $node);
$context_wrappers = $complex_plugin->getContexts();
// Make sure what came out of the wrappers is good.
$this->assertEqual($context_wrappers['user']->getContextValue()->label(), $user->label());
$this->assertEqual($context_wrappers['node']->getContextValue()->label(), $node->label());
// Make sure what comes out of the context values is good.
$contexts = $complex_plugin->getContextValues();
$this->assertEqual($contexts['user']->label(), $user->label());
$this->assertEqual($contexts['node']->label(), $node->label());
// Test the title method for the complex context plugin.
$this->assertEqual($user->label() . ' -- ' . $node->label(), $complex_plugin->getTitle());
}
}

View file

@ -0,0 +1,32 @@
<?php
namespace Drupal\KernelTests\Core\Plugin;
/**
* Tests that derivative plugins are correctly discovered.
*
* @group Plugin
*/
class DerivativeTest extends PluginTestBase {
/**
* Tests getDefinitions() and getDefinition() with a derivativeDecorator.
*/
function testDerivativeDecorator() {
// Ensure that getDefinitions() returns the expected definitions.
$this->assertEqual($this->mockBlockManager->getDefinitions(), $this->mockBlockExpectedDefinitions);
// Ensure that getDefinition() returns the expected definition.
foreach ($this->mockBlockExpectedDefinitions as $id => $definition) {
$this->assertEqual($this->mockBlockManager->getDefinition($id), $definition);
}
// Ensure that NULL is returned as the definition of a non-existing base
// plugin, a non-existing derivative plugin, or a base plugin that may not
// be used without deriving.
$this->assertIdentical($this->mockBlockManager->getDefinition('non_existing', FALSE), NULL, 'NULL returned as the definition of a non-existing base plugin.');
$this->assertIdentical($this->mockBlockManager->getDefinition('menu:non_existing', FALSE), NULL, 'NULL returned as the definition of a non-existing derivative plugin.');
$this->assertIdentical($this->mockBlockManager->getDefinition('menu', FALSE), NULL, 'NULL returned as the definition of a base plugin that may not be used without deriving.');
}
}

View file

@ -0,0 +1,85 @@
<?php
namespace Drupal\KernelTests\Core\Plugin\Discovery;
use Drupal\Core\Plugin\Discovery\AnnotatedClassDiscovery;
/**
* Tests that plugins are correctly discovered using annotated classes.
*
* @group Plugin
*/
class AnnotatedClassDiscoveryTest extends DiscoveryTestBase {
protected function setUp() {
parent::setUp();
$this->expectedDefinitions = array(
'apple' => array(
'id' => 'apple',
'label' => 'Apple',
'color' => 'green',
'class' => 'Drupal\plugin_test\Plugin\plugin_test\fruit\Apple',
'provider' => 'plugin_test',
),
'banana' => array(
'id' => 'banana',
'label' => 'Banana',
'color' => 'yellow',
'uses' => array(
'bread' => t('Banana bread'),
'loaf' => array(
'singular' => '@count loaf',
'plural' => '@count loaves',
'context' => NULL,
),
),
'class' => 'Drupal\plugin_test\Plugin\plugin_test\fruit\Banana',
'provider' => 'plugin_test',
),
'cherry' => array(
'id' => 'cherry',
'label' => 'Cherry',
'color' => 'red',
'class' => 'Drupal\plugin_test\Plugin\plugin_test\fruit\Cherry',
'provider' => 'plugin_test',
),
'kale' => array(
'id' => 'kale',
'label' => 'Kale',
'color' => 'green',
'class' => 'Drupal\plugin_test\Plugin\plugin_test\fruit\Kale',
'provider' => 'plugin_test',
),
'orange' => array(
'id' => 'orange',
'label' => 'Orange',
'color' => 'orange',
'class' => 'Drupal\plugin_test\Plugin\plugin_test\fruit\Orange',
'provider' => 'plugin_test',
),
'big_apple' => array(
'id' => 'big_apple',
'label' => 'Big Apple',
'color' => 'green',
'class' => 'Drupal\plugin_test_extended\Plugin\plugin_test\fruit\BigApple',
'provider' => 'plugin_test_extended',
),
'extending_non_installed_class' => array(
'id' => 'extending_non_installed_class',
'label' => 'A plugin whose class is extending from a non-installed module class',
'color' => 'pink',
'class' => 'Drupal\plugin_test\Plugin\plugin_test\fruit\ExtendingNonInstalledClass',
'provider' => 'plugin_test',
),
);
$base_directory = \Drupal::root() . '/core/modules/system/tests/modules/plugin_test/src';
$base_directory2 = \Drupal::root() . '/core/modules/system/tests/modules/plugin_test_extended/src';
$namespaces = new \ArrayObject(array('Drupal\plugin_test' => $base_directory, 'Drupal\plugin_test_extended' => $base_directory2));
$annotation_namespaces = ['Drupal\plugin_test\Plugin\Annotation', 'Drupal\plugin_test_extended\Plugin\Annotation'];
$this->discovery = new AnnotatedClassDiscovery('Plugin/plugin_test/fruit', $namespaces, 'Drupal\Component\Annotation\Plugin', $annotation_namespaces);
$this->emptyDiscovery = new AnnotatedClassDiscovery('Plugin/non_existing_module/non_existing_plugin_type', $namespaces);
}
}

View file

@ -0,0 +1,40 @@
<?php
namespace Drupal\KernelTests\Core\Plugin\Discovery;
use Drupal\Core\Plugin\Discovery\AnnotatedClassDiscovery;
/**
* Tests that a custom annotation class is used.
*
* @group Plugin
* @see \Drupal\plugin_test\Plugin\Annotation\PluginExample
*/
class CustomAnnotationClassDiscoveryTest extends DiscoveryTestBase {
protected function setUp() {
parent::setUp();
$this->expectedDefinitions = array(
'example_1' => array(
'id' => 'example_1',
'custom' => 'John',
'class' => 'Drupal\plugin_test\Plugin\plugin_test\custom_annotation\Example1',
'provider' => 'plugin_test',
),
'example_2' => array(
'id' => 'example_2',
'custom' => 'Paul',
'class' => 'Drupal\plugin_test\Plugin\plugin_test\custom_annotation\Example2',
'provider' => 'plugin_test',
),
);
$base_directory = \Drupal::root() . '/core/modules/system/tests/modules/plugin_test/src';
$root_namespaces = new \ArrayObject(array('Drupal\plugin_test' => $base_directory));
$this->discovery = new AnnotatedClassDiscovery('Plugin/plugin_test/custom_annotation', $root_namespaces, 'Drupal\plugin_test\Plugin\Annotation\PluginExample');
$this->emptyDiscovery = new AnnotatedClassDiscovery('Plugin/non_existing_module/non_existing_plugin_type', $root_namespaces, 'Drupal\plugin_test\Plugin\Annotation\PluginExample');
}
}

View file

@ -0,0 +1,91 @@
<?php
namespace Drupal\KernelTests\Core\Plugin\Discovery;
use Drupal\Core\Plugin\Discovery\AnnotatedClassDiscovery;
/**
* Tests that plugins in a custom directory are correctly discovered using
* annotated classes.
*
* @group Plugin
*/
class CustomDirectoryAnnotatedClassDiscoveryTest extends DiscoveryTestBase {
protected function setUp() {
parent::setUp();
$this->expectedDefinitions = array(
'custom_example_1' => array(
'id' => 'custom_example_1',
'custom' => 'Tim',
'class' => 'Drupal\plugin_test\CustomDirectoryExample1',
'provider' => 'plugin_test',
),
'custom_example_2' => array(
'id' => 'custom_example_2',
'custom' => 'Meghan',
'class' => 'Drupal\plugin_test\CustomDirectoryExample2',
'provider' => 'plugin_test',
),
'apple' => array(
'id' => 'apple',
'label' => 'Apple',
'color' => 'green',
'class' => 'Drupal\plugin_test\Plugin\plugin_test\fruit\Apple',
'provider' => 'plugin_test',
),
'banana' => array(
'id' => 'banana',
'label' => 'Banana',
'color' => 'yellow',
'uses' => array(
'bread' => t('Banana bread'),
'loaf' => array(
'singular' => '@count loaf',
'plural' => '@count loaves',
'context' => NULL,
),
),
'class' => 'Drupal\plugin_test\Plugin\plugin_test\fruit\Banana',
'provider' => 'plugin_test',
),
'cherry' => array(
'id' => 'cherry',
'label' => 'Cherry',
'color' => 'red',
'class' => 'Drupal\plugin_test\Plugin\plugin_test\fruit\Cherry',
'provider' => 'plugin_test',
),
'kale' => array(
'id' => 'kale',
'label' => 'Kale',
'color' => 'green',
'class' => 'Drupal\plugin_test\Plugin\plugin_test\fruit\Kale',
'provider' => 'plugin_test',
),
'orange' => array(
'id' => 'orange',
'label' => 'Orange',
'color' => 'orange',
'class' => 'Drupal\plugin_test\Plugin\plugin_test\fruit\Orange',
'provider' => 'plugin_test',
),
'extending_non_installed_class' => array(
'id' => 'extending_non_installed_class',
'label' => 'A plugin whose class is extending from a non-installed module class',
'color' => 'pink',
'class' => 'Drupal\plugin_test\Plugin\plugin_test\fruit\ExtendingNonInstalledClass',
'provider' => 'plugin_test',
),
);
$base_directory = \Drupal::root() . '/core/modules/system/tests/modules/plugin_test/src';
$namespaces = new \ArrayObject(array('Drupal\plugin_test' => $base_directory));
$this->discovery = new AnnotatedClassDiscovery('', $namespaces);
$empty_namespaces = new \ArrayObject();
$this->emptyDiscovery = new AnnotatedClassDiscovery('', $empty_namespaces);
}
}

View file

@ -0,0 +1,84 @@
<?php
namespace Drupal\KernelTests\Core\Plugin\Discovery;
use Drupal\Core\StringTranslation\TranslatableMarkup;
use Drupal\KernelTests\KernelTestBase;
/**
* Base class for plugin discovery tests.
*/
abstract class DiscoveryTestBase extends KernelTestBase {
/**
* The discovery component to test.
*
* @var \Drupal\Component\Plugin\Discovery\DiscoveryInterface
*/
protected $discovery;
/**
* The plugin definitions the discovery component is expected to discover.
*
* @var array
*/
protected $expectedDefinitions;
/**
* An empty discovery component.
*
* This will be tested to ensure that the case where no plugin information is
* found, is handled correctly.
*
* @var \Drupal\Component\Plugin\Discovery\DiscoveryInterface
*/
protected $emptyDiscovery;
/**
* Tests getDefinitions() and getDefinition().
*/
function testDiscoveryInterface() {
// Ensure that getDefinitions() returns the expected definitions.
// For the arrays to be identical (instead of only equal), they must be
// sorted equally, which seems unnecessary here.
// The discovered definitions may contain circular references; use a custom
// assertion message to prevent var_export() from getting called.
$this->assertEqual($this->discovery->getDefinitions(), $this->expectedDefinitions, 'Expected definitions found.');
// Ensure that getDefinition() returns the expected definition.
foreach ($this->expectedDefinitions as $id => $definition) {
$this->assertDefinitionIdentical($this->discovery->getDefinition($id), $definition);
}
// Ensure that an empty array is returned if no plugin definitions are found.
$this->assertIdentical($this->emptyDiscovery->getDefinitions(), array(), 'array() returned if no plugin definitions are found.');
// Ensure that NULL is returned as the definition of a non-existing plugin.
$this->assertIdentical($this->emptyDiscovery->getDefinition('non_existing', FALSE), NULL, 'NULL returned as the definition of a non-existing plugin.');
}
/**
* Asserts a definition against an expected definition.
*
* Converts any instances of \Drupal\Core\Annotation\Translation to a string.
*
* @param array $definition
* The definition to test.
* @param array $expected_definition
* The expected definition to test against.
*
* @return bool
* TRUE if the assertion succeeded, FALSE otherwise.
*/
protected function assertDefinitionIdentical(array $definition, array $expected_definition) {
$func = function (&$item){
if ($item instanceof TranslatableMarkup) {
$item = (string) $item;
}
};
array_walk_recursive($definition, $func);
array_walk_recursive($expected_definition, $func);
return $this->assertIdentical($definition, $expected_definition);
}
}

Some files were not shown because too many files have changed in this diff Show more