Run   A
last analyzed

Complexity

Total Complexity 16

Size/Duplication

Total Lines 252
Duplicated Lines 0 %

Coupling/Cohesion

Components 1
Dependencies 11

Test Coverage

Coverage 76.35%

Importance

Changes 0
Metric Value
wmc 16
lcom 1
cbo 11
dl 0
loc 252
ccs 84
cts 110
cp 0.7635
rs 10
c 0
b 0
f 0

9 Methods

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