From 8851bdf5b6634ad906b7905abab913220acd274c Mon Sep 17 00:00:00 2001 From: Seamus Lee Date: Mon, 15 Jun 2020 10:31:52 +1000 Subject: [PATCH] [REF] Use PHPUnit7 Fix tap printer --- Civi/Test/CiviTestListener.php | 3 + Civi/Test/CiviTestListenerPHPUnit7.php | 297 ++++++++++++++++ Civi/Test/TAP.php | 409 ++++++++++++----------- Civi/Test/TAPLegacy.php | 256 ++++++++++++++ phpunit.xml.dist | 1 - tests/phpunit/CiviTest/CiviTestSuite.php | 14 - tools/scripts/phpunit | 12 +- 7 files changed, 774 insertions(+), 218 deletions(-) create mode 100644 Civi/Test/CiviTestListenerPHPUnit7.php create mode 100644 Civi/Test/TAPLegacy.php diff --git a/Civi/Test/CiviTestListener.php b/Civi/Test/CiviTestListener.php index e32d9b45d2..2851bda3d8 100644 --- a/Civi/Test/CiviTestListener.php +++ b/Civi/Test/CiviTestListener.php @@ -7,6 +7,9 @@ if (class_exists('PHPUnit_Runner_Version') && version_compare(\PHPUnit_Runner_Ve // Using an early return instead of a else does not work when using the PHPUnit phar due to some weird PHP behavior (the class // gets defined without executing the code before it and so the definition is not properly conditional) } +elseif (version_compare(\PHPUnit\Runner\Version::id(), '7.0.0', '>=')) { + class_alias('Civi\Test\CiviTestListenerPHPUnit7', 'Civi\Test\CiviTestListener'); +} else { /** diff --git a/Civi/Test/CiviTestListenerPHPUnit7.php b/Civi/Test/CiviTestListenerPHPUnit7.php new file mode 100644 index 0000000000..ee8173d2b8 --- /dev/null +++ b/Civi/Test/CiviTestListenerPHPUnit7.php @@ -0,0 +1,297 @@ + Array(string $hookName => string $methodName)). + */ + private $cache = []; + + /** + * @var \CRM_Core_Transaction|null + */ + private $tx; + + public function startTestSuite(\PHPUnit\Framework\TestSuite $suite): void { + $byInterface = $this->indexTestsByInterface($suite->tests()); + $this->validateGroups($byInterface); + $this->autoboot($byInterface); + } + + public function endTestSuite(\PHPUnit\Framework\TestSuite $suite): void { + $this->cache = []; + } + + public function startTest(\PHPUnit\Framework\Test $test): void { + if ($this->isCiviTest($test)) { + error_reporting(E_ALL); + $this->errorScope = \CRM_Core_TemporaryErrorScope::useException(); + } + + if ($test instanceof HeadlessInterface) { + $this->bootHeadless($test); + } + + if ($test instanceof HookInterface) { + // Note: bootHeadless() indirectly resets any hooks, which means that hook_civicrm_config + // is unsubscribable. However, after bootHeadless(), we're free to subscribe to hooks again. + $this->registerHooks($test); + } + + if ($test instanceof TransactionalInterface) { + $this->tx = new \CRM_Core_Transaction(TRUE); + $this->tx->rollback(); + } + else { + $this->tx = NULL; + } + } + + public function endTest(\PHPUnit\Framework\Test $test, float $time): void { + if ($test instanceof TransactionalInterface) { + $this->tx->rollback()->commit(); + $this->tx = NULL; + } + if ($test instanceof HookInterface) { + \CRM_Utils_Hook::singleton()->reset(); + } + if ($this->isCiviTest($test)) { + error_reporting(E_ALL & ~E_NOTICE); + $this->errorScope = NULL; + } + } + + /** + * @param HeadlessInterface|\PHPUnit\Framework\Test $test + */ + protected function bootHeadless($test) { + if (CIVICRM_UF !== 'UnitTests') { + throw new \RuntimeException('HeadlessInterface requires CIVICRM_UF=UnitTests'); + } + + // Hrm, this seems wrong. Shouldn't we be resetting the entire session? + $session = \CRM_Core_Session::singleton(); + $session->set('userID', NULL); + $test->setUpHeadless(); + + \CRM_Utils_System::flushCache(); + \Civi::reset(); + \CRM_Core_Session::singleton()->set('userID', NULL); + // ugh, performance + $config = \CRM_Core_Config::singleton(TRUE, TRUE); + + if (property_exists($config->userPermissionClass, 'permissions')) { + $config->userPermissionClass->permissions = NULL; + } + } + + /** + * @param \Civi\Test\HookInterface $test + * @return array + * Array(string $hookName => string $methodName)). + */ + protected function findTestHooks(HookInterface $test) { + $class = get_class($test); + if (!isset($this->cache[$class])) { + $funcs = []; + foreach (get_class_methods($class) as $func) { + if (preg_match('/^hook_/', $func)) { + $funcs[substr($func, 5)] = $func; + } + } + $this->cache[$class] = $funcs; + } + return $this->cache[$class]; + } + + /** + * @param \PHPUnit\Framework\Test $test + * @return bool + */ + protected function isCiviTest(\PHPUnit\Framework\Test $test) { + return $test instanceof HookInterface || $test instanceof HeadlessInterface; + } + + /** + * Find any hook functions in $test and register them. + * + * @param \Civi\Test\HookInterface $test + */ + protected function registerHooks(HookInterface $test) { + if (CIVICRM_UF !== 'UnitTests') { + // This is not ideal -- it's just a side-effect of how hooks and E2E tests work. + // We can temporarily subscribe to hooks in-process, but for other processes, it gets messy. + throw new \RuntimeException('CiviHookTestInterface requires CIVICRM_UF=UnitTests'); + } + \CRM_Utils_Hook::singleton()->reset(); + /** @var \CRM_Utils_Hook_UnitTests $hooks */ + $hooks = \CRM_Utils_Hook::singleton(); + foreach ($this->findTestHooks($test) as $hook => $func) { + $hooks->setHook($hook, [$test, $func]); + } + } + + /** + * The first time we come across HeadlessInterface or EndToEndInterface, we'll + * try to autoboot. + * + * Once the system is booted, there's nothing we can do -- we're stuck with that + * environment. (Thank you, prolific define()s!) If there's a conflict between a + * test-class and the active boot-level, then we'll have to bail. + * + * @param array $byInterface + * List of test classes, keyed by major interface (HeadlessInterface vs EndToEndInterface). + */ + protected function autoboot($byInterface) { + if (defined('CIVICRM_UF')) { + // OK, nothing we can do. System has booted already. + } + elseif (!empty($byInterface['HeadlessInterface'])) { + putenv('CIVICRM_UF=UnitTests'); + // phpcs:disable + eval($this->cv('php:boot --level=full', 'phpcode')); + // phpcs:enable + } + elseif (!empty($byInterface['EndToEndInterface'])) { + putenv('CIVICRM_UF='); + // phpcs:disable + eval($this->cv('php:boot --level=full', 'phpcode')); + // phpcs:enable + } + + $blurb = "Tip: Run the headless tests and end-to-end tests separately, e.g.\n" + . " $ phpunit5 --group headless\n" + . " $ phpunit5 --group e2e \n"; + + if (!empty($byInterface['HeadlessInterface']) && CIVICRM_UF !== 'UnitTests') { + $testNames = implode(', ', array_keys($byInterface['HeadlessInterface'])); + throw new \RuntimeException("Suite includes headless tests ($testNames) which require CIVICRM_UF=UnitTests.\n\n$blurb"); + } + if (!empty($byInterface['EndToEndInterface']) && CIVICRM_UF === 'UnitTests') { + $testNames = implode(', ', array_keys($byInterface['EndToEndInterface'])); + throw new \RuntimeException("Suite includes end-to-end tests ($testNames) which do not support CIVICRM_UF=UnitTests.\n\n$blurb"); + } + } + + /** + * Call the "cv" command. + * + * This duplicates the standalone `cv()` wrapper that is recommended in bootstrap.php. + * This duplication is necessary because `cv()` is optional, and downstream implementers + * may alter, rename, or omit the wrapper, and (by virtue of its role in bootstrap) there + * it is impossible to define it centrally. + * + * @param string $cmd + * The rest of the command to send. + * @param string $decode + * Ex: 'json' or 'phpcode'. + * @return string + * Response output (if the command executed normally). + * @throws \RuntimeException + * If the command terminates abnormally. + */ + protected function cv($cmd, $decode = 'json') { + $cmd = 'cv ' . $cmd; + $descriptorSpec = [0 => ["pipe", "r"], 1 => ["pipe", "w"], 2 => STDERR]; + $oldOutput = getenv('CV_OUTPUT'); + putenv("CV_OUTPUT=json"); + $process = proc_open($cmd, $descriptorSpec, $pipes, __DIR__); + putenv("CV_OUTPUT=$oldOutput"); + fclose($pipes[0]); + $result = stream_get_contents($pipes[1]); + fclose($pipes[1]); + if (proc_close($process) !== 0) { + throw new \RuntimeException("Command failed ($cmd):\n$result"); + } + switch ($decode) { + case 'raw': + return $result; + + case 'phpcode': + // If the last output is /*PHPCODE*/, then we managed to complete execution. + if (substr(trim($result), 0, 12) !== "/*BEGINPHP*/" || substr(trim($result), -10) !== "/*ENDPHP*/") { + throw new \RuntimeException("Command failed ($cmd):\n$result"); + } + return $result; + + case 'json': + return json_decode($result, 1); + + default: + throw new \RuntimeException("Bad decoder format ($decode)"); + } + } + + /** + * @param $tests + * @return array + */ + protected function indexTestsByInterface($tests) { + $byInterface = ['HeadlessInterface' => [], 'EndToEndInterface' => []]; + foreach ($tests as $test) { + /** @var \PHPUnit\Framework\Test $test */ + if ($test instanceof HeadlessInterface) { + $byInterface['HeadlessInterface'][get_class($test)] = 1; + } + if ($test instanceof EndToEndInterface) { + $byInterface['EndToEndInterface'][get_class($test)] = 1; + } + } + return $byInterface; + } + + /** + * Ensure that any tests have sensible groups, e.g. + * + * `HeadlessInterface` ==> `group headless` + * `EndToEndInterface` ==> `group e2e` + * + * @param array $byInterface + */ + protected function validateGroups($byInterface) { + foreach ($byInterface['HeadlessInterface'] as $className => $nonce) { + $clazz = new \ReflectionClass($className); + $docComment = str_replace("\r\n", "\n", $clazz->getDocComment()); + if (strpos($docComment, "@group headless\n") === FALSE) { + echo "WARNING: Class $className implements HeadlessInterface. It should declare \"@group headless\".\n"; + } + if (strpos($docComment, "@group e2e\n") !== FALSE) { + echo "WARNING: Class $className implements HeadlessInterface. It should not declare \"@group e2e\".\n"; + } + } + foreach ($byInterface['EndToEndInterface'] as $className => $nonce) { + $clazz = new \ReflectionClass($className); + $docComment = str_replace("\r\n", "\n", $clazz->getDocComment()); + if (strpos($docComment, "@group e2e\n") === FALSE) { + echo "WARNING: Class $className implements EndToEndInterface. It should declare \"@group e2e\".\n"; + } + if (strpos($docComment, "@group headless\n") !== FALSE) { + echo "WARNING: Class $className implements EndToEndInterface. It should not declare \"@group headless\".\n"; + } + } + } + +} diff --git a/Civi/Test/TAP.php b/Civi/Test/TAP.php index 5b432ac953..dd1df865e2 100644 --- a/Civi/Test/TAP.php +++ b/Civi/Test/TAP.php @@ -26,231 +26,236 @@ namespace Civi\Test; -class TAP extends \PHPUnit\Util\Printer implements \PHPUnit\Framework\TestListener { - - /** - * @var int - */ - protected $testNumber = 0; - - /** - * @var int - */ - protected $testSuiteLevel = 0; +if (version_compare(\PHPUnit\Runner\Version::id(), '7.0.0', '<')) { + class_alias('Civi\Test\TAPLegacy', 'Civi\Test\TAP'); +} +else { + class TAP extends \PHPUnit\Util\Printer implements \PHPUnit\Framework\TestListener { - /** - * @var bool - */ - protected $testSuccessful = TRUE; + /** + * @var int + */ + protected $testNumber = 0; - /** - * Constructor. - * - * @param mixed $out - * - * @throws \PHPUnit\Framework\Exception - * - * @since Method available since Release 3.3.4 - */ - public function __construct($out = NULL) { - parent::__construct($out); - $this - ->write("TAP version 13\n"); - } + /** + * @var int + */ + protected $testSuiteLevel = 0; - /** - * An error occurred. - * - * @param \PHPUnit\Framework\Test $test - * @param \Exception $e - * @param float $time - */ - public function addError(\PHPUnit\Framework\Test $test, \Exception $e, $time) { - $this - ->writeNotOk($test, 'Error'); - } + /** + * @var bool + */ + protected $testSuccessful = TRUE; - /** - * A failure occurred. - * - * @param \PHPUnit\Framework\Test $test - * @param \PHPUnit\Framework\AssertionFailedError $e - * @param float $time - */ - public function addFailure(\PHPUnit\Framework\Test $test, \PHPUnit\Framework\AssertionFailedError $e, $time) { - $this - ->writeNotOk($test, 'Failure'); - $message = explode("\n", \PHPUnit\Framework\TestFailure::exceptionToString($e)); - $diagnostic = array( - 'message' => $message[0], - 'severity' => 'fail', - ); - if ($e instanceof \PHPUnit\Framework\ExpectationFailedException) { - $cf = $e - ->getComparisonFailure(); - if ($cf !== NULL) { - $diagnostic['data'] = array( - 'got' => $cf - ->getActual(), - 'expected' => $cf - ->getExpected(), - ); - } + /** + * Constructor. + * + * @param mixed $out + * + * @throws \PHPUnit\Framework\Exception + * + * @since Method available since Release 3.3.4 + */ + public function __construct($out = NULL) { + parent::__construct($out); + $this + ->write("TAP version 13\n"); } - if (function_exists('yaml_emit')) { - $content = \yaml_emit($diagnostic, YAML_UTF8_ENCODING); - $content = ' ' . strtr($content, ["\n" => "\n "]); - } - else { - // Any valid JSON document is a valid YAML document. - $content = json_encode($diagnostic, JSON_PRETTY_PRINT | JSON_UNESCAPED_SLASHES); - // For closest match, drop outermost {}'s. Realign indentation. - $content = substr($content, 0, strrpos($content, "}")) . ' }'; - $content = ' ' . ltrim($content); - $content = sprintf(" ---\n%s\n ...\n", $content); + /** + * An error occurred. + * + * @param \PHPUnit\Framework\Test $test + * @param \Throwable $t + * @param float $time + */ + public function addError(\PHPUnit\Framework\Test $test, \Throwable $t, float $time): void { + $this + ->writeNotOk($test, 'Error'); } - $this->write($content); - } - - /** - * Incomplete test. - * - * @param \PHPUnit\Framework\Test $test - * @param \Exception $e - * @param float $time - */ - public function addIncompleteTest(\PHPUnit\Framework\Test $test, \Exception $e, $time) { - $this - ->writeNotOk($test, '', 'TODO Incomplete Test'); - } - - /** - * Risky test. - * - * @param \PHPUnit\Framework\Test $test - * @param \Exception $e - * @param float $time - * - * @since Method available since Release 4.0.0 - */ - public function addRiskyTest(\PHPUnit\Framework\Test $test, \Exception $e, $time) { - $this - ->write(sprintf("ok %d - # RISKY%s\n", $this->testNumber, $e - ->getMessage() != '' ? ' ' . $e - ->getMessage() : '')); - $this->testSuccessful = FALSE; - } + /** + * A failure occurred. + * + * @param \PHPUnit\Framework\Test $test + * @param \PHPUnit\Framework\AssertionFailedError $e + * @param float $time + */ + public function addFailure(\PHPUnit\Framework\Test $test, \PHPUnit\Framework\AssertionFailedError $e, float $time): void { + $this + ->writeNotOk($test, 'Failure'); + $message = explode("\n", \PHPUnit\Framework\TestFailure::exceptionToString($e)); + $diagnostic = array( + 'message' => $message[0], + 'severity' => 'fail', + ); + if ($e instanceof \PHPUnit\Framework\ExpectationFailedException) { + $cf = $e + ->getComparisonFailure(); + if ($cf !== NULL) { + $diagnostic['data'] = array( + 'got' => $cf + ->getActual(), + 'expected' => $cf + ->getExpected(), + ); + } + } - /** - * Skipped test. - * - * @param \PHPUnit\Framework\Test $test - * @param \Exception $e - * @param float $time - * - * @since Method available since Release 3.0.0 - */ - public function addSkippedTest(\PHPUnit\Framework\Test $test, \Exception $e, $time) { - $this - ->write(sprintf("ok %d - # SKIP%s\n", $this->testNumber, $e - ->getMessage() != '' ? ' ' . $e - ->getMessage() : '')); - $this->testSuccessful = FALSE; - } + if (function_exists('yaml_emit')) { + $content = \yaml_emit($diagnostic, YAML_UTF8_ENCODING); + $content = ' ' . strtr($content, ["\n" => "\n "]); + } + else { + // Any valid JSON document is a valid YAML document. + $content = json_encode($diagnostic, JSON_PRETTY_PRINT | JSON_UNESCAPED_SLASHES); + // For closest match, drop outermost {}'s. Realign indentation. + $content = substr($content, 0, strrpos($content, "}")) . ' }'; + $content = ' ' . ltrim($content); + $content = sprintf(" ---\n%s\n ...\n", $content); + } - /** - * Warning test. - * - * @param \PHPUnit\Framework\Test $test - * @param \PHPUnit\Framework\Warning $e - * @param float $time - * - * @since Method available since Release 3.0.0 - */ - public function addWarning(\PHPUnit\Framework\Test $test, \PHPUnit\Framework\Warning $e, $time) { - $this - ->write(sprintf("ok %d - # Warning%s\n", $this->testNumber, $e - ->getMessage() != '' ? ' ' . $e - ->getMessage() : '')); - $this->testSuccessful = FALSE; - } + $this->write($content); + } - /** - * A testsuite started. - * - * @param \PHPUnit\Framework\TestSuite $suite - */ - public function startTestSuite(\PHPUnit\Framework\TestSuite $suite) { - $this->testSuiteLevel++; - } + /** + * Incomplete test. + * + * @param \PHPUnit\Framework\Test $test + * @param \Throwable $t + * @param float $time + */ + public function addIncompleteTest(\PHPUnit\Framework\Test $test, \Throwable $t, float $time): void { + $this + ->writeNotOk($test, '', 'TODO Incomplete Test'); + } - /** - * A testsuite ended. - * - * @param \PHPUnit\Framework\TestSuite $suite - */ - public function endTestSuite(\PHPUnit\Framework\TestSuite $suite) { - $this->testSuiteLevel--; - if ($this->testSuiteLevel == 0) { + /** + * Risky test. + * + * @param \PHPUnit\Framework\Test $test + * @param \Throwable $t + * @param float $time + * + * @since Method available since Release 4.0.0 + */ + public function addRiskyTest(\PHPUnit\Framework\Test $test, \Throwable $t, float $time): void { $this - ->write(sprintf("1..%d\n", $this->testNumber)); + ->write(sprintf("ok %d - # RISKY%s\n", $this->testNumber, $t + ->getMessage() != '' ? ' ' . $t + ->getMessage() : '')); + $this->testSuccessful = FALSE; } - } - /** - * A test started. - * - * @param \PHPUnit\Framework\Test $test - */ - public function startTest(\PHPUnit\Framework\Test $test) { - $this->testNumber++; - $this->testSuccessful = TRUE; - } + /** + * Skipped test. + * + * @param \PHPUnit\Framework\Test $test + * @param \Throwable $t + * @param float $time + * + * @since Method available since Release 3.0.0 + */ + public function addSkippedTest(\PHPUnit\Framework\Test $test, \Throwable $t, float $time): void { + $this + ->write(sprintf("ok %d - # SKIP%s\n", $this->testNumber, $t + ->getMessage() != '' ? ' ' . $t + ->getMessage() : '')); + $this->testSuccessful = FALSE; + } - /** - * A test ended. - * - * @param \PHPUnit\Framework\Test $test - * @param float $time - */ - public function endTest(\PHPUnit\Framework\Test $test, $time) { - if ($this->testSuccessful === TRUE) { + /** + * Warning test. + * + * @param \PHPUnit\Framework\Test $test + * @param \PHPUnit\Framework\Warning $e + * @param float $time + * + * @since Method available since Release 3.0.0 + */ + public function addWarning(\PHPUnit\Framework\Test $test, \PHPUnit\Framework\Warning $e, float $time): void { $this - ->write(sprintf("ok %d - %s\n", $this->testNumber, \PHPUnit\Util\Test::describe($test))); + ->write(sprintf("ok %d - # Warning%s\n", $this->testNumber, $e + ->getMessage() != '' ? ' ' . $e + ->getMessage() : '')); + $this->testSuccessful = FALSE; } - $this - ->writeDiagnostics($test); - } - /** - * @param \PHPUnit\Framework\Test $test - * @param string $prefix - * @param string $directive - */ - protected function writeNotOk(\PHPUnit\Framework\Test $test, $prefix = '', $directive = '') { - $this - ->write(sprintf("not ok %d - %s%s%s\n", $this->testNumber, $prefix != '' ? $prefix . ': ' : '', \PHPUnit\Util\Test::describe($test), $directive != '' ? ' # ' . $directive : '')); - $this->testSuccessful = FALSE; - } + /** + * A testsuite started. + * + * @param \PHPUnit\Framework\TestSuite $suite + */ + public function startTestSuite(\PHPUnit\Framework\TestSuite $suite): void { + $this->testSuiteLevel++; + } + + /** + * A testsuite ended. + * + * @param \PHPUnit\Framework\TestSuite $suite + */ + public function endTestSuite(\PHPUnit\Framework\TestSuite $suite): void { + $this->testSuiteLevel--; + if ($this->testSuiteLevel == 0) { + $this + ->write(sprintf("1..%d\n", $this->testNumber)); + } + } - /** - * @param \PHPUnit\Framework\Test $test - */ - private function writeDiagnostics(\PHPUnit\Framework\Test $test) { - if (!$test instanceof \PHPUnit\Framework\TestCase) { - return; + /** + * A test started. + * + * @param \PHPUnit\Framework\Test $test + */ + public function startTest(\PHPUnit\Framework\Test $test): void { + $this->testNumber++; + $this->testSuccessful = TRUE; } - if (!$test - ->hasOutput()) { - return; + + /** + * A test ended. + * + * @param \PHPUnit\Framework\Test $test + * @param float $time + */ + public function endTest(\PHPUnit\Framework\Test $test, float $time): void { + if ($this->testSuccessful === TRUE) { + $this + ->write(sprintf("ok %d - %s\n", $this->testNumber, \PHPUnit\Util\Test::describeAsString($test))); + } + $this + ->writeDiagnostics($test); } - foreach (explode("\n", trim($test - ->getActualOutput())) as $line) { + + /** + * @param \PHPUnit\Framework\Test $test + * @param string $prefix + * @param string $directive + */ + protected function writeNotOk(\PHPUnit\Framework\Test $test, $prefix = '', $directive = ''): void { $this - ->write(sprintf("# %s\n", $line)); + ->write(sprintf("not ok %d - %s%s%s\n", $this->testNumber, $prefix != '' ? $prefix . ': ' : '', \PHPUnit\Util\Test::describeAsString($test), $directive != '' ? ' # ' . $directive : '')); + $this->testSuccessful = FALSE; } - } + /** + * @param \PHPUnit\Framework\Test $test + */ + private function writeDiagnostics(\PHPUnit\Framework\Test $test): void { + if (!$test instanceof \PHPUnit\Framework\TestCase) { + return; + } + if (!$test + ->hasOutput()) { + return; + } + foreach (explode("\n", trim($test + ->getActualOutput())) as $line) { + $this + ->write(sprintf("# %s\n", $line)); + } + } + + } } diff --git a/Civi/Test/TAPLegacy.php b/Civi/Test/TAPLegacy.php new file mode 100644 index 0000000000..41c7da08b4 --- /dev/null +++ b/Civi/Test/TAPLegacy.php @@ -0,0 +1,256 @@ +write("TAP version 13\n"); + } + + /** + * An error occurred. + * + * @param \PHPUnit\Framework\Test $test + * @param \Exception $e + * @param float $time + */ + public function addError(\PHPUnit\Framework\Test $test, \Exception $e, $time) { + $this + ->writeNotOk($test, 'Error'); + } + + /** + * A failure occurred. + * + * @param \PHPUnit\Framework\Test $test + * @param \PHPUnit\Framework\AssertionFailedError $e + * @param float $time + */ + public function addFailure(\PHPUnit\Framework\Test $test, \PHPUnit\Framework\AssertionFailedError $e, $time) { + $this + ->writeNotOk($test, 'Failure'); + $message = explode("\n", \PHPUnit\Framework\TestFailure::exceptionToString($e)); + $diagnostic = array( + 'message' => $message[0], + 'severity' => 'fail', + ); + if ($e instanceof \PHPUnit\Framework\ExpectationFailedException) { + $cf = $e + ->getComparisonFailure(); + if ($cf !== NULL) { + $diagnostic['data'] = array( + 'got' => $cf + ->getActual(), + 'expected' => $cf + ->getExpected(), + ); + } + } + + if (function_exists('yaml_emit')) { + $content = \yaml_emit($diagnostic, YAML_UTF8_ENCODING); + $content = ' ' . strtr($content, ["\n" => "\n "]); + } + else { + // Any valid JSON document is a valid YAML document. + $content = json_encode($diagnostic, JSON_PRETTY_PRINT | JSON_UNESCAPED_SLASHES); + // For closest match, drop outermost {}'s. Realign indentation. + $content = substr($content, 0, strrpos($content, "}")) . ' }'; + $content = ' ' . ltrim($content); + $content = sprintf(" ---\n%s\n ...\n", $content); + } + + $this->write($content); + } + + /** + * Incomplete test. + * + * @param \PHPUnit\Framework\Test $test + * @param \Exception $e + * @param float $time + */ + public function addIncompleteTest(\PHPUnit\Framework\Test $test, \Exception $e, $time) { + $this + ->writeNotOk($test, '', 'TODO Incomplete Test'); + } + + /** + * Risky test. + * + * @param \PHPUnit\Framework\Test $test + * @param \Exception $e + * @param float $time + * + * @since Method available since Release 4.0.0 + */ + public function addRiskyTest(\PHPUnit\Framework\Test $test, \Exception $e, $time) { + $this + ->write(sprintf("ok %d - # RISKY%s\n", $this->testNumber, $e + ->getMessage() != '' ? ' ' . $e + ->getMessage() : '')); + $this->testSuccessful = FALSE; + } + + /** + * Skipped test. + * + * @param \PHPUnit\Framework\Test $test + * @param \Exception $e + * @param float $time + * + * @since Method available since Release 3.0.0 + */ + public function addSkippedTest(\PHPUnit\Framework\Test $test, \Exception $e, $time) { + $this + ->write(sprintf("ok %d - # SKIP%s\n", $this->testNumber, $e + ->getMessage() != '' ? ' ' . $e + ->getMessage() : '')); + $this->testSuccessful = FALSE; + } + + /** + * Warning test. + * + * @param \PHPUnit\Framework\Test $test + * @param \PHPUnit\Framework\Warning $e + * @param float $time + * + * @since Method available since Release 3.0.0 + */ + public function addWarning(\PHPUnit\Framework\Test $test, \PHPUnit\Framework\Warning $e, $time) { + $this + ->write(sprintf("ok %d - # Warning%s\n", $this->testNumber, $e + ->getMessage() != '' ? ' ' . $e + ->getMessage() : '')); + $this->testSuccessful = FALSE; + } + + /** + * A testsuite started. + * + * @param \PHPUnit\Framework\TestSuite $suite + */ + public function startTestSuite(\PHPUnit\Framework\TestSuite $suite) { + $this->testSuiteLevel++; + } + + /** + * A testsuite ended. + * + * @param \PHPUnit\Framework\TestSuite $suite + */ + public function endTestSuite(\PHPUnit\Framework\TestSuite $suite) { + $this->testSuiteLevel--; + if ($this->testSuiteLevel == 0) { + $this + ->write(sprintf("1..%d\n", $this->testNumber)); + } + } + + /** + * A test started. + * + * @param \PHPUnit\Framework\Test $test + */ + public function startTest(\PHPUnit\Framework\Test $test) { + $this->testNumber++; + $this->testSuccessful = TRUE; + } + + /** + * A test ended. + * + * @param \PHPUnit\Framework\Test $test + * @param float $time + */ + public function endTest(\PHPUnit\Framework\Test $test, $time) { + if ($this->testSuccessful === TRUE) { + $this + ->write(sprintf("ok %d - %s\n", $this->testNumber, \PHPUnit\Util\Test::describe($test))); + } + $this + ->writeDiagnostics($test); + } + + /** + * @param \PHPUnit\Framework\Test $test + * @param string $prefix + * @param string $directive + */ + protected function writeNotOk(\PHPUnit\Framework\Test $test, $prefix = '', $directive = '') { + $this + ->write(sprintf("not ok %d - %s%s%s\n", $this->testNumber, $prefix != '' ? $prefix . ': ' : '', \PHPUnit\Util\Test::describe($test), $directive != '' ? ' # ' . $directive : '')); + $this->testSuccessful = FALSE; + } + + /** + * @param \PHPUnit\Framework\Test $test + */ + private function writeDiagnostics(\PHPUnit\Framework\Test $test) { + if (!$test instanceof \PHPUnit\Framework\TestCase) { + return; + } + if (!$test + ->hasOutput()) { + return; + } + foreach (explode("\n", trim($test + ->getActualOutput())) as $line) { + $this + ->write(sprintf("# %s\n", $line)); + } + } + +} diff --git a/phpunit.xml.dist b/phpunit.xml.dist index 4d19c763c4..6a4c26f48e 100644 --- a/phpunit.xml.dist +++ b/phpunit.xml.dist @@ -6,7 +6,6 @@ convertWarningsToExceptions="true" processIsolation="false" stopOnFailure="false" - syntaxCheck="false" stderr="true" beStrictAboutTestsThatDoNotTestAnything="false" bootstrap="tests/phpunit/CiviTest/bootstrap.php" diff --git a/tests/phpunit/CiviTest/CiviTestSuite.php b/tests/phpunit/CiviTest/CiviTestSuite.php index 25567a40e6..854f650f93 100644 --- a/tests/phpunit/CiviTest/CiviTestSuite.php +++ b/tests/phpunit/CiviTest/CiviTestSuite.php @@ -55,20 +55,6 @@ class CiviTestSuite extends PHPUnit\Framework\TestSuite { CRM_Core_ClassLoader::singleton()->register(); } - /** - * Test suite setup. - */ - protected function setUp() { - //print __METHOD__ . "\n"; - } - - /** - * Test suite teardown. - */ - protected function tearDown() { - //print __METHOD__ . "\n"; - } - /** * suppress failed test error issued by phpunit when it finds. * a test suite with no tests diff --git a/tools/scripts/phpunit b/tools/scripts/phpunit index 2e682bc0db..37f1e3f672 100755 --- a/tools/scripts/phpunit +++ b/tools/scripts/phpunit @@ -15,7 +15,17 @@ $argFilters = []; if (PHP_SAPI !== 'cli') { die("phpunit can only be run from command line."); } -if (version_compare(PHP_VERSION, '7.0', '>=')) { +if (version_compare(PHP_VERSION, '7.1', '>=')) { + $phpunit = findCommand('phpunit7'); + $argFilters[] = function ($argv) { + $pos = array_search('--tap', $argv); + if ($pos !== FALSE) { + array_splice($argv, $pos, 1, ['--printer', '\Civi\Test\TAP']); + } + return $argv; + }; +} +elseif (version_compare(PHP_VERSION, '7.0', '>=')) { $phpunit = findCommand('phpunit6'); $argFilters[] = function ($argv) { $pos = array_search('--tap', $argv); -- 2.25.1