Skip to content

Commit

Permalink
Issue #1693398 by donquixote, pounard, sun, Sylvain Lecoy: Allow PSR-…
Browse files Browse the repository at this point in the history
…0 test classes to be used in D7.
  • Loading branch information
DavidRothstein committed Apr 2, 2013
1 parent de6e29d commit 7cbe4b7
Show file tree
Hide file tree
Showing 11 changed files with 269 additions and 4 deletions.
2 changes: 2 additions & 0 deletions CHANGELOG.txt
Original file line number Diff line number Diff line change
@@ -1,6 +1,8 @@

Drupal 7.22, xxxx-xx-xx (development version)
-----------------------
- Changed the Simpletest module to allow PSR-0 test classes to be used in
Drupal 7.
- Removed an unnecessary "Content-Disposition" header from private file
downloads; it prevented many private files from being viewed inline in a web
browser.
Expand Down
7 changes: 4 additions & 3 deletions modules/simpletest/drupal_web_test_case.php
Original file line number Diff line number Diff line change
Expand Up @@ -447,7 +447,8 @@ protected function error($message = '', $group = 'Other', array $caller = NULL)
*/
protected function verbose($message) {
if ($id = simpletest_verbose($message)) {
$url = file_create_url($this->originalFileDirectory . '/simpletest/verbose/' . get_class($this) . '-' . $id . '.html');
$class_safe = str_replace('\\', '_', get_class($this));
$url = file_create_url($this->originalFileDirectory . '/simpletest/verbose/' . $class_safe . '-' . $id . '.html');
$this->error(l(t('Verbose message'), $url, array('attributes' => array('target' => '_blank'))), 'User notice');
}
}
Expand All @@ -466,7 +467,8 @@ protected function verbose($message) {
*/
public function run(array $methods = array()) {
// Initialize verbose debugging.
simpletest_verbose(NULL, variable_get('file_public_path', conf_path() . '/files'), get_class($this));
$class = get_class($this);
simpletest_verbose(NULL, variable_get('file_public_path', conf_path() . '/files'), str_replace('\\', '_', $class));

// HTTP auth settings (<username>:<password>) for the simpletest browser
// when sending requests to the test site.
Expand All @@ -478,7 +480,6 @@ public function run(array $methods = array()) {
}

set_error_handler(array($this, 'errorHandler'));
$class = get_class($this);
// Iterate through all the methods in this class, unless a specific list of
// methods to run was passed.
$class_methods = get_class_methods($class);
Expand Down
18 changes: 18 additions & 0 deletions modules/simpletest/lib/Drupal/simpletest/Tests/PSR0WebTest.php
Original file line number Diff line number Diff line change
@@ -0,0 +1,18 @@
<?php

namespace Drupal\simpletest\Tests;

class PSR0WebTest extends \DrupalWebTestCase {

public static function getInfo() {
return array(
'name' => 'PSR0 web test',
'description' => 'We want to assert that this PSR-0 test case is being discovered.',
'group' => 'SimpleTest',
);
}

function testArithmetics() {
$this->assert(1 + 1 == 2, '1 + 1 == 2');
}
}
107 changes: 107 additions & 0 deletions modules/simpletest/simpletest.module
Original file line number Diff line number Diff line change
Expand Up @@ -157,6 +157,7 @@ function simpletest_run_tests($test_list, $reporter = 'drupal') {
* Batch operation callback.
*/
function _simpletest_batch_operation($test_list_init, $test_id, &$context) {
simpletest_classloader_register();
// Get working values.
if (!isset($context['sandbox']['max'])) {
// First iteration: initialize working values.
Expand Down Expand Up @@ -289,6 +290,9 @@ function simpletest_log_read($test_id, $prefix, $test_class, $during_test = FALS
* a static variable. In order to list tests provided by disabled modules
* hook_registry_files_alter() is used to forcefully add them to the registry.
*
* PSR-0 classes are found by searching the designated directory for each module
* for files matching the PSR-0 standard.
*
* @return
* An array of tests keyed with the groups specified in each of the tests
* getInfo() method and then keyed by the test class. An example of the array
Expand All @@ -309,6 +313,9 @@ function simpletest_test_get_all() {
$groups = &drupal_static(__FUNCTION__);

if (!$groups) {
// Register a simple class loader for PSR-0 test classes.
simpletest_classloader_register();

// Load test information from cache if available, otherwise retrieve the
// information from each tests getInfo() method.
if ($cache = cache_get('simpletest', 'cache')) {
Expand All @@ -318,6 +325,34 @@ function simpletest_test_get_all() {
// Select all clases in files ending with .test.
$classes = db_query("SELECT name FROM {registry} WHERE type = :type AND filename LIKE :name", array(':type' => 'class', ':name' => '%.test'))->fetchCol();

// Also discover PSR-0 test classes, if the PHP version allows it.
if (version_compare(PHP_VERSION, '5.3') > 0) {

// Select all PSR-0 classes in the Tests namespace of all modules.
$system_list = db_query("SELECT name, filename FROM {system}")->fetchAllKeyed();

foreach ($system_list as $name => $filename) {
// Build directory in which the test files would reside.
$tests_dir = DRUPAL_ROOT . '/' . dirname($filename) . '/lib/Drupal/' . $name . '/Tests';
// Scan it for test files if it exists.
if (is_dir($tests_dir)) {
$files = file_scan_directory($tests_dir, '/.*\.php/');
if (!empty($files)) {
$basedir = DRUPAL_ROOT . '/' . dirname($filename) . '/lib/';
foreach ($files as $file) {
// Convert the file name into the namespaced class name.
$replacements = array(
'/' => '\\',
$basedir => '',
'.php' => '',
);
$classes[] = strtr($file->uri, $replacements);
}
}
}
}
}

// Check that each class has a getInfo() method and store the information
// in an array keyed with the group specified in the test information.
$groups = array();
Expand Down Expand Up @@ -353,6 +388,78 @@ function simpletest_test_get_all() {
return $groups;
}

/*
* Register a simple class loader that can find D8-style PSR-0 test classes.
*
* Other PSR-0 class loading can happen in contrib, but those contrib class
* loader modules will not be enabled when testbot runs. So we need to do this
* one in core.
*/
function simpletest_classloader_register() {

// Prevent duplicate classloader registration.
static $first_run = TRUE;
if (!$first_run) {
return;
}
$first_run = FALSE;

// Only register PSR-0 class loading if we are on PHP 5.3 or higher.
if (version_compare(PHP_VERSION, '5.3') > 0) {
spl_autoload_register('_simpletest_autoload_psr0');
}
}

/**
* Autoload callback to find PSR-0 test classes.
*
* This will only work on classes where the namespace is of the pattern
* "Drupal\$extension\Tests\.."
*/
function _simpletest_autoload_psr0($class) {

// Static cache for extension paths.
// This cache is lazily filled as soon as it is needed.
static $extensions;

// Check that the first namespace fragment is "Drupal\"
if (substr($class, 0, 7) === 'Drupal\\') {
// Find the position of the second namespace separator.
$pos = strpos($class, '\\', 7);
// Check that the third namespace fragment is "\Tests\".
if (substr($class, $pos, 7) === '\\Tests\\') {

// Extract the second namespace fragment, which we expect to be the
// extension name.
$extension = substr($class, 7, $pos - 7);

// Lazy-load the extension paths, both enabled and disabled.
if (!isset($extensions)) {
$extensions = db_query("SELECT name, filename FROM {system}")->fetchAllKeyed();
}

// Check if the second namespace fragment is a known extension name.
if (isset($extensions[$extension])) {

// Split the class into namespace and classname.
$nspos = strrpos($class, '\\');
$namespace = substr($class, 0, $nspos);
$classname = substr($class, $nspos + 1);

// Build the filepath where we expect the class to be defined.
$path = dirname($extensions[$extension]) . '/lib/' .
str_replace('\\', '/', $namespace) . '/' .
str_replace('_', '/', $classname) . '.php';

// Include the file, if it does exist.
if (file_exists($path)) {
include $path;
}
}
}
}
}

/**
* Implements hook_registry_files_alter().
*
Expand Down
3 changes: 3 additions & 0 deletions modules/simpletest/simpletest.pages.inc
Original file line number Diff line number Diff line change
Expand Up @@ -181,6 +181,7 @@ function theme_simpletest_test_table($variables) {
* Run selected tests.
*/
function simpletest_test_form_submit($form, &$form_state) {
simpletest_classloader_register();
// Get list of tests.
$tests_list = array();
foreach ($form_state['values'] as $class_name => $value) {
Expand Down Expand Up @@ -233,6 +234,8 @@ function simpletest_result_form($form, &$form_state, $test_id) {
'#debug' => 0,
);

simpletest_classloader_register();

// Cycle through each test group.
$header = array(t('Message'), t('Group'), t('Filename'), t('Line'), t('Function'), array('colspan' => 2, 'data' => t('Status')));
$form['result']['results'] = array();
Expand Down
89 changes: 89 additions & 0 deletions modules/simpletest/simpletest.test
Original file line number Diff line number Diff line change
Expand Up @@ -655,3 +655,92 @@ class SimpleTestOtherInstallationProfileModuleTestsTestCase extends DrupalWebTes
$this->assertNoText('Installation profile module tests helper');
}
}

/**
* Verifies that tests in other installation profiles are not found.
*
* @see SimpleTestInstallationProfileModuleTestsTestCase
*/
class SimpleTestDiscoveryTestCase extends DrupalWebTestCase {
/**
* Use the Testing profile.
*
* The Testing profile contains drupal_system_listing_compatible_test.test,
* which attempts to:
* - run tests using the Minimal profile (which does not contain the
* drupal_system_listing_compatible_test.module)
* - but still install the drupal_system_listing_compatible_test.module
* contained in the Testing profile.
*
* @see DrupalSystemListingCompatibleTestCase
*/
protected $profile = 'testing';

public static function getInfo() {
return array(
'name' => 'Discovery of test classes',
'description' => 'Verifies that tests classes are discovered and can be autoloaded (class_exists).',
'group' => 'SimpleTest',
);
}

function setUp() {
parent::setUp(array('simpletest'));

$this->admin_user = $this->drupalCreateUser(array('administer unit tests'));
$this->drupalLogin($this->admin_user);
}

/**
* Test discovery of PSR-0 test classes.
*/
function testDiscoveryFunctions() {
if (version_compare(PHP_VERSION, '5.3') < 0) {
// Don't expect PSR-0 tests to be discovered on older PHP versions.
return;
}
// TODO: What if we have cached values? Do we need to force a cache refresh?
$classes_all = simpletest_test_get_all();
foreach (array(
'Drupal\\simpletest\\Tests\\PSR0WebTest',
'Drupal\\psr_0_test\\Tests\\ExampleTest',
) as $class) {
$this->assert(!empty($classes_all['SimpleTest'][$class]), t('Class @class must be discovered by simpletest_test_get_all().', array('@class' => $class)));
}
}

/**
* Tests existence of test cases.
*/
function testDiscovery() {
$this->drupalGet('admin/config/development/testing');
// Tests within enabled modules.
// (without these, this test wouldn't happen in the first place, so this is
// a bit pointless. We still run it for proof-of-concept.)
// This one is defined in system module.
$this->assertText('Drupal error handlers');
// This one is defined in simpletest module.
$this->assertText('Discovery of test classes');
// Tests within disabled modules.
if (version_compare(PHP_VERSION, '5.3') < 0) {
// Don't expect PSR-0 tests to be discovered on older PHP versions.
return;
}
// This one is provided by simpletest itself via PSR-0.
$this->assertText('PSR0 web test');
$this->assertText('PSR0 example test: PSR-0 in disabled modules.');
$this->assertText('PSR0 example test: PSR-0 in nested subfolders.');

// Test each test individually.
foreach (array(
'Drupal\\psr_0_test\\Tests\\ExampleTest',
'Drupal\\psr_0_test\\Tests\\Nested\\NestedExampleTest',
) as $class) {
$this->drupalGet('admin/config/development/testing');
$edit = array($class => TRUE);
$this->drupalPost(NULL, $edit, t('Run tests'));
$this->assertText('The test run finished', t('Test @class must finish.', array('@class' => $class)));
$this->assertText('1 pass, 0 fails, and 0 exceptions', t('Test @class must pass.', array('@class' => $class)));
}
}
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,18 @@
<?php

namespace Drupal\psr_0_test\Tests;

class ExampleTest extends \DrupalWebTestCase {

public static function getInfo() {
return array(
'name' => 'PSR0 example test: PSR-0 in disabled modules.',
'description' => 'We want to assert that this test case is being discovered.',
'group' => 'SimpleTest',
);
}

function testArithmetics() {
$this->assert(1 + 1 == 2, '1 + 1 == 2');
}
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,18 @@
<?php

namespace Drupal\psr_0_test\Tests\Nested;

class NestedExampleTest extends \DrupalWebTestCase {

public static function getInfo() {
return array(
'name' => 'PSR0 example test: PSR-0 in nested subfolders.',
'description' => 'We want to assert that this PSR-0 test case is being discovered.',
'group' => 'SimpleTest',
);
}

function testArithmetics() {
$this->assert(1 + 1 == 2, '1 + 1 == 2');
}
}
6 changes: 6 additions & 0 deletions modules/simpletest/tests/psr_0_test/psr_0_test.info
Original file line number Diff line number Diff line change
@@ -0,0 +1,6 @@
name = PSR-0 Test cases
description = Test classes to be discovered by simpletest.
core = 7.x

hidden = TRUE
package = Testing
1 change: 1 addition & 0 deletions modules/simpletest/tests/psr_0_test/psr_0_test.module
Original file line number Diff line number Diff line change
@@ -0,0 +1 @@
<?php
4 changes: 3 additions & 1 deletion scripts/run-tests.sh
Original file line number Diff line number Diff line change
Expand Up @@ -362,6 +362,8 @@ function simpletest_script_run_one_test($test_id, $test_class) {
// Bootstrap Drupal.
drupal_bootstrap(DRUPAL_BOOTSTRAP_FULL);

simpletest_classloader_register();

$test = new $test_class($test_id);
$test->run();
$info = $test->getInfo();
Expand Down Expand Up @@ -395,7 +397,7 @@ function simpletest_script_command($test_id, $test_class) {
if ($args['color']) {
$command .= ' --color';
}
$command .= " --php " . escapeshellarg($php) . " --test-id $test_id --execute-test $test_class";
$command .= " --php " . escapeshellarg($php) . " --test-id $test_id --execute-test " . escapeshellarg($test_class);
return $command;
}

Expand Down

0 comments on commit 7cbe4b7

Please sign in to comment.