mirror of
https://github.com/pestphp/pest.git
synced 2026-09-05 14:23:34 +02:00
fix: snapshot key on retry / repeat
This commit is contained in:
@@ -0,0 +1,68 @@
|
||||
<?php
|
||||
|
||||
declare(strict_types=1);
|
||||
|
||||
namespace Pest\Repositories;
|
||||
|
||||
use Pest\Exceptions\ShouldNotHappen;
|
||||
|
||||
/**
|
||||
* @internal
|
||||
*/
|
||||
final readonly class Snapshot
|
||||
{
|
||||
public function __construct(
|
||||
private string $filename,
|
||||
private string $basePath,
|
||||
) {}
|
||||
|
||||
public function exists(): bool
|
||||
{
|
||||
return file_exists($this->filename);
|
||||
}
|
||||
|
||||
public function path(): string
|
||||
{
|
||||
return str_replace($this->basePath, '', $this->filename);
|
||||
}
|
||||
|
||||
/**
|
||||
* @throws ShouldNotHappen
|
||||
*/
|
||||
public function read(): string
|
||||
{
|
||||
$contents = file_get_contents($this->filename);
|
||||
|
||||
if ($contents === false) {
|
||||
throw ShouldNotHappen::fromMessage('Snapshot file could not be read.');
|
||||
}
|
||||
|
||||
return $contents;
|
||||
}
|
||||
|
||||
public function write(string $contents): self
|
||||
{
|
||||
$directory = dirname($this->filename);
|
||||
|
||||
if (! is_dir($directory)) {
|
||||
@mkdir($directory, 0755, true);
|
||||
}
|
||||
|
||||
file_put_contents($this->filename, $contents);
|
||||
|
||||
return $this;
|
||||
}
|
||||
|
||||
/**
|
||||
* @throws ShouldNotHappen
|
||||
*/
|
||||
public function matches(string $value): bool
|
||||
{
|
||||
return $this->normalize($this->read()) === $this->normalize($value);
|
||||
}
|
||||
|
||||
public function normalize(string $value): string
|
||||
{
|
||||
return strtr($value, ["\r\n" => "\n", "\r" => "\n"]);
|
||||
}
|
||||
}
|
||||
@@ -4,6 +4,7 @@ declare(strict_types=1);
|
||||
|
||||
namespace Pest\Repositories;
|
||||
|
||||
use InvalidArgumentException;
|
||||
use Pest\Exceptions\ShouldNotHappen;
|
||||
use Pest\TestSuite;
|
||||
|
||||
@@ -12,8 +13,9 @@ use Pest\TestSuite;
|
||||
*/
|
||||
final class SnapshotRepository
|
||||
{
|
||||
/** @var array<string, int> */
|
||||
private static array $expectationsCounter = [];
|
||||
private static ?string $key = null;
|
||||
|
||||
private static int $ordinal = 0;
|
||||
|
||||
public function __construct(
|
||||
private readonly string $rootPath,
|
||||
@@ -21,47 +23,39 @@ final class SnapshotRepository
|
||||
private readonly string $snapshotsPath,
|
||||
) {}
|
||||
|
||||
public function has(): bool
|
||||
public function current(): Snapshot
|
||||
{
|
||||
return file_exists($this->getSnapshotFilename());
|
||||
$this->synchronize();
|
||||
|
||||
return $this->snapshot(self::$ordinal > 1 ? '__'.self::$ordinal : '');
|
||||
}
|
||||
|
||||
/**
|
||||
* @return array{0: string, 1: string}
|
||||
*
|
||||
* @throws ShouldNotHappen
|
||||
*/
|
||||
public function get(): array
|
||||
public function next(): Snapshot
|
||||
{
|
||||
$contents = file_get_contents($snapshotFilename = $this->getSnapshotFilename());
|
||||
$this->synchronize();
|
||||
|
||||
if ($contents === false) {
|
||||
throw ShouldNotHappen::fromMessage('Snapshot file could not be read.');
|
||||
self::$ordinal++;
|
||||
|
||||
return $this->current();
|
||||
}
|
||||
|
||||
public function named(string $name): Snapshot
|
||||
{
|
||||
$this->synchronize();
|
||||
|
||||
$suffix = trim((string) preg_replace('/[^\w-]+/', '_', $name), '_');
|
||||
|
||||
if ($suffix === '') {
|
||||
throw new InvalidArgumentException('The snapshot name must contain at least one alphanumeric character.');
|
||||
}
|
||||
|
||||
$snapshot = str_replace(dirname($this->testsPath).'/', '', $snapshotFilename);
|
||||
|
||||
return [$snapshot, $contents];
|
||||
return $this->snapshot('__'.$suffix);
|
||||
}
|
||||
|
||||
public function save(string $snapshot): string
|
||||
public function forget(): void
|
||||
{
|
||||
$snapshotFilename = $this->getSnapshotFilename();
|
||||
|
||||
$directory = dirname($snapshotFilename);
|
||||
|
||||
if (! is_dir($directory)) {
|
||||
@mkdir($directory, 0755, true);
|
||||
}
|
||||
|
||||
file_put_contents($snapshotFilename, $snapshot);
|
||||
|
||||
return $this->filename();
|
||||
}
|
||||
|
||||
public function filename(): string
|
||||
{
|
||||
return str_replace(dirname($this->testsPath).'/', '', $this->getSnapshotFilename());
|
||||
self::$key = null;
|
||||
self::$ordinal = 0;
|
||||
}
|
||||
|
||||
public function flush(): void
|
||||
@@ -92,47 +86,81 @@ final class SnapshotRepository
|
||||
}
|
||||
}
|
||||
|
||||
private function getSnapshotFilename(): string
|
||||
private function snapshot(string $suffix): Snapshot
|
||||
{
|
||||
$testFile = TestSuite::getInstance()->getFilename();
|
||||
|
||||
if (str_starts_with($testFile, $this->testsPath)) {
|
||||
$startPath = $this->testsPath;
|
||||
} else {
|
||||
$startPath = $this->rootPath;
|
||||
}
|
||||
$startPath = str_starts_with($testFile, $this->testsPath) ? $this->testsPath : $this->rootPath;
|
||||
|
||||
$relativePath = substr($testFile, strlen($startPath));
|
||||
|
||||
$relativePath = substr($relativePath, 0, (int) strrpos($relativePath, '.'));
|
||||
|
||||
$description = TestSuite::getInstance()->getDescription();
|
||||
return new Snapshot(
|
||||
sprintf(
|
||||
'%s/%s%s.snap',
|
||||
$this->testsPath.'/'.$this->snapshotsPath.$relativePath,
|
||||
TestSuite::getInstance()->getDescription(),
|
||||
$suffix,
|
||||
),
|
||||
dirname($this->testsPath).'/',
|
||||
);
|
||||
}
|
||||
|
||||
if ($this->getCurrentSnapshotCounter() > 1) {
|
||||
$description .= '__'.$this->getCurrentSnapshotCounter();
|
||||
private function synchronize(): void
|
||||
{
|
||||
$key = TestSuite::getInstance()->getFilename().'###'.TestSuite::getInstance()->getDescription();
|
||||
|
||||
if (self::$key === $key) {
|
||||
return;
|
||||
}
|
||||
|
||||
return sprintf('%s/%s.snap', $this->testsPath.'/'.$this->snapshotsPath.$relativePath, $description);
|
||||
}
|
||||
|
||||
private function getCurrentSnapshotKey(): string
|
||||
{
|
||||
return TestSuite::getInstance()->getFilename().'###'.TestSuite::getInstance()->getDescription();
|
||||
}
|
||||
|
||||
private function getCurrentSnapshotCounter(): int
|
||||
{
|
||||
return self::$expectationsCounter[$this->getCurrentSnapshotKey()] ?? 0;
|
||||
self::$key = $key;
|
||||
self::$ordinal = 0;
|
||||
}
|
||||
|
||||
/**
|
||||
* @deprecated Use `next` and `current` instead.
|
||||
*/
|
||||
public function startNewExpectation(): void
|
||||
{
|
||||
$key = $this->getCurrentSnapshotKey();
|
||||
$this->next();
|
||||
}
|
||||
|
||||
if (! isset(self::$expectationsCounter[$key])) {
|
||||
self::$expectationsCounter[$key] = 0;
|
||||
}
|
||||
/**
|
||||
* @deprecated Use `current` instead.
|
||||
*/
|
||||
public function has(): bool
|
||||
{
|
||||
return $this->current()->exists();
|
||||
}
|
||||
|
||||
self::$expectationsCounter[$key]++;
|
||||
/**
|
||||
* @deprecated Use `current` instead.
|
||||
*
|
||||
* @return array{0: string, 1: string}
|
||||
*
|
||||
* @throws ShouldNotHappen
|
||||
*/
|
||||
public function get(): array
|
||||
{
|
||||
$snapshot = $this->current();
|
||||
|
||||
return [$snapshot->path(), $snapshot->read()];
|
||||
}
|
||||
|
||||
/**
|
||||
* @deprecated Use `current` instead.
|
||||
*/
|
||||
public function save(string $snapshot): string
|
||||
{
|
||||
return $this->current()->write($snapshot)->path();
|
||||
}
|
||||
|
||||
/**
|
||||
* @deprecated Use `current` instead.
|
||||
*/
|
||||
public function filename(): string
|
||||
{
|
||||
return $this->current()->path();
|
||||
}
|
||||
}
|
||||
|
||||
Reference in New Issue
Block a user