Initial Drupal 11 with DDEV setup
This commit is contained in:
@ -0,0 +1,68 @@
|
||||
<?php
|
||||
|
||||
declare(strict_types=1);
|
||||
|
||||
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(): void {
|
||||
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');
|
||||
}
|
||||
|
||||
/**
|
||||
* Tests that getDefinitions() retrieves the file cache correctly.
|
||||
*
|
||||
* @covers ::getDefinitions
|
||||
*/
|
||||
public function testGetDefinitions(): void {
|
||||
// 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');
|
||||
/** @var \Drupal\Component\FileCache\FileCacheInterface $file_cache */
|
||||
$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
|
||||
|
||||
declare(strict_types=1);
|
||||
|
||||
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(): void {
|
||||
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(): void {
|
||||
$discovery = new AnnotatedClassDiscovery(['com/example' => [__DIR__]]);
|
||||
|
||||
$reflection = new \ReflectionMethod($discovery, 'getPluginNamespaces');
|
||||
$result = $reflection->invoke($discovery);
|
||||
$this->assertEquals(['com/example' => [__DIR__]], $result);
|
||||
}
|
||||
|
||||
/**
|
||||
* @covers ::getDefinitions
|
||||
* @covers ::prepareAnnotationDefinition
|
||||
* @covers ::getAnnotationReader
|
||||
*/
|
||||
public function testGetDefinitions(): void {
|
||||
$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,57 @@
|
||||
<?php
|
||||
|
||||
declare(strict_types=1);
|
||||
|
||||
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(): void {
|
||||
$plugin = new AnnotationBaseStub();
|
||||
$plugin->setProvider('example');
|
||||
$this->assertEquals('example', $plugin->getProvider());
|
||||
}
|
||||
|
||||
/**
|
||||
* @covers ::getId
|
||||
*/
|
||||
public function testGetId(): void {
|
||||
$plugin = new AnnotationBaseStub();
|
||||
// Doctrine sets the public prop directly.
|
||||
$plugin->id = 'example';
|
||||
$this->assertEquals('example', $plugin->getId());
|
||||
}
|
||||
|
||||
/**
|
||||
* @covers ::getClass
|
||||
* @covers ::setClass
|
||||
*/
|
||||
public function testSetClass(): void {
|
||||
$plugin = new AnnotationBaseStub();
|
||||
$plugin->setClass('example');
|
||||
$this->assertEquals('example', $plugin->getClass());
|
||||
}
|
||||
|
||||
}
|
||||
/**
|
||||
* {@inheritdoc}
|
||||
*/
|
||||
class AnnotationBaseStub extends AnnotationBase {
|
||||
|
||||
/**
|
||||
* {@inheritdoc}
|
||||
*/
|
||||
public function get() {}
|
||||
|
||||
}
|
||||
@ -0,0 +1,45 @@
|
||||
<?php
|
||||
|
||||
declare(strict_types=1);
|
||||
|
||||
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(): void {
|
||||
$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,12 @@
|
||||
<?php
|
||||
|
||||
declare(strict_types=1);
|
||||
|
||||
namespace Drupal\Tests\Component\Annotation\Doctrine\Fixtures\Annotation;
|
||||
|
||||
/** @Annotation */
|
||||
class AnnotWithDefaultValue
|
||||
{
|
||||
/** @var string */
|
||||
public $foo = 'bar';
|
||||
}
|
||||
@ -0,0 +1,12 @@
|
||||
<?php
|
||||
|
||||
declare(strict_types=1);
|
||||
|
||||
namespace Drupal\Tests\Component\Annotation\Doctrine\Fixtures\Annotation;
|
||||
|
||||
/**
|
||||
* @Annotation
|
||||
*/
|
||||
class Autoload
|
||||
{
|
||||
}
|
||||
@ -0,0 +1,13 @@
|
||||
<?php
|
||||
|
||||
declare(strict_types=1);
|
||||
|
||||
namespace Drupal\Tests\Component\Annotation\Doctrine\Fixtures\Annotation;
|
||||
|
||||
/** @Annotation */
|
||||
class Route
|
||||
{
|
||||
/** @var string @Required */
|
||||
public $pattern;
|
||||
public $name;
|
||||
}
|
||||
@ -0,0 +1,20 @@
|
||||
<?php
|
||||
|
||||
declare(strict_types=1);
|
||||
|
||||
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,16 @@
|
||||
<?php
|
||||
|
||||
declare(strict_types=1);
|
||||
|
||||
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,13 @@
|
||||
<?php
|
||||
|
||||
declare(strict_types=1);
|
||||
|
||||
namespace Drupal\Tests\Component\Annotation\Doctrine\Fixtures\Annotation;
|
||||
|
||||
/**
|
||||
* @Annotation
|
||||
* @Target("PROPERTY")
|
||||
*/
|
||||
final class Version
|
||||
{
|
||||
}
|
||||
@ -0,0 +1,23 @@
|
||||
<?php
|
||||
|
||||
declare(strict_types=1);
|
||||
|
||||
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,19 @@
|
||||
<?php
|
||||
|
||||
declare(strict_types=1);
|
||||
|
||||
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,36 @@
|
||||
<?php
|
||||
|
||||
declare(strict_types=1);
|
||||
|
||||
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,33 @@
|
||||
<?php
|
||||
|
||||
declare(strict_types=1);
|
||||
|
||||
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,16 @@
|
||||
<?php
|
||||
|
||||
declare(strict_types=1);
|
||||
|
||||
namespace Drupal\Tests\Component\Annotation\Doctrine\Fixtures;
|
||||
|
||||
/**
|
||||
* @Annotation
|
||||
* @Target("ALL")
|
||||
*/
|
||||
class AnnotationTargetAll
|
||||
{
|
||||
public $data;
|
||||
public $name;
|
||||
public $target;
|
||||
}
|
||||
@ -0,0 +1,16 @@
|
||||
<?php
|
||||
|
||||
declare(strict_types=1);
|
||||
|
||||
namespace Drupal\Tests\Component\Annotation\Doctrine\Fixtures;
|
||||
|
||||
/**
|
||||
* @Annotation
|
||||
* @Target({ "ANNOTATION" })
|
||||
*/
|
||||
final class AnnotationTargetAnnotation
|
||||
{
|
||||
public $data;
|
||||
public $name;
|
||||
public $target;
|
||||
}
|
||||
@ -0,0 +1,17 @@
|
||||
<?php
|
||||
|
||||
declare(strict_types=1);
|
||||
|
||||
namespace Drupal\Tests\Component\Annotation\Doctrine\Fixtures;
|
||||
|
||||
|
||||
/**
|
||||
* @Annotation
|
||||
* @Target("CLASS")
|
||||
*/
|
||||
final class AnnotationTargetClass
|
||||
{
|
||||
public $data;
|
||||
public $name;
|
||||
public $target;
|
||||
}
|
||||
@ -0,0 +1,16 @@
|
||||
<?php
|
||||
|
||||
declare(strict_types=1);
|
||||
|
||||
namespace Drupal\Tests\Component\Annotation\Doctrine\Fixtures;
|
||||
|
||||
/**
|
||||
* @Annotation
|
||||
* @Target({ "METHOD", "PROPERTY" })
|
||||
*/
|
||||
final class AnnotationTargetPropertyMethod
|
||||
{
|
||||
public $data;
|
||||
public $name;
|
||||
public $target;
|
||||
}
|
||||
@ -0,0 +1,131 @@
|
||||
<?php
|
||||
|
||||
declare(strict_types=1);
|
||||
|
||||
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,22 @@
|
||||
<?php
|
||||
|
||||
declare(strict_types=1);
|
||||
|
||||
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,52 @@
|
||||
<?php
|
||||
|
||||
declare(strict_types=1);
|
||||
|
||||
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,26 @@
|
||||
<?php
|
||||
|
||||
declare(strict_types=1);
|
||||
|
||||
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,13 @@
|
||||
<?php
|
||||
|
||||
declare(strict_types=1);
|
||||
|
||||
namespace Drupal\Tests\Component\Annotation\Doctrine\Fixtures;
|
||||
|
||||
/**
|
||||
* @Annotation
|
||||
* @Target(@)
|
||||
*/
|
||||
final class AnnotationWithTargetSyntaxError
|
||||
{
|
||||
}
|
||||
@ -0,0 +1,69 @@
|
||||
<?php
|
||||
|
||||
declare(strict_types=1);
|
||||
|
||||
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,8 @@
|
||||
<?php
|
||||
|
||||
namespace Drupal\Tests\Component\Annotation\Doctrine\Fixtures\Attribute;
|
||||
|
||||
#[\Attribute]
|
||||
final class AttributeClass
|
||||
{
|
||||
}
|
||||
@ -0,0 +1,8 @@
|
||||
<?php
|
||||
|
||||
namespace Drupal\Tests\Component\Annotation\Doctrine\Fixtures\Attribute;
|
||||
|
||||
#[/* Comment */\Drupal\Tests\Component\Annotation\Doctrine\Fixtures\ExtraAttributes\ExampleAttribute]
|
||||
final class FullyQualified
|
||||
{
|
||||
}
|
||||
@ -0,0 +1,9 @@
|
||||
<?php
|
||||
|
||||
namespace Drupal\Tests\Component\Annotation\Doctrine\Fixtures\Attribute;
|
||||
|
||||
#[\Attribute /* Comment */]
|
||||
#[/* Comment */AttributeClass]
|
||||
final class MultipleAttributes
|
||||
{
|
||||
}
|
||||
@ -0,0 +1,9 @@
|
||||
<?php
|
||||
|
||||
namespace Drupal\Tests\Component\Annotation\Doctrine\Fixtures\Attribute;
|
||||
|
||||
// @phpstan-ignore attribute.notFound
|
||||
#[NonexistentAttribute]
|
||||
final class Nonexistent
|
||||
{
|
||||
}
|
||||
@ -0,0 +1,9 @@
|
||||
<?php
|
||||
|
||||
namespace Drupal\Tests\Component\Annotation\Doctrine\Fixtures\Attribute;
|
||||
|
||||
use Drupal\Tests\Component\Annotation\Doctrine\Fixtures\ExtraAttributes;
|
||||
#[/* Comment */ExtraAttributes\ExampleAttribute]
|
||||
final class Qualified
|
||||
{
|
||||
}
|
||||
@ -0,0 +1,8 @@
|
||||
<?php
|
||||
|
||||
namespace Drupal\Tests\Component\Annotation\Doctrine\Fixtures\Attribute;
|
||||
|
||||
#[/* Comment */SubDir\SubDirAttribute]
|
||||
final class Relative
|
||||
{
|
||||
}
|
||||
@ -0,0 +1,8 @@
|
||||
<?php
|
||||
|
||||
namespace Drupal\Tests\Component\Annotation\Doctrine\Fixtures\Attribute\SubDir;
|
||||
|
||||
#[\Attribute]
|
||||
final class SubDirAttribute
|
||||
{
|
||||
}
|
||||
@ -0,0 +1,10 @@
|
||||
<?php
|
||||
|
||||
namespace Drupal\Tests\Component\Annotation\Doctrine\Fixtures\Attribute;
|
||||
|
||||
use Drupal\Tests\Component\Annotation\Doctrine\Fixtures\ExtraAttributes\ExampleAttribute;
|
||||
|
||||
#[/* Comment */ExampleAttribute]
|
||||
final class Used
|
||||
{
|
||||
}
|
||||
@ -0,0 +1,10 @@
|
||||
<?php
|
||||
|
||||
namespace Drupal\Tests\Component\Annotation\Doctrine\Fixtures\Attribute;
|
||||
|
||||
use Drupal\Tests\Component\Annotation\Doctrine\Fixtures\ExtraAttributes\ExampleAttribute as ClassAttribute;
|
||||
|
||||
#[/* Comment */ClassAttribute]
|
||||
final class UsedAs
|
||||
{
|
||||
}
|
||||
@ -0,0 +1,10 @@
|
||||
<?php
|
||||
|
||||
namespace Drupal\Tests\Component\Annotation\Doctrine\Fixtures\Attribute;
|
||||
|
||||
use Drupal\Tests\Component\Annotation\Doctrine\Fixtures\ExtraAttributes as ClassAttributes;
|
||||
|
||||
#[/* Comment */ClassAttributes\ExampleAttribute]
|
||||
final class UsedAsQualified
|
||||
{
|
||||
}
|
||||
@ -0,0 +1,13 @@
|
||||
<?php
|
||||
|
||||
declare(strict_types=1);
|
||||
|
||||
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,19 @@
|
||||
<?php
|
||||
|
||||
declare(strict_types=1);
|
||||
|
||||
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,22 @@
|
||||
<?php
|
||||
|
||||
declare(strict_types=1);
|
||||
|
||||
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,26 @@
|
||||
<?php
|
||||
|
||||
declare(strict_types=1);
|
||||
|
||||
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,42 @@
|
||||
<?php
|
||||
|
||||
declare(strict_types=1);
|
||||
|
||||
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,8 @@
|
||||
<?php
|
||||
|
||||
namespace Drupal\Tests\Component\Annotation\Doctrine\Fixtures\ExtraAttributes;
|
||||
|
||||
#[\Attribute]
|
||||
final class ExampleAttribute extends ExampleParentAttribute
|
||||
{
|
||||
}
|
||||
@ -0,0 +1,8 @@
|
||||
<?php
|
||||
|
||||
namespace Drupal\Tests\Component\Annotation\Doctrine\Fixtures\ExtraAttributes;
|
||||
|
||||
#[\Attribute]
|
||||
class ExampleParentAttribute {
|
||||
|
||||
}
|
||||
@ -0,0 +1,12 @@
|
||||
<?php
|
||||
|
||||
declare(strict_types=1);
|
||||
|
||||
namespace Drupal\Tests\Component\Annotation\Doctrine\Fixtures;
|
||||
|
||||
interface IntefaceWithConstants
|
||||
{
|
||||
|
||||
const SOME_VALUE = 'IntefaceWithConstants.SOME_VALUE';
|
||||
const SOME_KEY = 'IntefaceWithConstants.SOME_KEY';
|
||||
}
|
||||
@ -0,0 +1,43 @@
|
||||
<?php
|
||||
|
||||
namespace Drupal\Tests\Component\Annotation\Doctrine;
|
||||
|
||||
use Drupal\Component\Annotation\Doctrine\StaticReflectionParser;
|
||||
use Drupal\Component\Annotation\Reflection\MockFileFinder;
|
||||
use PHPUnit\Framework\TestCase;
|
||||
|
||||
/**
|
||||
* @coversDefaultClass \Drupal\Component\Annotation\Doctrine\StaticReflectionParser
|
||||
*
|
||||
* @group Annotation
|
||||
*/
|
||||
class StaticReflectionParserTest extends TestCase {
|
||||
|
||||
/**
|
||||
* @testWith ["AttributeClass", "\\Attribute", true]
|
||||
* ["AttributeClass", "attribute", true]
|
||||
* ["AttributeClass", "Attribute", true]
|
||||
* ["AttributeClass", "\\DoesNotExist", false]
|
||||
* ["Nonexistent", "NonexistentAttribute", false]
|
||||
* ["MultipleAttributes", "Attribute", true]
|
||||
* ["MultipleAttributes", "Drupal\\Tests\\Component\\Annotation\\Doctrine\\Fixtures\\Attribute\\AttributeClass", true]
|
||||
* ["MultipleAttributes", "DoesNotExist", false]
|
||||
* ["FullyQualified", "Drupal\\Tests\\Component\\Annotation\\Doctrine\\Fixtures\\ExtraAttributes\\ExampleAttribute", true]
|
||||
* ["Used", "Drupal\\Tests\\Component\\Annotation\\Doctrine\\Fixtures\\ExtraAttributes\\ExampleAttribute", true]
|
||||
* ["UsedAs", "Drupal\\Tests\\Component\\Annotation\\Doctrine\\Fixtures\\ExtraAttributes\\ExampleAttribute", true]
|
||||
* ["UsedAsQualified", "Drupal\\Tests\\Component\\Annotation\\Doctrine\\Fixtures\\ExtraAttributes\\ExampleAttribute", true]
|
||||
* ["Qualified", "Drupal\\Tests\\Component\\Annotation\\Doctrine\\Fixtures\\ExtraAttributes\\ExampleAttribute", true]
|
||||
* ["Relative", "Drupal\\Tests\\Component\\Annotation\\Doctrine\\Fixtures\\Attribute\\SubDir\\SubDirAttribute", true]
|
||||
* ["FullyQualified", "Drupal\\Tests\\Component\\Annotation\\Doctrine\\Fixtures\\ExtraAttributes\\ExampleParentAttribute", true]
|
||||
* ["Used", "Drupal\\Tests\\Component\\Annotation\\Doctrine\\Fixtures\\ExtraAttributes\\ExampleParentAttribute", true]
|
||||
* ["UsedAs", "Drupal\\Tests\\Component\\Annotation\\Doctrine\\Fixtures\\ExtraAttributes\\ExampleParentAttribute", true]
|
||||
* ["UsedAsQualified", "Drupal\\Tests\\Component\\Annotation\\Doctrine\\Fixtures\\ExtraAttributes\\ExampleParentAttribute", true]
|
||||
* ["Qualified", "Drupal\\Tests\\Component\\Annotation\\Doctrine\\Fixtures\\ExtraAttributes\\ExampleParentAttribute", true]
|
||||
*/
|
||||
public function testAttribute(string $class, string $attribute_class, bool $expected): void {
|
||||
$finder = MockFileFinder::create(__DIR__ . '/Fixtures/Attribute/' . $class . '.php');
|
||||
$parser = new StaticReflectionParser('\\Drupal\\Tests\\Component\\Annotation\\Doctrine\\Fixtures\\Attribute\\' . $class, $finder);
|
||||
$this->assertSame($expected, $parser->hasClassAttribute($attribute_class), "'$class' has attribute that is a '$attribute_class'");
|
||||
}
|
||||
|
||||
}
|
||||
@ -0,0 +1,16 @@
|
||||
<?php
|
||||
|
||||
declare(strict_types=1);
|
||||
|
||||
// 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,119 @@
|
||||
<?php
|
||||
|
||||
declare(strict_types=1);
|
||||
|
||||
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.
|
||||
*
|
||||
* Run this test in a separate process as it includes code that might have side
|
||||
* effects.
|
||||
*
|
||||
* @group DCOM58
|
||||
* @runTestsInSeparateProcesses
|
||||
*/
|
||||
class DCOM58Test extends TestCase
|
||||
{
|
||||
protected function setUp(): void
|
||||
{
|
||||
// Some class named Entity in the global namespace.
|
||||
include __DIR__ .'/DCOM58Entity.php';
|
||||
}
|
||||
|
||||
public function testIssueGlobalNamespace(): void
|
||||
{
|
||||
$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(): void
|
||||
{
|
||||
$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(): void
|
||||
{
|
||||
$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(): void
|
||||
{
|
||||
$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(): void
|
||||
{
|
||||
$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,18 @@
|
||||
<?php
|
||||
|
||||
declare(strict_types=1);
|
||||
|
||||
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,26 @@
|
||||
<?php
|
||||
|
||||
declare(strict_types=1);
|
||||
|
||||
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(): void {
|
||||
$tmp = MockFileFinder::create('test_filename.txt');
|
||||
$this->assertEquals('test_filename.txt', $tmp->findFile('n/a'));
|
||||
$this->assertEquals('test_filename.txt', $tmp->findFile('SomeClass'));
|
||||
}
|
||||
|
||||
}
|
||||
@ -0,0 +1,74 @@
|
||||
<?php
|
||||
|
||||
declare(strict_types=1);
|
||||
|
||||
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;
|
||||
use Prophecy\PhpUnit\ProphecyTrait;
|
||||
|
||||
/**
|
||||
* @coversDefaultClass \Drupal\Component\Annotation\Plugin\Discovery\AnnotationBridgeDecorator
|
||||
* @group Plugin
|
||||
*/
|
||||
class AnnotationBridgeDecoratorTest extends TestCase {
|
||||
|
||||
use ProphecyTrait;
|
||||
|
||||
/**
|
||||
* @covers ::getDefinitions
|
||||
*/
|
||||
public function testGetDefinitions(): void {
|
||||
$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,48 @@
|
||||
<?php
|
||||
|
||||
declare(strict_types=1);
|
||||
|
||||
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(): void {
|
||||
// Assert plugin starts empty.
|
||||
$plugin = new PluginID();
|
||||
$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(): void {
|
||||
$plugin = new PluginID();
|
||||
$plugin->value = 'example';
|
||||
$this->assertEquals('example', $plugin->getId());
|
||||
}
|
||||
|
||||
}
|
||||
108
web/core/tests/Drupal/Tests/Component/Annotation/PluginTest.php
Normal file
108
web/core/tests/Drupal/Tests/Component/Annotation/PluginTest.php
Normal file
@ -0,0 +1,108 @@
|
||||
<?php
|
||||
|
||||
declare(strict_types=1);
|
||||
|
||||
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(): void {
|
||||
// Assert all values are accepted through constructor and default value is
|
||||
// used for non existent but defined property.
|
||||
$plugin = new PluginStub([
|
||||
1 => 'oak',
|
||||
'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' => 'test_value',
|
||||
1 => 'oak',
|
||||
'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(): void {
|
||||
$plugin = new Plugin(['provider' => 'example']);
|
||||
$this->assertEquals('example', $plugin->getProvider());
|
||||
}
|
||||
|
||||
/**
|
||||
* @covers ::setProvider
|
||||
*/
|
||||
public function testSetProvider(): void {
|
||||
$plugin = new Plugin([]);
|
||||
$plugin->setProvider('example');
|
||||
$this->assertEquals('example', $plugin->getProvider());
|
||||
}
|
||||
|
||||
/**
|
||||
* @covers ::getId
|
||||
*/
|
||||
public function testGetId(): void {
|
||||
$plugin = new Plugin(['id' => 'example']);
|
||||
$this->assertEquals('example', $plugin->getId());
|
||||
}
|
||||
|
||||
/**
|
||||
* @covers ::getClass
|
||||
*/
|
||||
public function testGetClass(): void {
|
||||
$plugin = new Plugin(['class' => 'example']);
|
||||
$this->assertEquals('example', $plugin->getClass());
|
||||
}
|
||||
|
||||
/**
|
||||
* @covers ::setClass
|
||||
*/
|
||||
public function testSetClass(): void {
|
||||
$plugin = new Plugin([]);
|
||||
$plugin->setClass('example');
|
||||
$this->assertEquals('example', $plugin->getClass());
|
||||
}
|
||||
|
||||
}
|
||||
/**
|
||||
* {@inheritdoc}
|
||||
*/
|
||||
class PluginStub extends Plugin {
|
||||
|
||||
/**
|
||||
* A default property for testing.
|
||||
*
|
||||
* @var string
|
||||
*/
|
||||
protected $defaultProperty = 'test_value';
|
||||
|
||||
}
|
||||
@ -0,0 +1,255 @@
|
||||
<?php
|
||||
|
||||
declare(strict_types=1);
|
||||
|
||||
namespace Drupal\Tests\Component\Assertion;
|
||||
|
||||
use PHPUnit\Framework\TestCase;
|
||||
use Drupal\Component\Assertion\Inspector;
|
||||
use Drupal\TestTools\Extension\DeprecationBridge\ExpectDeprecationTrait;
|
||||
|
||||
/**
|
||||
* @coversDefaultClass \Drupal\Component\Assertion\Inspector
|
||||
* @group Assertion
|
||||
*/
|
||||
class InspectorTest extends TestCase {
|
||||
|
||||
use ExpectDeprecationTrait;
|
||||
|
||||
/**
|
||||
* Tests asserting all members are strings.
|
||||
*
|
||||
* @covers ::assertAllStrings
|
||||
* @dataProvider providerTestAssertAllStrings
|
||||
*/
|
||||
public function testAssertAllStrings($input, $expected): void {
|
||||
$this->assertSame($expected, Inspector::assertAllStrings($input));
|
||||
}
|
||||
|
||||
public static 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(): void {
|
||||
$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(): void {
|
||||
$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(): void {
|
||||
$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(): void {
|
||||
$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(): void {
|
||||
$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(): void {
|
||||
$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(): void {
|
||||
$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(): void {
|
||||
$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(): void {
|
||||
$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(): void {
|
||||
$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(): void {
|
||||
$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(): void {
|
||||
$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(): void {
|
||||
$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'));
|
||||
}
|
||||
|
||||
/**
|
||||
* Defines a test method referenced by ::testAllCallable().
|
||||
*/
|
||||
public function callMe() {
|
||||
return TRUE;
|
||||
}
|
||||
|
||||
/**
|
||||
* Defines a 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,42 @@
|
||||
<?php
|
||||
|
||||
declare(strict_types=1);
|
||||
|
||||
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(): void {
|
||||
$finder = new ClassFinder();
|
||||
|
||||
// The full path is returned therefore only tests with
|
||||
// assertStringEndsWith() so the test is portable.
|
||||
$expected_path = str_replace('/', DIRECTORY_SEPARATOR, 'core/tests/Drupal/Tests/Component/ClassFinder/ClassFinderTest.php');
|
||||
$this->assertStringEndsWith($expected_path, $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($expected_path, $finder->findFile(ClassFinderTest::class));
|
||||
|
||||
// Clean up the additional autoloader after the test.
|
||||
$loader->unregister();
|
||||
}
|
||||
|
||||
}
|
||||
@ -0,0 +1,944 @@
|
||||
<?php
|
||||
|
||||
declare(strict_types=1);
|
||||
|
||||
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 {
|
||||
|
||||
/**
|
||||
* Tests 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): void {
|
||||
$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));
|
||||
}
|
||||
|
||||
/**
|
||||
* Tests 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): void {
|
||||
$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));
|
||||
}
|
||||
|
||||
/**
|
||||
* Tests 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): void {
|
||||
$interval = $input1->diff($input2, $absolute);
|
||||
$this->assertEquals($interval, $expected);
|
||||
}
|
||||
|
||||
/**
|
||||
* Tests 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): void {
|
||||
$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');
|
||||
$input1->diff($input2, $absolute);
|
||||
}
|
||||
|
||||
/**
|
||||
* Tests 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): void {
|
||||
$this->expectException($class);
|
||||
$this->assertInstanceOf(
|
||||
'\Drupal\Component\DateTimePlus',
|
||||
DateTimePlus::createFromArray($input, $timezone)
|
||||
);
|
||||
}
|
||||
|
||||
/**
|
||||
* Tests DateTimePlus::checkArray().
|
||||
*
|
||||
* @param array $array
|
||||
* Input argument for DateTimePlus::checkArray().
|
||||
* @param bool $expected
|
||||
* The expected result of DateTimePlus::checkArray().
|
||||
*
|
||||
* @dataProvider providerTestCheckArray
|
||||
*/
|
||||
public function testCheckArray(array $array, $expected): void {
|
||||
$this->assertSame(
|
||||
$expected,
|
||||
DateTimePlus::checkArray($array)
|
||||
);
|
||||
}
|
||||
|
||||
/**
|
||||
* Tests 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): void {
|
||||
// Initialize a new date object.
|
||||
$date = DateTimePlus::createFromTimestamp($input, $initial['timezone']);
|
||||
$this->assertDateTimestamp($date, $input, $initial, $transform);
|
||||
}
|
||||
|
||||
/**
|
||||
* Tests 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): void {
|
||||
// Initialize a new date object.
|
||||
$date = new DateTimePlus($input, $initial['timezone']);
|
||||
$this->assertDateTimestamp($date, $input, $initial, $transform);
|
||||
}
|
||||
|
||||
/**
|
||||
* Asserts a DateTimePlus value.
|
||||
*
|
||||
* @param \Drupal\Component\Datetime\DateTimePlus $date
|
||||
* DateTimePlus to test.
|
||||
* @param string|int $input
|
||||
* The original input passed to the test method.
|
||||
* @param array $initial
|
||||
* @see testTimestamp()
|
||||
* @param array $transform
|
||||
* @see testTimestamp()
|
||||
*
|
||||
* @internal
|
||||
*/
|
||||
public function assertDateTimestamp(DateTimePlus $date, string|int $input, array $initial, array $transform): void {
|
||||
// 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));
|
||||
}
|
||||
|
||||
/**
|
||||
* Tests creating dates from format strings.
|
||||
*
|
||||
* @param string $input
|
||||
* Input argument for DateTimePlus.
|
||||
* @param string $timezone
|
||||
* Timezone argument for DateTimePlus.
|
||||
* @param string $format
|
||||
* PHP date() type format for parsing the input.
|
||||
* @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): void {
|
||||
$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));
|
||||
}
|
||||
|
||||
/**
|
||||
* Tests 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): void {
|
||||
$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): void {
|
||||
$date = new DateTimePlus($input, $timezone);
|
||||
$timezone = $date->getTimezone()->getName();
|
||||
$this->assertEquals($timezone, $expected_timezone, $message);
|
||||
}
|
||||
|
||||
/**
|
||||
* Tests that DrupalDateTime can detect the correct timezone to use.
|
||||
*
|
||||
* But only when the DrupalDateTime is constructed from a datetime object.
|
||||
*/
|
||||
public function testDateTimezoneWithDateTimeObject(): void {
|
||||
// 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 static 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 static 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 static 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 static 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 static 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],
|
||||
];
|
||||
}
|
||||
|
||||
/**
|
||||
* Data provider for testCheckArray.
|
||||
*
|
||||
* @return array
|
||||
* An array of arrays, each containing:
|
||||
* - 'array' - Input for DateTimePlus::checkArray().
|
||||
* - 'expected' - Expected output for DateTimePlus::checkArray().
|
||||
*
|
||||
* @see testCheckArray
|
||||
*/
|
||||
public static function providerTestCheckArray() {
|
||||
return [
|
||||
'Date array, date only' => [['year' => 2010, 'month' => 2, 'day' => 28], TRUE],
|
||||
'Date array with hour' => [['year' => 2010, 'month' => 2, 'day' => 28, 'hour' => 10], TRUE],
|
||||
'One year larger than the documented upper limit of checkdate()' => [['year' => 32768, 'month' => 1, 'day' => 8, 'hour' => 8, 'minute' => 0, 'second' => 0], FALSE],
|
||||
'One year smaller than the documented lower limit of checkdate()' => [['year' => 0, 'month' => 1, 'day' => 8, 'hour' => 8, 'minute' => 0, 'second' => 0], FALSE],
|
||||
'Invalid month from date array' => [['year' => 2010, 'month' => 27, 'day' => 8, 'hour' => 8, 'minute' => 0, 'second' => 0], FALSE],
|
||||
'Invalid hour from date array' => [['year' => 2010, 'month' => 2, 'day' => 28, 'hour' => 80, 'minute' => 0, 'second' => 0], FALSE],
|
||||
'Invalid minute from date array.' => [['year' => 2010, 'month' => 7, 'day' => 8, 'hour' => 8, 'minute' => 88, 'second' => 0], FALSE],
|
||||
'Missing day' => [['year' => 2059, 'month' => 1, 'second' => 1], FALSE],
|
||||
'Zero day' => [['year' => 2059, 'month' => 1, 'day' => 0], FALSE],
|
||||
];
|
||||
}
|
||||
|
||||
/**
|
||||
* 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 static 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 static 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 static 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 static 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.
|
||||
[
|
||||
'input1' => DateTimePlus::createFromFormat('Y-m-d H:i:s', '2000-01-01 00:00:00', new \DateTimeZone('Australia/Sydney')),
|
||||
'input2' => 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 an 18 hour time interval.
|
||||
[
|
||||
'input1' => DateTimePlus::createFromFormat('Y-m-d H:i:s', '1970-01-01 00:00:00', new \DateTimeZone('Australia/Sydney')),
|
||||
'input2' => 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::createFromTimestamp(0, new \DateTimeZone('UTC')),
|
||||
'absolute' => FALSE,
|
||||
'expected' => $negative_1_hour,
|
||||
],
|
||||
[
|
||||
'input1' => DateTimePlus::createFromTimestamp(3600),
|
||||
'input2' => DateTimePlus::createFromTimestamp(0),
|
||||
'absolute' => FALSE,
|
||||
'expected' => $negative_1_hour,
|
||||
],
|
||||
[
|
||||
'input1' => DateTimePlus::createFromTimestamp(3600),
|
||||
'input2' => \DateTime::createFromFormat('U', '0'),
|
||||
'absolute' => FALSE,
|
||||
'expected' => $negative_1_hour,
|
||||
],
|
||||
[
|
||||
'input1' => DateTimePlus::createFromTimestamp(3600),
|
||||
'input2' => DateTimePlus::createFromTimestamp(0),
|
||||
'absolute' => TRUE,
|
||||
'expected' => $positive_1_hour,
|
||||
],
|
||||
[
|
||||
'input1' => DateTimePlus::createFromTimestamp(3600),
|
||||
'input2' => \DateTime::createFromFormat('U', '0'),
|
||||
'absolute' => TRUE,
|
||||
'expected' => $positive_1_hour,
|
||||
],
|
||||
[
|
||||
'input1' => DateTimePlus::createFromTimestamp(0),
|
||||
'input2' => DateTimePlus::createFromTimestamp(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 static function providerTestInvalidDateDiff() {
|
||||
return [
|
||||
[
|
||||
'input1' => DateTimePlus::createFromTimestamp(3600),
|
||||
'input2' => '1970-01-01 00:00:00',
|
||||
'absolute' => FALSE,
|
||||
],
|
||||
[
|
||||
'input1' => DateTimePlus::createFromTimestamp(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): void {
|
||||
$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 static 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',
|
||||
],
|
||||
],
|
||||
[
|
||||
'invalid time string',
|
||||
[
|
||||
'The timezone could not be found in the database',
|
||||
'Double timezone specification',
|
||||
],
|
||||
],
|
||||
];
|
||||
}
|
||||
|
||||
/**
|
||||
* Tests the $settings['validate_format'] parameter in ::createFromFormat().
|
||||
*/
|
||||
public function testValidateFormat(): void {
|
||||
// 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(): void {
|
||||
$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(): void {
|
||||
$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(): void {
|
||||
$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(): void {
|
||||
$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(): void {
|
||||
$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.
|
||||
$date_time_plus = DateTimePlus::createFromFormat('Y-m-d H:i:s', '2017-07-13 22:40:00', $new_york, ['langcode' => 'en']);
|
||||
$this->assertEquals(1500000000, $date_time_plus->getTimestamp());
|
||||
$this->assertEquals('America/New_York', $date_time_plus->getTimezone()->getName());
|
||||
|
||||
$datetime = $date_time_plus->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, $date_time_plus->getTimestamp());
|
||||
$this->assertEquals('America/New_York', $date_time_plus->getTimezone()->getName());
|
||||
}
|
||||
|
||||
}
|
||||
151
web/core/tests/Drupal/Tests/Component/Datetime/TimeTest.php
Normal file
151
web/core/tests/Drupal/Tests/Component/Datetime/TimeTest.php
Normal file
@ -0,0 +1,151 @@
|
||||
<?php
|
||||
|
||||
declare(strict_types=1);
|
||||
|
||||
namespace Drupal\Tests\Component\Datetime;
|
||||
|
||||
use Drupal\Component\Datetime\Time;
|
||||
use PHPUnit\Framework\TestCase;
|
||||
use Symfony\Component\HttpFoundation\Request;
|
||||
|
||||
/**
|
||||
* Tests the Time class.
|
||||
*
|
||||
* Isolate the tests to prevent side effects from altering system time.
|
||||
*
|
||||
* @coversDefaultClass \Drupal\Component\Datetime\Time
|
||||
* @group Datetime
|
||||
* @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(): void {
|
||||
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(): void {
|
||||
$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(): void {
|
||||
$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(): void {
|
||||
// With no request, and no global variable, we expect to get the int part
|
||||
// of the microtime.
|
||||
$expected = 1234567;
|
||||
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(): void {
|
||||
$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(): void {
|
||||
$expected = 12345678;
|
||||
$this->assertEquals($expected, $this->time->getCurrentTime());
|
||||
}
|
||||
|
||||
/**
|
||||
* Tests the getCurrentMicroTime method.
|
||||
*
|
||||
* @covers ::getCurrentMicroTime
|
||||
*/
|
||||
public function testGetCurrentMicroTime(): void {
|
||||
$expected = 1234567.89;
|
||||
$this->assertEquals($expected, $this->time->getCurrentMicroTime());
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
namespace Drupal\Component\Datetime;
|
||||
|
||||
/**
|
||||
* Shadow time() system call.
|
||||
*
|
||||
* @return int
|
||||
* The fixed integer timestamp used for testing purposes.
|
||||
*/
|
||||
function time() {
|
||||
return 12345678;
|
||||
}
|
||||
|
||||
/**
|
||||
* Shadow microtime system call.
|
||||
*
|
||||
* @return float
|
||||
* The fixed float timestamp used for testing purposes.
|
||||
*/
|
||||
function microtime(bool $as_float = FALSE) {
|
||||
return 1234567.89;
|
||||
}
|
||||
@ -0,0 +1,62 @@
|
||||
<?php
|
||||
|
||||
declare(strict_types=1);
|
||||
|
||||
namespace Drupal\Tests\Component\Datetime;
|
||||
|
||||
use Drupal\Component\Datetime\Time;
|
||||
use PHPUnit\Framework\TestCase;
|
||||
|
||||
/**
|
||||
* Tests that getRequest(Micro)Time works when no underlying request exists.
|
||||
*
|
||||
* @coversDefaultClass \Drupal\Component\Datetime\Time
|
||||
* @group Datetime
|
||||
* @group #slow
|
||||
* @runTestsInSeparateProcesses
|
||||
* @preserveGlobalState disabled
|
||||
*/
|
||||
class TimeWithNoRequestTest extends TestCase {
|
||||
|
||||
/**
|
||||
* The time class for testing.
|
||||
*/
|
||||
protected Time $time;
|
||||
|
||||
/**
|
||||
* {@inheritdoc}
|
||||
*/
|
||||
protected function setUp(): void {
|
||||
parent::setUp();
|
||||
|
||||
// We need to explicitly unset the $_SERVER variables, so that Time is
|
||||
// forced to look for current time.
|
||||
unset($_SERVER['REQUEST_TIME']);
|
||||
unset($_SERVER['REQUEST_TIME_FLOAT']);
|
||||
|
||||
$this->time = new Time();
|
||||
}
|
||||
|
||||
/**
|
||||
* Tests the getRequestTime method.
|
||||
*
|
||||
* @covers ::getRequestTime
|
||||
*/
|
||||
public function testGetRequestTimeImmutable(): void {
|
||||
$requestTime = $this->time->getRequestTime();
|
||||
sleep(2);
|
||||
$this->assertSame($requestTime, $this->time->getRequestTime());
|
||||
}
|
||||
|
||||
/**
|
||||
* Tests the getRequestMicroTime method.
|
||||
*
|
||||
* @covers ::getRequestMicroTime
|
||||
*/
|
||||
public function testGetRequestMicroTimeImmutable(): void {
|
||||
$requestTime = $this->time->getRequestMicroTime();
|
||||
usleep(20000);
|
||||
$this->assertSame($requestTime, $this->time->getRequestMicroTime());
|
||||
}
|
||||
|
||||
}
|
||||
File diff suppressed because it is too large
Load Diff
@ -0,0 +1,755 @@
|
||||
<?php
|
||||
|
||||
declare(strict_types=1);
|
||||
|
||||
namespace Drupal\Tests\Component\DependencyInjection\Dumper;
|
||||
|
||||
use Drupal\Component\Utility\Crypt;
|
||||
use Drupal\TestTools\Extension\DeprecationBridge\ExpectDeprecationTrait;
|
||||
use PHPUnit\Framework\TestCase;
|
||||
use Prophecy\PhpUnit\ProphecyTrait;
|
||||
use Prophecy\Prophet;
|
||||
use Symfony\Component\DependencyInjection\Argument\IteratorArgument;
|
||||
use Symfony\Component\DependencyInjection\Argument\ServiceClosureArgument;
|
||||
use Symfony\Component\DependencyInjection\ContainerInterface;
|
||||
use Symfony\Component\DependencyInjection\Definition;
|
||||
use Symfony\Component\DependencyInjection\Exception\InvalidArgumentException;
|
||||
use Symfony\Component\DependencyInjection\Exception\RuntimeException;
|
||||
use Symfony\Component\DependencyInjection\Parameter;
|
||||
use Symfony\Component\DependencyInjection\ParameterBag\ParameterBag;
|
||||
use Symfony\Component\DependencyInjection\Reference;
|
||||
use Symfony\Component\ExpressionLanguage\Expression;
|
||||
|
||||
/**
|
||||
* @coversDefaultClass \Drupal\Component\DependencyInjection\Dumper\OptimizedPhpArrayDumper
|
||||
* @group DependencyInjection
|
||||
*/
|
||||
class OptimizedPhpArrayDumperTest extends TestCase {
|
||||
|
||||
use ExpectDeprecationTrait;
|
||||
use ProphecyTrait;
|
||||
|
||||
/**
|
||||
* 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(): void {
|
||||
// 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([]);
|
||||
$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(): void {
|
||||
$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): void {
|
||||
$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 static 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): void {
|
||||
$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);
|
||||
|
||||
$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 static 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,
|
||||
],
|
||||
];
|
||||
}
|
||||
|
||||
/**
|
||||
* 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 ::getServiceClosureCall
|
||||
* @covers ::getParameterCall
|
||||
*
|
||||
* @dataProvider getDefinitionsDataProvider
|
||||
*/
|
||||
public function testGetServiceDefinitions($services, $definition_services): void {
|
||||
$this->containerDefinition['services'] = $definition_services;
|
||||
|
||||
$this->containerBuilder->getDefinitions()->willReturn($services);
|
||||
|
||||
$bar_definition = new Definition('\stdClass');
|
||||
$bar_definition->setPublic(TRUE);
|
||||
$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 static 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' => static::getCollection(['foo', static::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' => static::getCollection([static::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' => static::getCollection([
|
||||
'foo',
|
||||
static::getPrivateServiceCall('private_definition', $private_definition, TRUE),
|
||||
]),
|
||||
] + $base_service_definition;
|
||||
|
||||
// Test a service closure.
|
||||
$service_definitions[] = [
|
||||
'arguments' => [
|
||||
'foo',
|
||||
[
|
||||
'alias-1' => new ServiceClosureArgument(new Reference('bar', ContainerInterface::NULL_ON_INVALID_REFERENCE)),
|
||||
'alias-2' => new ServiceClosureArgument(new Reference('bar', ContainerInterface::EXCEPTION_ON_INVALID_REFERENCE)),
|
||||
],
|
||||
],
|
||||
'arguments_count' => 2,
|
||||
'arguments_expected' => static::getCollection([
|
||||
'foo',
|
||||
static::getCollection([
|
||||
'alias-1' => static::getServiceClosureCall('bar', ContainerInterface::NULL_ON_INVALID_REFERENCE),
|
||||
'alias-2' => static::getServiceClosureCall('bar', ContainerInterface::EXCEPTION_ON_INVALID_REFERENCE),
|
||||
]),
|
||||
]),
|
||||
] + $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' => static::getCollection([
|
||||
'foo',
|
||||
static::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' => static::getCollection([static::getCollection([static::getServiceCall('bar')])]),
|
||||
] + $base_service_definition;
|
||||
|
||||
// Test an IteratorArgument collection with a reference to resolve.
|
||||
$service_definitions[] = [
|
||||
'arguments' => [new IteratorArgument([new Reference('bar')])],
|
||||
'arguments_count' => 1,
|
||||
'arguments_expected' => static::getCollection([static::getIterator([static::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' => static::getCollection([static::getParameterCall('llama_parameter')]),
|
||||
] + $base_service_definition;
|
||||
|
||||
// Test getMethodCalls.
|
||||
$calls = [
|
||||
['method', static::getCollection([])],
|
||||
['method2', static::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' => [static::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' => [static::getServiceCall('bar'), 'configureService'],
|
||||
] + $base_service_definition;
|
||||
|
||||
$services_provided = [];
|
||||
$services_provided[] = [
|
||||
[],
|
||||
[],
|
||||
];
|
||||
|
||||
foreach ($service_definitions as $service_definition) {
|
||||
$definition = (new Prophet())->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' => static::serializeDefinition($filtered_service_definition)],
|
||||
];
|
||||
}
|
||||
|
||||
return $services_provided;
|
||||
}
|
||||
|
||||
/**
|
||||
* Helper function to serialize a definition.
|
||||
*
|
||||
* Used to override serialization.
|
||||
*/
|
||||
protected static function serializeDefinition(array $service_definition) {
|
||||
return serialize($service_definition);
|
||||
}
|
||||
|
||||
/**
|
||||
* Helper function to return a service definition.
|
||||
*/
|
||||
protected static function getServiceCall($id, $invalid_behavior = ContainerInterface::EXCEPTION_ON_INVALID_REFERENCE) {
|
||||
return (object) [
|
||||
'type' => 'service',
|
||||
'id' => $id,
|
||||
'invalidBehavior' => $invalid_behavior,
|
||||
];
|
||||
}
|
||||
|
||||
/**
|
||||
* Helper function to return a service closure definition.
|
||||
*/
|
||||
protected static function getServiceClosureCall($id, $invalid_behavior = ContainerInterface::EXCEPTION_ON_INVALID_REFERENCE) {
|
||||
return (object) [
|
||||
'type' => 'service_closure',
|
||||
'id' => $id,
|
||||
'invalidBehavior' => $invalid_behavior,
|
||||
];
|
||||
}
|
||||
|
||||
/**
|
||||
* Tests that references to aliases work correctly.
|
||||
*
|
||||
* @covers ::getReferenceCall
|
||||
*
|
||||
* @dataProvider publicPrivateDataProvider
|
||||
*/
|
||||
public function testGetServiceDefinitionWithReferenceToAlias($public): void {
|
||||
$bar_definition = new Definition('\stdClass');
|
||||
$bar_definition_php_array = [
|
||||
'class' => '\stdClass',
|
||||
];
|
||||
if ($public) {
|
||||
$bar_definition->setPublic(TRUE);
|
||||
}
|
||||
else {
|
||||
$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->setPublic(TRUE);
|
||||
$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 = static::getServiceCall('bar');
|
||||
}
|
||||
else {
|
||||
$service_definition = static::getPrivateServiceCall('bar', $bar_definition_php_array, TRUE);
|
||||
}
|
||||
$data = [
|
||||
'class' => '\stdClass',
|
||||
'arguments' => static::getCollection([
|
||||
$service_definition,
|
||||
]),
|
||||
'arguments_count' => 1,
|
||||
];
|
||||
$this->assertEquals(static::serializeDefinition($data), $dump['services']['foo'], 'Expected definition matches dump.');
|
||||
}
|
||||
|
||||
public static function publicPrivateDataProvider() {
|
||||
return [
|
||||
[TRUE],
|
||||
[FALSE],
|
||||
];
|
||||
}
|
||||
|
||||
/**
|
||||
* Tests that getDecoratedService() is unsupported.
|
||||
*
|
||||
* Tests that the correct InvalidArgumentException is thrown for
|
||||
* getDecoratedService().
|
||||
*
|
||||
* @covers ::getServiceDefinition
|
||||
*/
|
||||
public function testGetServiceDefinitionForDecoratedService(): void {
|
||||
$bar_definition = new Definition('\stdClass');
|
||||
$bar_definition->setPublic(TRUE);
|
||||
$bar_definition->setDecoratedService((string) 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(): void {
|
||||
$expression = new Expression('');
|
||||
|
||||
$bar_definition = new Definition('\stdClass');
|
||||
$bar_definition->setPublic(TRUE);
|
||||
$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(): void {
|
||||
$service = new \stdClass();
|
||||
|
||||
$bar_definition = new Definition('\stdClass');
|
||||
$bar_definition->setPublic(TRUE);
|
||||
$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(): void {
|
||||
$resource = fopen('php://memory', 'r');
|
||||
|
||||
$bar_definition = new Definition('\stdClass');
|
||||
$bar_definition->setPublic(TRUE);
|
||||
$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): void {
|
||||
$definition = new Definition('\stdClass', [$argument]);
|
||||
$definition->setPublic(TRUE);
|
||||
$this->containerBuilder->getDefinitions()->willReturn([
|
||||
'test' => $definition,
|
||||
]);
|
||||
|
||||
$dump = $this->dumper->getArray();
|
||||
|
||||
$this->assertEquals(static::serializeDefinition([
|
||||
'class' => '\stdClass',
|
||||
'arguments' => static::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 static function percentsEscapeProvider() {
|
||||
return [
|
||||
['%foo%', '%%foo%%'],
|
||||
['foo%bar%', 'foo%%bar%%'],
|
||||
['%foo%bar', '%%foo%%bar'],
|
||||
['%', '%'],
|
||||
['%', '%%'],
|
||||
['%%', '%%%'],
|
||||
['%%', '%%%%'],
|
||||
];
|
||||
}
|
||||
|
||||
/**
|
||||
* Helper function to return a private service definition.
|
||||
*/
|
||||
protected static 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 static function getCollection($collection) {
|
||||
return (object) [
|
||||
'type' => 'collection',
|
||||
'value' => $collection,
|
||||
];
|
||||
}
|
||||
|
||||
/**
|
||||
* Helper function to return a machine-optimized iterator.
|
||||
*/
|
||||
protected static function getIterator($collection) {
|
||||
return (object) [
|
||||
'type' => 'iterator',
|
||||
'value' => $collection,
|
||||
];
|
||||
}
|
||||
|
||||
/**
|
||||
* Helper function to return a parameter definition.
|
||||
*/
|
||||
protected static 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,
|
||||
];
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
/**
|
||||
* Defines a dummy ExpressionLanguage component.
|
||||
*
|
||||
* 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 {
|
||||
|
||||
public function __construct($expression) {
|
||||
}
|
||||
|
||||
/**
|
||||
* Gets the string representation of the expression.
|
||||
*/
|
||||
public function __toString() {
|
||||
return 'dummy_expression';
|
||||
}
|
||||
|
||||
}
|
||||
}
|
||||
@ -0,0 +1,56 @@
|
||||
<?php
|
||||
|
||||
declare(strict_types=1);
|
||||
|
||||
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(): void {
|
||||
$this->machineFormat = FALSE;
|
||||
$this->dumperClass = '\Drupal\Component\DependencyInjection\Dumper\PhpArrayDumper';
|
||||
parent::setUp();
|
||||
}
|
||||
|
||||
/**
|
||||
* {@inheritdoc}
|
||||
*/
|
||||
protected static function serializeDefinition(array $service_definition): array {
|
||||
return $service_definition;
|
||||
}
|
||||
|
||||
/**
|
||||
* {@inheritdoc}
|
||||
*/
|
||||
protected static function getServiceCall($id, $invalid_behavior = ContainerInterface::EXCEPTION_ON_INVALID_REFERENCE): string {
|
||||
if ($invalid_behavior !== ContainerInterface::EXCEPTION_ON_INVALID_REFERENCE) {
|
||||
return sprintf('@?%s', $id);
|
||||
}
|
||||
|
||||
return sprintf('@%s', $id);
|
||||
}
|
||||
|
||||
/**
|
||||
* {@inheritdoc}
|
||||
*/
|
||||
protected static function getParameterCall($name): string {
|
||||
return '%' . $name . '%';
|
||||
}
|
||||
|
||||
/**
|
||||
* {@inheritdoc}
|
||||
*/
|
||||
protected static function getCollection($collection, $resolve = TRUE) {
|
||||
return $collection;
|
||||
}
|
||||
|
||||
}
|
||||
@ -0,0 +1,18 @@
|
||||
<?php
|
||||
|
||||
/**
|
||||
* @file
|
||||
* Contains a test function for container 'file' include testing.
|
||||
*/
|
||||
|
||||
declare(strict_types=1);
|
||||
|
||||
/**
|
||||
* 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,52 @@
|
||||
<?php
|
||||
|
||||
declare(strict_types=1);
|
||||
|
||||
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(): void {
|
||||
parent::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): string {
|
||||
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): string {
|
||||
return '%' . $name . '%';
|
||||
}
|
||||
|
||||
/**
|
||||
* Helper function to return a machine-optimized '@notation'.
|
||||
*/
|
||||
protected function getCollection($collection, $resolve = TRUE) {
|
||||
return $collection;
|
||||
}
|
||||
|
||||
}
|
||||
@ -0,0 +1,62 @@
|
||||
<?php
|
||||
|
||||
declare(strict_types=1);
|
||||
|
||||
namespace Drupal\Tests\Component\DependencyInjection;
|
||||
|
||||
use Drupal\Component\DependencyInjection\ReverseContainer;
|
||||
use PHPUnit\Framework\TestCase;
|
||||
use Symfony\Component\DependencyInjection\ContainerBuilder;
|
||||
|
||||
/**
|
||||
* Tests the ReverseContainer class.
|
||||
*
|
||||
* The reverse container uses a static to maintain information across
|
||||
* container rebuilds.
|
||||
*
|
||||
* @runTestsInSeparateProcesses
|
||||
* @coversDefaultClass \Drupal\Component\DependencyInjection\ReverseContainer
|
||||
* @group DependencyInjection
|
||||
*/
|
||||
class ReverseContainerTest extends TestCase {
|
||||
|
||||
/**
|
||||
* @covers ::getId
|
||||
*/
|
||||
public function testGetId(): void {
|
||||
$container = new ContainerBuilder();
|
||||
$service = new \stdClass();
|
||||
$container->set('bar', $service);
|
||||
|
||||
$reverse_container = new ReverseContainer($container);
|
||||
|
||||
$this->assertSame('bar', $reverse_container->getId($service));
|
||||
$non_service = new \stdClass();
|
||||
$this->assertNull($reverse_container->getId($non_service));
|
||||
$this->assertSame('service_container', $reverse_container->getId($container));
|
||||
}
|
||||
|
||||
/**
|
||||
* @covers ::recordContainer
|
||||
*/
|
||||
public function testRecordContainer(): void {
|
||||
$container = new ContainerBuilder();
|
||||
$service = new \stdClass();
|
||||
$container->set('bar', $service);
|
||||
|
||||
$reverse_container = new ReverseContainer($container);
|
||||
$reverse_container->recordContainer();
|
||||
|
||||
$container = new ContainerBuilder();
|
||||
$reverse_container = new ReverseContainer($container);
|
||||
|
||||
// New container does not have a bar service.
|
||||
$this->assertNull($reverse_container->getId($service));
|
||||
|
||||
// Add the bar service to make the lookup based on the old object work as
|
||||
// expected.
|
||||
$container->set('bar', new \stdClass());
|
||||
$this->assertSame('bar', $reverse_container->getId($service));
|
||||
}
|
||||
|
||||
}
|
||||
@ -0,0 +1,60 @@
|
||||
<?php
|
||||
|
||||
declare(strict_types=1);
|
||||
|
||||
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 static 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): void {
|
||||
$diff = new Diff($from, $to);
|
||||
$formatter = new DiffFormatter();
|
||||
$output = $formatter->format($diff);
|
||||
$this->assertEquals($expected, $output);
|
||||
}
|
||||
|
||||
}
|
||||
@ -0,0 +1,133 @@
|
||||
<?php
|
||||
|
||||
declare(strict_types=1);
|
||||
|
||||
namespace Drupal\Tests\Component\Diff;
|
||||
|
||||
use Drupal\Component\Diff\DiffOpOutputBuilder;
|
||||
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;
|
||||
use SebastianBergmann\Diff\Differ;
|
||||
|
||||
/**
|
||||
* @coversDefaultClass \Drupal\Component\Diff\DiffOpOutputBuilder
|
||||
*
|
||||
* @group Diff
|
||||
*/
|
||||
class DiffOpOutputBuilderTest 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 static function provideTestDiff(): array {
|
||||
return [
|
||||
'empty' => [[], [], []],
|
||||
'add' => [[new DiffOpAdd(['a'])], [], ['a']],
|
||||
'copy' => [[new DiffOpCopy(['a'])], ['a'], ['a']],
|
||||
'change' => [[new DiffOpChange(['a'], ['b'])], ['a'], ['b']],
|
||||
'copy-and-change' => [
|
||||
[
|
||||
new DiffOpCopy(['a']),
|
||||
new DiffOpChange(['b'], ['c']),
|
||||
],
|
||||
['a', 'b'],
|
||||
['a', 'c'],
|
||||
],
|
||||
'copy-change-copy' => [
|
||||
[
|
||||
new DiffOpCopy(['a']),
|
||||
new DiffOpChange(['b'], ['c']),
|
||||
new DiffOpCopy(['d']),
|
||||
],
|
||||
['a', 'b', 'd'],
|
||||
['a', 'c', 'd'],
|
||||
],
|
||||
'copy-change-copy-add' => [
|
||||
[
|
||||
new DiffOpCopy(['a']),
|
||||
new DiffOpChange(['b'], ['c']),
|
||||
new DiffOpCopy(['d']),
|
||||
new DiffOpAdd(['e']),
|
||||
],
|
||||
['a', 'b', 'd'],
|
||||
['a', 'c', 'd', 'e'],
|
||||
],
|
||||
'copy-delete' => [
|
||||
[
|
||||
new DiffOpCopy(['a']),
|
||||
new DiffOpDelete(['b', 'd']),
|
||||
],
|
||||
['a', 'b', 'd'],
|
||||
['a'],
|
||||
],
|
||||
'change-copy' => [
|
||||
[
|
||||
new DiffOpChange(['aa', 'bb', 'cc'], ['a', 'c']),
|
||||
new DiffOpCopy(['d']),
|
||||
],
|
||||
['aa', 'bb', 'cc', 'd'],
|
||||
['a', 'c', 'd'],
|
||||
],
|
||||
'copy-change-copy-change' => [
|
||||
[
|
||||
new DiffOpCopy(['a']),
|
||||
new DiffOpChange(['bb'], ['b', 'c']),
|
||||
new DiffOpCopy(['d']),
|
||||
new DiffOpChange(['ee'], ['e']),
|
||||
],
|
||||
['a', 'bb', 'd', 'ee'],
|
||||
['a', 'b', 'c', 'd', 'e'],
|
||||
],
|
||||
];
|
||||
}
|
||||
|
||||
/**
|
||||
* Tests whether op classes returned match expectations.
|
||||
*
|
||||
* @covers ::toOpsArray
|
||||
* @dataProvider provideTestDiff
|
||||
*/
|
||||
public function testToOpsArray(array $expected, array $from, array $to): void {
|
||||
$diffOpBuilder = new DiffOpOutputBuilder();
|
||||
$differ = new Differ($diffOpBuilder);
|
||||
$diff = $differ->diffToArray($from, $to);
|
||||
$this->assertEquals($expected, $diffOpBuilder->toOpsArray($diff));
|
||||
}
|
||||
|
||||
/**
|
||||
* @covers ::getDiff
|
||||
* @dataProvider provideTestDiff
|
||||
*/
|
||||
public function testGetDiff(array $expected, array $from, array $to): void {
|
||||
$differ = new Differ(new DiffOpOutputBuilder());
|
||||
$diff = $differ->diff($from, $to);
|
||||
$this->assertEquals($expected, unserialize($diff));
|
||||
}
|
||||
|
||||
/**
|
||||
* Tests that two files can be successfully diffed.
|
||||
*
|
||||
* @covers ::toOpsArray
|
||||
*/
|
||||
public function testDiffInfiniteLoop(): void {
|
||||
$from = explode("\n", file_get_contents(__DIR__ . '/Engine/fixtures/file1.txt'));
|
||||
$to = explode("\n", file_get_contents(__DIR__ . '/Engine/fixtures/file2.txt'));
|
||||
$diffOpBuilder = new DiffOpOutputBuilder();
|
||||
$differ = new Differ($diffOpBuilder);
|
||||
$diff = $differ->diffToArray($from, $to);
|
||||
$diffOps = $diffOpBuilder->toOpsArray($diff);
|
||||
$this->assertCount(4, $diffOps);
|
||||
$this->assertEquals($diffOps[0], new DiffOpAdd([' - image.style.max_325x325']));
|
||||
$this->assertEquals($diffOps[1], new DiffOpCopy([' - image.style.max_650x650']));
|
||||
$this->assertEquals($diffOps[2], new DiffOpChange([' - image.style.max_325x325'], ['_core:', ' default_config_hash: random_hash_string_here']));
|
||||
$this->assertEquals($diffOps[3], new DiffOpCopy(['fallback_image_style: max_325x325', '']));
|
||||
}
|
||||
|
||||
}
|
||||
@ -0,0 +1,59 @@
|
||||
<?php
|
||||
|
||||
declare(strict_types=1);
|
||||
|
||||
namespace Drupal\Tests\Component\Diff\Engine;
|
||||
|
||||
// cspell:ignore HWLDFWordAccumulator
|
||||
|
||||
use Drupal\Component\Diff\Engine\HWLDFWordAccumulator;
|
||||
use PHPUnit\Framework\TestCase;
|
||||
|
||||
// cspell:ignore wordword
|
||||
|
||||
/**
|
||||
* 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(): void {
|
||||
$acc = new HWLDFWordAccumulator();
|
||||
$this->assertEquals([' '], $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 static function provideAddWords() {
|
||||
return [
|
||||
[['wordword2'], ['word', 'word2'], 'tag'],
|
||||
[['word', 'word2'], ['word', "\nword2"], 'tag'],
|
||||
[[' ', 'word2'], ['', "\nword2"], 'tag'],
|
||||
];
|
||||
}
|
||||
|
||||
/**
|
||||
* @covers ::addWords
|
||||
* @dataProvider provideAddWords
|
||||
*/
|
||||
public function testAddWords($expected, $words, $tag): void {
|
||||
$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: random_hash_string_here
|
||||
fallback_image_style: max_325x325
|
||||
@ -0,0 +1,167 @@
|
||||
<?php
|
||||
|
||||
declare(strict_types=1);
|
||||
|
||||
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(): void {
|
||||
// Ensure that FileCacheFactory has a prefix.
|
||||
FileCacheFactory::setPrefix('prefix');
|
||||
}
|
||||
|
||||
/**
|
||||
* Tests YAML directory discovery.
|
||||
*
|
||||
* @covers ::findAll
|
||||
*/
|
||||
public function testDiscovery(): void {
|
||||
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();
|
||||
|
||||
// The file path is dependent on the operating system, so we adjust the
|
||||
// directory separator.
|
||||
$this->assertSame(['id' => 'item1', 'name' => 'test1 item 1', YamlDirectoryDiscovery::FILE_KEY => 'vfs://modules/test_1/subdir1' . DIRECTORY_SEPARATOR . 'item_1.test.yml'], $data['test_1']['item1']);
|
||||
$this->assertSame(['id' => 'item2', 'name' => 'test1 item 2', YamlDirectoryDiscovery::FILE_KEY => 'vfs://modules/test_1/subdir2' . DIRECTORY_SEPARATOR . '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' . DIRECTORY_SEPARATOR . 'item_3.test.yml'], $data['test_2']['item3']);
|
||||
$this->assertCount(1, $data['test_2']);
|
||||
|
||||
$this->assertArrayNotHasKey('test_3', $data, 'test_3 provides 0 items');
|
||||
|
||||
$this->assertSame(['id' => 'item4', 'name' => 'test4 item 4', YamlDirectoryDiscovery::FILE_KEY => 'vfs://modules/test_4/subdir1' . DIRECTORY_SEPARATOR . 'item_4.test.yml'], $data['test_4']['item4']);
|
||||
$this->assertSame(['id' => 'item5', 'name' => 'test4 item 5', YamlDirectoryDiscovery::FILE_KEY => 'vfs://modules/test_4/subdir1' . DIRECTORY_SEPARATOR . 'item_5.test.yml'], $data['test_4']['item5']);
|
||||
$this->assertSame(['id' => 'item6', 'name' => 'test4 item 6', YamlDirectoryDiscovery::FILE_KEY => 'vfs://modules/test_4/subdir1' . DIRECTORY_SEPARATOR . '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(): void {
|
||||
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' . DIRECTORY_SEPARATOR . '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(): void {
|
||||
$this->expectException(DiscoveryException::class);
|
||||
$this->expectExceptionMessage('The vfs://modules/test_1' . DIRECTORY_SEPARATOR . '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(): void {
|
||||
$this->expectException(DiscoveryException::class);
|
||||
$this->expectExceptionMessage('The vfs://modules/test_1' . DIRECTORY_SEPARATOR . '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,93 @@
|
||||
<?php
|
||||
|
||||
declare(strict_types=1);
|
||||
|
||||
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(): void {
|
||||
// Ensure that FileCacheFactory has a prefix.
|
||||
FileCacheFactory::setPrefix('prefix');
|
||||
}
|
||||
|
||||
/**
|
||||
* Tests the YAML file discovery.
|
||||
*/
|
||||
public function testDiscovery(): void {
|
||||
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->assertCount(4, $data);
|
||||
$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('test', $data[$key]['name']);
|
||||
}
|
||||
|
||||
$this->assertSame([], $data['test_4']);
|
||||
}
|
||||
|
||||
/**
|
||||
* Tests if filename is output for a broken YAML file.
|
||||
*/
|
||||
public function testForBrokenYml(): void {
|
||||
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();
|
||||
}
|
||||
|
||||
}
|
||||
163
web/core/tests/Drupal/Tests/Component/DrupalComponentTest.php
Normal file
163
web/core/tests/Drupal/Tests/Component/DrupalComponentTest.php
Normal file
@ -0,0 +1,163 @@
|
||||
<?php
|
||||
|
||||
declare(strict_types=1);
|
||||
|
||||
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(): void {
|
||||
$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(): void {
|
||||
$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 string $component_path
|
||||
* The path to the component.
|
||||
*
|
||||
* @dataProvider getComponents
|
||||
*/
|
||||
public function testComponentLicense(string $component_path): void {
|
||||
$this->assertFileExists($component_path . DIRECTORY_SEPARATOR . 'LICENSE.txt');
|
||||
$this->assertSame('e84dac1d9fbb5a4a69e38654ce644cea769aa76b', hash_file('sha1', $component_path . DIRECTORY_SEPARATOR . 'LICENSE.txt'));
|
||||
}
|
||||
|
||||
/**
|
||||
* Data provider.
|
||||
*
|
||||
* @return array
|
||||
* An associative array where the keys are component names and the values
|
||||
* are arrays containing the corresponding component path.
|
||||
*/
|
||||
public static function getComponents(): array {
|
||||
$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): array {
|
||||
$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.
|
||||
*
|
||||
* @internal
|
||||
*/
|
||||
protected function assertNoCoreUsage(string $class_path): void {
|
||||
$contents = file_get_contents($class_path);
|
||||
preg_match_all('/^.*Drupal\\\Core.*$/m', $contents, $matches);
|
||||
$matches = array_filter($matches[0], function ($line) {
|
||||
// Filter references that don't really matter.
|
||||
return preg_match('/@see|E_USER_DEPRECATED|expectDeprecation/', $line) === 0;
|
||||
});
|
||||
$this->assertEmpty($matches, "Checking for illegal reference to 'Drupal\\Core' namespace in $class_path");
|
||||
}
|
||||
|
||||
/**
|
||||
* Data provider for testAssertNoCoreUsage().
|
||||
*
|
||||
* @return array
|
||||
* Data for testAssertNoCoreUsage() in the form:
|
||||
* - TRUE if the test passes, FALSE otherwise.
|
||||
* - File data as a string. This will be used as a virtual file.
|
||||
*/
|
||||
public static function providerAssertNoCoreUsage() {
|
||||
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 providerAssertNoCoreUsage
|
||||
*/
|
||||
public function testAssertNoCoreUsage($expected_pass, $file_data): void {
|
||||
// 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) {
|
||||
$pass = FALSE;
|
||||
}
|
||||
$this->assertEquals($expected_pass, $pass, $expected_pass ?
|
||||
'Test caused a false positive' :
|
||||
'Test failed to detect Core usage');
|
||||
}
|
||||
|
||||
}
|
||||
@ -0,0 +1,186 @@
|
||||
<?php
|
||||
|
||||
declare(strict_types=1);
|
||||
|
||||
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(): void {
|
||||
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(): void {
|
||||
$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(): void {
|
||||
FileCacheFactory::setPrefix(NULL);
|
||||
$this->expectException(\InvalidArgumentException::class);
|
||||
$this->expectExceptionMessage('Required prefix configuration is missing');
|
||||
FileCacheFactory::get('test_foo_settings', []);
|
||||
}
|
||||
|
||||
/**
|
||||
* @covers ::get
|
||||
*/
|
||||
public function testGetDisabledFileCache(): void {
|
||||
// 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): void {
|
||||
FileCacheFactory::setConfiguration($configuration);
|
||||
|
||||
$file_cache = FileCacheFactory::get('test_foo_settings', $arguments);
|
||||
$this->assertInstanceOf($class, $file_cache);
|
||||
}
|
||||
|
||||
/**
|
||||
* Data provider for testGetConfigurationOverrides().
|
||||
*/
|
||||
public static function configurationDataProvider() {
|
||||
$data = [];
|
||||
|
||||
// Test fallback configuration.
|
||||
$data['fallback-configuration'] = [
|
||||
[],
|
||||
[],
|
||||
FileCache::class,
|
||||
];
|
||||
|
||||
// Test default configuration.
|
||||
$data['default-configuration'] = [
|
||||
['default' => ['class' => CustomFileCache::class]],
|
||||
[],
|
||||
CustomFileCache::class,
|
||||
];
|
||||
|
||||
// Test specific per collection setting.
|
||||
$data['collection-setting'] = [
|
||||
['test_foo_settings' => ['class' => CustomFileCache::class]],
|
||||
[],
|
||||
CustomFileCache::class,
|
||||
];
|
||||
|
||||
// Test default configuration plus specific per collection setting.
|
||||
$data['default-plus-collection-setting'] = [
|
||||
[
|
||||
'default' => ['class' => '\stdClass'],
|
||||
'test_foo_settings' => ['class' => CustomFileCache::class],
|
||||
],
|
||||
[],
|
||||
CustomFileCache::class,
|
||||
];
|
||||
|
||||
// Test default configuration plus class specific override.
|
||||
$data['default-plus-class-override'] = [
|
||||
['default' => ['class' => '\stdClass']],
|
||||
['class' => CustomFileCache::class],
|
||||
CustomFileCache::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' => CustomFileCache::class],
|
||||
],
|
||||
['class' => '\stdClass'],
|
||||
CustomFileCache::class,
|
||||
];
|
||||
|
||||
return $data;
|
||||
}
|
||||
|
||||
/**
|
||||
* @covers ::getConfiguration
|
||||
* @covers ::setConfiguration
|
||||
*/
|
||||
public function testGetSetConfiguration(): void {
|
||||
$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(): void {
|
||||
// Random generator.
|
||||
$random = new Random();
|
||||
|
||||
$prefix = $random->name(8, TRUE);
|
||||
FileCacheFactory::setPrefix($prefix);
|
||||
$this->assertEquals($prefix, FileCacheFactory::getPrefix());
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
/**
|
||||
* Class to help test the file cache class.
|
||||
*/
|
||||
class CustomFileCache extends FileCache {}
|
||||
@ -0,0 +1,144 @@
|
||||
<?php
|
||||
|
||||
declare(strict_types=1);
|
||||
|
||||
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(): void {
|
||||
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(): void {
|
||||
// Test a cache miss.
|
||||
$result = $this->fileCache->get(__DIR__ . DIRECTORY_SEPARATOR . 'Fixtures' . DIRECTORY_SEPARATOR . 'no-llama-42.yml');
|
||||
$this->assertNull($result);
|
||||
|
||||
// Test a cache hit.
|
||||
$filename = __DIR__ . DIRECTORY_SEPARATOR . 'Fixtures' . DIRECTORY_SEPARATOR . '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(): void {
|
||||
// Test a cache miss.
|
||||
$result = $this->fileCache->getMultiple([__DIR__ . DIRECTORY_SEPARATOR . 'Fixtures' . DIRECTORY_SEPARATOR . 'no-llama-42.yml']);
|
||||
$this->assertEmpty($result);
|
||||
|
||||
// Test a cache hit.
|
||||
$filename = __DIR__ . DIRECTORY_SEPARATOR . 'Fixtures' . DIRECTORY_SEPARATOR . '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__ . DIRECTORY_SEPARATOR . 'Fixtures' . DIRECTORY_SEPARATOR . '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(): void {
|
||||
$filename = __DIR__ . DIRECTORY_SEPARATOR . 'Fixtures' . DIRECTORY_SEPARATOR . '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(): void {
|
||||
$filename = __DIR__ . DIRECTORY_SEPARATOR . 'Fixtures' . DIRECTORY_SEPARATOR . '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 @@
|
||||
23
|
||||
@ -0,0 +1 @@
|
||||
42
|
||||
@ -0,0 +1,73 @@
|
||||
<?php
|
||||
|
||||
declare(strict_types=1);
|
||||
|
||||
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 = $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,66 @@
|
||||
<?php
|
||||
|
||||
declare(strict_types=1);
|
||||
|
||||
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(): void {
|
||||
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->assertStringContainsString("Require all denied", $htaccess_contents);
|
||||
}
|
||||
|
||||
/**
|
||||
* @covers ::writeHtaccess
|
||||
*/
|
||||
public function testWriteHtaccessPublic(): void {
|
||||
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->assertStringNotContainsString("Require all denied", $htaccess_contents);
|
||||
}
|
||||
|
||||
/**
|
||||
* @covers ::writeHtaccess
|
||||
*/
|
||||
public function testWriteHtaccessForceOverwrite(): void {
|
||||
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->assertStringContainsString("Require all denied", $htaccess_contents);
|
||||
$this->assertStringNotContainsString("foo", $htaccess_contents);
|
||||
}
|
||||
|
||||
/**
|
||||
* @covers ::writeHtaccess
|
||||
*/
|
||||
public function testWriteHtaccessFailure(): void {
|
||||
vfsStream::setup('root');
|
||||
$this->assertFalse(FileSecurity::writeHtaccess(vfsStream::url('root') . '/foo'));
|
||||
}
|
||||
|
||||
}
|
||||
@ -0,0 +1,111 @@
|
||||
<?php
|
||||
|
||||
declare(strict_types=1);
|
||||
|
||||
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): void {
|
||||
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 static 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,174 @@
|
||||
<?php
|
||||
|
||||
declare(strict_types=1);
|
||||
|
||||
namespace Drupal\Tests\Component\FrontMatter;
|
||||
|
||||
use Drupal\Component\FrontMatter\Exception\FrontMatterParseException;
|
||||
use Drupal\Component\FrontMatter\FrontMatter;
|
||||
use Drupal\Component\Serialization\Yaml;
|
||||
use PHPUnit\Framework\TestCase;
|
||||
|
||||
/**
|
||||
* Tests front matter parsing helper methods.
|
||||
*
|
||||
* @group FrontMatter
|
||||
*
|
||||
* @coversDefaultClass \Drupal\Component\FrontMatter\FrontMatter
|
||||
*/
|
||||
class FrontMatterTest extends TestCase {
|
||||
|
||||
/**
|
||||
* A basic source string.
|
||||
*/
|
||||
const SOURCE = '<div>Hello world</div>';
|
||||
|
||||
/**
|
||||
* Creates a front matter source string.
|
||||
*
|
||||
* @param array|null $yaml
|
||||
* The YAML array to prepend as a front matter block.
|
||||
* @param string $content
|
||||
* The source contents.
|
||||
*
|
||||
* @return string
|
||||
* The new source.
|
||||
*/
|
||||
public static function createFrontMatterSource(?array $yaml, string $content = self::SOURCE): string {
|
||||
// Encode YAML and wrap in a front matter block.
|
||||
$frontMatter = '';
|
||||
if (is_array($yaml)) {
|
||||
$yaml = $yaml ? trim(Yaml::encode($yaml)) . "\n" : '';
|
||||
$frontMatter = FrontMatter::SEPARATOR . "\n$yaml" . FrontMatter::SEPARATOR . "\n";
|
||||
}
|
||||
return $frontMatter . $content;
|
||||
}
|
||||
|
||||
/**
|
||||
* Tests when a passed serializer doesn't implement the proper interface.
|
||||
*
|
||||
* @covers ::__construct
|
||||
* @covers ::create
|
||||
*/
|
||||
public function testFrontMatterSerializerException(): void {
|
||||
$this->expectException(\AssertionError::class);
|
||||
$this->expectExceptionMessage('The $serializer parameter must reference a class that implements Drupal\Component\Serialization\SerializationInterface.');
|
||||
FrontMatter::create('', '');
|
||||
}
|
||||
|
||||
/**
|
||||
* Tests broken front matter.
|
||||
*
|
||||
* @covers ::__construct
|
||||
* @covers ::create
|
||||
* @covers ::parse
|
||||
* @covers \Drupal\Component\FrontMatter\Exception\FrontMatterParseException
|
||||
*/
|
||||
public function testFrontMatterBroken(): void {
|
||||
$this->expectException(FrontMatterParseException::class);
|
||||
$this->expectExceptionMessage('An error occurred when attempting to parse front matter data on line 4');
|
||||
$source = "---\ncollection:\n- key: foo\n foo: bar\n---\n";
|
||||
FrontMatter::create($source)->getData();
|
||||
}
|
||||
|
||||
/**
|
||||
* Tests the parsed data from front matter.
|
||||
*
|
||||
* @param array|null $yaml
|
||||
* The YAML used as front matter data to prepend the source.
|
||||
* @param int $line
|
||||
* The expected line number where the source code starts.
|
||||
* @param string $content
|
||||
* The content to use for testing purposes.
|
||||
*
|
||||
* @covers ::__construct
|
||||
* @covers ::getContent
|
||||
* @covers ::getData
|
||||
* @covers ::getLine
|
||||
* @covers ::create
|
||||
* @covers ::parse
|
||||
*
|
||||
* @dataProvider providerFrontMatterData
|
||||
*/
|
||||
public function testFrontMatterData($yaml, $line, $content = self::SOURCE): void {
|
||||
$source = static::createFrontMatterSource($yaml, $content);
|
||||
$frontMatter = FrontMatter::create($source);
|
||||
$this->assertEquals($content, $frontMatter->getContent());
|
||||
$this->assertEquals($yaml ?? [], $frontMatter->getData());
|
||||
$this->assertEquals($line, $frontMatter->getLine());
|
||||
}
|
||||
|
||||
/**
|
||||
* Provides the front matter data to test.
|
||||
*
|
||||
* @return array
|
||||
* Array of front matter data.
|
||||
*/
|
||||
public static function providerFrontMatterData() {
|
||||
$data['none'] = [
|
||||
'yaml' => NULL,
|
||||
'line' => 1,
|
||||
];
|
||||
$data['scalar'] = [
|
||||
'yaml' => [
|
||||
'string' => 'value',
|
||||
'number' => 42,
|
||||
'bool' => TRUE,
|
||||
'null' => NULL,
|
||||
],
|
||||
'line' => 7,
|
||||
];
|
||||
$data['indexed_arrays'] = [
|
||||
'yaml' => [
|
||||
'brackets' => [1, 2, 3],
|
||||
'items' => [
|
||||
'item1',
|
||||
'item2',
|
||||
'item3',
|
||||
],
|
||||
],
|
||||
'line' => 11,
|
||||
];
|
||||
$data['associative_arrays'] = [
|
||||
'yaml' => [
|
||||
'brackets' => [
|
||||
'a' => 1,
|
||||
'b' => 2,
|
||||
'c' => 3,
|
||||
],
|
||||
'items' => [
|
||||
'a' => 'item1',
|
||||
'b' => 'item2',
|
||||
'c' => 'item3',
|
||||
],
|
||||
],
|
||||
'line' => 11,
|
||||
];
|
||||
$data['empty_data'] = [
|
||||
'yaml' => [],
|
||||
'line' => 3,
|
||||
];
|
||||
$data['empty_content'] = [
|
||||
'yaml' => ['key' => 'value'],
|
||||
'line' => 4,
|
||||
'content' => '',
|
||||
];
|
||||
$data['empty_data_and_content'] = [
|
||||
'yaml' => [],
|
||||
'line' => 3,
|
||||
'content' => '',
|
||||
];
|
||||
$data['empty_string'] = [
|
||||
'yaml' => NULL,
|
||||
'line' => 1,
|
||||
'content' => '',
|
||||
];
|
||||
$data['multiple_separators'] = [
|
||||
'yaml' => ['key' => '---'],
|
||||
'line' => 4,
|
||||
'content' => "Something\n---\nSomething more",
|
||||
];
|
||||
return $data;
|
||||
}
|
||||
|
||||
}
|
||||
372
web/core/tests/Drupal/Tests/Component/Gettext/PoHeaderTest.php
Normal file
372
web/core/tests/Drupal/Tests/Component/Gettext/PoHeaderTest.php
Normal file
@ -0,0 +1,372 @@
|
||||
<?php
|
||||
|
||||
declare(strict_types=1);
|
||||
|
||||
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): void {
|
||||
$p = new PoHeader();
|
||||
[, $new_plural] = $p->parsePluralForms($plural);
|
||||
foreach ($expected as $number => $plural_form) {
|
||||
$result = $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 static 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,117 @@
|
||||
<?php
|
||||
|
||||
declare(strict_types=1);
|
||||
|
||||
namespace Drupal\Tests\Component\Gettext;
|
||||
|
||||
use Drupal\Component\Gettext\PoHeader;
|
||||
use Drupal\Component\Gettext\PoItem;
|
||||
use Drupal\Component\Gettext\PoStreamWriter;
|
||||
use org\bovigo\vfs\vfsStream;
|
||||
use org\bovigo\vfs\vfsStreamFile;
|
||||
use PHPUnit\Framework\TestCase;
|
||||
use Prophecy\PhpUnit\ProphecyTrait;
|
||||
|
||||
/**
|
||||
* @coversDefaultClass \Drupal\Component\Gettext\PoStreamWriter
|
||||
* @group Gettext
|
||||
*/
|
||||
class PoStreamWriterTest extends TestCase {
|
||||
|
||||
use ProphecyTrait;
|
||||
|
||||
/**
|
||||
* 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(): void {
|
||||
parent::setUp();
|
||||
|
||||
$poHeader = $this->prophesize(PoHeader::class);
|
||||
$poHeader->__toString()->willReturn('');
|
||||
$this->poWriter = new PoStreamWriter();
|
||||
$this->poWriter->setHeader($poHeader->reveal());
|
||||
|
||||
$root = vfsStream::setup();
|
||||
$this->poFile = new vfsStreamFile('poWriter.po');
|
||||
$root->addChild($this->poFile);
|
||||
}
|
||||
|
||||
/**
|
||||
* @covers ::getURI
|
||||
*/
|
||||
public function testGetUriException(): void {
|
||||
$this->expectException(\Exception::class);
|
||||
$this->expectExceptionMessage('No URI set.');
|
||||
|
||||
$this->poWriter->getURI();
|
||||
}
|
||||
|
||||
/**
|
||||
* @covers ::writeItem
|
||||
* @dataProvider providerWriteData
|
||||
*/
|
||||
public function testWriteItem($poContent, $expected, $long): void {
|
||||
if ($long) {
|
||||
$this->expectException(\Exception::class);
|
||||
$this->expectExceptionMessage('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 static function providerWriteData() {
|
||||
// cSpell:disable
|
||||
return [
|
||||
['', '', FALSE],
|
||||
["\r\n", "\r\n", FALSE],
|
||||
['write this if you can', 'write this', TRUE],
|
||||
['éáíó>&', 'éáíó>&', FALSE],
|
||||
['éáíó>&<', 'éáíó>&', TRUE],
|
||||
['中文 890', '中文 890', FALSE],
|
||||
['中文 89012', '中文 890', TRUE],
|
||||
];
|
||||
// cSpell:enable
|
||||
}
|
||||
|
||||
/**
|
||||
* @covers ::close
|
||||
*/
|
||||
public function testCloseException(): void {
|
||||
$this->expectException(\Exception::class);
|
||||
$this->expectExceptionMessage('Cannot close stream that is not open.');
|
||||
|
||||
$this->poWriter->close();
|
||||
}
|
||||
|
||||
}
|
||||
204
web/core/tests/Drupal/Tests/Component/Graph/GraphTest.php
Normal file
204
web/core/tests/Drupal/Tests/Component/Graph/GraphTest.php
Normal file
@ -0,0 +1,204 @@
|
||||
<?php
|
||||
|
||||
declare(strict_types=1);
|
||||
|
||||
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 {
|
||||
|
||||
/**
|
||||
* Tests depth-first-search features.
|
||||
*/
|
||||
public function testDepthFirstSearch(): void {
|
||||
// The sample graph used is:
|
||||
// @code
|
||||
// 1 --> 2 --> 3 5 ---> 6
|
||||
// | ^ ^
|
||||
// | | |
|
||||
// | | |
|
||||
// +---> 4 <-- 7 8 ---> 9
|
||||
// @endcode
|
||||
$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 array $graph
|
||||
* A graph array processed by \Drupal\Component\Graph\Graph::searchAndSort()
|
||||
*
|
||||
* @return array
|
||||
* The normalized version of a graph.
|
||||
*/
|
||||
protected function normalizeGraph($graph): array {
|
||||
$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 array $graph
|
||||
* A graph array processed by \Drupal\Component\Graph\Graph::searchAndSort()
|
||||
* @param array $expected_paths
|
||||
* An associative array containing vertices with their expected paths.
|
||||
*
|
||||
* @internal
|
||||
*/
|
||||
protected function assertPaths(array $graph, array $expected_paths): void {
|
||||
foreach ($expected_paths as $vertex => $paths) {
|
||||
// Build an array with keys = $paths and values = TRUE.
|
||||
$expected = array_fill_keys($paths, TRUE);
|
||||
$result = $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 array $graph
|
||||
* A graph array processed by \Drupal\Component\Graph\Graph::searchAndSort()
|
||||
* @param array $expected_reverse_paths
|
||||
* An associative array containing vertices with their expected reverse
|
||||
* paths.
|
||||
*
|
||||
* @internal
|
||||
*/
|
||||
protected function assertReversePaths(array $graph, array $expected_reverse_paths): void {
|
||||
foreach ($expected_reverse_paths as $vertex => $paths) {
|
||||
// Build an array with keys = $paths and values = TRUE.
|
||||
$expected = array_fill_keys($paths, TRUE);
|
||||
$result = $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 array $graph
|
||||
* A graph array processed by
|
||||
* \Drupal\Component\Graph\Graph::searchAndSort().
|
||||
* @param array $expected_components
|
||||
* An array containing of components defined as a list of their vertices.
|
||||
*
|
||||
* @internal
|
||||
*/
|
||||
protected function assertComponents(array $graph, array $expected_components): void {
|
||||
$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 array $graph
|
||||
* A graph array processed by \Drupal\Component\Graph\Graph::searchAndSort()
|
||||
* @param array $expected_orders
|
||||
* An array containing lists of vertices in their expected order.
|
||||
*
|
||||
* @internal
|
||||
*/
|
||||
protected function assertWeights(array $graph, array $expected_orders): void {
|
||||
foreach ($expected_orders as $order) {
|
||||
$previous_vertex = array_shift($order);
|
||||
foreach ($order as $vertex) {
|
||||
$this->assertLessThan($graph[$vertex]['weight'], $graph[$previous_vertex]['weight'], sprintf("Weight of vertex %s should be less than vertex %s.", $previous_vertex, $vertex));
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Helper function to output vertices as comma-separated list.
|
||||
*
|
||||
* @param array $paths
|
||||
* An array containing a list of vertices.
|
||||
* @param bool $keys
|
||||
* (optional) Whether to output the keys of $paths instead of the values.
|
||||
*/
|
||||
protected function displayArray($paths, $keys = FALSE): string {
|
||||
if (!empty($paths)) {
|
||||
return implode(', ', $keys ? array_keys($paths) : $paths);
|
||||
}
|
||||
else {
|
||||
return '(empty)';
|
||||
}
|
||||
}
|
||||
|
||||
}
|
||||
@ -0,0 +1,61 @@
|
||||
<?php
|
||||
|
||||
declare(strict_types=1);
|
||||
|
||||
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 {
|
||||
|
||||
/**
|
||||
* Tests copying of redirect response.
|
||||
*
|
||||
* @covers ::createFromRedirectResponse
|
||||
* @covers ::fromResponse
|
||||
*/
|
||||
public function testRedirectCopy(): void {
|
||||
$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->all();
|
||||
$headers2 = $secureRedirect->headers->all();
|
||||
$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());
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
/**
|
||||
* Test class for safe redirects.
|
||||
*/
|
||||
class SecuredRedirectStub extends SecuredRedirectResponse {
|
||||
|
||||
/**
|
||||
* {@inheritdoc}
|
||||
*/
|
||||
protected function isSafe($url): bool {
|
||||
// Empty implementation for testing.
|
||||
return TRUE;
|
||||
}
|
||||
|
||||
}
|
||||
@ -0,0 +1,111 @@
|
||||
<?php
|
||||
|
||||
declare(strict_types=1);
|
||||
|
||||
namespace Drupal\Tests\Component\PhpStorage;
|
||||
|
||||
use Drupal\Component\PhpStorage\FileStorage;
|
||||
use Drupal\Component\PhpStorage\FileReadOnlyStorage;
|
||||
use Drupal\Component\Utility\Random;
|
||||
use Drupal\TestTools\Extension\DeprecationBridge\ExpectDeprecationTrait;
|
||||
|
||||
/**
|
||||
* @coversDefaultClass \Drupal\Component\PhpStorage\FileReadOnlyStorage
|
||||
*
|
||||
* @group Drupal
|
||||
* @group PhpStorage
|
||||
*/
|
||||
class FileStorageReadOnlyTest extends PhpStorageTestBase {
|
||||
|
||||
use ExpectDeprecationTrait;
|
||||
|
||||
/**
|
||||
* 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(): void {
|
||||
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(): void {
|
||||
// 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 = 'test' . 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);
|
||||
$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->assertTrue($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 ::deleteAll
|
||||
*/
|
||||
public function testDeleteAll(): void {
|
||||
// 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,115 @@
|
||||
<?php
|
||||
|
||||
declare(strict_types=1);
|
||||
|
||||
namespace Drupal\Tests\Component\PhpStorage;
|
||||
|
||||
use Drupal\Component\PhpStorage\FileStorage;
|
||||
use Drupal\Component\Utility\Random;
|
||||
use Drupal\TestTools\Extension\DeprecationBridge\ExpectDeprecationTrait;
|
||||
use org\bovigo\vfs\vfsStreamDirectory;
|
||||
|
||||
/**
|
||||
* @coversDefaultClass \Drupal\Component\PhpStorage\FileStorage
|
||||
* @group Drupal
|
||||
* @group PhpStorage
|
||||
*/
|
||||
class FileStorageTest extends PhpStorageTestBase {
|
||||
|
||||
use ExpectDeprecationTrait;
|
||||
|
||||
/**
|
||||
* Standard test settings to pass to storage instances.
|
||||
*
|
||||
* @var array
|
||||
*/
|
||||
protected $standardSettings;
|
||||
|
||||
/**
|
||||
* {@inheritdoc}
|
||||
*/
|
||||
protected function setUp(): void {
|
||||
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(): void {
|
||||
$php = new FileStorage($this->standardSettings);
|
||||
$this->assertCRUD($php);
|
||||
}
|
||||
|
||||
/**
|
||||
* @covers ::deleteAll
|
||||
*/
|
||||
public function testDeleteAll(): void {
|
||||
// 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 = 'test' . 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->assertDirectoryDoesNotExist($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(): void {
|
||||
$directory = new vfsStreamDirectory('permissionDenied', 0200);
|
||||
$storage = new FileStorage([
|
||||
'directory' => $directory->url(),
|
||||
'bin' => 'test',
|
||||
]);
|
||||
$code = "<?php\n echo 'here';";
|
||||
|
||||
// PHPUnit 10 cannot expect warnings, so we have to catch them ourselves.
|
||||
$messages = [];
|
||||
set_error_handler(function (int $errno, string $errstr) use (&$messages): void {
|
||||
$messages[] = [$errno, $errstr];
|
||||
});
|
||||
|
||||
$storage->save('subdirectory/foo.php', $code);
|
||||
|
||||
restore_error_handler();
|
||||
$this->assertCount(2, $messages);
|
||||
$this->assertSame(E_USER_WARNING, $messages[0][0]);
|
||||
$this->assertSame('mkdir(): Permission Denied', $messages[0][1]);
|
||||
$this->assertSame(E_WARNING, $messages[1][0]);
|
||||
$this->assertStringStartsWith('file_put_contents(vfs://permissionDenied/test/subdirectory/foo.php)', $messages[1][1]);
|
||||
}
|
||||
|
||||
}
|
||||
@ -0,0 +1,35 @@
|
||||
<?php
|
||||
|
||||
declare(strict_types=1);
|
||||
|
||||
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.
|
||||
*
|
||||
* @var bool[]
|
||||
*/
|
||||
protected array $expected = [TRUE, FALSE];
|
||||
|
||||
/**
|
||||
* The PHP storage class to test.
|
||||
*
|
||||
* @var class-string
|
||||
*/
|
||||
protected $storageClass = 'Drupal\Component\PhpStorage\MTimeProtectedFastFileStorage';
|
||||
|
||||
}
|
||||
@ -0,0 +1,137 @@
|
||||
<?php
|
||||
|
||||
declare(strict_types=1);
|
||||
|
||||
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;
|
||||
|
||||
/**
|
||||
* The expected test results for the security test.
|
||||
*/
|
||||
protected array $expected;
|
||||
|
||||
/**
|
||||
* {@inheritdoc}
|
||||
*/
|
||||
protected function setUp(): void {
|
||||
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.
|
||||
*/
|
||||
public function testCRUD(): void {
|
||||
$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(): void {
|
||||
$php = new $this->storageClass($this->settings);
|
||||
$name = 'test.php';
|
||||
$php->save($name, '<?php');
|
||||
$expected_root_directory = $this->directory . '/test';
|
||||
if (str_ends_with($name, '.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,34 @@
|
||||
<?php
|
||||
|
||||
declare(strict_types=1);
|
||||
|
||||
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.
|
||||
*
|
||||
* @var bool[]
|
||||
*/
|
||||
protected array $expected = [FALSE, FALSE];
|
||||
|
||||
/**
|
||||
* The PHP storage class to test.
|
||||
*
|
||||
* @var class-string
|
||||
*/
|
||||
protected $storageClass = 'Drupal\Component\PhpStorage\MTimeProtectedFileStorage';
|
||||
|
||||
}
|
||||
@ -0,0 +1,84 @@
|
||||
<?php
|
||||
|
||||
declare(strict_types=1);
|
||||
|
||||
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(): void {
|
||||
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 = 'test' . 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,50 @@
|
||||
<?php
|
||||
|
||||
declare(strict_types=1);
|
||||
|
||||
namespace Drupal\Tests\Component\Plugin\Attribute;
|
||||
|
||||
use Drupal\Component\Plugin\Attribute\AttributeBase;
|
||||
use PHPUnit\Framework\TestCase;
|
||||
|
||||
/**
|
||||
* @coversDefaultClass \Drupal\Component\Plugin\Attribute\AttributeBase
|
||||
* @group Attribute
|
||||
*/
|
||||
class AttributeBaseTest extends TestCase {
|
||||
|
||||
/**
|
||||
* @covers ::getProvider
|
||||
* @covers ::setProvider
|
||||
*/
|
||||
public function testSetProvider(): void {
|
||||
$plugin = new AttributeBaseStub(id: '1');
|
||||
$plugin->setProvider('example');
|
||||
$this->assertEquals('example', $plugin->getProvider());
|
||||
}
|
||||
|
||||
/**
|
||||
* @covers ::getId
|
||||
*/
|
||||
public function testGetId(): void {
|
||||
$plugin = new AttributeBaseStub(id: 'example');
|
||||
$this->assertEquals('example', $plugin->getId());
|
||||
}
|
||||
|
||||
/**
|
||||
* @covers ::getClass
|
||||
* @covers ::setClass
|
||||
*/
|
||||
public function testSetClass(): void {
|
||||
$plugin = new AttributeBaseStub(id: '1');
|
||||
$plugin->setClass('example');
|
||||
$this->assertEquals('example', $plugin->getClass());
|
||||
}
|
||||
|
||||
}
|
||||
/**
|
||||
* {@inheritdoc}
|
||||
*/
|
||||
class AttributeBaseStub extends AttributeBase {
|
||||
|
||||
}
|
||||
@ -0,0 +1,156 @@
|
||||
<?php
|
||||
|
||||
declare(strict_types=1);
|
||||
|
||||
namespace Drupal\Tests\Component\Plugin\Attribute;
|
||||
|
||||
use Composer\Autoload\ClassLoader;
|
||||
use Drupal\Component\Plugin\Discovery\AttributeClassDiscovery;
|
||||
use Drupal\Component\FileCache\FileCacheFactory;
|
||||
use PHPUnit\Framework\TestCase;
|
||||
|
||||
/**
|
||||
* @coversDefaultClass \Drupal\Component\Plugin\Discovery\AttributeClassDiscovery
|
||||
* @covers \Drupal\Component\Discovery\MissingClassDetectionClassLoader
|
||||
* @group Attribute
|
||||
* @runTestsInSeparateProcesses
|
||||
*/
|
||||
class AttributeClassDiscoveryCachedTest extends TestCase {
|
||||
|
||||
/**
|
||||
* {@inheritdoc}
|
||||
*/
|
||||
protected function setUp(): void {
|
||||
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');
|
||||
|
||||
// Normally the attribute classes would be autoloaded.
|
||||
include_once __DIR__ . '/../../../../../fixtures/plugins/CustomPlugin.php';
|
||||
|
||||
$additionalClassLoader = new ClassLoader();
|
||||
$additionalClassLoader->addPsr4("com\\example\\PluginNamespace\\", __DIR__ . "/../../../../../fixtures/plugins/Plugin/PluginNamespace");
|
||||
$additionalClassLoader->register(TRUE);
|
||||
}
|
||||
|
||||
/**
|
||||
* Tests that getDefinitions() retrieves the file cache correctly.
|
||||
*
|
||||
* @covers ::getDefinitions
|
||||
*/
|
||||
public function testGetDefinitions(): void {
|
||||
// Path to the classes which we'll discover and parse annotation.
|
||||
$discovery_path = __DIR__ . "/../../../../../fixtures/plugins/Plugin";
|
||||
// File path that should be discovered within that directory.
|
||||
$file_path = $discovery_path . '/PluginNamespace/AttributeDiscoveryTest1.php';
|
||||
// Define file paths within the directory that should not be discovered.
|
||||
$non_discoverable_file_paths = [
|
||||
$discovery_path . '/PluginNamespace/AttributeDiscoveryTest2.php',
|
||||
$discovery_path . '/PluginNamespace/AttributeDiscoveryTestMissingInterface.php',
|
||||
$discovery_path . '/PluginNamespace/AttributeDiscoveryTestMissingTrait.php',
|
||||
];
|
||||
|
||||
$discovery = new AttributeClassDiscovery(['com\example' => [$discovery_path]]);
|
||||
$this->assertEquals([
|
||||
'discovery_test_1' => [
|
||||
'id' => 'discovery_test_1',
|
||||
'class' => 'com\example\PluginNamespace\AttributeDiscoveryTest1',
|
||||
],
|
||||
], $discovery->getDefinitions());
|
||||
|
||||
// Gain access to the file cache.
|
||||
$ref_file_cache = new \ReflectionProperty($discovery, 'fileCache');
|
||||
/** @var \Drupal\Component\FileCache\FileCacheInterface $file_cache */
|
||||
$file_cache = $ref_file_cache->getValue($discovery);
|
||||
|
||||
// The valid plugin definition should be cached.
|
||||
$this->assertEquals([
|
||||
'id' => 'discovery_test_1',
|
||||
'class' => 'com\example\PluginNamespace\AttributeDiscoveryTest1',
|
||||
], unserialize($file_cache->get($file_path)['content']));
|
||||
|
||||
// The plugins that extend a missing class, implement a missing interface,
|
||||
// and use a missing trait should not be cached.
|
||||
foreach ($non_discoverable_file_paths as $non_discoverable_file_path) {
|
||||
$this->assertTrue(file_exists($non_discoverable_file_path));
|
||||
$this->assertNull($file_cache->get($non_discoverable_file_path));
|
||||
}
|
||||
|
||||
// Change the file cache entry.
|
||||
// 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());
|
||||
}
|
||||
|
||||
/**
|
||||
* Tests discovery with missing traits.
|
||||
*
|
||||
* @covers ::getDefinitions
|
||||
*/
|
||||
public function testGetDefinitionsMissingTrait(): void {
|
||||
// Path to the classes which we'll discover and parse annotation.
|
||||
$discovery_path = __DIR__ . "/../../../../../fixtures/plugins/Plugin";
|
||||
// Define file paths within the directory that should not be discovered.
|
||||
$non_discoverable_file_paths = [
|
||||
$discovery_path . '/PluginNamespace/AttributeDiscoveryTest2.php',
|
||||
$discovery_path . '/PluginNamespace/AttributeDiscoveryTestMissingInterface.php',
|
||||
$discovery_path . '/PluginNamespace/AttributeDiscoveryTestMissingTrait.php',
|
||||
];
|
||||
|
||||
$discovery = new AttributeClassDiscovery(['com\example' => [$discovery_path]]);
|
||||
$this->assertEquals([
|
||||
'discovery_test_1' => [
|
||||
'id' => 'discovery_test_1',
|
||||
'class' => 'com\example\PluginNamespace\AttributeDiscoveryTest1',
|
||||
],
|
||||
], $discovery->getDefinitions());
|
||||
|
||||
// Gain access to the file cache.
|
||||
$ref_file_cache = new \ReflectionProperty($discovery, 'fileCache');
|
||||
/** @var \Drupal\Component\FileCache\FileCacheInterface $file_cache */
|
||||
$file_cache = $ref_file_cache->getValue($discovery);
|
||||
|
||||
// The plugins that extend a missing class, implement a missing interface,
|
||||
// and use a missing trait should not be cached.
|
||||
foreach ($non_discoverable_file_paths as $non_discoverable_file_path) {
|
||||
$this->assertTrue(file_exists($non_discoverable_file_path));
|
||||
$this->assertNull($file_cache->get($non_discoverable_file_path));
|
||||
}
|
||||
|
||||
$discovery = new AttributeClassDiscovery(['com\example' => [$discovery_path], 'Drupal\a_module_that_does_not_exist' => [$discovery_path]]);
|
||||
$this->assertEquals([
|
||||
'discovery_test_1' => [
|
||||
'id' => 'discovery_test_1',
|
||||
'class' => 'com\example\PluginNamespace\AttributeDiscoveryTest1',
|
||||
],
|
||||
'discovery_test_missing_trait' => [
|
||||
'id' => 'discovery_test_missing_trait',
|
||||
'class' => 'com\example\PluginNamespace\AttributeDiscoveryTestMissingTrait',
|
||||
'title' => 'Discovery test plugin missing trait',
|
||||
],
|
||||
], $discovery->getDefinitions());
|
||||
|
||||
// The plugins that extend a missing class, implement a missing interface,
|
||||
// and use a missing trait should not be cached. This is the case even for
|
||||
// the plugin that was just discovered.
|
||||
foreach ($non_discoverable_file_paths as $non_discoverable_file_path) {
|
||||
$this->assertTrue(file_exists($non_discoverable_file_path));
|
||||
$this->assertNull($file_cache->get($non_discoverable_file_path));
|
||||
}
|
||||
}
|
||||
|
||||
}
|
||||
@ -0,0 +1,78 @@
|
||||
<?php
|
||||
|
||||
declare(strict_types=1);
|
||||
|
||||
namespace Drupal\Tests\Component\Plugin\Attribute;
|
||||
|
||||
use Composer\Autoload\ClassLoader;
|
||||
use Drupal\Component\Plugin\Discovery\AttributeClassDiscovery;
|
||||
use Drupal\Component\FileCache\FileCacheFactory;
|
||||
use PHPUnit\Framework\TestCase;
|
||||
|
||||
/**
|
||||
* @coversDefaultClass \Drupal\Component\Plugin\Discovery\AttributeClassDiscovery
|
||||
* @group Attribute
|
||||
* @runTestsInSeparateProcesses
|
||||
*/
|
||||
class AttributeClassDiscoveryTest extends TestCase {
|
||||
|
||||
/**
|
||||
* {@inheritdoc}
|
||||
*/
|
||||
protected function setUp(): void {
|
||||
parent::setUp();
|
||||
|
||||
// Ensure the file cache is disabled.
|
||||
FileCacheFactory::setConfiguration([FileCacheFactory::DISABLE_CACHE => TRUE]);
|
||||
// Ensure that FileCacheFactory has a prefix.
|
||||
FileCacheFactory::setPrefix('prefix');
|
||||
|
||||
// Normally the attribute classes would be autoloaded.
|
||||
include_once __DIR__ . '/../../../../../fixtures/plugins/CustomPlugin.php';
|
||||
|
||||
$additionalClassLoader = new ClassLoader();
|
||||
$additionalClassLoader->addPsr4("com\\example\\PluginNamespace\\", __DIR__ . "/../../../../../fixtures/plugins/Plugin/PluginNamespace");
|
||||
$additionalClassLoader->register(TRUE);
|
||||
}
|
||||
|
||||
/**
|
||||
* @covers ::__construct
|
||||
* @covers ::getPluginNamespaces
|
||||
*/
|
||||
public function testGetPluginNamespaces(): void {
|
||||
// Path to the classes which we'll discover and parse annotation.
|
||||
$discovery = new AttributeClassDiscovery(['com/example' => [__DIR__]]);
|
||||
|
||||
$reflection = new \ReflectionMethod($discovery, 'getPluginNamespaces');
|
||||
|
||||
$result = $reflection->invoke($discovery);
|
||||
$this->assertEquals(['com/example' => [__DIR__]], $result);
|
||||
}
|
||||
|
||||
/**
|
||||
* @covers ::getDefinitions
|
||||
* @covers ::prepareAttributeDefinition
|
||||
*/
|
||||
public function testGetDefinitions(): void {
|
||||
$discovery = new AttributeClassDiscovery(['com\example' => [__DIR__ . "/../../../../../fixtures/plugins/Plugin"]]);
|
||||
$this->assertEquals([
|
||||
'discovery_test_1' => [
|
||||
'id' => 'discovery_test_1',
|
||||
'class' => 'com\example\PluginNamespace\AttributeDiscoveryTest1',
|
||||
],
|
||||
], $discovery->getDefinitions());
|
||||
|
||||
$custom_annotation_discovery = new AttributeClassDiscovery(['com\example' => [__DIR__ . "/../../../../../fixtures/plugins/Plugin"]], 'com\example\PluginNamespace\CustomPlugin');
|
||||
$this->assertEquals([
|
||||
'discovery_test_1' => [
|
||||
'id' => 'discovery_test_1',
|
||||
'class' => 'com\example\PluginNamespace\AttributeDiscoveryTest1',
|
||||
'title' => 'Discovery test plugin',
|
||||
],
|
||||
], $custom_annotation_discovery->getDefinitions());
|
||||
|
||||
$empty_discovery = new AttributeClassDiscovery(['com\example' => [__DIR__ . "/../../../../../fixtures/plugins/Plugin"]], 'com\example\PluginNamespace\CustomPlugin2');
|
||||
$this->assertEquals([], $empty_discovery->getDefinitions());
|
||||
}
|
||||
|
||||
}
|
||||
@ -0,0 +1,40 @@
|
||||
<?php
|
||||
|
||||
declare(strict_types=1);
|
||||
|
||||
namespace Drupal\Tests\Component\Plugin\Attribute;
|
||||
|
||||
use Drupal\Component\Plugin\Attribute\PluginID;
|
||||
use PHPUnit\Framework\TestCase;
|
||||
|
||||
/**
|
||||
* @coversDefaultClass \Drupal\Component\Plugin\Attribute\PluginID
|
||||
* @group Attribute
|
||||
*/
|
||||
class PluginIdTest extends TestCase {
|
||||
|
||||
/**
|
||||
* @covers ::get
|
||||
*/
|
||||
public function testGet(): void {
|
||||
// Assert plugin starts with only an ID.
|
||||
$plugin = new PluginID(id: 'test');
|
||||
// Plugin's always have a class set by discovery.
|
||||
$plugin->setClass('bar');
|
||||
$this->assertEquals([
|
||||
'id' => 'test',
|
||||
'class' => 'bar',
|
||||
'provider' => NULL,
|
||||
], $plugin->get());
|
||||
|
||||
// Set values and ensure we can retrieve them.
|
||||
$plugin->setClass('bar2');
|
||||
$plugin->setProvider('baz');
|
||||
$this->assertEquals([
|
||||
'id' => 'test',
|
||||
'class' => 'bar2',
|
||||
'provider' => 'baz',
|
||||
], $plugin->get());
|
||||
}
|
||||
|
||||
}
|
||||
@ -0,0 +1,65 @@
|
||||
<?php
|
||||
|
||||
declare(strict_types=1);
|
||||
|
||||
namespace Drupal\Tests\Component\Plugin\Attribute;
|
||||
|
||||
use Drupal\Component\Plugin\Attribute\Plugin;
|
||||
use PHPUnit\Framework\TestCase;
|
||||
|
||||
/**
|
||||
* @coversDefaultClass \Drupal\Component\Plugin\Attribute\Plugin
|
||||
* @group Attribute
|
||||
*/
|
||||
class PluginTest extends TestCase {
|
||||
|
||||
/**
|
||||
* @covers ::__construct
|
||||
* @covers ::get
|
||||
*/
|
||||
public function testGet(): void {
|
||||
$plugin = new PluginStub(id: 'example', deriver: 'test');
|
||||
$plugin->setClass('foo');
|
||||
$this->assertEquals([
|
||||
'id' => 'example',
|
||||
'class' => 'foo',
|
||||
'deriver' => 'test',
|
||||
], $plugin->get());
|
||||
}
|
||||
|
||||
/**
|
||||
* @covers ::setProvider
|
||||
* @covers ::getProvider
|
||||
*/
|
||||
public function testSetProvider(): void {
|
||||
$plugin = new Plugin(id: 'example');
|
||||
$plugin->setProvider('example');
|
||||
$this->assertEquals('example', $plugin->getProvider());
|
||||
}
|
||||
|
||||
/**
|
||||
* @covers ::getId
|
||||
*/
|
||||
public function testGetId(): void {
|
||||
$plugin = new Plugin(id: 'example');
|
||||
$this->assertEquals('example', $plugin->getId());
|
||||
}
|
||||
|
||||
/**
|
||||
* @covers ::setClass
|
||||
* @covers ::getClass
|
||||
*/
|
||||
public function testSetClass(): void {
|
||||
$plugin = new Plugin(id: 'test');
|
||||
$plugin->setClass('example');
|
||||
$this->assertEquals('example', $plugin->getClass());
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
/**
|
||||
* {@inheritdoc}
|
||||
*/
|
||||
class PluginStub extends Plugin {
|
||||
|
||||
}
|
||||
@ -0,0 +1,130 @@
|
||||
<?php
|
||||
|
||||
declare(strict_types=1);
|
||||
|
||||
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 static 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): void {
|
||||
// Mock a Context object.
|
||||
$mock_context = $this->getMockBuilder('Drupal\Component\Plugin\Context\Context')
|
||||
->disableOriginalConstructor()
|
||||
->onlyMethods(['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');
|
||||
// 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->createMock('Drupal\Component\Plugin\Context\ContextDefinitionInterface');
|
||||
|
||||
// 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());
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Data provider for testHasContextValue.
|
||||
*/
|
||||
public static function providerHasContextValue() {
|
||||
return [
|
||||
[TRUE, FALSE],
|
||||
[TRUE, 0],
|
||||
[TRUE, -0],
|
||||
[TRUE, 0.0],
|
||||
[TRUE, -0.0],
|
||||
[TRUE, ''],
|
||||
[TRUE, '0'],
|
||||
[TRUE, []],
|
||||
[FALSE, NULL],
|
||||
];
|
||||
}
|
||||
|
||||
/**
|
||||
* @covers ::hasContextValue
|
||||
* @dataProvider providerHasContextValue
|
||||
*/
|
||||
public function testHasContextValue($has_context_value, $default_value): void {
|
||||
$mock_definition = $this->createMock('Drupal\Component\Plugin\Context\ContextDefinitionInterface');
|
||||
|
||||
$mock_definition->expects($this->atLeastOnce())
|
||||
->method('getDefaultValue')
|
||||
->willReturn($default_value);
|
||||
|
||||
$context = new Context($mock_definition);
|
||||
|
||||
$this->assertSame($has_context_value, $context->hasContextValue());
|
||||
$this->assertSame($default_value, $context->getContextValue());
|
||||
}
|
||||
|
||||
/**
|
||||
* @covers ::getContextValue
|
||||
*/
|
||||
public function testDefaultValue(): void {
|
||||
$mock_definition = $this->createMock('Drupal\Component\Plugin\Context\ContextDefinitionInterface');
|
||||
|
||||
$mock_definition->expects($this->once())
|
||||
->method('getDefaultValue')
|
||||
->willReturn('test');
|
||||
|
||||
$context = new Context($mock_definition);
|
||||
$this->assertEquals('test', $context->getContextValue());
|
||||
}
|
||||
|
||||
}
|
||||
@ -0,0 +1,153 @@
|
||||
<?php
|
||||
|
||||
declare(strict_types=1);
|
||||
|
||||
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(): void {
|
||||
$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(): void {
|
||||
$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(): void {
|
||||
$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(): void {
|
||||
$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 non-existent class definition.
|
||||
*
|
||||
* @covers ::getPluginClass
|
||||
*/
|
||||
public function testGetPluginClassWithNotExistingClassWithArrayPluginDefinition(): void {
|
||||
$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 non-existent class definition.
|
||||
*
|
||||
* @covers ::getPluginClass
|
||||
*/
|
||||
public function testGetPluginClassWithNotExistingClassWithObjectPluginDefinition(): void {
|
||||
$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(): void {
|
||||
$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(): void {
|
||||
$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(): void {
|
||||
$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(): void {
|
||||
$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,102 @@
|
||||
<?php
|
||||
|
||||
declare(strict_types=1);
|
||||
|
||||
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 static 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): void {
|
||||
// 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,110 @@
|
||||
<?php
|
||||
|
||||
declare(strict_types=1);
|
||||
|
||||
namespace Drupal\Tests\Component\Plugin\Discovery;
|
||||
|
||||
use Drupal\Component\Plugin\Attribute\Plugin;
|
||||
use Drupal\Component\Plugin\Definition\PluginDefinition;
|
||||
use Drupal\Component\Plugin\Discovery\AttributeBridgeDecorator;
|
||||
use Drupal\Component\Plugin\Discovery\DiscoveryInterface;
|
||||
use PHPUnit\Framework\TestCase;
|
||||
|
||||
/**
|
||||
* @coversDefaultClass \Drupal\Component\Annotation\Plugin\Discovery\AnnotationBridgeDecorator
|
||||
* @group Plugin
|
||||
*/
|
||||
class AttributeBridgeDecoratorTest extends TestCase {
|
||||
|
||||
/**
|
||||
* @covers ::getDefinitions
|
||||
*/
|
||||
public function testGetDefinitions(): void {
|
||||
// Normally the attribute classes would be autoloaded.
|
||||
include_once __DIR__ . '/../../../../../fixtures/plugins/CustomPlugin.php';
|
||||
include_once __DIR__ . '/../../../../../fixtures/plugins/Plugin/PluginNamespace/AttributeDiscoveryTest1.php';
|
||||
|
||||
$definitions = [];
|
||||
$definitions['object'] = new ObjectDefinition(['id' => 'foo']);
|
||||
$definitions['array'] = [
|
||||
'id' => 'bar',
|
||||
'class' => 'com\example\PluginNamespace\AttributeDiscoveryTest1',
|
||||
];
|
||||
$discovery = $this->createMock(DiscoveryInterface::class);
|
||||
$discovery->expects($this->any())
|
||||
->method('getDefinitions')
|
||||
->willReturn($definitions);
|
||||
|
||||
$decorator = new AttributeBridgeDecorator($discovery, TestAttribute::class);
|
||||
|
||||
$expected = [
|
||||
'object' => new ObjectDefinition(['id' => 'foo']),
|
||||
'array' => (new ObjectDefinition(['id' => 'bar']))->setClass('com\example\PluginNamespace\AttributeDiscoveryTest1'),
|
||||
];
|
||||
$this->assertEquals($expected, $decorator->getDefinitions());
|
||||
}
|
||||
|
||||
/**
|
||||
* Tests that the decorator of other methods works.
|
||||
*
|
||||
* @covers ::__call
|
||||
*/
|
||||
public function testOtherMethod(): void {
|
||||
// Normally the attribute classes would be autoloaded.
|
||||
include_once __DIR__ . '/../../../../../fixtures/plugins/CustomPlugin.php';
|
||||
include_once __DIR__ . '/../../../../../fixtures/plugins/Plugin/PluginNamespace/AttributeDiscoveryTest1.php';
|
||||
|
||||
$discovery = $this->createMock(ExtendedDiscoveryInterface::class);
|
||||
$discovery->expects($this->exactly(2))
|
||||
->method('otherMethod')
|
||||
->willReturnCallback(fn($id) => $id === 'foo');
|
||||
|
||||
$decorator = new AttributeBridgeDecorator($discovery, TestAttribute::class);
|
||||
|
||||
$this->assertTrue($decorator->otherMethod('foo'));
|
||||
$this->assertFalse($decorator->otherMethod('bar'));
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
/**
|
||||
* An interface for testing the Discovery interface.
|
||||
*/
|
||||
interface ExtendedDiscoveryInterface extends DiscoveryInterface {
|
||||
|
||||
public function otherMethod(string $id): bool;
|
||||
|
||||
}
|
||||
|
||||
/**
|
||||
* {@inheritdoc}
|
||||
*/
|
||||
class TestAttribute extends Plugin {
|
||||
|
||||
/**
|
||||
* {@inheritdoc}
|
||||
*/
|
||||
public function get(): object {
|
||||
return new ObjectDefinition(parent::get());
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
/**
|
||||
* {@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,81 @@
|
||||
<?php
|
||||
|
||||
declare(strict_types=1);
|
||||
|
||||
namespace Drupal\Tests\Component\Plugin\Discovery;
|
||||
|
||||
use Drupal\Component\Plugin\Discovery\DiscoveryCachedTrait;
|
||||
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 static 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): void {
|
||||
$trait = $this->getMockBuilder(DiscoveryCachedTraitMockableClass::class)
|
||||
->onlyMethods(['getDefinitions'])
|
||||
->getMock();
|
||||
$reflection_definitions = new \ReflectionProperty($trait, 'definitions');
|
||||
// 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)
|
||||
);
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
/**
|
||||
* A class using the DiscoveryCachedTrait for mocking purposes.
|
||||
*/
|
||||
class DiscoveryCachedTraitMockableClass {
|
||||
|
||||
use DiscoveryCachedTrait;
|
||||
|
||||
public function getDefinitions(): array {
|
||||
return [];
|
||||
}
|
||||
|
||||
}
|
||||
Some files were not shown because too many files have changed in this diff Show More
Reference in New Issue
Block a user