diff --git a/app/container.php b/app/container.php index 6ebbe23cf..52948d081 100644 --- a/app/container.php +++ b/app/container.php @@ -15,6 +15,7 @@ use Infection\Command\InfectionCommand; use Infection\Process\Runner\Parallel\ParallelProcessRunner; use Infection\EventDispatcher\EventDispatcher; +use Infection\Filesystem\Filesystem; use Infection\Finder\Locator; use Infection\TestFramework\PhpUnit\Config\Path\PathReplacer; use Infection\TestFramework\Config\TestFrameworkConfigLocator; @@ -46,6 +47,10 @@ return $c['temp.dir.creator']->createAndGet(); }; +$c['filesystem'] = function (Container $c): Filesystem { + return new Filesystem(); +}; + $c['temp.dir.creator'] = function (): TempDirectoryCreator { return new TempDirectoryCreator(); }; diff --git a/src/Filesystem/Exception/IOException.php b/src/Filesystem/Exception/IOException.php new file mode 100644 index 000000000..2aeeaaac1 --- /dev/null +++ b/src/Filesystem/Exception/IOException.php @@ -0,0 +1,32 @@ +path = $path; + + parent::__construct($message, $code, $previous); + } + + /** + * {@inheritdoc} + */ + public function getPath() + { + return $this->path; + } +} diff --git a/src/Filesystem/Exception/IOExceptionInterface.php b/src/Filesystem/Exception/IOExceptionInterface.php new file mode 100644 index 000000000..e9e9e5e30 --- /dev/null +++ b/src/Filesystem/Exception/IOExceptionInterface.php @@ -0,0 +1,17 @@ +addSubscriber(new MutationGeneratingConsoleLoggerSubscriber($this->output, $mutationGeneratingProgressBar)); $eventDispatcher->addSubscriber(new MutantCreatingConsoleLoggerSubscriber($this->output, $mutantCreatingProgressBar)); $eventDispatcher->addSubscriber(new MutationTestingConsoleLoggerSubscriber($this->output, $this->getOutputFormatter(), $metricsCalculator, $this->get('diff.colorizer'), $this->input->getOption('show-mutations'))); - $eventDispatcher->addSubscriber(new TextFileLoggerSubscriber($this->get('infection.config'), $metricsCalculator)); + $eventDispatcher->addSubscriber(new TextFileLoggerSubscriber($this->get('infection.config'), $metricsCalculator, $this->get('filesystem'))); } private function getCodeCoverageData(string $testFrameworkKey): CodeCoverageData diff --git a/src/Process/Listener/TextFileLoggerSubscriber.php b/src/Process/Listener/TextFileLoggerSubscriber.php index d4da332b7..a37c62cdb 100644 --- a/src/Process/Listener/TextFileLoggerSubscriber.php +++ b/src/Process/Listener/TextFileLoggerSubscriber.php @@ -11,6 +11,7 @@ use Infection\Config\InfectionConfig; use Infection\EventDispatcher\EventSubscriberInterface; use Infection\Events\MutationTestingFinished; +use Infection\Filesystem\Filesystem; use Infection\Mutant\MetricsCalculator; use Infection\Process\MutantProcess; @@ -26,10 +27,16 @@ class TextFileLoggerSubscriber implements EventSubscriberInterface */ private $metricsCalculator; - public function __construct(InfectionConfig $infectionConfig, MetricsCalculator $metricsCalculator) + /** + * @var Filesystem + */ + private $fs; + + public function __construct(InfectionConfig $infectionConfig, MetricsCalculator $metricsCalculator, Filesystem $fs) { $this->infectionConfig = $infectionConfig; $this->metricsCalculator = $metricsCalculator; + $this->fs = $fs; } public function getSubscribedEvents() @@ -41,32 +48,26 @@ public function getSubscribedEvents() public function onMutationTestingFinished(MutationTestingFinished $event) { - $textFileLogPath = $this->infectionConfig->getTextFileLogPath(); + $logFilePath = $this->infectionConfig->getTextFileLogPath(); - if ($textFileLogPath) { - $logParts = []; + if ($logFilePath) { + $this->fs->mkdir(\dirname($logFilePath)); - $logParts = array_merge( - $logParts, - $this->getLogParts($this->metricsCalculator->getEscapedMutantProcesses(), 'Escaped') - ); + $escapedParts = $this->getLogParts($this->metricsCalculator->getEscapedMutantProcesses(), 'Escaped'); - $logParts = array_merge( - $logParts, - $this->getLogParts($this->metricsCalculator->getTimedOutProcesses(), 'Timeout') - ); + $timedOutParts = $this->getLogParts($this->metricsCalculator->getTimedOutProcesses(), 'Timeout'); - $logParts = array_merge( - $logParts, - $this->getLogParts($this->metricsCalculator->getKilledMutantProcesses(), 'Killed') - ); + $killedParts = $this->getLogParts($this->metricsCalculator->getKilledMutantProcesses(), 'Killed'); - $logParts = array_merge( - $logParts, - $this->getNotCoveredLogParts($this->metricsCalculator->getNotCoveredMutantProcesses(), 'Not covered') - ); + $notCoveredParts = $this->getNotCoveredLogParts($this->metricsCalculator->getNotCoveredMutantProcesses(), 'Not covered'); - file_put_contents($textFileLogPath, implode($logParts, "\n")); + file_put_contents( + $logFilePath, + implode( + array_merge($escapedParts, $timedOutParts, $killedParts, $notCoveredParts), + "\n" + ) + ); } } diff --git a/src/StreamWrapper/IncludeInterceptor.php b/src/StreamWrapper/IncludeInterceptor.php index 648b3412d..3ec658921 100644 --- a/src/StreamWrapper/IncludeInterceptor.php +++ b/src/StreamWrapper/IncludeInterceptor.php @@ -108,10 +108,13 @@ public function dir_rewinddir() public function mkdir($path, $mode, $options) { self::disable(); + + $isRecursive = (bool) ($options & STREAM_MKDIR_RECURSIVE); + if (isset($this->context)) { - $return = mkdir($path, $mode, $options, $this->context); + $return = mkdir($path, $mode, $isRecursive, $this->context); } else { - $return = mkdir($path, $mode, $options); + $return = mkdir($path, $mode, $isRecursive); } self::enable(); diff --git a/src/Utils/TempDirectoryCreator.php b/src/Utils/TempDirectoryCreator.php index 722f45910..0b445a24d 100644 --- a/src/Utils/TempDirectoryCreator.php +++ b/src/Utils/TempDirectoryCreator.php @@ -15,11 +15,19 @@ class TempDirectoryCreator */ private $tempDirectory; - public function createAndGet($dirName = null): string + /** + * @param string|null $dir + * + * @return string + */ + public function createAndGet(string $dirName = null): string { if ($this->tempDirectory === null) { - $root = sys_get_temp_dir(); - $path = $root . sprintf('/%s', $dirName ?: 'infection'); + $path = sprintf( + '%s/%s', + sys_get_temp_dir(), + $dirName ?: 'infection' + ); if (!@mkdir($path, 0777, true) && !is_dir($path)) { throw new \RuntimeException('Can not create temp dir'); diff --git a/tests/Config/ValueProvider/ExcludeDirsProviderTest.php b/tests/Config/ValueProvider/ExcludeDirsProviderTest.php index 59af7df03..243fcdfc0 100644 --- a/tests/Config/ValueProvider/ExcludeDirsProviderTest.php +++ b/tests/Config/ValueProvider/ExcludeDirsProviderTest.php @@ -16,6 +16,26 @@ class ExcludeDirsProviderTest extends AbstractBaseProviderTest { + /** + * @var string + */ + private $workspace; + + private $umask; + + protected function setUp() + { + $this->umask = \umask(0); + $this->workspace = \sys_get_temp_dir() . '/exclude' . \microtime(true) . \random_int(100, 999); + \mkdir($this->workspace, 0777, true); + } + + protected function tearDown() + { + @\unlink($this->workspace); + \umask($this->umask); + } + public function test_it_contains_vendors_when_sources_contains_current_dir() { $consoleMock = Mockery::mock(ConsoleHelper::class); @@ -64,6 +84,12 @@ public function test_passes_when_correct_dir_typed() $this->markTestSkipped("Stty is not available"); } + $dir1 = $this->workspace . DIRECTORY_SEPARATOR . 'test' . DIRECTORY_SEPARATOR; + $dir2 = $this->workspace . DIRECTORY_SEPARATOR . 'foo' . DIRECTORY_SEPARATOR; + + \mkdir($dir1); + \mkdir($dir2); + $consoleMock = Mockery::mock(ConsoleHelper::class); $consoleMock->shouldReceive('getQuestion')->once()->andReturn('?'); @@ -72,12 +98,12 @@ public function test_passes_when_correct_dir_typed() $provider = new ExcludeDirsProvider($consoleMock, $dialog); $excludeDirs = $provider->get( - $this->createStreamableInputInterfaceMock($this->getInputStream("Files\n")), + $this->createStreamableInputInterfaceMock($this->getInputStream("foo\n")), $this->createOutputInterface(), ['src'], - ['tests'] + [$this->workspace] ); - $this->assertContains('Files', $excludeDirs); + $this->assertContains('foo', $excludeDirs); } } \ No newline at end of file diff --git a/tests/Filesystem/FilesystemTest.php b/tests/Filesystem/FilesystemTest.php new file mode 100644 index 000000000..5df690f0f --- /dev/null +++ b/tests/Filesystem/FilesystemTest.php @@ -0,0 +1,100 @@ +umask = \umask(0); + $this->filesystem = new Filesystem(); + $this->workspace = \sys_get_temp_dir() . '/' . \microtime(true) . \random_int(100, 999); + \mkdir($this->workspace, 0777, true); + } + + protected function tearDown() + { + @\unlink($this->workspace); + \umask($this->umask); + } + + public function test_mkdir_creates_directory() + { + $dir = $this->workspace . DIRECTORY_SEPARATOR . 'test' . DIRECTORY_SEPARATOR; + + $this->filesystem->mkdir($dir); + + $this->assertTrue(\is_dir($dir)); + } + + public function test_mkdir_creates_directory_recursively() + { + $dir = $this->workspace + . DIRECTORY_SEPARATOR . 'test' + . DIRECTORY_SEPARATOR . 'sub_directory'; + + $this->filesystem->mkdir($dir); + + $this->assertTrue(\is_dir($dir)); + } + + /** + * @expectedException \Infection\Filesystem\Exception\IOException + * @expectedExceptionCode 0 + */ + public function test_mkdir_creates_directory_fails() + { + $basePath = $this->workspace.DIRECTORY_SEPARATOR; + $dir = $basePath.'2'; + + \file_put_contents($dir, ''); + + $this->filesystem->mkdir($dir); + } + + public function test_mkdir_passes_path_to_io_exceptino() + { + $basePath = $this->workspace.DIRECTORY_SEPARATOR; + $dir = $basePath.'2'; + + \file_put_contents($dir, ''); + + try { + $this->filesystem->mkdir($dir); + } catch (IOException $e) { + $this->assertSame($dir, $e->getPath()); + } + } + + public function test_mkdir_does_not_fail_when_dir_already_exists() + { + $dir = $this->workspace . DIRECTORY_SEPARATOR . 'test' . DIRECTORY_SEPARATOR; + + $this->filesystem->mkdir($dir); + $this->filesystem->mkdir($dir); + + $this->assertTrue(\is_dir($dir)); + } +}