Skip to content

Commit 7cbe4b7

Browse files
Issue #1693398 by donquixote, pounard, sun, Sylvain Lecoy: Allow PSR-0 test classes to be used in D7.
1 parent de6e29d commit 7cbe4b7

File tree

11 files changed

+269
-4
lines changed

11 files changed

+269
-4
lines changed

CHANGELOG.txt

+2
Original file line numberDiff line numberDiff line change
@@ -1,6 +1,8 @@
11

22
Drupal 7.22, xxxx-xx-xx (development version)
33
-----------------------
4+
- Changed the Simpletest module to allow PSR-0 test classes to be used in
5+
Drupal 7.
46
- Removed an unnecessary "Content-Disposition" header from private file
57
downloads; it prevented many private files from being viewed inline in a web
68
browser.

modules/simpletest/drupal_web_test_case.php

+4-3
Original file line numberDiff line numberDiff line change
@@ -447,7 +447,8 @@ protected function error($message = '', $group = 'Other', array $caller = NULL)
447447
*/
448448
protected function verbose($message) {
449449
if ($id = simpletest_verbose($message)) {
450-
$url = file_create_url($this->originalFileDirectory . '/simpletest/verbose/' . get_class($this) . '-' . $id . '.html');
450+
$class_safe = str_replace('\\', '_', get_class($this));
451+
$url = file_create_url($this->originalFileDirectory . '/simpletest/verbose/' . $class_safe . '-' . $id . '.html');
451452
$this->error(l(t('Verbose message'), $url, array('attributes' => array('target' => '_blank'))), 'User notice');
452453
}
453454
}
@@ -466,7 +467,8 @@ protected function verbose($message) {
466467
*/
467468
public function run(array $methods = array()) {
468469
// Initialize verbose debugging.
469-
simpletest_verbose(NULL, variable_get('file_public_path', conf_path() . '/files'), get_class($this));
470+
$class = get_class($this);
471+
simpletest_verbose(NULL, variable_get('file_public_path', conf_path() . '/files'), str_replace('\\', '_', $class));
470472

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

480482
set_error_handler(array($this, 'errorHandler'));
481-
$class = get_class($this);
482483
// Iterate through all the methods in this class, unless a specific list of
483484
// methods to run was passed.
484485
$class_methods = get_class_methods($class);
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,18 @@
1+
<?php
2+
3+
namespace Drupal\simpletest\Tests;
4+
5+
class PSR0WebTest extends \DrupalWebTestCase {
6+
7+
public static function getInfo() {
8+
return array(
9+
'name' => 'PSR0 web test',
10+
'description' => 'We want to assert that this PSR-0 test case is being discovered.',
11+
'group' => 'SimpleTest',
12+
);
13+
}
14+
15+
function testArithmetics() {
16+
$this->assert(1 + 1 == 2, '1 + 1 == 2');
17+
}
18+
}

modules/simpletest/simpletest.module

+107
Original file line numberDiff line numberDiff line change
@@ -157,6 +157,7 @@ function simpletest_run_tests($test_list, $reporter = 'drupal') {
157157
* Batch operation callback.
158158
*/
159159
function _simpletest_batch_operation($test_list_init, $test_id, &$context) {
160+
simpletest_classloader_register();
160161
// Get working values.
161162
if (!isset($context['sandbox']['max'])) {
162163
// First iteration: initialize working values.
@@ -289,6 +290,9 @@ function simpletest_log_read($test_id, $prefix, $test_class, $during_test = FALS
289290
* a static variable. In order to list tests provided by disabled modules
290291
* hook_registry_files_alter() is used to forcefully add them to the registry.
291292
*
293+
* PSR-0 classes are found by searching the designated directory for each module
294+
* for files matching the PSR-0 standard.
295+
*
292296
* @return
293297
* An array of tests keyed with the groups specified in each of the tests
294298
* getInfo() method and then keyed by the test class. An example of the array
@@ -309,6 +313,9 @@ function simpletest_test_get_all() {
309313
$groups = &drupal_static(__FUNCTION__);
310314

311315
if (!$groups) {
316+
// Register a simple class loader for PSR-0 test classes.
317+
simpletest_classloader_register();
318+
312319
// Load test information from cache if available, otherwise retrieve the
313320
// information from each tests getInfo() method.
314321
if ($cache = cache_get('simpletest', 'cache')) {
@@ -318,6 +325,34 @@ function simpletest_test_get_all() {
318325
// Select all clases in files ending with .test.
319326
$classes = db_query("SELECT name FROM {registry} WHERE type = :type AND filename LIKE :name", array(':type' => 'class', ':name' => '%.test'))->fetchCol();
320327

328+
// Also discover PSR-0 test classes, if the PHP version allows it.
329+
if (version_compare(PHP_VERSION, '5.3') > 0) {
330+
331+
// Select all PSR-0 classes in the Tests namespace of all modules.
332+
$system_list = db_query("SELECT name, filename FROM {system}")->fetchAllKeyed();
333+
334+
foreach ($system_list as $name => $filename) {
335+
// Build directory in which the test files would reside.
336+
$tests_dir = DRUPAL_ROOT . '/' . dirname($filename) . '/lib/Drupal/' . $name . '/Tests';
337+
// Scan it for test files if it exists.
338+
if (is_dir($tests_dir)) {
339+
$files = file_scan_directory($tests_dir, '/.*\.php/');
340+
if (!empty($files)) {
341+
$basedir = DRUPAL_ROOT . '/' . dirname($filename) . '/lib/';
342+
foreach ($files as $file) {
343+
// Convert the file name into the namespaced class name.
344+
$replacements = array(
345+
'/' => '\\',
346+
$basedir => '',
347+
'.php' => '',
348+
);
349+
$classes[] = strtr($file->uri, $replacements);
350+
}
351+
}
352+
}
353+
}
354+
}
355+
321356
// Check that each class has a getInfo() method and store the information
322357
// in an array keyed with the group specified in the test information.
323358
$groups = array();
@@ -353,6 +388,78 @@ function simpletest_test_get_all() {
353388
return $groups;
354389
}
355390

391+
/*
392+
* Register a simple class loader that can find D8-style PSR-0 test classes.
393+
*
394+
* Other PSR-0 class loading can happen in contrib, but those contrib class
395+
* loader modules will not be enabled when testbot runs. So we need to do this
396+
* one in core.
397+
*/
398+
function simpletest_classloader_register() {
399+
400+
// Prevent duplicate classloader registration.
401+
static $first_run = TRUE;
402+
if (!$first_run) {
403+
return;
404+
}
405+
$first_run = FALSE;
406+
407+
// Only register PSR-0 class loading if we are on PHP 5.3 or higher.
408+
if (version_compare(PHP_VERSION, '5.3') > 0) {
409+
spl_autoload_register('_simpletest_autoload_psr0');
410+
}
411+
}
412+
413+
/**
414+
* Autoload callback to find PSR-0 test classes.
415+
*
416+
* This will only work on classes where the namespace is of the pattern
417+
* "Drupal\$extension\Tests\.."
418+
*/
419+
function _simpletest_autoload_psr0($class) {
420+
421+
// Static cache for extension paths.
422+
// This cache is lazily filled as soon as it is needed.
423+
static $extensions;
424+
425+
// Check that the first namespace fragment is "Drupal\"
426+
if (substr($class, 0, 7) === 'Drupal\\') {
427+
// Find the position of the second namespace separator.
428+
$pos = strpos($class, '\\', 7);
429+
// Check that the third namespace fragment is "\Tests\".
430+
if (substr($class, $pos, 7) === '\\Tests\\') {
431+
432+
// Extract the second namespace fragment, which we expect to be the
433+
// extension name.
434+
$extension = substr($class, 7, $pos - 7);
435+
436+
// Lazy-load the extension paths, both enabled and disabled.
437+
if (!isset($extensions)) {
438+
$extensions = db_query("SELECT name, filename FROM {system}")->fetchAllKeyed();
439+
}
440+
441+
// Check if the second namespace fragment is a known extension name.
442+
if (isset($extensions[$extension])) {
443+
444+
// Split the class into namespace and classname.
445+
$nspos = strrpos($class, '\\');
446+
$namespace = substr($class, 0, $nspos);
447+
$classname = substr($class, $nspos + 1);
448+
449+
// Build the filepath where we expect the class to be defined.
450+
$path = dirname($extensions[$extension]) . '/lib/' .
451+
str_replace('\\', '/', $namespace) . '/' .
452+
str_replace('_', '/', $classname) . '.php';
453+
454+
// Include the file, if it does exist.
455+
if (file_exists($path)) {
456+
include $path;
457+
}
458+
}
459+
}
460+
}
461+
}
462+
356463
/**
357464
* Implements hook_registry_files_alter().
358465
*

modules/simpletest/simpletest.pages.inc

+3
Original file line numberDiff line numberDiff line change
@@ -181,6 +181,7 @@ function theme_simpletest_test_table($variables) {
181181
* Run selected tests.
182182
*/
183183
function simpletest_test_form_submit($form, &$form_state) {
184+
simpletest_classloader_register();
184185
// Get list of tests.
185186
$tests_list = array();
186187
foreach ($form_state['values'] as $class_name => $value) {
@@ -233,6 +234,8 @@ function simpletest_result_form($form, &$form_state, $test_id) {
233234
'#debug' => 0,
234235
);
235236

237+
simpletest_classloader_register();
238+
236239
// Cycle through each test group.
237240
$header = array(t('Message'), t('Group'), t('Filename'), t('Line'), t('Function'), array('colspan' => 2, 'data' => t('Status')));
238241
$form['result']['results'] = array();

modules/simpletest/simpletest.test

+89
Original file line numberDiff line numberDiff line change
@@ -655,3 +655,92 @@ class SimpleTestOtherInstallationProfileModuleTestsTestCase extends DrupalWebTes
655655
$this->assertNoText('Installation profile module tests helper');
656656
}
657657
}
658+
659+
/**
660+
* Verifies that tests in other installation profiles are not found.
661+
*
662+
* @see SimpleTestInstallationProfileModuleTestsTestCase
663+
*/
664+
class SimpleTestDiscoveryTestCase extends DrupalWebTestCase {
665+
/**
666+
* Use the Testing profile.
667+
*
668+
* The Testing profile contains drupal_system_listing_compatible_test.test,
669+
* which attempts to:
670+
* - run tests using the Minimal profile (which does not contain the
671+
* drupal_system_listing_compatible_test.module)
672+
* - but still install the drupal_system_listing_compatible_test.module
673+
* contained in the Testing profile.
674+
*
675+
* @see DrupalSystemListingCompatibleTestCase
676+
*/
677+
protected $profile = 'testing';
678+
679+
public static function getInfo() {
680+
return array(
681+
'name' => 'Discovery of test classes',
682+
'description' => 'Verifies that tests classes are discovered and can be autoloaded (class_exists).',
683+
'group' => 'SimpleTest',
684+
);
685+
}
686+
687+
function setUp() {
688+
parent::setUp(array('simpletest'));
689+
690+
$this->admin_user = $this->drupalCreateUser(array('administer unit tests'));
691+
$this->drupalLogin($this->admin_user);
692+
}
693+
694+
/**
695+
* Test discovery of PSR-0 test classes.
696+
*/
697+
function testDiscoveryFunctions() {
698+
if (version_compare(PHP_VERSION, '5.3') < 0) {
699+
// Don't expect PSR-0 tests to be discovered on older PHP versions.
700+
return;
701+
}
702+
// TODO: What if we have cached values? Do we need to force a cache refresh?
703+
$classes_all = simpletest_test_get_all();
704+
foreach (array(
705+
'Drupal\\simpletest\\Tests\\PSR0WebTest',
706+
'Drupal\\psr_0_test\\Tests\\ExampleTest',
707+
) as $class) {
708+
$this->assert(!empty($classes_all['SimpleTest'][$class]), t('Class @class must be discovered by simpletest_test_get_all().', array('@class' => $class)));
709+
}
710+
}
711+
712+
/**
713+
* Tests existence of test cases.
714+
*/
715+
function testDiscovery() {
716+
$this->drupalGet('admin/config/development/testing');
717+
// Tests within enabled modules.
718+
// (without these, this test wouldn't happen in the first place, so this is
719+
// a bit pointless. We still run it for proof-of-concept.)
720+
// This one is defined in system module.
721+
$this->assertText('Drupal error handlers');
722+
// This one is defined in simpletest module.
723+
$this->assertText('Discovery of test classes');
724+
// Tests within disabled modules.
725+
if (version_compare(PHP_VERSION, '5.3') < 0) {
726+
// Don't expect PSR-0 tests to be discovered on older PHP versions.
727+
return;
728+
}
729+
// This one is provided by simpletest itself via PSR-0.
730+
$this->assertText('PSR0 web test');
731+
$this->assertText('PSR0 example test: PSR-0 in disabled modules.');
732+
$this->assertText('PSR0 example test: PSR-0 in nested subfolders.');
733+
734+
// Test each test individually.
735+
foreach (array(
736+
'Drupal\\psr_0_test\\Tests\\ExampleTest',
737+
'Drupal\\psr_0_test\\Tests\\Nested\\NestedExampleTest',
738+
) as $class) {
739+
$this->drupalGet('admin/config/development/testing');
740+
$edit = array($class => TRUE);
741+
$this->drupalPost(NULL, $edit, t('Run tests'));
742+
$this->assertText('The test run finished', t('Test @class must finish.', array('@class' => $class)));
743+
$this->assertText('1 pass, 0 fails, and 0 exceptions', t('Test @class must pass.', array('@class' => $class)));
744+
}
745+
}
746+
}
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,18 @@
1+
<?php
2+
3+
namespace Drupal\psr_0_test\Tests;
4+
5+
class ExampleTest extends \DrupalWebTestCase {
6+
7+
public static function getInfo() {
8+
return array(
9+
'name' => 'PSR0 example test: PSR-0 in disabled modules.',
10+
'description' => 'We want to assert that this test case is being discovered.',
11+
'group' => 'SimpleTest',
12+
);
13+
}
14+
15+
function testArithmetics() {
16+
$this->assert(1 + 1 == 2, '1 + 1 == 2');
17+
}
18+
}
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,18 @@
1+
<?php
2+
3+
namespace Drupal\psr_0_test\Tests\Nested;
4+
5+
class NestedExampleTest extends \DrupalWebTestCase {
6+
7+
public static function getInfo() {
8+
return array(
9+
'name' => 'PSR0 example test: PSR-0 in nested subfolders.',
10+
'description' => 'We want to assert that this PSR-0 test case is being discovered.',
11+
'group' => 'SimpleTest',
12+
);
13+
}
14+
15+
function testArithmetics() {
16+
$this->assert(1 + 1 == 2, '1 + 1 == 2');
17+
}
18+
}
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,6 @@
1+
name = PSR-0 Test cases
2+
description = Test classes to be discovered by simpletest.
3+
core = 7.x
4+
5+
hidden = TRUE
6+
package = Testing
Original file line numberDiff line numberDiff line change
@@ -0,0 +1 @@
1+
<?php

scripts/run-tests.sh

+3-1
Original file line numberDiff line numberDiff line change
@@ -362,6 +362,8 @@ function simpletest_script_run_one_test($test_id, $test_class) {
362362
// Bootstrap Drupal.
363363
drupal_bootstrap(DRUPAL_BOOTSTRAP_FULL);
364364

365+
simpletest_classloader_register();
366+
365367
$test = new $test_class($test_id);
366368
$test->run();
367369
$info = $test->getInfo();
@@ -395,7 +397,7 @@ function simpletest_script_command($test_id, $test_class) {
395397
if ($args['color']) {
396398
$command .= ' --color';
397399
}
398-
$command .= " --php " . escapeshellarg($php) . " --test-id $test_id --execute-test $test_class";
400+
$command .= " --php " . escapeshellarg($php) . " --test-id $test_id --execute-test " . escapeshellarg($test_class);
399401
return $command;
400402
}
401403

0 commit comments

Comments
 (0)