Skip to content

Commit

Permalink
Issue #2304461 by sun: Rebase KernelTestBase onto PHPUnit.
Browse files Browse the repository at this point in the history
Includes various performance optimizations, since ContainerBuilder::compile()
is very slow: A precompiled Container is shared across test methods.
Each test boots new DrupalKernel that gets the precompiled Container
injected.

Each test registers itself as a Logger, so as to catch unexpected error
log messages, in case functional code fails to use trigger_error().

Various utility methods of the former Simpletest TestBase + KernelTestBase
are made available as traits (so as to keep the original code functional).
  • Loading branch information
sun committed Aug 1, 2014
1 parent 85af867 commit fd3953e
Show file tree
Hide file tree
Showing 18 changed files with 1,755 additions and 152 deletions.
17 changes: 17 additions & 0 deletions .travis.yml
Original file line number Diff line number Diff line change
@@ -0,0 +1,17 @@
language: php

php:
- 5.4

#before_script:
# - composer self-update
# # @todo Enable APC(u) extension.

script:
# @todo Re-enable. For now, we know that it works.
#- phpunit -v -c core --testsuite Unit
# Disable XDebug after running unit tests.
- phpenv config-rm xdebug.ini
- php --version
# @todo Update to new stable once all fixes are in upstream.
- ./core/vendor/phpunit/phpunit/phpunit -v -c core --testsuite Kernel
8 changes: 6 additions & 2 deletions core/includes/bootstrap.inc
Original file line number Diff line number Diff line change
Expand Up @@ -176,7 +176,9 @@ const DRUPAL_EXTENSION_NAME_MAX_LENGTH = 50;
* @see http://php.net/manual/reserved.variables.server.php
* @see http://php.net/manual/function.time.php
*/
define('REQUEST_TIME', (int) $_SERVER['REQUEST_TIME']);
if (!defined('REQUEST_TIME')) {
define('REQUEST_TIME', (int) $_SERVER['REQUEST_TIME']);
}

/**
* Regular expression to match PHP function names.
Expand Down Expand Up @@ -204,7 +206,9 @@ const CONFIG_STAGING_DIRECTORY = 'staging';
*
* This strips two levels of directories off the current directory.
*/
define('DRUPAL_ROOT', dirname(dirname(__DIR__)));
if (!defined('DRUPAL_ROOT')) {
define('DRUPAL_ROOT', dirname(dirname(__DIR__)));
}

/**
* Returns the appropriate configuration directory.
Expand Down
15 changes: 13 additions & 2 deletions core/lib/Drupal/Component/Discovery/YamlDiscovery.php
Original file line number Diff line number Diff line change
Expand Up @@ -28,6 +28,15 @@ class YamlDiscovery implements DiscoverableInterface {
*/
protected $directories = array();

/**
* Array of all parsed files, keyed by filename.
*
* Especially during kernel tests, YAML files are re-parsed often.
*
* @var array
*/
protected static $parsedFiles = array();

/**
* Constructs a YamlDiscovery object.
*
Expand All @@ -48,9 +57,11 @@ public function __construct($name, array $directories) {
public function findAll() {
$all = array();
foreach ($this->findFiles() as $provider => $file) {
$all[$provider] = Yaml::decode(file_get_contents($file));
if (!isset(static::$parsedFiles[$file])) {
static::$parsedFiles[$file] = Yaml::decode(file_get_contents($file));
}
$all[$provider] = static::$parsedFiles[$file];
}

return $all;
}

Expand Down
37 changes: 31 additions & 6 deletions core/lib/Drupal/Core/DrupalKernel.php
Original file line number Diff line number Diff line change
Expand Up @@ -409,6 +409,14 @@ public function getContainer() {
return $this->container;
}

/**
* {@inheritdoc}
*/
public function setContainer(ContainerInterface $container = NULL) {
$this->container = $container;
return $this;
}

