diff --git a/core/modules/simpletest/src/TestBase.php b/core/modules/simpletest/src/TestBase.php index 8fa831e735ce83a647f65f8eb1ee79ef350250ab..abf09fd05d87ae6e4d5d45f2232b36a04fed1096 100644 --- a/core/modules/simpletest/src/TestBase.php +++ b/core/modules/simpletest/src/TestBase.php @@ -9,8 +9,8 @@ use Drupal\Component\Utility\Crypt; use Drupal\Component\Utility\Random; -use Drupal\Core\Database\Database; use Drupal\Component\Utility\String; +use Drupal\Core\Database\Database; use Drupal\Core\Config\ConfigImporter; use Drupal\Core\Config\StorageComparer; use Drupal\Core\DependencyInjection\ContainerBuilder; @@ -343,6 +343,23 @@ protected function checkRequirements() { return array(); } + /** + * Helper method to store an assertion record in the configured database. + * + * This method decouples database access from assertion logic. + * + * @param array $assertion + * Keyed array representing an assertion, as generated by assert(). + * + * @see self::assert() + */ + protected function storeAssertion(array $assertion) { + return self::getDatabaseConnection() + ->insert('simpletest') + ->fields($assertion) + ->execute(); + } + /** * Internal helper: stores the assert. * @@ -393,10 +410,7 @@ protected function assert($status, $message = '', $group = 'Other', array $calle ); // Store assertion for display after the test has completed. - self::getDatabaseConnection() - ->insert('simpletest') - ->fields($assertion) - ->execute(); + $this->storeAssertion($assertion); // We do not use a ternary operator here to allow a breakpoint on // test failure. @@ -450,6 +464,7 @@ public static function insertAssert($test_id, $test_class, $status, $message = ' 'file' => $caller['file'], ); + // We can't use storeAssertion() because this method is static. return self::getDatabaseConnection() ->insert('simpletest') ->fields($assertion) @@ -468,6 +483,7 @@ public static function insertAssert($test_id, $test_class, $status, $message = ' * @see \Drupal\simpletest\TestBase::insertAssert() */ public static function deleteAssert($message_id) { + // We can't use storeAssertion() because this method is static. return (bool) self::getDatabaseConnection() ->delete('simpletest') ->condition('message_id', $message_id) @@ -1334,7 +1350,6 @@ private function restoreEnvironment() { */ public function errorHandler($severity, $message, $file = NULL, $line = NULL) { if ($severity & error_reporting()) { - require_once DRUPAL_ROOT . '/core/includes/errors.inc'; $error_map = array( E_STRICT => 'Run-time notice', E_WARNING => 'Warning', @@ -1369,7 +1384,6 @@ public function errorHandler($severity, $message, $file = NULL, $line = NULL) { * @see set_exception_handler */ protected function exceptionHandler($exception) { - require_once DRUPAL_ROOT . '/core/includes/errors.inc'; $backtrace = $exception->getTrace(); $verbose_backtrace = $backtrace; // Push on top of the backtrace the call that generated the exception. diff --git a/core/modules/simpletest/tests/src/Unit/TestBaseTest.php b/core/modules/simpletest/tests/src/Unit/TestBaseTest.php index 9bac4fc39bbfdf2cacfaab61d634319de34cd7ed..6fa9c551ba1612ddb715f7b72d4272bd7b79d418 100644 --- a/core/modules/simpletest/tests/src/Unit/TestBaseTest.php +++ b/core/modules/simpletest/tests/src/Unit/TestBaseTest.php @@ -2,7 +2,7 @@ /** * @file - * Contains \Drupal\simpletest\TestBaseTest. + * Contains \Drupal\Tests\simpletest\Unit\TestBaseTest. */ namespace Drupal\Tests\simpletest\Unit; @@ -16,68 +16,457 @@ class TestBaseTest extends UnitTestCase { /** - * A stub built using the TestBase class. + * Helper method for constructing a mock TestBase object. * - * @var \PHPUnit_Framework_MockObject_MockObject + * TestBase is abstract, so we have to mock it. We'll also + * mock the storeAssertion() method so we don't need the database. + * + * @param string $test_id + * An identifying name for the mocked test. + * + * @return object + * Mock of Drupal\simpletest\TestBase. */ - protected $stub; + public function getTestBaseForAssertionTests($test_id) { + $mock_test_base = $this->getMockBuilder('Drupal\simpletest\TestBase') + ->setConstructorArgs(array($test_id)) + ->setMethods(array('storeAssertion')) + ->getMockForAbstractClass(); + // Override storeAssertion() so we don't need a database. + $mock_test_base->expects($this->any()) + ->method('storeAssertion') + ->will($this->returnValue(NULL)); + return $mock_test_base; + } - protected function setUp() { - $this->stub = $this->getMockForAbstractClass('Drupal\simpletest\TestBase'); + /** + * Invoke methods that are protected or private. + * + * @param object $object + * Object on which to invoke the method. + * @param string $method_name + * Name of the method to invoke. + * @param array $arguments + * Array of arguments to be passed to the method. + * + * @return mixed + * Value returned by the invoked method. + */ + public function invokeProtectedMethod($object, $method_name, array $arguments) { + $ref_method = new \ReflectionMethod($object, $method_name); + $ref_method->setAccessible(TRUE); + return $ref_method->invokeArgs($object, $arguments); } /** * Provides data for the random string validation test. * * @return array - * An array of values passed to the test method. + * - The expected result of the validation. + * - The string to validate. */ - public function randomStringValidateProvider () { + public function providerRandomStringValidate() { return array( - array(' curry paste', FALSE), - array('curry paste ', FALSE), - array('curry paste', FALSE), - array('curry paste', FALSE), - array('curry paste', TRUE), - array('thai green curry paste', TRUE), - array('@startswithat', FALSE), - array('contains@at', TRUE), + array(FALSE, ' curry paste'), + array(FALSE, 'curry paste '), + array(FALSE, 'curry paste'), + array(FALSE, 'curry paste'), + array(TRUE, 'curry paste'), + array(TRUE, 'thai green curry paste'), + array(FALSE, '@startswithat'), + array(TRUE, 'contains@at'), ); } /** - * Tests the random strings validation rules. - * - * @param string $string - * The string to validate. - * @param bool $expected - * The expected result of the validation. - * - * @see \Drupal\simpletest\TestBase::randomStringValidate(). - * - * @dataProvider randomStringValidateProvider * @covers ::randomStringValidate + * @dataProvider providerRandomStringValidate */ - public function testRandomStringValidate($string, $expected) { - $actual = $this->stub->randomStringValidate($string); + public function testRandomStringValidate($expected, $string) { + $mock_test_base = $this->getMockForAbstractClass('Drupal\simpletest\TestBase'); + $actual = $mock_test_base->randomStringValidate($string); $this->assertEquals($expected, $actual); } /** - * Tests that the random string contains a non-alphanumeric character. - * - * @see \Drupal\simpletest\TestBase::randomString(). + * Provides data for testRandomString() and others. * + * @return array + * - The number of items (characters, object properties) we expect any of + * the random functions to give us. + */ + public function providerRandomItems() { + return [ + [NULL], + [0], + [1], + [2], + [3], + [4], + [7], + ]; + } + + /** * @covers ::randomString + * @dataProvider providerRandomItems + */ + public function testRandomString($length) { + $mock_test_base = $this->getMockForAbstractClass('Drupal\simpletest\TestBase'); + $string = $mock_test_base->randomString($length); + $this->assertEquals($length, strlen($string)); + // randomString() should always include an ampersand ('&') if $length is + // greater than 2. + if ($length > 2) { + $this->assertContains('&', $string); + } + } + + /** + * @covers ::randomObject + * @dataProvider providerRandomItems + */ + public function testRandomObject($size) { + $test_base = $this->getTestBaseForAssertionTests('test_id'); + // Note: count((array)object) works for now, maybe not later. + $this->assertEquals($size, count((array) $test_base->randomObject($size))); + } + + /** + * @covers ::checkRequirements + */ + public function testCheckRequirements() { + $test_base = $this->getTestBaseForAssertionTests('test_id'); + $this->assertInternalType( + 'array', + $this->invokeProtectedMethod($test_base, 'checkRequirements', array()) + ); + } + + /** + * Data provider for testAssert(). + * + * @return array + * Standard dataProvider array of arrays: + * - Expected result from assert(). + * - Expected status stored in TestBase->assertions. + * - Status, passed to assert(). + * - Message, passed to assert(). + * - Group, passed to assert(). + * - Caller, passed to assert(). + */ + public function providerAssert() { + return array( + array(TRUE, 'pass', TRUE, 'Yay pass', 'test', array()), + array(FALSE, 'fail', FALSE, 'Boo fail', 'test', array()), + array(TRUE, 'pass', 'pass', 'Yay pass', 'test', array()), + array(FALSE, 'fail', 'fail', 'Boo fail', 'test', array()), + array(FALSE, 'exception', 'exception', 'Boo fail', 'test', array()), + array(FALSE, 'debug', 'debug', 'Boo fail', 'test', array()), + ); + } + + /** + * @covers ::assert + * @dataProvider providerAssert + */ + public function testAssert($expected, $assertion_status, $status, $message, $group, $caller) { + $test_id = 'luke_i_am_your_' . $assertion_status; + $test_base = $this->getTestBaseForAssertionTests($test_id); + + // Verify some startup values. + $this->assertAttributeEmpty('assertions', $test_base); + if (is_string($status)) { + $this->assertEquals(0, $test_base->results['#' . $status]); + } + + // assert() is protected so we have to make it accessible. + $ref_assert = new \ReflectionMethod($test_base, 'assert'); + $ref_assert->setAccessible(TRUE); + + // Call assert() from within our hall of mirrors. + $this->assertEquals( + $expected, + $ref_assert->invokeArgs($test_base, + array($status, $message, $group, $caller) + ) + ); + + // Check the side-effects of assert(). + if (is_string($status)) { + $this->assertEquals(1, $test_base->results['#' . $status]); + } + $this->assertAttributeNotEmpty('assertions', $test_base); + // Make a ReflectionProperty for the assertions property, + // since it's protected. + $ref_assertions = new \ReflectionProperty($test_base, 'assertions'); + $ref_assertions->setAccessible(TRUE); + $assertions = $ref_assertions->getValue($test_base); + $assertion = reset($assertions); + $this->assertEquals($assertion_status, $assertion['status']); + $this->assertEquals($test_id, $assertion['test_id']); + $this->assertEquals(get_class($test_base), $assertion['test_class']); + $this->assertEquals($message, $assertion['message']); + $this->assertEquals($group, $assertion['message_group']); + } + + /** + * Data provider for assertTrue(). + */ + public function providerAssertTrue() { + return array( + array(TRUE, TRUE), + array(FALSE, FALSE), + ); + } + + /** + * @covers ::assertTrue + * @dataProvider providerAssertTrue + */ + public function testAssertTrue($expected, $value) { + $test_base = $this->getTestBaseForAssertionTests('test_id'); + $this->assertEquals( + $expected, + $this->invokeProtectedMethod($test_base, 'assertTrue', array($value)) + ); + } + + /** + * @covers ::assertFalse + * @dataProvider providerAssertTrue + */ + public function testAssertFalse($expected, $value) { + $test_base = $this->getTestBaseForAssertionTests('test_id'); + $this->assertEquals( + (!$expected), + $this->invokeProtectedMethod($test_base, 'assertFalse', array($value)) + ); + } + + /** + * Data provider for assertNull(). + */ + public function providerAssertNull() { + return array( + array(TRUE, NULL), + array(FALSE, ''), + ); + } + + /** + * @covers ::assertNull + * @dataProvider providerAssertNull + */ + public function testAssertNull($expected, $value) { + $test_base = $this->getTestBaseForAssertionTests('test_id'); + $this->assertEquals( + $expected, + $this->invokeProtectedMethod($test_base, 'assertNull', array($value)) + ); + } + + /** + * @covers ::assertNotNull + * @dataProvider providerAssertNull + */ + public function testAssertNotNull($expected, $value) { + $test_base = $this->getTestBaseForAssertionTests('test_id'); + $this->assertEquals( + (!$expected), + $this->invokeProtectedMethod($test_base, 'assertNotNull', array($value)) + ); + } + + /** + * Data provider for tests of equality assertions. + * + * Used by testAssertIdentical(), testAssertEqual(), testAssertNotIdentical(), + * and testAssertNotEqual(). + * + * @return + * Array of test data. + * - Expected assertion value for identical comparison. + * - Expected assertion value for equal comparison. + * - First value to compare. + * - Second value to compare. + */ + public function providerEqualityAssertions() { + return [ + // Integers and floats. + [TRUE, TRUE, 0, 0], + [FALSE, TRUE, 0, 0.0], + [FALSE, TRUE, '0', 0], + [FALSE, TRUE, '0.0', 0.0], + [FALSE, FALSE, 23, 77], + [TRUE, TRUE, 23.0, 23.0], + // Strings. + [FALSE, FALSE, 'foof', 'yay'], + [TRUE, TRUE, 'yay', 'yay'], + // Bools with type conversion. + [TRUE, TRUE, TRUE, TRUE], + [TRUE, TRUE, FALSE, FALSE], + [FALSE, TRUE, NULL, FALSE], + [FALSE, TRUE, 'TRUE', TRUE], + [FALSE, FALSE, 'FALSE', FALSE], + [FALSE, TRUE, 0, FALSE], + [FALSE, TRUE, 1, TRUE], + [FALSE, TRUE, -1, TRUE], + [FALSE, TRUE, '1', TRUE], + [FALSE, TRUE, '1.3', TRUE], + // Null. + [FALSE, FALSE, 'NULL', NULL], + [TRUE, TRUE, NULL, NULL], + ]; + } + + /** + * @covers ::assertIdentical + * @dataProvider providerEqualityAssertions + */ + public function testAssertIdentical($expected_identical, $expected_equal, $first, $second) { + $test_base = $this->getTestBaseForAssertionTests('test_id'); + $this->assertEquals( + $expected_identical, + $this->invokeProtectedMethod($test_base, 'assertIdentical', array($first, $second)) + ); + } + + /** + * @covers ::assertNotIdentical + * @dataProvider providerEqualityAssertions + */ + public function testAssertNotIdentical($expected_identical, $expected_equal, $first, $second) { + $test_base = $this->getTestBaseForAssertionTests('test_id'); + $this->assertEquals( + (!$expected_identical), + $this->invokeProtectedMethod($test_base, 'assertNotIdentical', array($first, $second)) + ); + } + + /** + * @covers ::assertEqual + * @dataProvider providerEqualityAssertions + */ + public function testAssertEqual($expected_identical, $expected_equal, $first, $second) { + $test_base = $this->getTestBaseForAssertionTests('test_id'); + $this->assertEquals( + $expected_equal, + $this->invokeProtectedMethod($test_base, 'assertEqual', array($first, $second)) + ); + } + + /** + * @covers ::assertNotEqual + * @dataProvider providerEqualityAssertions + */ + public function testAssertNotEqual($expected_identical, $expected_equal, $first, $second) { + $test_base = $this->getTestBaseForAssertionTests('test_id'); + $this->assertEquals( + (!$expected_equal), + $this->invokeProtectedMethod($test_base, 'assertNotEqual', array($first, $second)) + ); + } + + /** + * Data provider for testAssertIdenticalObject(). + */ + public function providerAssertIdenticalObject() { + $obj1 = new \stdClass(); + $obj1->foof = 'yay'; + $obj2 = $obj1; + $obj3 = clone $obj1; + $obj4 = new \stdClass(); + return array( + array(TRUE, $obj1, $obj2), + array(TRUE, $obj1, $obj3), + array(FALSE, $obj1, $obj4), + ); + } + + /** + * @covers ::assertIdenticalObject + * @dataProvider providerAssertIdenticalObject + */ + public function testAssertIdenticalObject($expected, $first, $second) { + $test_base = $this->getTestBaseForAssertionTests('test_id'); + $this->assertEquals( + $expected, + $this->invokeProtectedMethod($test_base, 'assertIdenticalObject', array($first, $second)) + ); + } + + /** + * @covers ::pass */ - public function testRandomString() { - $string = $this->stub->randomString(8); - $this->assertEquals(8, strlen($string)); - $this->assertContains('&', $string); + public function testPass() { + $test_base = $this->getTestBaseForAssertionTests('test_id'); + $this->assertEquals( + TRUE, + $this->invokeProtectedMethod($test_base, 'pass', array()) + ); + } - // Ensure that we can generate random strings with a length of 1. - $string = $this->stub->randomString(1); - $this->assertEquals(1, strlen($string)); + /** + * @covers ::fail + */ + public function testFail() { + $test_base = $this->getTestBaseForAssertionTests('test_id'); + $this->assertEquals( + FALSE, + $this->invokeProtectedMethod($test_base, 'fail', array()) + ); + } + + /** + * Data provider for testError(). + * + * @return array + * - Expected status for assertion. + * - Group for use in assert(). + */ + public function providerError() { + return array( + array('debug', 'User notice'), + array('exception', 'Not User notice'), + ); + } + + /** + * @covers ::error + * @dataProvider providerError + */ + public function testError($status, $group) { + // Mock up a TestBase object. + $mock_test_base = $this->getMockBuilder('Drupal\simpletest\TestBase') + ->setMethods(array('assert')) + ->getMockForAbstractClass(); + + // Set expectations for assert(). + $mock_test_base->expects($this->once()) + ->method('assert') + // The first argument to assert() should be the expected $status. This is + // the most important expectation of this test. + ->with($status) + // Arbitrary return value. + ->willReturn("$status:$group"); + + // Invoke error(). + $this->assertEquals( + "$status:$group", + $this->invokeProtectedMethod($mock_test_base, 'error', array('msg', $group)) + ); + } + + /** + * @covers ::getRandomGenerator + */ + public function testGetRandomGenerator() { + $test_base = $this->getTestBaseForAssertionTests('test_id'); + $this->assertInstanceOf( + 'Drupal\Component\Utility\Random', + $this->invokeProtectedMethod($test_base, 'getRandomGenerator', array()) + ); } }