Completed
Push — master ( 5ef5a6...726f17 )
by Jonathan
02:41
created

ChunkRunner::extractDataFromPhpunitOutput()   B

Complexity

Conditions 4
Paths 8

Size

Total Lines 22
Code Lines 11

Duplication

Lines 6
Ratio 27.27 %

Code Coverage

Tests 0
CRAP Score 20

Importance

Changes 0
Metric Value
dl 6
loc 22
ccs 0
cts 11
cp 0
rs 8.9197
c 0
b 0
f 0
cc 4
eloc 11
nc 8
nop 1
crap 20
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 1
    public function __construct(
56
        ChunkedTests $chunkedTests,
57
        ChunkResults $chunkResults,
58
        TestRunner $testRunner,
59
        Processes $processes,
60
        InputInterface $input,
61
        OutputInterface $output,
62
        bool $verbose = false,
63
        bool $parallel = false)
64
    {
65 1
        $this->chunkedTests = $chunkedTests;
66 1
        $this->chunkResults = $chunkResults;
67 1
        $this->testRunner = $testRunner;
68 1
        $this->processes = $processes;
69 1
        $this->input = $input;
70 1
        $this->output = $output;
71 1
        $this->verbose = $verbose;
72 1
        $this->parallel = $parallel;
73 1
        $this->showProgressBar = !$this->verbose && !$this->parallel;
0 ignored issues
show
Bug introduced by
The property showProgressBar does not exist. Did you maybe forget to declare it?

In PHP it is possible to write to properties without declaring them. For example, the following is perfectly valid PHP code:

class MyClass { }

$x = new MyClass();
$x->foo = true;

Generally, it is a good practice to explictly declare properties to avoid accidental typos and provide IDE auto-completion:

class MyClass {
    public $foo;
}

$x = new MyClass();
$x->foo = true;
Loading history...
74 1
    }
75
76
    /**
77
     * @return null|integer
78
     */
79 1
    public function runChunks()
80
    {
81 1
        $chunks = $this->chunkedTests->getChunks();
82
83 1
        foreach ($chunks as $i => $chunk) {
84
            // drop and recreate dbs before running this chunk of tests
85
            if ($this->input->getOption('create-dbs')) {
86
                $this->testRunner->runTestCommand('create-dbs', [
87
                    '--quiet' => true,
88
                ]);
89
            }
90
91
            $chunkNum = $i + 1;
92
93
            $code = $this->runChunk($chunkNum, $chunk);
94
95
            if ($code > 0) {
96
                return $code;
97
            }
98
        }
99
100 1
        if ($this->parallel) {
101
            $this->processes->wait();
102
        }
103 1
    }
104
105
    private function runChunk(int $chunkNum, array $chunk)
106
    {
107
        $numTests = $this->countNumTestsInChunk($chunk);
108
109
        $this->chunkResults->incrementTotalTestsRan($numTests);
110
111
        $process = $this->getChunkProcess($chunk);
112
        $this->processes->addProcess($process);
113
114
        $callback = $this->createProgressCallback($numTests);
115
116
        if ($this->parallel) {
117
            return $this->runChunkProcessParallel(
118
                $chunkNum, $process, $callback
119
            );
120
        }
121
122
        return $this->runChunkProcessSerial(
123
            $chunkNum, $process, $callback
124
        );
125
    }
126
127
    private function getChunkProcess(array $chunk) : Process
128
    {
129
        $files = $this->buildFilesFromChunk($chunk);
130
131
        $config = $this->testRunner->generatePhpunitXml($files);
132
133
        $command = sprintf('-c %s', $config);
134
135
        return $this->testRunner->getPhpunitProcess($command);
136
    }
137
138
    private function createProgressCallback(int $numTests) : Closure
139
    {
140
        if ($this->showProgressBar) {
141
            $this->progressBar = $this->createChunkProgressBar($numTests);
0 ignored issues
show
Bug introduced by
The property progressBar does not seem to exist. Did you mean showProgressBar?

An attempt at access to an undefined property has been detected. This may either be a typographical error or the property has been renamed but there are still references to its old name.

If you really want to allow access to undefined properties, you can define magic methods to allow access. See the php core documentation on Overloading.

Loading history...
142
143
            return $this->createProgressBarCallback($this->progressBar);
0 ignored issues
show
Bug introduced by
The property progressBar does not seem to exist. Did you mean showProgressBar?

An attempt at access to an undefined property has been detected. This may either be a typographical error or the property has been renamed but there are still references to its old name.

If you really want to allow access to undefined properties, you can define magic methods to allow access. See the php core documentation on Overloading.

Loading history...
144
        }
145
146
        if ($this->verbose) {
147
            return function($type, $out) {
148
                $this->extractDataFromPhpunitOutput($out);
149
150
                $this->output->write($out);
151
            };
152
        }
153
154
        return function($type, $out) {
155
            $this->extractDataFromPhpunitOutput($out);
156
        };
157
    }
158
159
    private function createProgressBarCallback(ProgressBar $progressBar)
