From 15931e241853dfa8649bac5afd577e560501adcf Mon Sep 17 00:00:00 2001 From: Nuno Maduro Date: Tue, 10 Jan 2023 22:23:52 +0000 Subject: [PATCH] feat: rewrites `--dirty` support --- bin/pest | 20 +++---- src/Contracts/TestCaseFilter.php | 5 +- src/Repositories/TestRepository.php | 40 +++++++------ src/Support/DirtyTestCaseFilter.php | 47 --------------- .../GitDirtyTestCaseFilter.php | 58 +++++++++++++++++++ src/TestSuite.php | 16 ++--- 6 files changed, 102 insertions(+), 84 deletions(-) delete mode 100644 src/Support/DirtyTestCaseFilter.php create mode 100644 src/TestCaseFilters/GitDirtyTestCaseFilter.php diff --git a/bin/pest b/bin/pest index 341fd059..7b31c0b1 100755 --- a/bin/pest +++ b/bin/pest @@ -3,10 +3,9 @@ use Pest\Actions\ValidatesEnvironment; use Pest\ConfigLoader; -use Pest\Repositories\TestRepository; -use Pest\Support\Container; use Pest\Kernel; -use Pest\Support\DirtyTestCaseFilter; +use Pest\Support\Container; +use Pest\TestCaseFilters\GitDirtyTestCaseFilter; use Pest\TestSuite; use Symfony\Component\Console\Input\ArgvInput; use Symfony\Component\Console\Output\ConsoleOutput; @@ -46,20 +45,17 @@ use Symfony\Component\Console\Output\OutputInterface; $rootPath = dirname($autoloadPath, 2); $argv = new ArgvInput(); - $filters = []; - - foreach ($args as $key => $value) { - if (str_contains($value, '--dirty')) { - $filters[] = new DirtyTestCaseFilter($rootPath); - } - } - $testSuite = TestSuite::getInstance( $rootPath, $argv->getParameterOption('--test-directory', (new ConfigLoader($rootPath))->getTestsDirectory()), - new TestRepository($filters), ); + foreach ($args as $key => $value) { + if (str_contains($value, '--dirty')) { + $testSuite->tests->filter(new GitDirtyTestCaseFilter($rootPath)); + } + } + $isDecorated = $argv->getParameterOption('--colors', 'always') !== 'never'; $output = new ConsoleOutput(ConsoleOutput::VERBOSITY_NORMAL, $isDecorated); diff --git a/src/Contracts/TestCaseFilter.php b/src/Contracts/TestCaseFilter.php index a9e89930..f458396e 100644 --- a/src/Contracts/TestCaseFilter.php +++ b/src/Contracts/TestCaseFilter.php @@ -6,5 +6,8 @@ namespace Pest\Contracts; interface TestCaseFilter { - public function canLoad(string $suiteClassFile): bool; + /** + * Whether the test case is accepted. + */ + public function accept(string $testCaseFilename): bool; } diff --git a/src/Repositories/TestRepository.php b/src/Repositories/TestRepository.php index ad553487..eb4e4936 100644 --- a/src/Repositories/TestRepository.php +++ b/src/Repositories/TestRepository.php @@ -29,11 +29,9 @@ final class TestRepository private array $uses = []; /** - * @param array $testCaseFilters + * @var array */ - public function __construct(private readonly array $testCaseFilters = []) - { - } + private array $filters = []; /** * Counts the number of test cases. @@ -56,16 +54,16 @@ final class TestRepository $testCases = $this->testCases; } - return array_values(array_map(static fn(TestCaseFactory $factory): string => $factory->filename, $testCases)); + return array_values(array_map(static fn (TestCaseFactory $factory): string => $factory->filename, $testCases)); } /** * Uses the given `$testCaseClass` on the given `$paths`. * - * @param array $classOrTraits - * @param array $groups - * @param array $paths - * @param array $hooks + * @param array $classOrTraits + * @param array $groups + * @param array $paths + * @param array $hooks */ public function use(array $classOrTraits, array $groups, array $paths, array $hooks): void { @@ -92,6 +90,14 @@ final class TestRepository } } + /** + * Filters the test cases using the given filter. + */ + public function filter(TestCaseFilter $filter): void + { + $this->filters[] = $filter; + } + /** * Gets the test case factory from the given filename. */ @@ -105,7 +111,7 @@ final class TestRepository */ public function set(TestCaseMethodFactory $method): void { - if (!array_key_exists($method->filename, $this->testCases)) { + if (! array_key_exists($method->filename, $this->testCases)) { $this->testCases[$method->filename] = new TestCaseFactory($method->filename); } @@ -117,17 +123,17 @@ final class TestRepository */ public function makeIfNeeded(string $filename): void { - if (!array_key_exists($filename, $this->testCases)) { + if (! array_key_exists($filename, $this->testCases)) { return; } - $canLoad = array_reduce( - $this->testCaseFilters, - fn(bool $carry, TestCaseFilter $filter): bool => $carry && $filter->canLoad($filename), + $accepted = array_reduce( + $this->filters, + fn (bool $carry, TestCaseFilter $filter): bool => $carry && $filter->accept($filename), true, ); - if ($canLoad) { + if ($accepted) { $this->make($this->testCases[$filename]); } } @@ -137,12 +143,12 @@ final class TestRepository */ private function make(TestCaseFactory $testCase): void { - $startsWith = static fn(string $target, string $directory): bool => Str::startsWith($target, $directory . DIRECTORY_SEPARATOR); + $startsWith = static fn (string $target, string $directory): bool => Str::startsWith($target, $directory.DIRECTORY_SEPARATOR); foreach ($this->uses as $path => $uses) { [$classOrTraits, $groups, $hooks] = $uses; - if ((!is_dir($path) && $testCase->filename === $path) || (is_dir($path) && $startsWith($testCase->filename, $path))) { + if ((! is_dir($path) && $testCase->filename === $path) || (is_dir($path) && $startsWith($testCase->filename, $path))) { foreach ($classOrTraits as $class) { /** @var string $class */ if (class_exists($class)) { diff --git a/src/Support/DirtyTestCaseFilter.php b/src/Support/DirtyTestCaseFilter.php deleted file mode 100644 index f074bf71..00000000 --- a/src/Support/DirtyTestCaseFilter.php +++ /dev/null @@ -1,47 +0,0 @@ - - */ - private array $changedFiles = []; - - public function __construct(private string $projectRoot) - { - $this->loadDiff(); - } - - public function canLoad(string $suiteClassFile): bool - { - $relativePath = str_replace($this->projectRoot, '', $suiteClassFile); - - if (str_starts_with($relativePath, '/')) { - $relativePath = substr($relativePath, 1); - } - - return in_array($relativePath, $this->changedFiles, true); - } - - private function loadDiff(): void - { - $process = new Process([ - 'git', - 'diff', - '--name-only', - 'HEAD', - '--', - '*.php', - ]); - $process->run(); - - $this->changedFiles = explode(PHP_EOL, trim($process->getOutput())); - } -} diff --git a/src/TestCaseFilters/GitDirtyTestCaseFilter.php b/src/TestCaseFilters/GitDirtyTestCaseFilter.php new file mode 100644 index 00000000..29e9f089 --- /dev/null +++ b/src/TestCaseFilters/GitDirtyTestCaseFilter.php @@ -0,0 +1,58 @@ + + */ + private array $changedFiles = []; + + public function __construct(private readonly string $projectRoot) + { + $this->loadDiff(); + } + + public function accept(string $testCaseFilename): bool + { + $relativePath = str_replace($this->projectRoot, '', $testCaseFilename); + + if (str_starts_with($relativePath, '/')) { + $relativePath = substr($relativePath, 1); + } + + return in_array($relativePath, $this->changedFiles, true); + } + + private function loadDiff(): void + { + $process = new Process(['git', 'status', '--short', '--', '*.php']); + $process->run(); + + if (! $process->isSuccessful()) { + throw new MissingDependency('Filter by dirty files', 'git'); + } + + $output = preg_split('/\R+/', $process->getOutput(), flags: PREG_SPLIT_NO_EMPTY); + assert(is_array($output)); + + $dirtyFiles = []; + + foreach ($output as $dirtyFile) { + $dirtyFiles[substr($dirtyFile, 3)] = trim(substr($dirtyFile, 0, 3)); + } + + $dirtyFiles = array_filter($dirtyFiles, fn ($status): bool => $status !== 'D'); + + $dirtyFiles = array_map(fn ($file, $status): string => in_array($status, ['R', 'RM'], true) ? explode(' -> ', $file)[1] : $file, array_keys($dirtyFiles), $dirtyFiles); + + $this->changedFiles = $dirtyFiles = array_values($dirtyFiles); + } +} diff --git a/src/TestSuite.php b/src/TestSuite.php index 1bf64ace..bdec4d4e 100644 --- a/src/TestSuite.php +++ b/src/TestSuite.php @@ -23,6 +23,11 @@ final class TestSuite */ public ?TestCase $test = null; + /** + * Holds the tests repository. + */ + public TestRepository $tests; + /** * Holds the before each repository. */ @@ -64,11 +69,10 @@ final class TestSuite public function __construct( string $rootPath, public string $testPath, - public TestRepository $tests = new TestRepository(), - ) - { + ) { $this->beforeAll = new BeforeAllRepository(); $this->beforeEach = new BeforeEachRepository(); + $this->tests = new TestRepository(); $this->afterEach = new AfterEachRepository(); $this->afterAll = new AfterAllRepository(); $this->retryRepository = new RetryRepository('retry'); @@ -82,11 +86,9 @@ final class TestSuite public static function getInstance( string $rootPath = null, string $testPath = null, - TestRepository $tests = null, - ): TestSuite - { + ): TestSuite { if (is_string($rootPath) && is_string($testPath)) { - self::$instance = new TestSuite($rootPath, $testPath, $tests); + self::$instance = new TestSuite($rootPath, $testPath); foreach (Plugin::$callables as $callable) { $callable();