Completed
Push — master ( 80d94b...95c442 )
by Jonathan
31s
created

TestWatcher::while()   A

Complexity

Conditions 1
Paths 1

Size

Total Lines 4
Code Lines 2

Duplication

Lines 0
Ratio 0 %

Code Coverage

Tests 0
CRAP Score 2

Importance

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