It looks like all the cache constants (DRUPAL_CACHE_PER_ROLE, etc.) have the same effect for block caching as DRUPAL_CACHE_GLOBAL. In other words, the granularity for block caching is broken. I'm currently writing some tests to verify this.

Support from Acquia helps fund testing for Drupal Acquia logo

Comments

tstoeckler’s picture

Is this related or duplicate to/of: #235673: Changes to block caching mode not caught?

carlos8f’s picture

That is a separate issue, but related. In HEAD, block caching seems to be "all or nothing", even when the proper cache value is stored in the {block} table.

carlos8f’s picture

No test coverage exists for block caching yet. Here are some I've written, indicating that per-user, per-role and per-page block caching are broken. DRUPAL_CACHE_GLOBAL and DRUPAL_NO_CACHE are working.

The next step is to debug the actual issue with the granularity. Setting to 'needs review' to record the fails (there should be 4).

carlos8f’s picture

Assigned: Unassigned » carlos8f
Status: Active » Needs review
FileSize
7.64 KB

...and the patch :)

carlos8f’s picture

Title: Block caching is broken » Block caching per-role, per-user, per-page is broken
tstoeckler’s picture

Title: Block caching per-role, per-user, per-page is broken » Block caching is broken

$this->assertNoText(variable_get('block_test_content', ''), t('Anonymous user does not see content cached per-role for normal user.'));

I don't see how that is going to work. Since you're getting the text right from the database, the text you're asserting on is always going to be the non-cached one, so I don't see why this is a "assertNoText". An "assertText" wouldn't make sense here either, you should assert on $old_content, except then you should move the cache_clear_all() to after that.
I think.
Haven't tried it out. Please ignore if that was complete nonsense.

tstoeckler’s picture

Title: Block caching is broken » Block caching per-role, per-user, per-page is broken

Cross-post.
Fixing title

Status: Needs review » Needs work

The last submitted patch, block-cache-tests.patch, failed testing.

carlos8f’s picture

I may have messed up on that assert, I'll check that later. Regardless, I think it's clear that there is a critical bug here.

moshe weitzman’s picture

subscribe

carlos8f’s picture

Status: Needs work » Needs review
FileSize
8.74 KB

Turns out that this is a really trivial fix:

--- modules/block/block.module	13 Jan 2010 05:40:03 -0000	1.407
+++ modules/block/block.module	23 Jan 2010 06:05:13 -0000
@@ -825,7 +825,7 @@
     // Start with common sub-patterns: block identification, theme, language.
     $cid_parts[] = $block->module;
     $cid_parts[] = $block->delta;
-    $cid_parts += drupal_render_cid_parts($block->cache);
+    $cid_parts = array_merge($cid_parts, drupal_render_cid_parts($block->cache));

ptah. += isn't appropriate here on the numeric array.

Looks like the last thing to figure out is, why a user with the same role-set isn't seeing the per-role cache. It might be a quirk in the way SimpleTest generates users and roles. I already had to manually copy $normal_user->roles to $normal_user_alt->roles, since DWTC->drupalCreateUser() generates new roles for each user, regardless of the permission set passed. Possibly $user->uid is getting added to the cache cid.

Dries’s picture

Nice catch. I'd be very interested to see how that affects our benchmarks.

carlos8f’s picture

To be clear, there is no expected effect on benchmarks. In HEAD, the cache is at least served, but the granularity is broken. That's a functionality-breaking bug rather than a performance one.

One example is that blocks cached for a certain user (DRUPAL_CACHE_PER_USER) would get shown to all other users, creating a security problem. Also, blocks whose content is dynamic based on the current path (DRUPAL_CACHE_PER_PAGE) would have their content all mixed up. Things like that :)

Dries’s picture

Status: Needs review » Fixed

Got it. Committed to CVS HEAD. Thanks for writing tests.

carlos8f’s picture

Status: Fixed » Needs work

Whoa Dries! This wasn't ready yet. This was also an incomplete commit: the tests depend on the new block_test.module, in the new tests directory, which wasn't committed. The new tests also have one fail, which needs to be addressed. See http://qa.drupal.org/pifr/test/26444. Please roll back the commit!

carlos8f’s picture

Status: Needs work » Reviewed & tested by the community

adding to RTBC queue, for the roll-back :)

carlos8f’s picture

Title: Block caching per-role, per-user, per-page is broken » [NEEDS ROLLBACK] Block caching per-role, per-user, per-page is broken
Dries’s picture

Status: Reviewed & tested by the community » Needs review

Sorry about that. I rolled it back for now.

