diff --git a/automatic_updates.info.yml b/automatic_updates.info.yml
index 5f79eeb10cbcd43d73db8dd2df1608add9aa4a02..fdcd5bb618134620ddf59884a21a92449b59b83b 100644
--- a/automatic_updates.info.yml
+++ b/automatic_updates.info.yml
@@ -5,4 +5,5 @@ core: 8.x
 package: 'Security'
 configure: automatic_updates.settings
 dependencies:
+  - drupal:system
   - drupal:update
diff --git a/automatic_updates.install b/automatic_updates.install
index 412a963fde461d834648adb304db4ae2e01ac556..25965a7ca70170bd1842cab775ca50c050279483 100644
--- a/automatic_updates.install
+++ b/automatic_updates.install
@@ -5,7 +5,9 @@
  * Automatic updates install file.
  */
 
+use Drupal\automatic_updates\ReadinessChecker\ReadinessCheckerManagerInterface;
 use Drupal\Core\StringTranslation\PluralTranslatableMarkup;
+use Drupal\Core\Url;
 
 /**
  * Implements hook_requirements().
@@ -16,8 +18,72 @@ function automatic_updates_requirements($phase) {
   }
 
   $requirements = [];
+  _automatic_updates_checker_requirements($requirements);
+  _automatic_updates_psa_requirements($requirements);
+  return $requirements;
+}
+
+/**
+ * Display requirements from results of readiness checker.
+ *
+ * @param array $requirements
+ *   The requirements array.
+ */
+function _automatic_updates_checker_requirements(array &$requirements) {
+  /** @var \Drupal\automatic_updates\ReadinessChecker\ReadinessCheckerManagerInterface $checker */
+  $checker = \Drupal::service('automatic_updates.readiness_checker');
+  if (!$checker->isEnabled()) {
+    return;
+  }
+
+  $last_check_timestamp = $checker->timestamp();
+  $requirements['automatic_updates_readiness'] = [
+    'title' => t('Update readiness checks'),
+    'severity' => REQUIREMENT_OK,
+    'value' => t('Your site is ready to for <a href="@readiness_checks">automatic updates</a>.', ['@readiness_checks' => 'https://www.drupal.org/docs/8/update/automatic-updates#readiness-checks']),
+  ];
+  $error_results = $checker->getResults('error');
+  $warning_results = $checker->getResults('warning');
+  $checker_results = array_merge($error_results, $warning_results);
+  if (!empty($checker_results)) {
+    $requirements['automatic_updates_readiness']['severity'] = $error_results ? REQUIREMENT_ERROR : REQUIREMENT_WARNING;
+    $requirements['automatic_updates_readiness']['value'] = new PluralTranslatableMarkup(count($checker_results), '@count check failed:', '@count checks failed:');
+    $requirements['automatic_updates_readiness']['description'] = [
+      '#theme' => 'item_list',
+      '#items' => $checker_results,
+    ];
+  }
+  if (\Drupal::time()->getRequestTime() > $last_check_timestamp + ReadinessCheckerManagerInterface::LAST_CHECKED_WARNING) {
+    $requirements['automatic_updates_readiness']['severity'] = REQUIREMENT_ERROR;
+    $requirements['automatic_updates_readiness']['value'] = t('Your site has not recently checked if it is ready to apply <a href="@readiness_checks">automatic updates</a>.', ['@readiness_checks' => 'https://www.drupal.org/docs/8/update/automatic-updates#readiness-checks']);
+    $readiness_check = Url::fromRoute('automatic_updates.update_readiness');
+    $time_ago = \Drupal::service('date.formatter')->formatTimeDiffSince($last_check_timestamp);
+    if ($last_check_timestamp === 0) {
+      $requirements['automatic_updates_readiness']['description'] = t('<a href="@link">Run readiness checks</a> manually.', [
+        '@link' => $readiness_check->toString(),
+      ]);
+    }
+    elseif ($readiness_check->access()) {
+      $requirements['automatic_updates_readiness']['description'] = t('Last run @time ago. <a href="@link">Run readiness checks</a> manually.', [
+        '@time' => $time_ago,
+        '@link' => $readiness_check->toString(),
+      ]);
+    }
+    else {
+      $requirements['automatic_updates_readiness']['description'] = t('Readiness checks were last run @time ago.', ['@time' => $time_ago]);
+    }
+  }
+}
+
+/**
+ * Display requirements from public service announcements.
+ *
+ * @param array $requirements
+ *   The requirements array.
+ */
+function _automatic_updates_psa_requirements(array &$requirements) {
   if (!\Drupal::config('automatic_updates.settings')->get('enable_psa')) {
-    return $requirements;
+    return;
   }
   /** @var \Drupal\automatic_updates\Services\AutomaticUpdatesPsa $psa */
   $psa = \Drupal::service('automatic_updates.psa');
@@ -35,5 +101,4 @@ function automatic_updates_requirements($phase) {
       '#items' => $messages,
     ];
   }
-  return $requirements;
 }
diff --git a/automatic_updates.module b/automatic_updates.module
index 65d6557ea1f6bc13f51376b2641f7437ecf13b54..cb65f4a4b04f92971b2026beb84b46927a693fb7 100644
--- a/automatic_updates.module
+++ b/automatic_updates.module
@@ -5,6 +5,8 @@
  * Contains automatic_updates.module..
  */
 
+use Drupal\automatic_updates\ReadinessChecker\ReadinessCheckerManagerInterface;
+
 /**
  * Implements hook_page_top().
  */
@@ -38,6 +40,40 @@ function automatic_updates_page_top(array &$page_top) {
         \Drupal::messenger()->addError($message);
       }
     }