160
    {
161
        return function(string $type, string $buffer) use ($progressBar) {
162
            $this->extractDataFromPhpunitOutput($buffer);
163
164
            if ($progressBar) {
165
                if (in_array($buffer, ['F', 'E'])) {
166
                    $progressBar->setBarCharacter('<fg=red>=</>');
167
                }
168
169
                if (in_array($buffer, ['F', 'E', 'S', '.'])) {
170
                    $progressBar->advance();
171
                }
172
            }
173
        };
174
    }
175
176
    private function runChunkProcessParallel(
177
        int $chunkNum,
178
        Process $process,
179
        Closure $callback)
180
    {
181
        $this->output->writeln(sprintf('Starting chunk <info>#%s</info>', $chunkNum));
182
183
        $process->start($callback);
184
185
        $this->processes->wait();
186
    }
187
188
    private function runChunkProcessSerial(
189
        int $chunkNum,
190
        Process $process,
191
        Closure $callback)
192
    {
193
        if ($this->verbose) {
194
            $this->output->writeln('');
195
            $this->output->writeln(sprintf('Running chunk <info>#%s</info>', $chunkNum));
196
        }
197
198
        $this->chunkResults->addCode($code = $process->run($callback));
199
200
        if ($code > 0) {
201
            $this->chunkResults->incrementNumChunkFailures();
202
203
            if ($this->verbose) {
204
                $this->output->writeln(sprintf('Chunk #%s <error>FAILED</error>', $chunkNum));
205
            }
206
207
            if ($this->input->getOption('stop')) {
208
                $this->output->writeln('');
209
                $this->output->writeln($process->getOutput());
210
211
                return $code;
212
            }
213
        }
214
215
        if (!$this->verbose) {
216
            $this->progressBar->finish();
0 ignored issues
show
Bug introduced by
The property progressBar does not seem to exist. Did you mean showProgressBar?

An attempt at access to an undefined property has been detected. This may either be a typographical error or the property has been renamed but there are still references to its old name.

If you really want to allow access to undefined properties, you can define magic methods to allow access. See the php core documentation on Overloading.

Loading history...
217
            $this->output->writeln('');
218
        }
219
220 View Code Duplication
        if ($code > 0) {
1 ignored issue
show
Duplication introduced by
This code seems to be duplicated across your project.

Duplicated code is one of the most pungent code smells. If you need to duplicate the same code in three or more different places, we strongly encourage you to look into extracting the code into a single class or operation.

You can also find more detailed suggestions in the “Code” section of your repository.

Loading history...
221
            $this->output->writeln('');
222
223
            if (!$this->verbose) {
224
                $this->output->writeln($process->getOutput());
225
            }
226
        }
227
    }
228
229
    private function countNumTestsInChunk(array $chunk) : int
230
    {
231
        return array_sum(array_map(function(array $chunkFile) {
232
            return $chunkFile['numTests'];
233
        }, $chunk));
234
    }
235
236
    private function buildFilesFromChunk(array $chunk) : array
237
    {
238
        return array_map(function(array $chunkFile) {
239
            return $chunkFile['file'];
240
        }, $chunk);
241
    }
242
243
    private function extractDataFromPhpunitOutput(string $outputBuffer) : int
244
    {
245
        preg_match_all('/([0-9]+) assertions/', $outputBuffer, $matches);
246
247 View Code Duplication
        if (isset($matches[1][0])) {
1 ignored issue
show
Duplication introduced by
This code seems to be duplicated across your project.

Duplicated code is one of the most pungent code smells. If you need to duplicate the same code in three or more different places, we strongly encourage you to look into extracting the code into a single class or operation.

You can also find more detailed suggestions in the “Code” section of your repository.

Loading history...
248
            $this->chunkResults->incrementNumAssertions((int) $matches[1][0]);
249
        }
250
251
        preg_match_all('/Assertions: ([0-9]+)/', $outputBuffer, $matches);
252
253 View Code Duplication
        if (isset($matches[1][0])) {
1 ignored issue
show
Duplication introduced by
This code seems to be duplicated across your project.

Duplicated code is one of the most pungent code smells. If you need to duplicate the same code in three or more different places, we strongly encourage you to look into extracting the code into a single class or operation.

You can also find more detailed suggestions in the “Code” section of your repository.

Loading history...
254
            $this->chunkResults->incrementNumAssertions((int) $matches[1][0]);
255
        }
256
257
        preg_match_all('/Failures: ([0-9]+)/', $outputBuffer, $matches);
258
259
        if (isset($matches[1][0])) {
260
            $this->chunkResults->incrementNumFailures((int) $matches[1][0]);
261
        }
262
263
        return 0;
264
    }
265
266
    private function createChunkProgressBar(int $numTests) : ProgressBar
267
    {
268
        $progressBar = new ProgressBar($this->output, $numTests);
269
        $progressBar->setBarCharacter('<fg=green>=</>');
270
        $progressBar->setProgressCharacter("\xF0\x9F\x8C\xAD");
271
272
        return $progressBar;
273
    }
274
}
275