ChunkRunner::runChunks()   B
last analyzed

Complexity

Conditions 5
Paths 8

Size

Total Lines 25
Code Lines 12

Duplication

Lines 0
Ratio 0 %

Code Coverage

Tests 5
CRAP Score 10.8265

Importance

Changes 0
Metric Value
dl 0
loc 25
ccs 5
cts 13
cp 0.3846
rs 8.439
c 0
b 0
f 0
cc 5
eloc 12
nc 8
nop 0
crap 10.8265
1
<?php
2
3
declare(strict_types = 1);
4
5
namespace PHPChunkit;
6
7
use Closure;
8
use Symfony\Component\Console\Helper\ProgressBar;
9
use Symfony\Component\Console\Input\InputInterface;
10
use Symfony\Component\Console\Output\OutputInterface;
11
use Symfony\Component\Process\Process;
12
13
class ChunkRunner
14
{
15
    /**
16
     * @var ChunkedTests
17
     */
18
    private $chunkedTests;
19
20
    /**
21
     * @var ChunkResults
22
     */
23
    private $chunkResults;
24
25
    /**
26
     * @var TestRunner
27
     */
28
    private $testRunner;
29
30
    /**
31
     * @var Processes
32
     */
33
    private $processes;
34
35
    /**
36
     * @var InputInterface
37
     */
38
    private $input;
39
40
    /**
41
     * @var OutputInterface
42
     */
43
    private $output;
44
45
    /**
46
     * @var bool
47
     */
48
    private $verbose = false;
49
50
    /**
51
     * @var bool
52
     */
53
    private $parallel = false;
54
55
    /**
56
     * @var bool
57
     */
58
    private $showProgressBar = false;
59
60
    /**
61
     * @var ProgressBar|null
62
     */
63
    private $progressBar;
64
65 1
    public function __construct(
66
        ChunkedTests $chunkedTests,
67
        ChunkResults $chunkResults,
68
        TestRunner $testRunner,
69
        Processes $processes,
70
        InputInterface $input,
71
        OutputInterface $output,
72
        bool $verbose = false,
73
        bool $parallel = false)
74
    {
75 1
        $this->chunkedTests = $chunkedTests;
76 1
        $this->chunkResults = $chunkResults;
77 1
        $this->testRunner = $testRunner;
78 1
        $this->processes = $processes;
79 1
        $this->input = $input;
80 1
        $this->output = $output;
81 1
        $this->verbose = $verbose;
82 1
        $this->parallel = $parallel;
83 1
        $this->showProgressBar = !$this->verbose && !$this->parallel;
84 1
    }
85
86
    /**
87
     * @return null|integer
88
     */
89 1
    public function runChunks()
90
    {
91 1
        $chunks = $this->chunkedTests->getChunks();
92
93 1
        foreach ($chunks as $i => $chunk) {
94
            // drop and recreate dbs before running this chunk of tests
95
            if ($this->input->getOption('create-dbs')) {
96
                $this->testRunner->runTestCommand('create-dbs', [
97
                    '--quiet' => true,
98
                ]);
99
            }
100
101
            $chunkNum = $i + 1;
102
103
            $code = $this->runChunk($chunkNum, $chunk);
104
105
            if ($code > 0) {
106
                return $code;
107
            }
108
        }
109
110 1
        if ($this->parallel) {
111
            $this->processes->wait();
112
        }
113 1
    }
114
115
    private function runChunk(int $chunkNum, array $chunk)
116
    {
117
        $numTests = $this->countNumTestsInChunk($chunk);
118
119
        $this->chunkResults->incrementTotalTestsRan($numTests);
120
121
        $process = $this->getChunkProcess($chunk);
122
        $this->processes->addProcess($process);
123
124
        $callback = $this->createProgressCallback($numTests);
125
126
        if ($this->parallel) {
127
            return $this->runChunkProcessParallel(
128
                $chunkNum, $process, $callback
129
            );
130
        }
131
132
        return $this->runChunkProcessSerial(
133
            $chunkNum, $process, $callback
134
        );
135
    }
136
137
    private function getChunkProcess(array $chunk) : Process
138
    {
139
        $files = $this->buildFilesFromChunk($chunk);
140
141
        $config = $this->testRunner->generatePhpunitXml($files);
142
143
        $command = sprintf('-c %s', $config);
144
145
        return $this->testRunner->getPhpunitProcess($command);
146
    }
147
148
    private function createProgressCallback(int $numTests) : Closure
149
    {
150
        if ($this->showProgressBar) {
151
            $this->progressBar = $this->createChunkProgressBar($numTests);
152
153
            return $this->createProgressBarCallback($this->progressBar);
154
        }
155
156
        if ($this->verbose) {
157
            return function($type, $out) {
158
                $this->extractDataFromPhpunitOutput($out);
159
160
                $this->output->write($out);
161
            };
162
        }
163
164
        return function($type, $out) {
165
            $this->extractDataFromPhpunitOutput($out);
166
        };
167
    }
168
169
    private function createProgressBarCallback(ProgressBar $progressBar)
170
    {
171
        return function(string $type, string $buffer) use ($progressBar) {
172
            $this->extractDataFromPhpunitOutput($buffer);
173
174
            if ($progressBar) {
175
                if (in_array($buffer, ['F', 'E'])) {
176
                    $progressBar->setBarCharacter('<fg=red>=</>');
177
                }
178
179
                if (in_array($buffer, ['F', 'E', 'S', '.'])) {
180
                    $progressBar->advance();
181
                }
182
            }
183
        };
184
    }
185
186
    private function runChunkProcessParallel(
187
        int $chunkNum,
188
        Process $process,
189
        Closure $callback)
190
    {
191
        $this->output->writeln(sprintf('Starting chunk <info>#%s</info>', $chunkNum));
192
193
        $process->start($callback);
194
195
        $this->processes->wait();
196
    }
197
198
    private function runChunkProcessSerial(
199
        int $chunkNum,
200
        Process $process,
201
        Closure $callback)
202
    {
203
        if ($this->verbose) {
204
            $this->output->writeln('');
205
            $this->output->writeln(sprintf('Running chunk <info>#%s</info>', $chunkNum));
206
        }
207
208
        $this->chunkResults->addCode($code = $process->run($callback));
209
210
        if ($code > 0) {
211
            $this->chunkResults->incrementNumChunkFailures();
212
213
            if ($this->verbose) {
214
                $this->output->writeln(sprintf('Chunk #%s <error>FAILED</error>', $chunkNum));
215
            }
216
217
            if ($this->input->getOption('stop')) {
218
                $this->output->writeln('');
219
                $this->output->writeln($process->getOutput());
220
221
                return $code;
222
            }
223
        }
224
225
        if (!$this->verbose) {
226
            $this->progressBar->finish();
227
            $this->output->writeln('');
228
        }
229
230 View Code Duplication
        if ($code > 0) {
231
            $this->output->writeln('');
232
233
            if (!$this->verbose) {
234
                $this->output->writeln($process->getOutput());
235
            }
236
        }
237
    }
238
239
    private function countNumTestsInChunk(array $chunk) : int
240
    {
241
        return array_sum(array_map(function(array $chunkFile) {
242
            return $chunkFile['numTests'];
243
        }, $chunk));
244
    }
245
246
    private function buildFilesFromChunk(array $chunk) : array
247
    {
248
        return array_map(function(array $chunkFile) {
249
            return $chunkFile['file'];
250
        }, $chunk);
251
    }
252
253
    private function extractDataFromPhpunitOutput(string $outputBuffer) : int
254
    {
255
        preg_match_all('/([0-9]+) assertions/', $outputBuffer, $matches);
256
257 View Code Duplication
        if (isset($matches[1][0])) {
258
            $this->chunkResults->incrementNumAssertions((int) $matches[1][0]);
259
        }
260
261
        preg_match_all('/Assertions: ([0-9]+)/', $outputBuffer, $matches);
262
263 View Code Duplication
        if (isset($matches[1][0])) {
264
            $this->chunkResults->incrementNumAssertions((int) $matches[1][0]);
265
        }
266
267
        preg_match_all('/Failures: ([0-9]+)/', $outputBuffer, $matches);
268
269
        if (isset($matches[1][0])) {
270
            $this->chunkResults->incrementNumFailures((int) $matches[1][0]);
271
        }
272
273
        return 0;
274
    }
275
276
    private function createChunkProgressBar(int $numTests) : ProgressBar
277
    {
278
        $progressBar = new ProgressBar($this->output, $numTests);
279
        $progressBar->setBarCharacter('<fg=green>=</>');
280
        $progressBar->setProgressCharacter("\xF0\x9F\x8C\xAD");
281
282
        return $progressBar;
283
    }
284
}
285