/**
* Helper method that does request related initialization.
*
Expand Down Expand Up @@ -520,9 +528,13 @@ public function discoverServiceProviders() {

// Add site-specific service providers.
if (!empty($GLOBALS['conf']['container_service_providers'])) {
foreach ($GLOBALS['conf']['container_service_providers'] as $class) {
if (class_exists($class)) {
$this->serviceProviderClasses['site'][] = $class;
foreach ($GLOBALS['conf']['container_service_providers'] as $key => $class) {
if (is_object($class)) {
$this->serviceProviderClasses['site'][$key] = get_class($class);
$this->serviceProviders['site'][$key] = $class;
}
elseif (class_exists($class)) {
$this->serviceProviderClasses['site'][$key] = $class;
}
}
}
Expand Down Expand Up @@ -683,9 +695,16 @@ protected function initializeContainer($rebuild = FALSE) {
}
}

// If we haven't booted yet but there is a container, then we're asked to
// boot the container injected via setContainer().
// @see \Drupal\Tests\KernelTestBase::setUp()
if (isset($this->container) && !$this->booted) {
$container = $this->container;
}

// If the module list hasn't already been set in updateModules and we are
// not forcing a rebuild, then try and load the container from the disk.
if (empty($this->moduleList) && !$rebuild) {
if (!isset($container) && !isset($this->moduleList) && !$rebuild) {
$class = $this->getClassName();
$cache_file = $class . '.php';

Expand All @@ -700,6 +719,7 @@ protected function initializeContainer($rebuild = FALSE) {
}
}

// If there is still no container, build a new one from scratch.
if (!isset($container)) {
$container = $this->compileContainer();
}
Expand Down Expand Up @@ -1085,13 +1105,18 @@ protected function compileContainer() {
*/
protected function initializeServiceProviders() {
$this->discoverServiceProviders();
$this->serviceProviders = array(
if (!isset($this->serviceProviders)) {
$this->serviceProviders = array();
}
$this->serviceProviders += array(
'app' => array(),
'site' => array(),
);
foreach ($this->serviceProviderClasses as $origin => $classes) {
foreach ($classes as $name => $class) {
$this->serviceProviders[$origin][$name] = new $class;
if (!isset($this->serviceProviders[$origin][$name])) {
$this->serviceProviders[$origin][$name] = new $class;
}
}
}
}
Expand Down
13 changes: 13 additions & 0 deletions core/lib/Drupal/Core/DrupalKernelInterface.php
Original file line number Diff line number Diff line change
Expand Up @@ -7,6 +7,7 @@

namespace Drupal\Core;

use Symfony\Component\DependencyInjection\ContainerInterface;
use Symfony\Component\HttpKernel\HttpKernelInterface;
use Symfony\Component\HttpFoundation\Request;

Expand Down Expand Up @@ -56,6 +57,18 @@ public function getServiceProviders($origin);
*/
public function getContainer();

/**
* Sets the current container.
*
* Must be called before boot() in order to bootstrap the given container.
*
* @param \Symfony\Component\DependencyInjection\ContainerInterface $container
* The container to set.
*
* @return $this
*/
public function setContainer(ContainerInterface $container = NULL);

/**
* Set the current site path.
*
Expand Down
2 changes: 2 additions & 0 deletions core/lib/Drupal/Core/Extension/InfoParser.php
Original file line number Diff line number Diff line change
Expand Up @@ -19,6 +19,8 @@ class InfoParser implements InfoParserInterface {
/**
* Array of all info keyed by filename.
*
* Especially during kernel tests, YAML files are re-parsed often.
*
* @var array
*/
protected static $parsedInfos = array();
Expand Down
8 changes: 8 additions & 0 deletions core/lib/Drupal/Core/Extension/ModuleHandler.php
Original file line number Diff line number Diff line change
Expand Up @@ -710,6 +710,10 @@ public function install(array $module_list, $enable_dependencies = TRUE) {
$extension_config = \Drupal::config('core.extension');
if ($enable_dependencies) {
// Get all module data so we can find dependencies and sort.
// @todo Remove dependency on system.module.
if (!function_exists('system_rebuild_module_data')) {
require_once DRUPAL_ROOT . '/core/modules/system/system.module';
}
$module_data = system_rebuild_module_data();
$module_list = $module_list ? array_combine($module_list, $module_list) : array();
if (array_diff_key($module_list, $module_data)) {
Expand Down Expand Up @@ -918,6 +922,10 @@ public function install(array $module_list, $enable_dependencies = TRUE) {
*/
public function uninstall(array $module_list, $uninstall_dependents = TRUE) {
// Get all module data so we can find dependencies and sort.
// @todo Remove dependency on system.module.
if (!function_exists('system_rebuild_module_data')) {
require_once DRUPAL_ROOT . '/core/modules/system/system.module';
}
$module_data = system_rebuild_module_data();
$module_list = $module_list ? array_combine($module_list, $module_list) : array();
if (array_diff_key($module_list, $module_data)) {
Expand Down
28 changes: 28 additions & 0 deletions core/lib/Drupal/Core/Queue/QueueMemoryFactory.php
Original file line number Diff line number Diff line change
@@ -0,0 +1,28 @@
<?php

/**
* @file
* Contains \Drupal\Core\Queue\QueueMemoryFactory.
*/

namespace Drupal\Core\Queue;

/**
* Defines the queue factory for the memory backend.
*/
class QueueMemoryFactory {

/**
* Constructs a new queue object for a given name.
*
* @param string $name
* The name of the collection holding key and value pairs.
*
* @return \Drupal\Core\Queue\Memory
* A queue implementation for the given $collection.
*/
public function get($name) {
return new Memory($name);
}

}
13 changes: 12 additions & 1 deletion core/lib/Drupal/Core/StreamWrapper/LocalStream.php
Original file line number Diff line number Diff line change
Expand Up @@ -120,14 +120,25 @@ protected function getLocalPath($uri = NULL) {
$uri = $this->uri;
}
$path = $this->getDirectoryPath() . '/' . $this->getTarget($uri);

// In PHPUnit tests, the base path for local streams may be a virtual
// filesystem stream wrapper URI, in which case this local stream acts like
// a proxy. realpath() is (obviously) not supported by vfsStream.
if (strpos($path, 'vfs://') === 0) {
return $path;
}

$realpath = realpath($path);
if (!$realpath) {
// This file does not yet exist.
$realpath = realpath(dirname($path)) . '/' . drupal_basename($path);
}
$directory = realpath($this->getDirectoryPath());
if (!$realpath || !$directory || strpos($realpath, $directory) !== 0) {
return FALSE;
throw new \RuntimeException(vsprintf("Unable to resolve URI %s to local path %s", array(
var_export($uri, TRUE),
var_export($path, TRUE),
)));
}
return $realpath;
}
Expand Down
120 changes: 120 additions & 0 deletions core/modules/simpletest/src/RandomGeneratorTrait.php
Original file line number Diff line number Diff line change
@@ -0,0 +1,120 @@
<?php

/**
* @file
* Contains \Drupal\simpletest\RandomGeneratorTrait.
*/

namespace Drupal\simpletest;

use Drupal\Component\Utility\Random;

/**
* Provides random generator utility methods.
*/
trait RandomGeneratorTrait {

/**
* The random generator.
*
* @var \Drupal\Component\Utility\Random
*/
protected $randomGenerator;

/**
* Generates a unique random string of ASCII characters of codes 32 to 126.
*
* Do not use this method when special characters are not possible (e.g., in
* machine or file names that have already been validated); instead, use
* randomName().
*
* @param int $length
* Length of random string to generate.
*
* @return string
* Randomly generated unique string.
*
* @see \Drupal\Component\Utility\Random::string()
*/
protected function randomString($length = 8) {
return $this->getRandomGenerator()->string($length, TRUE, array($this, 'randomStringValidate'));
}

/**
* Callback for random string validation.
*
* @see \Drupal\Component\Utility\Random::string()
*
* @param string $string
* The random string to validate.
*
* @return bool
* TRUE if the random string is valid, FALSE if not.
*/
protected function randomStringValidate($string) {
// Consecutive spaces causes issues for
// Drupal\simpletest\WebTestBase::assertLink().
if (preg_match('/\s{2,}/', $string)) {
return FALSE;
}
// Starting with a space means that length might not be what is expected.
// Starting with an @ sign causes CURL to fail if used in conjunction with a
// file upload, see https://drupal.org/node/2174997.
if (preg_match('/^(\s|@)/', $string)) {
return FALSE;
}
// Ending with a space means that length might not be what is expected.
if (preg_match('/\s$/', $string)) {
return FALSE;
}
return TRUE;
}

/**
* Generates a unique random string containing letters and numbers.
*
* Do not use this method when testing unvalidated user input. Instead, use
* \Drupal\simpletest\TestBase::randomString().
*
* @param int $length
* Length of random string to generate.
*
* @return string
* Randomly generated unique string.
*
* @see \Drupal\Component\Utility\Random::name()
*/
protected function randomName($length = 8) {
return $this->getRandomGenerator()->name($length, TRUE);
}

/**
* Generates a random PHP object.
*
* @param int $size
* The number of random keys to add to the object.
*
* @return \stdClass
* The generated object, with the specified number of random keys. Each key
* has a random string value.
*
* @see \Drupal\Component\Utility\Random::object()
*/
protected function randomObject($size = 4) {
return $this->getRandomGenerator()->object($size);
}

/**
* Gets the random generator for the utility methods.
*
* @return \Drupal\Component\Utility\Random
* The random generator.
*/
protected function getRandomGenerator() {
if (!is_object($this->randomGenerator)) {
$this->randomGenerator = new Random();
}
return $this->randomGenerator;
}

}
Loading

0 comments on commit fd3953e

Please sign in to comment.