diff --git a/.htaccess b/.htaccess index af418c4..d308dd8 100644 --- a/.htaccess +++ b/.htaccess @@ -28,13 +28,28 @@ DirectoryIndex index.php index.html index.htm AddType image/svg+xml svg svgz AddEncoding gzip svgz -# Override PHP settings that cannot be changed at runtime. See -# sites/default/default.settings.php and -# Drupal\Core\DrupalKernel::bootEnvironment() for settings that can be -# changed at runtime. - # PHP 5, Apache 1 and 2. + # PHP enables assert statements by default. Drupal uses them to perform + # validations that are only necessary during module and theme development, + # and that slow the system down. Hence the Drupal default is off. + php_value assert.active 0 + + # Assert statements are used to validate the cache. To prevent invalid cache + # entries from being written during development you need to turn assert bail + # on. You need to do this even if you disabled caching as Drupal writes caches + # even when it isn't reading from them. + php_value assert.bail 0 + + # The above two settings can be changed at runtime using assert_options() + # Set them here to enable assert statements before + # Drupal\Core\DrupalKernel::bootEnvironment() runs. + # See sites/example.settings.local.php + # + # Override PHP settings that cannot be changed at runtime. See + # sites/default/default.settings.php and + # Drupal\Core\DrupalKernel::bootEnvironment() for settings that can be + # changed at runtime. php_flag session.auto_start off php_value mbstring.http_input pass php_value mbstring.http_output pass diff --git a/core/core.api.php b/core/core.api.php index a8b13ca..a4c45e5 100644 --- a/core/core.api.php +++ b/core/core.api.php @@ -57,6 +57,7 @@ * - @link queue Queue API @endlink * - @link typed_data Typed Data @endlink * - @link testing Automated tests @endlink + * - @link assertions Assert Statements @endlink * - @link third_party Integrating third-party applications @endlink * * @section more_info Further information @@ -983,6 +984,61 @@ */ /** + * @defgroup assertions Assert Statements + * @{ + * Use of the Assert statement in Drupal. + * + * An assertion is a special type of error function that is used during the + * development of code. The Drupal project has adopted them to help monitor + * interactions between core code and third party modules. + * + * Unlike errors or exceptions, assertions can be disabled, and in this state + * they are ignored by PHP. For this reason they can't be used as control + * structures in any way. They are used to document and check conditions the + * progammer expects to be outright impossible if there are no errors in the + * code and in the configuration. Keep this in mind when you come across an + * assert statement in code - whatever the condition that is being checked + * for, it is one the author of that assertion believed was completely + * impossible so if it's happening there's a bug elsewhere in the code. + * + * Assert statements are most commonly used to enforce API expectations in a + * manner similar to type hinting, but they can enforce more complicated data + * structures, such as verifying an array contains only strings. Until PHP 7 + * becomes the baseline for Drupal they provide a means to verify the + * returns of functions. They can make other checks as well, such as checking + * to see if a dependency has been set on an object before calling it. + * + * These checks are unnecessary once development concludes, and collectively + * they hurt performance significantly. Assert is therefore used in preference + * to exception throwing since assert statements can be disabled. + * + * Assert statements are not a replacement for unit tests - they are a + * supplement. Unit tests excel at testing code units both individually and + * in small groups. Functional tests run the system through its paces in known + * scenarios. Neither can handle the unknown - this is where the assert + * statement comes into its own. It can test the validity of code that hasn't + * been written yet. They are part of a part of a methodology known as design + * by contract. + * + * @section assert_use Using the assert statement + * The @link http://php.net/manual/en/function.assert.php assert @endlink + * statement is part of the PHP language. The language allows you to pass any + * value you want to the statement as its first argument, but you are strongly + * encouraged to only use strings to minimize overhead when assertion checking + * is turned off. + * + * Keep in mind that strings passed to assert will be evaluated as per the + * eval() statement. Use single quotes only to insure any contained variables + * are not parsed when the error is displayed. + * + * If you have activated the settings.local.php file then assertions will be + * turned on while your module or theme is running. + * + * @see https://www.drupal.org/node/2492225 + * @} + */ + +/** * @defgroup info_types Information types * @{ * Types of information in Drupal. diff --git a/core/lib/Drupal/Component/Assertion/Assertion.php b/core/lib/Drupal/Component/Assertion/Assertion.php new file mode 100644 index 0000000..781a885 --- /dev/null +++ b/core/lib/Drupal/Component/Assertion/Assertion.php @@ -0,0 +1,122 @@ +startAssertionHandling(); + * parent::setUp(); + * } + * + * public function tearDown() { + * $this->stopAssertionHandling(); + * parent::setUp(); + * } + * @endcode + */ +trait BaseTestingTrait { + + /** + * Flag to throw an exception immediately on failure. + * + * @var bool + */ + protected $dieOnRaise = FALSE; + + /** + * Collections of captured assertions. + * + * @var array + */ + protected $assertionsRaised = []; + + + /** + * Callback handler for assert raises during testing. + */ + public function assertCallbackHandle($file, $line, $code, $message = '') { + + // We print out this warning during test development, and since the + // automated tests run in strict mode it will cause a test failure. + if (!$code) { + print ('Assertions should always be strings! Even though PHP permits + other argument types, those arguments will be evaluated which causes + a loss of performance.'); + } + + if ($this->dieOnRaise) { + throw new AssertionException($code . ' ' . $message); + } + + $this->assertionsRaised[] = $code; + return TRUE; + } + + /** + * Start assertion handling for the test. + * + * Call this from setUp() + */ + protected function startAssertionHandling() { + assert_options(ASSERT_WARNING, FALSE); + assert_options(ASSERT_BAIL, FALSE); + assert_options(ASSERT_CALLBACK, [$this, 'assertCallbackHandle']); + $this->assertionsRaised = []; + return FALSE; + } + + /** + * Suspend assertion handling. + */ + protected function suspendAssertionHandling() { + assert_options(ASSERT_WARNING, TRUE); + assert_options(ASSERT_CALLBACK, NULL); + $this->assertionsRaised = []; + } + + /** + * Cease handling assertions and clear the way for the next test. + * + * Call this from tearDown() + */ + protected function stopAssertionHandling() { + $this->assertAssertionNotRaised(); + $this->suspendAssertionHandling(); + $this->dieOnRaise = FALSE; + } + + /** + * Check if the assertions specified where raised. + * + * This function can be overloaded. Assertions should be passed in the order + * they are expected to occur. After being accounted for the assertion count + * is reset. + */ + abstract protected function assertAssertionsRaised(); + + /** + * Insure no assertions where thrown. + * + * Called during teardown, but you may wish to call it at other times. + */ + abstract protected function assertAssertionNotRaised(); + +} diff --git a/core/lib/Drupal/Component/Assertion/PHPUnitTestingTrait.php b/core/lib/Drupal/Component/Assertion/PHPUnitTestingTrait.php new file mode 100644 index 0000000..4337060 --- /dev/null +++ b/core/lib/Drupal/Component/Assertion/PHPUnitTestingTrait.php @@ -0,0 +1,31 @@ +assertEquals(func_get_args(), $this->assertionsRaised); + $this->assertionsRaised = []; + } + + /** + * {@inheritdoc} + */ + protected function assertAssertionNotRaised() { + $this->assertEmpty($this->assertionsRaised); + } + +} diff --git a/core/lib/Drupal/Core/Cache/ApcuBackend.php b/core/lib/Drupal/Core/Cache/ApcuBackend.php index 9bac79e..f04e240 100644 --- a/core/lib/Drupal/Core/Cache/ApcuBackend.php +++ b/core/lib/Drupal/Core/Cache/ApcuBackend.php @@ -53,6 +53,9 @@ class ApcuBackend implements CacheBackendInterface { * The cache tags checksum provider. */ public function __construct($bin, $site_prefix, CacheTagsChecksumInterface $checksum_provider) { + assert('is_string($bin)'); + assert('is_string($site_prefix)'); + $this->bin = $bin; $this->sitePrefix = $site_prefix; $this->checksumProvider = $checksum_provider; @@ -69,6 +72,7 @@ public function __construct($bin, $site_prefix, CacheTagsChecksumInterface $chec * The APCu key for the cache item ID. */ protected function getApcuKey($cid) { + assert('is_string($cid)'); return $this->binPrefix . $cid; } @@ -76,6 +80,7 @@ protected function getApcuKey($cid) { * {@inheritdoc} */ public function get($cid, $allow_invalid = FALSE) { + assert('is_string($cid)'); $cache = apc_fetch($this->getApcuKey($cid)); return $this->prepareItem($cache, $allow_invalid); } @@ -84,6 +89,7 @@ public function get($cid, $allow_invalid = FALSE) { * {@inheritdoc} */ public function getMultiple(&$cids, $allow_invalid = FALSE) { + assert('\\Drupal\\Component\\Assertion\\Assertion::allMembersAre(\'string\', $cids)'); // Translate the requested cache item IDs to APCu keys. $map = array(); foreach ($cids as $cid) { @@ -166,7 +172,8 @@ protected function prepareItem($cache, $allow_invalid) { * {@inheritdoc} */ public function set($cid, $data, $expire = CacheBackendInterface::CACHE_PERMANENT, array $tags = array()) { - Cache::validateTags($tags); + assert('is_string($cid)'); + assert('\\Drupal\\Component\\Assertion\\Assertion::allMembersAre(\'string\', $tags)'); $tags = array_unique($tags); $cache = new \stdClass(); $cache->cid = $cid; @@ -201,6 +208,7 @@ public function setMultiple(array $items = array()) { * {@inheritdoc} */ public function delete($cid) { + assert('is_string($cid)'); apc_delete($this->getApcuKey($cid)); } @@ -208,6 +216,7 @@ public function delete($cid) { * {@inheritdoc} */ public function deleteMultiple(array $cids) { + assert('\\Drupal\\Component\\Assertion\\Assertion::allMembersAre(\'string\', $cids)'); apc_delete(array_map(array($this, 'getApcuKey'), $cids)); } @@ -236,6 +245,7 @@ public function removeBin() { * {@inheritdoc} */ public function invalidate($cid) { + assert('is_string($cid)'); $this->invalidateMultiple(array($cid)); } @@ -243,6 +253,7 @@ public function invalidate($cid) { * {@inheritdoc} */ public function invalidateMultiple(array $cids) { + assert('\\Drupal\\Component\\Assertion\\Assertion::allMembersAre(\'string\', $cids)'); foreach ($this->getMultiple($cids) as $cache) { $this->set($cache->cid, $cache, REQUEST_TIME - 1); } diff --git a/core/lib/Drupal/Core/Cache/Cache.php b/core/lib/Drupal/Core/Cache/Cache.php index d149592..587d4e2 100644 --- a/core/lib/Drupal/Core/Cache/Cache.php +++ b/core/lib/Drupal/Core/Cache/Cache.php @@ -37,7 +37,7 @@ public static function mergeContexts() { $cache_contexts = array_merge($cache_contexts, $contexts); } $cache_contexts = array_unique($cache_contexts); - \Drupal::service('cache_contexts_manager')->validateTokens($cache_contexts); + assert('\\Drupal::service(\'cache_contexts_manager\')->assertValidTokens($cache_contexts)'); sort($cache_contexts); return $cache_contexts; } @@ -66,7 +66,7 @@ public static function mergeTags() { $cache_tags = array_merge($cache_tags, $tags); } $cache_tags = array_unique($cache_tags); - static::validateTags($cache_tags); + assert('\\Drupal\\Component\\Assertion\\Assertion::allMembersAre(\'string\', $cache_tags)'); sort($cache_tags); return $cache_tags; } @@ -110,6 +110,8 @@ public static function mergeMaxAges() { * An array of cache tags. * * @throws \LogicException + * + * @deprecated use assert('\\Drupal\\Component\\Assertion\\Assertion::allMembersAre(\'string\', $tags)'); */ public static function validateTags(array $tags) { if (empty($tags)) { @@ -139,10 +141,14 @@ public static function validateTags(array $tags) { * An array of cache tags. */ public static function buildTags($prefix, array $suffixes, $glue = ':') { + assert('is_string($prefix)'); + assert('is_string($glue)'); + $tags = []; foreach ($suffixes as $suffix) { $tags[] = $prefix . $glue . $suffix; } + assert('\\Drupal\\Component\\Assertion\\Assertion::allMembersAre(\'string\', $tags)'); return $tags; } @@ -153,6 +159,7 @@ public static function buildTags($prefix, array $suffixes, $glue = ':') { * The list of tags to invalidate cache items for. */ public static function invalidateTags(array $tags) { + assert('\\Drupal\\Component\\Assertion\\Assertion::allMembersAre(\'string\', $tags)'); \Drupal::service('cache_tags.invalidator')->invalidateTags($tags); } diff --git a/core/lib/Drupal/Core/Cache/CacheCollector.php b/core/lib/Drupal/Core/Cache/CacheCollector.php index a6d8ab5..4f833d6 100644 --- a/core/lib/Drupal/Core/Cache/CacheCollector.php +++ b/core/lib/Drupal/Core/Cache/CacheCollector.php @@ -115,7 +115,7 @@ * (optional) The tags to specify for the cache item. */ public function __construct($cid, CacheBackendInterface $cache, LockBackendInterface $lock, array $tags = array()) { - Cache::validateTags($tags); + assert('\\Drupal\\Component\\Assertion\\Assertion::allMembersAre(\'string\', $tags)'); $this->cid = $cid; $this->cache = $cache; $this->tags = $tags; @@ -135,6 +135,7 @@ protected function getCid() { * {@inheritdoc} */ public function has($key) { + assert('is_string($key)'); // Make sure the value is loaded. $this->get($key); return isset($this->storage[$key]) || array_key_exists($key, $this->storage); @@ -144,6 +145,7 @@ public function has($key) { * {@inheritdoc} */ public function get($key) { + assert('is_string($key)'); $this->lazyLoadCache(); if (isset($this->storage[$key]) || array_key_exists($key, $this->storage)) { return $this->storage[$key]; @@ -163,6 +165,8 @@ public function get($key) { * this behavior, for example by adding a call to persist(). */ public function set($key, $value) { + assert('is_string($key)'); + $this->lazyLoadCache(); $this->storage[$key] = $value; // The key might have been marked for deletion. @@ -175,6 +179,8 @@ public function set($key, $value) { * {@inheritdoc} */ public function delete($key) { + assert('is_string($key)'); + $this->lazyLoadCache(); unset($this->storage[$key]); $this->keysToRemove[$key] = $key; diff --git a/core/lib/Drupal/Core/Cache/CacheTagsInvalidator.php b/core/lib/Drupal/Core/Cache/CacheTagsInvalidator.php index 64a8eb0..10038b1 100644 --- a/core/lib/Drupal/Core/Cache/CacheTagsInvalidator.php +++ b/core/lib/Drupal/Core/Cache/CacheTagsInvalidator.php @@ -27,8 +27,7 @@ class CacheTagsInvalidator implements CacheTagsInvalidatorInterface { * {@inheritdoc} */ public function invalidateTags(array $tags) { - // Validate the tags. - Cache::validateTags($tags); + assert('\\Drupal\\Component\\Assertion\\Assertion::allMembersAre(\'string\', $tags)'); // Notify all added cache tags invalidators. foreach ($this->invalidators as $invalidator) { diff --git a/core/lib/Drupal/Core/Cache/Context/CacheContextsManager.php b/core/lib/Drupal/Core/Cache/Context/CacheContextsManager.php index 27b012a..1c50b26 100644 --- a/core/lib/Drupal/Core/Cache/Context/CacheContextsManager.php +++ b/core/lib/Drupal/Core/Cache/Context/CacheContextsManager.php @@ -148,6 +148,8 @@ public function convertTokensToKeys(array $context_tokens) { * A representative subset of the given set of cache context tokens.. */ public function optimizeTokens(array $context_tokens) { + assert('\\Drupal\\Component\\Assertion\\Assertion::allMembersAre(\'string\', $context_tokens)'); + $optimized_content_tokens = []; foreach ($context_tokens as $context_token) { // Context tokens without: @@ -178,6 +180,8 @@ public function optimizeTokens(array $context_tokens) { } } } + + assert('\\Drupal\\Component\\Assertion\\Assertion::allMembersAre(\'string\', $optimized_content_tokens)'); return $optimized_content_tokens; } @@ -209,6 +213,8 @@ protected function getService($context_id) { * there is no parameter. */ public static function parseTokens(array $context_tokens) { + assert('\\Drupal\\Component\\Assertion\\Assertion::allMembersAre(\'string\', $context_tokens)'); + $contexts_with_parameters = []; foreach ($context_tokens as $context) { $context_id = $context; @@ -231,6 +237,9 @@ public static function parseTokens(array $context_tokens) { * * @throws \LogicException * + * @deprecated Direct invocation of this method is deprecated. + * Use the assert statement to invoke ::assertValidTokens in this class. + * * @see \Drupal\Core\Cache\Context\CacheContextsManager::parseTokens() */ public function validateTokens(array $context_tokens = []) { @@ -271,4 +280,19 @@ public function validateTokens(array $context_tokens = []) { } } + /** + * Assert statement wrapper for ::validateTokens() + * + * @param string[] $context_tokens + * An array of cache context tokens. + */ + public function assertValidTokens(array $context_tokens = []) { + try { + $this->validateTokens($context_tokens); + } catch ( \LogicException $e) { + return FALSE; + } + return TRUE; + } + } diff --git a/core/lib/Drupal/Core/Cache/DatabaseBackend.php b/core/lib/Drupal/Core/Cache/DatabaseBackend.php index 49e830b..34325fe 100644 --- a/core/lib/Drupal/Core/Cache/DatabaseBackend.php +++ b/core/lib/Drupal/Core/Cache/DatabaseBackend.php @@ -52,6 +52,8 @@ class DatabaseBackend implements CacheBackendInterface { * The cache bin for which the object is created. */ public function __construct(Connection $connection, CacheTagsChecksumInterface $checksum_provider, $bin) { + assert('is_string($bin)'); + // All cache tables should be prefixed with 'cache_'. $bin = 'cache_' . $bin; @@ -64,6 +66,7 @@ public function __construct(Connection $connection, CacheTagsChecksumInterface $ * Implements Drupal\Core\Cache\CacheBackendInterface::get(). */ public function get($cid, $allow_invalid = FALSE) { + assert('is_string($cid)'); $cids = array($cid); $cache = $this->getMultiple($cids, $allow_invalid); return reset($cache); @@ -73,6 +76,7 @@ public function get($cid, $allow_invalid = FALSE) { * Implements Drupal\Core\Cache\CacheBackendInterface::getMultiple(). */ public function getMultiple(&$cids, $allow_invalid = FALSE) { + assert('\\Drupal\\Component\\Assertion\\Assertion::allMembersAre(\'string\', $cids)'); $cid_mapping = array(); foreach ($cids as $cid) { $cid_mapping[$this->normalizeCid($cid)] = $cid; @@ -120,6 +124,7 @@ public function getMultiple(&$cids, $allow_invalid = FALSE) { * whether the item is valid, or FALSE if there is no valid item to load. */ protected function prepareItem($cache, $allow_invalid) { + assert('is_object($cache)'); if (!isset($cache->data)) { return FALSE; } @@ -150,7 +155,8 @@ protected function prepareItem($cache, $allow_invalid) { * Implements Drupal\Core\Cache\CacheBackendInterface::set(). */ public function set($cid, $data, $expire = Cache::PERMANENT, array $tags = array()) { - Cache::validateTags($tags); + assert('is_string($cid)'); + assert('\\Drupal\\Component\\Assertion\\Assertion::allMembersAre(\'string\', $tags)'); $tags = array_unique($tags); // Sort the cache tags so that they are stored consistently in the database. sort($tags); @@ -210,7 +216,7 @@ public function setMultiple(array $items) { 'tags' => array(), ); - Cache::validateTags($item['tags']); + assert('\\Drupal\\Component\\Assertion\\Assertion::allMembersAre(\'string\', $item[\'tags\'])'); $item['tags'] = array_unique($item['tags']); // Sort the cache tags so that they are stored consistently in the DB. sort($item['tags']); @@ -315,6 +321,7 @@ public function deleteAll() { * Implements Drupal\Core\Cache\CacheBackendInterface::invalidate(). */ public function invalidate($cid) { + assert('is_string($cid)'); $this->invalidateMultiple(array($cid)); } @@ -322,6 +329,7 @@ public function invalidate($cid) { * Implements Drupal\Core\Cache\CacheBackendInterface::invalidateMultiple(). */ public function invalidateMultiple(array $cids) { + assert('\\Drupal\\Component\\Assertion\\Assertion::allMembersAre(\'string\', $cids)'); $cids = array_values(array_map(array($this, 'normalizeCid'), $cids)); try { // Update in chunks when a large array is passed. diff --git a/core/lib/Drupal/Core/Cache/MemoryBackend.php b/core/lib/Drupal/Core/Cache/MemoryBackend.php index d9bad8d..bc53260 100644 --- a/core/lib/Drupal/Core/Cache/MemoryBackend.php +++ b/core/lib/Drupal/Core/Cache/MemoryBackend.php @@ -37,6 +37,8 @@ public function __construct($bin) { * Implements Drupal\Core\Cache\CacheBackendInterface::get(). */ public function get($cid, $allow_invalid = FALSE) { + assert('is_string($cid)'); + if (isset($this->cache[$cid])) { return $this->prepareItem($this->cache[$cid], $allow_invalid); } @@ -49,6 +51,8 @@ public function get($cid, $allow_invalid = FALSE) { * Implements Drupal\Core\Cache\CacheBackendInterface::getMultiple(). */ public function getMultiple(&$cids, $allow_invalid = FALSE) { + assert('\\Drupal\\Component\\Assertion\\Assertion::allMembersAre(\'string\', $cids)'); + $ret = array(); $items = array_intersect_key($this->cache, array_flip($cids)); @@ -107,7 +111,9 @@ protected function prepareItem($cache, $allow_invalid) { * Implements Drupal\Core\Cache\CacheBackendInterface::set(). */ public function set($cid, $data, $expire = Cache::PERMANENT, array $tags = array()) { - Cache::validateTags($tags); + assert('is_string($cid)'); + assert('\\Drupal\\Component\\Assertion\\Assertion::allMembersAre(\'string\', $tags)'); + $tags = array_unique($tags); // Sort the cache tags so that they are stored consistently in the database. sort($tags); @@ -140,6 +146,7 @@ public function delete($cid) { * Implements Drupal\Core\Cache\CacheBackendInterface::deleteMultiple(). */ public function deleteMultiple(array $cids) { + assert('\\Drupal\\Component\\Assertion\\Assertion::allMembersAre(\'string\', $cids)'); $this->cache = array_diff_key($this->cache, array_flip($cids)); } @@ -154,6 +161,8 @@ public function deleteAll() { * Implements Drupal\Core\Cache\CacheBackendInterface::invalidate(). */ public function invalidate($cid) { + assert('is_string($cid)'); + if (isset($this->cache[$cid])) { $this->cache[$cid]->expire = $this->getRequestTime() - 1; } @@ -163,6 +172,8 @@ public function invalidate($cid) { * Implements Drupal\Core\Cache\CacheBackendInterface::invalidateMultiple(). */ public function invalidateMultiple(array $cids) { + assert('\\Drupal\\Component\\Assertion\\Assertion::allMembersAre(\'string\', $cids)'); + foreach ($cids as $cid) { $this->cache[$cid]->expire = $this->getRequestTime() - 1; } @@ -172,6 +183,7 @@ public function invalidateMultiple(array $cids) { * {@inheritdoc} */ public function invalidateTags(array $tags) { + assert('\\Drupal\\Component\\Assertion\\Assertion::allMembersAre(\'string\', $tags)'); foreach ($this->cache as $cid => $item) { if (array_intersect($tags, $item->tags)) { $this->cache[$cid]->expire = $this->getRequestTime() - 1; diff --git a/core/lib/Drupal/Core/Cache/PhpBackend.php b/core/lib/Drupal/Core/Cache/PhpBackend.php index 761e394..e2c30c8 100644 --- a/core/lib/Drupal/Core/Cache/PhpBackend.php +++ b/core/lib/Drupal/Core/Cache/PhpBackend.php @@ -51,6 +51,8 @@ class PhpBackend implements CacheBackendInterface { * The cache tags checksum provider. */ public function __construct($bin, CacheTagsChecksumInterface $checksum_provider) { + assert('is_string($bin)'); + $this->bin = 'cache_' . $bin; $this->checksumProvider = $checksum_provider; } @@ -59,6 +61,7 @@ public function __construct($bin, CacheTagsChecksumInterface $checksum_provider) * {@inheritdoc} */ public function get($cid, $allow_invalid = FALSE) { + assert('is_string($cid)'); return $this->getByHash($this->normalizeCid($cid), $allow_invalid); } @@ -74,6 +77,8 @@ public function get($cid, $allow_invalid = FALSE) { * @return bool|mixed */ protected function getByHash($cidhash, $allow_invalid = FALSE) { + assert('is_string($cidhash)'); + if ($file = $this->storage()->getFullPath($cidhash)) { $cache = @include $file; } @@ -96,6 +101,7 @@ public function setMultiple(array $items) { * {@inheritdoc} */ public function getMultiple(&$cids, $allow_invalid = FALSE) { + assert('\\Drupal\\Component\\Assertion\\Assertion::allMembersAre(\'string\', $cids)'); $ret = array(); foreach ($cids as $cid) { @@ -148,7 +154,9 @@ protected function prepareItem($cache, $allow_invalid) { * {@inheritdoc} */ public function set($cid, $data, $expire = Cache::PERMANENT, array $tags = array()) { - Cache::validateTags($tags); + assert('is_string($cid)'); + assert('\\Drupal\\Component\\Assertion\\Assertion::allMembersAre(\'string\', $tags)'); + $item = (object) array( 'cid' => $cid, 'data' => $data, @@ -164,6 +172,8 @@ public function set($cid, $data, $expire = Cache::PERMANENT, array $tags = array * {@inheritdoc} */ public function delete($cid) { + assert('is_string($cid)'); + $this->storage()->delete($this->normalizeCid($cid)); } @@ -171,6 +181,8 @@ public function delete($cid) { * {@inheritdoc} */ public function deleteMultiple(array $cids) { + assert('\\Drupal\\Component\\Assertion\\Assertion::allMembersAre(\'string\', $cids)'); + foreach ($cids as $cid) { $this->delete($cid); } @@ -187,6 +199,8 @@ public function deleteAll() { * {@inheritdoc} */ public function invalidate($cid) { + assert('is_string($cid)'); + $this->invalidatebyHash($this->normalizeCid($cid)); } @@ -207,6 +221,8 @@ protected function invalidatebyHash($cidhash) { * {@inheritdoc} */ public function invalidateMultiple(array $cids) { + assert('\\Drupal\\Component\\Assertion\\Assertion::allMembersAre(\'string\', $cids)'); + foreach ($cids as $cid) { $this->invalidate($cid); } diff --git a/core/lib/Drupal/Core/Plugin/DefaultPluginManager.php b/core/lib/Drupal/Core/Plugin/DefaultPluginManager.php index ceabcd9..5e1a77a 100644 --- a/core/lib/Drupal/Core/Plugin/DefaultPluginManager.php +++ b/core/lib/Drupal/Core/Plugin/DefaultPluginManager.php @@ -126,6 +126,9 @@ class DefaultPluginManager extends PluginManagerBase implements PluginManagerInt * Defaults to 'Drupal\Component\Annotation\Plugin'. */ public function __construct($subdir, \Traversable $namespaces, ModuleHandlerInterface $module_handler, $plugin_interface = NULL, $plugin_definition_annotation_name = 'Drupal\Component\Annotation\Plugin') { + assert('is_string($subdir) || is_bool($subdir)'); + assert('is_null($plugin_interface) || interface_exists($plugin_interface)'); + $this->subdir = $subdir; $this->namespaces = $namespaces; $this->pluginDefinitionAnnotationName = $plugin_definition_annotation_name; @@ -154,7 +157,9 @@ public function __construct($subdir, \Traversable $namespaces, ModuleHandlerInte * definitions should be cleared along with other, related cache entries. */ public function setCacheBackend(CacheBackendInterface $cache_backend, $cache_key, array $cache_tags = array()) { - Cache::validateTags($cache_tags); + assert('is_string($cache_key)'); + assert('\\Drupal\\Component\\Assertion\\Assertion::allMembersAre(\'string\', $cache_tags)'); + $this->cacheBackend = $cache_backend; $this->cacheKey = $cache_key; $this->cacheTags = $cache_tags; @@ -168,6 +173,7 @@ public function setCacheBackend(CacheBackendInterface $cache_backend, $cache_key * hook_mymodule_data_alter() pass in "mymodule_data". */ protected function alterInfo($alter_hook) { + assert('is_string($alter_hook)'); $this->alterHook = $alter_hook; } @@ -212,6 +218,7 @@ protected function getCachedDefinitions() { if (!isset($this->definitions) && $cache = $this->cacheGet($this->cacheKey)) { $this->definitions = $cache->data; } + assert('is_null($this->definitions) || is_array($this->definitions)'); return $this->definitions; } @@ -221,7 +228,7 @@ protected function getCachedDefinitions() { * @param array $definitions * List of definitions to store in cache. */ - protected function setCachedDefinitions($definitions) { + protected function setCachedDefinitions(array $definitions) { $this->cacheSet($this->cacheKey, $definitions, Cache::PERMANENT, $this->cacheTags); $this->definitions = $definitions; } @@ -318,6 +325,7 @@ protected function findDefinitions() { unset($definitions[$plugin_id]); } } + assert('is_array($definitions)'); return $definitions; } diff --git a/core/modules/language/tests/src/Unit/LanguageNegotiationUrlTest.php b/core/modules/language/tests/src/Unit/LanguageNegotiationUrlTest.php index 76d72b5..1a1e4cc 100644 --- a/core/modules/language/tests/src/Unit/LanguageNegotiationUrlTest.php +++ b/core/modules/language/tests/src/Unit/LanguageNegotiationUrlTest.php @@ -60,9 +60,12 @@ protected function setUp() { $cache_contexts_manager = $this->getMockBuilder('Drupal\Core\Cache\Context\CacheContextsManager') ->disableOriginalConstructor() ->getMock(); + $cache_contexts_manager->method('assertValidTokens')->willReturn(TRUE); $container = new ContainerBuilder(); $container->set('cache_contexts_manager', $cache_contexts_manager); + \Drupal::setContainer($container); + } /** diff --git a/core/modules/simpletest/src/AssertionTestingTrait.php b/core/modules/simpletest/src/AssertionTestingTrait.php new file mode 100644 index 0000000..d29f9a6 --- /dev/null +++ b/core/modules/simpletest/src/AssertionTestingTrait.php @@ -0,0 +1,33 @@ +assertIdentical(func_get_args(), $this->assertionsRaised, 'Expected Assertions Raised.'); + $this->assertionsRaised = []; + } + + /** + * {@inheritdoc} + */ + protected function assertAssertionNotRaised() { + $this->assertTrue(count($this->assertionsRaised) === 0, 'No Assertions Raised'); + } + +} diff --git a/core/modules/system/src/Tests/Cache/GenericCacheBackendUnitTestBase.php b/core/modules/system/src/Tests/Cache/GenericCacheBackendUnitTestBase.php index cc0d9df..a9efa88 100644 --- a/core/modules/system/src/Tests/Cache/GenericCacheBackendUnitTestBase.php +++ b/core/modules/system/src/Tests/Cache/GenericCacheBackendUnitTestBase.php @@ -10,6 +10,8 @@ use Drupal\Core\Cache\Cache; use Drupal\Core\Cache\CacheBackendInterface; use Drupal\simpletest\KernelTestBase; +use Drupal\simpletest\AssertionTestingTrait; +use Drupal\Component\Assertion\AssertionException; /** * Tests any cache backend. @@ -22,6 +24,7 @@ * For a full working implementation. */ abstract class GenericCacheBackendUnitTestBase extends KernelTestBase { + use AssertionTestingTrait; /** * Array of objects implementing Drupal\Core\Cache\CacheBackendInterface. @@ -107,6 +110,7 @@ protected function getCacheBackend($bin = null) { } protected function setUp() { + $this->startAssertionHandling(); $this->cachebackends = array(); $this->defaultValue = $this->randomMachineName(10); @@ -116,6 +120,8 @@ protected function setUp() { } protected function tearDown() { + $this->stopAssertionHandling(); + // Destruct the registered backend, each test will get a fresh instance, // properly emptying it here ensure that on persistent data backends they // will come up empty the next test. @@ -219,12 +225,14 @@ public function testSetGet() { $this->assertFalse($backend->get('test8')); // Calling ::set() with invalid cache tags. + $this->dieOnRaise = TRUE; try { $backend->set('exception_test', 'value', Cache::PERMANENT, ['node' => [3, 5, 7]]); - $this->fail('::set() was called with invalid cache tags, no exception was thrown.'); + $this->fail('::set() was called with invalid cache tags, no assertion raised.'); } - catch (\LogicException $e) { - $this->pass('::set() was called with invalid cache tags, an exception was thrown.'); + catch (AssertionException $e) { + $this->pass('::set() was called with invalid cache tags, an assertion was raised.'); + $this->dieOnRaise = FALSE; } } @@ -413,6 +421,7 @@ public function testSetMultiple() { $this->assertEqual($cached['cid_5']->data, $items['cid_5']['data'], 'New cache item set correctly.'); // Calling ::setMultiple() with invalid cache tags. + $this->dieOnRaise = TRUE; try { $items = [ 'exception_test_1' => array('data' => 1, 'tags' => []), @@ -420,10 +429,11 @@ public function testSetMultiple() { 'exception_test_3' => array('data' => 3, 'tags' => ['node' => [3, 5, 7]]), ]; $backend->setMultiple($items); - $this->fail('::setMultiple() was called with invalid cache tags, no exception was thrown.'); + $this->fail('::setMultiple() was called with invalid cache tags, no assertion was raised.'); } - catch (\LogicException $e) { - $this->pass('::setMultiple() was called with invalid cache tags, an exception was thrown.'); + catch (AssertionException $e) { + $this->pass('::setMultiple() was called with invalid cache tags, an assertion was raised.'); + $this->dieOnRaise = FALSE; } } diff --git a/core/modules/system/tests/src/Unit/Menu/MenuLinkTreeTest.php b/core/modules/system/tests/src/Unit/Menu/MenuLinkTreeTest.php index 5e7f2a2..2322bc3 100644 --- a/core/modules/system/tests/src/Unit/Menu/MenuLinkTreeTest.php +++ b/core/modules/system/tests/src/Unit/Menu/MenuLinkTreeTest.php @@ -47,6 +47,7 @@ protected function setUp() { $cache_contexts_manager = $this->getMockBuilder('Drupal\Core\Cache\Context\CacheContextsManager') ->disableOriginalConstructor() ->getMock(); + $cache_contexts_manager->method('assertValidTokens')->willReturn(TRUE); $container = new ContainerBuilder(); $container->set('cache_contexts_manager', $cache_contexts_manager); \Drupal::setContainer($container); diff --git a/core/tests/Drupal/Tests/Component/Assertion/AssertionTest.php b/core/tests/Drupal/Tests/Component/Assertion/AssertionTest.php new file mode 100644 index 0000000..16a77f6 --- /dev/null +++ b/core/tests/Drupal/Tests/Component/Assertion/AssertionTest.php @@ -0,0 +1,194 @@ +assertTrue( + Assertion::allMembersAre('ArrayObject', [ + new ArrayObject(), + new ArrayObject() + ]) + ); + // Traversable of objects. + $this->assertTrue( + Assertion::allMembersAre('ArrayObject', new ArrayObject([ + new ArrayObject(), + new ArrayObject() + ])) + ); + // Empty objects pass regardless - this assertion isn't for checking if + // objects are present. + $this->assertTrue( + Assertion::allMembersAre('foo', []) + ); + + // Non traversables fail. + $this->assertFalse( + Assertion::allMembersAre('foo', 'bar') + ); + + // All members are arrays. + $this->assertTrue( + Assertion::allMembersAre('array', [[], []]) + ); + + $this->assertFalse( + Assertion::allMembersAre('array', ['bar', []]) + ); + + // All members are integers. + foreach (['int', 'integer', 'long'] as $type_alias) { + $this->assertTrue( + Assertion::allMembersAre($type_alias, [1, 2, 3]) + ); + + $this->assertFalse( + Assertion::allMembersAre($type_alias, [1, 2, 3, []]) + ); + } + + // All members are floats. + foreach (['float', 'double', 'real'] as $type_alias) { + $this->assertTrue( + Assertion::allMembersAre($type_alias, [1.1, 2.2, 3.14]) + ); + + $this->assertFalse( + Assertion::allMembersAre($type_alias, [1.2, 2, 3, []]) + ); + } + + // All members can be called. + $this->assertTrue( + Assertion::allMembersAre('callable', [ + 'strchr', + [$this, 'callMe'], + [__CLASS__, 'callMeStatic'], + function() { + return TRUE; + } + ]) + ); + + $this->assertFalse( + Assertion::allMembersAre('callable', [ + 'strchr', + [$this, 'callMe'], + [__CLASS__, 'callMeStatic'], + function() { + return TRUE; + }, + 'bye' + ]) + ); + + // All members are numeric. This includes numeric strings. + $this->assertTrue( + Assertion::allMembersAre('numeric', [1, 1.2, '3']) + ); + + $this->assertFalse( + Assertion::allMembersAre('numeric', [1, 1.2, '3', 'pi']) + ); + + // All members have something. + $this->assertTrue( + Assertion::allMembersAre('not-empty', [1, 'foo']) + ); + + $this->assertFalse( + Assertion::allMembersAre('not-empty', ['']) + ); + + // All members are objects. + $this->assertTrue( + Assertion::allMembersAre('object', [ + new ArrayObject(), + new \stdClass() + ]) + ); + + $this->assertFalse( + Assertion::allMembersAre('ArrayObject', [ + new ArrayObject(), + [] + ]) + ); + + // Create a string mock. + $string_object = new StringObject(); + + // Now test strictly against the string type. + $this->assertTrue( + Assertion::allMembersAre('string', ['bar', 'foo']) + ); + + $this->assertFalse( + Assertion::allMembersAre('string', [$string_object]) + ); + + // But a stringable object should pass stringable. + $this->assertTrue( + Assertion::allMembersAre('stringable', ['bar', $string_object]) + ); + + $this->assertFalse( + Assertion::allMembersAre('stringable', [42]) + ); + + } + + /** + * Call me sometime. + * + * This method exists as part of the is_callable test. + */ + public function callMe() { + return TRUE; + } + + /** + * Call me static. + * + * Again, just a testing method. + */ + public static function callMeStatic() { + return TRUE; + } + +} +/** + * Quick class for testing for objects with __toString. + */ +class StringObject { + /** + * {@inheritdoc} + */ + public function __toString() { + return 'foo'; + } + +} diff --git a/core/tests/Drupal/Tests/Component/Assertion/AssertionTestTraitTest.php b/core/tests/Drupal/Tests/Component/Assertion/AssertionTestTraitTest.php new file mode 100644 index 0000000..c76e844 --- /dev/null +++ b/core/tests/Drupal/Tests/Component/Assertion/AssertionTestTraitTest.php @@ -0,0 +1,82 @@ +startAssertionHandling(); + parent::setUp(); + } + + /** + * {@inheritdoc} + */ + public function tearDown() { + $this->stopAssertionHandling(); + parent::setUp(); + } + + /** + * Test the PHPUnit Assert assist trait. + * + * This test is a bit unusual since we're self testing. The traits being + * tested are part of this object. + */ + public function testBasics() { + // Normal assert. + assert('1 > 2'); + $this->assertAssertionsRaised('1 > 2'); + + // Assert without string pitches error. + ob_start(); + assert(1 > 2); + $output = ob_get_clean(); + + $this->assertEquals($output, + 'Assertions should always be strings! Even though PHP permits + other argument types, those arguments will be evaluated which causes + a loss of performance.'); + + $this->assertAssertionsRaised(''); + + // Assertion log should be empty after the above call. + $this->assertAssertionNotRaised(); + + // Test accumulation of assert failures. + assert('1 > 2'); + assert('4 < 2'); + assert('false'); + + $this->assertAssertionsRaised('1 > 2', '4 < 2', 'false'); + } + + /** + * Tests if an exception is thrown when dieOnRaise is set. + * + * @expectedException \Drupal\Component\Assertion\AssertionException + */ + public function testAssertionExceptionThrow() { + $this->dieOnRaise = TRUE; + assert('4 < 2'); + } + +} diff --git a/core/tests/Drupal/Tests/Core/Cache/CacheTagsInvalidatorTest.php b/core/tests/Drupal/Tests/Core/Cache/CacheTagsInvalidatorTest.php index a2daba7..f60eb0f 100644 --- a/core/tests/Drupal/Tests/Core/Cache/CacheTagsInvalidatorTest.php +++ b/core/tests/Drupal/Tests/Core/Cache/CacheTagsInvalidatorTest.php @@ -19,17 +19,6 @@ class CacheTagsInvalidatorTest extends UnitTestCase { /** * @covers ::invalidateTags - * - * @expectedException \LogicException - * @expectedExceptionMessage Cache tags must be strings, array given. - */ - public function testInvalidateTagsWithInvalidTags() { - $cache_tags_invalidator = new CacheTagsInvalidator(); - $cache_tags_invalidator->invalidateTags(['node' => [2, 3, 5, 8, 13]]); - } - - /** - * @covers ::invalidateTags * @covers ::addInvalidator */ public function testInvalidateTags() { diff --git a/core/tests/Drupal/Tests/Core/Cache/CacheTest.php b/core/tests/Drupal/Tests/Core/Cache/CacheTest.php index c2e61a1..2cdc030 100644 --- a/core/tests/Drupal/Tests/Core/Cache/CacheTest.php +++ b/core/tests/Drupal/Tests/Core/Cache/CacheTest.php @@ -18,48 +18,6 @@ class CacheTest extends UnitTestCase { /** - * Provides a list of cache tags arrays. - * - * @return array - */ - public function validateTagsProvider() { - return [ - [[], FALSE], - [['foo'], FALSE], - [['foo', 'bar'], FALSE], - [['foo', 'bar', 'llama:2001988', 'baz', 'llama:14031991'], FALSE], - // Invalid. - [[FALSE], 'Cache tags must be strings, boolean given.'], - [[TRUE], 'Cache tags must be strings, boolean given.'], - [['foo', FALSE], 'Cache tags must be strings, boolean given.'], - [[NULL], 'Cache tags must be strings, NULL given.'], - [['foo', NULL], 'Cache tags must be strings, NULL given.'], - [[1337], 'Cache tags must be strings, integer given.'], - [['foo', 1337], 'Cache tags must be strings, integer given.'], - [[3.14], 'Cache tags must be strings, double given.'], - [['foo', 3.14], 'Cache tags must be strings, double given.'], - [[[]], 'Cache tags must be strings, array given.'], - [['foo', []], 'Cache tags must be strings, array given.'], - [['foo', ['bar']], 'Cache tags must be strings, array given.'], - [[new \stdClass()], 'Cache tags must be strings, object given.'], - [['foo', new \stdClass()], 'Cache tags must be strings, object given.'], - ]; - } - - /** - * @covers ::validateTags - * - * @dataProvider validateTagsProvider - */ - public function testValidateTags(array $tags, $expected_exception_message) { - if ($expected_exception_message !== FALSE) { - $this->setExpectedException('LogicException', $expected_exception_message); - } - // If it doesn't throw an exception, validateTags() returns NULL. - $this->assertNull(Cache::validateTags($tags)); - } - - /** * Provides a list of pairs of cache tags arrays to be merged. * * @return array diff --git a/core/tests/Drupal/Tests/Core/Cache/CacheableMetadataTest.php b/core/tests/Drupal/Tests/Core/Cache/CacheableMetadataTest.php index f2dbd3e..e02613a 100644 --- a/core/tests/Drupal/Tests/Core/Cache/CacheableMetadataTest.php +++ b/core/tests/Drupal/Tests/Core/Cache/CacheableMetadataTest.php @@ -35,6 +35,7 @@ public function testMerge(CacheableMetadata $a, CacheableMetadata $b, CacheableM $cache_contexts_manager = $this->getMockBuilder('Drupal\Core\Cache\Context\CacheContextsManager') ->disableOriginalConstructor() ->getMock(); + $cache_contexts_manager->method('assertValidTokens')->willReturn(TRUE); $container = new ContainerBuilder(); $container->set('cache_contexts_manager', $cache_contexts_manager); \Drupal::setContainer($container); diff --git a/core/tests/Drupal/Tests/Core/Render/BubbleableMetadataTest.php b/core/tests/Drupal/Tests/Core/Render/BubbleableMetadataTest.php index 52b6ef5..157007f 100644 --- a/core/tests/Drupal/Tests/Core/Render/BubbleableMetadataTest.php +++ b/core/tests/Drupal/Tests/Core/Render/BubbleableMetadataTest.php @@ -60,6 +60,7 @@ public function testMerge(BubbleableMetadata $a, CacheableMetadata $b, Bubbleabl $cache_contexts_manager = $this->getMockBuilder('Drupal\Core\Cache\Context\CacheContextsManager') ->disableOriginalConstructor() ->getMock(); + $cache_contexts_manager->method('assertValidTokens')->willReturn(TRUE); $container = new ContainerBuilder(); $container->set('cache_contexts_manager', $cache_contexts_manager); $container->set('renderer', $renderer); diff --git a/core/tests/Drupal/Tests/Core/Render/RendererTestBase.php b/core/tests/Drupal/Tests/Core/Render/RendererTestBase.php index 7388c61..27d7187 100644 --- a/core/tests/Drupal/Tests/Core/Render/RendererTestBase.php +++ b/core/tests/Drupal/Tests/Core/Render/RendererTestBase.php @@ -104,6 +104,7 @@ protected function setUp() { $this->cacheContextsManager = $this->getMockBuilder('Drupal\Core\Cache\Context\CacheContextsManager') ->disableOriginalConstructor() ->getMock(); + $this->cacheContextsManager->method('assertValidTokens')->willReturn(TRUE); $this->cacheContextsManager->expects($this->any()) ->method('convertTokensToKeys') ->willReturnCallback(function($context_tokens) { diff --git a/core/tests/Drupal/Tests/Core/Routing/UrlGeneratorTest.php b/core/tests/Drupal/Tests/Core/Routing/UrlGeneratorTest.php index 57edd51..839b0e1 100644 --- a/core/tests/Drupal/Tests/Core/Routing/UrlGeneratorTest.php +++ b/core/tests/Drupal/Tests/Core/Routing/UrlGeneratorTest.php @@ -62,6 +62,7 @@ protected function setUp() { $cache_contexts_manager = $this->getMockBuilder('Drupal\Core\Cache\Context\CacheContextsManager') ->disableOriginalConstructor() ->getMock(); + $cache_contexts_manager->method('assertValidTokens')->willReturn(TRUE); $container = new ContainerBuilder(); $container->set('cache_contexts_manager', $cache_contexts_manager); \Drupal::setContainer($container); diff --git a/sites/example.settings.local.php b/sites/example.settings.local.php index 4cc2109..798ff38 100644 --- a/sites/example.settings.local.php +++ b/sites/example.settings.local.php @@ -12,6 +12,20 @@ */ /** + * Assertions + * + * Drupal uses assert statements to perform validations that are only + * necessary during module and theme development, and that slow the system + * down. By default the statements are turned off in the .htaccess file. + * Here we turn them back on which should be sufficient for most projects, + * but assert statements located before the DrupalKernel parses this file + * will not be ran unless you modify the .htaccess file. We also set the + * system to halt on assert fail, just in case. + */ +assert_options(ASSERT_ACTIVE, 1); +assert_options(ASSERT_BAIL, 1); + +/** * Enable local development services. */ $settings['container_yamls'][] = DRUPAL_ROOT . '/sites/development.services.yml';