From d29d68a2c2ada29fe634924643df6dbd92c61063 Mon Sep 17 00:00:00 2001 From: Luke Downing Date: Mon, 5 Dec 2022 08:59:42 +0000 Subject: [PATCH] Adds initial implementation of `--dirty` option. --- bin/pest | 25 +++++++++------ src/Contracts/TestCaseFilter.php | 10 ++++++ src/Repositories/TestRepository.php | 38 +++++++++++++++++------ src/Support/DirtyTestCaseFilter.php | 47 +++++++++++++++++++++++++++++ src/TestSuite.php | 18 +++++------ 5 files changed, 109 insertions(+), 29 deletions(-) create mode 100644 src/Contracts/TestCaseFilter.php create mode 100644 src/Support/DirtyTestCaseFilter.php diff --git a/bin/pest b/bin/pest index ac1e98b6..341fd059 100755 --- a/bin/pest +++ b/bin/pest @@ -3,8 +3,10 @@ use Pest\Actions\ValidatesEnvironment; use Pest\ConfigLoader; +use Pest\Repositories\TestRepository; use Pest\Support\Container; use Pest\Kernel; +use Pest\Support\DirtyTestCaseFilter; use Pest\TestSuite; use Symfony\Component\Console\Input\ArgvInput; use Symfony\Component\Console\Output\ConsoleOutput; @@ -44,9 +46,18 @@ 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()) + $argv->getParameterOption('--test-directory', (new ConfigLoader($rootPath))->getTestsDirectory()), + new TestRepository($filters), ); $isDecorated = $argv->getParameterOption('--colors', 'always') !== 'never'; @@ -57,16 +68,10 @@ use Symfony\Component\Console\Output\OutputInterface; $container->add(TestSuite::class, $testSuite); $container->add(OutputInterface::class, $output); + $argsToUnset = ['--test-directory', '--compact', '--profile', '--dirty']; + foreach ($args as $key => $value) { - if (str_contains($value, '--test-directory')) { - unset($args[$key]); - } - - if (str_contains($value, '--compact')) { - unset($args[$key]); - } - - if (str_contains($value, '--profile')) { + if (in_array($value, $argsToUnset)) { unset($args[$key]); } } diff --git a/src/Contracts/TestCaseFilter.php b/src/Contracts/TestCaseFilter.php new file mode 100644 index 00000000..a9e89930 --- /dev/null +++ b/src/Contracts/TestCaseFilter.php @@ -0,0 +1,10 @@ + $testCaseFilters + */ + public function __construct(private readonly array $testCaseFilters = []) + { + } + /** * Counts the number of test cases. */ @@ -42,22 +50,22 @@ final class TestRepository */ public function getFilenames(): array { - $testCases = array_filter($this->testCases, static fn (TestCaseFactory $testCase) => $testCase->methodsUsingOnly() !== []); + $testCases = array_filter($this->testCases, static fn(TestCaseFactory $testCase) => $testCase->methodsUsingOnly() !== []); if ($testCases === []) { $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 { @@ -97,7 +105,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); } @@ -109,7 +117,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), + true, + ); + + if ($canLoad) { $this->make($this->testCases[$filename]); } } @@ -119,12 +137,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 new file mode 100644 index 00000000..f074bf71 --- /dev/null +++ b/src/Support/DirtyTestCaseFilter.php @@ -0,0 +1,47 @@ + + */ + 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/TestSuite.php b/src/TestSuite.php index c460f532..79402046 100644 --- a/src/TestSuite.php +++ b/src/TestSuite.php @@ -23,11 +23,6 @@ final class TestSuite */ public ?TestCase $test = null; - /** - * Holds the tests repository. - */ - public TestRepository $tests; - /** * Holds the before each repository. */ @@ -68,11 +63,12 @@ final class TestSuite */ public function __construct( string $rootPath, - public string $testPath) + 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->retryTempRepository = new TempRepository('retry'); @@ -83,10 +79,14 @@ final class TestSuite /** * Returns the current instance of the test suite. */ - public static function getInstance(string $rootPath = null, string $testPath = null): TestSuite + public static function getInstance( + string $rootPath = null, + string $testPath = null, + TestRepository $tests = null, + ): TestSuite { if (is_string($rootPath) && is_string($testPath)) { - self::$instance = new TestSuite($rootPath, $testPath); + self::$instance = new TestSuite($rootPath, $testPath, $tests); foreach (Plugin::$callables as $callable) { $callable();