+    $last_check_timestamp = \Drupal::service('automatic_updates.readiness_checker')->timestamp();
+    if (\Drupal::time()->getRequestTime() > $last_check_timestamp + ReadinessCheckerManagerInterface::LAST_CHECKED_WARNING) {
+      \Drupal::messenger()->addError(t('Your site has not recently run an update readiness check.'));
+    }
+    /** @var \Drupal\automatic_updates\ReadinessChecker\ReadinessCheckerManagerInterface $checker */
+    $checker = \Drupal::service('automatic_updates.readiness_checker');
+    $results = $checker->getResults('error');
+    if ($results) {
+      \Drupal::messenger()->addError(t('Your site is currently failing readiness checks for automatic updates. It cannot be <a href="@readiness_checks">automatically updated</a> until further action is performed:', ['@readiness_checks' => 'https://www.drupal.org/docs/8/update/automatic-updates#readiness-checks']));
+      foreach ($results as $message) {
+        \Drupal::messenger()->addError($message);
+      }
+    }
+    $results = $checker->getResults('warning');
+    if ($results) {
+      \Drupal::messenger()->addWarning(t('Your site does not pass some readiness checks for automatic updates. It might not be eligible for <a href="@readiness_checks">automatic updates</a>.', ['@readiness_checks' => 'https://www.drupal.org/docs/8/update/automatic-updates#readiness-checks']));
+      foreach ($results as $message) {
+        \Drupal::messenger()->addWarning($message);
+      }
+    }
+  }
+}
+
+/**
+ * Implements hook_cron().
+ */
+function automatic_updates_cron() {
+  /** @var \Drupal\automatic_updates\Services\NotifyInterface $notify */
+  $notify = \Drupal::service('automatic_updates.psa_notify');
+  $notify->send();
+  /** @var \Drupal\automatic_updates\ReadinessChecker\ReadinessCheckerManagerInterface $checker */
+  $checker = \Drupal::service('automatic_updates.readiness_checker');
+  foreach ($checker->getCategories() as $category) {
+    $checker->run($category);
   }
 }
 
@@ -54,15 +90,6 @@ function automatic_updates_theme(array $existing, $type, $theme, $path) {
   ];
 }
 
-/**
- * Implements hook_cron().
- */
-function automatic_updates_cron() {
-  /** @var \Drupal\automatic_updates\Services\NotifyInterface $notify */
-  $notify = \Drupal::service('automatic_updates.psa_notify');
-  $notify->send();
-}
-
 /**
  * Implements hook_mail().
  */
diff --git a/automatic_updates.routing.yml b/automatic_updates.routing.yml
index 921723373c902d5218b0f8922f88c58611a74da8..56b404baa77864f1e15ea73a929583480581bba5 100644
--- a/automatic_updates.routing.yml
+++ b/automatic_updates.routing.yml
@@ -7,3 +7,13 @@ automatic_updates.settings:
     _permission: 'administer software updates'
   options:
     _admin_route: TRUE
+
+automatic_updates.update_readiness:
+  path: '/admin/config/automatic_updates/readiness'
+  defaults:
+    _controller: '\Drupal\automatic_updates\Controller\ReadinessCheckerController::run'
+    _title: 'Update readiness checking...'
+  requirements:
+    _permission: 'administer software updates'
+  options:
+    _admin_route: TRUE
diff --git a/automatic_updates.services.yml b/automatic_updates.services.yml
index 31378b58ada5005c6b7e842b6633cd3c816e278f..f8cdb18bb301a35728b56fc4c17032b8277dfe3f 100644
--- a/automatic_updates.services.yml
+++ b/automatic_updates.services.yml
@@ -2,6 +2,8 @@ services:
   logger.channel.automatic_updates:
     parent: logger.channel_base
     arguments: ['automatic_updates']
+  automatic_updates.drupal_finder:
+    class: DrupalFinder\DrupalFinder
   automatic_updates.psa:
     class: Drupal\automatic_updates\Services\AutomaticUpdatesPsa
     arguments:
@@ -24,3 +26,32 @@ services:
       - '@datetime.time'
       - '@entity_type.manager'
       - '@string_translation'
+  automatic_updates.readiness_checker:
+    class: Drupal\automatic_updates\ReadinessChecker\ReadinessCheckerManager
+    arguments:
+      - '@keyvalue'
+      - '@config.factory'
+    tags:
+      - { name: service_collector, tag: readiness_checker, call: addChecker }
+  automatic_updates.readonly_checker:
+    class: Drupal\automatic_updates\ReadinessChecker\ReadOnlyFilesystem
+    arguments:
+      - '@logger.channel.automatic_updates'
+      - '@automatic_updates.drupal_finder'
+      - '@file_system'
+    tags:
+      - { name: readiness_checker, priority: 100, category: error }
+  automatic_updates.disk_space_checker:
+    class: Drupal\automatic_updates\ReadinessChecker\DiskSpace
+    arguments:
+      - '@logger.channel.automatic_updates'
+      - '@automatic_updates.drupal_finder'
+    tags:
+      - { name: readiness_checker, category: error}
+  automatic_updates.modified_code:
+    class: Drupal\automatic_updates\ReadinessChecker\ModifiedCode
+    arguments:
+      - '@logger.channel.automatic_updates'
+      - '@automatic_updates.drupal_finder'
+    tags:
+      - { name: readiness_checker, category: warning}
diff --git a/composer.json b/composer.json
index 0e18c7795b75552139ff9a17ac9191590ae4aa91..0b5f48fa1fefc7e16d2fb68fa7eb49817d910f3f 100644
--- a/composer.json
+++ b/composer.json
@@ -12,6 +12,7 @@
   },
   "require": {
     "ext-json": "*",
-    "composer/semver": "^1.0@dev"
+    "composer/semver": "^1.0",
+    "webflo/drupal-finder": "^1.1"
   }
 }
diff --git a/config/install/automatic_updates.settings.yml b/config/install/automatic_updates.settings.yml
index 93299fe027ec6460fa820f459a69b2c052ae9eca..18afbc79f6de87e5cb576b2906e8b4393b81fbdd 100644
--- a/config/install/automatic_updates.settings.yml
+++ b/config/install/automatic_updates.settings.yml
@@ -5,3 +5,4 @@ psa_endpoint: 'http://localhost/automatic_updates/test-json'
 enable_psa: true
 notify: true
 check_frequency: 43200
+enable_readiness_checks: true
diff --git a/config/schema/automatic_updates.schema.yml b/config/schema/automatic_updates.schema.yml
index 788c36a34da3a52c64a98713b8d56f0e1e149f0a..db243d342027609388026a3a520398e74bbf496e 100644
--- a/config/schema/automatic_updates.schema.yml
+++ b/config/schema/automatic_updates.schema.yml
@@ -14,3 +14,6 @@ automatic_updates.settings:
     check_frequency:
       type: integer
       label: 'Frequency to check for PSAs, defaults to 12 hours'
