[REF] Use PHPUnit7
authorSeamus Lee <seamuslee001@gmail.com>
Mon, 15 Jun 2020 00:31:52 +0000 (10:31 +1000)
committerSeamus Lee <seamuslee001@gmail.com>
Sat, 20 Jun 2020 01:48:02 +0000 (11:48 +1000)
Fix tap printer

Civi/Test/CiviTestListener.php
Civi/Test/CiviTestListenerPHPUnit7.php [new file with mode: 0644]
Civi/Test/TAP.php
Civi/Test/TAPLegacy.php [new file with mode: 0644]
phpunit.xml.dist
tests/phpunit/CiviTest/CiviTestSuite.php
tools/scripts/phpunit

index e32d9b45d28888a925824bb608a5de85bd944799..2851bda3d89c9d283186e3dd54beeee9f104b327 100644 (file)
@@ -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 (file)
index 0000000..ee8173d
--- /dev/null
@@ -0,0 +1,297 @@
+<?php
+
+namespace Civi\Test;
+
+/**
+ * Class CiviTestListener
+ * @package Civi\Test
+ *
+ * CiviTestListener participates in test-execution, looking for test-classes
+ * which have certain tags. If the tags are found, the listener will perform
+ * additional setup/teardown logic.
+ *
+ * @see EndToEndInterface
+ * @see HeadlessInterface
+ * @see HookInterface
+ */
+class CiviTestListenerPHPUnit7 implements \PHPUnit\Framework\TestListener {
+
+  use \PHPUnit\Framework\TestListenerDefaultImplementation;
+
+  /**
+   * @var \CRM_Core_TemporaryErrorScope
+   */
+  private $errorScope;
+
+  /**
+   * @var array
+   *  Ex: $cache['Some_Test_Class']['civicrm_foobar'] = 'hook_civicrm_foobar';
+   *  Array(string $testClass => 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";
+      }
+    }
+  }
+
+}
index 5b432ac9530d5a66f6639494dc52684e781cae10..dd1df865e2e51c8e54953e3ab6121cd182b6543e 100644 (file)
 
 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 (file)
index 0000000..41c7da0
--- /dev/null
@@ -0,0 +1,256 @@
+<?php
+
+/*
+ +--------------------------------------------------------------------+
+ | CiviCRM version 5                                                  |
+ +--------------------------------------------------------------------+
+ | This file is a part of CiviCRM.                                    |
+ |                                                                    |
+ | CiviCRM is free software; you can copy, modify, and distribute it  |
+ | under the terms of the GNU Affero General Public License           |
+ | Version 3, 19 November 2007 and the CiviCRM Licensing Exception.   |
+ |                                                                    |
+ | CiviCRM is distributed in the hope that it will be useful, but     |
+ | WITHOUT ANY WARRANTY; without even the implied warranty of         |
+ | MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.               |
+ | See the GNU Affero General Public License for more details.        |
+ |                                                                    |
+ | You should have received a copy of the GNU Affero General Public   |
+ | License and the CiviCRM Licensing Exception along                  |
+ | with this program; if not, contact CiviCRM LLC                     |
+ | at info[AT]civicrm[DOT]org. If you have questions about the        |
+ | GNU Affero General Public License or the licensing of CiviCRM,     |
+ | see the CiviCRM license FAQ at http://civicrm.org/licensing        |
+ +--------------------------------------------------------------------+
+ */
+
+namespace Civi\Test;
+
+class TAPLegacy extends \PHPUnit\Util\Printer implements \PHPUnit\Framework\TestListener {
+
+  /**
+   * @var int
+   */
+  protected $testNumber = 0;
+
+  /**
+   * @var int
+   */
+  protected $testSuiteLevel = 0;
+
+  /**
+   * @var bool
+   */
+  protected $testSuccessful = TRUE;
+
+  /**
+   * 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");
+  }
+
+  /**
+   * 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));
+    }
+  }
+
+}
index 4d19c763c4c72d30ecc93962c26bc2aa3d66b2c1..6a4c26f48ec53b1f823276f2b1933ddbfae82694 100644 (file)
@@ -6,7 +6,6 @@
          convertWarningsToExceptions="true"
          processIsolation="false"
          stopOnFailure="false"
-         syntaxCheck="false"
          stderr="true"
          beStrictAboutTestsThatDoNotTestAnything="false"
          bootstrap="tests/phpunit/CiviTest/bootstrap.php"
index 25567a40e60a8e7641afc5898a14ebe9da490e87..854f650f93f18a0e35a4919aa2085c447df70aca 100644 (file)
@@ -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
index 2e682bc0db51253824050b4d71b2a6a467c760d8..37f1e3f672fd1a019d2ffc41da73a9fd2d5f2dad 100755 (executable)
@@ -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);