int’s picture

Title: [NEEDS ROLLBACK] Block caching per-role, per-user, per-page is broken » Block caching per-role, per-user, per-page is broken
marcingy’s picture

Status: Needs review » Needs work

resetting status

mr.baileys’s picture

Status: Needs work » Needs review
FileSize
8.76 KB
+++ modules/block/block.test	23 Jan 2010 06:05:13 -0000
@@ -318,3 +318,180 @@
+    $this->normal_user_alt->roles = $this->normal_user->roles;

This won't work as it only alters the in-memory copy of the account.

Attached updates the patch so the role synchronization is persisted, all tests now pass. I have not reviewed the rest of the patch.

Powered by Dreditor.

carlos8f’s picture

Thanks, that would definitely explain it.

I added back the $this->normal_user_alt->roles = $this->normal_user->roles line since we might as well maintain the same roles in memory as well.

Also tweaked a couple of the code comments and removed the call to _block_rehash() in BlockCacheTestCase::setCacheMode() since it was unnecessary.

Now just need someone to review.

tstoeckler’s picture

+++ modules/block/block.test	1 Feb 2010 21:08:00 -0000
@@ -318,3 +318,181 @@ class BlockAdminThemeTestCase extends Dr
+    $this->admin_user = $this->drupalCreateUser(array('administer blocks', 'administer filters', 'access administration pages'));

Minor: Is there any reason this user needs 'administer filters'?

All in all, the tests look good and it seems like they cover everything. They pass locally and I didn't really know what to test manually.
So, from my view, RTBC if the test bot agrees, but I'll let someone more knowledgeable than me, make the decision.

Powered by Dreditor.

carlos8f’s picture

Removed 'administer filters'. It was only there because I copied it from BlockTestCase.

mr.baileys’s picture

Looks pretty good to me... Some minor nitpicks:

+++ modules/block/block.test	15 Feb 2010 17:26:50 -0000
@@ -318,3 +318,181 @@
+    cache_clear_all();
+    $this->drupalGet('');
+    $this->assertNoText($old_content, t('Block cache clear removes stale cache data.'));

I would add an additional positive assertion here to make sure the new, non-cached, content *is* displayed.

+++ modules/block/tests/block_test.module	1 Jan 1970 00:00:00 -0000
@@ -0,0 +1,24 @@
+    'info' => 'Test block caching',

Should be 'info' => t('Test block caching'),

+++ modules/block/tests/block_test.info	1 Jan 1970 00:00:00 -0000
@@ -0,0 +1,8 @@
+package = Core

Should be package = Testing similar to how other test modules are set up.

Powered by Dreditor.

carlos8f’s picture

Thanks for the input, mr.baileys. I incorporated your changes.

mr.baileys’s picture

Ok, one final question from me and then I think this is RTBC (and I might be totally missing something here):

+++ modules/block/block.test	15 Feb 2010 19:20:26 -0000
@@ -318,3 +318,182 @@
+    $this->drupalLogin($this->admin_user);
+    $this->drupalGet('');
+    $this->assertNoText($old_content, t('Admin user does not see content cached per-role for normal user.'));

User 1 is always excluded from block caching:

