Verified Commit 72601e5a authored by Dave Long's avatar Dave Long
Browse files

Issue #3453341 by mondrake, alexpott, longwave: Bootstrap HtmlOutputLogger from phpunit.xml

(cherry picked from commit 0bfc9722)
parent 1c3d9629
Loading
Loading
Loading
Loading
Loading
+19 −9
Original line number Diff line number Diff line
@@ -27,15 +27,6 @@
    <env name="SIMPLETEST_BASE_URL" value=""/>
    <!-- Example SIMPLETEST_DB value: mysql://username:password@localhost/database_name#table_prefix -->
    <env name="SIMPLETEST_DB" value=""/>
    <!-- Example BROWSERTEST_OUTPUT_DIRECTORY value: /path/to/webroot/sites/simpletest/browser_output -->
    <env name="BROWSERTEST_OUTPUT_DIRECTORY" value=""/>
    <!-- To avoid overcrowding the output in CI environments, browser tests
     will not print the individual links in the test run report by default.
     The output in Drupal testing environment is saved as an artifact that
     can be browsed or downloaded from Gitlab. However, if you need to
     print the individual links locally you can set the
     BROWSERTEST_OUTPUT_VERBOSE environment variable to "true". -->
    <!-- <env name="BROWSERTEST_OUTPUT_VERBOSE" value="true"/> -->
    <!-- By default, browser tests will output links that use the base URL set
     in SIMPLETEST_BASE_URL. However, if your SIMPLETEST_BASE_URL is an internal
     path (such as may be the case in a virtual or Docker-based environment),
@@ -65,6 +56,25 @@
    <!-- Example for changing the driver args to webdriver tests MINK_DRIVER_ARGS_WEBDRIVER value: '["chrome", { "goog:chromeOptions": { "w3c": false } }, "http://localhost:4444/wd/hub"]' For using the Firefox browser, replace "chrome" with "firefox" -->
    <env name="MINK_DRIVER_ARGS_WEBDRIVER" value=""/>
  </php>
  <extensions>
    <!-- Functional tests HTML output logging. -->
    <bootstrap class="Drupal\TestTools\Extension\HtmlLogging\HtmlOutputLogger">
      <!-- The directory where the browser output will be stored. If a relative
        path is specified, it will be relative to the current working directory
        of the process running the PHPUnit CLI. In CI environments, this can be
        overridden by the value set for the "BROWSERTEST_OUTPUT_DIRECTORY"
        environment variable.
      -->
      <parameter name="outputDirectory" value="sites/simpletest/browser_output"/>
      <!-- By default browser tests print the individual links in the test run
        report. To avoid overcrowding the output in CI environments, you can
        set the "verbose" parameter or the "BROWSERTEST_OUTPUT_VERBOSE"
        environment variable to "false". In GitLabCI, the output is saved
        anyway as an artifact that can be browsed or downloaded from Gitlab.
      -->
      <parameter name="verbose" value="true"/>
    </bootstrap>
  </extensions>
  <testsuites>
    <testsuite name="unit">
      <directory>tests/Drupal/Tests</directory>
+118 −10
Original line number Diff line number Diff line
@@ -4,6 +4,7 @@

namespace Drupal\FunctionalTests\Test;

use Drupal\Core\File\FileExists;
use Drupal\Tests\BrowserTestBase;
use Symfony\Component\Process\Process;