+    enable_readiness_checks:
+      type: boolean
+      label: 'Enable readiness checks'
diff --git a/src/Controller/ReadinessCheckerController.php b/src/Controller/ReadinessCheckerController.php
new file mode 100644
index 0000000000000000000000000000000000000000..b5872cf7479b0f423428dab2d576fc23878dbb65
--- /dev/null
+++ b/src/Controller/ReadinessCheckerController.php
@@ -0,0 +1,62 @@
+<?php
+
+namespace Drupal\automatic_updates\Controller;
+
+use Drupal\automatic_updates\ReadinessChecker\ReadinessCheckerManagerInterface;
+use Drupal\Core\Controller\ControllerBase;
+use Drupal\Core\StringTranslation\TranslationInterface;
+use Symfony\Component\DependencyInjection\ContainerInterface;
+
+/**
+ * Class ReadinessCheckerController.
+ */
+class ReadinessCheckerController extends ControllerBase {
+
+  /**
+   * The readiness checker.
+   *
+   * @var \Drupal\automatic_updates\ReadinessChecker\ReadinessCheckerManagerInterface
+   */
+  protected $checker;
+
+  /**
+   * ReadinessCheckerController constructor.
+   *
+   * @param \Drupal\automatic_updates\ReadinessChecker\ReadinessCheckerManagerInterface $checker
+   *   The readiness checker.
+   * @param \Drupal\Core\StringTranslation\TranslationInterface $string_translation
+   *   The string translation service.
+   */
+  public function __construct(ReadinessCheckerManagerInterface $checker, TranslationInterface $string_translation) {
+    $this->checker = $checker;
+    $this->stringTranslation = $string_translation;
+  }
+
+  /**
+   * {@inheritdoc}
+   */
+  public static function create(ContainerInterface $container) {
+    return new static(
+      $container->get('automatic_updates.readiness_checker'),
+      $container->get('string_translation')
+    );
+  }
+
+  /**
+   * Run the readiness checkers.
+   *
+   * @return \Symfony\Component\HttpFoundation\RedirectResponse
+   *   A redirect
+   */
+  public function run() {
+    $messages = [];
+    foreach ($this->checker->getCategories() as $category) {
+      $messages = array_merge($this->checker->run($category), $messages);
+    }
+    if (empty($messages)) {
+      $this->messenger()->addStatus($this->t('No issues found. Your site is ready to for <a href="@readiness_checks">automatic updates</a>.', ['@readiness_checks' => 'https://www.drupal.org/docs/8/update/automatic-updates#readiness-checks']));
+    }
+    return $this->redirect('automatic_updates.settings');
+  }
+
+}
diff --git a/src/Form/SettingsForm.php b/src/Form/SettingsForm.php
index 112a67ce110d543ddd70c0c5160245ca78ea0ed3..b3806509d8beef947b903ebe9c98bef5841855ad 100644
--- a/src/Form/SettingsForm.php
+++ b/src/Form/SettingsForm.php
@@ -5,11 +5,35 @@ namespace Drupal\automatic_updates\Form;
 use Drupal\Core\Form\ConfigFormBase;
 use Drupal\Core\Form\FormStateInterface;
 use Drupal\Core\Url;
