From 914d47f168f0a9192fee3e79ea51546bef7b40ff Mon Sep 17 00:00:00 2001
From: Lee Rowlands <lee.rowlands@previousnext.com.au>
Date: Thu, 12 Sep 2019 13:01:25 +1000
Subject: [PATCH] Issue #2800267 by Mile23, Lendude, drugan, dawehner,
 mondrake: Turn simpletest_clean*() functions into a helper class

---
 .../Drupal/Core/Test/EnvironmentCleaner.php   | 194 ++++++++++++++++++
 .../Core/Test/EnvironmentCleanerInterface.php |  57 +++++
 core/lib/Drupal/Core/Test/TestDatabase.php    |   2 +-
 core/modules/simpletest/simpletest.install    |   2 +-
 core/modules/simpletest/simpletest.module     | 117 ++++-------
 .../simpletest/simpletest.services.yml        |   8 +-
 .../src/EnvironmentCleanerFactory.php         |  55 +++++
 .../src/EnvironmentCleanerService.php         | 124 +++++++++++
 .../src/Form/SimpletestResultsForm.php        |  16 +-
 .../tests/src/Functional/SimpletestTest.php   |  36 ++++
 .../src/Kernel/DeprecatedCleanupTest.php      |  65 ++++++
 core/scripts/run-tests.sh                     |  20 +-
 .../Core/Test/EnvironmentCleanerTest.php      |  55 +++++
 13 files changed, 668 insertions(+), 83 deletions(-)
 create mode 100644 core/lib/Drupal/Core/Test/EnvironmentCleaner.php
 create mode 100644 core/lib/Drupal/Core/Test/EnvironmentCleanerInterface.php
 create mode 100644 core/modules/simpletest/src/EnvironmentCleanerFactory.php
 create mode 100644 core/modules/simpletest/src/EnvironmentCleanerService.php
 create mode 100644 core/modules/simpletest/tests/src/Functional/SimpletestTest.php
 create mode 100644 core/modules/simpletest/tests/src/Kernel/DeprecatedCleanupTest.php
 create mode 100644 core/tests/Drupal/KernelTests/Core/Test/EnvironmentCleanerTest.php

