Skip to content

Commit fa3b25a

Browse files
dxntertaylorotwell
andauthored
[12.x] Isolate compiled views per process during parallel testing (#58390)
* feat: isolate compiled views * style: formatting * formatting * fix test --------- Co-authored-by: Taylor Otwell <taylor@laravel.com>
1 parent aad1f10 commit fa3b25a

File tree

3 files changed

+218
-1
lines changed

3 files changed

+218
-1
lines changed
Lines changed: 73 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,73 @@
1+
<?php
2+
3+
namespace Illuminate\Testing\Concerns;
4+
5+
use Illuminate\Support\Facades\File;
6+
use Illuminate\Support\Facades\ParallelTesting;
7+
8+
trait TestViews
9+
{
10+
/**
11+
* The original compiled view path prior to appending the token.
12+
*
13+
* @var string|null
14+
*/
15+
protected static $originalCompiledViewPath = null;
16+
17+
/**
18+
* Boot test views for parallel testing.
19+
*
20+
* @return void
21+
*/
22+
protected function bootTestViews()
23+
{
24+
ParallelTesting::setUpProcess(function () {
25+
if ($path = $this->parallelSafeCompiledViewPath()) {
26+
File::ensureDirectoryExists($path);
27+
}
28+
});
29+
30+
ParallelTesting::setUpTestCase(function () {
31+
if ($path = $this->parallelSafeCompiledViewPath()) {
32+
$this->switchToCompiledViewPath($path);
33+
}
34+
});
35+
}
36+
37+
/**
38+
* Get the test compiled view path.
39+
*
40+
* @return string|null
41+
*/
42+
protected function parallelSafeCompiledViewPath()
43+
{
44+
self::$originalCompiledViewPath ??= $this->app['config']->get('view.compiled', '');
45+
46+
if (! self::$originalCompiledViewPath) {
47+
return null;
48+
}
49+
50+
return rtrim(self::$originalCompiledViewPath, '\/')
51+
.'/test_'
52+
.ParallelTesting::token();
53+
}
54+
55+
/**
56+
* Switch to the given compiled view path.
57+
*
58+
* @param string $path
59+
* @return void
60+
*/
61+
protected function switchToCompiledViewPath($path)
62+
{
63+
$this->app['config']->set('view.compiled', $path);
64+
65+
if ($this->app->resolved('blade.compiler')) {
66+
$compiler = $this->app['blade.compiler'];
67+
68+
(function () use ($path) {
69+
$this->cachePath = $path;
70+
})->bindTo($compiler, $compiler)();
71+
}
72+
}
73+
}

src/Illuminate/Testing/ParallelTestingServiceProvider.php

Lines changed: 3 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -5,10 +5,11 @@
55
use Illuminate\Contracts\Support\DeferrableProvider;
66
use Illuminate\Support\ServiceProvider;
77
use Illuminate\Testing\Concerns\TestDatabases;
8+
use Illuminate\Testing\Concerns\TestViews;
89

910
class ParallelTestingServiceProvider extends ServiceProvider implements DeferrableProvider
1011
{
11-
use TestDatabases;
12+
use TestDatabases, TestViews;
1213

1314
/**
1415
* Boot the application's service providers.
@@ -19,6 +20,7 @@ public function boot()
1920
{
2021
if ($this->app->runningInConsole()) {
2122
$this->bootTestDatabase();
23+
$this->bootTestViews();
2224
}
2325
}
2426

Lines changed: 142 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,142 @@
1+
<?php
2+
3+
namespace Illuminate\Tests\Testing\Concerns;
4+
5+
use Illuminate\Config\Repository as Config;
6+
use Illuminate\Container\Container;
7+
use Illuminate\Filesystem\Filesystem;
8+
use Illuminate\Support\Facades\Facade;
9+
use Illuminate\Support\Facades\ParallelTesting as ParallelTestingFacade;
10+
use Illuminate\Testing\Concerns\TestViews;
11+
use Illuminate\Testing\ParallelTesting;
12+
use Illuminate\View\Compilers\BladeCompiler;
13+
use Mockery as m;
14+
use PHPUnit\Framework\TestCase;
15+
use ReflectionMethod;
16+
use ReflectionProperty;
17+
18+
class TestViewsTest extends TestCase
19+
{
20+
protected function setUp(): void
21+
{
22+
parent::setUp();
23+
24+
Container::setInstance($container = new Container);
25+
26+
Facade::setFacadeApplication($container);
27+
28+
$container->singleton('config', fn () => new Config([
29+
'view' => [
30+
'compiled' => '/path/to/compiled/views',
31+
],
32+
]));
33+
34+
$container->singleton(ParallelTesting::class, fn ($app) => new ParallelTesting($app));
35+
36+
$_SERVER['LARAVEL_PARALLEL_TESTING'] = 1;
37+
}
38+
39+
protected function tearDown(): void
40+
{
41+
Container::setInstance(null);
42+
ParallelTestingFacade::clearResolvedInstance();
43+
Facade::setFacadeApplication(null);
44+
45+
unset($_SERVER['LARAVEL_PARALLEL_TESTING']);
46+
47+
m::close();
48+
49+
parent::tearDown();
50+
}
51+
52+
public function testCompiledViewPathAppendsToken()
53+
{
54+
Container::getInstance()->make(ParallelTesting::class)->resolveTokenUsing(fn () => '5');
55+
56+
$this->assertSame('/path/to/compiled/views/test_5', $this->testCompiledViewPath());
57+
}
58+
59+
public function testCompiledViewPathTrimsTrailingSlash()
60+
{
61+
Container::getInstance()->make(ParallelTesting::class)->resolveTokenUsing(fn () => '3');
62+
63+
Container::getInstance()['config']->set('view.compiled', '/path/to/compiled/views/');
64+
65+
$this->assertSame('/path/to/compiled/views/test_3', $this->testCompiledViewPath());
66+
}
67+
68+
public function testCompiledViewPathWithDifferentToken()
69+
{
70+
Container::getInstance()->make(ParallelTesting::class)->resolveTokenUsing(fn () => '42');
71+
72+
Container::getInstance()['config']->set('view.compiled', '/var/www/storage/views');
73+
74+
$this->assertSame('/var/www/storage/views/test_42', $this->testCompiledViewPath());
75+
}
76+
77+
public function testCompiledViewPathReturnsNullWhenEmpty()
78+
{
79+
Container::getInstance()['config']->set('view.compiled', '');
80+
81+
$this->assertNull($this->testCompiledViewPath());
82+
}
83+
84+
public function testSwitchToCompiledViewPathUpdatesConfig()
85+
{
86+
$this->switchToCompiledViewPath('/new/compiled/path');
87+
88+
$this->assertSame('/new/compiled/path', Container::getInstance()['config']->get('view.compiled'));
89+
}
90+
91+
public function testSwitchToCompiledViewPathUpdatesCompilerCachePath()
92+
{
93+
$container = Container::getInstance();
94+
$compiler = new BladeCompiler(m::mock(Filesystem::class), '/original/path');
95+
96+
$container->instance('blade.compiler', $compiler);
97+
98+
$this->switchToCompiledViewPath('/new/compiled/path');
99+
100+
$this->assertSame('/new/compiled/path', $container['config']->get('view.compiled'));
101+
$this->assertSame('/new/compiled/path', (new ReflectionProperty($compiler, 'cachePath'))->getValue($compiler));
102+
}
103+
104+
public function testCompiledViewPath()
105+
{
106+
$instance = new class
107+
{
108+
use TestViews;
109+
110+
public $app;
111+
112+
public function __construct()
113+
{
114+
$this->app = Container::getInstance();
115+
}
116+
};
117+
118+
(new ReflectionProperty($instance::class, 'originalCompiledViewPath'))->setValue(null, null);
119+
120+
$method = new ReflectionMethod($instance, 'parallelSafeCompiledViewPath');
121+
122+
return $method->invoke($instance);
123+
}
124+
125+
public function switchToCompiledViewPath($path)
126+
{
127+
$instance = new class
128+
{
129+
use TestViews;
130+
131+
public $app;
132+
133+
public function __construct()
134+
{
135+
$this->app = Container::getInstance();
136+
}
137+
};
138+
139+
$method = new ReflectionMethod($instance, 'switchToCompiledViewPath');
140+
$method->invoke($instance, $path);
141+
}
142+
}

0 commit comments

Comments
 (0)