Completed
Push — master ( 23e37f...b7a3e1 )
by Jonathan
02:43
created

TestRunner::run()   A

Complexity

Conditions 4
Paths 4

Size

Total Lines 20
Code Lines 10

Duplication

Lines 0
Ratio 0 %

Code Coverage

Tests 9
CRAP Score 4

Importance

Changes 0
Metric Value
dl 0
loc 20
ccs 9
cts 9
cp 1
rs 9.2
c 0
b 0
f 0
cc 4
eloc 10
nc 4
nop 4
crap 4
1
<?php
2
3
namespace PHPChunkit;
4
5
use RuntimeException;
6
use Symfony\Component\Console\Application;
7
use Symfony\Component\Console\Input\ArrayInput;
8
use Symfony\Component\Console\Input\InputInterface;
9
use Symfony\Component\Console\Output\Output;
10
use Symfony\Component\Console\Output\OutputInterface;
11
use Symfony\Component\Process\Process;
12
13
/**
14
 * @testClass PHPChunkit\Test\TestRunnerTest
15
 */
16
class TestRunner
17
{
18
    /**
19
     * @var Application
20
     */
21
    private $app;
22
23
    /**
24
     * @var InputInterface
25
     */
26
    private $input;
27
28
    /**
29
     * @var OutputInterface
30
     */
31
    private $output;
32
33
    /**
34
     * @var Configuration
35
     */
36
    private $configuration;
37
38
    /**
39
     * @param Application     $app
40
     * @param InputInterface  $input
41
     * @param OutputInterface $output
42
     * @param Configuration   $configuration
43
     */
44 6
    public function __construct(
45
        Application $app,
46
        InputInterface $input,
47
        OutputInterface $output,
48
        Configuration $configuration)
49
    {
50 6
        $this->app = $app;
51 6
        $this->input = $input;
52 6
        $this->output = $output;
53 6
        $this->configuration = $configuration;
54 6
    }
55
56
    /**
57
     * @return string
58
     */
59 1
    public function getRootDir()
60
    {
61 1
        return $this->configuration->getRootDir();
62
    }
63
64
    /**
65
     * @param [] $files
0 ignored issues
show
Documentation introduced by
The doc-type [] could not be parsed: Unknown type name "" at position 0. [(view supported doc-types)

This check marks PHPDoc comments that could not be parsed by our parser. To see which comment annotations we can parse, please refer to our documentation on supported doc-types.

Loading history...
66
     *
67
     * @return string $path
68
     */
69 1
    public function generatePhpunitXml(array &$files)
70
    {
71
        // load the config into memory
72 1
        $phpunitXmlDistPath = is_file($file = $this->getRootDir().'/phpunit.xml') ? $file : $this->getRootDir().'/phpunit.xml.dist';
73 1
        $src = file_get_contents($phpunitXmlDistPath);
74 1
        $xml = simplexml_load_string(str_replace('./', $this->getRootDir().'/', $src));
75
76
        // temp config file
77 1
        $config = tempnam('/tmp', 'phpunitxml');
78
        register_shutdown_function(function() use ($config) {
79
            unlink($config);
80 1
        });
81
82 1
        unset($xml->testsuites[0]->testsuite);
83 1
        $suite = $xml->testsuites[0]->addChild('testsuite');
84
85 1
        if ($files) {
0 ignored issues
show
Bug Best Practice introduced by
The expression $files of type array is implicitly converted to a boolean; are you sure this is intended? If so, consider using ! empty($expr) instead to make it clear that you intend to check for an array without elements.

This check marks implicit conversions of arrays to boolean values in a comparison. While in PHP an empty array is considered to be equal (but not identical) to false, this is not always apparent.

Consider making the comparison explicit by using empty(..) or ! empty(...) instead.

Loading history...
86 1
            $files = array_unique($files);
87
88 1
            foreach ($files as $file) {
89 1
                $path = strpos($file, '/') === 0
90
                    ? $file
91 1
                    : $this->getRootDir().'/'.$file;
92
93 1
                $suite->addChild('file', $path);
94
            }
95
96 1
            file_put_contents($config, $xml->asXml());
97
98 1
            return $config;
99
        }
100
    }
101
102
    /**
103
     * @param array $files
104
     * @param array $env
105
     *
106
     * @return int $code
107
     */
108 1
    public function runTestFiles(array $files, array $env = [])
109
    {
110 1
        $config = $this->generatePhpunitXml($files);
111
112 1
        if ($config) {
0 ignored issues
show
Bug Best Practice introduced by
The expression $config of type string|null is loosely compared to true; this is ambiguous if the string can be empty. You might want to explicitly use !== null instead.

In PHP, under loose comparison (like ==, or !=, or switch conditions), values of different types might be equal.

For string values, the empty string '' is a special case, in particular the following results might be unexpected:

''   == false // true
''   == null  // true
'ab' == false // false
'ab' == null  // false

// It is often better to use strict comparison
'' === false // false
'' === null  // false
Loading history...
113 1
            $this->output->writeln('');
114
115 1
            foreach ($files as $file) {
116 1
                $this->output->writeln(sprintf(' - Executing <comment>%s</comment>', $file));
117
            }
118
119 1
            $this->output->writeln('');
120
121 1
            return $this->runPhpunit(sprintf('-c %s', escapeshellarg($config)), $env);
122
        } else {
123
            $this->output->writeln('No tests to run.');
124
        }
125
    }
126
127
    /**
128
     * @param string $command
129
     * @param array  $env
130
     *
131
     * @return int
132
     */
133 1 View Code Duplication
    public function runPhpunit($command, array $env = [], \Closure $callback = null)
0 ignored issues
show
Duplication introduced by
This method seems to be duplicated in 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...
134
    {
135 1
        $command = sprintf('%s %s %s',
136 1
            $this->configuration->getPhpunitPath(),
137
            $command,
138 1
            $this->flags($this->input, $this->output)
0 ignored issues
show
Unused Code introduced by
The call to TestRunner::flags() has too many arguments starting with $this->input.

This check compares calls to functions or methods with their respective definitions. If the call has more arguments than are defined, it raises an issue.

If a function is defined several times with a different number of parameters, the check may pick up the wrong definition and report false positives. One codebase where this has been known to happen is Wordpress.

In this case you can add the @ignore PhpDoc annotation to the duplicate definition and it will be ignored.

Loading history...
139
        );
140
141 1
        return $this->run($command, false, $env, $callback);
142
    }
143
144
    /**
145
     * @param string $command
146
     * @param array  $env
147
     *
148
     * @return Process
149
     */
150 View Code Duplication
    public function getPhpunitProcess($command, array $env = []) : Process
0 ignored issues
show
Duplication introduced by
This method seems to be duplicated in 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...
151
    {
152
        $command = sprintf('%s %s %s',
153
            $this->configuration->getPhpunitPath(),
154
            $command,
155
            $this->flags($this->input, $this->output)
0 ignored issues
show
Unused Code introduced by
The call to TestRunner::flags() has too many arguments starting with $this->input.

This check compares calls to functions or methods with their respective definitions. If the call has more arguments than are defined, it raises an issue.

If a function is defined several times with a different number of parameters, the check may pick up the wrong definition and report false positives. One codebase where this has been known to happen is Wordpress.

In this case you can add the @ignore PhpDoc annotation to the duplicate definition and it will be ignored.

Loading history...
156
        );
157
158
        return $this->getProcess($command, $env);
159
    }
160
161
    /**
162
     * @param string $command
163
     * @param bool   $throw
164
     * @param []     $env
0 ignored issues
show
Documentation introduced by
The doc-type [] could not be parsed: Unknown type name "" at position 0. [(view supported doc-types)

This check marks PHPDoc comments that could not be parsed by our parser. To see which comment annotations we can parse, please refer to our documentation on supported doc-types.

Loading history...
165
     *
166
     * @return int
167
     */
168 2
    public function run($command, $throw = true, array $env = [], \Closure $callback = null)
169
    {
170 2
        $process = $this->getProcess($command, $env);
171
172 2
        if ($callback === null) {
173
            $callback = function($output) {
174
                echo $output;
175 2
            };
176
        }
177
178 2
        $process->run(function($type, $output) use ($callback) {
179
            $callback($output);
180 2
        });
181
182 2
        if ($process->getExitCode() && $throw) {
0 ignored issues
show
Bug Best Practice introduced by
The expression $process->getExitCode() of type integer|null is loosely compared to true; this is ambiguous if the integer can be zero. You might want to explicitly use !== null instead.

In PHP, under loose comparison (like ==, or !=, or switch conditions), values of different types might be equal.

For integer values, zero is a special case, in particular the following results might be unexpected:

0   == false // true
0   == null  // true
123 == false // false
123 == null  // false

// It is often better to use strict comparison
0 === false // false
0 === null  // false
Loading history...
183 1
            throw new RuntimeException('The command did not exit successfully.');
184
        }
185
186 1
        return $process->getExitCode();
187
    }
188
189
    /**
190
     * @param string $command
191
     */
192 2
    public function getProcess($command, array $env = []) : Process
193
    {
194 2
        foreach ($env as $key => $value) {
195
            $command = sprintf('export %s=%s && %s', $key, $value, $command);
196
        }
197
198 2
        if ($this->output->getVerbosity() >= OutputInterface::VERBOSITY_VERBOSE) {
199
            $this->output->writeln('+ <comment>'.$command.'</comment>');
200
        }
201
202 2
        return $this->createProcess($command);
203
    }
204
205
    /**
206
     * @param string $command
207
     * @param array  $input
208
     *
209
     * @return int
210
     */
211 1
    public function runTestCommand($command, array $input = []) : int
212
    {
213 1
        $input['command'] = $command;
214
215 1
        return $this->app->find($command)
216 1
            ->run(new ArrayInput($input), $this->output);
217
    }
218
219
    /**
220
     * @return string
221
     */
222 1
    private function flags()
223
    {
224 1
        $memoryLimit = $this->input->getOption('memory-limit')
225 1
            ?: $this->configuration->getMemoryLimit();
226
227 1
        $flags = '-d memory_limit='.escapeshellarg($memoryLimit);
228
229 1
        if ($this->input->getOption('stop')) {
230
            $flags .= ' --stop-on-failure --stop-on-error';
231
        }
232
233 1
        if ($this->output->getVerbosity() > Output::VERBOSITY_NORMAL) {
234
            $flags .= ' --verbose';
235
        }
236
237 1
        if ($this->input->getOption('debug')) {
238
            $flags .= ' --debug';
239
        }
240
241 1
        if ($this->output->isDecorated()) {
242
            $flags .= ' --colors';
243
        }
244
245 1
        if ($this->input->hasOption('phpunit-opt')
246 1
            && $phpunitOptions = $this->input->getOption('phpunit-opt')) {
247
            $flags .= ' '.$phpunitOptions;
248
        }
249
250 1
        return $flags;
251
    }
252
253
    /**
254
     * @param string
255
     * @param string $command
256
     *
257
     * @return Process
258
     */
259
    protected function createProcess($command)
260
    {
261
        return new Process($command, null, null, null, null);
262
    }
263
}
264