Completed
Pull Request — master (#38)
by Jonathan
02:53
created

TestWatcher::findAssociatedTestFiles()   B

Complexity

Conditions 5
Paths 4

Size

Total Lines 23
Code Lines 11

Duplication

Lines 0
Ratio 0 %

Code Coverage

Tests 0
CRAP Score 30

Importance

Changes 0
Metric Value
dl 0
loc 23
ccs 0
cts 11
cp 0
rs 8.5906
c 0
b 0
f 0
cc 5
eloc 11
nc 4
nop 1
crap 30
1
<?php
2
3
declare(strict_types=1);
4
5
namespace PHPChunkit\Command;
6
7
use PHPChunkit\FileClassesHelper;
8
use PHPChunkit\TestRunner;
9
use PHPChunkit\Configuration;
10
use Symfony\Component\Console\Command\Command;
11
use Symfony\Component\Console\Input\InputInterface;
12
use Symfony\Component\Console\Input\InputOption;
13
use Symfony\Component\Console\Output\OutputInterface;
14
use Symfony\Component\Finder\Finder;
15
use Symfony\Component\Finder\SplFileInfo;
16
17
/**
18
 * @testClass PHPChunkit\Test\Command\TestWatcherTest
19
 */
20
class TestWatcher implements CommandInterface
21
{
22
    /**
23
     * @var TestRunner
24
     */
25
    private $testRunner;
26
27
    /**
28
     * @var Configuration
29
     */
30
    private $configuration;
31
32
    /**
33
     * @var FileClassesHelper
34
     */
35
    private $fileClassesHelper;
36
37 1
    public function __construct(
38
        TestRunner $testRunner,
39
        Configuration $configuration,
40
        FileClassesHelper $fileClassesHelper)
41
    {
42 1
        $this->testRunner = $testRunner;
43 1
        $this->configuration = $configuration;
44 1
        $this->fileClassesHelper = $fileClassesHelper;
45 1
    }
46
47
    public function configure(Command $command)
48
    {
49
        $command
50
            ->setDescription('Watch for changes to files and run the associated tests.')
51
            ->addOption('debug', null, InputOption::VALUE_NONE, 'Run tests in debug mode.')
52
            ->addOption('memory-limit', null, InputOption::VALUE_REQUIRED, 'Memory limit for PHP.')
53
            ->addOption('stop', null, InputOption::VALUE_NONE, 'Stop on failure or error.')
54
            ->addOption('failed', null, InputOption::VALUE_REQUIRED, 'Track tests that have failed.', true)
55
        ;
56
    }
57
58 1
    public function execute(InputInterface $input, OutputInterface $output)
59
    {
60 1
        $output->writeln('<info>Watching for changes to your code.</info>');
61
62 1
        $lastTime = time();
63
64 1
        while ($this->while()) {
65
            $this->sleep();
66
67
            $finder = $this->createFinder();
68
69
            foreach ($finder as $file) {
70
                $lastTime = $this->checkFile($file, $lastTime);
71
            }
72
        }
73 1
    }
74
75
    protected function sleep()
76
    {
77
        usleep(300000);
78
    }
79
80
    protected function while () : bool
81
    {
82
        return true;
83
    }
84
85
    private function checkFile(SplFileInfo $file, int $lastTime) : int
86
    {
87
        $fileLastModified = $file->getMTime();
88
89
        if ($fileLastModified > $lastTime) {
90
91
            $lastTime = $fileLastModified;
92
93
            if (!$this->isTestFile($file)) {
94
                // TODO figure out a better way
95
                // We have to wait a litte bit to look at the contents of the
96
                // file because it might be empty because of the save operation.
97
                usleep(10000);
98
99
                $files = $this->findAssociatedTestFiles($file);
100
101
                if (empty($files)) {
102
                    return $lastTime;
103
                }
104
            } else {
105
                $files = [$file->getPathName()];
106
            }
107
108
            $this->testRunner->runTestFiles($files);
109
        }
110
111
        return $lastTime;
112
    }
113
114
    private function createFinder() : Finder
115
    {
116
        return Finder::create()
117
            ->files()
118
            ->name('*.php')
119
            ->in($this->configuration->getWatchDirectories())
120
        ;
121
    }
122
123
    private function isTestFile(SplFileInfo $file) : bool
124
    {
125
        return strpos($file->getPathName(), 'Test.php') !== false;
126
    }
127
128
    private function findAssociatedTestFiles(SplFileInfo $file) : array
129
    {
130
        $classes = $this->getClassesInsideFile($file->getPathName());
131
132
        $testFiles = [];
133
134
        foreach ($classes as $className) {
135
136
            $reflectionClass = new \ReflectionClass($className);
137
138
            $docComment = $reflectionClass->getDocComment();
139
140
            preg_match_all('/@testClass\s(.*)/', $docComment, $testClasses);
141
142
            if (isset($testClasses[1]) && $testClasses[1]) {
143
                foreach ($testClasses[1] as $className) {
144
                    $testFiles[] = (new \ReflectionClass($className))->getFileName();
145
                }
146
            }
147
        }
148
149
        return $testFiles;
150
    }
151
152
    private function getClassesInsideFile(string $file) : array
153
    {
154
        return $this->fileClassesHelper->getFileClasses($file);
155
    }
156
}
157