Skip to content

Parallel test cache isolation breaks shared cache (no opt-out available) #58800

@mgjgid

Description

@mgjgid

Laravel Version

12.51.0

PHP Version

8.4.17

Database Driver & Version

No response

Description

This change #57584 introduces a breaking issue for our test suite.

After upgrading to v12.51.0, hundreds of our tests started failing. Our application is multi-tenant and uses a database per tenant architecture. In our CI setup, we intentionally share a single test tenant across all parallel test processes. This avoids creating dozens of tenant databases (for example, 32 databases on a 32-core machine), which would significantly increase setup time and resource usage.

With the new cache prefix / isolation behavior per parallel process, this setup no longer works. The shared tenant relies on a consistent cache namespace across processes, but the automatic isolation now prevents that, causing test failures.

Is there a recommended way to opt out of cache isolation for parallel testing, or to configure a shared cache namespace across processes?

Steps To Reproduce

  1. Create a fresh Laravel 12 application with Redis:
laravel new cache-isolation-bug
cd cache-isolation-bug
composer require predis/predis
  1. Configure Redis as the cache driver in .env:
CACHE_STORE=redis
CACHE_PREFIX=tenant_shared
  1. Seed shared tenant details in the base TestCase, simulating tenant config that is cached once during CI bootstrap and read by all processes:
// tests/TestCase.php

namespace Tests;

use Illuminate\Foundation\Testing\TestCase as BaseTestCase;
use Illuminate\Support\Facades\Redis;

abstract class TestCase extends BaseTestCase
{
    protected function setUp(): void
    {
        parent::setUp();

        // Simulate shared tenant details seeded during CI bootstrap.
        // Written directly to Redis with the expected prefix to bypass
        // any prefix manipulation by the framework.
        $tenantDetails = [
            'id' => 'tenant-001',
            'name' => 'Acme Travel Agency',
            'domain' => 'acme.example.com',
            'database' => 'tenant_acme',
            'timezone' => 'Europe/Zurich',
            'locale' => 'de_CH',
        ];

        Redis::set('tenant_shared_tenant:details', serialize($tenantDetails));
    }
}
  1. Create two test files so they run in separate parallel processes:
// tests/Feature/TenantDetailsAlphaTest.php

namespace Tests\Feature;

use Illuminate\Support\Facades\Cache;
use Tests\TestCase;

class TenantDetailsAlphaTest extends TestCase
{
    public function test_process_can_read_shared_tenant_details(): void
    {
        $tenant = Cache::get('tenant:details');

        $this->assertNotNull($tenant, sprintf(
            'Shared tenant details not found in cache. '
            . 'Cache prefix is "%s" — expected "tenant_shared_". '
            . 'Per-process isolation has changed the prefix.',
            Cache::getPrefix()
        ));

        $this->assertEquals('tenant-001', $tenant['id']);
        $this->assertEquals('Acme Travel Agency', $tenant['name']);
        $this->assertEquals('tenant_acme', $tenant['database']);
    }
}
// tests/Feature/TenantDetailsBravoTest.php

namespace Tests\Feature;

use Illuminate\Support\Facades\Cache;
use Tests\TestCase;

class TenantDetailsBravoTest extends TestCase
{
    public function test_process_can_read_shared_tenant_details(): void
    {
        $tenant = Cache::get('tenant:details');

        $this->assertNotNull($tenant, sprintf(
            'Shared tenant details not found in cache. '
            . 'Cache prefix is "%s" — expected "tenant_shared_". '
            . 'Per-process isolation has changed the prefix.',
            Cache::getPrefix()
        ));

        $this->assertEquals('acme.example.com', $tenant['domain']);
        $this->assertEquals('Europe/Zurich', $tenant['timezone']);
        $this->assertEquals('de_CH', $tenant['locale']);
    }
}
  1. Run tests in parallel:
php artisan test --parallel --processes=2

Metadata

Metadata

Assignees

No one assigned

    Labels

    No labels
    No labels

    Type

    No type

    Projects

    No projects

    Milestone

    No milestone

    Relationships

    None yet

    Development

    No branches or pull requests

    Issue actions