@@ -28,13 +29,36 @@ class FunctionalTestDebugHtmlOutputTest extends BrowserTestBase {
  public function testFunctionalTestDebugHtmlOutput(): void {
    $command = [
      'vendor/bin/phpunit',
      'core/tests/Drupal/FunctionalTests/Test/FunctionalTestDebugHtmlOutputHelperTest.php',
    ];

    // Test with the default settings in phpunit.xml.dist.
    $config = [
      '--configuration',
      'core',
      'core/tests/Drupal/FunctionalTests/Test/FunctionalTestDebugHtmlOutputHelperTest.php',
    ];
    $process = new Process(array_merge($command, $config));
    $process->setWorkingDirectory($this->root)
      ->setTimeout(300)
      ->setIdleTimeout(300);
    $process->run();
    $this->assertEquals(0, $process->getExitCode(),
      'COMMAND: ' . $process->getCommandLine() . "\n" .
      'OUTPUT: ' . $process->getOutput() . "\n" .
      'ERROR: ' . $process->getErrorOutput() . "\n");
    $this->assertStringContainsString('HTML output was generated.', $process->getOutput());
    $this->assertStringContainsString('Drupal_FunctionalTests_Test_FunctionalTestDebugHtmlOutputHelperTest', $process->getOutput());

    // Test with the default output directory, specified by BROWSERTEST_OUTPUT_DIRECTORY.
    $process = new Process($command);
    // Test without verbose output, set in xml.
    $alteredConfigFile = $this->getAlteredPhpunitXmlConfigurationFile(
      '<parameter name="verbose" value="true"/>',
      '<parameter name="verbose" value="false"/>',
    );
    $config = [
      '--configuration',
      $alteredConfigFile,
    ];
    $process = new Process(array_merge($command, $config));
    $process->setWorkingDirectory($this->root)
      ->setTimeout(300)
      ->setIdleTimeout(300);
@@ -44,24 +68,57 @@ public function testFunctionalTestDebugHtmlOutput(): void {
      'OUTPUT: ' . $process->getOutput() . "\n" .
      'ERROR: ' . $process->getErrorOutput() . "\n");
    $this->assertMatchesRegularExpression('/HTML output was generated, \d+ page\(s\)\./m', $process->getOutput());
    unlink($alteredConfigFile);

    // Test with verbose output.
    $process = new Process($command);
    // Test without verbose output, overridden by BROWSERTEST_OUTPUT_VERBOSE
    // environment variable.
    $config = [
      '--configuration',
      'core',
    ];
    $process = new Process(array_merge($command, $config));
    $process->setWorkingDirectory($this->root)
      ->setTimeout(300)
      ->setIdleTimeout(300);
    $process->run(NULL, [
      'BROWSERTEST_OUTPUT_VERBOSE' => '1',
      'BROWSERTEST_OUTPUT_VERBOSE' => 'false',
    ]);
    $this->assertEquals(0, $process->getExitCode(),
      'COMMAND: ' . $process->getCommandLine() . "\n" .
      'OUTPUT: ' . $process->getOutput() . "\n" .
      'ERROR: ' . $process->getErrorOutput() . "\n");
    $this->assertStringContainsString('HTML output was generated.', $process->getOutput());
    $this->assertStringContainsString('Drupal_FunctionalTests_Test_FunctionalTestDebugHtmlOutputHelperTest', $process->getOutput());
    $this->assertMatchesRegularExpression('/HTML output was generated, \d+ page\(s\)\./m', $process->getOutput());

    // Test with a wrong output directory.
    $process = new Process($command);
    // Test with a wrong output directory, set in xml.
    $alteredConfigFile = $this->getAlteredPhpunitXmlConfigurationFile(
      '<parameter name="outputDirectory" value="sites/simpletest/browser_output"/>',
      '<parameter name="outputDirectory" value="can_we_assume_that_a_subdirectory_with_this_name_does_not_exist"/>',
    );
    $config = [
      '--configuration',
      $alteredConfigFile,
    ];
    $process = new Process(array_merge($command, $config));
    $process->setWorkingDirectory($this->root)
      ->setTimeout(300)
      ->setIdleTimeout(300);
    $process->run(NULL, [
      'BROWSERTEST_OUTPUT_DIRECTORY' => FALSE,
    ]);
    $this->assertEquals(0, $process->getExitCode(),
      'COMMAND: ' . $process->getCommandLine() . "\n" .
      'OUTPUT: ' . $process->getOutput() . "\n" .
      'ERROR: ' . $process->getErrorOutput() . "\n");
    $this->assertStringContainsString('HTML output directory can_we_assume_that_a_subdirectory_with_this_name_does_not_exist is not a writable directory.', $process->getOutput());
    unlink($alteredConfigFile);

    // Test with a wrong output directory, overridden by
    // BROWSERTEST_OUTPUT_DIRECTORY environment variable.
    $config = [
      '--configuration',
      'core',
    ];
    $process = new Process(array_merge($command, $config));
    $process->setWorkingDirectory($this->root)
      ->setTimeout(300)
      ->setIdleTimeout(300);
@@ -73,6 +130,57 @@ public function testFunctionalTestDebugHtmlOutput(): void {
      'OUTPUT: ' . $process->getOutput() . "\n" .
      'ERROR: ' . $process->getErrorOutput() . "\n");
    $this->assertStringContainsString('HTML output directory can_we_assume_that_a_subdirectory_with_this_name_does_not_exist is not a writable directory.', $process->getOutput());

    // Test disabling by setting BROWSERTEST_OUTPUT_DIRECTORY = ''.
    $config = [
      '--configuration',
      'core',
    ];
    $process = new Process(array_merge($command, $config));
    $process->setWorkingDirectory($this->root)
      ->setTimeout(300)
      ->setIdleTimeout(300);
    $process->run(NULL, [
      'BROWSERTEST_OUTPUT_DIRECTORY' => '',
    ]);
    $this->assertEquals(0, $process->getExitCode(),
      'COMMAND: ' . $process->getCommandLine() . "\n" .
      'OUTPUT: ' . $process->getOutput() . "\n" .
      'ERROR: ' . $process->getErrorOutput() . "\n");
    $this->assertStringContainsString('HTML output disabled by BROWSERTEST_OUTPUT_DIRECTORY = \'\'.', $process->getOutput());

    // Test missing 'outputDirectory' parameter.
    $alteredConfigFile = $this->getAlteredPhpunitXmlConfigurationFile(
      '<parameter name="outputDirectory" value="sites/simpletest/browser_output"/>',
      '',
    );
    $config = [
      '--configuration',
      $alteredConfigFile,
    ];
    $process = new Process(array_merge($command, $config));
    $process->setWorkingDirectory($this->root)
      ->setTimeout(300)
      ->setIdleTimeout(300);
    $process->run(NULL, [
      'BROWSERTEST_OUTPUT_DIRECTORY' => FALSE,
    ]);
    $this->assertEquals(0, $process->getExitCode(),
      'COMMAND: ' . $process->getCommandLine() . "\n" .
      'OUTPUT: ' . $process->getOutput() . "\n" .
      'ERROR: ' . $process->getErrorOutput() . "\n");
    $this->assertStringContainsString('HTML output directory not specified.', $process->getOutput());
    unlink($alteredConfigFile);
  }

  private function getAlteredPhpunitXmlConfigurationFile(array|string $search, array|string $replace): string {
    $fileSystem = \Drupal::service('file_system');
    $copiedConfigFile = $fileSystem->tempnam($this->root . \DIRECTORY_SEPARATOR . 'core', 'pux');
    $fileSystem->copy($this->root . \DIRECTORY_SEPARATOR . 'core' . \DIRECTORY_SEPARATOR . 'phpunit.xml.dist', $copiedConfigFile, FileExists::Replace);
    $content = file_get_contents($copiedConfigFile);
    $content = str_replace($search, $replace, $content);
    file_put_contents($copiedConfigFile, $content);
    return $fileSystem->realpath($copiedConfigFile);
  }

}
+63 −48
Original line number Diff line number Diff line
@@ -4,21 +4,24 @@

