diff --git a/core/lib/Drupal/Core/Test/TestStatus.php b/core/lib/Drupal/Core/Test/TestStatus.php new file mode 100644 index 0000000000000000000000000000000000000000..b70dcdd1d8ad33fbc45ddd572898a15d4adaaef9 --- /dev/null +++ b/core/lib/Drupal/Core/Test/TestStatus.php @@ -0,0 +1,63 @@ +<?php + +namespace Drupal\Core\Test; + +/** + * Consolidates test result status information. + * + * For our test runners, a $status of 0 = passed test, 1 = failed test, + * 2 = exception, >2 indicates segfault timeout, or other type of system + * failure. + */ +class TestStatus { + + /** + * Signify that the test result was a passed test. + */ + const PASS = 0; + + /** + * Signify that the test result was a failed test. + */ + const FAIL = 1; + + /** + * Signify that the test result was an exception or code error. + * + * This means that the test runner was able to exit and report an error. + */ + const EXCEPTION = 2; + + /** + * Signify a system error where the test runner was unable to complete. + * + * Note that SYSTEM actually represents the lowest value of system errors, and + * the returned value could be as high as 127. Since that's the case, this + * constant should be used for range comparisons, and not just for equality. + * + * @see http://php.net/manual/en/pcntl.constants.php + */ + const SYSTEM = 3; + + /** + * Turns a status code into a human-readable string. + * + * @param int $status + * A test runner return code. + * + * @return string + * The human-readable version of the status code. + */ + public static function label($status) { + $statusMap = [ + static::PASS => 'pass', + static::FAIL => 'fail', + static::EXCEPTION => 'exception', + static::SYSTEM => 'error', + ]; + // For status 3 and higher, we want 'error.' + $label = $statusMap[$status > static::SYSTEM ? static::SYSTEM : $status]; + return $label; + } + +} diff --git a/core/modules/simpletest/simpletest.module b/core/modules/simpletest/simpletest.module index f2c34806ca0e1f5dfcee4b1d30baaa7791e2699e..b4776d524b5cb96d25b4456abdc8023a5d249e9a 100644 --- a/core/modules/simpletest/simpletest.module +++ b/core/modules/simpletest/simpletest.module @@ -13,6 +13,7 @@ use Drupal\Core\Test\TestDatabase; use Drupal\simpletest\TestDiscovery; use Symfony\Component\Process\PhpExecutableFinder; +use Drupal\Core\Test\TestStatus; /** * Implements hook_help(). @@ -183,29 +184,15 @@ function simpletest_run_phpunit_tests($test_id, array $unescaped_test_classnames $phpunit_file = simpletest_phpunit_xml_filepath($test_id); simpletest_phpunit_run_command($unescaped_test_classnames, $phpunit_file, $status, $output); - $rows = simpletest_phpunit_xml_to_rows($test_id, $phpunit_file); - // A $status of 0 = passed test, 1 = failed test, > 1 indicates segfault - // timeout, or other type of failure. - if ($status > 1) { - // Something broke during the execution of phpunit. - // Return an error record of all failed classes. - $rows[] = [ - 'test_id' => $test_id, - 'test_class' => implode(",", $unescaped_test_classnames), - 'status' => 'fail', - 'message' => 'PHPunit Test failed to complete; Error: ' . implode("\n", $output), - 'message_group' => 'Other', - 'function' => implode(",", $unescaped_test_classnames), - 'line' => '0', - 'file' => $phpunit_file, - ]; + $rows = []; + if ($status == TestStatus::PASS) { + $rows = simpletest_phpunit_xml_to_rows($test_id, $phpunit_file); } - - if ($status === 1) { + else { $rows[] = [ 'test_id' => $test_id, 'test_class' => implode(",", $unescaped_test_classnames), - 'status' => 'fail', + 'status' => TestStatus::label($status), 'message' => 'PHPunit Test failed to complete; Error: ' . implode("\n", $output), 'message_group' => 'Other', 'function' => implode(",", $unescaped_test_classnames), @@ -213,7 +200,6 @@ function simpletest_run_phpunit_tests($test_id, array $unescaped_test_classnames 'file' => $phpunit_file, ]; } - return $rows; } diff --git a/core/modules/simpletest/tests/fixtures/simpletest_phpunit_run_command_test.php b/core/modules/simpletest/tests/fixtures/simpletest_phpunit_run_command_test.php index 6fb61d523c8f3ed8398109a872ba188b7238e170..a4ffc7ae45a7ed482dd2015af171c14c05ff5f81 100644 --- a/core/modules/simpletest/tests/fixtures/simpletest_phpunit_run_command_test.php +++ b/core/modules/simpletest/tests/fixtures/simpletest_phpunit_run_command_test.php @@ -2,22 +2,27 @@ namespace Drupal\Tests\simpletest\Unit; -use Drupal\Tests\UnitTestCase; - /** * This test crashes PHP. * * To avoid accidentally running, it is not in a normal PSR-4 directory, the * file name does not adhere to PSR-4 and an environment variable also needs to * be set for the crash to happen. + * + * @see \Drupal\Tests\simpletest\Unit\SimpletestPhpunitRunCommandTest::testSimpletestPhpUnitRunCommand() */ -class SimpletestPhpunitRunCommandTestWillDie extends UnitTestCase { +class SimpletestPhpunitRunCommandTestWillDie extends \PHPUnit_Framework_TestCase { + /** + * Performs the status specified by SimpletestPhpunitRunCommandTestWillDie. + */ public function testWillDie() { - if (getenv('SimpletestPhpunitRunCommandTestWillDie') === 'fail') { - exit(2); + $status = (int) getenv('SimpletestPhpunitRunCommandTestWillDie'); + if ($status == 0) { + $this->assertTrue(TRUE, 'Assertion to ensure test pass'); + return; } - $this->assertTrue(TRUE, 'Assertion to ensure test pass'); + exit($status); } } diff --git a/core/modules/simpletest/tests/src/Unit/SimpletestPhpunitRunCommandTest.php b/core/modules/simpletest/tests/src/Unit/SimpletestPhpunitRunCommandTest.php index 2afea1c7ff4e75f18458c1d7ada6cad18d71762b..a96db963c8a66273e18940db7106bb83a57788db 100644 --- a/core/modules/simpletest/tests/src/Unit/SimpletestPhpunitRunCommandTest.php +++ b/core/modules/simpletest/tests/src/Unit/SimpletestPhpunitRunCommandTest.php @@ -3,7 +3,7 @@ namespace Drupal\Tests\simpletest\Unit; use Drupal\Core\DependencyInjection\ContainerBuilder; -use Drupal\Tests\UnitTestCase; +use Drupal\Core\File\FileSystemInterface; /** * Tests simpletest_run_phpunit_tests() handles PHPunit fatals correctly. @@ -11,27 +11,92 @@ * @group simpletest * * @runTestsInSeparateProcesses - * @preserveGlobalState disabled */ -class SimpletestPhpunitRunCommandTest extends UnitTestCase { +class SimpletestPhpunitRunCommandTest extends \PHPUnit_Framework_TestCase { - function testSimpletestPhpUnitRunCommand() { - include_once __DIR__ . '/../../fixtures/simpletest_phpunit_run_command_test.php'; - $app_root = __DIR__ . '/../../../../../..'; - include_once "$app_root/core/modules/simpletest/simpletest.module"; + /** + * Path to the app root. + * + * @var string + */ + protected static $root; + + /** + * {@inheritdoc} + */ + public static function setUpBeforeClass() { + parent::setUpBeforeClass(); + // Figure out our app root. + self::$root = dirname(dirname(dirname(dirname(dirname(dirname(__DIR__)))))); + // Include the files we need for tests. The stub test we will run is + // SimpletestPhpunitRunCommandTestWillDie which is located in + // simpletest_phpunit_run_command_test.php. + include_once self::$root . '/core/modules/simpletest/tests/fixtures/simpletest_phpunit_run_command_test.php'; + // Since we're testing simpletest_run_phpunit_tests(), we need to include + // simpletest.module. + include_once self::$root . '/core/modules/simpletest/simpletest.module'; + } + + /** + * {@inheritdoc} + */ + protected function setUp() { + parent::setUp(); + // Organize our mock container. $container = new ContainerBuilder(); - $container->set('app.root', $app_root); - $file_system = $this->prophesize('Drupal\Core\File\FileSystemInterface'); + $container->set('app.root', self::$root); + $file_system = $this->prophesize(FileSystemInterface::class); + // The simpletest directory wrapper will always point to /tmp. $file_system->realpath('public://simpletest')->willReturn(sys_get_temp_dir()); $container->set('file_system', $file_system->reveal()); \Drupal::setContainer($container); - $test_id = basename(tempnam(sys_get_temp_dir(), 'xxx')); - foreach (['pass', 'fail'] as $status) { - putenv('SimpletestPhpunitRunCommandTestWillDie=' . $status); - $ret = simpletest_run_phpunit_tests($test_id, ['Drupal\Tests\simpletest\Unit\SimpletestPhpunitRunCommandTestWillDie']); - $this->assertSame($ret[0]['status'], $status); + } + + /** + * Data provider for testSimpletestPhpUnitRunCommand(). + * + * @return array + * Arrays of status codes and the label they're expected to have. + */ + public function provideStatusCodes() { + $data = [ + [0, 'pass'], + [1, 'fail'], + [2, 'exception'], + ]; + // All status codes 3 and above should be labeled 'error'. + // @todo: The valid values here would be 3 to 127. But since the test + // touches the file system a lot, we only have 3, 4, and 127 for speed. + foreach ([3, 4, 127] as $status) { + $data[] = [$status, 'error']; } + return $data; + } + + /** + * Test the round trip for PHPUnit execution status codes. + * + * @covers ::simpletest_run_phpunit_tests + * + * @dataProvider provideStatusCodes + */ + public function testSimpletestPhpUnitRunCommand($status, $label) { + $test_id = basename(tempnam(sys_get_temp_dir(), 'xxx')); + putenv('SimpletestPhpunitRunCommandTestWillDie=' . $status); + $ret = simpletest_run_phpunit_tests($test_id, [SimpletestPhpunitRunCommandTestWillDie::class]); + $this->assertSame($ret[0]['status'], $label); + putenv('SimpletestPhpunitRunCommandTestWillDie'); unlink(simpletest_phpunit_xml_filepath($test_id)); } + /** + * {@inheritdoc} + */ + protected function tearDown() { + // We unset the $base_url global, since the test code sets it as a + // side-effect. + unset($GLOBALS['base_url']); + parent::tearDown(); + } + }