Follow-up to #2228093: Modernize theme initialization

Problem

#2228093: Modernize theme initialization introduced the following two changes, which are highly problematic, since each of them is priming theme configuration ahead of time, which doesn't actually exist yet, and unfortunately worse, they conflict with actual expectations of the installation profile as well as tests:

+++ b/core/config/install/core.extension.yml
@@ -1,4 +1,5 @@
-theme: {}
+theme:
+  stark: 0

+++ b/core/modules/simpletest/src/KernelTestBase.php
@@ -176,7 +176,7 @@ protected function setUp() {
-    \Drupal::service('config.storage')->write('core.extension', array('module' => array(), 'theme' => array()));
+    \Drupal::service('config.storage')->write('core.extension', array('module' => array(), 'theme' => array('seven' => 1, 'stark' => 1)));

These two changes present a critical problem, since priming the configuration ahead of time will cause all new code to be based on the expectation that the configuration "magically" exists already - even though it should not and must not exist, because no code asked for it.

The affected extensions are already marked as installed and will be loaded by default. Their default configuration will not be installed. hook_install() will never be invoked. All tests are running against the changed extension configuration, which does not resemble a clean slate Drupal application.

In other words: Patches that transitioned to RTBC since 2014-08-21 17:00 UTC might not be safe to commit, since fundamental conditions/expectations of the testing framework have changed.

Therefore, we need to fix this ASAP.

Expected behavior

  1. The installer is supposed to use the 'distribution:install:theme' property of the installation profile's .info.yml file, if defined, otherwise fall back to 'seven'.

    cf. #1351352: Distribution installation profiles are no longer able to override the early installer screens

    Nevermind. This still works as before and as expected, because neither _drupal_maintenance_theme() nor install_begin_request() was changed by #2228093: Modernize theme initialization

  2. Kernel tests are supposed to be executed against "no theme at all" by default; i.e., whatever gets rendered is exclusively rendered through the unmodified, original theme functions/templates of modules only.

    The primary purpose of asserting output in kernel tests is to assert the original output of a module, without any kind of theme interaction — unless a theme was explicitly installed manually by a test.

    cf. #1067408: Themes do not have an installation status

Comments

sun’s picture

Status: Active » Needs review
StatusFileSize
new1.13 KB

It's not really clear to me what fails without those changes and why they were necessary, so let's start with this.

Status: Needs review » Needs work

The last submitted patch, 1: theme.prime_.0.patch, failed testing.

sun’s picture

Briefly discussed with @dawehner in IRC already.

We probably need a custom theme negotiator in the (early) installer to resemble the original behavior of a custom installation profile theme. That should get us past the initial "Drupal installation failed" test failure.

The other expectation for kernel tests likely needs an adjustment to ThemeInitialization service, so as to fall back to the (fake) "core" theme in case no default theme is configured, or no themes are enabled.

Some helpful links:

Installer theme selection in install_begin_request():
http://cgit.drupalcode.org/drupal/tree/core/includes/install.core.inc#n395

The case of "no/unknown theme" was handled by the if (!$theme || !isset($themes[$theme])) condition in drupal_theme_initialize() in:
http://cgit.drupalcode.org/drupal/diff/core/includes/theme.inc?id=a4125e...

sun’s picture

Status: Needs work » Needs review
StatusFileSize
new4.57 KB

Attached patch allows me to manually install locally. Let's see what the bot has to say.

+++ b/core/lib/Drupal/Core/Theme/ThemeInitialization.php
@@ -61,29 +61,32 @@ public function getActiveThemeByName($theme_name) {
-      if (empty($themes)) {
-        throw new \RuntimeException('No theme is enabled.');
-      }
-      if (!isset($themes[$theme_name])) {
-        throw new \InvalidArgumentException(String::format('Theme %theme is not enabled/does not exist.', array('theme' => $theme_name)));
-      }
...
+    // If no theme could be negotiated, or if the negotiated theme is not within
+    // the list of enabled themes, fall back to the default theme output of core
+    // and modules (similar to Stark, but without a theme extension at all). This
+    // is possible, because loadActiveTheme() always loads the Twig theme engine.
+    if (empty($themes) || !$theme_name || !isset($themes[$theme_name])) {
+      $theme_name = 'core';
+      // /core/core.info.yml does not actually exist, but is required because
+      // Extension expects a pathname.
+      $active_theme = $this->getActiveTheme(new Extension('theme', 'core/core.info.yml'));
+      return $active_theme;
+    }

In essence, this just simply resurrects the original code from http://cgit.drupalcode.org/drupal/diff/core/includes/theme.inc?id=a4125e...

dawehner’s picture

  1. +++ b/core/lib/Drupal/Core/Theme/ThemeInitialization.php
    @@ -61,29 +61,32 @@ public function getActiveThemeByName($theme_name) {
    +    // If no theme could be negotiated, or if the negotiated theme is not within
    +    // the list of enabled themes, fall back to the default theme output of core
    +    // and modules (similar to Stark, but without a theme extension at all). This
    +    // is possible, because loadActiveTheme() always loads the Twig theme engine.
    +    if (empty($themes) || !$theme_name || !isset($themes[$theme_name])) {
    

    In general I like that actually not much code has to be changed.

  2. +++ b/core/lib/Drupal/Core/Theme/ThemeInitialization.php
    @@ -61,29 +61,32 @@ public function getActiveThemeByName($theme_name) {
    +      $theme_name = 'core';
    +      // /core/core.info.yml does not actually exist, but is required because
    +      // Extension expects a pathname.
    +      $active_theme = $this->getActiveTheme(new Extension('theme', 'core/core.info.yml'));
    +      return $active_theme;
    ...
    +    $this->state->set('theme.active_theme.' . $theme_name, $active_theme);
    +    return $active_theme;
    

    I wonder whether storing the active_theme for 'core' makes sense.

  3. +++ b/core/lib/Drupal/Core/Theme/ThemeInitialization.php
    @@ -123,7 +126,7 @@ public function loadActiveTheme(ActiveTheme $active_theme) {
    +  public function getActiveTheme(Extension $theme, array $base_themes = array()) {
    
    +++ b/core/lib/Drupal/Core/Theme/ThemeInitializationInterface.php
    @@ -60,6 +60,6 @@ public function loadActiveTheme(ActiveTheme $active_theme);
    +  public function getActiveTheme(Extension $theme, array $base_themes = array());
    

    OT: I prefer to use [] in new code

sun’s picture

StatusFileSize
new8.9 KB
new5.72 KB
  1. Addresses #5.
  2. Reverts additional changes/workarounds from #2228093: Modernize theme initialization that are caused by the same root cause.
  3. Adds a regression test to KernelTestBaseTest, which would have prevented at least the KTB::setUp() change, but didn't exist yet, so it's no surprise that the unintentional change in behavior wasn't caught :)

The last submitted patch, 4: theme.prime_.4.patch, failed testing.

sun’s picture

Issue summary: View changes

The installer still works as before and as expected, because neither _drupal_maintenance_theme() nor install_begin_request() was changed by #2228093: Modernize theme initialization

The expected behavior is covered by the DistributionProfileTest already.

Updating issue summary accordingly.


That said, while the KTB expectations can be tested, the expected emptiness of the core.extension.yml default config cannot.

That is, unless we'd write a relatively dumb test that would do nothing else than to load the default config file and assert that it's empty.

Sounds a little wonky, but might actually be worth to add.

sun’s picture

Assigned: Unassigned » sun
StatusFileSize
new10.34 KB
new2.21 KB

Added clean regression unit test for core.extension.yml + slightly adjusted docs in ThemeInitialization.

sun’s picture

Issue summary: View changes

Last patch should still be green, adds regression test coverage, and thus should be ready to be committed.

Clarified the issue summary.

tim.plunkett’s picture

Issue tags: -beta blocker

Sorry, only committers can add that tag.
This can block release, but the issue summary seems to exaggerate the urgency of this issue.

dawehner’s picture

+++ b/core/tests/Drupal/Tests/Core/Extension/DefaultConfigTest.php
@@ -0,0 +1,42 @@
+    $expected = array(
+      'module' => array(),
+      'theme' => array(),
+      'disabled' => array(
+        'theme' => array(),
+      ),
+    );
+    $this->assertEquals($expected, $config);

I would suggest to just check that the keys you have here, have the proper value. Otherwise it would be more complex to add new configuration, which might happen in the future.

sun’s picture

Issue tags: +Testing system

@dawehner: I considered that, but (1) it's unlikely to change any time soon, and (2) in case a new property would be added, then this test should cover that new property, too, but individual assertions would miss the addition, so the new property would be able to prime an extension ahead of time. The test should intentionally fail in case of any change.

sun queued 9: theme.prime_.9.patch for re-testing.

dawehner’s picture

Status: Needs review » Reviewed & tested by the community

I am already convinced by (2). This though means that we have to ensure that we don't include UIs into Core at some point, which hopefully won't happen.

webchick’s picture

Status: Reviewed & tested by the community » Fixed

I admit this patch is a bit over my head, but looks to have been thoroughly reviewed, and unblocks a critical.

Committed and pushed to 8.x. Thanks!

  • webchick committed 1203a09 on 8.0.x
    Issue #2325575 by sun: Fixed Theme must not be primed in core.extension...

Status: Fixed » Closed (fixed)

Automatically closed - issue fixed for 2 weeks with no activity.