Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
27 changes: 25 additions & 2 deletions src/Command/ExecuteSuite.php
Original file line number Diff line number Diff line change
Expand Up @@ -117,6 +117,7 @@ protected function outputTitle()

public function execute(InputInterface $input, OutputInterface $output): int
{
$this->beginRun();
$this->handleDir(dirname($this->file));
$this->cli = true;
$this->output = $output;
Expand Down Expand Up @@ -295,14 +296,36 @@ public function execute(InputInterface $input, OutputInterface $output): int
$this->success('Rapport visuel écrit dans ' . $visualPath, newLine: true, force: true);
}

$this->releaseBrowser();

return $summary->hasFailures() ? Command::FAILURE : Command::SUCCESS;
}

/**
* Give this run a browser of its own.
*
* The run ends by closing its browser (releaseBrowser()), so it must never
* pick up one another PrestaFlow process is driving, nor let that process
* find and close its own. Scoping the socket file to this run does both.
*/
public function beginRun(): void
{
\PrestaFlow\Library\Tests\TestsSuite::scopeBrowserFilesTo(
'run-' . getmypid() . '-' . bin2hex(random_bytes(4))
);
}

/**
* Close the browser this run launched and forget its socket file.
*/
public function releaseBrowser(): void
{
try {
\PrestaFlow\Library\Tests\TestsSuite::getBrowser(force: false)?->close();
} catch (\Throwable $e) {
}
@unlink(\PrestaFlow\Library\Tests\TestsSuite::getFilePath('.browser'));
@unlink(\PrestaFlow\Library\Tests\TestsSuite::getFilePath('.browser-options'));

return $summary->hasFailures() ? Command::FAILURE : Command::SUCCESS;
}

protected function handleDir($path)
Expand Down
27 changes: 27 additions & 0 deletions src/Tests/TestsSuite.php
Original file line number Diff line number Diff line change
Expand Up @@ -154,6 +154,11 @@ private static function buildVisualBlock(int $startIndex): array
protected static $browserInstance = null;
protected static ?string $browserInstanceSocket = null;

/**
* Owner of the keepAlive browser files, see scopeBrowserFilesTo().
*/
protected static ?string $browserFilesScope = null;

protected $draft = false;
protected $groups = 'all';

Expand Down Expand Up @@ -316,8 +321,30 @@ public function getGroups() : string|array
return $this->groups;
}

/**
* Scope the keepAlive browser files (socket, options) to one run.
*
* Without a scope they sit at one path per machine, which every PrestaFlow
* process shares: a run would reconnect to the Chrome another run is
* driving, and ExecuteSuite, which closes that browser and deletes the file
* when it ends, would tear it down under the other run ("The page was
* closed and is not available anymore"). A run scopes the files to itself
* so it only ever finds, and releases, the browser it launched.
*
* Null restores the shared path, for callers outside ExecuteSuite that
* rely on reconnecting to an existing browser.
*/
public static function scopeBrowserFilesTo(?string $scope): void
{
self::$browserFilesScope = ($scope === null || $scope === '') ? null : $scope;
}

public static function getFilePath($filename = '.browser')
{
if (self::$browserFilesScope !== null) {
$filename = self::$browserFilesScope.'-'.$filename;
}

if (function_exists('storage_path')) {
$dir = storage_path().'/datas';
if (!is_dir($dir)) {
Expand Down
90 changes: 90 additions & 0 deletions tests/Unit/Command/BrowserOwnershipTest.php
Original file line number Diff line number Diff line change
@@ -0,0 +1,90 @@
<?php

namespace PrestaFlow\Tests\Unit\Command;

use PHPUnit\Framework\TestCase;
use PrestaFlow\Library\Command\ExecuteSuite;
use PrestaFlow\Library\Tests\TestsSuite;

/**
* Two runs on the same machine must not share, nor tear down, each other's
* browser.
*
* The keepAlive browser is found again through a socket file. That file used
* to sit at one fixed path per machine, so every run read and wrote the same
* one, and every run ended by closing the browser the file named and deleting
* it. A run that finished — even a browser-free one — closed the Chrome another
* run was driving, which then failed with "The page was closed and is not
* available anymore".
*
* No browser is needed: the socket file points to a port nothing listens on,
* so releaseBrowser() cannot connect and only its file handling is exercised.
*/
final class BrowserOwnershipTest extends TestCase
{
private array $files = [];

protected function tearDown(): void
{
foreach ($this->files as $file) {
@unlink($file);
}
TestsSuite::scopeBrowserFilesTo(null);
}

public function testARunThatEndsLeavesTheBrowserOfAnotherRunAlone(): void
{
$first = new ExecuteSuite();
$first->beginRun();
$firstSocketFile = TestsSuite::getFilePath('.browser');
$this->files[] = $firstSocketFile;
file_put_contents($firstSocketFile, 'ws://127.0.0.1:1/devtools/browser/first-run');

// A second run starts and finishes while the first is still driving
// its browser (in real life: another process, same temp dir).
$second = new ExecuteSuite();
$second->beginRun();
$this->files[] = TestsSuite::getFilePath('.browser');
$second->releaseBrowser();

$this->assertFileExists(
$firstSocketFile,
'the second run deleted the socket file of the first one'
);
}

public function testEachRunGetsItsOwnSocketFile(): void
{
$first = new ExecuteSuite();
$first->beginRun();
$firstSocketFile = TestsSuite::getFilePath('.browser');

$second = new ExecuteSuite();
$second->beginRun();

$this->assertNotSame($firstSocketFile, TestsSuite::getFilePath('.browser'));
}

public function testARunReleasesItsOwnSocketFile(): void
{
$run = new ExecuteSuite();
$run->beginRun();
$socketFile = TestsSuite::getFilePath('.browser');
$this->files[] = $socketFile;
file_put_contents($socketFile, 'ws://127.0.0.1:1/devtools/browser/own-run');

$run->releaseBrowser();

$this->assertFileDoesNotExist($socketFile);
}

public function testWithoutARunTheSharedPathIsUnchanged(): void
{
TestsSuite::scopeBrowserFilesTo(null);

$this->assertSame(
sys_get_temp_dir() . '/prestaflow-.browser',
TestsSuite::getFilePath('.browser')
);
}
}
Loading