function _block_get_cache_id($block) {
  global $theme, $base_root, $user;

  // User 1 being out of the regular 'roles define permissions' schema,
  // it brings too many chances of having unwanted output get in the cache
  // and later be served to other users. We therefore exclude user 1 from
  // block caching.
  if (variable_get('block_cache', 0) && $block->cache != BLOCK_NO_CACHE && $user->uid != 1) {

This means it should not be used to check caching per role (as it will never see cached content, irregardless of role). I think it's ok to just drop those three lines alltogether, since I think we have full coverage between the "anonymous user" role and the normal_user/normal_user_alt role, right?

On the other hand, we probably want to add a couple of lines to each test to verify that admin user never gets to see cached content, even though for example caching is set to DRUPAL_CACHE_PAGE and the admin user visits a page which has cached blocks on it.

Does that make sense?

In light of the above, I don't understand why the following test case succeeds:

  /**
  * Test global caching.
  */
  function testCacheGlobal() {
    $this->setCacheMode(DRUPAL_CACHE_GLOBAL);
    variable_set('block_test_content', $this->randomName());

    $this->drupalGet('');
    $this->assertText(variable_get('block_test_content', ''), t('Block content displays.'));

    $old_content = variable_get('block_test_content', '');
    variable_set('block_test_content', $this->randomName());

    $this->drupalLogout();
    $this->drupalGet('user');
    $this->assertText($old_content, t('Block content served from global cache.'));
  }

Since the first page is requested as "admin" (set in setUp), no blocks should be cached, and the second page request should serve fresh content instead of cached content. No?

Powered by Dreditor.

carlos8f’s picture

mr.baileys, as far as my knowledge goes, user #1 is created as part of SimpleTest's DWTC::setUp(), so technically the user I'm creating called $admin_user is user #2, which is subject to permission limitations. This is correct for test cases so permissions can figure into the tests. It verifies that 'administer blocks' and 'access administration pages' are actually the permissions needed to administer blocks.

I think it's valuable to test block cache functionality for user #2 (administer blocks), user #3 (access content) and user #4 (same as #3) as well as the anonymous user. Just testing the difference between #3 and anonymous doesn't really test PER_ROLE in my opinion, since we treat the anonymous user specially in a lot of cases, especially when it comes to caching. Comparing #2 to #3 makes sense to test PER_ROLE for authenticated users.

That said, I could add a part to test if user #1 is not seeing cached blocks. I'm not sure if that's really important though. It's the other situations that are broken :)

mr.baileys’s picture

Status: Needs review » Reviewed & tested by the community

I knew I was missing something obvious, thanks for clearing that up!

A one line fix and an impressive 200 lines of tests to prove that the fix works and to make sure this doesn't get broken again, RTBC!

Dries’s picture

Nice work! Great tests. Some feedback -- nothing major:

+++ modules/block/block.test	15 Feb 2010 19:20:26 -0000
@@ -318,3 +318,182 @@
+    // Sync the roles, since drupalCreateUser() creates separate roles for
+    // the same permission sets.
+    user_save($this->normal_user_alt, array('roles' => $this->normal_user->roles));
+    $this->normal_user_alt->roles = $this->normal_user->roles;
+

Wishlist: would be nice if drupalCreateUser() would take a role as input, instead of permissions. That would avoid these kind of hacks and would align with how Drupal works. Not for this issue though.

+++ modules/block/block.test	15 Feb 2010 19:20:26 -0000
@@ -318,3 +318,182 @@
+    // Enable block caching.
+    variable_set('block_cache', 1);

Mmm, it looks like we're mixing booleans and integers.

$ grep -r block_cache . | grep variable
./modules/block/block.module:  if (variable_get('block_cache', 0) && !in_array($block->cache, array(DRUPAL_NO_CACHE, DRUPAL_CACHE_CUSTOM)) && $user->uid != 1) {
./modules/block/block.module:    '#default_value' => variable_get('block_cache', FALSE),

Maybe we can tidy that up too?

+++ modules/block/block.test	15 Feb 2010 19:20:26 -0000
@@ -318,3 +318,182 @@
+    $old_content = variable_get('block_test_content', '');

Small code optimization: this could be moved up as it would avoid the previous variable_get(). This pattern occurs a few times.

+++ modules/block/block.test	15 Feb 2010 19:20:26 -0000
@@ -318,3 +318,182 @@
+   * Test DRUPAL_NO_CACHE.

Inconsistent documentation. You don't use the defines in the other phpdoc comments.

mr.baileys’s picture

Wishlist: would be nice if drupalCreateUser() would take a role as input, instead of permissions. That would avoid these kind of hacks and would align with how Drupal works. Not for this issue though.

Maybe worthwile to revisit #463354: Support custom role name in drupalCreateUser()?

catch’s picture

Status: Reviewed & tested by the community » Needs work

Looks like this needs a quick re-roll for the rest.

carlos8f’s picture

Status: Needs work » Needs review
FileSize
9.52 KB

Let's give this one a try.

carlos8f’s picture

Created #727964: Test users in SimpleTest can't share roles to address the role problem.

Dries’s picture

Status: Needs review » Fixed

Committed to CVS HEAD. Thanks carlos8f and mr.baileys.

Damien Tournoud’s picture

Title: Block caching per-role, per-user, per-page is broken » [HEAD Broken] Block caching per-role, per-user, per-page is broken
Status: Fixed » Active

Seems like HEAD is broken:

Block caching (BlockCacheTestCase) [Block] 192 passes, 30 fails, 0 exceptions
Damien Tournoud’s picture

HEAD is broken because the helper test module has not been committed:

modules/block/tests/block_test.info
modules/block/tests/block_test.module
Damien Tournoud’s picture

Title: [HEAD Broken] Block caching per-role, per-user, per-page is broken » Block caching per-role, per-user, per-page is broken
Status: Active » Fixed

Now fixed. Thanks, Dries.

Status: Fixed » Closed (fixed)

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