namespace Drupal\TestTools\Extension\HtmlLogging;

use PHPUnit\Event\Facade;
use PHPUnit\Event\TestRunner\Finished as TestRunnerFinished;
use PHPUnit\Event\TestRunner\Started as TestRunnerStarted;
use PHPUnit\Runner\Extension\Extension;
use PHPUnit\Runner\Extension\Facade;
use PHPUnit\Runner\Extension\ParameterCollection;
use PHPUnit\TextUI\Configuration\Configuration;

/**
 * Drupal's extension for providing HTML output results for functional tests.
 *
 * @internal
 */
final class HtmlOutputLogger {
final class HtmlOutputLogger implements Extension {

  /**
   * The singleton instance.
   * The status of the extension.
   */
  private static ?self $instance = NULL;
  private bool $enabled = FALSE;

  /**
   * A file with list of links to HTML pages generated.
@@ -26,53 +29,66 @@ final class HtmlOutputLogger {
  private ?string $browserOutputFile = NULL;

  /**
   * @throws \PHPUnit\Event\EventFacadeIsSealedException
   * @throws \PHPUnit\Util\Exception
   * @throws \PHPUnit\Event\UnknownSubscriberTypeException
   * @throws \RuntimeException
   * A file with list of links to HTML pages generated.
   */
  private function __construct(
    private readonly string $outputDirectory,
    private readonly bool $outputVerbose,
    private readonly Facade $facade,
  ) {
    $this->facade->registerSubscriber(new TestRunnerStartedSubscriber($this));
    $this->facade->registerSubscriber(new TestRunnerFinishedSubscriber($this));
  }
  private string $outputDirectory;

