first commit

This commit is contained in:
2020-06-08 23:57:36 +02:00
commit 6277454f7a
16057 changed files with 1715382 additions and 0 deletions
@@ -0,0 +1,35 @@
<?php
namespace Drupal\Tests;
use Drupal\Component\Render\MarkupInterface;
/**
* Provides helper methods for assertions.
*/
trait AssertHelperTrait {
/**
* Casts MarkupInterface objects into strings.
*
* @param string|array $value
* The value to act on.
*
* @return mixed
* The input value, with MarkupInterface objects casted to string.
*/
protected static function castSafeStrings($value) {
if ($value instanceof MarkupInterface) {
$value = (string) $value;
}
if (is_array($value)) {
array_walk_recursive($value, function (&$item) {
if ($item instanceof MarkupInterface) {
$item = (string) $item;
}
});
}
return $value;
}
}
@@ -0,0 +1,43 @@
<?php
namespace Drupal\Tests;
use Drupal\Core\Render\Markup;
/**
* @coversDefaultClass \Drupal\Tests\AssertHelperTrait
* @group simpletest
* @group Tests
*/
class AssertHelperTraitTest extends UnitTestCase {
/**
* @covers ::castSafeStrings
* @dataProvider providerCastSafeStrings
*/
public function testCastSafeStrings($expected, $value) {
$class = new AssertHelperTestClass();
$this->assertSame($expected, $class->testMethod($value));
}
public function providerCastSafeStrings() {
$safe_string = Markup::create('test safe string');
return [
['test simple string', 'test simple string'],
[['test simple array', 'test simple array'], ['test simple array', 'test simple array']],
['test safe string', $safe_string],
[['test safe string', 'test safe string'], [$safe_string, $safe_string]],
[['test safe string', 'mixed array', 'test safe string'], [$safe_string, 'mixed array', $safe_string]],
];
}
}
class AssertHelperTestClass {
use AssertHelperTrait;
public function testMethod($value) {
return $this->castSafeStrings($value);
}
}
@@ -0,0 +1,233 @@
<?php
namespace Drupal\Tests;
use Drupal\Component\Utility\Html;
use Drupal\Core\Utility\Error;
use Psr\Http\Message\RequestInterface;
use Psr\Http\Message\ResponseInterface;
/**
* Provides the debug functions for browser tests.
*/
trait BrowserHtmlDebugTrait {
/**
* Class name for HTML output logging.
*
* @var string
*/
protected $htmlOutputClassName;
/**
* Directory name for HTML output logging.
*
* @var string
*/
protected $htmlOutputDirectory;
/**
* Counter storage for HTML output logging.
*
* @var string
*/
protected $htmlOutputCounterStorage;
/**
* Counter for HTML output logging.
*
* @var int
*/
protected $htmlOutputCounter = 1;
/**
* HTML output output enabled.
*
* @var bool
*/
protected $htmlOutputEnabled = FALSE;
/**
* The file name to write the list of URLs to.
*
* This file is read by the PHPUnit result printer.
*
* @var string
*
* @see \Drupal\Tests\Listeners\HtmlOutputPrinter
*/
protected $htmlOutputFile;
/**
* HTML output test ID.
*
* @var int
*/
protected $htmlOutputTestId;
/**
* The Base URI to use for links to the output files.
*
* @var string
*/
protected $htmlOutputBaseUrl;
/**
* Formats HTTP headers as string for HTML output logging.
*
* @param array[] $headers
* Headers that should be formatted.
*
* @return string
* The formatted HTML string.
*/
protected function formatHtmlOutputHeaders(array $headers) {
$flattened_headers = array_map(function ($header) {
if (is_array($header)) {
return implode(';', array_map('trim', $header));
}
else {
return $header;
}
}, $headers);
return '<hr />Headers: <pre>' . Html::escape(var_export($flattened_headers, TRUE)) . '</pre>';
}
/**
* Returns headers in HTML output format.
*
* @return string
* HTML output headers.
*/
protected function getHtmlOutputHeaders() {
return $this->formatHtmlOutputHeaders($this->getSession()->getResponseHeaders());
}
/**
* Logs a HTML output message in a text file.
*
* The link to the HTML output message will be printed by the results printer.
*
* @param string|null $message
* (optional) The HTML output message to be stored. If not supplied the
* current page content is used.
*
* @see \Drupal\Tests\Listeners\VerbosePrinter::printResult()
*/
protected function htmlOutput($message = NULL) {
if (!$this->htmlOutputEnabled) {
return;
}
$message = $message ?: $this->getSession()->getPage()->getContent();
$message = '<hr />ID #' . $this->htmlOutputCounter . ' (<a href="' . $this->htmlOutputClassName . '-' . ($this->htmlOutputCounter - 1) . '-' . $this->htmlOutputTestId . '.html">Previous</a> | <a href="' . $this->htmlOutputClassName . '-' . ($this->htmlOutputCounter + 1) . '-' . $this->htmlOutputTestId . '.html">Next</a>)<hr />' . $message;
$html_output_filename = $this->htmlOutputClassName . '-' . $this->htmlOutputCounter . '-' . $this->htmlOutputTestId . '.html';
file_put_contents($this->htmlOutputDirectory . '/' . $html_output_filename, $message);
file_put_contents($this->htmlOutputCounterStorage, $this->htmlOutputCounter++);
// Do not use file_create_url() as the module_handler service might not be
// available.
$uri = $this->htmlOutputBaseUrl . '/sites/simpletest/browser_output/' . $html_output_filename;
file_put_contents($this->htmlOutputFile, $uri . "\n", FILE_APPEND);
}
/**
* Creates the directory to store browser output.
*
* Creates the directory to store browser output in if a file to write
* URLs to has been created by \Drupal\Tests\Listeners\HtmlOutputPrinter.
*/
protected function initBrowserOutputFile() {
$browser_output_file = getenv('BROWSERTEST_OUTPUT_FILE');
$this->htmlOutputEnabled = is_file($browser_output_file);
$this->htmlOutputBaseUrl = getenv('BROWSERTEST_OUTPUT_BASE_URL') ?: $GLOBALS['base_url'];
if ($this->htmlOutputEnabled) {
$this->htmlOutputFile = $browser_output_file;
$this->htmlOutputClassName = str_replace("\\", "_", get_called_class());
$this->htmlOutputDirectory = DRUPAL_ROOT . '/sites/simpletest/browser_output';
// Do not use the file_system service so this method can be called before
// it is available. Checks !is_dir() twice around mkdir() because a
// concurrent test might have made the directory and caused mkdir() to
// fail. In this case we can still use the directory even though we failed
// to make it.
if (!is_dir($this->htmlOutputDirectory) && !@mkdir($this->htmlOutputDirectory, 0775, TRUE) && !is_dir($this->htmlOutputDirectory)) {
throw new \RuntimeException(sprintf('Unable to create directory: %s', $this->htmlOutputDirectory));
}
if (!file_exists($this->htmlOutputDirectory . '/.htaccess')) {
file_put_contents($this->htmlOutputDirectory . '/.htaccess', "<IfModule mod_expires.c>\nExpiresActive Off\n</IfModule>\n");
}
$this->htmlOutputCounterStorage = $this->htmlOutputDirectory . '/' . $this->htmlOutputClassName . '.counter';
$this->htmlOutputTestId = str_replace('sites/simpletest/', '', $this->siteDirectory);
if (is_file($this->htmlOutputCounterStorage)) {
$this->htmlOutputCounter = max(1, (int) file_get_contents($this->htmlOutputCounterStorage)) + 1;
}
}
}
/**
* Provides a Guzzle middleware handler to log every response received.
*
* @return callable
* The callable handler that will do the logging.
*/
protected function getResponseLogHandler() {
return function (callable $handler) {
return function (RequestInterface $request, array $options) use ($handler) {
return $handler($request, $options)
->then(function (ResponseInterface $response) use ($request) {
if ($this->htmlOutputEnabled) {
$caller = $this->getTestMethodCaller();
$html_output = 'Called from ' . $caller['function'] . ' line ' . $caller['line'];
$html_output .= '<hr />' . $request->getMethod() . ' request to: ' . $request->getUri();
// On redirect responses (status code starting with '3') we need
// to remove the meta tag that would do a browser refresh. We
// don't want to redirect developers away when they look at the
// debug output file in their browser.
$body = $response->getBody();
$status_code = (string) $response->getStatusCode();
if ($status_code[0] === '3') {
$body = preg_replace('#<meta http-equiv="refresh" content=.+/>#', '', $body, 1);
}
$html_output .= '<hr />' . $body;
$html_output .= $this->formatHtmlOutputHeaders($response->getHeaders());
$this->htmlOutput($html_output);
}
return $response;
});
};
};
}
/**
* Retrieves the current calling line in the class under test.
*
* @return array
* An associative array with keys 'file', 'line' and 'function'.
*/
protected function getTestMethodCaller() {
$backtrace = debug_backtrace();
// Find the test class that has the test method.
while ($caller = Error::getLastCaller($backtrace)) {
if (isset($caller['class']) && $caller['class'] === get_class($this)) {
break;
}
// If the test method is implemented by a test class's parent then the
// class name of $this will not be part of the backtrace.
// In that case we process the backtrace until the caller is not a
// subclass of $this and return the previous caller.
if (isset($last_caller) && (!isset($caller['class']) || !is_subclass_of($this, $caller['class']))) {
// Return the last caller since that has to be the test class.
$caller = $last_caller;
break;
}
// Otherwise we have not reached our test class yet: save the last caller
// and remove an element from to backtrace to process the next call.
$last_caller = $caller;
array_shift($backtrace);
}
return $caller;
}
}
@@ -0,0 +1,762 @@
<?php
namespace Drupal\Tests;
use Behat\Mink\Driver\GoutteDriver;
use Behat\Mink\Element\Element;
use Behat\Mink\Mink;
use Behat\Mink\Selector\SelectorsHandler;
use Behat\Mink\Session;
use Drupal\Component\Serialization\Json;
use Drupal\Core\Database\Database;
use Drupal\Core\Test\FunctionalTestSetupTrait;
use Drupal\Core\Test\TestSetupTrait;
use Drupal\Core\Utility\Error;
use Drupal\FunctionalTests\AssertLegacyTrait;
use Drupal\Tests\block\Traits\BlockCreationTrait;
use Drupal\Tests\node\Traits\ContentTypeCreationTrait;
use Drupal\Tests\node\Traits\NodeCreationTrait;
use Drupal\Tests\user\Traits\UserCreationTrait;
use Drupal\TestTools\Comparator\MarkupInterfaceComparator;
use GuzzleHttp\Cookie\CookieJar;
use PHPUnit\Framework\TestCase;
use Psr\Http\Message\RequestInterface;
use Psr\Http\Message\ResponseInterface;
use Symfony\Component\CssSelector\CssSelectorConverter;
/**
* Provides a test case for functional Drupal tests.
*
* Tests extending BrowserTestBase must exist in the
* Drupal\Tests\yourmodule\Functional namespace and live in the
* modules/yourmodule/tests/src/Functional directory.
*
* Tests extending this base class should only translate text when testing
* translation functionality. For example, avoid wrapping test text with t()
* or TranslatableMarkup().
*
* @ingroup testing
*/
abstract class BrowserTestBase extends TestCase {
use FunctionalTestSetupTrait;
use UiHelperTrait {
FunctionalTestSetupTrait::refreshVariables insteadof UiHelperTrait;
}
use TestSetupTrait;
use BlockCreationTrait {
placeBlock as drupalPlaceBlock;
}
use AssertLegacyTrait;
use RandomGeneratorTrait;
use NodeCreationTrait {
getNodeByTitle as drupalGetNodeByTitle;
createNode as drupalCreateNode;
}
use ContentTypeCreationTrait {
createContentType as drupalCreateContentType;
}
use ConfigTestTrait;
use TestRequirementsTrait;
use UserCreationTrait {
createRole as drupalCreateRole;
createUser as drupalCreateUser;
}
use XdebugRequestTrait;
use PhpunitCompatibilityTrait;
/**
* The database prefix of this test run.
*
* @var string
*/
protected $databasePrefix;
/**
* Time limit in seconds for the test.
*
* @var int
*/
protected $timeLimit = 500;
/**
* The translation file directory for the test environment.
*
* This is set in BrowserTestBase::prepareEnvironment().
*
* @var string
*/
protected $translationFilesDirectory;
/**
* The config importer that can be used in a test.
*
* @var \Drupal\Core\Config\ConfigImporter
*/
protected $configImporter;
/**
* Modules to enable.
*
* The test runner will merge the $modules lists from this class, the class
* it extends, and so on up the class hierarchy. It is not necessary to
* include modules in your list that a parent class has already declared.
*
* @var string[]
*
* @see \Drupal\Tests\BrowserTestBase::installDrupal()
*/
protected static $modules = [];
/**
* The profile to install as a basis for testing.
*
* @var string
*/
protected $profile = 'testing';
/**
* The theme to install as the default for testing.
*
* Defaults to the install profile's default theme, if it specifies any.
*
* @var string
*/
protected $defaultTheme;
/**
* An array of custom translations suitable for drupal_rewrite_settings().
*
* @var array
*/
protected $customTranslations;
/*
* Mink class for the default driver to use.
*
* Should be a fully-qualified class name that implements
* Behat\Mink\Driver\DriverInterface.
*
* Value can be overridden using the environment variable MINK_DRIVER_CLASS.
*
* @var string
*/
protected $minkDefaultDriverClass = GoutteDriver::class;
/*
* Mink default driver params.
*
* If it's an array its contents are used as constructor params when default
* Mink driver class is instantiated.
*
* Can be overridden using the environment variable MINK_DRIVER_ARGS. In this
* case that variable should be a JSON array, for example:
* '["firefox", null, "http://localhost:4444/wd/hub"]'.
*
*
* @var array
*/
protected $minkDefaultDriverArgs;
/**
* Mink session manager.
*
* This will not be initialized if there was an error during the test setup.
*
* @var \Behat\Mink\Mink|null
*/
protected $mink;
/**
* {@inheritdoc}
*
* Browser tests are run in separate processes to prevent collisions between
* code that may be loaded by tests.
*/
protected $runTestInSeparateProcess = TRUE;
/**
* {@inheritdoc}
*/
protected $preserveGlobalState = FALSE;
/**
* The base URL.
*
* @var string
*/
protected $baseUrl;
/**
* The original array of shutdown function callbacks.
*
* @var array
*/
protected $originalShutdownCallbacks = [];
/**
* The app root.
*
* @var string
*/
protected $root;
/**
* The original container.
*
* Move this to \Drupal\Core\Test\FunctionalTestSetupTrait once TestBase no
* longer provides the same value.
*
* @var \Symfony\Component\DependencyInjection\ContainerInterface
*/
protected $originalContainer;
/**
* {@inheritdoc}
*/
public function __construct($name = NULL, array $data = [], $dataName = '') {
parent::__construct($name, $data, $dataName);
$this->root = dirname(dirname(substr(__DIR__, 0, -strlen(__NAMESPACE__))));
}
/**
* Initializes Mink sessions.
*/
protected function initMink() {
$driver = $this->getDefaultDriverInstance();
if ($driver instanceof GoutteDriver) {
// Turn off curl timeout. Having a timeout is not a problem in a normal
// test running, but it is a problem when debugging. Also, disable SSL
// peer verification so that testing under HTTPS always works.
/** @var \GuzzleHttp\Client $client */
$client = $this->container->get('http_client_factory')->fromOptions([
'timeout' => NULL,
'verify' => FALSE,
]);
// Inject a Guzzle middleware to generate debug output for every request
// performed in the test.
$handler_stack = $client->getConfig('handler');
$handler_stack->push($this->getResponseLogHandler());
$driver->getClient()->setClient($client);
}
$selectors_handler = new SelectorsHandler([
'hidden_field_selector' => new HiddenFieldSelector(),
]);
$session = new Session($driver, $selectors_handler);
$this->mink = new Mink();
$this->mink->registerSession('default', $session);
$this->mink->setDefaultSessionName('default');
$this->registerSessions();
$this->initFrontPage();
// Copies cookies from the current environment, for example, XDEBUG_SESSION
// in order to support Xdebug.
// @see BrowserTestBase::initFrontPage()
$cookies = $this->extractCookiesFromRequest(\Drupal::request());
foreach ($cookies as $cookie_name => $values) {
foreach ($values as $value) {
$session->setCookie($cookie_name, $value);
}
}
return $session;
}
/**
* Visits the front page when initializing Mink.
*
* According to the W3C WebDriver specification a cookie can only be set if
* the cookie domain is equal to the domain of the active document. When the
* browser starts up the active document is not our domain but 'about:blank'
* or similar. To be able to set our User-Agent and Xdebug cookies at the
* start of the test we now do a request to the front page so the active
* document matches the domain.
*
* @see https://w3c.github.io/webdriver/webdriver-spec.html#add-cookie
* @see https://www.w3.org/Bugs/Public/show_bug.cgi?id=20975
*/
protected function initFrontPage() {
$session = $this->getSession();
$session->visit($this->baseUrl);
}
/**
* Gets an instance of the default Mink driver.
*
* @return Behat\Mink\Driver\DriverInterface
* Instance of default Mink driver.
*
* @throws \InvalidArgumentException
* When provided default Mink driver class can't be instantiated.
*/
protected function getDefaultDriverInstance() {
// Get default driver params from environment if available.
if ($arg_json = $this->getMinkDriverArgs()) {
$this->minkDefaultDriverArgs = json_decode($arg_json, TRUE);
}
// Get and check default driver class from environment if available.
if ($minkDriverClass = getenv('MINK_DRIVER_CLASS')) {
if (class_exists($minkDriverClass)) {
$this->minkDefaultDriverClass = $minkDriverClass;
}
else {
throw new \InvalidArgumentException("Can't instantiate provided $minkDriverClass class by environment as default driver class.");
}
}
if (is_array($this->minkDefaultDriverArgs)) {
// Use ReflectionClass to instantiate class with received params.
$reflector = new \ReflectionClass($this->minkDefaultDriverClass);
$driver = $reflector->newInstanceArgs($this->minkDefaultDriverArgs);
}
else {
$driver = new $this->minkDefaultDriverClass();
}
return $driver;
}
/**
* Get the Mink driver args from an environment variable, if it is set. Can
* be overridden in a derived class so it is possible to use a different
* value for a subset of tests, e.g. the JavaScript tests.
*
* @return string|false
* The JSON-encoded argument string. False if it is not set.
*/
protected function getMinkDriverArgs() {
return getenv('MINK_DRIVER_ARGS');
}
/**
* Provides a Guzzle middleware handler to log every response received.
*
* @return callable
* The callable handler that will do the logging.
*/
protected function getResponseLogHandler() {
return function (callable $handler) {
return function (RequestInterface $request, array $options) use ($handler) {
return $handler($request, $options)
->then(function (ResponseInterface $response) use ($request) {
if ($this->htmlOutputEnabled) {
$caller = $this->getTestMethodCaller();
$html_output = 'Called from ' . $caller['function'] . ' line ' . $caller['line'];
$html_output .= '<hr />' . $request->getMethod() . ' request to: ' . $request->getUri();
// Get the response body as a string. Any errors are silenced as
// tests should not fail if there is a problem. On PHP 7.4
// \Drupal\Tests\migrate\Functional\process\DownloadFunctionalTest
// fails without the usage of a silence operator.
$body = @(string) $response->getBody();
// On redirect responses (status code starting with '3') we need
// to remove the meta tag that would do a browser refresh. We
// don't want to redirect developers away when they look at the
// debug output file in their browser.
$status_code = (string) $response->getStatusCode();
if ($status_code[0] === '3') {
$body = preg_replace('#<meta http-equiv="refresh" content=.+/>#', '', $body, 1);
}
$html_output .= '<hr />' . $body;
$html_output .= $this->formatHtmlOutputHeaders($response->getHeaders());
$this->htmlOutput($html_output);
}
return $response;
});
};
};
}
/**
* Registers additional Mink sessions.
*
* Tests wishing to use a different driver or change the default driver should
* override this method.
*
* @code
* // Register a new session that uses the MinkPonyDriver.
* $pony = new MinkPonyDriver();
* $session = new Session($pony);
* $this->mink->registerSession('pony', $session);
* @endcode
*/
protected function registerSessions() {}
/**
* {@inheritdoc}
*/
protected function setUp() {
parent::setUp();
// Allow tests to compare MarkupInterface objects via assertEquals().
$this->registerComparator(new MarkupInterfaceComparator());
$this->setupBaseUrl();
// Install Drupal test site.
$this->prepareEnvironment();
$this->installDrupal();
// Setup Mink.
$this->initMink();
// Set up the browser test output file.
$this->initBrowserOutputFile();
// Ensure that the test is not marked as risky because of no assertions. In
// PHPUnit 6 tests that only make assertions using $this->assertSession()
// can be marked as risky.
$this->addToAssertionCount(1);
}
/**
* Ensures test files are deletable.
*
* Some tests chmod generated files to be read only. During
* BrowserTestBase::cleanupEnvironment() and other cleanup operations,
* these files need to get deleted too.
*
* @param string $path
* The file path.
*
* @see \Drupal\Core\File\FileSystemInterface::deleteRecursive()
*/
public static function filePreDeleteCallback($path) {
// When the webserver runs with the same system user as phpunit, we can
// make read-only files writable again. If not, chmod will fail while the
// file deletion still works if file permissions have been configured
// correctly. Thus, we ignore any problems while running chmod.
@chmod($path, 0700);
}
/**
* Clean up the Simpletest environment.
*/
protected function cleanupEnvironment() {
// Remove all prefixed tables.
$original_connection_info = Database::getConnectionInfo('simpletest_original_default');
$original_prefix = $original_connection_info['default']['prefix']['default'];
$test_connection_info = Database::getConnectionInfo('default');
$test_prefix = $test_connection_info['default']['prefix']['default'];
if ($original_prefix != $test_prefix) {
$tables = Database::getConnection()->schema()->findTables('%');
foreach ($tables as $table) {
if (Database::getConnection()->schema()->dropTable($table)) {
unset($tables[$table]);
}
}
}
// Delete test site directory.
\Drupal::service('file_system')->deleteRecursive($this->siteDirectory, [$this, 'filePreDeleteCallback']);
}
/**
* {@inheritdoc}
*/
protected function tearDown() {
parent::tearDown();
// Destroy the testing kernel.
if (isset($this->kernel)) {
$this->cleanupEnvironment();
$this->kernel->shutdown();
}
// Ensure that internal logged in variable is reset.
$this->loggedInUser = FALSE;
if ($this->mink) {
$this->mink->stopSessions();
}
// Restore original shutdown callbacks.
if (function_exists('drupal_register_shutdown_function')) {
$callbacks = &drupal_register_shutdown_function();
$callbacks = $this->originalShutdownCallbacks;
}
}
/**
* Returns Mink session.
*
* @param string $name
* (optional) Name of the session. Defaults to the active session.
*
* @return \Behat\Mink\Session
* The active Mink session object.
*/
public function getSession($name = NULL) {
return $this->mink->getSession($name);
}
/**
* Get session cookies from current session.
*
* @return \GuzzleHttp\Cookie\CookieJar
* A cookie jar with the current session.
*/
protected function getSessionCookies() {
$domain = parse_url($this->getUrl(), PHP_URL_HOST);
$session_id = $this->getSession()->getCookie($this->getSessionName());
$cookies = CookieJar::fromArray([$this->getSessionName() => $session_id], $domain);
return $cookies;
}
/**
* Obtain the HTTP client for the system under test.
*
* Use this method for arbitrary HTTP requests to the site under test. For
* most tests, you should not get the HTTP client and instead use navigation
* methods such as drupalGet() and clickLink() in order to benefit from
* assertions.
*
* Subclasses which substitute a different Mink driver should override this
* method and provide a Guzzle client if the Mink driver provides one.
*
* @return \GuzzleHttp\ClientInterface
* The client with BrowserTestBase configuration.
*
* @throws \RuntimeException
* If the Mink driver does not support a Guzzle HTTP client, throw an
* exception.
*/
protected function getHttpClient() {
/* @var $mink_driver \Behat\Mink\Driver\DriverInterface */
$mink_driver = $this->getSession()->getDriver();
if ($mink_driver instanceof GoutteDriver) {
return $mink_driver->getClient()->getClient();
}
throw new \RuntimeException('The Mink client type ' . get_class($mink_driver) . ' does not support getHttpClient().');
}
/**
* Helper function to get the options of select field.
*
* @param \Behat\Mink\Element\NodeElement|string $select
* Name, ID, or Label of select field to assert.
* @param \Behat\Mink\Element\Element $container
* (optional) Container element to check against. Defaults to current page.
*
* @return array
* Associative array of option keys and values.
*/
protected function getOptions($select, Element $container = NULL) {
if (is_string($select)) {
$select = $this->assertSession()->selectExists($select, $container);
}
$options = [];
/* @var \Behat\Mink\Element\NodeElement $option */
foreach ($select->findAll('xpath', '//option') as $option) {
$label = $option->getText();
$value = $option->getAttribute('value') ?: $label;
$options[$value] = $label;
}
return $options;
}
/**
* Installs Drupal into the Simpletest site.
*/
public function installDrupal() {
$this->initUserSession();
$this->prepareSettings();
$this->doInstall();
$this->initSettings();
$container = $this->initKernel(\Drupal::request());
$this->initConfig($container);
$this->installDefaultThemeFromClassProperty($container);
$this->installModulesFromClassProperty($container);
$this->rebuildAll();
}
/**
* Prevents serializing any properties.
*
* Browser tests are run in a separate process. To do this PHPUnit creates a
* script to run the test. If it fails, the test result object will contain a
* stack trace which includes the test object. It will attempt to serialize
* it. Returning an empty array prevents it from serializing anything it
* should not.
*
* @return array
* An empty array.
*
* @see vendor/phpunit/phpunit/src/Util/PHP/Template/TestCaseMethod.tpl.dist
*/
public function __sleep() {
return [];
}
/**
* Translates a CSS expression to its XPath equivalent.
*
* The search is relative to the root element (HTML tag normally) of the page.
*
* @param string $selector
* CSS selector to use in the search.
* @param bool $html
* (optional) Enables HTML support. Disable it for XML documents.
* @param string $prefix
* (optional) The prefix for the XPath expression.
*
* @return string
* The equivalent XPath of a CSS expression.
*/
protected function cssSelectToXpath($selector, $html = TRUE, $prefix = 'descendant-or-self::') {
return (new CssSelectorConverter($html))->toXPath($selector, $prefix);
}
/**
* Performs an xpath search on the contents of the internal browser.
*
* The search is relative to the root element (HTML tag normally) of the page.
*
* @param string $xpath
* The xpath string to use in the search.
* @param array $arguments
* An array of arguments with keys in the form ':name' matching the
* placeholders in the query. The values may be either strings or numeric
* values.
*
* @return \Behat\Mink\Element\NodeElement[]
* The list of elements matching the xpath expression.
*/
protected function xpath($xpath, array $arguments = []) {
$xpath = $this->assertSession()->buildXPathQuery($xpath, $arguments);
return $this->getSession()->getPage()->findAll('xpath', $xpath);
}
/**
* Configuration accessor for tests. Returns non-overridden configuration.
*
* @param string $name
* Configuration name.
*
* @return \Drupal\Core\Config\Config
* The configuration object with original configuration data.
*/
protected function config($name) {
return $this->container->get('config.factory')->getEditable($name);
}
/**
* Returns all response headers.
*
* @return array
* The HTTP headers values.
*
* @deprecated in drupal:8.8.0 and is removed from drupal:9.0.0.
* Use $this->getSession()->getResponseHeaders() instead.
*
* @see https://www.drupal.org/node/3067207
*/
protected function drupalGetHeaders() {
@trigger_error('Drupal\Tests\BrowserTestBase::drupalGetHeaders() is deprecated in drupal:8.8.0 and is removed from drupal:9.0.0. Use $this->getSession()->getResponseHeaders() instead. See https://www.drupal.org/node/3067207', E_USER_DEPRECATED);
return $this->getSession()->getResponseHeaders();
}
/**
* Gets the value of an HTTP response header.
*
* If multiple requests were required to retrieve the page, only the headers
* from the last request will be checked by default.
*
* @param string $name
* The name of the header to retrieve. Names are case-insensitive (see RFC
* 2616 section 4.2).
*
* @return string|null
* The HTTP header value or NULL if not found.
*/
protected function drupalGetHeader($name) {
return $this->getSession()->getResponseHeader($name);
}
/**
* Gets the JavaScript drupalSettings variable for the currently-loaded page.
*
* @return array
* The JSON decoded drupalSettings value from the current page.
*/
protected function getDrupalSettings() {
$html = $this->getSession()->getPage()->getContent();
if (preg_match('@<script type="application/json" data-drupal-selector="drupal-settings-json">([^<]*)</script>@', $html, $matches)) {
return Json::decode($matches[1]);
}
return [];
}
/**
* Retrieves the current calling line in the class under test.
*
* @return array
* An associative array with keys 'file', 'line' and 'function'.
*/
protected function getTestMethodCaller() {
$backtrace = debug_backtrace();
// Find the test class that has the test method.
while ($caller = Error::getLastCaller($backtrace)) {
// If we match PHPUnit's TestCase::runTest, then the previously processed
// caller entry is where our test method sits.
if (isset($last_caller) && isset($caller['function']) && $caller['function'] === 'PHPUnit\Framework\TestCase->runTest()') {
// Return the last caller since that has to be the test class.
$caller = $last_caller;
break;
}
// If the test method is implemented by a test class's parent then the
// class name of $this will not be part of the backtrace.
// In that case we process the backtrace until the caller is not a
// subclass of $this and return the previous caller.
if (isset($last_caller) && (!isset($caller['class']) || !is_subclass_of($this, $caller['class']))) {
// Return the last caller since that has to be the test class.
$caller = $last_caller;
break;
}
if (isset($caller['class']) && $caller['class'] === get_class($this)) {
break;
}
// Otherwise we have not reached our test class yet: save the last caller
// and remove an element from to backtrace to process the next call.
$last_caller = $caller;
array_shift($backtrace);
}
return $caller;
}
/**
* Transforms a nested array into a flat array suitable for drupalPostForm().
*
* @param array $values
* A multi-dimensional form values array to convert.
*
* @return array
* The flattened $edit array suitable for BrowserTestBase::drupalPostForm().
*/
protected function translatePostValues(array $values) {
$edit = [];
// The easiest and most straightforward way to translate values suitable for
// BrowserTestBase::drupalPostForm() is to actually build the POST data
// string and convert the resulting key/value pairs back into a flat array.
$query = http_build_query($values);
foreach (explode('&', $query) as $item) {
list($key, $value) = explode('=', $item);
$edit[urldecode($key)] = urldecode($value);
}
return $edit;
}
}
@@ -0,0 +1,67 @@
<?php
namespace Drupal\Tests\Component\Annotation;
use Drupal\Component\Annotation\Plugin\Discovery\AnnotatedClassDiscovery;
use Drupal\Component\FileCache\FileCacheFactory;
use PHPUnit\Framework\TestCase;
/**
* @coversDefaultClass \Drupal\Component\Annotation\Plugin\Discovery\AnnotatedClassDiscovery
* @group Annotation
* @runTestsInSeparateProcesses
*/
class AnnotatedClassDiscoveryCachedTest extends TestCase {
/**
* {@inheritdoc}
*/
protected function setUp() {
parent::setUp();
// Ensure FileCacheFactory::DISABLE_CACHE is *not* set, since we're testing
// integration with the file cache.
FileCacheFactory::setConfiguration([]);
// Ensure that FileCacheFactory has a prefix.
FileCacheFactory::setPrefix('prefix');
}
/**
* Test that getDefinitions() retrieves the file cache correctly.
*
* @covers ::getDefinitions
*/
public function testGetDefinitions() {
// Path to the classes which we'll discover and parse annotation.
$discovery_path = __DIR__ . '/Fixtures';
// File path that should be discovered within that directory.
$file_path = $discovery_path . '/PluginNamespace/DiscoveryTest1.php';
$discovery = new AnnotatedClassDiscovery(['com\example' => [$discovery_path]]);
$this->assertEquals([
'discovery_test_1' => [
'id' => 'discovery_test_1',
'class' => 'com\example\PluginNamespace\DiscoveryTest1',
],
], $discovery->getDefinitions());
// Gain access to the file cache so we can change it.
$ref_file_cache = new \ReflectionProperty($discovery, 'fileCache');
$ref_file_cache->setAccessible(TRUE);
/* @var $file_cache \Drupal\Component\FileCache\FileCacheInterface */
$file_cache = $ref_file_cache->getValue($discovery);
// The file cache is keyed by the file path, and we'll add some known
// content to test against.
$file_cache->set($file_path, [
'id' => 'wrong_id',
'content' => serialize(['an' => 'array']),
]);
// Now perform the same query and check for the cached results.
$this->assertEquals([
'wrong_id' => [
'an' => 'array',
],
], $discovery->getDefinitions());
}
}
@@ -0,0 +1,101 @@
<?php
namespace Drupal\Tests\Component\Annotation;
use Drupal\Component\Annotation\Plugin;
use Drupal\Component\Annotation\Plugin\Discovery\AnnotatedClassDiscovery;
use Drupal\Component\FileCache\FileCacheFactory;
use PHPUnit\Framework\TestCase;
/**
* @coversDefaultClass \Drupal\Component\Annotation\Plugin\Discovery\AnnotatedClassDiscovery
* @group Annotation
* @runTestsInSeparateProcesses
*/
class AnnotatedClassDiscoveryTest extends TestCase {
/**
* {@inheritdoc}
*/
protected function setUp() {
parent::setUp();
// Ensure the file cache is disabled.
FileCacheFactory::setConfiguration([FileCacheFactory::DISABLE_CACHE => TRUE]);
// Ensure that FileCacheFactory has a prefix.
FileCacheFactory::setPrefix('prefix');
}
/**
* @covers ::__construct
* @covers ::getPluginNamespaces
*/
public function testGetPluginNamespaces() {
$discovery = new AnnotatedClassDiscovery(['com/example' => [__DIR__]]);
$reflection = new \ReflectionMethod($discovery, 'getPluginNamespaces');
$reflection->setAccessible(TRUE);
$result = $reflection->invoke($discovery);
$this->assertEquals(['com/example' => [__DIR__]], $result);
}
/**
* @covers ::getDefinitions
* @covers ::prepareAnnotationDefinition
* @covers ::getAnnotationReader
*/
public function testGetDefinitions() {
$discovery = new AnnotatedClassDiscovery(['com\example' => [__DIR__ . '/Fixtures']]);
$this->assertEquals([
'discovery_test_1' => [
'id' => 'discovery_test_1',
'class' => 'com\example\PluginNamespace\DiscoveryTest1',
],
], $discovery->getDefinitions());
$custom_annotation_discovery = new AnnotatedClassDiscovery(['com\example' => [__DIR__ . '/Fixtures']], CustomPlugin::class, ['Drupal\Tests\Component\Annotation']);
$this->assertEquals([
'discovery_test_1' => [
'id' => 'discovery_test_1',
'class' => 'com\example\PluginNamespace\DiscoveryTest1',
'title' => 'Discovery test plugin',
],
], $custom_annotation_discovery->getDefinitions());
$empty_discovery = new AnnotatedClassDiscovery(['com\example' => [__DIR__ . '/Fixtures']], CustomPlugin2::class, ['Drupal\Tests\Component\Annotation']);
$this->assertEquals([], $empty_discovery->getDefinitions());
}
}
/**
* Custom plugin annotation.
*
* @Annotation
*/
class CustomPlugin extends Plugin {
/**
* The plugin ID.
*
* @var string
*/
public $id;
/**
* The plugin title.
*
* @var string
*
* @ingroup plugin_translatable
*/
public $title = '';
}
/**
* Custom plugin annotation.
*
* @Annotation
*/
class CustomPlugin2 extends Plugin {}
@@ -0,0 +1,55 @@
<?php
namespace Drupal\Tests\Component\Annotation;
use Drupal\Component\Annotation\AnnotationBase;
use PHPUnit\Framework\TestCase;
/**
* @coversDefaultClass \Drupal\Component\Annotation\AnnotationBase
* @group Annotation
*/
class AnnotationBaseTest extends TestCase {
/**
* @covers ::getProvider
* @covers ::setProvider
*/
public function testSetProvider() {
$plugin = new AnnotationBaseStub();
$plugin->setProvider('example');
$this->assertEquals('example', $plugin->getProvider());
}
/**
* @covers ::getId
*/
public function testGetId() {
$plugin = new AnnotationBaseStub();
// Doctrine sets the public prop directly.
$plugin->id = 'example';
$this->assertEquals('example', $plugin->getId());
}
/**
* @covers ::getClass
* @covers ::setClass
*/
public function testSetClass() {
$plugin = new AnnotationBaseStub();
$plugin->setClass('example');
$this->assertEquals('example', $plugin->getClass());
}
}
/**
* {@inheritdoc}
*/
class AnnotationBaseStub extends AnnotationBase {
/**
* {@inheritdoc}
*/
public function get() {}
}
@@ -0,0 +1,43 @@
<?php
namespace Drupal\Tests\Component\Annotation;
use Drupal\Component\Annotation\Doctrine\DocParser;
use PHPUnit\Framework\TestCase;
/**
* @coversDefaultClass \Drupal\Component\Annotation\Doctrine\DocParser
*
* @group Annotation
*/
class DocParserIgnoredClassesTest extends TestCase {
/**
* Ensure annotations can be ignored when namespaces are present.
*
* Drupal's DocParser should never use class_exists() on an ignored
* annotation, including cases where namespaces are set.
*/
public function testIgnoredAnnotationSkippedBeforeReflection() {
$annotation = 'neverReflectThis';
$parser = new DocParser();
$parser->setIgnoredAnnotationNames([$annotation => TRUE]);
$parser->addNamespace('\\Arbitrary\\Namespace');
// Register our class loader which will fail if the parser tries to
// autoload disallowed annotations.
$autoloader = function ($class_name) use ($annotation) {
$name_array = explode('\\', $class_name);
$name = array_pop($name_array);
if ($name == $annotation) {
$this->fail('Attempted to autoload an ignored annotation: ' . $name);
}
};
spl_autoload_register($autoloader, TRUE, TRUE);
// Perform the parse.
$this->assertEmpty($parser->parse('@neverReflectThis'));
// Clean up after ourselves.
spl_autoload_unregister($autoloader);
}
}
File diff suppressed because it is too large Load Diff
@@ -0,0 +1,11 @@
<?php
// @codingStandardsIgnoreFile
namespace Drupal\Tests\Component\Annotation\Doctrine\Fixtures\Annotation;
/** @Annotation */
class AnnotWithDefaultValue
{
/** @var string */
public $foo = 'bar';
}
@@ -0,0 +1,11 @@
<?php
// @codingStandardsIgnoreFile
namespace Drupal\Tests\Component\Annotation\Doctrine\Fixtures\Annotation;
/**
* @Annotation
*/
class Autoload
{
}
@@ -0,0 +1,12 @@
<?php
// @codingStandardsIgnoreFile
namespace Drupal\Tests\Component\Annotation\Doctrine\Fixtures\Annotation;
/** @Annotation */
class Route
{
/** @var string @Required */
public $pattern;
public $name;
}
@@ -0,0 +1,19 @@
<?php
// @codingStandardsIgnoreFile
namespace Drupal\Tests\Component\Annotation\Doctrine\Fixtures\Annotation;
/** @Annotation */
class Secure
{
private $roles;
public function __construct(array $values)
{
if (is_string($values['value'])) {
$values['value'] = array($values['value']);
}
$this->roles = $values['value'];
}
}
@@ -0,0 +1,15 @@
<?php
// @codingStandardsIgnoreFile
namespace Drupal\Tests\Component\Annotation\Doctrine\Fixtures\Annotation;
/** @Annotation */
class Template
{
private $name;
public function __construct(array $values)
{
$this->name = isset($values['value']) ? $values['value'] : null;
}
}
@@ -0,0 +1,12 @@
<?php
// @codingStandardsIgnoreFile
namespace Drupal\Tests\Component\Annotation\Doctrine\Fixtures\Annotation;
/**
* @Annotation
* @Target("PROPERTY")
*/
final class Version
{
}
@@ -0,0 +1,22 @@
<?php
// @codingStandardsIgnoreFile
namespace Drupal\Tests\Component\Annotation\Doctrine\Fixtures;
/**
* @Annotation
* @Target("ALL")
*/
final class AnnotationEnum
{
const ONE = 'ONE';
const TWO = 'TWO';
const THREE = 'THREE';
/**
* @var mixed
*
* @Enum({"ONE","TWO","THREE"})
*/
public $value;
}
@@ -0,0 +1,18 @@
<?php
// @codingStandardsIgnoreFile
namespace Drupal\Tests\Component\Annotation\Doctrine\Fixtures;
/**
* @Annotation
* @Target("ALL")
*/
final class AnnotationEnumInvalid
{
/**
* @var mixed
*
* @Enum({1, 2, "foo", "bar", {"foo":"bar"}})
*/
public $value;
}
@@ -0,0 +1,35 @@
<?php
// @codingStandardsIgnoreFile
namespace Drupal\Tests\Component\Annotation\Doctrine\Fixtures;
use Drupal\Tests\Component\Annotation\Doctrine\Fixtures\AnnotationEnumLiteral as SelfEnum;
/**
* @Annotation
* @Target("ALL")
*/
final class AnnotationEnumLiteral
{
const ONE = 1;
const TWO = 2;
const THREE = 3;
/**
* @var mixed
*
* @Enum(
* value = {
* 1,
* 2,
* 3,
* },
* literal = {
* 1 : "AnnotationEnumLiteral::ONE",
* 2 : "AnnotationEnumLiteral::TWO",
* 3 : "AnnotationEnumLiteral::THREE",
* }
* )
*/
public $value;
}
@@ -0,0 +1,32 @@
<?php
// @codingStandardsIgnoreFile
namespace Drupal\Tests\Component\Annotation\Doctrine\Fixtures;
/**
* @Annotation
* @Target("ALL")
*/
final class AnnotationEnumLiteralInvalid
{
const ONE = 1;
const TWO = 2;
const THREE = 3;
/**
* @var mixed
*
* @Enum(
* value = {
* 1,
* 2
* },
* literal = {
* 1 : "AnnotationEnumLiteral::ONE",
* 2 : "AnnotationEnumLiteral::TWO",
* 3 : "AnnotationEnumLiteral::THREE"
* }
* )
*/
public $value;
}
@@ -0,0 +1,15 @@
<?php
// @codingStandardsIgnoreFile
namespace Drupal\Tests\Component\Annotation\Doctrine\Fixtures;
/**
* @Annotation
* @Target("ALL")
*/
class AnnotationTargetAll
{
public $data;
public $name;
public $target;
}
@@ -0,0 +1,15 @@
<?php
// @codingStandardsIgnoreFile
namespace Drupal\Tests\Component\Annotation\Doctrine\Fixtures;
/**
* @Annotation
* @Target({ "ANNOTATION" })
*/
final class AnnotationTargetAnnotation
{
public $data;
public $name;
public $target;
}
@@ -0,0 +1,16 @@
<?php
// @codingStandardsIgnoreFile
namespace Drupal\Tests\Component\Annotation\Doctrine\Fixtures;
/**
* @Annotation
* @Target("CLASS")
*/
final class AnnotationTargetClass
{
public $data;
public $name;
public $target;
}
@@ -0,0 +1,15 @@
<?php
// @codingStandardsIgnoreFile
namespace Drupal\Tests\Component\Annotation\Doctrine\Fixtures;
/**
* @Annotation
* @Target({ "METHOD", "PROPERTY" })
*/
final class AnnotationTargetPropertyMethod
{
public $data;
public $name;
public $target;
}
@@ -0,0 +1,130 @@
<?php
// @codingStandardsIgnoreFile
namespace Drupal\Tests\Component\Annotation\Doctrine\Fixtures;
/**
* @Annotation
* @Target("ALL")
* @Attributes({
@Attribute("mixed", type = "mixed"),
@Attribute("boolean", type = "boolean"),
@Attribute("bool", type = "bool"),
@Attribute("float", type = "float"),
@Attribute("string", type = "string"),
@Attribute("integer", type = "integer"),
@Attribute("array", type = "array"),
@Attribute("arrayOfIntegers", type = "array<integer>"),
@Attribute("arrayOfStrings", type = "string[]"),
@Attribute("annotation", type = "Drupal\Tests\Component\Annotation\Doctrine\Fixtures\AnnotationTargetAll"),
@Attribute("arrayOfAnnotations", type = "array<Drupal\Tests\Component\Annotation\Doctrine\Fixtures\AnnotationTargetAll>"),
})
*/
final class AnnotationWithAttributes
{
public final function __construct(array $data)
{
foreach ($data as $key => $value) {
$this->$key = $value;
}
}
private $mixed;
private $boolean;
private $bool;
private $float;
private $string;
private $integer;
private $array;
private $annotation;
private $arrayOfIntegers;
private $arrayOfStrings;
private $arrayOfAnnotations;
/**
* @return mixed
*/
public function getMixed()
{
return $this->mixed;
}
/**
* @return boolean
*/
public function getBoolean()
{
return $this->boolean;
}
/**
* @return bool
*/
public function getBool()
{
return $this->bool;
}
/**
* @return float
*/
public function getFloat()
{
return $this->float;
}
/**
* @return string
*/
public function getString()
{
return $this->string;
}
public function getInteger()
{
return $this->integer;
}
/**
* @return array
*/
public function getArray()
{
return $this->array;
}
/**
* @return Drupal\Tests\Component\Annotation\Doctrine\Fixtures\AnnotationTargetAll
*/
public function getAnnotation()
{
return $this->annotation;
}
/**
* @return string[]
*/
public function getArrayOfStrings()
{
return $this->arrayOfIntegers;
}
/**
* @return array<integer>
*/
public function getArrayOfIntegers()
{
return $this->arrayOfIntegers;
}
/**
* @return array<Drupal\Tests\Component\Annotation\Doctrine\Fixtures\AnnotationTargetAll>
*/
public function getArrayOfAnnotations()
{
return $this->arrayOfAnnotations;
}
}
@@ -0,0 +1,21 @@
<?php
// @codingStandardsIgnoreFile
namespace Drupal\Tests\Component\Annotation\Doctrine\Fixtures;
/**
* @Annotation
* @Target("ALL")
*/
final class AnnotationWithConstants
{
const INTEGER = 1;
const FLOAT = 1.2;
const STRING = '1.2.3';
/**
* @var mixed
*/
public $value;
}
@@ -0,0 +1,51 @@
<?php
// @codingStandardsIgnoreFile
namespace Drupal\Tests\Component\Annotation\Doctrine\Fixtures;
/**
* @Annotation
* @Target("ALL")
* @Attributes({
@Attribute("value", required = true , type = "string"),
@Attribute("annot", required = true , type = "Drupal\Tests\Component\Annotation\Doctrine\Fixtures\AnnotationTargetAnnotation"),
})
*/
final class AnnotationWithRequiredAttributes
{
public final function __construct(array $data)
{
foreach ($data as $key => $value) {
$this->$key = $value;
}
}
/**
* @var string
*/
private $value;
/**
*
* @var Drupal\Tests\Component\Annotation\Doctrine\Fixtures\AnnotationTargetAnnotation
*/
private $annot;
/**
* @return string
*/
public function getValue()
{
return $this->value;
}
/**
* @return Drupal\Tests\Component\Annotation\Doctrine\Fixtures\AnnotationTargetAnnotation
*/
public function getAnnot()
{
return $this->annot;
}
}
@@ -0,0 +1,25 @@
<?php
// @codingStandardsIgnoreFile
namespace Drupal\Tests\Component\Annotation\Doctrine\Fixtures;
/**
* @Annotation
* @Target("ALL")
*/
final class AnnotationWithRequiredAttributesWithoutContructor
{
/**
* @Required
* @var string
*/
public $value;
/**
* @Required
* @var Drupal\Tests\Component\Annotation\Doctrine\Fixtures\AnnotationTargetAnnotation
*/
public $annot;
}
@@ -0,0 +1,12 @@
<?php
// @codingStandardsIgnoreFile
namespace Drupal\Tests\Component\Annotation\Doctrine\Fixtures;
/**
* @Annotation
* @Target(@)
*/
final class AnnotationWithTargetSyntaxError
{
}
@@ -0,0 +1,68 @@
<?php
// @codingStandardsIgnoreFile
namespace Drupal\Tests\Component\Annotation\Doctrine\Fixtures;
/**
* @Annotation
* @Target("ALL")
*/
final class AnnotationWithVarType
{
/**
* @var mixed
*/
public $mixed;
/**
* @var boolean
*/
public $boolean;
/**
* @var bool
*/
public $bool;
/**
* @var float
*/
public $float;
/**
* @var string
*/
public $string;
/**
* @var integer
*/
public $integer;
/**
* @var array
*/
public $array;
/**
* @var Drupal\Tests\Component\Annotation\Doctrine\Fixtures\AnnotationTargetAll
*/
public $annotation;
/**
* @var array<integer>
*/
public $arrayOfIntegers;
/**
* @var string[]
*/
public $arrayOfStrings;
/**
* @var array<Drupal\Tests\Component\Annotation\Doctrine\Fixtures\AnnotationTargetAll>
*/
public $arrayOfAnnotations;
}
@@ -0,0 +1,12 @@
<?php
// @codingStandardsIgnoreFile
namespace Drupal\Tests\Component\Annotation\Doctrine\Fixtures;
class ClassWithConstants
{
const SOME_VALUE = 'ClassWithConstants.SOME_VALUE';
const SOME_KEY = 'ClassWithConstants.SOME_KEY';
const OTHER_KEY_ = 'ClassWithConstants.OTHER_KEY_';
const OTHER_KEY_2 = 'ClassWithConstants.OTHER_KEY_2';
}
@@ -0,0 +1,18 @@
<?php
// @codingStandardsIgnoreFile
namespace Drupal\Tests\Component\Annotation\Doctrine\Fixtures;
use Drupal\Tests\Component\Annotation\Doctrine\Fixtures\AnnotationTargetPropertyMethod;
/**
* @AnnotationTargetPropertyMethod("Some data")
*/
class ClassWithInvalidAnnotationTargetAtClass
{
/**
* @AnnotationTargetPropertyMethod("Bar")
*/
public $foo;
}
@@ -0,0 +1,21 @@
<?php
// @codingStandardsIgnoreFile
namespace Drupal\Tests\Component\Annotation\Doctrine\Fixtures;
use Drupal\Tests\Component\Annotation\Doctrine\Fixtures\AnnotationTargetClass;
/**
* @AnnotationTargetClass("Some data")
*/
class ClassWithInvalidAnnotationTargetAtMethod
{
/**
* @AnnotationTargetClass("functionName")
*/
public function functionName($param)
{
}
}
@@ -0,0 +1,25 @@
<?php
// @codingStandardsIgnoreFile
namespace Drupal\Tests\Component\Annotation\Doctrine\Fixtures;
use Drupal\Tests\Component\Annotation\Doctrine\Fixtures\AnnotationTargetClass;
use Drupal\Tests\Component\Annotation\Doctrine\Fixtures\AnnotationTargetAnnotation;
/**
* @AnnotationTargetClass("Some data")
*/
class ClassWithInvalidAnnotationTargetAtProperty
{
/**
* @AnnotationTargetClass("Bar")
*/
public $foo;
/**
* @AnnotationTargetAnnotation("Foo")
*/
public $bar;
}
@@ -0,0 +1,41 @@
<?php
// @codingStandardsIgnoreFile
namespace Drupal\Tests\Component\Annotation\Doctrine\Fixtures;
use Drupal\Tests\Component\Annotation\Doctrine\Fixtures\AnnotationTargetClass;
use Drupal\Tests\Component\Annotation\Doctrine\Fixtures\AnnotationTargetAll;
use Drupal\Tests\Component\Annotation\Doctrine\Fixtures\AnnotationTargetPropertyMethod;
/**
* @AnnotationTargetClass("Some data")
*/
class ClassWithValidAnnotationTarget
{
/**
* @AnnotationTargetPropertyMethod("Some data")
*/
public $foo;
/**
* @AnnotationTargetAll("Some data",name="Some name")
*/
public $name;
/**
* @AnnotationTargetPropertyMethod("Some data",name="Some name")
*/
public function someFunction()
{
}
/**
* @AnnotationTargetAll(@AnnotationTargetAnnotation)
*/
public $nested;
}
@@ -0,0 +1,11 @@
<?php
// @codingStandardsIgnoreFile
namespace Drupal\Tests\Component\Annotation\Doctrine\Fixtures;
interface IntefaceWithConstants
{
const SOME_VALUE = 'IntefaceWithConstants.SOME_VALUE';
const SOME_KEY = 'IntefaceWithConstants.SOME_KEY';
}
@@ -0,0 +1,15 @@
<?php
// @codingStandardsIgnoreFile
// Some class named Entity in the global namespace
/**
* This class is a near-copy of
* tests/Doctrine/Tests/Common/Annotations/Ticket/DCOM58Entity.php, which is
* part of the Doctrine project: <http://www.doctrine-project.org>. It was
* copied from version 1.2.7.
*
* @Annotation
*/
class Entity
{
}
@@ -0,0 +1,118 @@
<?php
// @codingStandardsIgnoreFile
namespace Drupal\Tests\Component\Annotation\Doctrine\Ticket;
use Drupal\Component\Annotation\Doctrine\DocParser;
use Drupal\Component\Annotation\Doctrine\SimpleAnnotationReader;
use PHPUnit\Framework\TestCase;
/**
* This class is a near-copy of
* \Doctrine\Tests\Common\Annotations\Ticket\DCOM58Test, which is part of the
* Doctrine project: <http://www.doctrine-project.org>. It was copied from
* version 1.2.7.
*
* @group DCOM58
*
* Run this test in a separate process as it includes code that might have side
* effects.
* @runTestsInSeparateProcesses
*/
class DCOM58Test extends TestCase
{
public function setUp()
{
// Some class named Entity in the global namespace.
include __DIR__ .'/DCOM58Entity.php';
}
public function testIssueGlobalNamespace()
{
$docblock = "@Entity";
$parser = new DocParser();
$parser->setImports(array(
"__NAMESPACE__" =>"Drupal\Tests\Component\Annotation\Doctrine\Ticket\Doctrine\ORM\Mapping"
));
$annots = $parser->parse($docblock);
$this->assertCount(1, $annots);
$this->assertInstanceOf("Drupal\Tests\Component\Annotation\Doctrine\Ticket\Doctrine\ORM\Mapping\Entity", $annots[0]);
}
public function testIssueNamespaces()
{
$docblock = "@Entity";
$parser = new DocParser();
$parser->addNamespace("Drupal\Tests\Component\Annotation\Doctrine\Ticket\Doctrine\ORM");
$annots = $parser->parse($docblock);
$this->assertCount(1, $annots);
$this->assertInstanceOf("Drupal\Tests\Component\Annotation\Doctrine\Ticket\Doctrine\ORM\Entity", $annots[0]);
}
public function testIssueMultipleNamespaces()
{
$docblock = "@Entity";
$parser = new DocParser();
$parser->addNamespace("Drupal\Tests\Component\Annotation\Doctrine\Ticket\Doctrine\ORM\Mapping");
$parser->addNamespace("Drupal\Tests\Component\Annotation\Doctrine\Ticket\Doctrine\ORM");
$annots = $parser->parse($docblock);
$this->assertCount(1, $annots);
$this->assertInstanceOf("Drupal\Tests\Component\Annotation\Doctrine\Ticket\Doctrine\ORM\Mapping\Entity", $annots[0]);
}
public function testIssueWithNamespacesOrImports()
{
$docblock = "@Entity";
$parser = new DocParser();
$annots = $parser->parse($docblock);
$this->assertCount(1, $annots);
$this->assertInstanceOf("Entity", $annots[0]);
$this->assertCount(1, $annots);
}
public function testIssueSimpleAnnotationReader()
{
$reader = new SimpleAnnotationReader();
$reader->addNamespace('Drupal\Tests\Component\Annotation\Doctrine\Ticket\Doctrine\ORM\Mapping');
$annots = $reader->getClassAnnotations(new \ReflectionClass(__NAMESPACE__."\MappedClass"));
$this->assertCount(1, $annots);
$this->assertInstanceOf("Drupal\Tests\Component\Annotation\Doctrine\Ticket\Doctrine\ORM\Mapping\Entity", $annots[0]);
}
}
/**
* @Entity
*/
class MappedClass
{
}
namespace Drupal\Tests\Component\Annotation\Doctrine\Ticket\Doctrine\ORM\Mapping;
/**
* @Annotation
*/
class Entity
{
}
namespace Drupal\Tests\Component\Annotation\Doctrine\Ticket\Doctrine\ORM;
/**
* @Annotation
*/
class Entity
{
}
@@ -0,0 +1,16 @@
All files within this directory where copied from the Doctrine project: <http://www.doctrine-project.org>
They were copied from version 1.2.7.
Original copyright:
Copyright (c) 2006-2013 Doctrine Project
Permission is hereby granted, free of charge, to any person obtaining a copy of
this software and associated documentation files (the "Software"), to deal in
the Software without restriction, including without limitation the rights to
use, copy, modify, merge, publish, distribute, sublicense, and/or sell copies
of the Software, and to permit persons to whom the Software is furnished to do
so, subject to the following conditions:
The above copyright notice and this permission notice shall be included in all
copies or substantial portions of the Software.
@@ -0,0 +1,16 @@
<?php
namespace com\example\PluginNamespace;
/**
* Provides a custom test plugin.
*
* @Plugin(
* id = "discovery_test_1"
* )
* @CustomPlugin(
* id = "discovery_test_1",
* title = "Discovery test plugin"
* )
*/
class DiscoveryTest1 {}
@@ -0,0 +1,2 @@
# This should not be loaded by our annotated class discovery.
id:discovery_test_2
@@ -0,0 +1,24 @@
<?php
namespace Drupal\Tests\Component\Annotation;
use Drupal\Component\Annotation\Reflection\MockFileFinder;
use PHPUnit\Framework\TestCase;
/**
* @coversDefaultClass \Drupal\Component\Annotation\Reflection\MockFileFinder
* @group Annotation
*/
class MockFileFinderTest extends TestCase {
/**
* @covers ::create
* @covers ::findFile
*/
public function testFindFile() {
$tmp = MockFileFinder::create('testfilename.txt');
$this->assertEquals('testfilename.txt', $tmp->findFile('n/a'));
$this->assertEquals('testfilename.txt', $tmp->findFile('someclass'));
}
}
@@ -0,0 +1,69 @@
<?php
namespace Drupal\Tests\Component\Annotation\Plugin\Discovery;
use Drupal\Component\Annotation\Plugin;
use Drupal\Component\Annotation\Plugin\Discovery\AnnotationBridgeDecorator;
use Drupal\Component\Plugin\Definition\PluginDefinition;
use Drupal\Component\Plugin\Discovery\DiscoveryInterface;
use PHPUnit\Framework\TestCase;
/**
* @coversDefaultClass \Drupal\Component\Annotation\Plugin\Discovery\AnnotationBridgeDecorator
* @group Plugin
*/
class AnnotationBridgeDecoratorTest extends TestCase {
/**
* @covers ::getDefinitions
*/
public function testGetDefinitions() {
$definitions = [];
$definitions['object'] = new ObjectDefinition(['id' => 'foo']);
$definitions['array'] = ['id' => 'bar'];
$discovery = $this->prophesize(DiscoveryInterface::class);
$discovery->getDefinitions()->willReturn($definitions);
$decorator = new AnnotationBridgeDecorator($discovery->reveal(), TestAnnotation::class);
$expected = [
'object' => new ObjectDefinition(['id' => 'foo']),
'array' => new ObjectDefinition(['id' => 'bar']),
];
$this->assertEquals($expected, $decorator->getDefinitions());
}
}
/**
* {@inheritdoc}
*/
class TestAnnotation extends Plugin {
/**
* {@inheritdoc}
*/
public function get() {
return new ObjectDefinition($this->definition);
}
}
/**
* {@inheritdoc}
*/
class ObjectDefinition extends PluginDefinition {
/**
* ObjectDefinition constructor.
*
* @param array $definition
* An array of definition values.
*/
public function __construct(array $definition) {
foreach ($definition as $property => $value) {
$this->{$property} = $value;
}
}
}
@@ -0,0 +1,55 @@
<?php
namespace Drupal\Tests\Component\Annotation;
use Drupal\Component\Annotation\PluginID;
use PHPUnit\Framework\TestCase;
/**
* @coversDefaultClass \Drupal\Component\Annotation\PluginId
* @group Annotation
*/
class PluginIdTest extends TestCase {
/**
* @covers ::get
*/
public function testGet() {
// Assert plugin starts empty regardless of constructor.
$plugin = new PluginID([
'foo' => 'bar',
'biz' => [
'baz' => 'boom',
],
'nestedAnnotation' => new PluginID([
'foo' => 'bar',
]),
'value' => 'biz',
]);
$this->assertEquals([
'id' => NULL,
'class' => NULL,
'provider' => NULL,
], $plugin->get());
// Set values and ensure we can retrieve them.
$plugin->value = 'foo';
$plugin->setClass('bar');
$plugin->setProvider('baz');
$this->assertEquals([
'id' => 'foo',
'class' => 'bar',
'provider' => 'baz',
], $plugin->get());
}
/**
* @covers ::getId
*/
public function testGetId() {
$plugin = new PluginID([]);
$plugin->value = 'example';
$this->assertEquals('example', $plugin->getId());
}
}
@@ -0,0 +1,98 @@
<?php
namespace Drupal\Tests\Component\Annotation;
use Drupal\Component\Annotation\Plugin;
use PHPUnit\Framework\TestCase;
/**
* @coversDefaultClass \Drupal\Component\Annotation\Plugin
* @group Annotation
*/
class PluginTest extends TestCase {
/**
* @covers ::__construct
* @covers ::parse
* @covers ::get
*/
public function testGet() {
// Assert all values are accepted through constructor and default value is
// used for non existent but defined property.
$plugin = new PluginStub([
'foo' => 'bar',
'biz' => [
'baz' => 'boom',
],
'nestedAnnotation' => new Plugin([
'foo' => 'bar',
]),
]);
$this->assertEquals([
// This property wasn't in our definition but is defined as a property on
// our plugin class.
'defaultProperty' => 'testvalue',
'foo' => 'bar',
'biz' => [
'baz' => 'boom',
],
'nestedAnnotation' => [
'foo' => 'bar',
],
], $plugin->get());
// Without default properties, we get a completely empty plugin definition.
$plugin = new Plugin([]);
$this->assertEquals([], $plugin->get());
}
/**
* @covers ::getProvider
*/
public function testGetProvider() {
$plugin = new Plugin(['provider' => 'example']);
$this->assertEquals('example', $plugin->getProvider());
}
/**
* @covers ::setProvider
*/
public function testSetProvider() {
$plugin = new Plugin([]);
$plugin->setProvider('example');
$this->assertEquals('example', $plugin->getProvider());
}
/**
* @covers ::getId
*/
public function testGetId() {
$plugin = new Plugin(['id' => 'example']);
$this->assertEquals('example', $plugin->getId());
}
/**
* @covers ::getClass
*/
public function testGetClass() {
$plugin = new Plugin(['class' => 'example']);
$this->assertEquals('example', $plugin->getClass());
}
/**
* @covers ::setClass
*/
public function testSetClass() {
$plugin = new Plugin([]);
$plugin->setClass('example');
$this->assertEquals('example', $plugin->getClass());
}
}
/**
* {@inheritdoc}
*/
class PluginStub extends Plugin {
protected $defaultProperty = 'testvalue';
}
@@ -0,0 +1,267 @@
<?php
/**
* @file
* Contains \Drupal\Tests\Component\Assertion\InspectorTest.
*/
namespace Drupal\Tests\Component\Assertion;
use PHPUnit\Framework\TestCase;
use Drupal\Component\Assertion\Inspector;
/**
* @coversDefaultClass \Drupal\Component\Assertion\Inspector
* @group Assertion
*/
class InspectorTest extends TestCase {
/**
* Tests asserting argument is an array or traversable object.
*
* @covers ::assertTraversable
*/
public function testAssertTraversable() {
$this->assertTrue(Inspector::assertTraversable([]));
$this->assertTrue(Inspector::assertTraversable(new \ArrayObject()));
$this->assertFalse(Inspector::assertTraversable(new \stdClass()));
$this->assertFalse(Inspector::assertTraversable('foo'));
}
/**
* Tests asserting all members are strings.
*
* @covers ::assertAllStrings
* @dataProvider providerTestAssertAllStrings
*/
public function testAssertAllStrings($input, $expected) {
$this->assertSame($expected, Inspector::assertAllStrings($input));
}
public function providerTestAssertAllStrings() {
$data = [
'empty-array' => [[], TRUE],
'array-with-strings' => [['foo', 'bar'], TRUE],
'string' => ['foo', FALSE],
'array-with-strings-with-colon' => [['foo', 'bar', 'llama:2001988', 'baz', 'llama:14031991'], TRUE],
'with-FALSE' => [[FALSE], FALSE],
'with-TRUE' => [[TRUE], FALSE],
'with-string-and-boolean' => [['foo', FALSE], FALSE],
'with-NULL' => [[NULL], FALSE],
'string-with-NULL' => [['foo', NULL], FALSE],
'integer' => [[1337], FALSE],
'string-and-integer' => [['foo', 1337], FALSE],
'double' => [[3.14], FALSE],
'string-and-double' => [['foo', 3.14], FALSE],
'array' => [[[]], FALSE],
'string-and-array' => [['foo', []], FALSE],
'string-and-nested-array' => [['foo', ['bar']], FALSE],
'object' => [[new \stdClass()], FALSE],
'string-and-object' => [['foo', new StringObject()], FALSE],
];
return $data;
}
/**
* Tests asserting all members are strings or objects with __toString().
*
* @covers ::assertAllStringable
*/
public function testAssertAllStringable() {
$this->assertTrue(Inspector::assertAllStringable([]));
$this->assertTrue(Inspector::assertAllStringable(['foo', 'bar']));
$this->assertFalse(Inspector::assertAllStringable('foo'));
$this->assertTrue(Inspector::assertAllStringable(['foo', new StringObject()]));
}
/**
* Tests asserting all members are arrays.
*
* @covers ::assertAllArrays
*/
public function testAssertAllArrays() {
$this->assertTrue(Inspector::assertAllArrays([]));
$this->assertTrue(Inspector::assertAllArrays([[], []]));
$this->assertFalse(Inspector::assertAllArrays([[], 'foo']));
}
/**
* Tests asserting array is 0-indexed - the strict definition of array.
*
* @covers ::assertStrictArray
*/
public function testAssertStrictArray() {
$this->assertTrue(Inspector::assertStrictArray([]));
$this->assertTrue(Inspector::assertStrictArray(['bar', 'foo']));
$this->assertFalse(Inspector::assertStrictArray(['foo' => 'bar', 'bar' => 'foo']));
}
/**
* Tests asserting all members are strict arrays.
*
* @covers ::assertAllStrictArrays
*/
public function testAssertAllStrictArrays() {
$this->assertTrue(Inspector::assertAllStrictArrays([]));
$this->assertTrue(Inspector::assertAllStrictArrays([[], []]));
$this->assertFalse(Inspector::assertAllStrictArrays([['foo' => 'bar', 'bar' => 'foo']]));
}
/**
* Tests asserting all members have specified keys.
*
* @covers ::assertAllHaveKey
*/
public function testAssertAllHaveKey() {
$this->assertTrue(Inspector::assertAllHaveKey([]));
$this->assertTrue(Inspector::assertAllHaveKey([['foo' => 'bar', 'bar' => 'foo']]));
$this->assertTrue(Inspector::assertAllHaveKey([['foo' => 'bar', 'bar' => 'foo']], 'foo'));
$this->assertTrue(Inspector::assertAllHaveKey([['foo' => 'bar', 'bar' => 'foo']], 'bar', 'foo'));
$this->assertFalse(Inspector::assertAllHaveKey([['foo' => 'bar', 'bar' => 'foo']], 'bar', 'foo', 'moo'));
}
/**
* Tests asserting all members are integers.
*
* @covers ::assertAllIntegers
*/
public function testAssertAllIntegers() {
$this->assertTrue(Inspector::assertAllIntegers([]));
$this->assertTrue(Inspector::assertAllIntegers([1, 2, 3]));
$this->assertFalse(Inspector::assertAllIntegers([1, 2, 3.14]));
$this->assertFalse(Inspector::assertAllIntegers([1, '2', 3]));
}
/**
* Tests asserting all members are floating point variables.
*
* @covers ::assertAllFloat
*/
public function testAssertAllFloat() {
$this->assertTrue(Inspector::assertAllFloat([]));
$this->assertTrue(Inspector::assertAllFloat([1.0, 2.1, 3.14]));
$this->assertFalse(Inspector::assertAllFloat([1, 2.1, 3.14]));
$this->assertFalse(Inspector::assertAllFloat([1.0, '2', 3]));
$this->assertFalse(Inspector::assertAllFloat(['Titanic']));
}
/**
* Tests asserting all members are callable.
*
* @covers ::assertAllCallable
*/
public function testAllCallable() {
$this->assertTrue(Inspector::assertAllCallable([
'strchr',
[$this, 'callMe'],
[__CLASS__, 'callMeStatic'],
function () {
return TRUE;
},
]));
$this->assertFalse(Inspector::assertAllCallable([
'strchr',
[$this, 'callMe'],
[__CLASS__, 'callMeStatic'],
function () {
return TRUE;
},
"I'm not callable",
]));
}
/**
* Tests asserting all members are !empty().
*
* @covers ::assertAllNotEmpty
*/
public function testAllNotEmpty() {
$this->assertTrue(Inspector::assertAllNotEmpty([1, 'two']));
$this->assertFalse(Inspector::assertAllNotEmpty(['']));
}
/**
* Tests asserting all arguments are numbers or strings castable to numbers.
*
* @covers ::assertAllNumeric
*/
public function testAssertAllNumeric() {
$this->assertTrue(Inspector::assertAllNumeric([1, '2', 3.14]));
$this->assertFalse(Inspector::assertAllNumeric([1, 'two', 3.14]));
}
/**
* Tests asserting strstr() or stristr() match.
*
* @covers ::assertAllMatch
*/
public function testAssertAllMatch() {
$this->assertTrue(Inspector::assertAllMatch('f', ['fee', 'fi', 'fo']));
$this->assertTrue(Inspector::assertAllMatch('F', ['fee', 'fi', 'fo']));
$this->assertTrue(Inspector::assertAllMatch('f', ['fee', 'fi', 'fo'], TRUE));
$this->assertFalse(Inspector::assertAllMatch('F', ['fee', 'fi', 'fo'], TRUE));
$this->assertFalse(Inspector::assertAllMatch('e', ['fee', 'fi', 'fo']));
$this->assertFalse(Inspector::assertAllMatch('1', [12]));
}
/**
* Tests asserting regular expression match.
*
* @covers ::assertAllRegularExpressionMatch
*/
public function testAssertAllRegularExpressionMatch() {
$this->assertTrue(Inspector::assertAllRegularExpressionMatch('/f/i', ['fee', 'fi', 'fo']));
$this->assertTrue(Inspector::assertAllRegularExpressionMatch('/F/i', ['fee', 'fi', 'fo']));
$this->assertTrue(Inspector::assertAllRegularExpressionMatch('/f/', ['fee', 'fi', 'fo']));
$this->assertFalse(Inspector::assertAllRegularExpressionMatch('/F/', ['fee', 'fi', 'fo']));
$this->assertFalse(Inspector::assertAllRegularExpressionMatch('/e/', ['fee', 'fi', 'fo']));
$this->assertFalse(Inspector::assertAllRegularExpressionMatch('/1/', [12]));
}
/**
* Tests asserting all members are objects.
*
* @covers ::assertAllObjects
*/
public function testAssertAllObjects() {
$this->assertTrue(Inspector::assertAllObjects([new \ArrayObject(), new \ArrayObject()]));
$this->assertFalse(Inspector::assertAllObjects([new \ArrayObject(), new \ArrayObject(), 'foo']));
$this->assertTrue(Inspector::assertAllObjects([new \ArrayObject(), new \ArrayObject()], '\\Traversable'));
$this->assertFalse(Inspector::assertAllObjects([new \ArrayObject(), new \ArrayObject(), 'foo'], '\\Traversable'));
$this->assertFalse(Inspector::assertAllObjects([new \ArrayObject(), new StringObject()], '\\Traversable'));
$this->assertTrue(Inspector::assertAllObjects([new \ArrayObject(), new StringObject()], '\\Traversable', '\\Drupal\\Tests\\Component\\Assertion\\StringObject'));
$this->assertFalse(Inspector::assertAllObjects([new \ArrayObject(), new StringObject(), new \stdClass()], '\\ArrayObject', '\\Drupal\\Tests\\Component\\Assertion\\StringObject'));
}
/**
* Test method referenced by ::testAllCallable().
*/
public function callMe() {
return TRUE;
}
/**
* Test method referenced by ::testAllCallable().
*/
public static function callMeStatic() {
return TRUE;
}
}
/**
* Quick class for testing for objects with __toString.
*/
class StringObject {
/**
* {@inheritdoc}
*/
public function __toString() {
return 'foo';
}
}
@@ -0,0 +1,156 @@
<?php
namespace Drupal\Tests\Component\Bridge;
use Drupal\Component\Bridge\ZfExtensionManagerSfContainer;
use PHPUnit\Framework\TestCase;
use Symfony\Component\DependencyInjection\ContainerBuilder;
use Symfony\Component\DependencyInjection\Exception\ServiceNotFoundException;
use Laminas\Feed\Reader\Extension\Atom\Entry;
use Laminas\Feed\Reader\StandaloneExtensionManager;
/**
* @coversDefaultClass \Drupal\Component\Bridge\ZfExtensionManagerSfContainer
* @group Bridge
*/
class ZfExtensionManagerSfContainerTest extends TestCase {
/**
* @covers ::setContainer
* @covers ::setStandalone
* @covers ::get
*/
public function testGet() {
$service = new \stdClass();
$service->value = 'myvalue';
$container = new ContainerBuilder();
$container->set('foo', $service);
$bridge = new ZfExtensionManagerSfContainer();
$bridge->setContainer($container);
$this->assertEquals($service, $bridge->get('foo'));
$bridge->setStandalone(StandaloneExtensionManager::class);
$this->assertInstanceOf(Entry::class, $bridge->get('Atom\Entry'));
// Ensure that the container is checked first.
$container->set('atomentry', $service);
$this->assertEquals($service, $bridge->get('Atom\Entry'));
}
/**
* @covers ::setContainer
* @covers ::setStandalone
* @covers ::has
*/
public function testHas() {
$service = new \stdClass();
$service->value = 'myvalue';
$container = new ContainerBuilder();
$container->set('foo', $service);
$bridge = new ZfExtensionManagerSfContainer();
$bridge->setContainer($container);
$this->assertTrue($bridge->has('foo'));
$this->assertFalse($bridge->has('bar'));
$this->assertFalse($bridge->has('Atom\Entry'));
$bridge->setStandalone(StandaloneExtensionManager::class);
$this->assertTrue($bridge->has('Atom\Entry'));
}
/**
* @covers ::setStandalone
*/
public function testSetStandaloneException() {
$this->expectException(\RuntimeException::class);
$this->expectExceptionMessage('Drupal\Tests\Component\Bridge\ZfExtensionManagerSfContainerTest must implement Laminas\Feed\Reader\ExtensionManagerInterface or Laminas\Feed\Writer\ExtensionManagerInterface');
$bridge = new ZfExtensionManagerSfContainer();
$bridge->setStandalone(static::class);
}
/**
* @covers ::get
*/
public function testGetContainerException() {
$this->expectException(ServiceNotFoundException::class);
$this->expectExceptionMessage('You have requested a non-existent service "test.foo".');
$container = new ContainerBuilder();
$bridge = new ZfExtensionManagerSfContainer('test.');
$bridge->setContainer($container);
$bridge->setStandalone(StandaloneExtensionManager::class);
$bridge->get('foo');
}
/**
* @covers ::__construct
* @covers ::has
* @covers ::get
*/
public function testPrefix() {
$service = new \stdClass();
$service->value = 'myvalue';
$container = new ContainerBuilder();
$container->set('foo.bar', $service);
$bridge = new ZfExtensionManagerSfContainer('foo.');
$bridge->setContainer($container);
$this->assertTrue($bridge->has('bar'));
$this->assertFalse($bridge->has('baz'));
$this->assertEquals($service, $bridge->get('bar'));
}
/**
* @covers ::canonicalizeName
* @dataProvider canonicalizeNameProvider
*/
public function testCanonicalizeName($name, $canonical_name) {
$service = new \stdClass();
$service->value = 'myvalue';
$container = new ContainerBuilder();
$container->set($canonical_name, $service);
$bridge = new ZfExtensionManagerSfContainer();
$bridge->setContainer($container);
$this->assertTrue($bridge->has($name));
$this->assertEquals($service, $bridge->get($name));
}
/**
* Data provider for testReverseProxyEnabled.
*
* Replacements:
* array('-' => '', '_' => '', ' ' => '', '\\' => '', '/' => '')
*/
public function canonicalizeNameProvider() {
return [
[
'foobar',
'foobar',
],
[
'foo-bar',
'foobar',
],
[
'foo_bar',
'foobar',
],
[
'foo bar',
'foobar',
],
[
'foo\\bar',
'foobar',
],
[
'foo/bar',
'foobar',
],
// There is also a strtolower in canonicalizeName.
[
'Foo/bAr',
'foobar',
],
[
'foo/-_\\ bar',
'foobar',
],
];
}
}
@@ -0,0 +1,39 @@
<?php
namespace Drupal\Tests\Component\ClassFinder;
use Composer\Autoload\ClassLoader;
use Drupal\Component\ClassFinder\ClassFinder;
use PHPUnit\Framework\TestCase;
/**
* @coversDefaultClass \Drupal\Component\ClassFinder\ClassFinder
* @group ClassFinder
*/
class ClassFinderTest extends TestCase {
/**
* @covers ::findFile
*/
public function testFindFile() {
$finder = new ClassFinder();
// The full path is returned therefore only tests with
// assertStringEndsWith() so the test is portable.
$this->assertStringEndsWith('core/tests/Drupal/Tests/Component/ClassFinder/ClassFinderTest.php', $finder->findFile(ClassFinderTest::class));
$class = 'Not\\A\\Class';
$this->assertNull($finder->findFile($class));
// Register an autoloader that can find this class.
$loader = new ClassLoader();
$loader->addClassMap([$class => __FILE__]);
$loader->register();
$this->assertEquals(__FILE__, $finder->findFile($class));
// This shouldn't prevent us from finding the original file.
$this->assertStringEndsWith('core/tests/Drupal/Tests/Component/ClassFinder/ClassFinderTest.php', $finder->findFile(ClassFinderTest::class));
// Clean up the additional autoloader after the test.
$loader->unregister();
}
}
@@ -0,0 +1,894 @@
<?php
namespace Drupal\Tests\Component\Datetime;
use Drupal\Component\Datetime\DateTimePlus;
use PHPUnit\Framework\TestCase;
/**
* @coversDefaultClass \Drupal\Component\Datetime\DateTimePlus
* @group Datetime
*/
class DateTimePlusTest extends TestCase {
/**
* Test creating dates from string and array input.
*
* @param mixed $input
* Input argument for DateTimePlus.
* @param string $timezone
* Timezone argument for DateTimePlus.
* @param string $expected
* Expected output from DateTimePlus::format().
*
* @dataProvider providerTestDates
*/
public function testDates($input, $timezone, $expected) {
$date = new DateTimePlus($input, $timezone);
$value = $date->format('c');
if (is_array($input)) {
$input = var_export($input, TRUE);
}
$this->assertEquals($expected, $value, sprintf("Test new DateTimePlus(%s, %s): should be %s, found %s.", $input, $timezone, $expected, $value));
}
/**
* Test creating dates from string and array input.
*
* @param mixed $input
* Input argument for DateTimePlus.
* @param string $timezone
* Timezone argument for DateTimePlus.
* @param string $expected
* Expected output from DateTimePlus::format().
*
* @dataProvider providerTestDateArrays
*/
public function testDateArrays($input, $timezone, $expected) {
$date = DateTimePlus::createFromArray($input, $timezone);
$value = $date->format('c');
if (is_array($input)) {
$input = var_export($input, TRUE);
}
$this->assertEquals($expected, $value, sprintf("Test new DateTimePlus(%s, %s): should be %s, found %s.", $input, $timezone, $expected, $value));
}
/**
* Test date diffs.
*
* @param mixed $input1
* A DateTimePlus object.
* @param mixed $input2
* Date argument for DateTimePlus::diff method.
* @param bool $absolute
* Absolute flag for DateTimePlus::diff method.
* @param \DateInterval $expected
* The expected result of the DateTimePlus::diff operation.
*
* @dataProvider providerTestDateDiff
*/
public function testDateDiff($input1, $input2, $absolute, \DateInterval $expected) {
$interval = $input1->diff($input2, $absolute);
$this->assertEquals($interval, $expected);
}
/**
* Test date diff exception caused by invalid input.
*
* @param mixed $input1
* A DateTimePlus object.
* @param mixed $input2
* Date argument for DateTimePlus::diff method.
* @param bool $absolute
* Absolute flag for DateTimePlus::diff method.
*
* @dataProvider providerTestInvalidDateDiff
*/
public function testInvalidDateDiff($input1, $input2, $absolute) {
$this->expectException(\BadMethodCallException::class);
$this->expectExceptionMessage('Method Drupal\Component\Datetime\DateTimePlus::diff expects parameter 1 to be a \DateTime or \Drupal\Component\Datetime\DateTimePlus object');
$interval = $input1->diff($input2, $absolute);
}
/**
* Test creating dates from invalid array input.
*
* @param mixed $input
* Input argument for DateTimePlus.
* @param string $timezone
* Timezone argument for DateTimePlus.
* @param string $class
* The Exception subclass to expect to be thrown.
*
* @dataProvider providerTestInvalidDateArrays
*/
public function testInvalidDateArrays($input, $timezone, $class) {
$this->expectException($class);
$this->assertInstanceOf(
'\Drupal\Component\DateTimePlus',
DateTimePlus::createFromArray($input, $timezone)
);
}
/**
* Test creating dates from timestamps, and manipulating timezones.
*
* @param int $input
* Input argument for DateTimePlus::createFromTimestamp().
* @param array $initial
* An array containing:
* - 'timezone_initial' - Timezone argument for DateTimePlus.
* - 'format_initial' - Format argument for DateTimePlus.
* - 'expected_initial_date' - Expected output from DateTimePlus::format().
* - 'expected_initial_timezone' - Expected output from
* DateTimePlus::getTimeZone()::getName().
* - 'expected_initial_offset' - Expected output from DateTimePlus::getOffset().
* @param array $transform
* An array containing:
* - 'timezone_transform' - Argument to transform date to another timezone via
* DateTimePlus::setTimezone().
* - 'format_transform' - Format argument to use when transforming date to
* another timezone.
* - 'expected_transform_date' - Expected output from DateTimePlus::format(),
* after timezone transform.
* - 'expected_transform_timezone' - Expected output from
* DateTimePlus::getTimeZone()::getName(), after timezone transform.
* - 'expected_transform_offset' - Expected output from
* DateTimePlus::getOffset(), after timezone transform.
*
* @dataProvider providerTestTimestamp
*/
public function testTimestamp($input, array $initial, array $transform) {
// Initialize a new date object.
$date = DateTimePlus::createFromTimestamp($input, $initial['timezone']);
$this->assertDateTimestamp($date, $input, $initial, $transform);
}
/**
* Test creating dates from datetime strings.
*
* @param string $input
* Input argument for DateTimePlus().
* @param array $initial
* @see testTimestamp()
* @param array $transform
* @see testTimestamp()
*
* @dataProvider providerTestDateTimestamp
*/
public function testDateTimestamp($input, array $initial, array $transform) {
// Initialize a new date object.
$date = new DateTimePlus($input, $initial['timezone']);
$this->assertDateTimestamp($date, $input, $initial, $transform);
}
/**
* Assertion helper for testTimestamp and testDateTimestamp since they need
* different dataProviders.
*
* @param \Drupal\Component\Datetime\DateTimePlus $date
* DateTimePlus to test.
* @input mixed $input
* The original input passed to the test method.
* @param array $initial
* @see testTimestamp()
* @param array $transform
* @see testTimestamp()
*/
public function assertDateTimestamp($date, $input, $initial, $transform) {
// Check format.
$value = $date->format($initial['format']);
$this->assertEquals($initial['expected_date'], $value, sprintf("Test new DateTimePlus(%s, %s): should be %s, found %s.", $input, $initial['timezone'], $initial['expected_date'], $value));
// Check timezone name.
$value = $date->getTimeZone()->getName();
$this->assertEquals($initial['expected_timezone'], $value, sprintf("The current timezone is %s: should be %s.", $value, $initial['expected_timezone']));
// Check offset.
$value = $date->getOffset();
$this->assertEquals($initial['expected_offset'], $value, sprintf("The current offset is %s: should be %s.", $value, $initial['expected_offset']));
// Transform the date to another timezone.
$date->setTimezone(new \DateTimeZone($transform['timezone']));
// Check transformed format.
$value = $date->format($transform['format']);
$this->assertEquals($transform['expected_date'], $value, sprintf("Test \$date->setTimezone(new \\DateTimeZone(%s)): should be %s, found %s.", $transform['timezone'], $transform['expected_date'], $value));
// Check transformed timezone.
$value = $date->getTimeZone()->getName();
$this->assertEquals($transform['expected_timezone'], $value, sprintf("The current timezone should be %s, found %s.", $transform['expected_timezone'], $value));
// Check transformed offset.
$value = $date->getOffset();
$this->assertEquals($transform['expected_offset'], $value, sprintf("The current offset should be %s, found %s.", $transform['expected_offset'], $value));
}
/**
* Test creating dates from format strings.
*
* @param string $input
* Input argument for DateTimePlus.
* @param string $timezone
* Timezone argument for DateTimePlus.
* @param string $format_date
* Format argument for DateTimePlus::format().
* @param string $expected
* Expected output from DateTimePlus::format().
*
* @dataProvider providerTestDateFormat
*/
public function testDateFormat($input, $timezone, $format, $format_date, $expected) {
$date = DateTimePlus::createFromFormat($format, $input, $timezone);
$value = $date->format($format_date);
$this->assertEquals($expected, $value, sprintf("Test new DateTimePlus(%s, %s, %s): should be %s, found %s.", $input, $timezone, $format, $expected, $value));
}
/**
* Test invalid date handling.
*
* @param mixed $input
* Input argument for DateTimePlus.
* @param string $timezone
* Timezone argument for DateTimePlus.
* @param string $format
* Format argument for DateTimePlus.
* @param string $message
* Message to print if no errors are thrown by the invalid dates.
* @param string $class
* The Exception subclass to expect to be thrown.
*
* @dataProvider providerTestInvalidDates
*/
public function testInvalidDates($input, $timezone, $format, $message, $class) {
$this->expectException($class);
DateTimePlus::createFromFormat($format, $input, $timezone);
}
/**
* Tests that DrupalDateTime can detect the right timezone to use.
* When specified or not.
*
* @param mixed $input
* Input argument for DateTimePlus.
* @param mixed $timezone
* Timezone argument for DateTimePlus.
* @param string $expected_timezone
* Expected timezone returned from DateTimePlus::getTimezone::getName().
* @param string $message
* Message to print on test failure.
*
* @dataProvider providerTestDateTimezone
*/
public function testDateTimezone($input, $timezone, $expected_timezone, $message) {
$date = new DateTimePlus($input, $timezone);
$timezone = $date->getTimezone()->getName();
$this->assertEquals($timezone, $expected_timezone, $message);
}
/**
* Test that DrupalDateTime can detect the right timezone to use when
* constructed from a datetime object.
*/
public function testDateTimezoneWithDateTimeObject() {
// Create a date object with another date object.
$input = new \DateTime('now', new \DateTimeZone('Pacific/Midway'));
$timezone = NULL;
$expected_timezone = 'Pacific/Midway';
$message = 'DateTimePlus uses the specified timezone if provided.';
$date = DateTimePlus::createFromDateTime($input, $timezone);
$timezone = $date->getTimezone()->getName();
$this->assertEquals($timezone, $expected_timezone, $message);
}
/**
* Provides data for date tests.
*
* @return array
* An array of arrays, each containing the input parameters for
* DateTimePlusTest::testDates().
*
* @see DateTimePlusTest::testDates()
*/
public function providerTestDates() {
$dates = [
// String input.
// Create date object from datetime string.
['2009-03-07 10:30', 'America/Chicago', '2009-03-07T10:30:00-06:00'],
// Same during daylight savings time.
['2009-06-07 10:30', 'America/Chicago', '2009-06-07T10:30:00-05:00'],
// Create date object from date string.
['2009-03-07', 'America/Chicago', '2009-03-07T00:00:00-06:00'],
// Same during daylight savings time.
['2009-06-07', 'America/Chicago', '2009-06-07T00:00:00-05:00'],
// Create date object from date string.
['2009-03-07 10:30', 'Australia/Canberra', '2009-03-07T10:30:00+11:00'],
// Same during daylight savings time.
['2009-06-07 10:30', 'Australia/Canberra', '2009-06-07T10:30:00+10:00'],
];
// On 32-bit systems, timestamps are limited to 1901-2038.
if (PHP_INT_SIZE > 4) {
// Create a date object in the distant past.
// @see https://www.drupal.org/node/2795489#comment-12127088
// Note that this date is after the United States standardized its
// timezones.
$dates[] = ['1883-11-19 10:30', 'America/Chicago', '1883-11-19T10:30:00-06:00'];
// Create a date object in the far future.
$dates[] = ['2345-01-02 02:04', 'UTC', '2345-01-02T02:04:00+00:00'];
}
return $dates;
}
/**
* Provides data for date tests.
*
* @return array
* An array of arrays, each containing the input parameters for
* DateTimePlusTest::testDates().
*
* @see DateTimePlusTest::testDates()
*/
public function providerTestDateArrays() {
$dates = [
// Array input.
// Create date object from date array, date only.
[['year' => 2010, 'month' => 2, 'day' => 28], 'America/Chicago', '2010-02-28T00:00:00-06:00'],
// Create date object from date array with hour.
[['year' => 2010, 'month' => 2, 'day' => 28, 'hour' => 10], 'America/Chicago', '2010-02-28T10:00:00-06:00'],
// Create date object from date array, date only.
[['year' => 2010, 'month' => 2, 'day' => 28], 'Europe/Berlin', '2010-02-28T00:00:00+01:00'],
// Create date object from date array with hour.
[['year' => 2010, 'month' => 2, 'day' => 28, 'hour' => 10], 'Europe/Berlin', '2010-02-28T10:00:00+01:00'],
];
// On 32-bit systems, timestamps are limited to 1901-2038.
if (PHP_INT_SIZE > 4) {
// Create a date object in the distant past.
// @see https://www.drupal.org/node/2795489#comment-12127088
// Note that this date is after the United States standardized its
// timezones.
$dates[] = [['year' => 1883, 'month' => 11, 'day' => 19], 'America/Chicago', '1883-11-19T00:00:00-06:00'];
// Create a date object in the far future.
$dates[] = [['year' => 2345, 'month' => 1, 'day' => 2], 'UTC', '2345-01-02T00:00:00+00:00'];
}
return $dates;
}
/**
* Provides data for testDateFormats.
*
* @return array
* An array of arrays, each containing:
* - 'input' - Input to DateTimePlus.
* - 'timezone' - Timezone for DateTimePlus.
* - 'format' - Date format for DateTimePlus.
* - 'format_date' - Date format for use in $date->format() method.
* - 'expected' - The expected return from DateTimePlus.
*
* @see testDateFormats()
*/
public function providerTestDateFormat() {
return [
// Create a year-only date.
['2009', NULL, 'Y', 'Y', '2009'],
// Create a month and year-only date.
['2009-10', NULL, 'Y-m', 'Y-m', '2009-10'],
// Create a time-only date.
['T10:30:00', NULL, '\TH:i:s', 'H:i:s', '10:30:00'],
// Create a time-only date.
['10:30:00', NULL, 'H:i:s', 'H:i:s', '10:30:00'],
];
}
/**
* Provides data for testInvalidDates.
*
* @return array
* An array of arrays, each containing:
* - 'input' - Input for DateTimePlus.
* - 'timezone' - Timezone for DateTimePlus.
* - 'format' - Format for DateTimePlus.
* - 'message' - Message to display on failure.
*
* @see testInvalidDates
*/
public function providerTestInvalidDates() {
return [
// Test for invalid month names when we are using a short version
// of the month.
['23 abc 2012', NULL, 'd M Y', "23 abc 2012 contains an invalid month name and did not produce errors.", \InvalidArgumentException::class],
// Test for invalid hour.
['0000-00-00T45:30:00', NULL, 'Y-m-d\TH:i:s', "0000-00-00T45:30:00 contains an invalid hour and did not produce errors.", \UnexpectedValueException::class],
// Test for invalid day.
['0000-00-99T05:30:00', NULL, 'Y-m-d\TH:i:s', "0000-00-99T05:30:00 contains an invalid day and did not produce errors.", \UnexpectedValueException::class],
// Test for invalid month.
['0000-75-00T15:30:00', NULL, 'Y-m-d\TH:i:s', "0000-75-00T15:30:00 contains an invalid month and did not produce errors.", \UnexpectedValueException::class],
// Test for invalid year.
['11-08-01T15:30:00', NULL, 'Y-m-d\TH:i:s', "11-08-01T15:30:00 contains an invalid year and did not produce errors.", \UnexpectedValueException::class],
];
}
/**
* Data provider for testInvalidDateArrays.
*
* @return array
* An array of arrays, each containing:
* - 'input' - Input for DateTimePlus.
* - 'timezone' - Timezone for DateTimePlus.
*
* @see testInvalidDateArrays
*/
public function providerTestInvalidDateArrays() {
return [
// One year larger than the documented upper limit of checkdate().
[['year' => 32768, 'month' => 1, 'day' => 8, 'hour' => 8, 'minute' => 0, 'second' => 0], 'America/Chicago', \InvalidArgumentException::class],
// One year smaller than the documented lower limit of checkdate().
[['year' => 0, 'month' => 1, 'day' => 8, 'hour' => 8, 'minute' => 0, 'second' => 0], 'America/Chicago', \InvalidArgumentException::class],
// Test for invalid month from date array.
[['year' => 2010, 'month' => 27, 'day' => 8, 'hour' => 8, 'minute' => 0, 'second' => 0], 'America/Chicago', \InvalidArgumentException::class],
// Test for invalid hour from date array.
[['year' => 2010, 'month' => 2, 'day' => 28, 'hour' => 80, 'minute' => 0, 'second' => 0], 'America/Chicago', \InvalidArgumentException::class],
// Test for invalid minute from date array.
[['year' => 2010, 'month' => 7, 'day' => 8, 'hour' => 8, 'minute' => 88, 'second' => 0], 'America/Chicago', \InvalidArgumentException::class],
// Regression test for https://www.drupal.org/node/2084455.
[['hour' => 59, 'minute' => 1, 'second' => 1], 'America/Chicago', \InvalidArgumentException::class],
];
}
/**
* Provides data for testDateTimezone.
*
* @return array
* An array of arrays, each containing:
* - 'date' - Date string or object for DateTimePlus.
* - 'timezone' - Timezone string for DateTimePlus.
* - 'expected' - Expected return from DateTimePlus::getTimezone()::getName().
* - 'message' - Message to display on test failure.
*
* @see testDateTimezone
*/
public function providerTestDateTimezone() {
// Use a common date for most of the tests.
$date_string = '2007-01-31 21:00:00';
// Detect the system timezone.
$system_timezone = date_default_timezone_get();
return [
// Create a date object with an unspecified timezone, which should
// end up using the system timezone.
[$date_string, NULL, $system_timezone, 'DateTimePlus uses the system timezone when there is no site timezone.'],
// Create a date object with a specified timezone name.
[$date_string, 'America/Yellowknife', 'America/Yellowknife', 'DateTimePlus uses the specified timezone if provided.'],
// Create a date object with a timezone object.
[$date_string, new \DateTimeZone('Australia/Canberra'), 'Australia/Canberra', 'DateTimePlus uses the specified timezone if provided.'],
// Create a date object with another date object.
[new DateTimePlus('now', 'Pacific/Midway'), NULL, 'Pacific/Midway', 'DateTimePlus uses the specified timezone if provided.'],
];
}
/**
* Provides data for testTimestamp.
*
* @return array
* An array of arrays, each containing the arguments required for
* self::testTimestamp().
*
* @see testTimestamp()
*/
public function providerTestTimestamp() {
return [
// Create date object from a unix timestamp and display it in
// local time.
[
'input' => 0,
'initial' => [
'timezone' => 'UTC',
'format' => 'c',
'expected_date' => '1970-01-01T00:00:00+00:00',
'expected_timezone' => 'UTC',
'expected_offset' => 0,
],
'transform' => [
'timezone' => 'America/Los_Angeles',
'format' => 'c',
'expected_date' => '1969-12-31T16:00:00-08:00',
'expected_timezone' => 'America/Los_Angeles',
'expected_offset' => '-28800',
],
],
// Create a date using the timestamp of zero, then display its
// value both in UTC and the local timezone.
[
'input' => 0,
'initial' => [
'timezone' => 'America/Los_Angeles',
'format' => 'c',
'expected_date' => '1969-12-31T16:00:00-08:00',
'expected_timezone' => 'America/Los_Angeles',
'expected_offset' => '-28800',
],
'transform' => [
'timezone' => 'UTC',
'format' => 'c',
'expected_date' => '1970-01-01T00:00:00+00:00',
'expected_timezone' => 'UTC',
'expected_offset' => 0,
],
],
];
}
/**
* Provides data for testDateTimestamp.
*
* @return array
* An array of arrays, each containing the arguments required for
* self::testDateTimestamp().
*
* @see testDateTimestamp()
*/
public function providerTestDateTimestamp() {
return [
// Create date object from datetime string in UTC, and convert
// it to a local date.
[
'input' => '1970-01-01 00:00:00',
'initial' => [
'timezone' => 'UTC',
'format' => 'c',
'expected_date' => '1970-01-01T00:00:00+00:00',
'expected_timezone' => 'UTC',
'expected_offset' => 0,
],
'transform' => [
'timezone' => 'America/Los_Angeles',
'format' => 'c',
'expected_date' => '1969-12-31T16:00:00-08:00',
'expected_timezone' => 'America/Los_Angeles',
'expected_offset' => '-28800',
],
],
// Convert the local time to UTC using string input.
[
'input' => '1969-12-31 16:00:00',
'initial' => [
'timezone' => 'America/Los_Angeles',
'format' => 'c',
'expected_date' => '1969-12-31T16:00:00-08:00',
'expected_timezone' => 'America/Los_Angeles',
'expected_offset' => '-28800',
],
'transform' => [
'timezone' => 'UTC',
'format' => 'c',
'expected_date' => '1970-01-01T00:00:00+00:00',
'expected_timezone' => 'UTC',
'expected_offset' => 0,
],
],
// Convert the local time to UTC using string input.
[
'input' => '1969-12-31 16:00:00',
'initial' => [
'timezone' => 'Europe/Warsaw',
'format' => 'c',
'expected_date' => '1969-12-31T16:00:00+01:00',
'expected_timezone' => 'Europe/Warsaw',
'expected_offset' => '+3600',
],
'transform' => [
'timezone' => 'UTC',
'format' => 'c',
'expected_date' => '1969-12-31T15:00:00+00:00',
'expected_timezone' => 'UTC',
'expected_offset' => 0,
],
],
];
}
/**
* Provides data for date tests.
*
* @return array
* An array of arrays, each containing the input parameters for
* DateTimePlusTest::testDateDiff().
*
* @see DateTimePlusTest::testDateDiff()
*/
public function providerTestDateDiff() {
$empty_interval = new \DateInterval('PT0S');
$positive_19_hours = new \DateInterval('PT19H');
$positive_18_hours = new \DateInterval('PT18H');
$positive_1_hour = new \DateInterval('PT1H');
$negative_1_hour = new \DateInterval('PT1H');
$negative_1_hour->invert = 1;
return [
// There should be a 19 hour time interval between
// new years in Sydney and new years in LA in year 2000.
[
'input2' => DateTimePlus::createFromFormat('Y-m-d H:i:s', '2000-01-01 00:00:00', new \DateTimeZone('Australia/Sydney')),
'input1' => DateTimePlus::createFromFormat('Y-m-d H:i:s', '2000-01-01 00:00:00', new \DateTimeZone('America/Los_Angeles')),
'absolute' => FALSE,
'expected' => $positive_19_hours,
],
// In 1970 Sydney did not observe daylight savings time
// So there is only a 18 hour time interval.
[
'input2' => DateTimePlus::createFromFormat('Y-m-d H:i:s', '1970-01-01 00:00:00', new \DateTimeZone('Australia/Sydney')),
'input1' => DateTimePlus::createFromFormat('Y-m-d H:i:s', '1970-01-01 00:00:00', new \DateTimeZone('America/Los_Angeles')),
'absolute' => FALSE,
'expected' => $positive_18_hours,
],
[
'input1' => DateTimePlus::createFromFormat('U', 3600, new \DateTimeZone('America/Los_Angeles')),
'input2' => DateTimePlus::createFromFormat('U', 0, new \DateTimeZone('UTC')),
'absolute' => FALSE,
'expected' => $negative_1_hour,
],
[
'input1' => DateTimePlus::createFromFormat('U', 3600),
'input2' => DateTimePlus::createFromFormat('U', 0),
'absolute' => FALSE,
'expected' => $negative_1_hour,
],
[
'input1' => DateTimePlus::createFromFormat('U', 3600),
'input2' => \DateTime::createFromFormat('U', 0),
'absolute' => FALSE,
'expected' => $negative_1_hour,
],
[
'input1' => DateTimePlus::createFromFormat('U', 3600),
'input2' => DateTimePlus::createFromFormat('U', 0),
'absolute' => TRUE,
'expected' => $positive_1_hour,
],
[
'input1' => DateTimePlus::createFromFormat('U', 3600),
'input2' => \DateTime::createFromFormat('U', 0),
'absolute' => TRUE,
'expected' => $positive_1_hour,
],
[
'input1' => DateTimePlus::createFromFormat('U', 0),
'input2' => DateTimePlus::createFromFormat('U', 0),
'absolute' => FALSE,
'expected' => $empty_interval,
],
];
}
/**
* Provides data for date tests.
*
* @return array
* An array of arrays, each containing the input parameters for
* DateTimePlusTest::testInvalidDateDiff().
*
* @see DateTimePlusTest::testInvalidDateDiff()
*/
public function providerTestInvalidDateDiff() {
return [
[
'input1' => DateTimePlus::createFromFormat('U', 3600),
'input2' => '1970-01-01 00:00:00',
'absolute' => FALSE,
],
[
'input1' => DateTimePlus::createFromFormat('U', 3600),
'input2' => NULL,
'absolute' => FALSE,
],
];
}
/**
* Tests invalid values passed to constructor.
*
* @param string $time
* A date/time string.
* @param string[] $errors
* An array of error messages.
*
* @covers ::__construct
*
* @dataProvider providerTestInvalidConstructor
*/
public function testInvalidConstructor($time, array $errors) {
$date = new DateTimePlus($time);
$this->assertEquals(TRUE, $date->hasErrors());
$this->assertEquals($errors, $date->getErrors());
}
/**
* Provider for testInvalidConstructor().
*
* @return array
* An array of invalid date/time strings, and corresponding error messages.
*/
public function providerTestInvalidConstructor() {
return [
[
'YYYY-MM-DD',
[
'The timezone could not be found in the database',
'Unexpected character',
'Double timezone specification',
],
],
[
'2017-MM-DD',
[
'Unexpected character',
'The timezone could not be found in the database',
],
],
[
'YYYY-03-DD',
[
'The timezone could not be found in the database',
'Unexpected character',
'Double timezone specification',
],
],
[
'YYYY-MM-07',
[
'The timezone could not be found in the database',
'Unexpected character',
'Double timezone specification',
],
],
[
'2017-13-55',
[
'Unexpected character',
],
],
[
'YYYY-MM-DD hh:mm:ss',
[
'The timezone could not be found in the database',
'Unexpected character',
'Double timezone specification',
],
],
[
'2017-03-07 25:70:80',
[
'Unexpected character',
'Double time specification',
],
],
[
'lorem ipsum dolor sit amet',
[
'The timezone could not be found in the database',
'Double timezone specification',
],
],
];
}
/**
* Tests the $settings['validate_format'] parameter in ::createFromFormat().
*/
public function testValidateFormat() {
// Check that an input that does not strictly follow the input format will
// produce the desired date. In this case the year string '11' doesn't
// precisely match the 'Y' formatter parameter, but PHP will parse it
// regardless. However, when formatted with the same string, the year will
// be output with four digits. With the ['validate_format' => FALSE]
// $settings, this will not thrown an exception.
$date = DateTimePlus::createFromFormat('Y-m-d H:i:s', '11-03-31 17:44:00', 'UTC', ['validate_format' => FALSE]);
$this->assertEquals('0011-03-31 17:44:00', $date->format('Y-m-d H:i:s'));
// Parse the same date with ['validate_format' => TRUE] and make sure we
// get the expected exception.
$this->expectException(\UnexpectedValueException::class);
$date = DateTimePlus::createFromFormat('Y-m-d H:i:s', '11-03-31 17:44:00', 'UTC', ['validate_format' => TRUE]);
}
/**
* Tests setting the default time for date-only objects.
*/
public function testDefaultDateTime() {
$utc = new \DateTimeZone('UTC');
$date = DateTimePlus::createFromFormat('Y-m-d H:i:s', '2017-05-23 22:58:00', $utc);
$this->assertEquals('22:58:00', $date->format('H:i:s'));
$date->setDefaultDateTime();
$this->assertEquals('12:00:00', $date->format('H:i:s'));
}
/**
* Tests that object methods are chainable.
*
* @covers ::__call
*/
public function testChainable() {
$date = new DateTimePlus('now', 'Australia/Sydney');
$date->setTimestamp(12345678);
$rendered = $date->render();
$this->assertEquals('1970-05-24 07:21:18 Australia/Sydney', $rendered);
$date->setTimestamp(23456789);
$rendered = $date->setTimezone(new \DateTimeZone('America/New_York'))->render();
$this->assertEquals('1970-09-29 07:46:29 America/New_York', $rendered);
$date = DateTimePlus::createFromFormat('Y-m-d H:i:s', '1970-05-24 07:21:18', new \DateTimeZone('Australia/Sydney'))
->setTimezone(new \DateTimeZone('America/New_York'));
$rendered = $date->render();
$this->assertInstanceOf(DateTimePlus::class, $date);
$this->assertEquals(12345678, $date->getTimestamp());
$this->assertEquals('1970-05-23 17:21:18 America/New_York', $rendered);
}
/**
* Tests that non-chainable methods work.
*
* @covers ::__call
*/
public function testChainableNonChainable() {
$datetime1 = new DateTimePlus('2009-10-11 12:00:00');
$datetime2 = new DateTimePlus('2009-10-13 12:00:00');
$interval = $datetime1->diff($datetime2);
$this->assertInstanceOf(\DateInterval::class, $interval);
$this->assertEquals('+2 days', $interval->format('%R%a days'));
}
/**
* Tests that chained calls to non-existent functions throw an exception.
*
* @covers ::__call
*/
public function testChainableNonCallable() {
$this->expectException(\BadMethodCallException::class);
$this->expectExceptionMessage('Call to undefined method Drupal\Component\Datetime\DateTimePlus::nonexistent()');
$date = new DateTimePlus('now', 'Australia/Sydney');
$date->setTimezone(new \DateTimeZone('America/New_York'))->nonexistent();
}
/**
* @covers ::getPhpDateTime
*/
public function testGetPhpDateTime() {
$new_york = new \DateTimeZone('America/New_York');
$berlin = new \DateTimeZone('Europe/Berlin');
// Test retrieving a cloned copy of the wrapped \DateTime object, and that
// altering it does not change the DateTimePlus object.
$datetimeplus = DateTimePlus::createFromFormat('Y-m-d H:i:s', '2017-07-13 22:40:00', $new_york, ['langcode' => 'en']);
$this->assertEquals(1500000000, $datetimeplus->getTimestamp());
$this->assertEquals('America/New_York', $datetimeplus->getTimezone()->getName());
$datetime = $datetimeplus->getPhpDateTime();
$this->assertInstanceOf('DateTime', $datetime);
$this->assertEquals(1500000000, $datetime->getTimestamp());
$this->assertEquals('America/New_York', $datetime->getTimezone()->getName());
$datetime->setTimestamp(1400000000)->setTimezone($berlin);
$this->assertEquals(1400000000, $datetime->getTimestamp());
$this->assertEquals('Europe/Berlin', $datetime->getTimezone()->getName());
$this->assertEquals(1500000000, $datetimeplus->getTimestamp());
$this->assertEquals('America/New_York', $datetimeplus->getTimezone()->getName());
}
}
@@ -0,0 +1,144 @@
<?php
namespace Drupal\Tests\Component\Datetime;
use Drupal\Component\Datetime\Time;
use PHPUnit\Framework\TestCase;
use Symfony\Component\HttpFoundation\Request;
/**
* @coversDefaultClass \Drupal\Component\Datetime\Time
* @group Datetime
*
* Isolate the tests to prevent side effects from altering system time.
*
* @runTestsInSeparateProcesses
* @preserveGlobalState disabled
*/
class TimeTest extends TestCase {
/**
* The mocked request stack.
*
* @var \Symfony\Component\HttpFoundation\RequestStack|\PHPUnit\Framework\MockObject\MockObject
*/
protected $requestStack;
/**
* The mocked time class.
*
* @var \Drupal\Component\Datetime\Time
*/
protected $time;
/**
* {@inheritdoc}
*/
protected function setUp() {
parent::setUp();
$this->requestStack = $this->getMockBuilder('Symfony\Component\HttpFoundation\RequestStack')->getMock();
$this->time = new Time($this->requestStack);
}
/**
* Tests the getRequestTime method.
*
* @covers ::getRequestTime
*/
public function testGetRequestTime() {
$expected = 12345678;
$request = Request::createFromGlobals();
$request->server->set('REQUEST_TIME', $expected);
// Mocks a the request stack getting the current request.
$this->requestStack->expects($this->any())
->method('getCurrentRequest')
->willReturn($request);
$this->assertEquals($expected, $this->time->getRequestTime());
}
/**
* Tests the getRequestMicroTime method.
*
* @covers ::getRequestMicroTime
*/
public function testGetRequestMicroTime() {
$expected = 1234567.89;
$request = Request::createFromGlobals();
$request->server->set('REQUEST_TIME_FLOAT', $expected);
// Mocks a the request stack getting the current request.
$this->requestStack->expects($this->any())
->method('getCurrentRequest')
->willReturn($request);
$this->assertEquals($expected, $this->time->getRequestMicroTime());
}
/**
* @covers ::getRequestTime
*/
public function testGetRequestTimeNoRequest() {
$expected = 12345678;
unset($_SERVER['REQUEST_TIME']);
$this->assertEquals($expected, $this->time->getRequestTime());
$_SERVER['REQUEST_TIME'] = 23456789;
$this->assertEquals(23456789, $this->time->getRequestTime());
}
/**
* @covers ::getRequestMicroTime
*/
public function testGetRequestMicroTimeNoRequest() {
$expected = 1234567.89;
unset($_SERVER['REQUEST_TIME_FLOAT']);
$this->assertEquals($expected, $this->time->getRequestMicroTime());
$_SERVER['REQUEST_TIME_FLOAT'] = 2345678.90;
$this->assertEquals(2345678.90, $this->time->getRequestMicroTime());
}
/**
* Tests the getCurrentTime method.
*
* @covers ::getCurrentTime
*/
public function testGetCurrentTime() {
$expected = 12345678;
$this->assertEquals($expected, $this->time->getCurrentTime());
}
/**
* Tests the getCurrentMicroTime method.
*
* @covers ::getCurrentMicroTime
*/
public function testGetCurrentMicroTime() {
$expected = 1234567.89;
$this->assertEquals($expected, $this->time->getCurrentMicroTime());
}
}
namespace Drupal\Component\Datetime;
/**
* Shadow time() system call.
*
* @returns int
*/
function time() {
return 12345678;
}
/**
* Shadow microtime system call.
*
* @returns float
*/
function microtime() {
return 1234567.89;
}
File diff suppressed because it is too large Load Diff
@@ -0,0 +1,716 @@
<?php
/**
* @file
* Contains \Drupal\Tests\Component\DependencyInjection\Dumper\OptimizedPhpArrayDumperTest.
*/
namespace Drupal\Tests\Component\DependencyInjection\Dumper {
use Drupal\Component\Utility\Crypt;
use PHPUnit\Framework\TestCase;
use Symfony\Component\DependencyInjection\Definition;
use Symfony\Component\DependencyInjection\Reference;
use Symfony\Component\DependencyInjection\Parameter;
use Symfony\Component\ExpressionLanguage\Expression;
use Symfony\Component\DependencyInjection\ParameterBag\ParameterBag;
use Symfony\Component\DependencyInjection\ContainerInterface;
use Symfony\Component\DependencyInjection\Exception\InvalidArgumentException;
use Symfony\Component\DependencyInjection\Exception\RuntimeException;
/**
* @coversDefaultClass \Drupal\Component\DependencyInjection\Dumper\OptimizedPhpArrayDumper
* @group DependencyInjection
*/
class OptimizedPhpArrayDumperTest extends TestCase {
/**
* The container builder instance.
*
* @var \Symfony\Component\DependencyInjection\ContainerBuilder
*/
protected $containerBuilder;
/**
* The definition for the container to build in tests.
*
* @var array
*/
protected $containerDefinition;
/**
* Whether the dumper uses the machine-optimized format or not.
*
* @var bool
*/
protected $machineFormat = TRUE;
/**
* Stores the dumper class to use.
*
* @var string
*/
protected $dumperClass = '\Drupal\Component\DependencyInjection\Dumper\OptimizedPhpArrayDumper';
/**
* The dumper instance.
*
* @var \Symfony\Component\DependencyInjection\Dumper\DumperInterface
*/
protected $dumper;
/**
* {@inheritdoc}
*/
protected function setUp() {
// Setup a mock container builder.
$this->containerBuilder = $this->prophesize('\Symfony\Component\DependencyInjection\ContainerBuilder');
$this->containerBuilder->getAliases()->willReturn([]);
$this->containerBuilder->getParameterBag()->willReturn(new ParameterBag());
$this->containerBuilder->getDefinitions()->willReturn(NULL);
$this->containerBuilder->isCompiled()->willReturn(TRUE);
$definition = [];
$definition['aliases'] = [];
$definition['parameters'] = [];
$definition['services'] = [];
$definition['frozen'] = TRUE;
$definition['machine_format'] = $this->machineFormat;
$this->containerDefinition = $definition;
// Create the dumper.
$this->dumper = new $this->dumperClass($this->containerBuilder->reveal());
}
/**
* Tests that an empty container works properly.
*
* @covers ::dump
* @covers ::getArray
* @covers ::supportsMachineFormat
*/
public function testDumpForEmptyContainer() {
$serialized_definition = $this->dumper->dump();
$this->assertEquals(serialize($this->containerDefinition), $serialized_definition);
}
/**
* Tests that alias processing works properly.
*
* @covers ::getAliases
*
* @dataProvider getAliasesDataProvider
*/
public function testGetAliases($aliases, $definition_aliases) {
$this->containerDefinition['aliases'] = $definition_aliases;
$this->containerBuilder->getAliases()->willReturn($aliases);
$this->assertEquals($this->containerDefinition, $this->dumper->getArray(), 'Expected definition matches dump.');
}
/**
* Data provider for testGetAliases().
*
* @return array[]
* Returns data-set elements with:
* - aliases as returned by ContainerBuilder.
* - aliases as expected in the container definition.
*/
public function getAliasesDataProvider() {
return [
[[], []],
[
['foo' => 'foo.alias'],
['foo' => 'foo.alias'],
],
[
['foo' => 'foo.alias', 'foo.alias' => 'foo.alias.alias'],
['foo' => 'foo.alias.alias', 'foo.alias' => 'foo.alias.alias'],
],
];
}
/**
* Tests that parameter processing works properly.
*
* @covers ::getParameters
* @covers ::prepareParameters
* @covers ::escape
* @covers ::dumpValue
* @covers ::getReferenceCall
*
* @dataProvider getParametersDataProvider
*/
public function testGetParameters($parameters, $definition_parameters, $is_frozen) {
$this->containerDefinition['parameters'] = $definition_parameters;
$this->containerDefinition['frozen'] = $is_frozen;
$parameter_bag = new ParameterBag($parameters);
$this->containerBuilder->getParameterBag()->willReturn($parameter_bag);
$this->containerBuilder->isCompiled()->willReturn($is_frozen);
if (isset($parameters['reference'])) {
$definition = new Definition('\stdClass');
$this->containerBuilder->getDefinition('referenced_service')->willReturn($definition);
}
$this->assertEquals($this->containerDefinition, $this->dumper->getArray(), 'Expected definition matches dump.');
}
/**
* Data provider for testGetParameters().
*
* @return array[]
* Returns data-set elements with:
* - parameters as returned by ContainerBuilder.
* - parameters as expected in the container definition.
* - frozen value
*/
public function getParametersDataProvider() {
return [
[[], [], TRUE],
[
['foo' => 'value_foo'],
['foo' => 'value_foo'],
TRUE,
],
[
['foo' => ['llama' => 'yes']],
['foo' => ['llama' => 'yes']],
TRUE,
],
[
['foo' => '%llama%', 'llama' => 'yes'],
['foo' => '%%llama%%', 'llama' => 'yes'],
TRUE,
],
[
['foo' => '%llama%', 'llama' => 'yes'],
['foo' => '%llama%', 'llama' => 'yes'],
FALSE,
],
[
['reference' => new Reference('referenced_service')],
['reference' => $this->getServiceCall('referenced_service')],
TRUE,
],
];
}
/**
* Tests that service processing works properly.
*
* @covers ::getServiceDefinitions
* @covers ::getServiceDefinition
* @covers ::dumpMethodCalls
* @covers ::dumpCollection
* @covers ::dumpCallable
* @covers ::dumpValue
* @covers ::getPrivateServiceCall
* @covers ::getReferenceCall
* @covers ::getServiceCall
* @covers ::getParameterCall
*
* @dataProvider getDefinitionsDataProvider
*
* @group legacy
*/
public function testGetServiceDefinitions($services, $definition_services) {
$this->containerDefinition['services'] = $definition_services;
$this->containerBuilder->getDefinitions()->willReturn($services);
$bar_definition = new Definition('\stdClass');
$this->containerBuilder->getDefinition('bar')->willReturn($bar_definition);
$private_definition = new Definition('\stdClass');
$private_definition->setPublic(FALSE);
$this->containerBuilder->getDefinition('private_definition')->willReturn($private_definition);
$this->assertEquals($this->containerDefinition, $this->dumper->getArray(), 'Expected definition matches dump.');
}
/**
* Data provider for testGetServiceDefinitions().
*
* @return array[]
* Returns data-set elements with:
* - parameters as returned by ContainerBuilder.
* - parameters as expected in the container definition.
* - frozen value
*/
public function getDefinitionsDataProvider() {
$base_service_definition = [
'class' => '\stdClass',
'public' => TRUE,
'file' => FALSE,
'synthetic' => FALSE,
'lazy' => FALSE,
'arguments' => [],
'arguments_count' => 0,
'properties' => [],
'calls' => [],
'shared' => TRUE,
'factory' => FALSE,
'configurator' => FALSE,
];
// Test basic flags.
$service_definitions[] = [] + $base_service_definition;
$service_definitions[] = [
'public' => FALSE,
] + $base_service_definition;
$service_definitions[] = [
'file' => 'test_include.php',
] + $base_service_definition;
$service_definitions[] = [
'synthetic' => TRUE,
] + $base_service_definition;
$service_definitions[] = [
'shared' => FALSE,
] + $base_service_definition;
$service_definitions[] = [
'lazy' => TRUE,
] + $base_service_definition;
// Test a basic public Reference.
$service_definitions[] = [
'arguments' => ['foo', new Reference('bar')],
'arguments_count' => 2,
'arguments_expected' => $this->getCollection(['foo', $this->getServiceCall('bar')]),
] + $base_service_definition;
// Test a public reference that should not throw an Exception.
$reference = new Reference('bar', ContainerInterface::NULL_ON_INVALID_REFERENCE);
$service_definitions[] = [
'arguments' => [$reference],
'arguments_count' => 1,
'arguments_expected' => $this->getCollection([$this->getServiceCall('bar', ContainerInterface::NULL_ON_INVALID_REFERENCE)]),
] + $base_service_definition;
// Test a private shared service, denoted by having a Reference.
$private_definition = [
'class' => '\stdClass',
'public' => FALSE,
'arguments_count' => 0,
];
$service_definitions[] = [
'arguments' => ['foo', new Reference('private_definition')],
'arguments_count' => 2,
'arguments_expected' => $this->getCollection([
'foo',
$this->getPrivateServiceCall('private_definition', $private_definition, TRUE),
]),
] + $base_service_definition;
// Test a private non-shared service, denoted by having a Definition.
$private_definition_object = new Definition('\stdClass');
$private_definition_object->setPublic(FALSE);
$service_definitions[] = [
'arguments' => ['foo', $private_definition_object],
'arguments_count' => 2,
'arguments_expected' => $this->getCollection([
'foo',
$this->getPrivateServiceCall(NULL, $private_definition),
]),
] + $base_service_definition;
// Test a deep collection without a reference.
$service_definitions[] = [
'arguments' => [[['foo']]],
'arguments_count' => 1,
] + $base_service_definition;
// Test a deep collection with a reference to resolve.
$service_definitions[] = [
'arguments' => [[new Reference('bar')]],
'arguments_count' => 1,
'arguments_expected' => $this->getCollection([$this->getCollection([$this->getServiceCall('bar')])]),
] + $base_service_definition;
// Test a collection with a variable to resolve.
$service_definitions[] = [
'arguments' => [new Parameter('llama_parameter')],
'arguments_count' => 1,
'arguments_expected' => $this->getCollection([$this->getParameterCall('llama_parameter')]),
] + $base_service_definition;
// Test objects that have _serviceId property.
$drupal_service = new \stdClass();
$drupal_service->_serviceId = 'bar';
$service_definitions[] = [
'arguments' => [$drupal_service],
'arguments_count' => 1,
'arguments_expected' => $this->getCollection([$this->getServiceCall('bar')]),
] + $base_service_definition;
// Test getMethodCalls.
$calls = [
['method', $this->getCollection([])],
['method2', $this->getCollection([])],
];
$service_definitions[] = [
'calls' => $calls,
] + $base_service_definition;
$service_definitions[] = [
'shared' => FALSE,
] + $base_service_definition;
// Test factory.
$service_definitions[] = [
'factory' => [new Reference('bar'), 'factoryMethod'],
'factory_expected' => [$this->getServiceCall('bar'), 'factoryMethod'],
] + $base_service_definition;
// Test invalid factory - needed to test deep dumpValue().
$service_definitions[] = [
'factory' => [['foo', 'llama'], 'factoryMethod'],
] + $base_service_definition;
// Test properties.
$service_definitions[] = [
'properties' => ['_value' => 'llama'],
] + $base_service_definition;
// Test configurator.
$service_definitions[] = [
'configurator' => [new Reference('bar'), 'configureService'],
'configurator_expected' => [$this->getServiceCall('bar'), 'configureService'],
] + $base_service_definition;
$services_provided = [];
$services_provided[] = [
[],
[],
];
foreach ($service_definitions as $service_definition) {
$definition = $this->prophesize('\Symfony\Component\DependencyInjection\Definition');
$definition->getClass()->willReturn($service_definition['class']);
$definition->isPublic()->willReturn($service_definition['public']);
$definition->getFile()->willReturn($service_definition['file']);
$definition->isSynthetic()->willReturn($service_definition['synthetic']);
$definition->isLazy()->willReturn($service_definition['lazy']);
$definition->getArguments()->willReturn($service_definition['arguments']);
$definition->getProperties()->willReturn($service_definition['properties']);
$definition->getMethodCalls()->willReturn($service_definition['calls']);
$definition->isShared()->willReturn($service_definition['shared']);
$definition->getDecoratedService()->willReturn(NULL);
$definition->getFactory()->willReturn($service_definition['factory']);
$definition->getConfigurator()->willReturn($service_definition['configurator']);
// Preserve order.
$filtered_service_definition = [];
foreach ($base_service_definition as $key => $value) {
$filtered_service_definition[$key] = $service_definition[$key];
unset($service_definition[$key]);
if ($key == 'class' || $key == 'arguments_count') {
continue;
}
if ($filtered_service_definition[$key] === $base_service_definition[$key]) {
unset($filtered_service_definition[$key]);
}
}
// Add remaining properties.
$filtered_service_definition += $service_definition;
// Allow to set _expected values.
foreach (['arguments', 'factory', 'configurator'] as $key) {
$expected = $key . '_expected';
if (isset($filtered_service_definition[$expected])) {
$filtered_service_definition[$key] = $filtered_service_definition[$expected];
unset($filtered_service_definition[$expected]);
}
}
if (isset($filtered_service_definition['public']) && $filtered_service_definition['public'] === FALSE) {
$services_provided[] = [
['foo_service' => $definition->reveal()],
[],
];
continue;
}
$services_provided[] = [
['foo_service' => $definition->reveal()],
['foo_service' => $this->serializeDefinition($filtered_service_definition)],
];
}
return $services_provided;
}
/**
* Helper function to serialize a definition.
*
* Used to override serialization.
*/
protected function serializeDefinition(array $service_definition) {
return serialize($service_definition);
}
/**
* Helper function to return a service definition.
*/
protected function getServiceCall($id, $invalid_behavior = ContainerInterface::EXCEPTION_ON_INVALID_REFERENCE) {
return (object) [
'type' => 'service',
'id' => $id,
'invalidBehavior' => $invalid_behavior,
];
}
/**
* Tests that references to aliases work correctly.
*
* @covers ::getReferenceCall
*
* @dataProvider publicPrivateDataProvider
*
* @group legacy
*/
public function testGetServiceDefinitionWithReferenceToAlias($public) {
$bar_definition = new Definition('\stdClass');
$bar_definition_php_array = [
'class' => '\stdClass',
];
if (!$public) {
$bar_definition->setPublic(FALSE);
$bar_definition_php_array['public'] = FALSE;
}
$bar_definition_php_array['arguments_count'] = 0;
$services['bar'] = $bar_definition;
$aliases['bar.alias'] = 'bar';
$foo = new Definition('\stdClass');
$foo->addArgument(new Reference('bar.alias'));
$services['foo'] = $foo;
$this->containerBuilder->getAliases()->willReturn($aliases);
$this->containerBuilder->getDefinitions()->willReturn($services);
$this->containerBuilder->getDefinition('bar')->willReturn($bar_definition);
$dump = $this->dumper->getArray();
if ($public) {
$service_definition = $this->getServiceCall('bar');
}
else {
$service_definition = $this->getPrivateServiceCall('bar', $bar_definition_php_array, TRUE);
}
$data = [
'class' => '\stdClass',
'arguments' => $this->getCollection([
$service_definition,
]),
'arguments_count' => 1,
];
$this->assertEquals($this->serializeDefinition($data), $dump['services']['foo'], 'Expected definition matches dump.');
}
public function publicPrivateDataProvider() {
return [
[TRUE],
[FALSE],
];
}
/**
* Tests that getDecoratedService() is unsupported.
*
* Tests that the correct InvalidArgumentException is thrown for
* getDecoratedService().
*
* @covers ::getServiceDefinition
*
* @group legacy
*/
public function testGetServiceDefinitionForDecoratedService() {
$bar_definition = new Definition('\stdClass');
$bar_definition->setDecoratedService(new Reference('foo'));
$services['bar'] = $bar_definition;
$this->containerBuilder->getDefinitions()->willReturn($services);
$this->expectException(InvalidArgumentException::class);
$this->dumper->getArray();
}
/**
* Tests that the correct RuntimeException is thrown for expressions.
*
* @covers ::dumpValue
*/
public function testGetServiceDefinitionForExpression() {
$expression = new Expression();
$bar_definition = new Definition('\stdClass');
$bar_definition->addArgument($expression);
$services['bar'] = $bar_definition;
$this->containerBuilder->getDefinitions()->willReturn($services);
$this->expectException(RuntimeException::class);
$this->dumper->getArray();
}
/**
* Tests that the correct RuntimeException is thrown for dumping an object.
*
* @covers ::dumpValue
*/
public function testGetServiceDefinitionForObject() {
$service = new \stdClass();
$bar_definition = new Definition('\stdClass');
$bar_definition->addArgument($service);
$services['bar'] = $bar_definition;
$this->containerBuilder->getDefinitions()->willReturn($services);
$this->expectException(RuntimeException::class);
$this->dumper->getArray();
}
/**
* Tests that the correct RuntimeException is thrown for dumping a resource.
*
* @covers ::dumpValue
*/
public function testGetServiceDefinitionForResource() {
$resource = fopen('php://memory', 'r');
$bar_definition = new Definition('\stdClass');
$bar_definition->addArgument($resource);
$services['bar'] = $bar_definition;
$this->containerBuilder->getDefinitions()->willReturn($services);
$this->expectException(RuntimeException::class);
$this->dumper->getArray();
}
/**
* Tests that service arguments with escaped percents are correctly dumped.
*
* @dataProvider percentsEscapeProvider
*/
public function testPercentsEscape($expected, $argument) {
$this->containerBuilder->getDefinitions()->willReturn([
'test' => new Definition('\stdClass', [$argument]),
]);
$dump = $this->dumper->getArray();
$this->assertEquals($this->serializeDefinition([
'class' => '\stdClass',
'arguments' => $this->getCollection([
$this->getRaw($expected),
]),
'arguments_count' => 1,
]), $dump['services']['test']);
}
/**
* Data provider for testPercentsEscape().
*
* @return array[]
* Returns data-set elements with:
* - expected final value.
* - escaped value in service definition.
*/
public function percentsEscapeProvider() {
return [
['%foo%', '%%foo%%'],
['foo%bar%', 'foo%%bar%%'],
['%foo%bar', '%%foo%%bar'],
['%', '%'],
['%', '%%'],
['%%', '%%%'],
['%%', '%%%%'],
];
}
/**
* Helper function to return a private service definition.
*/
protected function getPrivateServiceCall($id, $service_definition, $shared = FALSE) {
if (!$id) {
$hash = Crypt::hashBase64(serialize($service_definition));
$id = 'private__' . $hash;
}
return (object) [
'type' => 'private_service',
'id' => $id,
'value' => $service_definition,
'shared' => $shared,
];
}
/**
* Helper function to return a machine-optimized collection.
*/
protected function getCollection($collection, $resolve = TRUE) {
return (object) [
'type' => 'collection',
'value' => $collection,
'resolve' => $resolve,
];
}
/**
* Helper function to return a parameter definition.
*/
protected function getParameterCall($name) {
return (object) [
'type' => 'parameter',
'name' => $name,
];
}
/**
* Helper function to return a raw value definition.
*/
protected function getRaw($value) {
return (object) [
'type' => 'raw',
'value' => $value,
];
}
}
}
/**
* As Drupal Core does not ship with ExpressionLanguage component we need to
* define a dummy, else it cannot be tested.
*/
namespace Symfony\Component\ExpressionLanguage {
if (!class_exists('\Symfony\Component\ExpressionLanguage\Expression')) {
/**
* Dummy class to ensure non-existent Symfony component can be tested.
*/
class Expression {
/**
* Gets the string representation of the expression.
*/
public function __toString() {
return 'dummy_expression';
}
}
}
}
@@ -0,0 +1,54 @@
<?php
namespace Drupal\Tests\Component\DependencyInjection\Dumper;
use Symfony\Component\DependencyInjection\ContainerInterface;
/**
* @coversDefaultClass \Drupal\Component\DependencyInjection\Dumper\PhpArrayDumper
* @group DependencyInjection
*/
class PhpArrayDumperTest extends OptimizedPhpArrayDumperTest {
/**
* {@inheritdoc}
*/
protected function setUp() {
$this->machineFormat = FALSE;
$this->dumperClass = '\Drupal\Component\DependencyInjection\Dumper\PhpArrayDumper';
parent::setUp();
}
/**
* {@inheritdoc}
*/
protected function serializeDefinition(array $service_definition) {
return $service_definition;
}
/**
* {@inheritdoc}
*/
protected function getServiceCall($id, $invalid_behavior = ContainerInterface::EXCEPTION_ON_INVALID_REFERENCE) {
if ($invalid_behavior !== ContainerInterface::EXCEPTION_ON_INVALID_REFERENCE) {
return sprintf('@?%s', $id);
}
return sprintf('@%s', $id);
}
/**
* {@inheritdoc}
*/
protected function getParameterCall($name) {
return '%' . $name . '%';
}
/**
* {@inheritdoc}
*/
protected function getCollection($collection, $resolve = TRUE) {
return $collection;
}
}
@@ -0,0 +1,16 @@
<?php
/**
* @file
* Contains a test function for container 'file' include testing.
*/
/**
* Test function for container testing.
*
* @return string
* A string just for testing.
*/
function container_test_file_service_test_service_function() {
return 'Hello Container';
}
@@ -0,0 +1,48 @@
<?php
namespace Drupal\Tests\Component\DependencyInjection;
use Symfony\Component\DependencyInjection\ContainerInterface;
/**
* @coversDefaultClass \Drupal\Component\DependencyInjection\PhpArrayContainer
* @group DependencyInjection
*/
class PhpArrayContainerTest extends ContainerTest {
/**
* {@inheritdoc}
*/
protected function setUp() {
$this->machineFormat = FALSE;
$this->containerClass = '\Drupal\Component\DependencyInjection\PhpArrayContainer';
$this->containerDefinition = $this->getMockContainerDefinition();
$this->container = new $this->containerClass($this->containerDefinition);
}
/**
* Helper function to return a service definition.
*/
protected function getServiceCall($id, $invalid_behavior = ContainerInterface::EXCEPTION_ON_INVALID_REFERENCE) {
if ($invalid_behavior !== ContainerInterface::EXCEPTION_ON_INVALID_REFERENCE) {
return sprintf('@?%s', $id);
}
return sprintf('@%s', $id);
}
/**
* Helper function to return a service definition.
*/
protected function getParameterCall($name) {
return '%' . $name . '%';
}
/**
* Helper function to return a machine-optimized '@notation'.
*/
protected function getCollection($collection, $resolve = TRUE) {
return $collection;
}
}
@@ -0,0 +1,58 @@
<?php
namespace Drupal\Tests\Component\Diff;
use Drupal\Component\Diff\Diff;
use Drupal\Component\Diff\DiffFormatter;
use PHPUnit\Framework\TestCase;
/**
* Test DiffFormatter classes.
*
* @coversDefaultClass \Drupal\Component\Diff\DiffFormatter
*
* @group Diff
*/
class DiffFormatterTest extends TestCase {
/**
* @return array
* - Expected formatted diff output.
* - First array of text to diff.
* - Second array of text to diff.
*/
public function provideTestDiff() {
return [
'empty' => ['', [], []],
'add' => [
"3a3\n> line2a\n",
['line1', 'line2', 'line3'],
['line1', 'line2', 'line2a', 'line3'],
],
'delete' => [
"3d3\n< line2a\n",
['line1', 'line2', 'line2a', 'line3'],
['line1', 'line2', 'line3'],
],
'change' => [
"3c3\n< line2a\n---\n> line2b\n",
['line1', 'line2', 'line2a', 'line3'],
['line1', 'line2', 'line2b', 'line3'],
],
];
}
/**
* Tests whether op classes returned by DiffEngine::diff() match expectations.
*
* @covers ::format
* @dataProvider provideTestDiff
*/
public function testDiff($expected, $from, $to) {
$diff = new Diff($from, $to);
$formatter = new DiffFormatter();
$output = $formatter->format($diff);
$this->assertEquals($expected, $output);
}
}
@@ -0,0 +1,106 @@
<?php
namespace Drupal\Tests\Component\Diff\Engine;
use Drupal\Component\Diff\Engine\DiffEngine;
use Drupal\Component\Diff\Engine\DiffOpAdd;
use Drupal\Component\Diff\Engine\DiffOpCopy;
use Drupal\Component\Diff\Engine\DiffOpChange;
use Drupal\Component\Diff\Engine\DiffOpDelete;
use PHPUnit\Framework\TestCase;
/**
* Test DiffEngine class.
*
* @coversDefaultClass \Drupal\Component\Diff\Engine\DiffEngine
*
* @group Diff
*/
class DiffEngineTest extends TestCase {
/**
* @return array
* - Expected output in terms of return class. A list of class names
* expected to be returned by DiffEngine::diff().
* - An array of strings to change from.
* - An array of strings to change to.
*/
public function provideTestDiff() {
return [
'empty' => [[], [], []],
'add' => [[DiffOpAdd::class], [], ['a']],
'copy' => [[DiffOpCopy::class], ['a'], ['a']],
'change' => [[DiffOpChange::class], ['a'], ['b']],
'copy-and-change' => [
[
DiffOpCopy::class,
DiffOpChange::class,
],
['a', 'b'],
['a', 'c'],
],
'copy-change-copy' => [
[
DiffOpCopy::class,
DiffOpChange::class,
DiffOpCopy::class,
],
['a', 'b', 'd'],
['a', 'c', 'd'],
],
'copy-change-copy-add' => [
[
DiffOpCopy::class,
DiffOpChange::class,
DiffOpCopy::class,
DiffOpAdd::class,
],
['a', 'b', 'd'],
['a', 'c', 'd', 'e'],
],
'copy-delete' => [
[
DiffOpCopy::class,
DiffOpDelete::class,
],
['a', 'b', 'd'],
['a'],
],
];
}
/**
* Tests whether op classes returned by DiffEngine::diff() match expectations.
*
* @covers ::diff
* @dataProvider provideTestDiff
*/
public function testDiff($expected, $from, $to) {
$diff_engine = new DiffEngine();
$diff = $diff_engine->diff($from, $to);
// Make sure we have the same number of results as expected.
$this->assertCount(count($expected), $diff);
// Make sure the diff objects match our expectations.
foreach ($expected as $index => $op_class) {
$this->assertEquals($op_class, get_class($diff[$index]));
}
}
/**
* Tests that two files can be successfully diffed.
*
* @covers ::diff
*/
public function testDiffInfiniteLoop() {
$from = explode("\n", file_get_contents(__DIR__ . '/fixtures/file1.txt'));
$to = explode("\n", file_get_contents(__DIR__ . '/fixtures/file2.txt'));
$diff_engine = new DiffEngine();
$diff = $diff_engine->diff($from, $to);
$this->assertCount(4, $diff);
$this->assertEquals($diff[0], new DiffOpDelete([' - image.style.max_650x650']));
$this->assertEquals($diff[1], new DiffOpCopy([' - image.style.max_325x325']));
$this->assertEquals($diff[2], new DiffOpAdd([' - image.style.max_650x650', '_core:', ' default_config_hash: 3mjM9p-kQ8syzH7N8T0L9OnCJDSPvHAZoi3q6jcXJKM']));
$this->assertEquals($diff[3], new DiffOpCopy(['fallback_image_style: max_325x325', '']));
}
}
@@ -0,0 +1,33 @@
<?php
namespace Drupal\Tests\Component\Diff\Engine;
use Drupal\Component\Diff\Engine\DiffOp;
use PHPUnit\Framework\TestCase;
use PHPUnit\Framework\Error\Error;
/**
* Test DiffOp base class.
*
* The only significant behavior here is that ::reverse() should throw an error
* if not overridden. In versions of this code in other projects, reverse() is
* marked as abstract, which enforces some of this behavior.
*
* @coversDefaultClass \Drupal\Component\Diff\Engine\DiffOp
*
* @group Diff
*/
class DiffOpTest extends TestCase {
/**
* DiffOp::reverse() always throws an error.
*
* @covers ::reverse
*/
public function testReverse() {
$this->expectException(Error::class);
$op = new DiffOp();
$result = $op->reverse();
}
}
@@ -0,0 +1,53 @@
<?php
namespace Drupal\Tests\Component\Diff\Engine;
use Drupal\Component\Diff\Engine\HWLDFWordAccumulator;
use PHPUnit\Framework\TestCase;
/**
* Test HWLDFWordAccumulator.
*
* @coversDefaultClass \Drupal\Component\Diff\Engine\HWLDFWordAccumulator
*
* @group Diff
*/
class HWLDFWordAccumulatorTest extends TestCase {
/**
* Verify that we only get back a NBSP from an empty accumulator.
*
* @covers ::getLines
*
* @see Drupal\Component\Diff\Engine\HWLDFWordAccumulator::NBSP
*/
public function testGetLinesEmpty() {
$acc = new HWLDFWordAccumulator();
$this->assertEquals(['&#160;'], $acc->getLines());
}
/**
* @return array
* - Expected array of lines from getLines().
* - Array of strings for the $words parameter to addWords().
* - String tag for the $tag parameter to addWords().
*/
public function provideAddWords() {
return [
[['wordword2'], ['word', 'word2'], 'tag'],
[['word', 'word2'], ['word', "\nword2"], 'tag'],
[['&#160;', 'word2'], ['', "\nword2"], 'tag'],
];
}
/**
* @covers ::addWords
* @dataProvider provideAddWords
*/
public function testAddWords($expected, $words, $tag) {
$acc = new HWLDFWordAccumulator();
$acc->addWords($words, $tag);
$this->assertEquals($expected, $acc->getLines());
}
}
@@ -0,0 +1,3 @@
- image.style.max_650x650
- image.style.max_325x325
fallback_image_style: max_325x325
@@ -0,0 +1,5 @@
- image.style.max_325x325
- image.style.max_650x650
_core:
default_config_hash: 3mjM9p-kQ8syzH7N8T0L9OnCJDSPvHAZoi3q6jcXJKM
fallback_image_style: max_325x325
@@ -0,0 +1,163 @@
<?php
namespace Drupal\Tests\Component\Discovery;
use Drupal\Component\Discovery\DiscoveryException;
use Drupal\Component\Discovery\YamlDirectoryDiscovery;
use Drupal\Component\FileCache\FileCacheFactory;
use org\bovigo\vfs\vfsStream;
use PHPUnit\Framework\TestCase;
/**
* YamlDirectoryDiscoveryTest component unit tests.
*
* @coversDefaultClass \Drupal\Component\Discovery\YamlDirectoryDiscovery
*
* @group Discovery
*/
class YamlDirectoryDiscoveryTest extends TestCase {
/**
* {@inheritdoc}
*/
protected function setUp() {
// Ensure that FileCacheFactory has a prefix.
FileCacheFactory::setPrefix('prefix');
}
/**
* Tests YAML directory discovery.
*
* @covers ::findAll
*/
public function testDiscovery() {
vfsStream::setup('modules', NULL, [
'test_1' => [
'subdir1' => [
'item_1.test.yml' => "id: item1\nname: 'test1 item 1'",
],
'subdir2' => [
'item_2.test.yml' => "id: item2\nname: 'test1 item 2'",
],
],
'test_2' => [
'subdir1' => [
'item_3.test.yml' => "id: item3\nname: 'test2 item 3'",
],
'subdir2' => [],
],
'test_3' => [],
'test_4' => [
'subdir1' => [
'item_4.test.yml' => "id: item4\nname: 'test4 item 4'",
'item_5.test.yml' => "id: item5\nname: 'test4 item 5'",
'item_6.test.yml' => "id: item6\nname: 'test4 item 6'",
],
],
]);
// Set up the directories to search.
$directories = [
// Multiple directories both with valid items.
'test_1' => [
vfsStream::url('modules/test_1/subdir1'),
vfsStream::url('modules/test_1/subdir2'),
],
// The subdir2 directory is empty.
'test_2' => [
vfsStream::url('modules/test_2/subdir1'),
vfsStream::url('modules/test_2/subdir2'),
],
// Directories that do not exist.
'test_3' => [
vfsStream::url('modules/test_3/subdir1'),
vfsStream::url('modules/test_3/subdir2'),
],
// A single directory.
'test_4' => vfsStream::url('modules/test_4/subdir1'),
];
$discovery = new YamlDirectoryDiscovery($directories, 'test');
$data = $discovery->findAll();
$this->assertSame(['id' => 'item1', 'name' => 'test1 item 1', YamlDirectoryDiscovery::FILE_KEY => 'vfs://modules/test_1/subdir1/item_1.test.yml'], $data['test_1']['item1']);
$this->assertSame(['id' => 'item2', 'name' => 'test1 item 2', YamlDirectoryDiscovery::FILE_KEY => 'vfs://modules/test_1/subdir2/item_2.test.yml'], $data['test_1']['item2']);
$this->assertCount(2, $data['test_1']);
$this->assertSame(['id' => 'item3', 'name' => 'test2 item 3', YamlDirectoryDiscovery::FILE_KEY => 'vfs://modules/test_2/subdir1/item_3.test.yml'], $data['test_2']['item3']);
$this->assertCount(1, $data['test_2']);
$this->assertTrue(empty($data['test_3']), 'test_3 provides 0 items');
$this->assertSame(['id' => 'item4', 'name' => 'test4 item 4', YamlDirectoryDiscovery::FILE_KEY => 'vfs://modules/test_4/subdir1/item_4.test.yml'], $data['test_4']['item4']);
$this->assertSame(['id' => 'item5', 'name' => 'test4 item 5', YamlDirectoryDiscovery::FILE_KEY => 'vfs://modules/test_4/subdir1/item_5.test.yml'], $data['test_4']['item5']);
$this->assertSame(['id' => 'item6', 'name' => 'test4 item 6', YamlDirectoryDiscovery::FILE_KEY => 'vfs://modules/test_4/subdir1/item_6.test.yml'], $data['test_4']['item6']);
$this->assertCount(3, $data['test_4']);
}
/**
* Tests YAML directory discovery with an alternate ID key.
*
* @covers ::findAll
*/
public function testDiscoveryAlternateId() {
vfsStream::setup('modules', NULL, [
'test_1' => [
'item_1.test.yml' => "alt_id: item1\nid: ignored",
],
]);
// Set up the directories to search.
$directories = ['test_1' => vfsStream::url('modules/test_1')];
$discovery = new YamlDirectoryDiscovery($directories, 'test', 'alt_id');
$data = $discovery->findAll();
$this->assertSame(['alt_id' => 'item1', 'id' => 'ignored', YamlDirectoryDiscovery::FILE_KEY => 'vfs://modules/test_1/item_1.test.yml'], $data['test_1']['item1']);
$this->assertCount(1, $data['test_1']);
}
/**
* Tests YAML directory discovery with a missing ID key.
*
* @covers ::findAll
* @covers ::getIdentifier
*/
public function testDiscoveryNoIdException() {
$this->expectException(DiscoveryException::class);
$this->expectExceptionMessage('The vfs://modules/test_1/item_1.test.yml contains no data in the identifier key \'id\'');
vfsStream::setup('modules', NULL, [
'test_1' => [
'item_1.test.yml' => "",
],
]);
// Set up the directories to search.
$directories = ['test_1' => vfsStream::url('modules/test_1')];
$discovery = new YamlDirectoryDiscovery($directories, 'test');
$discovery->findAll();
}
/**
* Tests YAML directory discovery with invalid YAML.
*
* @covers ::findAll
*/
public function testDiscoveryInvalidYamlException() {
$this->expectException(DiscoveryException::class);
$this->expectExceptionMessage('The vfs://modules/test_1/item_1.test.yml contains invalid YAML');
vfsStream::setup('modules', NULL, [
'test_1' => [
'item_1.test.yml' => "id: invalid\nfoo : [bar}",
],
]);
// Set up the directories to search.
$directories = ['test_1' => vfsStream::url('modules/test_1')];
$discovery = new YamlDirectoryDiscovery($directories, 'test');
$discovery->findAll();
}
}
@@ -0,0 +1,91 @@
<?php
namespace Drupal\Tests\Component\Discovery;
use Drupal\Component\Discovery\YamlDiscovery;
use Drupal\Component\FileCache\FileCacheFactory;
use Drupal\Component\Serialization\Exception\InvalidDataTypeException;
use org\bovigo\vfs\vfsStream;
use org\bovigo\vfs\vfsStreamDirectory;
use org\bovigo\vfs\vfsStreamWrapper;
use PHPUnit\Framework\TestCase;
/**
* YamlDiscovery component unit tests.
*
* @group Discovery
*/
class YamlDiscoveryTest extends TestCase {
/**
* {@inheritdoc}
*/
protected function setUp() {
// Ensure that FileCacheFactory has a prefix.
FileCacheFactory::setPrefix('prefix');
}
/**
* Tests the YAML file discovery.
*/
public function testDiscovery() {
vfsStreamWrapper::register();
$root = new vfsStreamDirectory('modules');
vfsStreamWrapper::setRoot($root);
$url = vfsStream::url('modules');
mkdir($url . '/test_1');
file_put_contents($url . '/test_1/test_1.test.yml', 'name: test');
file_put_contents($url . '/test_1/test_2.test.yml', 'name: test');
mkdir($url . '/test_2');
file_put_contents($url . '/test_2/test_3.test.yml', 'name: test');
// Write an empty YAML file.
file_put_contents($url . '/test_2/test_4.test.yml', '');
// Set up the directories to search.
$directories = [
'test_1' => $url . '/test_1',
'test_2' => $url . '/test_1',
'test_3' => $url . '/test_2',
'test_4' => $url . '/test_2',
];
$discovery = new YamlDiscovery('test', $directories);
$data = $discovery->findAll();
$this->assertEquals(count($data), count($directories));
$this->assertArrayHasKey('test_1', $data);
$this->assertArrayHasKey('test_2', $data);
$this->assertArrayHasKey('test_3', $data);
$this->assertArrayHasKey('test_4', $data);
foreach (['test_1', 'test_2', 'test_3'] as $key) {
$this->assertArrayHasKey('name', $data[$key]);
$this->assertEquals($data[$key]['name'], 'test');
}
$this->assertSame([], $data['test_4']);
}
/**
* Tests if filename is output for a broken YAML file.
*/
public function testForBrokenYml() {
vfsStreamWrapper::register();
$root = new vfsStreamDirectory('modules');
vfsStreamWrapper::setRoot($root);
$url = vfsStream::url('modules');
mkdir($url . '/test_broken');
file_put_contents($url . '/test_broken/test_broken.test.yml', "broken:\n:");
$this->expectException(InvalidDataTypeException::class);
$this->expectExceptionMessage('vfs://modules/test_broken/test_broken.test.yml');
$directories = ['test_broken' => $url . '/test_broken'];
$discovery = new YamlDiscovery('test', $directories);
$discovery->findAll();
}
}
@@ -0,0 +1,156 @@
<?php
namespace Drupal\Tests\Component;
use org\bovigo\vfs\vfsStream;
use PHPUnit\Framework\AssertionFailedError;
use PHPUnit\Framework\TestCase;
/**
* General tests for \Drupal\Component that can't go anywhere else.
*
* @group Component
*/
class DrupalComponentTest extends TestCase {
/**
* Tests that classes in Component do not use any Core class.
*/
public function testNoCoreInComponent() {
$component_path = dirname(substr(__DIR__, 0, -strlen(__NAMESPACE__))) . '/lib/Drupal/Component';
foreach ($this->findPhpClasses($component_path) as $class) {
$this->assertNoCoreUsage($class);
}
}
/**
* Tests that classes in Component Tests do not use any Core class.
*/
public function testNoCoreInComponentTests() {
$component_path = dirname(substr(__DIR__, 0, -strlen(__NAMESPACE__))) . '/tests/Drupal/Tests/Component';
foreach ($this->findPhpClasses($component_path) as $class) {
$this->assertNoCoreUsage($class);
}
}
/**
* Tests LICENSE.txt is present and has the correct content.
*
* @param $component_path
* The path to the component.
* @dataProvider \Drupal\Tests\Component\DrupalComponentTest::getComponents
*/
public function testComponentLicence($component_path) {
$this->assertFileExists($component_path . DIRECTORY_SEPARATOR . 'LICENSE.txt');
$this->assertSame('e84dac1d9fbb5a4a69e38654ce644cea769aa76b', hash_file('sha1', $component_path . DIRECTORY_SEPARATOR . 'LICENSE.txt'));
}
/**
* Data provider.
*
* @return array
*/
public function getComponents() {
$root_component_path = dirname(substr(__DIR__, 0, -strlen(__NAMESPACE__))) . '/lib/Drupal/Component';
$component_paths = [];
foreach (new \DirectoryIterator($root_component_path) as $file) {
if ($file->isDir() && !$file->isDot()) {
$component_paths[$file->getBasename()] = [$file->getPathname()];
}
}
return $component_paths;
}
/**
* Searches a directory recursively for PHP classes.
*
* @param string $dir
* The full path to the directory that should be checked.
*
* @return array
* An array of class paths.
*/
protected function findPhpClasses($dir) {
$classes = [];
foreach (new \DirectoryIterator($dir) as $file) {
if ($file->isDir() && !$file->isDot()) {
$classes = array_merge($classes, $this->findPhpClasses($file->getPathname()));
}
elseif ($file->getExtension() == 'php') {
$classes[] = $file->getPathname();
}
}
return $classes;
}
/**
* Asserts that the given class is not using any class from Core namespace.
*
* @param string $class_path
* The full path to the class that should be checked.
*/
protected function assertNoCoreUsage($class_path) {
$contents = file_get_contents($class_path);
preg_match_all('/^.*Drupal\\\Core.*$/m', $contents, $matches);
$matches = array_filter($matches[0], function ($line) {
// Filter references to @see as they don't really matter.
return strpos($line, '@see') === FALSE;
});
$this->assertEmpty($matches, "Checking for illegal reference to 'Drupal\\Core' namespace in $class_path");
}
/**
* Data provider for testAssertNoCoreUseage().
*
* @return array
* Data for testAssertNoCoreUseage() in the form:
* - TRUE if the test passes, FALSE otherwise.
* - File data as a string. This will be used as a virtual file.
*/
public function providerAssertNoCoreUseage() {
return [
[
TRUE,
'@see \\Drupal\\Core\\Something',
],
[
FALSE,
'\\Drupal\\Core\\Something',
],
[
FALSE,
"@see \\Drupal\\Core\\Something\n" .
'\\Drupal\\Core\\Something',
],
[
FALSE,
"\\Drupal\\Core\\Something\n" .
'@see \\Drupal\\Core\\Something',
],
];
}
/**
* @covers \Drupal\Tests\Component\DrupalComponentTest::assertNoCoreUsage
* @dataProvider providerAssertNoCoreUseage
*/
public function testAssertNoCoreUseage($expected_pass, $file_data) {
// Set up a virtual file to read.
$vfs_root = vfsStream::setup('root');
vfsStream::newFile('Test.php')->at($vfs_root)->setContent($file_data);
$file_uri = vfsStream::url('root/Test.php');
try {
$pass = TRUE;
$this->assertNoCoreUsage($file_uri);
}
catch (AssertionFailedError $e) {
$pass = FALSE;
}
$this->assertEquals($expected_pass, $pass, $expected_pass ?
'Test caused a false positive' :
'Test failed to detect Core usage');
}
}
@@ -0,0 +1,602 @@
<?php
namespace Drupal\Tests\Component\EventDispatcher;
use Drupal\Component\EventDispatcher\ContainerAwareEventDispatcher;
use PHPUnit\Framework\TestCase;
use Symfony\Component\DependencyInjection\Container;
use Symfony\Component\DependencyInjection\ContainerBuilder;
use Symfony\Component\DependencyInjection\ContainerInterface;
use Symfony\Component\EventDispatcher\Event;
use Symfony\Component\EventDispatcher\EventSubscriberInterface;
/**
* Unit tests for the ContainerAwareEventDispatcher.
*
* NOTE: Most of this code is a literal copy of Symfony 3.4's
* Symfony\Component\EventDispatcher\Tests\AbstractEventDispatcherTest.
*
* This file does NOT follow Drupal coding standards, so as to simplify future
* synchronizations.
*
* @group EventDispatcher
*/
class ContainerAwareEventDispatcherTest extends TestCase {
/* Some pseudo events */
const PREFOO = 'pre.foo';
const POSTFOO = 'post.foo';
const PREBAR = 'pre.bar';
const POSTBAR = 'post.bar';
/**
* @var \Symfony\Component\EventDispatcher\EventDispatcher
*/
private $dispatcher;
private $listener;
protected function setUp() {
$this->dispatcher = $this->createEventDispatcher();
$this->listener = new TestEventListener();
}
protected function tearDown() {
$this->dispatcher = NULL;
$this->listener = NULL;
}
protected function createEventDispatcher() {
$container = new Container();
return new ContainerAwareEventDispatcher($container);
}
public function testGetListenersWithCallables() {
// When passing in callables exclusively as listeners into the event
// dispatcher constructor, the event dispatcher must not attempt to
// resolve any services.
$container = $this->getMockBuilder(ContainerInterface::class)->getMock();
$container->expects($this->never())->method($this->anything());
$firstListener = new CallableClass();
$secondListener = function () {
};
$thirdListener = [new TestEventListener(), 'preFoo'];
$listeners = [
'test_event' => [
0 => [
['callable' => $firstListener],
['callable' => $secondListener],
['callable' => $thirdListener],
],
],
];
$dispatcher = new ContainerAwareEventDispatcher($container, $listeners);
$actualListeners = $dispatcher->getListeners();
$expectedListeners = [
'test_event' => [
$firstListener,
$secondListener,
$thirdListener,
],
];
$this->assertSame($expectedListeners, $actualListeners);
}
public function testDispatchWithCallables() {
// When passing in callables exclusively as listeners into the event
// dispatcher constructor, the event dispatcher must not attempt to
// resolve any services.
$container = $this->getMockBuilder(ContainerInterface::class)->getMock();
$container->expects($this->never())->method($this->anything());
$firstListener = new CallableClass();
$secondListener = function () {
};
$thirdListener = [new TestEventListener(), 'preFoo'];
$listeners = [
'test_event' => [
0 => [
['callable' => $firstListener],
['callable' => $secondListener],
['callable' => $thirdListener],
],
],
];
$dispatcher = new ContainerAwareEventDispatcher($container, $listeners);
$dispatcher->dispatch('test_event');
$this->assertTrue($thirdListener[0]->preFooInvoked);
}
public function testGetListenersWithServices() {
$container = new ContainerBuilder();
$container->register('listener_service', TestEventListener::class);
$listeners = [
'test_event' => [
0 => [
['service' => ['listener_service', 'preFoo']],
],
],
];
$dispatcher = new ContainerAwareEventDispatcher($container, $listeners);
$actualListeners = $dispatcher->getListeners();
$listenerService = $container->get('listener_service');
$expectedListeners = [
'test_event' => [
[$listenerService, 'preFoo'],
],
];
$this->assertSame($expectedListeners, $actualListeners);
}
public function testDispatchWithServices() {
$container = new ContainerBuilder();
$container->register('listener_service', TestEventListener::class);
$listeners = [
'test_event' => [
0 => [
['service' => ['listener_service', 'preFoo']],
],
],
];
$dispatcher = new ContainerAwareEventDispatcher($container, $listeners);
$dispatcher->dispatch('test_event');
$listenerService = $container->get('listener_service');
$this->assertTrue($listenerService->preFooInvoked);
}
public function testRemoveService() {
$container = new ContainerBuilder();
$container->register('listener_service', TestEventListener::class);
$container->register('other_listener_service', TestEventListener::class);
$listeners = [
'test_event' => [
0 => [
['service' => ['listener_service', 'preFoo']],
['service' => ['other_listener_service', 'preFoo']],
],
],
];
$dispatcher = new ContainerAwareEventDispatcher($container, $listeners);
$listenerService = $container->get('listener_service');
$dispatcher->removeListener('test_event', [$listenerService, 'preFoo']);
// Ensure that other service was not initialized during removal of the
// listener service.
$this->assertFalse($container->initialized('other_listener_service'));
$dispatcher->dispatch('test_event');
$this->assertFalse($listenerService->preFooInvoked);
$otherService = $container->get('other_listener_service');
$this->assertTrue($otherService->preFooInvoked);
}
public function testGetListenerPriorityWithServices() {
$container = new ContainerBuilder();
$container->register('listener_service', TestEventListener::class);
$listeners = [
'test_event' => [
5 => [
['service' => ['listener_service', 'preFoo']],
],
],
];
$dispatcher = new ContainerAwareEventDispatcher($container, $listeners);
$listenerService = $container->get('listener_service');
$actualPriority = $dispatcher->getListenerPriority('test_event', [$listenerService, 'preFoo']);
$this->assertSame(5, $actualPriority);
}
public function testInitialState() {
$this->assertEquals([], $this->dispatcher->getListeners());
$this->assertFalse($this->dispatcher->hasListeners(self::PREFOO));
$this->assertFalse($this->dispatcher->hasListeners(self::POSTFOO));
}
public function testAddListener() {
$this->dispatcher->addListener('pre.foo', [$this->listener, 'preFoo']);
$this->dispatcher->addListener('post.foo', [$this->listener, 'postFoo']);
$this->assertTrue($this->dispatcher->hasListeners());
$this->assertTrue($this->dispatcher->hasListeners(self::PREFOO));
$this->assertTrue($this->dispatcher->hasListeners(self::POSTFOO));
$this->assertCount(1, $this->dispatcher->getListeners(self::PREFOO));
$this->assertCount(1, $this->dispatcher->getListeners(self::POSTFOO));
$this->assertCount(2, $this->dispatcher->getListeners());
}
public function testGetListenersSortsByPriority() {
$listener1 = new TestEventListener();
$listener2 = new TestEventListener();
$listener3 = new TestEventListener();
$listener1->name = '1';
$listener2->name = '2';
$listener3->name = '3';
$this->dispatcher->addListener('pre.foo', [$listener1, 'preFoo'], -10);
$this->dispatcher->addListener('pre.foo', [$listener2, 'preFoo'], 10);
$this->dispatcher->addListener('pre.foo', [$listener3, 'preFoo']);
$expected = [
[$listener2, 'preFoo'],
[$listener3, 'preFoo'],
[$listener1, 'preFoo'],
];
$this->assertSame($expected, $this->dispatcher->getListeners('pre.foo'));
}
public function testGetAllListenersSortsByPriority() {
$listener1 = new TestEventListener();
$listener2 = new TestEventListener();
$listener3 = new TestEventListener();
$listener4 = new TestEventListener();
$listener5 = new TestEventListener();
$listener6 = new TestEventListener();
$this->dispatcher->addListener('pre.foo', $listener1, -10);
$this->dispatcher->addListener('pre.foo', $listener2);
$this->dispatcher->addListener('pre.foo', $listener3, 10);
$this->dispatcher->addListener('post.foo', $listener4, -10);
$this->dispatcher->addListener('post.foo', $listener5);
$this->dispatcher->addListener('post.foo', $listener6, 10);
$expected = [
'pre.foo' => [$listener3, $listener2, $listener1],
'post.foo' => [$listener6, $listener5, $listener4],
];
$this->assertSame($expected, $this->dispatcher->getListeners());
}
public function testGetListenerPriority() {
$listener1 = new TestEventListener();
$listener2 = new TestEventListener();
$this->dispatcher->addListener('pre.foo', $listener1, -10);
$this->dispatcher->addListener('pre.foo', $listener2);
$this->assertSame(-10, $this->dispatcher->getListenerPriority('pre.foo', $listener1));
$this->assertSame(0, $this->dispatcher->getListenerPriority('pre.foo', $listener2));
$this->assertNull($this->dispatcher->getListenerPriority('pre.bar', $listener2));
$this->assertNull($this->dispatcher->getListenerPriority('pre.foo', function () {
}));
}
public function testDispatch() {
$this->dispatcher->addListener('pre.foo', [$this->listener, 'preFoo']);
$this->dispatcher->addListener('post.foo', [$this->listener, 'postFoo']);
$this->dispatcher->dispatch(self::PREFOO);
$this->assertTrue($this->listener->preFooInvoked);
$this->assertFalse($this->listener->postFooInvoked);
$this->assertInstanceOf(Event::class, $this->dispatcher->dispatch('noevent'));
$this->assertInstanceOf(Event::class, $this->dispatcher->dispatch(self::PREFOO));
$event = new Event();
$return = $this->dispatcher->dispatch(self::PREFOO, $event);
$this->assertSame($event, $return);
}
public function testDispatchForClosure() {
$invoked = 0;
$listener = function () use (&$invoked) {
++$invoked;
};
$this->dispatcher->addListener('pre.foo', $listener);
$this->dispatcher->addListener('post.foo', $listener);
$this->dispatcher->dispatch(self::PREFOO);
$this->assertEquals(1, $invoked);
}
public function testStopEventPropagation() {
$otherListener = new TestEventListener();
// postFoo() stops the propagation, so only one listener should
// be executed
// Manually set priority to enforce $this->listener to be called first
$this->dispatcher->addListener('post.foo', [$this->listener, 'postFoo'], 10);
$this->dispatcher->addListener('post.foo', [$otherListener, 'postFoo']);
$this->dispatcher->dispatch(self::POSTFOO);
$this->assertTrue($this->listener->postFooInvoked);
$this->assertFalse($otherListener->postFooInvoked);
}
public function testDispatchByPriority() {
$invoked = [];
$listener1 = function () use (&$invoked) {
$invoked[] = '1';
};
$listener2 = function () use (&$invoked) {
$invoked[] = '2';
};
$listener3 = function () use (&$invoked) {
$invoked[] = '3';
};
$this->dispatcher->addListener('pre.foo', $listener1, -10);
$this->dispatcher->addListener('pre.foo', $listener2);
$this->dispatcher->addListener('pre.foo', $listener3, 10);
$this->dispatcher->dispatch(self::PREFOO);
$this->assertEquals(['3', '2', '1'], $invoked);
}
public function testRemoveListener() {
$this->dispatcher->addListener('pre.bar', $this->listener);
$this->assertTrue($this->dispatcher->hasListeners(self::PREBAR));
$this->dispatcher->removeListener('pre.bar', $this->listener);
$this->assertFalse($this->dispatcher->hasListeners(self::PREBAR));
$this->dispatcher->removeListener('notExists', $this->listener);
}
public function testAddSubscriber() {
$eventSubscriber = new TestEventSubscriber();
$this->dispatcher->addSubscriber($eventSubscriber);
$this->assertTrue($this->dispatcher->hasListeners(self::PREFOO));
$this->assertTrue($this->dispatcher->hasListeners(self::POSTFOO));
}
public function testAddSubscriberWithPriorities() {
$eventSubscriber = new TestEventSubscriber();
$this->dispatcher->addSubscriber($eventSubscriber);
$eventSubscriber = new TestEventSubscriberWithPriorities();
$this->dispatcher->addSubscriber($eventSubscriber);
$listeners = $this->dispatcher->getListeners('pre.foo');
$this->assertTrue($this->dispatcher->hasListeners(self::PREFOO));
$this->assertCount(2, $listeners);
$this->assertInstanceOf(TestEventSubscriberWithPriorities::class, $listeners[0][0]);
}
public function testAddSubscriberWithMultipleListeners() {
$eventSubscriber = new TestEventSubscriberWithMultipleListeners();
$this->dispatcher->addSubscriber($eventSubscriber);
$listeners = $this->dispatcher->getListeners('pre.foo');
$this->assertTrue($this->dispatcher->hasListeners(self::PREFOO));
$this->assertCount(2, $listeners);
$this->assertEquals('preFoo2', $listeners[0][1]);
}
public function testRemoveSubscriber() {
$eventSubscriber = new TestEventSubscriber();
$this->dispatcher->addSubscriber($eventSubscriber);
$this->assertTrue($this->dispatcher->hasListeners(self::PREFOO));
$this->assertTrue($this->dispatcher->hasListeners(self::POSTFOO));
$this->dispatcher->removeSubscriber($eventSubscriber);
$this->assertFalse($this->dispatcher->hasListeners(self::PREFOO));
$this->assertFalse($this->dispatcher->hasListeners(self::POSTFOO));
}
public function testRemoveSubscriberWithPriorities() {
$eventSubscriber = new TestEventSubscriberWithPriorities();
$this->dispatcher->addSubscriber($eventSubscriber);
$this->assertTrue($this->dispatcher->hasListeners(self::PREFOO));
$this->dispatcher->removeSubscriber($eventSubscriber);
$this->assertFalse($this->dispatcher->hasListeners(self::PREFOO));
}
public function testRemoveSubscriberWithMultipleListeners() {
$eventSubscriber = new TestEventSubscriberWithMultipleListeners();
$this->dispatcher->addSubscriber($eventSubscriber);
$this->assertTrue($this->dispatcher->hasListeners(self::PREFOO));
$this->assertCount(2, $this->dispatcher->getListeners(self::PREFOO));
$this->dispatcher->removeSubscriber($eventSubscriber);
$this->assertFalse($this->dispatcher->hasListeners(self::PREFOO));
}
public function testEventReceivesTheDispatcherInstanceAsArgument() {
$listener = new TestWithDispatcher();
$this->dispatcher->addListener('test', [$listener, 'foo']);
$this->assertNull($listener->name);
$this->assertNull($listener->dispatcher);
$this->dispatcher->dispatch('test');
$this->assertEquals('test', $listener->name);
$this->assertSame($this->dispatcher, $listener->dispatcher);
}
/**
* @see https://bugs.php.net/bug.php?id=62976
*
* This bug affects:
* - The PHP 5.3 branch for versions < 5.3.18
* - The PHP 5.4 branch for versions < 5.4.8
* - The PHP 5.5 branch is not affected
*/
public function testWorkaroundForPhpBug62976() {
$dispatcher = $this->createEventDispatcher();
$dispatcher->addListener('bug.62976', new CallableClass());
$dispatcher->removeListener('bug.62976', function () {
});
$this->assertTrue($dispatcher->hasListeners('bug.62976'));
}
public function testHasListenersWhenAddedCallbackListenerIsRemoved() {
$listener = function () {
};
$this->dispatcher->addListener('foo', $listener);
$this->dispatcher->removeListener('foo', $listener);
$this->assertFalse($this->dispatcher->hasListeners());
}
public function testGetListenersWhenAddedCallbackListenerIsRemoved() {
$listener = function () {
};
$this->dispatcher->addListener('foo', $listener);
$this->dispatcher->removeListener('foo', $listener);
$this->assertSame([], $this->dispatcher->getListeners());
}
public function testHasListenersWithoutEventsReturnsFalseAfterHasListenersWithEventHasBeenCalled() {
$this->assertFalse($this->dispatcher->hasListeners('foo'));
$this->assertFalse($this->dispatcher->hasListeners());
}
public function testHasListenersIsLazy() {
$called = 0;
$listener = [
function () use (&$called) {
++$called;
},
'onFoo',
];
$this->dispatcher->addListener('foo', $listener);
$this->assertTrue($this->dispatcher->hasListeners());
$this->assertTrue($this->dispatcher->hasListeners('foo'));
$this->assertSame(0, $called);
}
public function testDispatchLazyListener() {
$called = 0;
$factory = function () use (&$called) {
++$called;
return new TestWithDispatcher();
};
$this->dispatcher->addListener('foo', [$factory, 'foo']);
$this->assertSame(0, $called);
$this->dispatcher->dispatch('foo', new Event());
$this->dispatcher->dispatch('foo', new Event());
$this->assertSame(1, $called);
}
public function testRemoveFindsLazyListeners() {
$test = new TestWithDispatcher();
$factory = function () use ($test) {
return $test;
};
$this->dispatcher->addListener('foo', [$factory, 'foo']);
$this->assertTrue($this->dispatcher->hasListeners('foo'));
$this->dispatcher->removeListener('foo', [$test, 'foo']);
$this->assertFalse($this->dispatcher->hasListeners('foo'));
$this->dispatcher->addListener('foo', [$test, 'foo']);
$this->assertTrue($this->dispatcher->hasListeners('foo'));
$this->dispatcher->removeListener('foo', [$factory, 'foo']);
$this->assertFalse($this->dispatcher->hasListeners('foo'));
}
public function testPriorityFindsLazyListeners() {
$test = new TestWithDispatcher();
$factory = function () use ($test) {
return $test;
};
$this->dispatcher->addListener('foo', [$factory, 'foo'], 3);
$this->assertSame(3, $this->dispatcher->getListenerPriority('foo', [$test, 'foo']));
$this->dispatcher->removeListener('foo', [$factory, 'foo']);
$this->dispatcher->addListener('foo', [$test, 'foo'], 5);
$this->assertSame(5, $this->dispatcher->getListenerPriority('foo', [$factory, 'foo']));
}
public function testGetLazyListeners() {
$test = new TestWithDispatcher();
$factory = function () use ($test) {
return $test;
};
$this->dispatcher->addListener('foo', [$factory, 'foo'], 3);
$this->assertSame([[$test, 'foo']], $this->dispatcher->getListeners('foo'));
$this->dispatcher->removeListener('foo', [$test, 'foo']);
$this->dispatcher->addListener('bar', [$factory, 'foo'], 3);
$this->assertSame(['bar' => [[$test, 'foo']]], $this->dispatcher->getListeners());
}
}
class CallableClass {
public function __invoke() {
}
}
class TestEventListener {
public $preFooInvoked = FALSE;
public $postFooInvoked = FALSE;
/**
* Listener methods
*/
public function preFoo(Event $e) {
$this->preFooInvoked = TRUE;
}
public function postFoo(Event $e) {
$this->postFooInvoked = TRUE;
$e->stopPropagation();
}
}
class TestWithDispatcher {
public $name;
public $dispatcher;
public function foo(Event $e, $name, $dispatcher) {
$this->name = $name;
$this->dispatcher = $dispatcher;
}
}
class TestEventSubscriber implements EventSubscriberInterface {
public static function getSubscribedEvents() {
return ['pre.foo' => 'preFoo', 'post.foo' => 'postFoo'];
}
}
class TestEventSubscriberWithPriorities implements EventSubscriberInterface {
public static function getSubscribedEvents() {
return [
'pre.foo' => ['preFoo', 10],
'post.foo' => ['postFoo'],
];
}
}
class TestEventSubscriberWithMultipleListeners implements EventSubscriberInterface {
public static function getSubscribedEvents() {
return [
'pre.foo' => [
['preFoo1'],
['preFoo2', 10],
],
];
}
}
@@ -0,0 +1,185 @@
<?php
namespace Drupal\Tests\Component\FileCache;
use Drupal\Component\FileCache\FileCache;
use Drupal\Component\FileCache\NullFileCache;
use Drupal\Component\FileCache\FileCacheFactory;
use Drupal\Component\Utility\Random;
use PHPUnit\Framework\TestCase;
/**
* @coversDefaultClass \Drupal\Component\FileCache\FileCacheFactory
* @group FileCache
*/
class FileCacheFactoryTest extends TestCase {
/**
* {@inheritdoc}
*/
protected function setUp() {
parent::setUp();
$configuration = [
'test_foo_settings' => [
'collection' => 'test-23',
'cache_backend_class' => '\Drupal\Tests\Component\FileCache\StaticFileCacheBackend',
'cache_backend_configuration' => [
'bin' => 'dog',
],
],
];
FileCacheFactory::setConfiguration($configuration);
FileCacheFactory::setPrefix('prefix');
}
/**
* @covers ::get
*/
public function testGet() {
$file_cache = FileCacheFactory::get('test_foo_settings', []);
// Ensure the right backend and configuration is used.
$filename = __DIR__ . '/Fixtures/llama-23.txt';
$realpath = realpath($filename);
$cid = 'prefix:test-23:' . $realpath;
$file_cache->set($filename, 23);
$static_cache = new StaticFileCacheBackend(['bin' => 'dog']);
$result = $static_cache->fetch([$cid]);
$this->assertNotEmpty($result);
// Cleanup static caches.
$file_cache->delete($filename);
}
/**
* @covers ::get
*/
public function testGetNoPrefix() {
FileCacheFactory::setPrefix(NULL);
$this->expectException(\InvalidArgumentException::class);
$this->expectExceptionMessage('Required prefix configuration is missing');
FileCacheFactory::get('test_foo_settings', []);
}
/**
* @covers ::get
*/
public function testGetDisabledFileCache() {
// Ensure the returned FileCache is an instance of FileCache::class.
$file_cache = FileCacheFactory::get('test_foo_settings', []);
$this->assertInstanceOf(FileCache::class, $file_cache);
$configuration = FileCacheFactory::getConfiguration();
$configuration[FileCacheFactory::DISABLE_CACHE] = TRUE;
FileCacheFactory::setConfiguration($configuration);
// Ensure the returned FileCache is now an instance of NullFileCache::class.
$file_cache = FileCacheFactory::get('test_foo_settings', []);
$this->assertInstanceOf(NullFileCache::class, $file_cache);
}
/**
* @covers ::get
*
* @dataProvider configurationDataProvider
*/
public function testGetConfigurationOverrides($configuration, $arguments, $class) {
FileCacheFactory::setConfiguration($configuration);
$file_cache = FileCacheFactory::get('test_foo_settings', $arguments);
$this->assertInstanceOf($class, $file_cache);
}
/**
* Data provider for testGetConfigurationOverrides().
*/
public function configurationDataProvider() {
$data = [];
// Get a unique FileCache class.
$file_cache = $this->getMockBuilder(FileCache::class)
->disableOriginalConstructor()
->getMock();
$class = get_class($file_cache);
// Test fallback configuration.
$data['fallback-configuration'] = [
[],
[],
FileCache::class,
];
// Test default configuration.
$data['default-configuration'] = [
['default' => ['class' => $class]],
[],
$class,
];
// Test specific per collection setting.
$data['collection-setting'] = [
['test_foo_settings' => ['class' => $class]],
[],
$class,
];
// Test default configuration plus specific per collection setting.
$data['default-plus-collection-setting'] = [
[
'default' => ['class' => '\stdClass'],
'test_foo_settings' => ['class' => $class],
],
[],
$class,
];
// Test default configuration plus class specific override.
$data['default-plus-class-override'] = [
['default' => ['class' => '\stdClass']],
['class' => $class],
$class,
];
// Test default configuration plus class specific override plus specific
// per collection setting.
$data['default-plus-class-plus-collection-setting'] = [
[
'default' => ['class' => '\stdClass'],
'test_foo_settings' => ['class' => $class],
],
['class' => '\stdClass'],
$class,
];
return $data;
}
/**
* @covers ::getConfiguration
* @covers ::setConfiguration
*/
public function testGetSetConfiguration() {
$configuration = FileCacheFactory::getConfiguration();
$configuration['test_foo_bar'] = ['bar' => 'llama'];
FileCacheFactory::setConfiguration($configuration);
$configuration = FileCacheFactory::getConfiguration();
$this->assertEquals(['bar' => 'llama'], $configuration['test_foo_bar']);
}
/**
* @covers ::getPrefix
* @covers ::setPrefix
*/
public function testGetSetPrefix() {
// Random generator.
$random = new Random();
$prefix = $random->name(8, TRUE);
FileCacheFactory::setPrefix($prefix);
$this->assertEquals($prefix, FileCacheFactory::getPrefix());
}
}
@@ -0,0 +1,142 @@
<?php
namespace Drupal\Tests\Component\FileCache;
use Drupal\Component\FileCache\FileCache;
use PHPUnit\Framework\TestCase;
/**
* @coversDefaultClass \Drupal\Component\FileCache\FileCache
* @group FileCache
*/
class FileCacheTest extends TestCase {
/**
* FileCache object used for the tests.
*
* @var \Drupal\Component\FileCache\FileCacheInterface
*/
protected $fileCache;
/**
* Static FileCache object used for verification of tests.
*
* @var \Drupal\Component\FileCache\FileCacheBackendInterface
*/
protected $staticFileCache;
/**
* {@inheritdoc}
*/
protected function setUp() {
parent::setUp();
$this->fileCache = new FileCache('prefix', 'test', '\Drupal\Tests\Component\FileCache\StaticFileCacheBackend', ['bin' => 'llama']);
$this->staticFileCache = new StaticFileCacheBackend(['bin' => 'llama']);
}
/**
* @covers ::get
* @covers ::__construct
*/
public function testGet() {
// Test a cache miss.
$result = $this->fileCache->get(__DIR__ . '/Fixtures/no-llama-42.yml');
$this->assertNull($result);
// Test a cache hit.
$filename = __DIR__ . '/Fixtures/llama-42.txt';
$realpath = realpath($filename);
$cid = 'prefix:test:' . $realpath;
$data = [
'mtime' => filemtime($realpath),
'filepath' => $realpath,
'data' => 42,
];
$this->staticFileCache->store($cid, $data);
$result = $this->fileCache->get($filename);
$this->assertEquals(42, $result);
// Cleanup static caches.
$this->fileCache->delete($filename);
}
/**
* @covers ::getMultiple
*/
public function testGetMultiple() {
// Test a cache miss.
$result = $this->fileCache->getMultiple([__DIR__ . '/Fixtures/no-llama-42.yml']);
$this->assertEmpty($result);
// Test a cache hit.
$filename = __DIR__ . '/Fixtures/llama-42.txt';
$realpath = realpath($filename);
$cid = 'prefix:test:' . $realpath;
$data = [
'mtime' => filemtime($realpath),
'filepath' => $realpath,
'data' => 42,
];
$this->staticFileCache->store($cid, $data);
$result = $this->fileCache->getMultiple([$filename]);
$this->assertEquals([$filename => 42], $result);
// Test a static cache hit.
$file2 = __DIR__ . '/Fixtures/llama-23.txt';
$this->fileCache->set($file2, 23);
$result = $this->fileCache->getMultiple([$filename, $file2]);
$this->assertEquals([$filename => 42, $file2 => 23], $result);
// Cleanup static caches.
$this->fileCache->delete($filename);
$this->fileCache->delete($file2);
}
/**
* @covers ::set
*/
public function testSet() {
$filename = __DIR__ . '/Fixtures/llama-23.txt';
$realpath = realpath($filename);
$cid = 'prefix:test:' . $realpath;
$data = [
'mtime' => filemtime($realpath),
'filepath' => $realpath,
'data' => 23,
];
$this->fileCache->set($filename, 23);
$result = $this->staticFileCache->fetch([$cid]);
$this->assertEquals([$cid => $data], $result);
// Cleanup static caches.
$this->fileCache->delete($filename);
}
/**
* @covers ::delete
*/
public function testDelete() {
$filename = __DIR__ . '/Fixtures/llama-23.txt';
$realpath = realpath($filename);
$cid = 'prefix:test:' . $realpath;
$this->fileCache->set($filename, 23);
// Ensure data is removed after deletion.
$this->fileCache->delete($filename);
$result = $this->staticFileCache->fetch([$cid]);
$this->assertEquals([], $result);
$result = $this->fileCache->get($filename);
$this->assertNull($result);
}
}
@@ -0,0 +1,71 @@
<?php
namespace Drupal\Tests\Component\FileCache;
use Drupal\Component\FileCache\FileCacheBackendInterface;
/**
* Allows to cache data based on file modification dates in a static cache.
*/
class StaticFileCacheBackend implements FileCacheBackendInterface {
/**
* Internal static cache.
*
* @var array
*/
protected static $cache = [];
/**
* Bin used for storing the data in the static cache.
*
* @var string
*/
protected $bin;
/**
* Constructs a PHP Storage FileCache backend.
*
* @param array $configuration
* (optional) Configuration used to configure this object.
*/
public function __construct($configuration) {
$this->bin = isset($configuration['bin']) ? $configuration['bin'] : 'file_cache';
}
/**
* {@inheritdoc}
*/
public function fetch(array $cids) {
$result = [];
foreach ($cids as $cid) {
if (isset(static::$cache[$this->bin][$cid])) {
$result[$cid] = static::$cache[$this->bin][$cid];
}
}
return $result;
}
/**
* {@inheritdoc}
*/
public function store($cid, $data) {
static::$cache[$this->bin][$cid] = $data;
}
/**
* {@inheritdoc}
*/
public function delete($cid) {
unset(static::$cache[$this->bin][$cid]);
}
/**
* Allows tests to reset the static cache to avoid side effects.
*/
public static function reset() {
static::$cache = [];
}
}
@@ -0,0 +1,96 @@
<?php
namespace Drupal\Tests\Component\FileSecurity;
use Drupal\Component\FileSecurity\FileSecurity;
use org\bovigo\vfs\vfsStream;
use PHPUnit\Framework\TestCase;
/**
* Tests the file security component.
*
* @coversDefaultClass \Drupal\Component\FileSecurity\FileSecurity
* @group FileSecurity
*/
class FileSecurityTest extends TestCase {
/**
* @covers ::writeHtaccess
*/
public function testWriteHtaccessPrivate() {
vfsStream::setup('root');
FileSecurity::writeHtaccess(vfsStream::url('root'));
$htaccess_file = vfsStream::url('root') . '/.htaccess';
$this->assertFileExists($htaccess_file);
$this->assertEquals('0444', substr(sprintf('%o', fileperms($htaccess_file)), -4));
$htaccess_contents = file_get_contents($htaccess_file);
$this->assertContains("Require all denied", $htaccess_contents);
}
/**
* @covers ::writeHtaccess
*/
public function testWriteHtaccessPublic() {
vfsStream::setup('root');
$this->assertTrue(FileSecurity::writeHtaccess(vfsStream::url('root'), FALSE));
$htaccess_file = vfsStream::url('root') . '/.htaccess';
$this->assertFileExists($htaccess_file);
$this->assertEquals('0444', substr(sprintf('%o', fileperms($htaccess_file)), -4));
$htaccess_contents = file_get_contents($htaccess_file);
$this->assertNotContains("Require all denied", $htaccess_contents);
}
/**
* @covers ::writeHtaccess
*/
public function testWriteHtaccessForceOverwrite() {
vfsStream::setup('root');
$htaccess_file = vfsStream::url('root') . '/.htaccess';
file_put_contents($htaccess_file, "foo");
$this->assertTrue(FileSecurity::writeHtaccess(vfsStream::url('root'), TRUE, TRUE));
$htaccess_contents = file_get_contents($htaccess_file);
$this->assertContains("Require all denied", $htaccess_contents);
$this->assertNotContains("foo", $htaccess_contents);
}
/**
* @covers ::writeHtaccess
*/
public function testWriteHtaccessFailure() {
vfsStream::setup('root');
$this->assertFalse(FileSecurity::writeHtaccess(vfsStream::url('root') . '/foo'));
}
/**
* @covers ::writeWebConfig
*/
public function testWriteWebConfig() {
vfsStream::setup('root');
$this->assertTrue(FileSecurity::writeWebConfig(vfsStream::url('root')));
$web_config_file = vfsStream::url('root') . '/web.config';
$this->assertFileExists($web_config_file);
$this->assertEquals('0444', substr(sprintf('%o', fileperms($web_config_file)), -4));
}
/**
* @covers ::writeWebConfig
*/
public function testWriteWebConfigForceOverwrite() {
vfsStream::setup('root');
$web_config_file = vfsStream::url('root') . '/web.config';
file_put_contents($web_config_file, "foo");
$this->assertTrue(FileSecurity::writeWebConfig(vfsStream::url('root'), TRUE));
$this->assertFileExists($web_config_file);
$this->assertEquals('0444', substr(sprintf('%o', fileperms($web_config_file)), -4));
$this->assertNotContains("foo", $web_config_file);
}
/**
* @covers ::writeWebConfig
*/
public function testWriteWebConfigFailure() {
vfsStream::setup('root');
$this->assertFalse(FileSecurity::writeWebConfig(vfsStream::url('root') . '/foo'));
}
}
@@ -0,0 +1,109 @@
<?php
namespace Drupal\Tests\Component\FileSystem;
use Drupal\Component\FileSystem\RegexDirectoryIterator;
use org\bovigo\vfs\vfsStream;
use PHPUnit\Framework\TestCase;
/**
* @coversDefaultClass \Drupal\Component\FileSystem\RegexDirectoryIterator
* @group FileSystem
*/
class RegexDirectoryIteratorTest extends TestCase {
/**
* @covers ::accept
* @dataProvider providerTestRegexDirectoryIterator
*/
public function testRegexDirectoryIterator(array $directory, $regex, array $expected) {
vfsStream::setup('root', NULL, $directory);
$iterator = new RegexDirectoryIterator(vfsStream::url('root'), $regex);
// Create an array of filenames to assert against.
$file_list = array_map(function (\SplFileInfo $file) {
return $file->getFilename();
}, array_values(iterator_to_array($iterator)));
$this->assertSame($expected, $file_list);
}
/**
* Provider for self::testRegexDirectoryIterator().
*/
public function providerTestRegexDirectoryIterator() {
return [
[
[
'1.yml' => '',
],
'/\.yml$/',
[
'1.yml',
],
],
[
[
'1.yml' => '',
'2.yml' => '',
'3.txt' => '',
],
'/\.yml$/',
[
'1.yml',
'2.yml',
],
],
[
[
'1.yml' => '',
'2.yml' => '',
'3.txt' => '',
],
'/\.txt/',
[
'3.txt',
],
],
[
[
'1.yml' => '',
// Ensure we don't recurse in directories even if that match the
// regex.
'2.yml' => [
'3.yml' => '',
'4.yml' => '',
],
'3.txt' => '',
],
'/\.yml$/',
[
'1.yml',
],
],
[
[
'1.yml' => '',
'2.yml' => '',
'3.txt' => '',
],
'/^\d/',
[
'1.yml',
'2.yml',
'3.txt',
],
],
[
[
'1.yml' => '',
'2.yml' => '',
'3.txt' => '',
],
'/^\D/',
[],
],
];
}
}
@@ -0,0 +1,371 @@
<?php
namespace Drupal\Tests\Component\Gettext;
use Drupal\Component\Gettext\PoHeader;
use PHPUnit\Framework\TestCase;
/**
* Unit tests for the Gettext PO file header handling features.
*
* @see Drupal\Component\Gettext\PoHeader.
*
* @group Gettext
*/
class PoHeaderTest extends TestCase {
/**
* Tests that plural expressions are evaluated correctly.
*
* Validate that the given plural expressions is evaluated with the correct
* plural formula.
*
* @param string $plural
* The plural expression.
* @param array $expected
* Array of expected plural positions keyed by plural value.
*
* @dataProvider providerTestPluralsFormula
*/
public function testPluralsFormula($plural, $expected) {
$p = new PoHeader();
$parsed = $p->parsePluralForms($plural);
list($nplurals, $new_plural) = $parsed;
foreach ($expected as $number => $plural_form) {
$result = isset($new_plural[$number]) ? $new_plural[$number] : $new_plural['default'];
$this->assertEquals($result, $plural_form, 'Difference found at ' . $number . ': ' . $plural_form . ' versus ' . $result);
}
}
/**
* Data provider for testPluralsFormula.
*
* Gets pairs of plural expressions and expected plural positions keyed by
* plural value.
*
* @return array
* Pairs of plural expressions and expected plural positions keyed by plural
* value.
*/
public function providerTestPluralsFormula() {
return [
[
'nplurals=1; plural=0;',
['default' => 0],
],
[
'nplurals=2; plural=(n > 1);',
[0 => 0, 1 => 0, 'default' => 1],
],
[
'nplurals=2; plural=(n!=1);',
[1 => 0, 'default' => 1],
],
[
'nplurals=2; plural=(((n==1)||((n%10)==1))?(0):1);',
[
1 => 0,
11 => 0,
21 => 0,
31 => 0,
41 => 0,
51 => 0,
61 => 0,
71 => 0,
81 => 0,
91 => 0,
101 => 0,
111 => 0,
121 => 0,
131 => 0,
141 => 0,
151 => 0,
161 => 0,
171 => 0,
181 => 0,
191 => 0,
'default' => 1,
],
],
[
'nplurals=3; plural=((((n%10)==1)&&((n%100)!=11))?(0):(((((n%10)>=2)&&((n%10)<=4))&&(((n%100)<10)||((n%100)>=20)))?(1):2));',
[
1 => 0,
2 => 1,
3 => 1,
4 => 1,
21 => 0,
22 => 1,
23 => 1,
24 => 1,
31 => 0,
32 => 1,
33 => 1,
34 => 1,
41 => 0,
42 => 1,
43 => 1,
44 => 1,
51 => 0,
52 => 1,
53 => 1,
54 => 1,
61 => 0,
62 => 1,
63 => 1,
64 => 1,
71 => 0,
72 => 1,
73 => 1,
74 => 1,
81 => 0,
82 => 1,
83 => 1,
84 => 1,
91 => 0,
92 => 1,
93 => 1,
94 => 1,
101 => 0,
102 => 1,
103 => 1,
104 => 1,
121 => 0,
122 => 1,
123 => 1,
124 => 1,
131 => 0,
132 => 1,
133 => 1,
134 => 1,
141 => 0,
142 => 1,
143 => 1,
144 => 1,
151 => 0,
152 => 1,
153 => 1,
154 => 1,
161 => 0,
162 => 1,
163 => 1,
164 => 1,
171 => 0,
172 => 1,
173 => 1,
174 => 1,
181 => 0,
182 => 1,
183 => 1,
184 => 1,
191 => 0,
192 => 1,
193 => 1,
194 => 1,
'default' => 2,
],
],
[
'nplurals=3; plural=((n==1)?(0):(((n>=2)&&(n<=4))?(1):2));',
[
1 => 0,
2 => 1,
3 => 1,
4 => 1,
'default' => 2,
],
],
[
'nplurals=3; plural=((n==1)?(0):(((n==0)||(((n%100)>0)&&((n%100)<20)))?(1):2));',
[
0 => 1,
1 => 0,
2 => 1,
3 => 1,
4 => 1,
5 => 1,
6 => 1,
7 => 1,
8 => 1,
9 => 1,
10 => 1,
11 => 1,
12 => 1,
13 => 1,
14 => 1,
15 => 1,
16 => 1,
17 => 1,
18 => 1,
19 => 1,
101 => 1,
102 => 1,
103 => 1,
104 => 1,
105 => 1,
106 => 1,
107 => 1,
108 => 1,
109 => 1,
110 => 1,
111 => 1,
112 => 1,
113 => 1,
114 => 1,
115 => 1,
116 => 1,
117 => 1,
118 => 1,
119 => 1,
'default' => 2,
],
],
[
'nplurals=3; plural=((n==1)?(0):(((((n%10)>=2)&&((n%10)<=4))&&(((n%100)<10)||((n%100)>=20)))?(1):2));',
[
1 => 0,
2 => 1,
3 => 1,
4 => 1,
22 => 1,
23 => 1,
24 => 1,
32 => 1,
33 => 1,
34 => 1,
42 => 1,
43 => 1,
44 => 1,
52 => 1,
53 => 1,
54 => 1,
62 => 1,
63 => 1,
64 => 1,
72 => 1,
73 => 1,
74 => 1,
82 => 1,
83 => 1,
84 => 1,
92 => 1,
93 => 1,
94 => 1,
102 => 1,
103 => 1,
104 => 1,
122 => 1,
123 => 1,
124 => 1,
132 => 1,
133 => 1,
134 => 1,
142 => 1,
143 => 1,
144 => 1,
152 => 1,
153 => 1,
154 => 1,
162 => 1,
163 => 1,
164 => 1,
172 => 1,
173 => 1,
174 => 1,
182 => 1,
183 => 1,
184 => 1,
192 => 1,
193 => 1,
194 => 1,
'default' => 2,
],
],
[
'nplurals=4; plural=(((n==1)||(n==11))?(0):(((n==2)||(n==12))?(1):(((n>2)&&(n<20))?(2):3)));',
[
1 => 0,
2 => 1,
3 => 2,
4 => 2,
5 => 2,
6 => 2,
7 => 2,
8 => 2,
9 => 2,
10 => 2,
11 => 0,
12 => 1,
13 => 2,
14 => 2,
15 => 2,
16 => 2,
17 => 2,
18 => 2,
19 => 2,
'default' => 3,
],
],
[
'nplurals=4; plural=(((n%100)==1)?(0):(((n%100)==2)?(1):((((n%100)==3)||((n%100)==4))?(2):3)));',
[
1 => 0,
2 => 1,
3 => 2,
4 => 2,
101 => 0,
102 => 1,
103 => 2,
104 => 2,
'default' => 3,
],
],
[
'nplurals=5; plural=((n==1)?(0):((n==2)?(1):((n<7)?(2):((n<11)?(3):4))));',
[
0 => 2,
1 => 0,
2 => 1,
3 => 2,
4 => 2,
5 => 2,
6 => 2,
7 => 3,
8 => 3,
9 => 3,
10 => 3,
'default' => 4,
],
],
[
'nplurals=6; plural=((n==1)?(0):((n==0)?(1):((n==2)?(2):((((n%100)>=3)&&((n%100)<=10))?(3):((((n%100)>=11)&&((n%100)<=99))?(4):5)))));',
[
0 => 1,
1 => 0,
2 => 2,
3 => 3,
4 => 3,
5 => 3,
6 => 3,
7 => 3,
8 => 3,
9 => 3,
10 => 3,
100 => 5,
101 => 5,
102 => 5,
103 => 3,
104 => 3,
105 => 3,
106 => 3,
107 => 3,
108 => 3,
109 => 3,
110 => 3,
'default' => 4,
],
],
];
}
}
@@ -0,0 +1,103 @@
<?php
namespace Drupal\Tests\Component\Gettext;
use Drupal\Component\Gettext\PoItem;
use Drupal\Component\Gettext\PoStreamWriter;
use org\bovigo\vfs\vfsStream;
use org\bovigo\vfs\vfsStreamFile;
use PHPUnit\Framework\TestCase;
/**
* @coversDefaultClass \Drupal\Component\Gettext\PoStreamWriter
* @group Gettext
*/
class PoStreamWriterTest extends TestCase {
/**
* The PO writer object under test.
*
* @var \Drupal\Component\Gettext\PoStreamWriter
*/
protected $poWriter;
/**
* The mock po file.
*
* @var \org\bovigo\vfs\vfsStreamFile
*/
protected $poFile;
/**
* {@inheritdoc}
*/
protected function setUp() {
parent::setUp();
$this->poWriter = new PoStreamWriter();
$root = vfsStream::setup();
$this->poFile = new vfsStreamFile('powriter.po');
$root->addChild($this->poFile);
}
/**
* @covers ::getURI
*/
public function testGetUriException() {
$this->expectException(\Exception::class, 'No URI set.');
$this->poWriter->getURI();
}
/**
* @covers ::writeItem
* @dataProvider providerWriteData
*/
public function testWriteItem($poContent, $expected, $long) {
if ($long) {
$this->expectException(\Exception::class, 'Unable to write data:');
}
// Limit the file system quota to make the write fail on long strings.
vfsStream::setQuota(10);
$this->poWriter->setURI($this->poFile->url());
$this->poWriter->open();
$poItem = $this->prophesize(PoItem::class);
$poItem->__toString()->willReturn($poContent);
$this->poWriter->writeItem($poItem->reveal());
$this->poWriter->close();
$this->assertEquals(file_get_contents($this->poFile->url()), $expected);
}
/**
* @return array
* - Content to write.
* - Written content.
* - Content longer than 10 bytes.
*/
public function providerWriteData() {
return [
['', '', FALSE],
["\r\n", "\r\n", FALSE],
['write this if you can', 'write this', TRUE],
['éáíó>&', 'éáíó>&', FALSE],
['éáíó>&<', 'éáíó>&', TRUE],
['中文 890', '中文 890', FALSE],
['中文 89012', '中文 890', TRUE],
];
}
/**
* @covers ::close
*/
public function testCloseException() {
$this->expectException(\Exception::class, 'Cannot close stream that is not open.');
$this->poWriter->close();
}
}
@@ -0,0 +1,191 @@
<?php
namespace Drupal\Tests\Component\Graph;
use Drupal\Component\Graph\Graph;
use PHPUnit\Framework\TestCase;
/**
* @coversDefaultClass \Drupal\Component\Graph\Graph
* @group Graph
*/
class GraphTest extends TestCase {
/**
* Test depth-first-search features.
*/
public function testDepthFirstSearch() {
// The sample graph used is:
// 1 --> 2 --> 3 5 ---> 6
// | ^ ^
// | | |
// | | |
// +---> 4 <-- 7 8 ---> 9
$graph = $this->normalizeGraph([
1 => [2],
2 => [3, 4],
3 => [],
4 => [3],
5 => [6],
7 => [4, 5],
8 => [9],
9 => [],
]);
$graph_object = new Graph($graph);
$graph = $graph_object->searchAndSort();
$expected_paths = [
1 => [2, 3, 4],
2 => [3, 4],
3 => [],
4 => [3],
5 => [6],
7 => [4, 3, 5, 6],
8 => [9],
9 => [],
];
$this->assertPaths($graph, $expected_paths);
$expected_reverse_paths = [
1 => [],
2 => [1],
3 => [2, 1, 4, 7],
4 => [2, 1, 7],
5 => [7],
7 => [],
8 => [],
9 => [8],
];
$this->assertReversePaths($graph, $expected_reverse_paths);
// Assert that DFS didn't created "missing" vertexes automatically.
$this->assertFalse(isset($graph[6]), 'Vertex 6 has not been created');
$expected_components = [
[1, 2, 3, 4, 5, 7],
[8, 9],
];
$this->assertComponents($graph, $expected_components);
$expected_weights = [
[1, 2, 3],
[2, 4, 3],
[7, 4, 3],
[7, 5],
[8, 9],
];
$this->assertWeights($graph, $expected_weights);
}
/**
* Normalizes a graph.
*
* @param $graph
* A graph array processed by \Drupal\Component\Graph\Graph::searchAndSort()
*
* @return array
* The normalized version of a graph.
*/
protected function normalizeGraph($graph) {
$normalized_graph = [];
foreach ($graph as $vertex => $edges) {
// Create vertex even if it hasn't any edges.
$normalized_graph[$vertex] = [];
foreach ($edges as $edge) {
$normalized_graph[$vertex]['edges'][$edge] = TRUE;
}
}
return $normalized_graph;
}
/**
* Verify expected paths in a graph.
*
* @param $graph
* A graph array processed by \Drupal\Component\Graph\Graph::searchAndSort()
* @param $expected_paths
* An associative array containing vertices with their expected paths.
*/
protected function assertPaths($graph, $expected_paths) {
foreach ($expected_paths as $vertex => $paths) {
// Build an array with keys = $paths and values = TRUE.
$expected = array_fill_keys($paths, TRUE);
$result = isset($graph[$vertex]['paths']) ? $graph[$vertex]['paths'] : [];
$this->assertEquals($expected, $result, sprintf('Expected paths for vertex %s: %s, got %s', $vertex, $this->displayArray($expected, TRUE), $this->displayArray($result, TRUE)));
}
}
/**
* Verify expected reverse paths in a graph.
*
* @param $graph
* A graph array processed by \Drupal\Component\Graph\Graph::searchAndSort()
* @param $expected_reverse_paths
* An associative array containing vertices with their expected reverse
* paths.
*/
protected function assertReversePaths($graph, $expected_reverse_paths) {
foreach ($expected_reverse_paths as $vertex => $paths) {
// Build an array with keys = $paths and values = TRUE.
$expected = array_fill_keys($paths, TRUE);
$result = isset($graph[$vertex]['reverse_paths']) ? $graph[$vertex]['reverse_paths'] : [];
$this->assertEquals($expected, $result, sprintf('Expected reverse paths for vertex %s: %s, got %s', $vertex, $this->displayArray($expected, TRUE), $this->displayArray($result, TRUE)));
}
}
/**
* Verify expected components in a graph.
*
* @param $graph
* A graph array processed by \Drupal\Component\Graph\Graph::searchAndSort().
* @param $expected_components
* An array containing of components defined as a list of their vertices.
*/
protected function assertComponents($graph, $expected_components) {
$unassigned_vertices = array_fill_keys(array_keys($graph), TRUE);
foreach ($expected_components as $component) {
$result_components = [];
foreach ($component as $vertex) {
$result_components[] = $graph[$vertex]['component'];
unset($unassigned_vertices[$vertex]);
}
$this->assertCount(1, array_unique($result_components), sprintf('Expected one unique component for vertices %s, got %s', $this->displayArray($component), $this->displayArray($result_components)));
}
$this->assertEquals([], $unassigned_vertices, sprintf('Vertices not assigned to a component: %s', $this->displayArray($unassigned_vertices, TRUE)));
}
/**
* Verify expected order in a graph.
*
* @param $graph
* A graph array processed by \Drupal\Component\Graph\Graph::searchAndSort()
* @param $expected_orders
* An array containing lists of vertices in their expected order.
*/
protected function assertWeights($graph, $expected_orders) {
foreach ($expected_orders as $order) {
$previous_vertex = array_shift($order);
foreach ($order as $vertex) {
$this->assertTrue($graph[$previous_vertex]['weight'] < $graph[$vertex]['weight'], sprintf('Weights of %s and %s are correct relative to each other', $previous_vertex, $vertex));
}
}
}
/**
* Helper function to output vertices as comma-separated list.
*
* @param $paths
* An array containing a list of vertices.
* @param $keys
* (optional) Whether to output the keys of $paths instead of the values.
*/
protected function displayArray($paths, $keys = FALSE) {
if (!empty($paths)) {
return implode(', ', $keys ? array_keys($paths) : $paths);
}
else {
return '(empty)';
}
}
}
@@ -0,0 +1,63 @@
<?php
/**
* @file
* Contains \Drupal\Tests\Component\HttpFoundation\SecuredRedirectResponseTest.
*/
namespace Drupal\Tests\Component\HttpFoundation;
use Drupal\Component\HttpFoundation\SecuredRedirectResponse;
use PHPUnit\Framework\TestCase;
use Symfony\Component\HttpFoundation\Cookie;
use Symfony\Component\HttpFoundation\RedirectResponse;
/**
* Test secure redirect base class.
*
* @group Routing
* @coversDefaultClass \Drupal\Component\HttpFoundation\SecuredRedirectResponse
*/
class SecuredRedirectResponseTest extends TestCase {
/**
* Test copying of redirect response.
*
* @covers ::createFromRedirectResponse
* @covers ::fromResponse
*/
public function testRedirectCopy() {
$redirect = new RedirectResponse('/magic_redirect_url', 301, ['x-cache-foobar' => 123]);
$redirect->setProtocolVersion('2.0');
$redirect->setCharset('ibm-943_P14A-2000');
$redirect->headers->setCookie(new Cookie('name', 'value', 0, '/', NULL, FALSE, TRUE, FALSE, NULL));
// Make a cloned redirect.
$secureRedirect = SecuredRedirectStub::createFromRedirectResponse($redirect);
$this->assertEquals('/magic_redirect_url', $secureRedirect->getTargetUrl());
$this->assertEquals(301, $secureRedirect->getStatusCode());
// We pull the headers from the original redirect because there are default headers applied.
$headers1 = $redirect->headers->allPreserveCase();
$headers2 = $secureRedirect->headers->allPreserveCase();
// We unset cache headers so we don't test arcane Symfony weirdness.
// https://github.com/symfony/symfony/issues/16171
unset($headers1['Cache-Control'], $headers2['Cache-Control']);
$this->assertEquals($headers1, $headers2);
$this->assertEquals('2.0', $secureRedirect->getProtocolVersion());
$this->assertEquals('ibm-943_P14A-2000', $secureRedirect->getCharset());
$this->assertEquals($redirect->headers->getCookies(), $secureRedirect->headers->getCookies());
}
}
class SecuredRedirectStub extends SecuredRedirectResponse {
/**
* {@inheritdoc}
*/
protected function isSafe($url) {
// Empty implementation for testing.
return TRUE;
}
}
@@ -0,0 +1,25 @@
<?php
namespace Drupal\Tests\Component\PhpStorage;
use Drupal\Component\PhpStorage\FileStorage;
use PHPUnit\Framework\TestCase;
/**
* Tests FileStorage deprecations.
*
* @coversDefaultClass \Drupal\Component\PhpStorage\FileStorage
* @group legacy
* @group Drupal
* @group PhpStorage
*/
class FileStorageDeprecationTest extends TestCase {
/**
* @expectedDeprecation htaccessLines() is deprecated in drupal:8.8.0 and is removed from drupal:9.0.0. Use \Drupal\Component\FileSecurity\FileSecurity::htaccessLines() instead. See https://www.drupal.org/node/3075098
*/
public function testHtAccessLines() {
$this->assertNotEmpty(FileStorage::htaccessLines());
}
}
@@ -0,0 +1,114 @@
<?php
namespace Drupal\Tests\Component\PhpStorage;
use Drupal\Component\PhpStorage\FileStorage;
use Drupal\Component\PhpStorage\FileReadOnlyStorage;
use Drupal\Component\Utility\Random;
/**
* @coversDefaultClass \Drupal\Component\PhpStorage\FileReadOnlyStorage
*
* @group Drupal
* @group PhpStorage
*/
class FileStorageReadOnlyTest extends PhpStorageTestBase {
/**
* Standard test settings to pass to storage instances.
*
* @var array
*/
protected $standardSettings;
/**
* Read only test settings to pass to storage instances.
*
* @var array
*/
protected $readonlyStorage;
/**
* {@inheritdoc}
*/
protected function setUp() {
parent::setUp();
$this->standardSettings = [
'directory' => $this->directory,
'bin' => 'test',
];
$this->readonlyStorage = [
'directory' => $this->directory,
// Let this read from the bin where the other instance is writing.
'bin' => 'test',
];
}
/**
* Tests writing with one class and reading with another.
*/
public function testReadOnly() {
// Random generator.
$random = new Random();
$php = new FileStorage($this->standardSettings);
$name = $random->name(8, TRUE) . '/' . $random->name(8, TRUE) . '.php';
// Find a global that doesn't exist.
do {
$random = mt_rand(10000, 100000);
} while (isset($GLOBALS[$random]));
// Write out a PHP file and ensure it's successfully loaded.
$code = "<?php\n\$GLOBALS[$random] = TRUE;";
$success = $php->save($name, $code);
$this->assertSame(TRUE, $success);
$php_read = new FileReadOnlyStorage($this->readonlyStorage);
$php_read->load($name);
$this->assertTrue($GLOBALS[$random]);
// If the file was successfully loaded, it must also exist, but ensure the
// exists() method returns that correctly.
$this->assertSame(TRUE, $php_read->exists($name));
// Saving and deleting should always fail.
$this->assertFalse($php_read->save($name, $code));
$this->assertFalse($php_read->delete($name));
unset($GLOBALS[$random]);
}
/**
* @covers ::writeable
*/
public function testWriteable() {
$php_read = new FileReadOnlyStorage($this->readonlyStorage);
$this->assertFalse($php_read->writeable());
}
/**
* @covers ::deleteAll
*/
public function testDeleteAll() {
// Random generator.
$random = new Random();
$php = new FileStorage($this->standardSettings);
$name = $random->name(8, TRUE) . '/' . $random->name(8, TRUE) . '.php';
// Find a global that doesn't exist.
do {
$random = mt_rand(10000, 100000);
} while (isset($GLOBALS[$random]));
// Write our the file so we can test deleting.
$code = "<?php\n\$GLOBALS[$random] = TRUE;";
$this->assertTrue($php->save($name, $code));
$php_read = new FileReadOnlyStorage($this->readonlyStorage);
$this->assertFalse($php_read->deleteAll());
// Make sure directory exists prior to removal.
$this->assertDirectoryExists($this->directory . '/test');
}
}
@@ -0,0 +1,107 @@
<?php
namespace Drupal\Tests\Component\PhpStorage;
use Drupal\Component\PhpStorage\FileStorage;
use Drupal\Component\Utility\Random;
use org\bovigo\vfs\vfsStreamDirectory;
use PHPUnit\Framework\Error\Warning;
/**
* @coversDefaultClass \Drupal\Component\PhpStorage\FileStorage
* @group Drupal
* @group PhpStorage
*/
class FileStorageTest extends PhpStorageTestBase {
/**
* Standard test settings to pass to storage instances.
*
* @var array
*/
protected $standardSettings;
/**
* {@inheritdoc}
*/
protected function setUp() {
parent::setUp();
$this->standardSettings = [
'directory' => $this->directory,
'bin' => 'test',
];
}
/**
* Tests basic load/save/delete operations.
*
* @covers ::load
* @covers ::save
* @covers ::exists
* @covers ::delete
*/
public function testCRUD() {
$php = new FileStorage($this->standardSettings);
$this->assertCRUD($php);
}
/**
* @covers ::writeable
*/
public function testWriteable() {
$php = new FileStorage($this->standardSettings);
$this->assertTrue($php->writeable());
}
/**
* @covers ::deleteAll
*/
public function testDeleteAll() {
// Random generator.
$random_generator = new Random();
// Write out some files.
$php = new FileStorage($this->standardSettings);
$name = $random_generator->name(8, TRUE) . '/' . $random_generator->name(8, TRUE) . '.php';
// Find a global that doesn't exist.
do {
$random = mt_rand(10000, 100000);
} while (isset($GLOBALS[$random]));
// Write out a PHP file and ensure it's successfully loaded.
$code = "<?php\n\$GLOBALS[$random] = TRUE;";
$this->assertTrue($php->save($name, $code), 'Saved php file');
$php->load($name);
$this->assertTrue($GLOBALS[$random], 'File saved correctly with correct value');
// Make sure directory exists prior to removal.
$this->assertDirectoryExists($this->directory . '/test');
$this->assertTrue($php->deleteAll(), 'Delete all reported success');
$this->assertFalse($php->load($name));
$this->assertDirectoryNotExists($this->directory . '/test');
// Should still return TRUE if directory has already been deleted.
$this->assertTrue($php->deleteAll(), 'Delete all succeeds with nothing to delete');
unset($GLOBALS[$random]);
}
/**
* @covers ::createDirectory
*/
public function testCreateDirectoryFailWarning() {
$directory = new vfsStreamDirectory('permissionDenied', 0200);
$storage = new FileStorage([
'directory' => $directory->url(),
'bin' => 'test',
]);
$code = "<?php\n echo 'here';";
$this->expectException(Warning::class);
$this->expectExceptionMessage('mkdir(): Permission Denied');
$storage->save('subdirectory/foo.php', $code);
}
}
@@ -0,0 +1,29 @@
<?php
namespace Drupal\Tests\Component\PhpStorage;
/**
* Tests the MTimeProtectedFastFileStorage implementation.
*
* @coversDefaultClass \Drupal\Component\PhpStorage\MTimeProtectedFastFileStorage
*
* @group Drupal
* @group PhpStorage
*/
class MTimeProtectedFastFileStorageTest extends MTimeProtectedFileStorageBase {
/**
* The expected test results for the security test.
*
* The first iteration does not change the directory mtime so this class will
* include the hacked file on the first try but the second test will change
* the directory mtime and so on the second try the file will not be included.
*/
protected $expected = [TRUE, FALSE];
/**
* The PHP storage class to test.
*/
protected $storageClass = 'Drupal\Component\PhpStorage\MTimeProtectedFastFileStorage';
}
@@ -0,0 +1,134 @@
<?php
namespace Drupal\Tests\Component\PhpStorage;
use Drupal\Component\FileSecurity\FileSecurity;
use Drupal\Component\Utility\Crypt;
use Drupal\Component\Utility\Random;
/**
* Base test class for MTime protected storage.
*/
abstract class MTimeProtectedFileStorageBase extends PhpStorageTestBase {
/**
* The PHP storage class to test.
*
* This should be overridden by extending classes.
*
* @var string
*/
protected $storageClass;
/**
* The secret string to use for file creation.
*
* @var string
*/
protected $secret;
/**
* Test settings to pass to storage instances.
*
* @var array
*/
protected $settings;
/**
* {@inheritdoc}
*/
protected function setUp() {
parent::setUp();
// Random generator.
$random = new Random();
$this->secret = $random->name(8, TRUE);
$this->settings = [
'directory' => $this->directory,
'bin' => 'test',
'secret' => $this->secret,
];
}
/**
* Tests basic load/save/delete operations.
*
* @covers ::load
* @covers ::save
* @covers ::delete
* @covers ::exists
*/
public function testCRUD() {
$php = new $this->storageClass($this->settings);
$this->assertCRUD($php);
}
/**
* Tests the security of the MTimeProtectedFileStorage implementation.
*
* We test two attacks: first changes the file mtime, then the directory
* mtime too.
*
* We need to delay over 1 second for mtime test.
* @medium
*/
public function testSecurity() {
$php = new $this->storageClass($this->settings);
$name = 'simpletest.php';
$php->save($name, '<?php');
$expected_root_directory = $this->directory . '/test';
if (substr($name, -4) === '.php') {
$expected_directory = $expected_root_directory . '/' . substr($name, 0, -4);
}
else {
$expected_directory = $expected_root_directory . '/' . $name;
}
$directory_mtime = filemtime($expected_directory);
$expected_filename = $expected_directory . '/' . Crypt::hmacBase64($name, $this->secret . $directory_mtime) . '.php';
// Ensure the file exists and that it and the containing directory have
// minimal permissions. fileperms() can return high bits unrelated to
// permissions, so mask with 0777.
$this->assertFileExists($expected_filename);
$this->assertSame(0444, fileperms($expected_filename) & 0777);
$this->assertSame(0777, fileperms($expected_directory) & 0777);
// Ensure the root directory for the bin has a .htaccess file denying web
// access.
$this->assertSame(file_get_contents($expected_root_directory . '/.htaccess'), FileSecurity::htaccessLines());
// Ensure that if the file is replaced with an untrusted one (due to another
// script's file upload vulnerability), it does not get loaded. Since mtime
// granularity is 1 second, we cannot prevent an attack that happens within
// a second of the initial save().
sleep(1);
for ($i = 0; $i < 2; $i++) {
$php = new $this->storageClass($this->settings);
$GLOBALS['hacked'] = FALSE;
$untrusted_code = "<?php\n" . '$GLOBALS["hacked"] = TRUE;';
chmod($expected_directory, 0700);
chmod($expected_filename, 0700);
if ($i) {
// Now try to write the file in such a way that the directory mtime
// changes and invalidates the hash.
file_put_contents($expected_filename . '.tmp', $untrusted_code);
rename($expected_filename . '.tmp', $expected_filename);
}
else {
// On the first try do not change the directory mtime but the filemtime
// is now larger than the directory mtime.
file_put_contents($expected_filename, $untrusted_code);
}
chmod($expected_filename, 0400);
chmod($expected_directory, 0100);
$this->assertSame(file_get_contents($expected_filename), $untrusted_code);
$this->assertSame($this->expected[$i], $php->exists($name));
$this->assertSame($this->expected[$i], $php->load($name));
$this->assertSame($this->expected[$i], $GLOBALS['hacked']);
}
unset($GLOBALS['hacked']);
}
}
@@ -0,0 +1,28 @@
<?php
namespace Drupal\Tests\Component\PhpStorage;
/**
* Tests the MTimeProtectedFileStorage implementation.
*
* @coversDefaultClass \Drupal\Component\PhpStorage\MTimeProtectedFileStorage
*
* @group Drupal
* @group PhpStorage
*/
class MTimeProtectedFileStorageTest extends MTimeProtectedFileStorageBase {
/**
* The expected test results for the security test.
*
* The default implementation protects against even the filemtime change so
* both iterations will return FALSE.
*/
protected $expected = [FALSE, FALSE];
/**
* The PHP storage class to test.
*/
protected $storageClass = 'Drupal\Component\PhpStorage\MTimeProtectedFileStorage';
}
@@ -0,0 +1,82 @@
<?php
namespace Drupal\Tests\Component\PhpStorage;
use Drupal\Component\PhpStorage\PhpStorageInterface;
use Drupal\Component\Utility\Random;
use org\bovigo\vfs\vfsStream;
use PHPUnit\Framework\TestCase;
/**
* Base test for PHP storages.
*/
abstract class PhpStorageTestBase extends TestCase {
/**
* A unique per test class directory path to test php storage.
*
* @var string
*/
protected $directory;
/**
* {@inheritdoc}
*/
protected function setUp() {
parent::setUp();
vfsStream::setup('exampleDir');
$this->directory = vfsStream::url('exampleDir');
}
/**
* Assert that a PHP storage's load/save/delete operations work.
*/
public function assertCRUD($php) {
// Random generator.
$random_generator = new Random();
$name = $random_generator->name(8, TRUE) . '/' . $random_generator->name(8, TRUE) . '.php';
// Find a global that doesn't exist.
do {
$random = mt_rand(10000, 100000);
} while (isset($GLOBALS[$random]));
// Write out a PHP file and ensure it's successfully loaded.
$code = "<?php\n\$GLOBALS[$random] = TRUE;";
$success = $php->save($name, $code);
$this->assertTrue($success, 'Saved php file');
$php->load($name);
$this->assertTrue($GLOBALS[$random], 'File saved correctly with correct value');
// Run additional asserts.
$this->additionalAssertCRUD($php, $name);
// If the file was successfully loaded, it must also exist, but ensure the
// exists() method returns that correctly.
$this->assertTrue($php->exists($name), 'Exists works correctly');
// Delete the file, and then ensure exists() returns FALSE.
$this->assertTrue($php->delete($name), 'Delete succeeded');
$this->assertFalse($php->exists($name), 'Delete deleted file');
// Ensure delete() can be called on a non-existing file. It should return
// FALSE, but not trigger errors.
$this->assertFalse($php->delete($name), 'Delete fails on missing file');
unset($GLOBALS[$random]);
}
/**
* Additional asserts to be run.
*
* @param \Drupal\Component\PhpStorage\PhpStorageInterface $php
* The PHP storage object.
* @param string $name
* The name of an object. It should exist in the storage.
*/
protected function additionalAssertCRUD(PhpStorageInterface $php, $name) {
// By default do not do any additional asserts. This is a way of extending
// tests in contrib.
}
}
@@ -0,0 +1,59 @@
<?php
namespace Drupal\Tests\Component\Plugin;
use Drupal\Component\Plugin\ConfigurablePluginInterface;
use Drupal\Component\Plugin\PluginBase;
use PHPUnit\Framework\TestCase;
/**
* Tests ConfigurablePluginInterface deprecation.
*
* @group legacy
* @group plugin
*/
class ConfigurablePluginInterfaceTest extends TestCase {
/**
* Tests the deprecation error is thrown.
*
* @expectedDeprecation Drupal\Component\Plugin\ConfigurablePluginInterface is deprecated in Drupal 8.7.0 and will be removed before Drupal 9.0.0. You should implement ConfigurableInterface and/or DependentPluginInterface directly as needed. If you implement ConfigurableInterface you may choose to implement ConfigurablePluginInterface in Drupal 8 as well for maximum compatibility, however this must be removed prior to Drupal 9. See https://www.drupal.org/node/2946161
*/
public function testDeprecation() {
new ConfigurablePluginInterfaceTestClass([], '', []);
}
}
/**
* Test Class to trigger deprecation error.
*/
class ConfigurablePluginInterfaceTestClass extends PluginBase implements ConfigurablePluginInterface {
/**
* {@inheritdoc}
*/
public function getConfiguration() {
return [];
}
/**
* {@inheritdoc}
*/
public function setConfiguration(array $configuration) {}
/**
* {@inheritdoc}
*/
public function defaultConfiguration() {
return [];
}
/**
* {@inheritdoc}
*/
public function calculateDependencies() {
return [];
}
}
@@ -0,0 +1,99 @@
<?php
namespace Drupal\Tests\Component\Plugin\Context;
use Drupal\Component\Plugin\Context\Context;
use PHPUnit\Framework\TestCase;
/**
* @coversDefaultClass \Drupal\Component\Plugin\Context\Context
* @group Plugin
*/
class ContextTest extends TestCase {
/**
* Data provider for testGetContextValue.
*/
public function providerGetContextValue() {
return [
['context_value', 'context_value', FALSE, 'data_type'],
[NULL, NULL, FALSE, 'data_type'],
['will throw exception', NULL, TRUE, 'data_type'],
];
}
/**
* @covers ::getContextValue
* @dataProvider providerGetContextValue
*/
public function testGetContextValue($expected, $context_value, $is_required, $data_type) {
// Mock a Context object.
$mock_context = $this->getMockBuilder('Drupal\Component\Plugin\Context\Context')
->disableOriginalConstructor()
->setMethods(['getContextDefinition'])
->getMock();
// If the context value exists, getContextValue() behaves like a normal
// getter.
if ($context_value) {
// Set visibility of contextValue.
$ref_context_value = new \ReflectionProperty($mock_context, 'contextValue');
$ref_context_value->setAccessible(TRUE);
// Set contextValue to a testable state.
$ref_context_value->setValue($mock_context, $context_value);
// Exercise getContextValue().
$this->assertEquals($context_value, $mock_context->getContextValue());
}
// If no context value exists, we have to cover either returning NULL or
// throwing an exception if the definition requires it.
else {
// Create a mock definition.
$mock_definition = $this->getMockBuilder('Drupal\Component\Plugin\Context\ContextDefinitionInterface')
->setMethods(['isRequired', 'getDataType'])
->getMockForAbstractClass();
// Set expectation for isRequired().
$mock_definition->expects($this->once())
->method('isRequired')
->willReturn($is_required);
// Set expectation for getDataType().
$mock_definition->expects($this->exactly(
$is_required ? 1 : 0
))
->method('getDataType')
->willReturn($data_type);
// Set expectation for getContextDefinition().
$mock_context->expects($this->once())
->method('getContextDefinition')
->willReturn($mock_definition);
// Set expectation for exception.
if ($is_required) {
$this->expectException('Drupal\Component\Plugin\Exception\ContextException');
$this->expectExceptionMessage(sprintf("The %s context is required and not present.", $data_type));
}
// Exercise getContextValue().
$this->assertEquals($context_value, $mock_context->getContextValue());
}
}
/**
* @covers ::getContextValue
*/
public function testDefaultValue() {
$mock_definition = $this->getMockBuilder('Drupal\Component\Plugin\Context\ContextDefinitionInterface')
->setMethods(['getDefaultValue'])
->getMockForAbstractClass();
$mock_definition->expects($this->once())
->method('getDefaultValue')
->willReturn('test');
$context = new Context($mock_definition);
$this->assertEquals('test', $context->getContextValue());
}
}
@@ -0,0 +1,151 @@
<?php
namespace Drupal\Tests\Component\Plugin;
use Drupal\Component\Plugin\Definition\PluginDefinitionInterface;
use Drupal\Component\Plugin\Exception\PluginException;
use Drupal\Component\Plugin\Factory\DefaultFactory;
use Drupal\Tests\Component\Plugin\Fixtures\vegetable\Broccoli;
use Drupal\Tests\Component\Plugin\Fixtures\vegetable\Corn;
use Drupal\Tests\Component\Plugin\Fixtures\vegetable\VegetableInterface;
use PHPUnit\Framework\TestCase;
/**
* @coversDefaultClass \Drupal\Component\Plugin\Factory\DefaultFactory
* @group Plugin
*/
class DefaultFactoryTest extends TestCase {
/**
* Tests getPluginClass() with a valid array plugin definition.
*
* @covers ::getPluginClass
*/
public function testGetPluginClassWithValidArrayPluginDefinition() {
$plugin_class = Corn::class;
$class = DefaultFactory::getPluginClass('corn', ['class' => $plugin_class]);
$this->assertEquals($plugin_class, $class);
}
/**
* Tests getPluginClass() with a valid object plugin definition.
*
* @covers ::getPluginClass
*/
public function testGetPluginClassWithValidObjectPluginDefinition() {
$plugin_class = Corn::class;
$plugin_definition = $this->getMockBuilder(PluginDefinitionInterface::class)->getMock();
$plugin_definition->expects($this->atLeastOnce())
->method('getClass')
->willReturn($plugin_class);
$class = DefaultFactory::getPluginClass('corn', $plugin_definition);
$this->assertEquals($plugin_class, $class);
}
/**
* Tests getPluginClass() with a missing class definition.
*
* @covers ::getPluginClass
*/
public function testGetPluginClassWithMissingClassWithArrayPluginDefinition() {
$this->expectException(PluginException::class);
$this->expectExceptionMessage('The plugin (corn) did not specify an instance class.');
DefaultFactory::getPluginClass('corn', []);
}
/**
* Tests getPluginClass() with a missing class definition.
*
* @covers ::getPluginClass
*/
public function testGetPluginClassWithMissingClassWithObjectPluginDefinition() {
$plugin_definition = $this->getMockBuilder(PluginDefinitionInterface::class)
->getMock();
$this->expectException(PluginException::class);
$this->expectExceptionMessage('The plugin (corn) did not specify an instance class.');
DefaultFactory::getPluginClass('corn', $plugin_definition);
}
/**
* Tests getPluginClass() with a not existing class definition.
*
* @covers ::getPluginClass
*/
public function testGetPluginClassWithNotExistingClassWithArrayPluginDefinition() {
$this->expectException(PluginException::class);
$this->expectExceptionMessage('Plugin (carrot) instance class "Drupal\Tests\Component\Plugin\Fixtures\vegetable\Carrot" does not exist.');
DefaultFactory::getPluginClass('carrot', ['class' => 'Drupal\Tests\Component\Plugin\Fixtures\vegetable\Carrot']);
}
/**
* Tests getPluginClass() with a not existing class definition.
*
* @covers ::getPluginClass
*/
public function testGetPluginClassWithNotExistingClassWithObjectPluginDefinition() {
$plugin_class = 'Drupal\Tests\Component\Plugin\Fixtures\vegetable\Carrot';
$plugin_definition = $this->getMockBuilder(PluginDefinitionInterface::class)->getMock();
$plugin_definition->expects($this->atLeastOnce())
->method('getClass')
->willReturn($plugin_class);
$this->expectException(PluginException::class);
DefaultFactory::getPluginClass('carrot', $plugin_definition);
}
/**
* Tests getPluginClass() with a required interface.
*
* @covers ::getPluginClass
*/
public function testGetPluginClassWithInterfaceWithArrayPluginDefinition() {
$plugin_class = Corn::class;
$class = DefaultFactory::getPluginClass('corn', ['class' => $plugin_class], VegetableInterface::class);
$this->assertEquals($plugin_class, $class);
}
/**
* Tests getPluginClass() with a required interface.
*
* @covers ::getPluginClass
*/
public function testGetPluginClassWithInterfaceWithObjectPluginDefinition() {
$plugin_class = Corn::class;
$plugin_definition = $this->getMockBuilder(PluginDefinitionInterface::class)->getMock();
$plugin_definition->expects($this->atLeastOnce())
->method('getClass')
->willReturn($plugin_class);
$class = DefaultFactory::getPluginClass('corn', $plugin_definition, VegetableInterface::class);
$this->assertEquals($plugin_class, $class);
}
/**
* Tests getPluginClass() with a required interface but no implementation.
*
* @covers ::getPluginClass
*/
public function testGetPluginClassWithInterfaceAndInvalidClassWithArrayPluginDefinition() {
$this->expectException(PluginException::class);
$this->expectExceptionMessage('Plugin "corn" (Drupal\Tests\Component\Plugin\Fixtures\vegetable\Broccoli) must implement interface Drupal\Tests\Component\Plugin\Fixtures\vegetable\VegetableInterface.');
DefaultFactory::getPluginClass('corn', ['class' => Broccoli::class], VegetableInterface::class);
}
/**
* Tests getPluginClass() with a required interface but no implementation.
*
* @covers ::getPluginClass
*/
public function testGetPluginClassWithInterfaceAndInvalidClassWithObjectPluginDefinition() {
$plugin_class = Broccoli::class;
$plugin_definition = $this->getMockBuilder(PluginDefinitionInterface::class)->getMock();
$plugin_definition->expects($this->atLeastOnce())
->method('getClass')
->willReturn($plugin_class);
$this->expectException(PluginException::class);
DefaultFactory::getPluginClass('corn', $plugin_definition, VegetableInterface::class);
}
}
@@ -0,0 +1,100 @@
<?php
namespace Drupal\Tests\Component\Plugin\Discovery;
use Drupal\Component\Annotation\Plugin\Discovery\AnnotatedClassDiscovery;
use Drupal\Component\FileCache\FileCacheFactory;
use org\bovigo\vfs\vfsStream;
use org\bovigo\vfs\vfsStreamDirectory;
use org\bovigo\vfs\vfsStreamWrapper;
use PHPUnit\Framework\TestCase;
/**
* @coversDefaultClass \Drupal\Component\Annotation\Plugin\Discovery\AnnotatedClassDiscovery
*
* @group Annotation
* @group Plugin
*/
class AnnotatedClassDiscoveryTest extends TestCase {
/**
* All the Drupal documentation standards tags.
*
* @var string[]
*/
public function provideBadAnnotations() {
return [
['addtogroup'],
['code'],
['defgroup'],
['deprecated'],
['endcode'],
['endlink'],
['file'],
['ingroup'],
['group'],
['link'],
['mainpage'],
['param'],
['ref'],
['return'],
['section'],
['see'],
['subsection'],
['throws'],
['todo'],
['var'],
['{'],
['}'],
];
}
/**
* Make sure AnnotatedClassDiscovery never tries to autoload bad annotations.
*
* @dataProvider provideBadAnnotations
*
* @coversNothing
*/
public function testAutoloadBadAnnotations($annotation) {
// Set up a class file in vfsStream.
vfsStreamWrapper::register();
$root = new vfsStreamDirectory('root');
vfsStreamWrapper::setRoot($root);
FileCacheFactory::setPrefix(__CLASS__);
// Make a directory for discovery.
$url = vfsStream::url('root');
mkdir($url . '/DrupalTest');
// Create a class docblock with our annotation.
$php_file = "<?php\nnamespace DrupalTest;\n/**\n";
$php_file .= " * @$annotation\n";
$php_file .= " */\nclass TestClass {}";
file_put_contents($url . '/DrupalTest/TestClass.php', $php_file);
// Create an AnnotatedClassDiscovery object referencing the virtual file.
$discovery = new AnnotatedClassDiscovery(
['\\DrupalTest\\TestClass' => [vfsStream::url('root/DrupalTest')]], '\\DrupalTest\\Component\\Annotation\\'
);
// Register our class loader which will fail if the annotation reader tries
// to autoload disallowed annotations.
$class_loader = function ($class_name) use ($annotation) {
$name_array = explode('\\', $class_name);
$name = array_pop($name_array);
if ($name == $annotation) {
$this->fail('Attempted to autoload a non-plugin annotation: ' . $name);
}
};
spl_autoload_register($class_loader, TRUE, TRUE);
// Now try to get plugin definitions.
$definitions = $discovery->getDefinitions();
// Unregister to clean up.
spl_autoload_unregister($class_loader);
// Assert that no annotations were loaded.
$this->assertEmpty($definitions);
}
}
@@ -0,0 +1,65 @@
<?php
namespace Drupal\Tests\Component\Plugin\Discovery;
use PHPUnit\Framework\TestCase;
/**
* @coversDefaultClass \Drupal\Component\Plugin\Discovery\DiscoveryCachedTrait
* @uses \Drupal\Component\Plugin\Discovery\DiscoveryTrait
* @group Plugin
*/
class DiscoveryCachedTraitTest extends TestCase {
/**
* Data provider for testGetDefinition().
*
* @return array
* - Expected result from getDefinition().
* - Cached definitions to be placed into self::$definitions
* - Definitions to be returned by getDefinitions().
* - Plugin name to query for.
*/
public function providerGetDefinition() {
return [
['definition', [], ['plugin_name' => 'definition'], 'plugin_name'],
['definition', ['plugin_name' => 'definition'], [], 'plugin_name'],
[NULL, ['plugin_name' => 'definition'], [], 'bad_plugin_name'],
];
}
/**
* @covers ::getDefinition
* @dataProvider providerGetDefinition
*/
public function testGetDefinition($expected, $cached_definitions, $get_definitions, $plugin_id) {
// Mock a DiscoveryCachedTrait.
$trait = $this->getMockForTrait('Drupal\Component\Plugin\Discovery\DiscoveryCachedTrait');
$reflection_definitions = new \ReflectionProperty($trait, 'definitions');
$reflection_definitions->setAccessible(TRUE);
// getDefinition() needs the ::$definitions property to be set in one of two
// ways: 1) As existing cached data, or 2) as a side-effect of calling
// getDefinitions().
// If there are no cached definitions, then we have to fake the side-effect
// of getDefinitions().
if (count($cached_definitions) < 1) {
$trait->expects($this->once())
->method('getDefinitions')
// Use a callback method, so we can perform the side-effects.
->willReturnCallback(function () use ($reflection_definitions, $trait, $get_definitions) {
$reflection_definitions->setValue($trait, $get_definitions);
return $get_definitions;
});
}
else {
// Put $cached_definitions into our mocked ::$definitions.
$reflection_definitions->setValue($trait, $cached_definitions);
}
// Call getDefinition(), with $exception_on_invalid always FALSE.
$this->assertSame(
$expected,
$trait->getDefinition($plugin_id, FALSE)
);
}
}
@@ -0,0 +1,150 @@
<?php
namespace Drupal\Tests\Component\Plugin\Discovery;
use Drupal\Component\Plugin\Exception\PluginNotFoundException;
use PHPUnit\Framework\TestCase;
/**
* @group Plugin
* @coversDefaultClass \Drupal\Component\Plugin\Discovery\DiscoveryTrait
*/
class DiscoveryTraitTest extends TestCase {
/**
* Data provider for testDoGetDefinition().
*
* @return array
* - Expected plugin definition.
* - Plugin definition array, to pass to doGetDefinition().
* - Plugin ID to get, passed to doGetDefinition().
*/
public function providerDoGetDefinition() {
return [
['definition', ['plugin_name' => 'definition'], 'plugin_name'],
[NULL, ['plugin_name' => 'definition'], 'bad_plugin_name'],
];
}
/**
* @covers ::doGetDefinition
* @dataProvider providerDoGetDefinition
*/
public function testDoGetDefinition($expected, $definitions, $plugin_id) {
// Mock the trait.
$trait = $this->getMockForTrait('Drupal\Component\Plugin\Discovery\DiscoveryTrait');
// Un-protect the method using reflection.
$method_ref = new \ReflectionMethod($trait, 'doGetDefinition');
$method_ref->setAccessible(TRUE);
// Call doGetDefinition, with $exception_on_invalid always FALSE.
$this->assertSame(
$expected,
$method_ref->invoke($trait, $definitions, $plugin_id, FALSE)
);
}
/**
* Data provider for testDoGetDefinitionException()
*
* @return array
* - Expected plugin definition.
* - Plugin definition array, to pass to doGetDefinition().
* - Plugin ID to get, passed to doGetDefinition().
*/
public function providerDoGetDefinitionException() {
return [
[FALSE, ['plugin_name' => 'definition'], 'bad_plugin_name'],
];
}
/**
* @covers ::doGetDefinition
* @dataProvider providerDoGetDefinitionException
* @uses \Drupal\Component\Plugin\Exception\PluginNotFoundException
*/
public function testDoGetDefinitionException($expected, $definitions, $plugin_id) {
// Mock the trait.
$trait = $this->getMockForTrait('Drupal\Component\Plugin\Discovery\DiscoveryTrait');
// Un-protect the method using reflection.
$method_ref = new \ReflectionMethod($trait, 'doGetDefinition');
$method_ref->setAccessible(TRUE);
// Call doGetDefinition, with $exception_on_invalid always TRUE.
$this->expectException(PluginNotFoundException::class);
$method_ref->invoke($trait, $definitions, $plugin_id, TRUE);
}
/**
* @covers ::getDefinition
* @dataProvider providerDoGetDefinition
*/
public function testGetDefinition($expected, $definitions, $plugin_id) {
// Since getDefinition is a wrapper around doGetDefinition(), we can re-use
// its data provider. We just have to tell abstract method getDefinitions()
// to use the $definitions array.
$trait = $this->getMockForTrait('Drupal\Component\Plugin\Discovery\DiscoveryTrait');
$trait->expects($this->once())
->method('getDefinitions')
->willReturn($definitions);
// Call getDefinition(), with $exception_on_invalid always FALSE.
$this->assertSame(
$expected,
$trait->getDefinition($plugin_id, FALSE)
);
}
/**
* @covers ::getDefinition
* @dataProvider providerDoGetDefinitionException
* @uses \Drupal\Component\Plugin\Exception\PluginNotFoundException
*/
public function testGetDefinitionException($expected, $definitions, $plugin_id) {
// Since getDefinition is a wrapper around doGetDefinition(), we can re-use
// its data provider. We just have to tell abstract method getDefinitions()
// to use the $definitions array.
$trait = $this->getMockForTrait('Drupal\Component\Plugin\Discovery\DiscoveryTrait');
$trait->expects($this->once())
->method('getDefinitions')
->willReturn($definitions);
// Call getDefinition(), with $exception_on_invalid always TRUE.
$this->expectException(PluginNotFoundException::class);
$trait->getDefinition($plugin_id, TRUE);
}
/**
* Data provider for testHasDefinition().
*
* @return array
* - Expected TRUE or FALSE.
* - Plugin ID to look for.
*/
public function providerHasDefinition() {
return [
[TRUE, 'valid'],
[FALSE, 'not_valid'],
];
}
/**
* @covers ::hasDefinition
* @dataProvider providerHasDefinition
*/
public function testHasDefinition($expected, $plugin_id) {
$trait = $this->getMockBuilder('Drupal\Component\Plugin\Discovery\DiscoveryTrait')
->setMethods(['getDefinition'])
->getMockForTrait();
// Set up our mocked getDefinition() to return TRUE for 'valid' and FALSE
// for 'not_valid'.
$trait->expects($this->once())
->method('getDefinition')
->will($this->returnValueMap([
['valid', FALSE, TRUE],
['not_valid', FALSE, FALSE],
]));
// Call hasDefinition().
$this->assertSame(
$expected,
$trait->hasDefinition($plugin_id)
);
}
}
@@ -0,0 +1,229 @@
<?php
namespace Drupal\Tests\Component\Plugin\Discovery;
use PHPUnit\Framework\TestCase;
/**
* @group Plugin
* @coversDefaultClass \Drupal\Component\Plugin\Discovery\StaticDiscoveryDecorator
*/
class StaticDiscoveryDecoratorTest extends TestCase {
/**
* Helper method to provide a mocked callback object with expectations.
*
* If there should be a registered definition, then we have to place a
* \Callable in the mock object. The return value of this callback is
* never used.
*
* @return \PHPUnit\Framework\MockObject\MockObject
* Mocked object with expectation of registerDefinitionsCallback() being
* called once.
*/
public function getRegisterDefinitionsCallback() {
$mock_callable = $this->getMockBuilder('\stdClass')
->setMethods(['registerDefinitionsCallback'])
->getMock();
// Set expectations for the callback method.
$mock_callable->expects($this->once())
->method('registerDefinitionsCallback');
return $mock_callable;
}
/**
* Data provider for testGetDefinitions().
*
* @return array
* - Expected plugin definition.
* - Whether we require the method to register definitions through a
* callback.
* - Whether to throw an exception if the definition is invalid.
* - A plugin definition.
* - Base plugin ID.
*/
public function providerGetDefinition() {
return [
['is_defined', TRUE, FALSE, ['plugin-definition' => 'is_defined'], 'plugin-definition'],
// Make sure we don't call the decorated method if we shouldn't.
['is_defined', FALSE, FALSE, ['plugin-definition' => 'is_defined'], 'plugin-definition'],
// Return NULL for bad plugin id.
[NULL, FALSE, FALSE, ['plugin-definition' => 'is_defined'], 'BAD-plugin-definition'],
// Generate an exception.
[NULL, FALSE, TRUE, ['plugin-definition' => 'is_defined'], 'BAD-plugin-definition'],
];
}
/**
* @covers ::getDefinition
* @dataProvider providerGetDefinition
*/
public function testGetDefinition($expected, $has_register_definitions, $exception_on_invalid, $definitions, $base_plugin_id) {
// Mock our StaticDiscoveryDecorator.
$mock_decorator = $this->getMockBuilder('Drupal\Component\Plugin\Discovery\StaticDiscoveryDecorator')
->disableOriginalConstructor()
->setMethods(['registeredDefintionCallback'])
->getMock();
// Set up the ::$registerDefinitions property.
$ref_register_definitions = new \ReflectionProperty($mock_decorator, 'registerDefinitions');
$ref_register_definitions->setAccessible(TRUE);
if ($has_register_definitions) {
// Set the callback object on the mocked decorator.
$ref_register_definitions->setValue(
$mock_decorator,
[$this->getRegisterDefinitionsCallback(), 'registerDefinitionsCallback']
);
}
else {
// There should be no registerDefinitions callback.
$ref_register_definitions->setValue($mock_decorator, NULL);
}
// Set up ::$definitions to an empty array.
$ref_definitions = new \ReflectionProperty($mock_decorator, 'definitions');
$ref_definitions->setAccessible(TRUE);
$ref_definitions->setValue($mock_decorator, []);
// Mock a decorated object.
$mock_decorated = $this->getMockBuilder('Drupal\Component\Plugin\Discovery\DiscoveryInterface')
->setMethods(['getDefinitions'])
->getMockForAbstractClass();
// Return our definitions from getDefinitions().
$mock_decorated->expects($this->once())
->method('getDefinitions')
->willReturn($definitions);
// Set up ::$decorated to our mocked decorated object.
$ref_decorated = new \ReflectionProperty($mock_decorator, 'decorated');
$ref_decorated->setAccessible(TRUE);
$ref_decorated->setValue($mock_decorator, $mock_decorated);
if ($exception_on_invalid) {
$this->expectException('Drupal\Component\Plugin\Exception\PluginNotFoundException');
}
// Exercise getDefinition(). It calls parent::getDefinition().
$this->assertEquals(
$expected,
$mock_decorator->getDefinition($base_plugin_id, $exception_on_invalid)
);
}
/**
* Data provider for testGetDefinitions().
*
* @return array
* - bool Whether the test mock has a callback.
* - array Plugin definitions.
*/
public function providerGetDefinitions() {
return [
[TRUE, ['definition' => 'is_fake']],
[FALSE, ['definition' => 'array_of_stuff']],
];
}
/**
* @covers ::getDefinitions
* @dataProvider providerGetDefinitions
*/
public function testGetDefinitions($has_register_definitions, $definitions) {
// Mock our StaticDiscoveryDecorator.
$mock_decorator = $this->getMockBuilder('Drupal\Component\Plugin\Discovery\StaticDiscoveryDecorator')
->disableOriginalConstructor()
->setMethods(['registeredDefintionCallback'])
->getMock();
// Set up the ::$registerDefinitions property.
$ref_register_definitions = new \ReflectionProperty($mock_decorator, 'registerDefinitions');
$ref_register_definitions->setAccessible(TRUE);
if ($has_register_definitions) {
// Set the callback object on the mocked decorator.
$ref_register_definitions->setValue(
$mock_decorator,
[$this->getRegisterDefinitionsCallback(), 'registerDefinitionsCallback']
);
}
else {
// There should be no registerDefinitions callback.
$ref_register_definitions->setValue($mock_decorator, NULL);
}
// Set up ::$definitions to an empty array.
$ref_definitions = new \ReflectionProperty($mock_decorator, 'definitions');
$ref_definitions->setAccessible(TRUE);
$ref_definitions->setValue($mock_decorator, []);
// Mock a decorated object.
$mock_decorated = $this->getMockBuilder('Drupal\Component\Plugin\Discovery\DiscoveryInterface')
->setMethods(['getDefinitions'])
->getMockForAbstractClass();
// Our mocked method will return any arguments sent to it.
$mock_decorated->expects($this->once())
->method('getDefinitions')
->willReturn($definitions);
// Set up ::$decorated to our mocked decorated object.
$ref_decorated = new \ReflectionProperty($mock_decorator, 'decorated');
$ref_decorated->setAccessible(TRUE);
$ref_decorated->setValue($mock_decorator, $mock_decorated);
// Exercise getDefinitions(). It calls parent::getDefinitions() but in this
// case there will be no side-effects.
$this->assertEquals(
$definitions,
$mock_decorator->getDefinitions()
);
}
/**
* Data provider for testCall().
*
* @return array
* - Method name.
* - Array of arguments to pass to the method, with the expectation that our
* mocked __call() will return them.
*/
public function providerCall() {
return [
['complexArguments', ['1', 2.0, 3, ['4' => 'five']]],
['noArguments', []],
];
}
/**
* @covers ::__call
* @dataProvider providerCall
*/
public function testCall($method, $args) {
// Mock a decorated object.
$mock_decorated = $this->getMockBuilder('Drupal\Component\Plugin\Discovery\DiscoveryInterface')
->setMethods([$method])
->getMockForAbstractClass();
// Our mocked method will return any arguments sent to it.
$mock_decorated->expects($this->once())
->method($method)
->willReturnCallback(
function () {
return \func_get_args();
}
);
// Create a mock decorator.
$mock_decorator = $this->getMockBuilder('Drupal\Component\Plugin\Discovery\StaticDiscoveryDecorator')
->disableOriginalConstructor()
->getMock();
// Poke the decorated object into our decorator.
$ref_decorated = new \ReflectionProperty($mock_decorator, 'decorated');
$ref_decorated->setAccessible(TRUE);
$ref_decorated->setValue($mock_decorator, $mock_decorated);
// Exercise __call.
$this->assertEquals(
$args,
\call_user_func_array([$mock_decorated, $method], $args)
);
}
}
@@ -0,0 +1,214 @@
<?php
/**
* @file
* Contains \Drupal\Tests\Component\Plugin\Factory\ReflectionFactoryTest.
*
* Also contains Argument* classes used as data for testing.
*/
namespace Drupal\Tests\Component\Plugin\Factory;
use Drupal\Component\Plugin\Factory\ReflectionFactory;
use PHPUnit\Framework\TestCase;
/**
* @group Plugin
* @coversDefaultClass \Drupal\Component\Plugin\Factory\ReflectionFactory
*/
class ReflectionFactoryTest extends TestCase {
/**
* Data provider for testGetInstanceArguments.
*
* The classes used here are defined at the bottom of this file.
*
* @return array
* - Expected output.
* - Class to reflect for input to getInstanceArguments().
* - $plugin_id parameter to getInstanceArguments().
* - $plugin_definition parameter to getInstanceArguments().
* - $configuration parameter to getInstanceArguments().
*/
public function providerGetInstanceArguments() {
return [
[
['arguments_plugin_id'],
'Drupal\Tests\Component\Plugin\Factory\ArgumentsPluginId',
'arguments_plugin_id',
['arguments_plugin_id' => ['class' => 'Drupal\Tests\Component\Plugin\Factory\ArgumentsPluginId']],
[],
],
[
[[], ['arguments_many' => ['class' => 'Drupal\Tests\Component\Plugin\Factory\ArgumentsMany']], 'arguments_many', 'default_value', 'what_default'],
'Drupal\Tests\Component\Plugin\Factory\ArgumentsMany',
'arguments_many',
['arguments_many' => ['class' => 'Drupal\Tests\Component\Plugin\Factory\ArgumentsMany']],
[],
],
[
// Config array key exists and is set.
['thing'],
'Drupal\Tests\Component\Plugin\Factory\ArgumentsConfigArrayKey',
'arguments_config_array_key',
['arguments_config_array_key' => ['class' => 'Drupal\Tests\Component\Plugin\Factory\ArgumentsConfigArrayKey']],
['config_name' => 'thing'],
],
[
// Config array key exists and is not set.
[NULL],
'Drupal\Tests\Component\Plugin\Factory\ArgumentsConfigArrayKey',
'arguments_config_array_key',
['arguments_config_array_key' => ['class' => 'Drupal\Tests\Component\Plugin\Factory\ArgumentsConfigArrayKey']],
['config_name' => NULL],
],
[
// Touch the else clause at the end of the method.
[NULL, NULL, NULL, NULL],
'Drupal\Tests\Component\Plugin\Factory\ArgumentsAllNull',
'arguments_all_null',
['arguments_all_null' => ['class' => 'Drupal\Tests\Component\Plugin\Factory\ArgumentsAllNull']],
[],
],
[
// A plugin with no constructor.
[NULL, NULL, NULL, NULL],
'Drupal\Tests\Component\Plugin\Factory\ArgumentsNoConstructor',
'arguments_no_constructor',
['arguments_no_constructor' => ['class' => 'Drupal\Tests\Component\Plugin\Factory\ArgumentsNoConstructor']],
[],
],
];
}
/**
* @covers ::createInstance
* @dataProvider providerGetInstanceArguments
*/
public function testCreateInstance($expected, $reflector_name, $plugin_id, $plugin_definition, $configuration) {
// Create a mock DiscoveryInterface which can return our plugin definition.
$mock_discovery = $this->getMockBuilder('Drupal\Component\Plugin\Discovery\DiscoveryInterface')
->setMethods(['getDefinition', 'getDefinitions', 'hasDefinition'])
->getMock();
$mock_discovery->expects($this->never())->method('getDefinitions');
$mock_discovery->expects($this->never())->method('hasDefinition');
$mock_discovery->expects($this->once())
->method('getDefinition')
->willReturn($plugin_definition);
// Create a stub ReflectionFactory object. We use StubReflectionFactory
// because createInstance() has a dependency on a static method.
// StubReflectionFactory overrides this static method.
$reflection_factory = new StubReflectionFactory($mock_discovery);
// Finally test that createInstance() returns an object of the class we
// want.
$this->assertInstanceOf($reflector_name, $reflection_factory->createInstance($plugin_id));
}
/**
* @covers ::getInstanceArguments
* @dataProvider providerGetInstanceArguments
*/
public function testGetInstanceArguments($expected, $reflector_name, $plugin_id, $plugin_definition, $configuration) {
$reflection_factory = $this->getMockBuilder('Drupal\Component\Plugin\Factory\ReflectionFactory')
->disableOriginalConstructor()
->getMock();
$get_instance_arguments_ref = new \ReflectionMethod($reflection_factory, 'getInstanceArguments');
$get_instance_arguments_ref->setAccessible(TRUE);
// Special case for plugin class without a constructor.
// getInstanceArguments() throws an exception if there's no constructor.
// This is not a documented behavior of getInstanceArguments(), but allows
// us to use one data set for this test method as well as
// testCreateInstance().
if ($plugin_id == 'arguments_no_constructor') {
$this->expectException('\ReflectionException');
}
// Finally invoke getInstanceArguments() on our mocked factory.
$ref = new \ReflectionClass($reflector_name);
$result = $get_instance_arguments_ref->invoke(
$reflection_factory, $ref, $plugin_id, $plugin_definition, $configuration);
$this->assertEquals($expected, $result);
}
}
/**
* Override ReflectionFactory because ::createInstance() calls a static method.
*
* We have to override getPluginClass so that we can stub out its return value.
*/
class StubReflectionFactory extends ReflectionFactory {
/**
* {@inheritdoc}
*/
public static function getPluginClass($plugin_id, $plugin_definition = NULL, $required_interface = NULL) {
// Return the class name from the plugin definition.
return $plugin_definition[$plugin_id]['class'];
}
}
/**
* A stub class used by testGetInstanceArguments().
*
* @see providerGetInstanceArguments()
*/
class ArgumentsPluginId {
public function __construct($plugin_id) {
// No-op.
}
}
/**
* A stub class used by testGetInstanceArguments().
*
* @see providerGetInstanceArguments()
*/
class ArgumentsMany {
public function __construct($configuration, $plugin_definition, $plugin_id, $foo = 'default_value', $what_am_i_doing_here = 'what_default') {
// No-op.
}
}
/**
* A stub class used by testGetInstanceArguments().
*
* @see providerGetInstanceArguments()
*/
class ArgumentsConfigArrayKey {
public function __construct($config_name) {
// No-op.
}
}
/**
* A stub class used by testGetInstanceArguments().
*
* @see providerGetInstanceArguments()
*/
class ArgumentsAllNull {
public function __construct($charismatic, $demure, $delightful, $electrostatic) {
// No-op.
}
}
/**
* A stub class used by testGetInstanceArguments().
*
* @see providerGetInstanceArguments()
*/
class ArgumentsNoConstructor {
}
@@ -0,0 +1,12 @@
<?php
namespace Drupal\Tests\Component\Plugin\Fixtures\vegetable;
/**
* @Plugin(
* id = "broccoli",
* label = "Broccoli",
* color = "green"
* )
*/
class Broccoli {}
@@ -0,0 +1,12 @@
<?php
namespace Drupal\Tests\Component\Plugin\Fixtures\vegetable;
/**
* @Plugin(
* id = "corn",
* label = "Corn",
* color = "yellow"
* )
*/
class Corn implements VegetableInterface {}
@@ -0,0 +1,8 @@
<?php
namespace Drupal\Tests\Component\Plugin\Fixtures\vegetable;
/**
* Provides an interface for test plugins.
*/
interface VegetableInterface {}
@@ -0,0 +1,106 @@
<?php
namespace Drupal\Tests\Component\Plugin;
use PHPUnit\Framework\TestCase;
/**
* @coversDefaultClass \Drupal\Component\Plugin\PluginBase
* @group Plugin
*/
class PluginBaseTest extends TestCase {
/**
* @dataProvider providerTestGetPluginId
* @covers ::getPluginId
*/
public function testGetPluginId($plugin_id, $expected) {
$plugin_base = $this->getMockForAbstractClass('Drupal\Component\Plugin\PluginBase', [
[],
$plugin_id,
[],
]);
$this->assertEquals($expected, $plugin_base->getPluginId());
}
/**
* Returns test data for testGetPluginId().
*
* @return array
*/
public function providerTestGetPluginId() {
return [
['base_id', 'base_id'],
['base_id:derivative', 'base_id:derivative'],
];
}
/**
* @dataProvider providerTestGetBaseId
* @coves ::getBaseId
*/
public function testGetBaseId($plugin_id, $expected) {
/** @var \Drupal\Component\Plugin\PluginBase|\PHPUnit\Framework\MockObject\MockObject $plugin_base */
$plugin_base = $this->getMockForAbstractClass('Drupal\Component\Plugin\PluginBase', [
[],
$plugin_id,
[],
]);
$this->assertEquals($expected, $plugin_base->getBaseId());
}
/**
* Returns test data for testGetBaseId().
*
* @return array
*/
public function providerTestGetBaseId() {
return [
['base_id', 'base_id'],
['base_id:derivative', 'base_id'],
];
}
/**
* @dataProvider providerTestGetDerivativeId
* @covers ::getDerivativeId
*/
public function testGetDerivativeId($plugin_id = NULL, $expected = NULL) {
/** @var \Drupal\Component\Plugin\PluginBase|\PHPUnit\Framework\MockObject\MockObject $plugin_base */
$plugin_base = $this->getMockForAbstractClass('Drupal\Component\Plugin\PluginBase', [
[],
$plugin_id,
[],
]);
$this->assertEquals($expected, $plugin_base->getDerivativeId());
}
/**
* Returns test data for testGetDerivativeId().
*
* @return array
*/
public function providerTestGetDerivativeId() {
return [
['base_id', NULL],
['base_id:derivative', 'derivative'],
];
}
/**
* @covers ::getPluginDefinition
*/
public function testGetPluginDefinition() {
$plugin_base = $this->getMockForAbstractClass('Drupal\Component\Plugin\PluginBase', [
[],
'plugin_id',
['value', ['key' => 'value']],
]);
$this->assertEquals(['value', ['key' => 'value']], $plugin_base->getPluginDefinition());
}
}
@@ -0,0 +1,129 @@
<?php
namespace Drupal\Tests\Component\Plugin;
use Drupal\Component\Plugin\Exception\PluginNotFoundException;
use Drupal\Component\Plugin\Mapper\MapperInterface;
use Drupal\Component\Plugin\PluginManagerBase;
use PHPUnit\Framework\TestCase;
/**
* @coversDefaultClass \Drupal\Component\Plugin\PluginManagerBase
* @group Plugin
*/
class PluginManagerBaseTest extends TestCase {
/**
* A callback method for mocking FactoryInterface objects.
*/
public function createInstanceCallback() {
$args = func_get_args();
$plugin_id = $args[0];
$configuration = $args[1];
if ('invalid' == $plugin_id) {
throw new PluginNotFoundException($plugin_id);
}
return [
'plugin_id' => $plugin_id,
'configuration' => $configuration,
];
}
/**
* Generates a mocked FactoryInterface object with known properties.
*/
public function getMockFactoryInterface($expects_count) {
$mock_factory = $this->getMockBuilder('Drupal\Component\Plugin\Factory\FactoryInterface')
->setMethods(['createInstance'])
->getMockForAbstractClass();
$mock_factory->expects($this->exactly($expects_count))
->method('createInstance')
->willReturnCallback([$this, 'createInstanceCallback']);
return $mock_factory;
}
/**
* Tests createInstance() with no fallback methods.
*
* @covers ::createInstance
*/
public function testCreateInstance() {
$manager = $this->getMockBuilder('Drupal\Component\Plugin\PluginManagerBase')
->getMockForAbstractClass();
// PluginManagerBase::createInstance() looks for a factory object and then
// calls createInstance() on it. So we have to mock a factory object.
$factory_ref = new \ReflectionProperty($manager, 'factory');
$factory_ref->setAccessible(TRUE);
$factory_ref->setValue($manager, $this->getMockFactoryInterface(1));
// Finally the test.
$configuration_array = ['config' => 'something'];
$result = $manager->createInstance('valid', $configuration_array);
$this->assertEquals('valid', $result['plugin_id']);
$this->assertEquals($configuration_array, $result['configuration']);
}
/**
* Tests createInstance() with a fallback method.
*
* @covers ::createInstance
*/
public function testCreateInstanceFallback() {
// We use our special stub class which extends PluginManagerBase and also
// implements FallbackPluginManagerInterface.
$manager = new StubFallbackPluginManager();
// Put our stubbed factory on the base object.
$factory_ref = new \ReflectionProperty($manager, 'factory');
$factory_ref->setAccessible(TRUE);
// Set up the configuration array.
$configuration_array = ['config' => 'something'];
// Test with fallback interface and valid plugin_id.
$factory_ref->setValue($manager, $this->getMockFactoryInterface(1));
$no_fallback_result = $manager->createInstance('valid', $configuration_array);
$this->assertEquals('valid', $no_fallback_result['plugin_id']);
$this->assertEquals($configuration_array, $no_fallback_result['configuration']);
// Test with fallback interface and invalid plugin_id.
$factory_ref->setValue($manager, $this->getMockFactoryInterface(2));
$fallback_result = $manager->createInstance('invalid', $configuration_array);
$this->assertEquals('invalid_fallback', $fallback_result['plugin_id']);
$this->assertEquals($configuration_array, $fallback_result['configuration']);
}
/**
* @covers ::getInstance
*/
public function testGetInstance() {
$options = [
'foo' => 'F00',
'bar' => 'bAr',
];
$instance = new \stdClass();
$mapper = $this->prophesize(MapperInterface::class);
$mapper->getInstance($options)
->shouldBeCalledTimes(1)
->willReturn($instance);
$manager = new StubPluginManagerBaseWithMapper($mapper->reveal());
$this->assertEquals($instance, $manager->getInstance($options));
}
/**
* @covers ::getInstance
*/
public function testGetInstanceWithoutMapperShouldThrowException() {
$options = [
'foo' => 'F00',
'bar' => 'bAr',
];
/** @var \Drupal\Component\Plugin\PluginManagerBase $manager */
$manager = $this->getMockBuilder(PluginManagerBase::class)
->getMockForAbstractClass();
// Set the expected exception thrown by ::getInstance.
$this->expectException(\BadMethodCallException::class);
$this->expectExceptionMessage(sprintf('%s does not support this method unless %s::$mapper is set.', get_class($manager), get_class($manager)));
$manager->getInstance($options);
}
}
@@ -0,0 +1,28 @@
<?php
namespace Drupal\Tests\Component\Plugin;
use Drupal\Component\Plugin\FallbackPluginManagerInterface;
use Drupal\Component\Plugin\PluginManagerBase;
/**
* Stubs \Drupal\Component\Plugin\FallbackPluginManagerInterface.
*
* We have to stub \Drupal\Component\Plugin\FallbackPluginManagerInterface for
* \Drupal\Tests\Component\Plugin\PluginManagerBaseTest so that we can
* implement ::getFallbackPluginId().
*
* We do this so we can have it just return the plugin ID passed to it, with
* '_fallback' appended.
*/
class StubFallbackPluginManager extends PluginManagerBase implements FallbackPluginManagerInterface {
/**
* {@inheritdoc}
*/
public function getFallbackPluginId($plugin_id, array $configuration = []) {
// Minimally implement getFallbackPluginId so that we can test it.
return $plugin_id . '_fallback';
}
}
@@ -0,0 +1,22 @@
<?php
namespace Drupal\Tests\Component\Plugin;
use Drupal\Component\Plugin\Mapper\MapperInterface;
use Drupal\Component\Plugin\PluginManagerBase;
/**
* Stubs \Drupal\Component\Plugin\PluginManagerBase to take a MapperInterface.
*/
final class StubPluginManagerBaseWithMapper extends PluginManagerBase {
/**
* Constructs a new instance.
*
* @param \Drupal\Component\Plugin\Mapper\MapperInterface $mapper
*/
public function __construct(MapperInterface $mapper) {
$this->mapper = $mapper;
}
}
@@ -0,0 +1,445 @@
<?php
/**
* @file
* Contains \Drupal\Tests\Component\ProxyBuilder\ProxyBuilderTest.
*/
namespace Drupal\Tests\Component\ProxyBuilder;
use Drupal\Component\ProxyBuilder\ProxyBuilder;
use PHPUnit\Framework\TestCase;
/**
* @coversDefaultClass \Drupal\Component\ProxyBuilder\ProxyBuilder
* @group proxy_builder
*/
class ProxyBuilderTest extends TestCase {
/**
* The tested proxy builder.
*
* @var \Drupal\Component\ProxyBuilder\ProxyBuilder
*/
protected $proxyBuilder;
/**
* {@inheritdoc}
*/
protected function setUp() {
parent::setUp();
$this->proxyBuilder = new ProxyBuilder();
}
/**
* @covers ::buildProxyClassName
*/
public function testBuildProxyClassName() {
$class_name = $this->proxyBuilder->buildProxyClassName('Drupal\Tests\Component\ProxyBuilder\TestServiceNoMethod');
$this->assertEquals('Drupal\Tests\ProxyClass\Component\ProxyBuilder\TestServiceNoMethod', $class_name);
}
/**
* @covers ::buildProxyClassName
*/
public function testBuildProxyClassNameForModule() {
$class_name = $this->proxyBuilder->buildProxyClassName('Drupal\views_ui\ParamConverter\ViewUIConverter');
$this->assertEquals('Drupal\views_ui\ProxyClass\ParamConverter\ViewUIConverter', $class_name);
}
/**
* @covers ::buildProxyNamespace
*/
public function testBuildProxyNamespace() {
$class_name = $this->proxyBuilder->buildProxyNamespace('Drupal\Tests\Component\ProxyBuilder\TestServiceNoMethod');
$this->assertEquals('Drupal\Tests\ProxyClass\Component\ProxyBuilder', $class_name);
}
/**
* Tests the basic methods like the constructor and the lazyLoadItself method.
*
* @covers ::build
* @covers ::buildConstructorMethod
* @covers ::buildLazyLoadItselfMethod
*/
public function testBuildNoMethod() {
$class = 'Drupal\Tests\Component\ProxyBuilder\TestServiceNoMethod';
$result = $this->proxyBuilder->build($class);
$this->assertEquals($this->buildExpectedClass($class, ''), $result);
}
/**
* @covers ::buildMethod
* @covers ::buildMethodBody
*/
public function testBuildSimpleMethod() {
$class = 'Drupal\Tests\Component\ProxyBuilder\TestServiceSimpleMethod';
$result = $this->proxyBuilder->build($class);
$method_body = <<<'EOS'
/**
* {@inheritdoc}
*/
public function method()
{
return $this->lazyLoadItself()->method();
}
EOS;
$this->assertEquals($this->buildExpectedClass($class, $method_body), $result);
}
/**
* @covers ::buildMethod
* @covers ::buildParameter
* @covers ::buildMethodBody
*/
public function testBuildMethodWithParameter() {
$class = 'Drupal\Tests\Component\ProxyBuilder\TestServiceMethodWithParameter';
$result = $this->proxyBuilder->build($class);
$method_body = <<<'EOS'
/**
* {@inheritdoc}
*/
public function methodWithParameter($parameter)
{
return $this->lazyLoadItself()->methodWithParameter($parameter);
}
EOS;
$this->assertEquals($this->buildExpectedClass($class, $method_body), $result);
}
/**
* @covers ::buildMethod
* @covers ::buildParameter
* @covers ::buildMethodBody
*/
public function testBuildComplexMethod() {
$class = 'Drupal\Tests\Component\ProxyBuilder\TestServiceComplexMethod';
$result = $this->proxyBuilder->build($class);
// @todo Solve the silly linebreak for array()
$method_body = <<<'EOS'
/**
* {@inheritdoc}
*/
public function complexMethod($parameter, callable $function, \Drupal\Tests\Component\ProxyBuilder\TestServiceNoMethod $test_service = NULL, array &$elements = array (
))
{
return $this->lazyLoadItself()->complexMethod($parameter, $function, $test_service, $elements);
}
EOS;
$this->assertEquals($this->buildExpectedClass($class, $method_body), $result);
}
/**
* @covers ::buildMethod
* @covers ::buildMethodBody
*/
public function testBuildReturnReference() {
$class = 'Drupal\Tests\Component\ProxyBuilder\TestServiceReturnReference';
$result = $this->proxyBuilder->build($class);
// @todo Solve the silly linebreak for array()
$method_body = <<<'EOS'
/**
* {@inheritdoc}
*/
public function &returnReference()
{
return $this->lazyLoadItself()->returnReference();
}
EOS;
$this->assertEquals($this->buildExpectedClass($class, $method_body), $result);
}
/**
* @covers ::buildMethod
* @covers ::buildParameter
* @covers ::buildMethodBody
*/
public function testBuildWithInterface() {
$class = 'Drupal\Tests\Component\ProxyBuilder\TestServiceWithInterface';
$result = $this->proxyBuilder->build($class);
$method_body = <<<'EOS'
/**
* {@inheritdoc}
*/
public function testMethod($parameter)
{
return $this->lazyLoadItself()->testMethod($parameter);
}
EOS;
$interface_string = ' implements \Drupal\Tests\Component\ProxyBuilder\TestInterface';
$this->assertEquals($this->buildExpectedClass($class, $method_body, $interface_string), $result);
}
/**
* @covers ::build
*/
public function testBuildWithNestedInterface() {
$class = 'Drupal\Tests\Component\ProxyBuilder\TestServiceWithChildInterfaces';
$result = $this->proxyBuilder->build($class);
$method_body = '';
$interface_string = ' implements \Drupal\Tests\Component\ProxyBuilder\TestChildInterface';
$this->assertEquals($this->buildExpectedClass($class, $method_body, $interface_string), $result);
}
/**
* @covers ::buildMethod
* @covers ::buildParameter
* @covers ::buildMethodBody
*/
public function testBuildWithProtectedAndPrivateMethod() {
$class = 'Drupal\Tests\Component\ProxyBuilder\TestServiceWithProtectedMethods';
$result = $this->proxyBuilder->build($class);
$method_body = <<<'EOS'
/**
* {@inheritdoc}
*/
public function testMethod($parameter)
{
return $this->lazyLoadItself()->testMethod($parameter);
}
EOS;
$this->assertEquals($this->buildExpectedClass($class, $method_body), $result);
}
/**
* @covers ::buildMethod
* @covers ::buildParameter
* @covers ::buildMethodBody
*/
public function testBuildWithPublicStaticMethod() {
$class = 'Drupal\Tests\Component\ProxyBuilder\TestServiceWithPublicStaticMethod';
$result = $this->proxyBuilder->build($class);
// Ensure that the static method is not wrapped.
$method_body = <<<'EOS'
/**
* {@inheritdoc}
*/
public static function testMethod($parameter)
{
\Drupal\Tests\Component\ProxyBuilder\TestServiceWithPublicStaticMethod::testMethod($parameter);
}
EOS;
$this->assertEquals($this->buildExpectedClass($class, $method_body), $result);
}
/**
* Constructs the expected class output.
*
* @param string $expected_methods_body
* The expected body of decorated methods.
*
* @return string
* The code of the entire proxy.
*/
protected function buildExpectedClass($class, $expected_methods_body, $interface_string = '') {
$namespace = ProxyBuilder::buildProxyNamespace($class);
$reflection = new \ReflectionClass($class);
$proxy_class = $reflection->getShortName();
$expected_string = <<<'EOS'
namespace {{ namespace }} {
/**
* Provides a proxy class for \{{ class }}.
*
* @see \Drupal\Component\ProxyBuilder
*/
class {{ proxy_class }}{{ interface_string }}
{
/**
* The id of the original proxied service.
*
* @var string
*/
protected $drupalProxyOriginalServiceId;
/**
* The real proxied service, after it was lazy loaded.
*
* @var \{{ class }}
*/
protected $service;
/**
* The service container.
*
* @var \Symfony\Component\DependencyInjection\ContainerInterface
*/
protected $container;
/**
* Constructs a ProxyClass Drupal proxy object.
*
* @param \Symfony\Component\DependencyInjection\ContainerInterface $container
* The container.
* @param string $drupal_proxy_original_service_id
* The service ID of the original service.
*/
public function __construct(\Symfony\Component\DependencyInjection\ContainerInterface $container, $drupal_proxy_original_service_id)
{
$this->container = $container;
$this->drupalProxyOriginalServiceId = $drupal_proxy_original_service_id;
}
/**
* Lazy loads the real service from the container.
*
* @return object
* Returns the constructed real service.
*/
protected function lazyLoadItself()
{
if (!isset($this->service)) {
$this->service = $this->container->get($this->drupalProxyOriginalServiceId);
}
return $this->service;
}
{{ expected_methods_body }}
}
}
EOS;
$expected_methods_body = implode("\n", array_map(function ($value) {
if ($value === '') {
return $value;
}
return " $value";
}, explode("\n", $expected_methods_body)));
$expected_string = str_replace('{{ proxy_class }}', $proxy_class, $expected_string);
$expected_string = str_replace('{{ namespace }}', $namespace, $expected_string);
$expected_string = str_replace('{{ class }}', $class, $expected_string);
$expected_string = str_replace('{{ expected_methods_body }}', $expected_methods_body, $expected_string);
$expected_string = str_replace('{{ interface_string }}', $interface_string, $expected_string);
return $expected_string;
}
}
class TestServiceNoMethod {
}
class TestServiceSimpleMethod {
public function method() {
}
}
class TestServiceMethodWithParameter {
public function methodWithParameter($parameter) {
}
}
class TestServiceComplexMethod {
public function complexMethod($parameter, callable $function, TestServiceNoMethod $test_service = NULL, array &$elements = []) {
}
}
class TestServiceReturnReference {
public function &returnReference() {
}
}
interface TestInterface {
public function testMethod($parameter);
}
class TestServiceWithInterface implements TestInterface {
public function testMethod($parameter) {
}
}
class TestServiceWithProtectedMethods {
public function testMethod($parameter) {
}
protected function protectedMethod($parameter) {
}
protected function privateMethod($parameter) {
}
}
class TestServiceWithPublicStaticMethod {
public static function testMethod($parameter) {
}
}
interface TestBaseInterface {
}
interface TestChildInterface extends TestBaseInterface {
}
class TestServiceWithChildInterfaces implements TestChildInterface {
}
@@ -0,0 +1,102 @@
<?php
namespace Drupal\Tests\Component\Render;
use Drupal\Component\Render\FormattableMarkup;
use PHPUnit\Framework\TestCase;
/**
* Tests the TranslatableMarkup class.
*
* @coversDefaultClass \Drupal\Component\Render\FormattableMarkup
* @group utility
*/
class FormattableMarkupTest extends TestCase {
/**
* The error message of the last error in the error handler.
*
* @var string
*/
protected $lastErrorMessage;
/**
* The error number of the last error in the error handler.
*
* @var int
*/
protected $lastErrorNumber;
/**
* @covers ::__toString
* @covers ::jsonSerialize
*/
public function testToString() {
$string = 'Can I please have a @replacement';
$formattable_string = new FormattableMarkup($string, ['@replacement' => 'kitten']);
$text = (string) $formattable_string;
$this->assertEquals('Can I please have a kitten', $text);
$text = $formattable_string->jsonSerialize();
$this->assertEquals('Can I please have a kitten', $text);
}
/**
* @covers ::count
*/
public function testCount() {
$string = 'Can I please have a @replacement';
$formattable_string = new FormattableMarkup($string, ['@replacement' => 'kitten']);
$this->assertEquals(strlen($string), $formattable_string->count());
}
/**
* Custom error handler that saves the last error.
*
* We need this custom error handler because we cannot rely on the error to
* exception conversion as __toString is never allowed to leak any kind of
* exception.
*
* @param int $error_number
* The error number.
* @param string $error_message
* The error message.
*/
public function errorHandler($error_number, $error_message) {
$this->lastErrorNumber = $error_number;
$this->lastErrorMessage = $error_message;
}
/**
* @covers ::__toString
* @dataProvider providerTestUnexpectedPlaceholder
*/
public function testUnexpectedPlaceholder($string, $arguments, $error_number, $error_message) {
// We set a custom error handler because of https://github.com/sebastianbergmann/phpunit/issues/487
set_error_handler([$this, 'errorHandler']);
// We want this to trigger an error.
$markup = new FormattableMarkup($string, $arguments);
// Cast it to a string which will generate the errors.
$output = (string) $markup;
restore_error_handler();
// The string should not change.
$this->assertEquals($string, $output);
$this->assertEquals($error_number, $this->lastErrorNumber);
$this->assertEquals($error_message, $this->lastErrorMessage);
}
/**
* Data provider for FormattableMarkupTest::testUnexpectedPlaceholder().
*
* @return array
*/
public function providerTestUnexpectedPlaceholder() {
return [
['Non alpha starting character: ~placeholder', ['~placeholder' => 'replaced'], E_USER_ERROR, 'Invalid placeholder (~placeholder) in string: Non alpha starting character: ~placeholder'],
['Alpha starting character: placeholder', ['placeholder' => 'replaced'], E_USER_DEPRECATED, 'Invalid placeholder (placeholder) in string: Alpha starting character: placeholder'],
// Ensure that where the placeholder is located in the string is
// irrelevant.
['placeholder', ['placeholder' => 'replaced'], E_USER_DEPRECATED, 'Invalid placeholder (placeholder) in string: placeholder'],
];
}
}
@@ -0,0 +1,64 @@
<?php
namespace Drupal\Tests\Component\Render;
use Drupal\Component\Render\HtmlEscapedText;
use Drupal\Component\Render\MarkupInterface;
use PHPUnit\Framework\TestCase;
/**
* Tests the HtmlEscapedText class.
*
* @coversDefaultClass \Drupal\Component\Render\HtmlEscapedText
* @group utility
*/
class HtmlEscapedTextTest extends TestCase {
/**
* @covers ::__toString
* @covers ::jsonSerialize
*
* @dataProvider providerToString
*/
public function testToString($text, $expected, $message) {
$escapeable_string = new HtmlEscapedText($text);
$this->assertEquals($expected, (string) $escapeable_string, $message);
$this->assertEquals($expected, $escapeable_string->jsonSerialize());
}
/**
* Data provider for testToString().
*
* @see testToString()
*/
public function providerToString() {
// Checks that invalid multi-byte sequences are escaped.
$tests[] = ["Foo\xC0barbaz", 'Foobarbaz', 'Escapes invalid sequence "Foo\xC0barbaz"'];
$tests[] = ["\xc2\"", '&quot;', 'Escapes invalid sequence "\xc2\""'];
$tests[] = ["Fooÿñ", "Fooÿñ", 'Does not escape valid sequence "Fooÿñ"'];
// Checks that special characters are escaped.
$script_tag = $this->prophesize(MarkupInterface::class);
$script_tag->__toString()->willReturn('<script>');
$script_tag = $script_tag->reveal();
$tests[] = [$script_tag, '&lt;script&gt;', 'Escapes &lt;script&gt; even inside an object that implements MarkupInterface.'];
$tests[] = ["<script>", '&lt;script&gt;', 'Escapes &lt;script&gt;'];
$tests[] = ['<>&"\'', '&lt;&gt;&amp;&quot;&#039;', 'Escapes reserved HTML characters.'];
$specialchars = $this->prophesize(MarkupInterface::class);
$specialchars->__toString()->willReturn('<>&"\'');
$specialchars = $specialchars->reveal();
$tests[] = [$specialchars, '&lt;&gt;&amp;&quot;&#039;', 'Escapes reserved HTML characters even inside an object that implements MarkupInterface.'];
return $tests;
}
/**
* @covers ::count
*/
public function testCount() {
$string = 'Can I please have a <em>kitten</em>';
$escapeable_string = new HtmlEscapedText($string);
$this->assertEquals(strlen($string), $escapeable_string->count());
}
}

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