mirror of
https://github.com/pestphp/pest.git
synced 2026-03-10 09:47:23 +01:00
[feat] scoped datasets
This commit is contained in:
@ -9,6 +9,7 @@ use function Pest\testDirectory;
|
||||
use Pest\TestSuite;
|
||||
use RecursiveDirectoryIterator;
|
||||
use RecursiveIteratorIterator;
|
||||
use SebastianBergmann\FileIterator\Facade as PhpUnitFileIterator;
|
||||
|
||||
/**
|
||||
* @internal
|
||||
@ -21,8 +22,6 @@ final class BootFiles
|
||||
* @var array<int, string>
|
||||
*/
|
||||
private const STRUCTURE = [
|
||||
'Datasets',
|
||||
'Datasets.php',
|
||||
'Expectations',
|
||||
'Expectations.php',
|
||||
'Helpers',
|
||||
@ -56,6 +55,8 @@ final class BootFiles
|
||||
$this->load($filename);
|
||||
}
|
||||
}
|
||||
|
||||
$this->bootDatasets($testsPath);
|
||||
}
|
||||
|
||||
/**
|
||||
@ -73,4 +74,26 @@ final class BootFiles
|
||||
|
||||
include_once $filename;
|
||||
}
|
||||
|
||||
private function bootDatasets(string $testsPath): void
|
||||
{
|
||||
$files = (new PhpUnitFileIterator)->getFilesAsArray($testsPath, '.php');
|
||||
|
||||
foreach ($files as $fullPath) {
|
||||
$filename = Str::afterLast($fullPath, DIRECTORY_SEPARATOR);
|
||||
|
||||
if ($filename === 'Datasets.php') {
|
||||
$this->load($fullPath);
|
||||
|
||||
continue;
|
||||
}
|
||||
|
||||
$directoryFullPath = Str::beforeLast($fullPath, DIRECTORY_SEPARATOR);
|
||||
$directory = Str::afterLast($directoryFullPath, DIRECTORY_SEPARATOR);
|
||||
|
||||
if ($directory === 'Datasets') {
|
||||
$this->load($fullPath);
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@ -17,8 +17,8 @@ final class DatasetAlreadyExist extends InvalidArgumentException implements Exce
|
||||
/**
|
||||
* Creates a new Exception instance.
|
||||
*/
|
||||
public function __construct(string $name)
|
||||
public function __construct(string $name, string $scope)
|
||||
{
|
||||
parent::__construct(sprintf('A dataset with the name `%s` already exist.', $name));
|
||||
parent::__construct(sprintf('A dataset with the name `%s` already exist in scope [%s].', $name, $scope));
|
||||
}
|
||||
}
|
||||
|
||||
@ -10,6 +10,7 @@ use Pest\PendingCalls\UsesCall;
|
||||
use Pest\Repositories\DatasetsRepository;
|
||||
use Pest\Support\Backtrace;
|
||||
use Pest\Support\HigherOrderTapProxy;
|
||||
use Pest\Support\Str;
|
||||
use Pest\TestSuite;
|
||||
use PHPUnit\Framework\TestCase;
|
||||
|
||||
@ -60,7 +61,17 @@ if (! function_exists('dataset')) {
|
||||
*/
|
||||
function dataset(string $name, Closure|iterable $dataset): void
|
||||
{
|
||||
DatasetsRepository::set($name, $dataset);
|
||||
$file = Backtrace::datasetsFile();
|
||||
$filename = Str::afterLast($file, DIRECTORY_SEPARATOR);
|
||||
$scope = Str::beforeLast($file, DIRECTORY_SEPARATOR);
|
||||
|
||||
if (Str::afterLast($scope, DIRECTORY_SEPARATOR) === 'Datasets') {
|
||||
$scope = Str::beforeLast($scope, DIRECTORY_SEPARATOR);
|
||||
} elseif ($filename !== 'Datasets.php') {
|
||||
$scope = $file;
|
||||
}
|
||||
|
||||
DatasetsRepository::set($name, $dataset, $scope);
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
@ -36,13 +36,15 @@ final class DatasetsRepository
|
||||
*
|
||||
* @param Closure|iterable<int|string, mixed> $data
|
||||
*/
|
||||
public static function set(string $name, Closure|iterable $data): void
|
||||
public static function set(string $name, Closure|iterable $data, string $scope): void
|
||||
{
|
||||
if (array_key_exists($name, self::$datasets)) {
|
||||
throw new DatasetAlreadyExist($name);
|
||||
$datasetKey = "$scope>>>$name";
|
||||
|
||||
if (array_key_exists("$datasetKey", self::$datasets)) {
|
||||
throw new DatasetAlreadyExist($name, $scope);
|
||||
}
|
||||
|
||||
self::$datasets[$name] = $data;
|
||||
self::$datasets[$datasetKey] = $data;
|
||||
}
|
||||
|
||||
/**
|
||||
@ -52,7 +54,7 @@ final class DatasetsRepository
|
||||
*/
|
||||
public static function with(string $filename, string $description, array $with): void
|
||||
{
|
||||
self::$withs[$filename.'>>>'.$description] = $with;
|
||||
self::$withs["$filename>>>$description"] = $with;
|
||||
}
|
||||
|
||||
public static function has(string $filename, string $description): bool
|
||||
@ -67,9 +69,10 @@ final class DatasetsRepository
|
||||
*/
|
||||
public static function get(string $filename, string $description)
|
||||
{
|
||||
// dump("requesting file: " . $filename);
|
||||
$dataset = self::$withs[$filename.'>>>'.$description];
|
||||
|
||||
$dataset = self::resolve($description, $dataset);
|
||||
$dataset = self::resolve($dataset, $filename);
|
||||
|
||||
if ($dataset === null) {
|
||||
throw ShouldNotHappen::fromMessage('Dataset [%s] not resolvable.');
|
||||
@ -84,14 +87,14 @@ final class DatasetsRepository
|
||||
* @param array<Closure|iterable<int|string, mixed>|string> $dataset
|
||||
* @return array<string, mixed>|null
|
||||
*/
|
||||
public static function resolve(string $description, array $dataset): array|null
|
||||
public static function resolve(array $dataset, string $currentTestFile): array|null
|
||||
{
|
||||
/* @phpstan-ignore-next-line */
|
||||
if (empty($dataset)) {
|
||||
return null;
|
||||
}
|
||||
|
||||
$dataset = self::processDatasets($dataset);
|
||||
$dataset = self::processDatasets($dataset, $currentTestFile);
|
||||
|
||||
$datasetCombinations = self::getDatasetsCombinations($dataset);
|
||||
|
||||
@ -132,11 +135,45 @@ final class DatasetsRepository
|
||||
return $namedData;
|
||||
}
|
||||
|
||||
/**
|
||||
* @return Closure|iterable<int|string, mixed>
|
||||
*/
|
||||
private static function getScopedDataset(string $name, string $currentTestFile)
|
||||
{
|
||||
$matchingDatasets = array_filter(self::$datasets, function (string $key) use ($name, $currentTestFile) {
|
||||
[$datasetScope, $datasetName] = explode('>>>', $key);
|
||||
|
||||
if ($name !== $datasetName) {
|
||||
return false;
|
||||
}
|
||||
|
||||
if (! str_starts_with($currentTestFile, $datasetScope)) {
|
||||
return false;
|
||||
}
|
||||
|
||||
return true;
|
||||
}, ARRAY_FILTER_USE_KEY);
|
||||
|
||||
$closestScopeDatasetKey = array_reduce(array_keys($matchingDatasets), function ($keyA, $keyB) {
|
||||
if ($keyA === null) {
|
||||
return $keyB;
|
||||
}
|
||||
|
||||
return strlen($keyA) > strlen($keyB) ? $keyA : $keyB;
|
||||
});
|
||||
|
||||
if ($closestScopeDatasetKey === null) {
|
||||
throw new DatasetDoesNotExist($name);
|
||||
}
|
||||
|
||||
return $matchingDatasets[$closestScopeDatasetKey];
|
||||
}
|
||||
|
||||
/**
|
||||
* @param array<Closure|iterable<int|string, mixed>|string> $datasets
|
||||
* @return array<array<mixed>>
|
||||
*/
|
||||
private static function processDatasets(array $datasets): array
|
||||
private static function processDatasets(array $datasets, string $currentTestFile): array
|
||||
{
|
||||
$processedDatasets = [];
|
||||
|
||||
@ -144,11 +181,7 @@ final class DatasetsRepository
|
||||
$processedDataset = [];
|
||||
|
||||
if (is_string($data)) {
|
||||
if (! array_key_exists($data, self::$datasets)) {
|
||||
throw new DatasetDoesNotExist($data);
|
||||
}
|
||||
|
||||
$datasets[$index] = self::$datasets[$data];
|
||||
$datasets[$index] = self::getScopedDataset($data, $currentTestFile);
|
||||
}
|
||||
|
||||
if (is_callable($datasets[$index])) {
|
||||
|
||||
@ -42,6 +42,30 @@ final class Backtrace
|
||||
return $current[self::FILE];
|
||||
}
|
||||
|
||||
/**
|
||||
* Returns the current datasets file.
|
||||
*/
|
||||
public static function datasetsFile(): string
|
||||
{
|
||||
$current = null;
|
||||
|
||||
foreach (debug_backtrace(self::BACKTRACE_OPTIONS) as $trace) {
|
||||
assert(array_key_exists(self::FILE, $trace));
|
||||
|
||||
if (Str::endsWith($trace['file'], 'Bootstrappers/BootFiles.php') || Str::endsWith($trace[self::FILE], 'overrides/Runner/TestSuiteLoader.php')) {
|
||||
break;
|
||||
}
|
||||
|
||||
$current = $trace;
|
||||
}
|
||||
|
||||
if ($current === null) {
|
||||
throw ShouldNotHappen::fromMessage('Dataset file not found.');
|
||||
}
|
||||
|
||||
return $current[self::FILE];
|
||||
}
|
||||
|
||||
/**
|
||||
* Returns the filename that called the current function/method.
|
||||
*/
|
||||
|
||||
@ -59,6 +59,24 @@ final class Str
|
||||
return (string) preg_replace('/[^A-Z_a-z0-9\\\\]/', '', $code);
|
||||
}
|
||||
|
||||
/**
|
||||
* Return the remainder of a string after the last occurrence of a given value.
|
||||
*/
|
||||
public static function afterLast(string $subject, string $search): string
|
||||
{
|
||||
if ($search === '') {
|
||||
return $subject;
|
||||
}
|
||||
|
||||
$position = strrpos($subject, $search);
|
||||
|
||||
if ($position === false) {
|
||||
return $subject;
|
||||
}
|
||||
|
||||
return substr($subject, $position + strlen($search));
|
||||
}
|
||||
|
||||
/**
|
||||
* Get the portion of a string before the last occurrence of a given value.
|
||||
*/
|
||||
|
||||
Reference in New Issue
Block a user