  /**
   * Initializes the extension.
   * Verbosity of the final report.
   *
   * @param string $outputDirectory
   *   The directory where the HTML pages should be generated.
   * @param bool $outputVerbose
   * If TRUE, a list of links generated will be output at the end of the test
   * run; if FALSE, only a summary with the count of pages generated.
   *
   * @throws \PHPUnit\Event\EventFacadeIsSealedException
   * @throws \PHPUnit\Util\Exception
   * @throws \PHPUnit\Event\UnknownSubscriberTypeException
   * @throws \RuntimeException
   */
  public static function init(string $outputDirectory, bool $outputVerbose): void {
    if (self::$instance === NULL) {
      $realDirectory = realpath($outputDirectory);
  private bool $outputVerbose;

  /**
   * {@inheritdoc}
   */
  public function bootstrap(
    Configuration $configuration,
    Facade $facade,
    ParameterCollection $parameters,
  ): void {
    // Determine output directory.
    $envDirectory = getenv('BROWSERTEST_OUTPUT_DIRECTORY');
    if ($envDirectory === "") {
      print "HTML output disabled by BROWSERTEST_OUTPUT_DIRECTORY = ''.\n\n";
      return;
    }
    elseif ($envDirectory !== FALSE) {
      $directory = $envDirectory;
    }
    elseif ($parameters->has('outputDirectory')) {
      $directory = $parameters->get('outputDirectory');
    }
    else {
      print "HTML output directory not specified.\n\n";
      return;
    }
    $realDirectory = realpath($directory);
    if ($realDirectory === FALSE || !is_dir($realDirectory) || !is_writable($realDirectory)) {
        print "HTML output directory {$outputDirectory} is not a writable directory.\n\n";
      print "HTML output directory {$directory} is not a writable directory.\n\n";
      return;
    }
      self::$instance = new self($realDirectory, $outputVerbose, Facade::instance());
    $this->outputDirectory = $realDirectory;

    // Determine output verbosity.
    $envVerbose = getenv('BROWSERTEST_OUTPUT_VERBOSE');
    if ($envVerbose !== FALSE) {
      $verbose = $envVerbose;
    }
    elseif ($parameters->has('verbose')) {
      $verbose = $parameters->get('verbose');
    }
    else {
      $verbose = FALSE;
    }
    $this->outputVerbose = filter_var($verbose, \FILTER_VALIDATE_BOOLEAN);

  /**
   * Determines if the extension is enabled.
   *
   * @return bool
   *   TRUE if enabled, FALSE if disabled.
   */
  public static function isEnabled(): bool {
    return self::$instance !== NULL;
    $facade->registerSubscriber(new TestRunnerStartedSubscriber($this));
    $facade->registerSubscriber(new TestRunnerFinishedSubscriber($this));

    $this->enabled = TRUE;
  }

  /**
@@ -84,11 +100,10 @@ public static function isEnabled(): bool {
   * @throws \RuntimeException
   */
  public static function log(string $logEntry): void {
    if (!self::isEnabled()) {
    $browserOutputFile = getenv('BROWSERTEST_OUTPUT_FILE');
    if ($browserOutputFile === FALSE) {
      throw new \RuntimeException("HTML output is not enabled");
    }

    $browserOutputFile = getenv('BROWSERTEST_OUTPUT_FILE');
    file_put_contents($browserOutputFile, $logEntry . "\n", FILE_APPEND);
  }

@@ -96,7 +111,7 @@ public static function log(string $logEntry): void {
   * Empties the list of the HTML output created during the test run.
   */
  public function testRunnerStarted(TestRunnerStarted $event): void {
    if (!self::isEnabled()) {
    if (!$this->enabled) {
      throw new \RuntimeException("HTML output is not enabled");
    }

@@ -118,7 +133,7 @@ public function testRunnerStarted(TestRunnerStarted $event): void {
   * Prints the list of HTML output generated during the test.
   */
  public function testRunnerFinished(TestRunnerFinished $event): void {
    if (!self::isEnabled()) {
    if (!$this->enabled) {
      throw new \RuntimeException("HTML output is not enabled");
    }

+2 −1
Original line number Diff line number Diff line
@@ -125,7 +125,8 @@ protected function htmlOutput($message = NULL) {
   * Creates the directory to store browser output.
   */
  protected function initBrowserOutputFile() {
    $this->htmlOutputEnabled = HtmlOutputLogger::isEnabled();
    $browserOutputFile = getenv('BROWSERTEST_OUTPUT_FILE');
    $this->htmlOutputEnabled = $browserOutputFile !== FALSE;
    $this->htmlOutputBaseUrl = getenv('BROWSERTEST_OUTPUT_BASE_URL') ?: $GLOBALS['base_url'];
    if ($this->htmlOutputEnabled) {
      $this->htmlOutputClassName = str_replace("\\", "_", static::class);
+0 −3
Original line number Diff line number Diff line
@@ -29,9 +29,6 @@ Settings to change in this file:
* SIMPLETEST_DB: The URL of your Drupal database
* The bootstrap attribute of the top-level phpunit tag, to take into account
  the location of the file
* BROWSERTEST_OUTPUT_DIRECTORY: Set to sites/simpletest/browser_output;
  you will also want to uncomment the printerClass attribute of the
  top-level phpunit tag.

### Additional setup for JavaScript tests

Loading