diff --git a/core/lib/Drupal/Core/Test/EnvironmentCleaner.php b/core/lib/Drupal/Core/Test/EnvironmentCleaner.php
new file mode 100644
index 000000000000..31da3cfc07b0
--- /dev/null
+++ b/core/lib/Drupal/Core/Test/EnvironmentCleaner.php
@@ -0,0 +1,194 @@
+<?php
+
+namespace Drupal\Core\Test;
+
+use Drupal\Core\Database\Connection;
+use Drupal\Core\File\FileSystemInterface;
+use Symfony\Component\Console\Output\OutputInterface;
+
+/**
+ * Helper class for cleaning test environments.
+ */
+class EnvironmentCleaner implements EnvironmentCleanerInterface {
+
+  /**
+   * Path to Drupal root directory.
+   *
+   * @var string
+   */
+  protected $root;
+
+  /**
+   * Connection to the database being used for tests.
+   *
+   * @var \Drupal\Core\Database\Connection
+   */
+  protected $testDatabase;
+
+  /**
+   * Connection to the database where test results are stored.
+   *
+   * This could be the same as $testDatabase, or it could be different.
+   * run-tests.sh allows you to specify a different results database with the
+   * --sqlite parameter.
+   *
+   * @var \Drupal\Core\Database\Connection
+   */
+  protected $resultsDatabase;
+
+  /**
+   * The file system service.
+   *
+   * @var \Drupal\Core\File\FileSystemInterface
+   */
+  protected $fileSystem;
+
+  /**
+   * Console output.
+   *
+   * @var \Symfony\Component\Console\Output\OutputInterface
+   */
+  protected $output;
+
+  /**
+   * Construct an environment cleaner.
+   *
+   * @param string $root
+   *   The path to the root of the Drupal installation.
+   * @param \Drupal\Core\Database\Connection $test_database
+   *   Connection to the database against which tests were run.
+   * @param \Drupal\Core\Database\Connection $results_database
+   *   Connection to the database where test results were stored. This could be
+   *   the same as $test_database, or it could be different.
+   * @param \Symfony\Component\Console\Output\OutputInterface $output
+   *   A symfony console output object.
+   * @param \Drupal\Core\File\FileSystemInterface $file_system
+   *   The file_system service.
+   */
+  public function __construct($root, Connection $test_database, Connection $results_database, OutputInterface $output, FileSystemInterface $file_system) {
+    $this->root = $root;
+    $this->testDatabase = $test_database;
+    $this->resultsDatabase = $results_database;
+    $this->output = $output;
+    $this->fileSystem = $file_system;
+  }
+
+  /**
+   * {@inheritdoc}
+   */
+  public function cleanEnvironment($clear_results = TRUE, $clear_temp_directories = TRUE, $clear_database = TRUE) {
+    $count = 0;
+    if ($clear_database) {
+      $this->doCleanDatabase();
+    }
+    if ($clear_temp_directories) {
+      $this->doCleanTemporaryDirectories();
+    }
+    if ($clear_results) {
+      $count = $this->cleanResultsTable();
+      $this->output->write('Test results removed: ' . $count);
+    }
+    else {
+      $this->output->write('Test results were not removed.');
+    }
+  }
+
+  /**
+   * {@inheritdoc}
+   */
+  public function cleanDatabase() {
+    $count = $this->doCleanDatabase();
+    if ($count > 0) {
+      $this->output->write('Leftover tables removed: ' . $count);
+    }
+    else {
+      $this->output->write('No leftover tables to remove.');
+    }
+  }
+
+  /**
+   * Performs the fixture database cleanup.
+   *
+   * @return int
+   *   The number of tables that were removed.
+   */
+  protected function doCleanDatabase() {
+    /* @var $schema \Drupal\Core\Database\Schema */
+    $schema = $this->testDatabase->schema();
+    $tables = $schema->findTables('test%');
+    $count = 0;
+    foreach ($tables as $table) {
+      // Only drop tables which begin wih 'test' followed by digits, for example,
+      // {test12345678node__body}.
+      if (preg_match('/^test\d+.*/', $table, $matches)) {
+        $schema->dropTable($matches[0]);
+        $count++;
+      }
+    }
+    return $count;
+  }
+
+  /**
+   * {@inheritdoc}
+   */
+  public function cleanTemporaryDirectories() {
+    $count = $this->doCleanTemporaryDirectories();
+    if ($count > 0) {
+      $this->output->write('Temporary directories removed: ' . $count);
+    }
+    else {
+      $this->output->write('No temporary directories to remove.');
+    }
+  }
+
+  /**
+   * Performs the cleanup of temporary test directories.
+   *
+   * @return int
+   *   The count of temporary directories removed.
+   */
+  protected function doCleanTemporaryDirectories() {
+    $count = 0;
+    $simpletest_dir = $this->root . '/sites/simpletest';
+    if (is_dir($simpletest_dir)) {
+      $files = scandir($simpletest_dir);
+      foreach ($files as $file) {
+        if ($file[0] != '.') {
+          $path = $simpletest_dir . '/' . $file;
+          $this->fileSystem->deleteRecursive($path, function ($any_path) {
+            @chmod($any_path, 0700);
+          });
+          $count++;
+        }
+      }
+    }
+    return $count;
+  }
+
+  /**
+   * {@inheritdoc}
+   */
+  public function cleanResultsTable($test_id = NULL) {
+    $count = 0;
+    if ($test_id) {
+      $count = $this->resultsDatabase->query('SELECT COUNT(test_id) FROM {simpletest_test_id} WHERE test_id = :test_id', [':test_id' => $test_id])->fetchField();
+
+      $this->resultsDatabase->delete('simpletest')
+        ->condition('test_id', $test_id)
+        ->execute();
+      $this->resultsDatabase->delete('simpletest_test_id')
+        ->condition('test_id', $test_id)
+        ->execute();
+    }
+    else {
+      $count = $this->resultsDatabase->query('SELECT COUNT(test_id) FROM {simpletest_test_id}')->fetchField();
+
+      // Clear test results.
+      $this->resultsDatabase->delete('simpletest')->execute();
+      $this->resultsDatabase->delete('simpletest_test_id')->execute();
+    }
+
+    return $count;
+  }
+
+}
diff --git a/core/lib/Drupal/Core/Test/EnvironmentCleanerInterface.php b/core/lib/Drupal/Core/Test/EnvironmentCleanerInterface.php
new file mode 100644
index 000000000000..4487278ad2e9
--- /dev/null
+++ b/core/lib/Drupal/Core/Test/EnvironmentCleanerInterface.php
@@ -0,0 +1,57 @@
+<?php
+
+namespace Drupal\Core\Test;
+
+/**
+ * Defines an interface for cleaning up test results and fixtures.
+ *
+ * This interface is marked internal. It does not imply an API.
+ *
+ * @todo Formalize this interface in
+ *   https://www.drupal.org/project/drupal/issues/3075490 and
+ *   https://www.drupal.org/project/drupal/issues/3075608
+ *
+ * @see https://www.drupal.org/project/drupal/issues/3075490
+ * @see https://www.drupal.org/project/drupal/issues/3075608
+ *
+ * @internal
+ */
+interface EnvironmentCleanerInterface {
+
+  /**
+   * Removes all test-related database tables and directories.
+   *
+   * This method removes fixture files and database entries from the system
+   * under test.
+   *
+   * @param bool $clear_results
+   *   (optional) Whether to clear the test results database. Defaults to TRUE.
+   * @param bool $clear_temp_directories
+   *   (optional) Whether to clear the test site directories. Defaults to TRUE.
+   * @param bool $clear_database
+   *   (optional) Whether to clean up the fixture database. Defaults to TRUE.
+   */
+  public function cleanEnvironment($clear_results = TRUE, $clear_temp_directories = TRUE, $clear_database = TRUE);
+
+  /**
+   * Remove database entries left over in the fixture database.
+   */
+  public function cleanDatabase();
+
+  /**
+   * Finds all leftover fixture site directories and removes them.
+   */
+  public function cleanTemporaryDirectories();
+
+  /**
+   * Clears test result tables from the results database.
+   *
+   * @param $test_id
+   *   Test ID to remove results for, or NULL to remove all results.
+   *
+   * @return int
+   *   The number of results that were removed.
+   */
+  public function cleanResultsTable($test_id = NULL);
+
+}
diff --git a/core/lib/Drupal/Core/Test/TestDatabase.php b/core/lib/Drupal/Core/Test/TestDatabase.php
index 70c5c084fe10..115568f67b47 100644
--- a/core/lib/Drupal/Core/Test/TestDatabase.php
+++ b/core/lib/Drupal/Core/Test/TestDatabase.php
@@ -7,7 +7,7 @@
 use Drupal\Core\Database\Database;
 
 /**
- * Provides helper methods for interacting with the Simpletest database.
+ * Provides helper methods for interacting with the fixture database.
  */
 class TestDatabase {
 
diff --git a/core/modules/simpletest/simpletest.install b/core/modules/simpletest/simpletest.install
index 424502789135..ae3cf832391d 100644
--- a/core/modules/simpletest/simpletest.install
+++ b/core/modules/simpletest/simpletest.install
@@ -90,7 +90,7 @@ function simpletest_uninstall() {
   // in a (recursive) test for itself, since simpletest_clean_environment()
   // would also delete the test site of the parent test process.
   if (!drupal_valid_test_ua()) {
-    simpletest_clean_environment();
+    \Drupal::service('environment_cleaner')->cleanEnvironment();
   }
   // Delete verbose test output and any other testing framework files.
   try {
diff --git a/core/modules/simpletest/simpletest.module b/core/modules/simpletest/simpletest.module
index 07a2588602cc..5775c1d83da2 100644
--- a/core/modules/simpletest/simpletest.module
+++ b/core/modules/simpletest/simpletest.module
@@ -6,7 +6,6 @@
  */
 
 use Drupal\Core\Asset\AttachedAssetsInterface;
-use Drupal\Core\Database\Database;
 use Drupal\Core\File\Exception\FileException;
 use Drupal\Core\Render\Element;
 use Drupal\Core\Routing\RouteMatchInterface;
@@ -596,72 +595,51 @@ function simpletest_generate_file($filename, $width, $lines, $type = 'binary-tex
 
 /**
  * Removes all temporary database tables and directories.
+ *
+ * @deprecated in drupal:8.8.0 and is removed from drupal:9.0.0. Access the
+ *   environment_cleaner service and call its cleanEnvironment() method, or use
+ *   \Drupal\Core\Test\EnvironmentCleaner::cleanEnvironment() instead.
+ *
+ * @see https://www.drupal.org/node/3076634
  */
 function simpletest_clean_environment() {
-  simpletest_clean_database();
-  simpletest_clean_temporary_directories();
-  if (\Drupal::config('simpletest.settings')->get('clear_results')) {
-    $count = simpletest_clean_results_table();
-    \Drupal::messenger()->addStatus(\Drupal::translation()->formatPlural($count, 'Removed 1 test result.', 'Removed @count test results.'));
-  }
-  else {
-    \Drupal::messenger()->addWarning(t('Clear results is disabled and the test results table will not be cleared.'), 'warning');
-  }
+  @trigger_error(__FUNCTION__ . ' is deprecated in drupal:8.8.0 and is removed from drupal:9.0.0. Access the environment_cleaner service and call its cleanEnvironment() method, or use \Drupal\Core\Test\EnvironmentCleaner::cleanEnvironment() instead.. See https://www.drupal.org/node/3076634', E_USER_DEPRECATED);
+  /* @var $cleaner \Drupal\simpletest\EnvironmentCleanerService */
+  $cleaner = \Drupal::service('environment_cleaner');
+  $cleaner->cleanEnvironment();
 }
 
 /**
  * Removes prefixed tables from the database from crashed tests.
+ *
+ * @deprecated in drupal:8.8.0 and is removed from drupal:9.0.0. Access the
+ *   environment_cleaner service and call its cleanDatabase() method, or use
+ *   \Drupal\Core\Test\EnvironmentCleaner::cleanDatabase() instead.
+ *
+ * @see https://www.drupal.org/node/3076634
  */
 function simpletest_clean_database() {
-  $schema = Database::getConnection()->schema();
-  $tables = $schema->findTables('test%');
-  $count = 0;
-  foreach ($tables as $table) {
-    // Only drop tables which begin wih 'test' followed by digits, for example,
-    // {test12345678node__body}.
-    if (preg_match('/^test\d+.*/', $table, $matches)) {
-      $schema->dropTable($matches[0]);
-      $count++;
-    }
-  }
-
-  if ($count > 0) {
-    \Drupal::messenger()->addStatus(\Drupal::translation()->formatPlural($count, 'Removed 1 leftover table.', 'Removed @count leftover tables.'));
-  }
-  else {
-    \Drupal::messenger()->addStatus(t('No leftover tables to remove.'));
-  }
+  @trigger_error(__FUNCTION__ . ' is deprecated in drupal:8.8.0 and is removed from drupal:9.0.0. Access the environment_cleaner service and call its cleanDatabase() method, or use \Drupal\Core\Test\EnvironmentCleaner::cleanDatabase() instead. See https://www.drupal.org/node/3076634', E_USER_DEPRECATED);
+  /* @var $cleaner \Drupal\simpletest\EnvironmentCleanerService */
+  $cleaner = \Drupal::service('environment_cleaner');
+  $cleaner->cleanDatabase();
 }
 
 /**
  * Finds all leftover temporary directories and removes them.
+ *
+ * @deprecated in drupal:8.8.0 and is removed from drupal:9.0.0. Access the
+ *   environment_cleaner service and call its cleanTemporaryDirectories()
+ *   method, or use
+ *   \Drupal\Core\Test\EnvironmentCleaner::cleanTemporaryDirectories() instead.
+ *
+ * @see https://www.drupal.org/node/3076634
  */
 function simpletest_clean_temporary_directories() {
-  $count = 0;
-  if (is_dir(DRUPAL_ROOT . '/sites/simpletest')) {
-    $files = scandir(DRUPAL_ROOT . '/sites/simpletest');
-    foreach ($files as $file) {
-      if ($file[0] != '.') {
-        $path = DRUPAL_ROOT . '/sites/simpletest/' . $file;
-        try {
-          \Drupal::service('file_system')->deleteRecursive($path, function ($any_path) {
-            @chmod($any_path, 0700);
-          });
-        }
-        catch (FileException $e) {
-          // Ignore failed deletes.
-        }
-        $count++;
-      }
-    }
-  }
-
-  if ($count > 0) {
-    \Drupal::messenger()->addStatus(\Drupal::translation()->formatPlural($count, 'Removed 1 temporary directory.', 'Removed @count temporary directories.'));
-  }
-  else {
-    \Drupal::messenger()->addStatus(t('No temporary directories to remove.'));
-  }
+  @trigger_error(__FUNCTION__ . ' is deprecated in drupal:8.8.0 and is removed from drupal:9.0.0. Access the environment_cleaner service and call its cleanTemporaryDirectories() method, or use \Drupal\Core\Test\EnvironmentCleaner::cleanTemporaryDirectories() instead. See https://www.drupal.org/node/3076634', E_USER_DEPRECATED);
+  /* @var $cleaner \Drupal\simpletest\EnvironmentCleanerService */
+  $cleaner = \Drupal::service('environment_cleaner');
+  $cleaner->cleanTemporaryDirectories();
 }
 
 /**
@@ -672,31 +650,22 @@ function simpletest_clean_temporary_directories() {
  *
  * @return int
  *   The number of results that were removed.
+ *
+ * @deprecated in drupal:8.8.0 and is removed from drupal:9.0.0. Access the
+ *   environment_cleaner service and call its cleanResultsTable() method, or use
+ *   \Drupal\Core\Test\EnvironmentCleaner::cleanResultsTable() instead.
+ *
+ * @see https://www.drupal.org/node/3076634
  */
 function simpletest_clean_results_table($test_id = NULL) {
+  @trigger_error(__FUNCTION__ . ' is deprecated in drupal:8.8.0 and is removed from drupal:9.0.0. Access the environment_cleaner service and call its cleanResultsTable() method, or use \Drupal\Core\Test\EnvironmentCleaner::cleanResultsTable() instead. See https://www.drupal.org/node/3076634', E_USER_DEPRECATED);
+  $count = 0;
   if (\Drupal::config('simpletest.settings')->get('clear_results')) {
-    $connection = TestDatabase::getConnection();
-    if ($test_id) {
-      $count = $connection->query('SELECT COUNT(test_id) FROM {simpletest_test_id} WHERE test_id = :test_id', [':test_id' => $test_id])->fetchField();
-
-      $connection->delete('simpletest')
-        ->condition('test_id', $test_id)
-        ->execute();
-      $connection->delete('simpletest_test_id')
-        ->condition('test_id', $test_id)
-        ->execute();
-    }
-    else {
-      $count = $connection->query('SELECT COUNT(test_id) FROM {simpletest_test_id}')->fetchField();
-
-      // Clear test results.
-      $connection->delete('simpletest')->execute();
-      $connection->delete('simpletest_test_id')->execute();
-    }
-
-    return $count;
+    /* @var $cleaner \Drupal\simpletest\EnvironmentCleanerService */
+    $cleaner = \Drupal::service('environment_cleaner');
+    $count = $cleaner->cleanResultsTable($test_id);
   }
-  return 0;
+  return $count;
 }
 
 /**
diff --git a/core/modules/simpletest/simpletest.services.yml b/core/modules/simpletest/simpletest.services.yml
index 241519795798..afae7c4358c4 100644
--- a/core/modules/simpletest/simpletest.services.yml
+++ b/core/modules/simpletest/simpletest.services.yml
@@ -1,9 +1,13 @@
 services:
   test_discovery:
-    # @todo Change this service so that it uses \Drupal\Core\Test\TestDiscovery
-    # in https://www.drupal.org/node/1667822
     class: Drupal\simpletest\TestDiscovery
     arguments: ['@app.root', '@class_loader', '@module_handler']
+  environment_cleaner_factory:
+    class: Drupal\simpletest\EnvironmentCleanerFactory
+    arguments: ['@service_container']
+  environment_cleaner:
+    class: Drupal\simpletest\EnvironmentCleanerService
+    factory: 'environment_cleaner_factory:createCleaner'
   cache_context.test_discovery:
     class: Drupal\simpletest\Cache\Context\TestDiscoveryCacheContext
     arguments: ['@test_discovery', '@private_key']
diff --git a/core/modules/simpletest/src/EnvironmentCleanerFactory.php b/core/modules/simpletest/src/EnvironmentCleanerFactory.php
new file mode 100644
index 000000000000..dc48b50a214f
--- /dev/null
+++ b/core/modules/simpletest/src/EnvironmentCleanerFactory.php
@@ -0,0 +1,55 @@
+<?php
+
+namespace Drupal\simpletest;
+
+use Drupal\Core\DependencyInjection\Container;
+use Drupal\Core\Test\TestDatabase;
+use Drupal\Core\Database\Database;
+
+/**
+ * Test environment cleaner factory.
+ *
+ * We use a factory pattern here so that we can inject the test results database
+ * which is not a service (and should not be).
+ */
+class EnvironmentCleanerFactory {
+
+  /**
+   * The container.
+   *
+   * @var \Drupal\Core\DependencyInjection\Container
+   */
+  protected $container;
+
+  /**
+   * Construct an environment cleaner factory.
+   *
+   * @param \Drupal\Core\DependencyInjection\Container $container
+   *   The container.
+   */
+  public function __construct(Container $container) {
+    $this->container = $container;
+  }
+
+  /**
+   * Factory method to create the environment cleaner service.
+   *
+   * @return \Drupal\simpletest\EnvironmentCleanerService
+   *   The environment cleaner service.
+   */
+  public function createCleaner() {
+    $cleaner = new EnvironmentCleanerService(
+      $this->container->get('app.root'),
+      Database::getConnection(),
+      TestDatabase::getConnection(),
+      $this->container->get('messenger'),
+      $this->container->get('string_translation'),
+      $this->container->get('config.factory'),
+      $this->container->get('cache.default'),
+      $this->container->get('file_system')
+    );
+
+    return $cleaner;
+  }
+
+}
diff --git a/core/modules/simpletest/src/EnvironmentCleanerService.php b/core/modules/simpletest/src/EnvironmentCleanerService.php
new file mode 100644
index 000000000000..b4de5789ea9b
--- /dev/null
+++ b/core/modules/simpletest/src/EnvironmentCleanerService.php
@@ -0,0 +1,124 @@
+<?php
+
+namespace Drupal\simpletest;
+
+use Drupal\Core\Database\Connection;
+use Drupal\Core\Messenger\MessengerInterface;
+use Drupal\Core\StringTranslation\TranslationInterface;
+use Drupal\Core\Config\ConfigFactory;
+use Drupal\Core\Cache\CacheBackendInterface;
+use Drupal\Core\File\FileSystem;
+use Drupal\Core\Test\EnvironmentCleaner;
+
+/**
+ * Uses containerized services to perform post-test cleanup.
+ */
+class EnvironmentCleanerService extends EnvironmentCleaner {
+
+  /**
+   * Messenger service.
+   *
+   * @var \Drupal\Core\Messenger\MessengerInterface
+   */
+  protected $messenger;
+
+  /**
+   * The translation service.
+   *
+   * @var \Drupal\Core\StringTranslation\TranslationInterface
+   */
+  protected $translation;
+
+  /**
+   * The config factory.
+   *
+   * @var \Drupal\Core\Config\ConfigFactory
+   */
+  protected $configFactory;
+
+  /**
+   * Default cache.
+   *
+   * @var \Drupal\Core\Cache\CacheBackendInterface
+   */
+  protected $cacheDefault;
+
+  /**
+   * Construct an environment cleaner.
+   *
+   * @param string $root
+   *   The path to the root of the Drupal installation.
+   * @param \Drupal\Core\Database\Connection $test_database
+   *   Connection to the database against which tests were run.
+   * @param \Drupal\Core\Database\Connection $results_database
+   *   Connection to the database where test results were stored. This could be
+   *   the same as $test_database, or it could be different.
+   * @param \Drupal\Core\StringTranslation\TranslationInterface|null $translation
+   *   (optional) The translation service. If none is supplied, this class will
+   *   attempt to discover one using \Drupal.
+   */
+  public function __construct($root, Connection $test_database, Connection $results_database, MessengerInterface $messenger, TranslationInterface $translation, ConfigFactory $config, CacheBackendInterface $cache_default, FileSystem $file_system) {
+    $this->root = $root;
+    $this->testDatabase = $test_database;
+    $this->resultsDatabase = $results_database;
+    $this->messenger = $messenger;
+    $this->translation = $translation;
+    $this->configFactory = $config;
+    $this->cacheDefault = $cache_default;
+    $this->fileSystem = $file_system;
+  }
+
+  /**
+   * {@inheritdoc}
+   */
+  public function cleanEnvironment($clear_results = TRUE, $clear_temp_directories = TRUE, $clear_database = TRUE) {
+    $results_removed = 0;
+    $clear_results = $this->configFactory->get('simpletest.settings')->get('clear_results');
+
+    if ($clear_database) {
+      $this->cleanDatabase();
+    }
+    if ($clear_temp_directories) {
+      $this->cleanTemporaryDirectories();
+    }
+    if ($clear_results) {
+      $results_removed = $this->cleanResultsTable();
+    }
+    $this->cacheDefault->delete('simpletest');
+    $this->cacheDefault->delete('simpletest_phpunit');
+
+    if ($clear_results) {
+      $this->messenger->addMessage($this->translation->formatPlural($results_removed, 'Removed 1 test result.', 'Removed @count test results.'));
+    }
+    else {
+      $this->messenger->addMessage($this->translation->translate('Clear results is disabled and the test results table will not be cleared.'), 'warning');
+    }
+  }
+
+  /**
+   * {@inheritdoc}
+   */
+  public function cleanDatabase() {
+    $tables_removed = $this->doCleanDatabase();
+    if ($tables_removed > 0) {
+      $this->messenger->addMessage($this->translation->formatPlural($tables_removed, 'Removed 1 leftover table.', 'Removed @count leftover tables.'));
+    }
+    else {
+      $this->messenger->addMessage($this->translation->translate('No leftover tables to remove.'));
+    }
+  }
+
+  /**
+   * {@inheritdoc}
+   */
+  public function cleanTemporaryDirectories() {
+    $directories_removed = $this->doCleanTemporaryDirectories();
+    if ($directories_removed > 0) {
+      $this->messenger->addMessage($this->translation->formatPlural($directories_removed, 'Removed 1 temporary directory.', 'Removed @count temporary directories.'));
+    }
+    else {
+      $this->messenger->addMessage($this->translation->translate('No temporary directories to remove.'));
+    }
+  }
+
+}
diff --git a/core/modules/simpletest/src/Form/SimpletestResultsForm.php b/core/modules/simpletest/src/Form/SimpletestResultsForm.php
index 5db8846353fb..adc1d4a5bbbe 100644
--- a/core/modules/simpletest/src/Form/SimpletestResultsForm.php
+++ b/core/modules/simpletest/src/Form/SimpletestResultsForm.php
@@ -6,6 +6,7 @@
 use Drupal\Core\Form\FormBase;
 use Drupal\Core\Form\FormState;
 use Drupal\Core\Form\FormStateInterface;
+use Drupal\Core\Test\EnvironmentCleanerInterface;
 use Drupal\Core\Url;
 use Drupal\simpletest\TestDiscovery;
 use Symfony\Component\DependencyInjection\ContainerInterface;
@@ -37,12 +38,20 @@ class SimpletestResultsForm extends FormBase {
    */
   protected $database;
 
+  /**
+   * The environment cleaner service.
+   *
+   * @var \Drupal\Core\Test\EnvironmentCleanerInterface
+   */
+  protected $cleaner;
+
   /**
    * {@inheritdoc}
    */
   public static function create(ContainerInterface $container) {
     return new static(
-      $container->get('database')
+      $container->get('database'),
+      $container->get('environment_cleaner')
     );
   }
 
@@ -52,8 +61,9 @@ public static function create(ContainerInterface $container) {
    * @param \Drupal\Core\Database\Connection $database
    *   The database connection service.
    */
-  public function __construct(Connection $database) {
+  public function __construct(Connection $database, EnvironmentCleanerInterface $cleaner) {
     $this->database = $database;
+    $this->cleaner = $cleaner;
   }
 
   /**
@@ -162,7 +172,7 @@ public function buildForm(array $form, FormStateInterface $form_state, $test_id
     ];
 
     if (is_numeric($test_id)) {
-      simpletest_clean_results_table($test_id);
+      $this->cleaner->cleanResultsTable($test_id);
     }
 
     return $form;
diff --git a/core/modules/simpletest/tests/src/Functional/SimpletestTest.php b/core/modules/simpletest/tests/src/Functional/SimpletestTest.php
new file mode 100644
index 000000000000..a7f634deb0fd
--- /dev/null
+++ b/core/modules/simpletest/tests/src/Functional/SimpletestTest.php
@@ -0,0 +1,36 @@
+<?php
+
+namespace Drupal\Tests\simpletest\Functional;
+
+use Drupal\Tests\BrowserTestBase;
+
+/**
+ * Basic functionality of the Testing module.
+ *
+ * @group simpletest
+ */
+class SimpletestTest extends BrowserTestBase {
+
+  /**
+   * Modules to enable.
+   *
+   * @var array
+   */
+  public static $modules = ['simpletest'];
+
+  /**
+   * Test that we can uninstall the module without mishap.
+   *
+   * Upon uninstall, simpletest will clean up after itself. This should neither
+   * break the test runner's expectations, nor cause any kind of exception.
+   *
+   * Note that this might break run-tests.sh test runs that don't use the
+   * --sqlite argument.
+   */
+  public function testUninstallModule() {
+    /* @var $installer \Drupal\Core\Extension\ModuleInstallerInterface */
+    $installer = $this->container->get('module_installer');
+    $this->assertTrue($installer->uninstall(['simpletest']));
+  }
+
+}
diff --git a/core/modules/simpletest/tests/src/Kernel/DeprecatedCleanupTest.php b/core/modules/simpletest/tests/src/Kernel/DeprecatedCleanupTest.php
new file mode 100644
index 000000000000..ec29a7676868
--- /dev/null
+++ b/core/modules/simpletest/tests/src/Kernel/DeprecatedCleanupTest.php
@@ -0,0 +1,65 @@
+<?php
+
+namespace Drupal\Tests\simpletest\Kernel;
+
+use Drupal\Core\DependencyInjection\ContainerBuilder;
+use Drupal\Core\Test\EnvironmentCleanerInterface;
+use Drupal\KernelTests\KernelTestBase;
+use Symfony\Component\DependencyInjection\Definition;
+
+/**
+ * Verify deprecation errors for the cleanup functions.
+ *
+ * @group simpletest
+ * @group legacy
+ */
+class DeprecatedCleanupTest extends KernelTestBase {
+
+  public static $modules = ['simpletest'];
+
+  /**
+   * {@inheritdoc}
+   */
+  public function register(ContainerBuilder $container) {
+    parent::register($container);
+    $cleaner_definition = new Definition(StubEnvironmentCleanerService::class);
+    $container->setDefinition('environment_cleaner', $cleaner_definition);
+  }
+
+  /**
+   * @expectedDeprecation simpletest_clean_environment is deprecated in drupal:8.8.0 and is removed from drupal:9.0.0. Access the environment_cleaner service and call its cleanEnvironment() method, or use \Drupal\Core\Test\EnvironmentCleaner::cleanEnvironment() instead.. See https://www.drupal.org/node/3076634
+   * @expectedDeprecation simpletest_clean_database is deprecated in drupal:8.8.0 and is removed from drupal:9.0.0. Access the environment_cleaner service and call its cleanDatabase() method, or use \Drupal\Core\Test\EnvironmentCleaner::cleanDatabase() instead. See https://www.drupal.org/node/3076634
+   * @expectedDeprecation simpletest_clean_temporary_directories is deprecated in drupal:8.8.0 and is removed from drupal:9.0.0. Access the environment_cleaner service and call its cleanTemporaryDirectories() method, or use \Drupal\Core\Test\EnvironmentCleaner::cleanTemporaryDirectories() instead. See https://www.drupal.org/node/3076634
+   * @expectedDeprecation simpletest_clean_results_table is deprecated in drupal:8.8.0 and is removed from drupal:9.0.0. Access the environment_cleaner service and call its cleanResultsTable() method, or use \Drupal\Core\Test\EnvironmentCleaner::cleanResultsTable() instead. See https://www.drupal.org/node/3076634
+   */
+  public function testDeprecatedCleanFunctions() {
+    $this->assertNull(simpletest_clean_environment());
+    $this->assertNull(simpletest_clean_database());
+    $this->assertNull(simpletest_clean_temporary_directories());
+    $this->assertEquals(0, simpletest_clean_results_table());
+  }
+
+}
+
+/**
+ * Mock environment_cleaner service that does not perform any cleaning.
+ */
+class StubEnvironmentCleanerService implements EnvironmentCleanerInterface {
+
+  public function cleanDatabase() {
+
+  }
+
+  public function cleanEnvironment($clear_results = TRUE, $clear_temp_directories = TRUE, $clear_database = TRUE) {
+
+  }
+
+  public function cleanResultsTable($test_id = NULL) {
+
+  }
+
+  public function cleanTemporaryDirectories() {
+
+  }
+
+}
diff --git a/core/scripts/run-tests.sh b/core/scripts/run-tests.sh
index c4f910e2024d..c3f9d1a73aa8 100755
--- a/core/scripts/run-tests.sh
+++ b/core/scripts/run-tests.sh
@@ -14,12 +14,14 @@
 use Drupal\Core\File\Exception\FileException;
 use Drupal\Core\File\FileSystemInterface;
 use Drupal\Core\StreamWrapper\PublicStream;
+use Drupal\Core\Test\EnvironmentCleaner;
 use Drupal\Core\Test\PhpUnitTestRunner;
 use Drupal\Core\Test\TestDatabase;
 use Drupal\Core\Test\TestRunnerKernel;
 use Drupal\simpletest\Form\SimpletestResultsForm;
 use Drupal\Core\Test\TestDiscovery;
 use PHPUnit\Framework\TestCase;
+use Symfony\Component\Console\Output\ConsoleOutput;
 use Symfony\Component\HttpFoundation\Request;
 
 // Define some colors for display.
@@ -126,8 +128,15 @@
 
 if ($args['clean']) {
   // Clean up left-over tables and directories.
+  $cleaner = new EnvironmentCleaner(
+    DRUPAL_ROOT,
+    Database::getConnection(),
+    TestDatabase::getConnection(),
+    new ConsoleOutput(),
+    \Drupal::service('file_system')
+  );
   try {
-    simpletest_clean_environment();
+    $cleaner->cleanEnvironment();
   }
   catch (Exception $e) {
     echo (string) $e;
@@ -181,7 +190,14 @@
 // Clean up all test results.
 if (!$args['keep-results']) {
   try {
-    simpletest_clean_results_table();
+    $cleaner = new EnvironmentCleaner(
+      DRUPAL_ROOT,
+      Database::getConnection(),
+      TestDatabase::getConnection(),
+      new ConsoleOutput(),
+      \Drupal::service('file_system')
+    );
+    $cleaner->cleanResultsTable();
   }
   catch (Exception $e) {
     echo (string) $e;
diff --git a/core/tests/Drupal/KernelTests/Core/Test/EnvironmentCleanerTest.php b/core/tests/Drupal/KernelTests/Core/Test/EnvironmentCleanerTest.php
new file mode 100644
index 000000000000..0ca66926007d
--- /dev/null
+++ b/core/tests/Drupal/KernelTests/Core/Test/EnvironmentCleanerTest.php
@@ -0,0 +1,55 @@
+<?php
+
+namespace Drupal\KernelTests\Core\Test;
+
+use Drupal\Core\Database\Connection;
+use Drupal\Core\Test\EnvironmentCleaner;
+use Drupal\KernelTests\KernelTestBase;
+use org\bovigo\vfs\vfsStream;
+use Symfony\Component\Console\Output\NullOutput;
+
+/**
+ * @coversDefaultClass \Drupal\Core\Test\EnvironmentCleaner
+ * @group Test
+ */
+class EnvironmentCleanerTest extends KernelTestBase {
+
+  /**
+   * @covers ::doCleanTemporaryDirectories
+   */
+  public function testDoCleanTemporaryDirectories() {
+    vfsStream::setup('cleanup_test', NULL, [
+      'sites' => [
+        'simpletest' => [
+          'delete_dir' => [
+            'delete.me' => 'I am a gonner.',
+          ],
+          'delete_me.too' => 'delete this file.',
+        ],
+      ],
+    ]);
+
+    $connection = $this->prophesize(Connection::class);
+
+    $cleaner = new EnvironmentCleaner(
+      vfsStream::url('cleanup_test'),
+      $connection->reveal(),
+      $connection->reveal(),
+      new NullOutput(),
+      \Drupal::service('file_system')
+    );
+
+    $do_cleanup_ref = new \ReflectionMethod($cleaner, 'doCleanTemporaryDirectories');
+    $do_cleanup_ref->setAccessible(TRUE);
+
+    $this->assertFileExists(vfsStream::url('cleanup_test/sites/simpletest/delete_dir/delete.me'));
+    $this->assertFileExists(vfsStream::url('cleanup_test/sites/simpletest/delete_me.too'));
+
+    $this->assertEquals(2, $do_cleanup_ref->invoke($cleaner));
+
+    $this->assertDirectoryNotExists(vfsStream::url('cleanup_test/sites/simpletest/delete_dir'));
+    $this->assertFileNotExists(vfsStream::url('cleanup_test/sites/simpletest/delete_dir/delete.me'));
+    $this->assertFileNotExists(vfsStream::url('cleanup_test/sites/simpletest/delete_me.too'));
+  }
+
+}
-- 
GitLab