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

Run::runChunks()   B

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\Command;
6
7
use Closure;
8
use PHPChunkit\ChunkRunner;
9
use PHPChunkit\ChunkedTests;
10
use PHPChunkit\ChunkRepository;
11
use PHPChunkit\ChunkResults;
12
use PHPChunkit\Processes;
13
use PHPChunkit\TestChunker;
14
use PHPChunkit\TestFinder;
15
use PHPChunkit\TestRunner;
16
use PHPChunkit\Configuration;
17
use Symfony\Component\Console\Command\Command;
18
use Symfony\Component\Console\Helper\ProgressBar;
19
use Symfony\Component\Console\Input\InputInterface;
20
use Symfony\Component\Console\Input\InputOption;
21
use Symfony\Component\Console\Output\OutputInterface;
22
use Symfony\Component\Process\Process;
23
use Symfony\Component\Stopwatch\Stopwatch;
24
25
/**
26
 * @testClass PHPChunkit\Test\Command\RunTest
27
 */
28
class Run implements CommandInterface
29
{
30
    const NAME = 'run';
31
32
    /**
33
     * @var TestRunner
34
     */
35
    private $testRunner;
36
37
    /**
38
     * @var Configuration
39
     */
40
    private $configuration;
41
42
    /**
43
     * @var TestChunker
44
     */
45
    private $testChunker;
46
47
    /**
48
     * @var TestFinder
49
     */
50
    private $testFinder;
51
52
    /**
53
     * @var InputInterface
54
     */
55
    private $input;
56
57
    /**
58
     * @var OutputInterface
59
     */
60
    private $output;
61
62
    /**
63
     * @var Stopwatch
64
     */
65
    private $stopwatch;
66
67
    /**
68
     * @var bool
69
     */
70
    private $verbose = false;
71
72
    /**
73
     * @var bool
74
     */
75
    private $parallel = false;
76
77
    /**
78
     * @var null|integer
79
     */
80
    private $chunk = null;
81
82
    /**
83
     * @var ChunkedTests
84
     */
85
    private $chunkedTests;
86
87
    /**
88
     * @var integer
89
     */
90
    private $numParallelProcesses = 1;
91
92
    /**
93
     * @var ChunkResults
94
     */
95
    private $chunkResults;
96
97
    /**
98
     * @var Processes
99
     */
100
    private $processes;
101
102 1
    public function __construct(
103
        TestRunner $testRunner,
104
        Configuration $configuration,
105
        TestChunker $testChunker,
106
        TestFinder $testFinder)
107
    {
108 1
        $this->testRunner = $testRunner;
109 1
        $this->configuration = $configuration;
110 1
        $this->testChunker = $testChunker;
111 1
        $this->testFinder = $testFinder;
112 1
    }
113
114
    public function getName() : string
115
    {
116
        return self::NAME;
117
    }
118
119
    public function configure(Command $command)
120
    {
121
        $command
122
            ->setDescription('Run tests.')
123
            ->addOption('debug', null, InputOption::VALUE_NONE, 'Run tests in debug mode.')
124
            ->addOption('memory-limit', null, InputOption::VALUE_REQUIRED, 'Memory limit for PHP.')
125
            ->addOption('stop', null, InputOption::VALUE_NONE, 'Stop on failure or error.')
126
            ->addOption('failed', null, InputOption::VALUE_NONE, 'Track tests that have failed.')
127
            ->addOption('create-dbs', null, InputOption::VALUE_NONE, 'Create the test databases before running tests.')
128
            ->addOption('sandbox', null, InputOption::VALUE_NONE, 'Configure unique names.')
129
            ->addOption('chunk', null, InputOption::VALUE_REQUIRED, 'Run a specific chunk of tests.')
130
            ->addOption('num-chunks', null, InputOption::VALUE_REQUIRED, 'The number of chunks to run tests in.')
131
            ->addOption('group', null, InputOption::VALUE_REQUIRED | InputOption::VALUE_IS_ARRAY, 'Run all tests in these groups.')
132
            ->addOption('exclude-group', null, InputOption::VALUE_REQUIRED | InputOption::VALUE_IS_ARRAY, 'Run all tests excluding these groups.')
133
            ->addOption('changed', null, InputOption::VALUE_NONE, 'Run changed tests.')
134
            ->addOption('parallel', null, InputOption::VALUE_REQUIRED, 'Run test chunks in parallel.')
135
            ->addOption('filter', null, InputOption::VALUE_REQUIRED | InputOption::VALUE_IS_ARRAY, 'Filter tests by path/file name and run them.')
136
            ->addOption('contains', null, InputOption::VALUE_REQUIRED | InputOption::VALUE_IS_ARRAY, 'Run tests that match the given content.')
137
            ->addOption('not-contains', null, InputOption::VALUE_REQUIRED | InputOption::VALUE_IS_ARRAY, 'Run tests that do not match the given content.')
138
            ->addOption('file', null, InputOption::VALUE_REQUIRED | InputOption::VALUE_IS_ARRAY, 'Run test file.')
139
            ->addOption('phpunit-opt', null, InputOption::VALUE_REQUIRED, 'Pass through phpunit options.')
140
        ;
141
    }
142
143 1
    public function execute(InputInterface $input, OutputInterface $output)
144
    {
145 1
        $this->initialize($input, $output);
146
147 1
        $this->chunkedTests = $this->chunkRepository->getChunkedTests(
0 ignored issues
show
Bug introduced by
The property chunkRepository 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...
148 1
            $this->input
149
        );
150
151 1
        $this->chunkRunner = new ChunkRunner(
0 ignored issues
show
Bug introduced by
The property chunkRunner 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...
152 1
            $this->chunkedTests,
153 1
            $this->chunkResults,
154 1
            $this->testRunner,
155 1
            $this->processes,
156 1
            $this->input,
157 1
            $this->output,
158 1
            $this->verbose,
159 1
            $this->parallel
160
        );
161
162 1
        if (!$this->chunkedTests->hasTests()) {
163
            $this->output->writeln('<error>No tests found to run.</error>');
164
165
            return;
166
        }
167
168 1
        $this->outputHeader();
169
170 1
        $this->setupSandbox();
171
172 1
        $this->chunkRunner->runChunks();
173
174 1
        $this->outputFooter();
175
176 1
        return $this->chunkResults->hasFailed() ? 1 : 0;
177
    }
178
179 1
    private function initialize(InputInterface $input, OutputInterface $output)
180
    {
181 1
        $this->stopwatch = new Stopwatch();
182 1
        $this->stopwatch->start('Tests');
183
184 1
        $this->input = $input;
185 1
        $this->output = $output;
186 1
        $this->verbose = $output->getVerbosity() >= OutputInterface::VERBOSITY_VERBOSE;
187 1
        $this->numParallelProcesses = (int) $this->input->getOption('parallel');
188 1
        $this->parallel = $this->numParallelProcesses > 1;
189 1
        $this->stop = (bool) $this->input->getOption('stop');
0 ignored issues
show
Bug introduced by
The property stop 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...
190
191 1
        $this->chunkRepository = new ChunkRepository(
192 1
            $this->testFinder,
193 1
            $this->testChunker,
194 1
            $this->configuration
195
        );
196 1
        $this->chunkResults = new ChunkResults();
197 1
        $this->processes = new Processes(
198 1
            $this->chunkResults,
199 1
            $this->output,
200 1
            $this->numParallelProcesses,
201 1
            $this->verbose,
202 1
            $this->stop
203
        );
204 1
    }
205
206 1
    private function outputHeader()
207
    {
208 1
        $chunks = $this->chunkedTests->getChunks();
209 1
        $testsPerChunk = $this->chunkedTests->getTestsPerChunk();
210 1
        $totalTests = $this->chunkedTests->getTotalTests();
211 1
        $numChunks = $this->chunkedTests->getNumChunks();
212
213 1
        $this->output->writeln(sprintf('Total Tests: <info>%s</info>', $totalTests));
214 1
        $this->output->writeln(sprintf('Number of Chunks Configured: <info>%s</info>', $numChunks));
215 1
        $this->output->writeln(sprintf('Number of Chunks Produced: <info>%s</info>', count($chunks)));
216 1
        $this->output->writeln(sprintf('Tests Per Chunk: <info>~%s</info>', $testsPerChunk));
217
218 1
        if ($chunk = $this->chunkedTests->getChunk()) {
219
            $this->output->writeln(sprintf('Chunk: <info>%s</info>', $chunk));
220
        }
221
222 1
        $this->output->writeln('-----------');
223 1
        $this->output->writeln('');
224 1
    }
225
226 1
    private function setupSandbox()
227
    {
228 1
        if ($this->input->getOption('sandbox')) {
229 1
            $this->testRunner->runTestCommand('sandbox');
230
        }
231 1
    }
232
233 1
    private function outputFooter()
234
    {
235 1
        $chunks = $this->chunkedTests->getChunks();
236
237 1
        $failed = $this->chunkResults->hasFailed();
238
239 1
        $event = $this->stopwatch->stop('Tests');
240
241 1
        $this->output->writeln('');
242 1
        $this->output->writeln(sprintf('Time: %s seconds, Memory: %s',
243 1
            round($event->getDuration() / 1000, 2),
244 1
            $this->formatBytes($event->getMemory())
245
        ));
246
247 1
        $this->output->writeln('');
248 1
        $this->output->writeln(sprintf('%s (%s chunks, %s tests, %s assertions, %s failures%s)',
249 1
            $failed ? '<error>FAILED</error>' : '<info>PASSED</info>',
250
            count($chunks),
251 1
            $this->chunkResults->getTotalTestsRan(),
252 1
            $this->chunkResults->getNumAssertions(),
253 1
            $this->chunkResults->getNumFailures(),
254 1
            $failed ? sprintf(', Failed chunks: %s', $this->chunkResults->getNumChunkFailures()) : ''
255
        ));
256 1
    }
257
258 1
    private function formatBytes(int $size, int $precision = 2) : string
259
    {
260 1
        if (!$size) {
261
            return 0;
262
        }
263
264 1
        $base = log($size, 1024);
265 1
        $suffixes = ['', 'KB', 'MB', 'GB', 'TB'];
266
267 1
        return round(pow(1024, $base - floor($base)), $precision).$suffixes[floor($base)];
268
    }
269
}
270