+use Symfony\Component\DependencyInjection\ContainerInterface;
 
 /**
  * Settings form for automatic updates.
  */
 class SettingsForm extends ConfigFormBase {
+  /**
+   * The readiness checker.
+   *
+   * @var \Drupal\automatic_updates\ReadinessChecker\ReadinessCheckerManagerInterface
+   */
+  protected $checker;
+
+  /**
+   * The data formatter.
+   *
+   * @var \Drupal\Core\Datetime\DateFormatterInterface
+   */
+  protected $dateFormatter;
+
+  /**
+   * {@inheritdoc}
+   */
+  public static function create(ContainerInterface $container) {
+    $instance = parent::create($container);
+    $instance->checker = $container->get('automatic_updates.readiness_checker');
+    $instance->dateFormatter = $container->get('date.formatter');
+    return $instance;
+  }
 
   /**
    * {@inheritdoc}
@@ -24,7 +48,7 @@ class SettingsForm extends ConfigFormBase {
    * {@inheritdoc}
    */
   public function getFormId() {
-    return 'automatic_updates_admin_form';
+    return 'automatic_updates_settings_form';
   }
 
   /**
@@ -46,6 +70,18 @@ class SettingsForm extends ConfigFormBase {
       '#default_value' => $config->get('notify'),
       '#description' => $this->t('The email addresses listed in <a href="@update_manager">update manager settings</a> will be notified.', ['@update_manager' => Url::fromRoute('update.settings')->toString()]),
     ];
+    $last_check_timestamp = $this->checker->timestamp();
+    $form['enable_readiness_checks'] = [
+      '#type' => 'checkbox',
+      '#title' => $this->t('Check the readiness of automatically updating the site.'),
+      '#default_value' => $config->get('enable_readiness_checks'),
+    ];
+    if ($this->checker->isEnabled()) {
+      $form['enable_readiness_checks']['#description'] = $this->t('Readiness checks were last run @time ago. Manually <a href="@link">run the readiness checks</a>.', [
+        '@time' => $this->dateFormatter->formatTimeDiffSince($last_check_timestamp),
+        '@link' => Url::fromRoute('automatic_updates.update_readiness')->toString(),
+      ]);
+    }
     return parent::buildForm($form, $form_state);
   }
 
diff --git a/src/ReadinessChecker/DiskSpace.php b/src/ReadinessChecker/DiskSpace.php
new file mode 100644
index 0000000000000000000000000000000000000000..91404300e251f8563d306ae07d25f35cc52e02b3
--- /dev/null
+++ b/src/ReadinessChecker/DiskSpace.php
@@ -0,0 +1,83 @@
+<?php
+
+namespace Drupal\automatic_updates\ReadinessChecker;
+
+use Drupal\Core\StringTranslation\StringTranslationTrait;
+use DrupalFinder\DrupalFinder;
+use Psr\Log\LoggerInterface;
+
+/**
+ * Disk space checker.
+ */
+class DiskSpace extends Filesystem {
+  use StringTranslationTrait;
+
+  /**
+   * Minimum disk space (in bytes) is 10mb.
+   */
+  const MINIMUM_DISK_SPACE = 10000000;
+
+  /**
+   * Megabyte divisor.
+   */
+  const MEGABYTE_DIVISOR = 1000000;
+
+  /**
+   * The logger.
+   *
+   * @var \Psr\Log\LoggerInterface
+   */
+  protected $logger;
+
+  /**
+   * ReadOnlyFilesystem constructor.
+   *
+   * @param \Psr\Log\LoggerInterface $logger
+   *   The logger.
+   * @param \DrupalFinder\DrupalFinder $drupal_finder
+   *   The Drupal finder.
+   */
+  public function __construct(LoggerInterface $logger, DrupalFinder $drupal_finder) {
+    $this->logger = $logger;
+    $this->drupalFinder = $drupal_finder;
+  }
+
+  /**
+   * {@inheritdoc}
+   */
+  protected function doCheck() {
+    return $this->diskSpaceCheck();
+  }
+
+  /**
+   * Check if the filesystem has sufficient disk space.
+   *
+   * @return array
+   *   An array of translatable strings if there is not sufficient space.
+   */
+  protected function diskSpaceCheck() {
+    $messages = [];
+    if (!$this->areSameLogicalDisk($this->getRootPath(), $this->getVendorPath())) {
+      if (disk_free_space($this->getRootPath()) < static::MINIMUM_DISK_SPACE) {
+        $messages[] = $this->t('Drupal root filesystem "@root" has insufficient space. There must be at least @space megabytes free.', [
+          '@root' => $this->getRootPath(),
+          '@space' => static::MINIMUM_DISK_SPACE / static::MEGABYTE_DIVISOR,
+        ]);
+      }
+      if (disk_free_space($this->getVendorPath()) < static::MINIMUM_DISK_SPACE) {
+        $messages[] = $this->t('Vendor filesystem "@vendor" has insufficient space. There must be at least @space megabytes free.', [
+          '@vendor' => $this->getVendorPath(),
+          '@space' => static::MINIMUM_DISK_SPACE / static::MEGABYTE_DIVISOR,
+        ]);
+      }
+    }
+    elseif (disk_free_space($this->getRootPath()) < static::MINIMUM_DISK_SPACE) {
+      $messages[] = $this->t('Logical disk "@root" has insufficient space. There must be at least @space megabytes free.', [
+        '@root' => $this->getRootPath(),
+        '@space' => static::MINIMUM_DISK_SPACE / static::MEGABYTE_DIVISOR,
+      ]);
+    }
+    return $messages;
+  }
+
+}
diff --git a/src/ReadinessChecker/Filesystem.php b/src/ReadinessChecker/Filesystem.php
new file mode 100644
index 0000000000000000000000000000000000000000..ce810bd5fd03ebb59e1567a4cc3cdafd486d4eae
--- /dev/null
+++ b/src/ReadinessChecker/Filesystem.php
@@ -0,0 +1,119 @@
+<?php
+
+namespace Drupal\automatic_updates\ReadinessChecker;
+
+/**
+ * Base class for filesystem checkers.
+ */
+abstract class Filesystem implements ReadinessCheckerInterface {
+
+  /**
+   * The root file path.
+   *
+   * @var string
+   */
+  protected $rootPath;
+
+  /**
+   * The vendor file path.
+   *
+   * @var string
+   */
+  protected $vendorPath;
+
+  /**
+   * The drupal finder service.
+   *
+   * @var \DrupalFinder\DrupalFinder
+   */
+  protected $drupalFinder;
+
+  /**
+   * {@inheritdoc}
+   */
+  public function run() {
+    if (!$this->getRootPath() || !$this->exists($this->getRootPath() . '/core/core.api.php')) {
+      return [$this->t('The web root could not be located.')];
+    }
+
+    return $this->doCheck();
+  }
+
+  /**
+   * Perform checks.
+   *
+   * @return array
+   *   An array of translatable strings if any checks fail.
+   */
+  abstract protected function doCheck();
+
+  /**
+   * Get the drupal finder service.
+   *
+   * @return \DrupalFinder\DrupalFinder
+   *   Get drupal finder service.
+   */
+  protected function getDrupalFinder() {
+    if (!$this->drupalFinder) {
+      $this->drupalFinder = \Drupal::service('automatic_updates.drupal_finder');
+    }
+    return $this->drupalFinder;
+  }
+
+  /**
+   * Get the root file path.
+   *
+   * @return string
+   *   The root file path.
+   */
+  protected function getRootPath() {
+    if (!$this->rootPath && $this->getDrupalFinder()->locateRoot(getcwd())) {
+      $this->rootPath = $this->getDrupalFinder()->getDrupalRoot();
+    }
+    return $this->rootPath;
+  }
+
+  /**
+   * Get the vendor file path.
+   *
+   * @return string
+   *   The vendor file path.
+   */
+  protected function getVendorPath() {
+    if (!$this->vendorPath && $this->getDrupalFinder()->locateRoot(getcwd())) {
+      $this->vendorPath = $this->getDrupalFinder()->getVendorDir();
+    }
+    return $this->vendorPath;
+  }
+
+  /**
+   * Determine if the root and vendor file system are the same logical disk.
+   *
+   * @param string $root
+   *   Root file path.
+   * @param string $vendor
+   *   Vendor file path.
+   *
+   * @return bool
+   *   TRUE if same file system, FALSE otherwise.
+   */
+  protected function areSameLogicalDisk($root, $vendor) {
+    $root_statistics = stat($root);
+    $vendor_statistics = stat($vendor);
+    return $root_statistics && $vendor_statistics && $root_statistics['dev'] === $vendor_statistics['dev'];
+  }
+
+  /**
+   * Checks whether a file or directory exists.
+   *
+   * @param string $file
+   *   The file path to test.
+   *
+   * @return bool
+   *   TRUE if the file exists, otherwise FALSE.
+   */
+  protected function exists($file) {
+    return file_exists($file);
+  }
+
+}
diff --git a/src/ReadinessChecker/ModifiedCode.php b/src/ReadinessChecker/ModifiedCode.php
new file mode 100644
index 0000000000000000000000000000000000000000..f8c0b05b949cdf4131cd05feded04c77de2ad4a9
--- /dev/null
+++ b/src/ReadinessChecker/ModifiedCode.php
@@ -0,0 +1,106 @@
+<?php
+
+namespace Drupal\automatic_updates\ReadinessChecker;
+
+use Drupal\Core\StringTranslation\StringTranslationTrait;
+use DrupalFinder\DrupalFinder;
+use Psr\Log\LoggerInterface;
+
+/**
+ * Modified code checker.
+ */
+class ModifiedCode implements ReadinessCheckerInterface {
+  use StringTranslationTrait;
+
+  /**
+   * The logger.
+   *
+   * @var \Psr\Log\LoggerInterface
+   */
+  protected $logger;
+
+  /**
+   * The Drupal root path.
+   *
+   * @var string
+   */
+  protected $drupalRoot;
+
+  /**
+   * The vendor path.
+   *
+   * @var string
+   */
+  protected $vendorPath;
+
+  /**
+   * The drupal finder service.
+   *
+   * @var \DrupalFinder\DrupalFinder
+   */
+  protected $drupalFinder;
+
+  /**
+   * ReadOnlyFilesystem constructor.
+   *
+   * @param \Psr\Log\LoggerInterface $logger
+   *   The logger.
+   * @param \DrupalFinder\DrupalFinder $drupal_finder
+   *   The Drupal finder.
+   */
+  public function __construct(LoggerInterface $logger, DrupalFinder $drupal_finder) {
+    $this->logger = $logger;
+    $this->drupalFinder = $drupal_finder;
+  }
+
+  /**
+   * {@inheritdoc}
+   */
+  public function run() {
+    $messages = [];
+    if (!$this->getDrupalRoot()) {
+      $messages[] = $this->t('The Drupal root directory could not be located.');
+      return $messages;
+    }
+    $this->modifiedCode($messages);
+    return $messages;
+  }
+
+  /**
+   * Get the Drupal root path.
+   *
+   * @return string
+   *   The Drupal root path.
+   */
+  protected function getDrupalRoot() {
+    if (!$this->drupalRoot && $this->drupalFinder->locateRoot(getcwd())) {
+      $this->drupalRoot = $this->drupalFinder->getDrupalRoot();
+    }
+    return $this->drupalRoot;
+  }
+
+  /**
+   * Get the vendor path.
+   *
+   * @return string
+   *   The vendor path.
+   */
+  protected function getVendorPath() {
+    if (!$this->vendorPath && $this->drupalFinder->locateRoot(getcwd())) {
+      $this->vendorPath = $this->drupalFinder->getVendorDir();
+    }
+    return $this->vendorPath;
+  }
+
+  /**
+   * Check if the site contains any modified code.
+   *
+   * @param array $messages
+   *   The messages array of translatable strings.
+   */
+  protected function modifiedCode(array &$messages) {
+    // TODO: Implement file hashing logic against all code files.
+    // See: https://www.drupal.org/project/automatic_updates/issues/3050804
+  }
+
+}
diff --git a/src/ReadinessChecker/ReadOnlyFilesystem.php b/src/ReadinessChecker/ReadOnlyFilesystem.php
new file mode 100644
index 0000000000000000000000000000000000000000..e172e92bb45fad90feae0b7e23b63b82d899266e
--- /dev/null
+++ b/src/ReadinessChecker/ReadOnlyFilesystem.php
@@ -0,0 +1,103 @@
+<?php
+
+namespace Drupal\automatic_updates\ReadinessChecker;
+
+use Drupal\Component\Render\MarkupInterface;
+use Drupal\Core\File\Exception\FileException;
+use Drupal\Core\File\FileSystemInterface;
+use Drupal\Core\StringTranslation\StringTranslationTrait;
+use DrupalFinder\DrupalFinder;
+use Psr\Log\LoggerInterface;
+
+/**
+ * Read only filesystem checker.
+ */
+class ReadOnlyFilesystem extends Filesystem {
+  use StringTranslationTrait;
+
+  /**
+   * The logger.
+   *
+   * @var \Psr\Log\LoggerInterface
+   */
+  protected $logger;
+
+  /**
+   * The file system service.
+   *
+   * @var \Drupal\Core\File\FileSystemInterface
+   */
+  protected $fileSystem;
+
+  /**
+   * ReadOnlyFilesystem constructor.
+   *
+   * @param \Psr\Log\LoggerInterface $logger
+   *   The logger.
+   * @param \DrupalFinder\DrupalFinder $drupal_finder
+   *   The Drupal finder.
+   * @param \Drupal\Core\File\FileSystemInterface $file_system
+   *   The file system service.
+   */
+  public function __construct(LoggerInterface $logger, DrupalFinder $drupal_finder, FileSystemInterface $file_system) {
+    $this->logger = $logger;
+    $this->drupalFinder = $drupal_finder;
+    $this->fileSystem = $file_system;
+  }
+
+  /**
+   * {@inheritdoc}
+   */
+  protected function doCheck() {
+    return $this->readOnlyCheck();
+  }
+
+  /**
+   * Check if the filesystem is read only.
+   */
+  protected function readOnlyCheck() {
+    $messages = [];
+    if ($this->areSameLogicalDisk($this->getRootPath(), $this->getVendorPath())) {
+      $error = $this->t('Logical disk at "@path" is read only. Updates to Drupal cannot be applied against a read only file system.', ['@path' => $this->rootPath]);
+      $this->doReadOnlyCheck($this->getRootPath(), 'core/core.api.php', $messages, $error);
+    }
+    else {
+      $error = $this->t('Drupal core filesystem at "@path" is read only. Updates to Drupal core cannot be applied against a read only file system.', ['@path' => $this->rootPath . '/core']);
+      $this->doReadOnlyCheck($this->getRootPath(), 'core.api.php', $messages, $error);
+      $error = $this->t('Vendor filesystem at "@path" is read only. Updates to the site\'s code base cannot be applied against a read only file system.', ['@path' => $this->vendorPath]);
+      $this->doReadOnlyCheck($this->getVendorPath(), 'composer/LICENSE', $messages, $error);
+    }
+    return $messages;
+  }
+
+  /**
+   * Do the read only check.
+   *
+   * @param string $file_path
+   *   The filesystem to test.
+   * @param string $file
+   *   The file path.
+   * @param array $messages
+   *   The messages array of translatable strings.
+   * @param \Drupal\Component\Render\MarkupInterface $message
+   *   The error message to add if the file is read only.
+   */
+  protected function doReadOnlyCheck($file_path, $file, array &$messages, MarkupInterface $message) {
+    try {
+      // If we can copy and delete a file, then we don't have a read only
+      // file system.
+      if ($this->fileSystem->copy("$file_path/$file", "$file_path/$file.automatic_updates", FileSystemInterface::EXISTS_REPLACE)) {
+        // Delete it after copying.
+        $this->fileSystem->delete("$file_path/$file.automatic_updates");
+      }
+      else {
+        $this->logger->error($message);
+        $messages[] = $message;
+      }
+    }
+    catch (FileException $exception) {
+      $messages[] = $message;
+    }
+  }
+
+}
diff --git a/src/ReadinessChecker/ReadinessCheckerInterface.php b/src/ReadinessChecker/ReadinessCheckerInterface.php
new file mode 100644
index 0000000000000000000000000000000000000000..11fe60ae395bb1a690ca172362a0745a4f9c7ef1
--- /dev/null
+++ b/src/ReadinessChecker/ReadinessCheckerInterface.php
@@ -0,0 +1,18 @@
+<?php
+
+namespace Drupal\automatic_updates\ReadinessChecker;
+
+/**
+ * Interface for objects capable of readiness checking.
+ */
+interface ReadinessCheckerInterface {
+
+  /**
+   * Run check.
+   *
+   * @return array
+   *   An array of translatable strings.
+   */
+  public function run();
+
+}
diff --git a/src/ReadinessChecker/ReadinessCheckerManager.php b/src/ReadinessChecker/ReadinessCheckerManager.php
new file mode 100644
index 0000000000000000000000000000000000000000..c6f8f9fd2c9a5e2d17f80af50c0b05f895aecad0
--- /dev/null
+++ b/src/ReadinessChecker/ReadinessCheckerManager.php
@@ -0,0 +1,126 @@
+<?php
+
+namespace Drupal\automatic_updates\ReadinessChecker;
+
+use Drupal\Core\Config\ConfigFactoryInterface;
+use Drupal\Core\KeyValueStore\KeyValueFactoryInterface;
+
+/**
+ * Defines a chained readiness checker implementation combining multiple checks.
+ */
+class ReadinessCheckerManager implements ReadinessCheckerManagerInterface {
+
+  /**
+   * The key/value storage.
+   *
+   * @var \Drupal\Core\KeyValueStore\KeyValueStoreInterface
+   */
+  protected $keyValue;
+
+  /**
+   * The config factory.
+   *
+   * @var \Drupal\Core\Config\ConfigFactoryInterface
+   */
+  protected $configFactory;
+
+  /**
+   * An unsorted array of arrays of active checkers.
+   *
+   * An associative array. The keys are integers that indicate priority. Values
+   * are arrays of ReadinessCheckerInterface objects.
+   *
+   * @var \Drupal\automatic_updates\ReadinessChecker\ReadinessCheckerInterface[][]
+   */
+  protected $checkers = [];
+
+  /**
+   * ReadinessCheckerManager constructor.
+   *
+   * @param \Drupal\Core\KeyValueStore\KeyValueFactoryInterface $key_value
+   *   The key/value service.
+   * @param \Drupal\Core\Config\ConfigFactoryInterface $config_factory
+   *   The config factory.
+   */
+  public function __construct(KeyValueFactoryInterface $key_value, ConfigFactoryInterface $config_factory) {
+    $this->keyValue = $key_value->get('automatic_updates');
+    $this->configFactory = $config_factory;
+  }
+
+  /**
+   * {@inheritdoc}
+   */
+  public function addChecker(ReadinessCheckerInterface $checker, $category = 'warning', $priority = 0) {
+    if (!in_array($category, $this->getCategories(), TRUE)) {
+      throw new \InvalidArgumentException(sprintf('Readiness checker category "%s" is invalid. Use "%s" instead.', $category, implode('" or "', $this->getCategories())));
+    }
+    $this->checkers[$category][$priority][] = $checker;
+    return $this;
+  }
+
+  /**
+   * {@inheritdoc}
+   */
+  public function run($category) {
+    $messages = [];
+    if (!$this->isEnabled()) {
+      return $messages;
+    }
+    if (!isset($this->getSortedCheckers()[$category])) {
+      throw new \InvalidArgumentException(sprintf('No readiness checkers exist of category "%s"', $category));
+    }
+
+    foreach ($this->getSortedCheckers()[$category] as $checker) {
+      $messages = array_merge($messages, $checker->run());
+    }
+    $this->keyValue->set("readiness_check_results.$category", $messages);
+    $this->keyValue->set('readiness_check_timestamp', \Drupal::time()->getRequestTime());
+    return $messages;
+  }
+
+  /**
+   * {@inheritdoc}
+   */
+  public function getResults($category) {
+    return $this->keyValue->get("readiness_check_results.$category", []);
+  }
+
+  /**
+   * {@inheritdoc}
+   */
+  public function timestamp() {
+    return $this->keyValue->get('readiness_check_timestamp', 0);
+  }
+
+  /**
+   * {@inheritdoc}
+   */
+  public function isEnabled() {
+    return $this->configFactory->get('automatic_updates.settings')->get('enable_readiness_checks');
+  }
+
+  /**
+   * {@inheritdoc}
+   */
+  public function getCategories() {
+    return ['warning', 'error'];
+  }
+
+  /**
+   * Sorts checkers according to priority.
+   *
+   * @return \Drupal\automatic_updates\ReadinessChecker\ReadinessCheckerInterface[]
+   *   A sorted array of checker objects.
+   */
+  protected function getSortedCheckers() {
+    $sorted = [];
+    foreach ($this->checkers as $category => $priorities) {
+      foreach ($priorities as $checkers) {
+        krsort($checkers);
+        $sorted[$category] = isset($sorted[$category]) ? array_merge($sorted[$category], $checkers) : array_merge([], $checkers);
+      }
+    }
+    return $sorted;
+  }
+
+}
diff --git a/src/ReadinessChecker/ReadinessCheckerManagerInterface.php b/src/ReadinessChecker/ReadinessCheckerManagerInterface.php
new file mode 100644
index 0000000000000000000000000000000000000000..f63a40e007b052e072d44340dd0edea1bd274fa6
--- /dev/null
+++ b/src/ReadinessChecker/ReadinessCheckerManagerInterface.php
@@ -0,0 +1,75 @@
+<?php
+
+namespace Drupal\automatic_updates\ReadinessChecker;
+
+/**
+ * Readiness checker manager interface.
+ */
+interface ReadinessCheckerManagerInterface {
+
+  /**
+   * Last checked ago warning (in seconds).
+   */
+  const LAST_CHECKED_WARNING = 3600 * 24;
+
+  /**
+   * Appends a checker to the checker chain.
+   *
+   * @param \Drupal\automatic_updates\ReadinessChecker\ReadinessCheckerInterface $checker
+   *   The checker interface to be appended to the checker chain.
+   * @param string $category
+   *   (optional) The category of check.
+   * @param int $priority
+   *   (optional) The priority of the checker being added.
+   *
+   * @return $this
+   */
+  public function addChecker(ReadinessCheckerInterface $checker, $category = 'warning', $priority = 0);
+
+  /**
+   * Run checks.
+   *
+   * @param string $category
+   *   The category of check.
+   *
+   * @return array
+   *   An array of translatable strings.
+   */
+  public function run($category);
+
+  /**
+   * Get results of most recent run.
+   *
+   * @param string $category
+   *   The category of check.
+   *
+   * @return array
+   *   An array of translatable strings.
+   */
+  public function getResults($category);
+
+  /**
+   * Get timestamp of most recent run.
+   *
+   * @return int
+   *   A unix timestamp of most recent completed run.
+   */
+  public function timestamp();
+
+  /**
+   * Determine if readiness checks is enabled.
+   *
+   * @return bool
+   *   TRUE if enabled, otherwise FALSE.
+   */
+  public function isEnabled();
+
+  /**
+   * Get the categories of checkers.
+   *
+   * @return array
+   *   The categories of checkers.
+   */
+  public function getCategories();
+
+}
diff --git a/templates/automatic-updates-psa-notify.html.twig b/templates/automatic-updates-psa-notify.html.twig
index 5bd8a9062eaa1c8bb6cf92138d5ee531478e9482..dd49aac63d3151a4e3bd2c93abce41349c91f0f9 100644
--- a/templates/automatic-updates-psa-notify.html.twig
+++ b/templates/automatic-updates-psa-notify.html.twig
@@ -12,7 +12,7 @@
 
 <p>
   {% trans %}
-    A security update will be made available soon for your Drupal site. To ensure the security of the site, you should prepare the site to immediately install the update once it is released!
+    A security update will be made available soon for your Drupal site. To ensure the security of the site, you should prepare the site to immediately apply the update once it is released!
   {% endtrans %}
 </p>
 <p>
diff --git a/tests/src/Functional/AutomaticUpdatesTest.php b/tests/src/Functional/AutomaticUpdatesTest.php
index b682a3a3f7a0e0353bde566371a8e5993202a12b..a41615417099093ba475ccc8de78b1dcdc9b299f 100644
--- a/tests/src/Functional/AutomaticUpdatesTest.php
+++ b/tests/src/Functional/AutomaticUpdatesTest.php
@@ -34,8 +34,9 @@ class AutomaticUpdatesTest extends BrowserTestBase {
   protected function setUp() {
     parent::setUp();
     $this->user = $this->drupalCreateUser([
-      'administer site configuration',
       'access administration pages',
+      'administer site configuration',
+      'administer software updates',
     ]);
     $this->drupalLogin($this->user);
   }
@@ -86,4 +87,14 @@ class AutomaticUpdatesTest extends BrowserTestBase {
     $this->assertSession()->pageTextNotContains('urgent announcements require your attention');
   }
 
+  /**
+   * Tests manually running readiness checks.
+   */
+  public function testReadinessChecks() {
+    // Test manually running readiness checks.
+    $this->drupalGet(Url::fromRoute('automatic_updates.settings'));
+    $this->clickLink('run the readiness checks');
+    $this->assertSession()->pageTextContains('No issues found. Your site is ready to for automatic updates.');
+  }
+
 }
diff --git a/tests/src/Kernel/ReadinessChecker/DiskSpaceTest.php b/tests/src/Kernel/ReadinessChecker/DiskSpaceTest.php
new file mode 100644
index 0000000000000000000000000000000000000000..5e289e3de1c42b8d58e46b9b0261d3732c61b0a7
--- /dev/null
+++ b/tests/src/Kernel/ReadinessChecker/DiskSpaceTest.php
@@ -0,0 +1,70 @@
+<?php
+
+namespace Drupal\Tests\automatic_updates\Kernel\ReadinessChecker;
+
+use Drupal\automatic_updates\ReadinessChecker\DiskSpace;
+use Drupal\Core\StringTranslation\StringTranslationTrait;
+use Drupal\KernelTests\KernelTestBase;
+
+/**
+ * Tests disk space readiness checking.
+ *
+ * @group automatic_updates
+ */
+class DiskSpaceTest extends KernelTestBase {
+  use StringTranslationTrait;
+
+  /**
+   * {@inheritdoc}
+   */
+  public static $modules = [
+    'automatic_updates',
+  ];
+
+  /**
+   * Tests the functionality of disk space readiness checks.
+   */
+  public function testDiskSpace() {
+    // No disk space issues.
+    $disk_space = new DiskSpace($this->container->get('logger.channel.automatic_updates'), $this->container->get('automatic_updates.drupal_finder'));
+    $messages = $disk_space->run();
+    $this->assertEmpty($messages);
+
+    // Out of space.
+    $disk_space = new TestDiskSpace($this->container->get('logger.channel.automatic_updates'), $this->container->get('automatic_updates.drupal_finder'));
+    $messages = $disk_space->run();
+    $this->assertCount(1, $messages);
+
+    // Out of space not the same logical disk.
+    $disk_space = new TestDiskSpaceNonSameDisk($this->container->get('logger.channel.automatic_updates'), $this->container->get('automatic_updates.drupal_finder'));
+    $messages = $disk_space->run();
+    $this->assertCount(2, $messages);
+  }
+
+}
+
+/**
+ * Class TestDiskSpace.
+ */
+class TestDiskSpace extends DiskSpace {
+
+  /**
+   * Override the default free disk space minimum to an insanely high number.
+   */
+  const MINIMUM_DISK_SPACE = 99999999999999999999999999999999999999999999999999;
+
+}
+
+/**
+ * Class TestDiskSpaceNonSameDisk.
+ */
+class TestDiskSpaceNonSameDisk extends TestDiskSpace {
+
+  /**
+   * {@inheritdoc}
+   */
+  protected function areSameLogicalDisk($root, $vendor) {
+    return FALSE;
+  }
+
+}
diff --git a/tests/src/Kernel/ReadinessChecker/ModifiedCodeTest.php b/tests/src/Kernel/ReadinessChecker/ModifiedCodeTest.php
new file mode 100644
index 0000000000000000000000000000000000000000..fd6c86f13c1b19f4ffb5d230708ca8f56c475d2a
--- /dev/null
+++ b/tests/src/Kernel/ReadinessChecker/ModifiedCodeTest.php
@@ -0,0 +1,34 @@
+<?php
+
+namespace Drupal\Tests\automatic_updates\Kernel\ReadinessChecker;
+
+use Drupal\automatic_updates\ReadinessChecker\ModifiedCode;
+use Drupal\Core\StringTranslation\StringTranslationTrait;
+use Drupal\KernelTests\KernelTestBase;
+
+/**
+ * Tests modified code readiness checking.
+ *
+ * @group automatic_updates
+ */
+class ModifiedCodeTest extends KernelTestBase {
+  use StringTranslationTrait;
+
+  /**
+   * {@inheritdoc}
+   */
+  public static $modules = [
+    'automatic_updates',
+  ];
+
+  /**
+   * Tests the functionality of modified code readiness checks.
+   */
+  public function testModifiedCode() {
+    // No modified code.
+    $modified_code = new ModifiedCode($this->container->get('logger.channel.automatic_updates'), $this->container->get('automatic_updates.drupal_finder'));
+    $messages = $modified_code->run();
+    $this->assertEmpty($messages);
+  }
+
+}
diff --git a/tests/src/Kernel/ReadinessChecker/ReadOnlyTest.php b/tests/src/Kernel/ReadinessChecker/ReadOnlyTest.php
new file mode 100644
index 0000000000000000000000000000000000000000..e442f960733faa4aca10454a2fab29e3eebf4a83
--- /dev/null
+++ b/tests/src/Kernel/ReadinessChecker/ReadOnlyTest.php
@@ -0,0 +1,131 @@
+<?php
+
+namespace Drupal\Tests\automatic_updates\Kernel\ReadinessChecker;
+
+use Drupal\automatic_updates\ReadinessChecker\ReadOnlyFilesystem;
+use Drupal\Core\File\Exception\FileException;
+use Drupal\Core\File\Exception\FileWriteException;
+use Drupal\Core\File\FileSystemInterface;
+use Drupal\Core\StringTranslation\StringTranslationTrait;
+use Drupal\KernelTests\KernelTestBase;
+use DrupalFinder\DrupalFinder;
+use Psr\Log\LoggerInterface;
+
+/**
+ * Tests read only readiness checking.
+ *
+ * @group automatic_updates
+ */
+class ReadOnlyTest extends KernelTestBase {
+  use StringTranslationTrait;
+
+  /**
+   * {@inheritdoc}
+   */
+  public static $modules = [
+    'automatic_updates',
+    'system',
+  ];
+
+  /**
+   * Tests the functionality of read only filesystem readiness checks.
+   */
+  public function testReadOnly() {
+    $messages = $filesystem = $this->container->get('automatic_updates.readonly_checker')->run();
+    $this->assertEmpty($messages);
+
+    $filesystem = $this->createMock(FileSystemInterface::class);
+    $filesystem->expects($this->any())
+      ->method('copy')
+      ->withAnyParameters()
+      ->will($this->onConsecutiveCalls(
+        $this->throwException(new FileWriteException('core.api.php')),
+        $this->throwException(new FileWriteException('core.api.php')),
+        $this->throwException(new FileWriteException('composer/LICENSE')),
+        'full/file/path',
+        'full/file/path'
+      )
+    );
+    $filesystem->expects($this->any())
+      ->method('delete')
+      ->withAnyParameters()
+      ->will($this->onConsecutiveCalls(
+        $this->throwException(new FileException('delete failed.')),
+        $this->throwException(new FileException('delete failed.'))
+      )
+    );
+
+    $readonly = $this->getMockBuilder(TestReadOnlyFilesystem::class)
+      ->setConstructorArgs([
+        $this->createMock(LoggerInterface::class),
+        new DrupalFinder(),
+        $filesystem,
+      ])
+      ->setMethods([
+        'areSameLogicalDisk',
+        'exists',
+      ])
+      ->getMock();
+    $readonly->expects($this->any())
+      ->method('areSameLogicalDisk')
+      ->withAnyParameters()
+      ->will($this->onConsecutiveCalls(
+        TRUE,
+        FALSE,
+        FALSE
+      )
+    );
+    $readonly->expects($this->any())
+      ->method('exists')
+      ->withAnyParameters()
+      ->will($this->onConsecutiveCalls(
+        FALSE,
+        TRUE,
+        TRUE,
+        TRUE
+      )
+    );
+
+    // Test can't locate drupal.
+    $messages = $readonly->run();
+    $this->assertEquals([$this->t('The web root could not be located.')], $messages);
+
+    // Test same logical disk.
+    $expected_messages = [];
+    $expected_messages[] = $this->t('Logical disk at "/var/www" is read only. Updates to Drupal cannot be applied against a read only file system.');
+    $messages = $readonly->run();
+    $this->assertEquals($expected_messages, $messages);
+
+    // Test read-only.
+    $expected_messages = [];
+    $expected_messages[] = $this->t('Drupal core filesystem at "/var/www/core" is read only. Updates to Drupal core cannot be applied against a read only file system.');
+    $expected_messages[] = $this->t('Vendor filesystem at "/var/www/vendor" is read only. Updates to the site\'s code base cannot be applied against a read only file system.');
+    $messages = $readonly->run();
+    $this->assertEquals($expected_messages, $messages);
+
+    // Test delete fails.
+    $expected_messages = [];
+    $expected_messages[] = $this->t('Drupal core filesystem at "/var/www/core" is read only. Updates to Drupal core cannot be applied against a read only file system.');
+    $expected_messages[] = $this->t('Vendor filesystem at "/var/www/vendor" is read only. Updates to the site\'s code base cannot be applied against a read only file system.');
+    $messages = $readonly->run();
+    $this->assertEquals($expected_messages, $messages);
+  }
+
+}
+
+/**
+ * Class TestReadOnlyFilesystem.
+ */
+class TestReadOnlyFilesystem extends ReadOnlyFilesystem {
+
+  /**
+   * {@inheritdoc}
+   */
+  protected $rootPath = '/var/www';
+
+  /**
+   * {@inheritdoc}
+   */
+  protected $vendorPath = '/var/www/vendor';
+
+}
diff --git a/tests/src/Kernel/ReadinessChecker/ReadinessCheckerTest.php b/tests/src/Kernel/ReadinessChecker/ReadinessCheckerTest.php
new file mode 100644
index 0000000000000000000000000000000000000000..3ecab007f9b64c0e25b7e991f49fd0daa3f44ddb
--- /dev/null
+++ b/tests/src/Kernel/ReadinessChecker/ReadinessCheckerTest.php
@@ -0,0 +1,33 @@
+<?php
+
+namespace Drupal\Tests\automatic_updates\Kernel;
+
+use Drupal\Core\StringTranslation\StringTranslationTrait;
+use Drupal\KernelTests\KernelTestBase;
+
+/**
+ * Tests automatic updates readiness checking.
+ *
+ * @group automatic_updates
+ */
+class ReadinessCheckerTest extends KernelTestBase {
+  use StringTranslationTrait;
+
+  /**
+   * {@inheritdoc}
+   */
+  public static $modules = [
+    'automatic_updates',
+  ];
+
+  /**
+   * Tests the functionality of readiness checks.
+   */
+  public function testReadinessChecker() {
+    /** @var \Drupal\automatic_updates\ReadinessChecker\ReadinessCheckerManagerInterface $checker */
+    $checker = $this->container->get('automatic_updates.readiness_checker');
+    $this->assertEmpty($checker->run('warning'));
+    $this->assertEmpty($checker->run('error'));
+  }
+
+}