PHPUnit   A
last analyzed

Complexity

Total Complexity 30

Size/Duplication

Total Lines 218
Duplicated Lines 0 %

Coupling/Cohesion

Components 2
Dependencies 6

Test Coverage

Coverage 66.25%

Importance

Changes 0
Metric Value
wmc 30
lcom 2
cbo 6
dl 0
loc 218
ccs 53
cts 80
cp 0.6625
rs 10
c 0
b 0
f 0

10 Methods

Rating   Name   Duplication   Size   Complexity  
A configure() 0 17 1
A hasPath() 0 7 2
A hasConfig() 0 4 1
A getConfig() 0 16 4
A getRunnerOptions() 0 17 3
A requireBootstrap() 0 13 3
A scopedRequire() 0 6 1
A hasCoverage() 0 7 3
A getBootstrapFile() 0 15 4
C execute() 0 27 8
1
<?php
2
3
declare(strict_types=1);
4
5
namespace ParaTest\Console\Testers;
6
7
use ParaTest\Runners\PHPUnit\Configuration;
8
use ParaTest\Runners\PHPUnit\Runner;
9
use ParaTest\Runners\PHPUnit\WrapperRunner;
10
use Symfony\Component\Console\Command\Command;
11
use Symfony\Component\Console\Input\InputArgument;
12
use Symfony\Component\Console\Input\InputInterface;
13
use Symfony\Component\Console\Input\InputOption;
14
use Symfony\Component\Console\Output\OutputInterface;
15
16
/**
17
 * Class PHPUnit.
18
 *
19
 * Creates the interface for PHPUnit testing
20
 */
21
class PHPUnit extends Tester
22
{
23
    /**
24
     * @var \ParaTest\Console\Commands\ParaTestCommand
25
     */
26
    protected $command;
27
28
    /**
29
     * Configures the ParaTestCommand with PHPUnit specific
30
     * definitions.
31
     *
32
     * @param Command $command
33
     */
34 8
    public function configure(Command $command)
35
    {
36
        $command
37 8
            ->addOption('phpunit', null, InputOption::VALUE_REQUIRED, 'The PHPUnit binary to execute. <comment>(default: vendor/bin/phpunit)</comment>')
38 8
            ->addOption('runner', null, InputOption::VALUE_REQUIRED, 'Runner or WrapperRunner. <comment>(default: Runner)</comment>')
39 8
            ->addOption('bootstrap', null, InputOption::VALUE_REQUIRED, 'The bootstrap file to be used by PHPUnit.')
40 8
            ->addOption('configuration', 'c', InputOption::VALUE_REQUIRED, 'The PHPUnit configuration file to use.')
41 8
            ->addOption('group', 'g', InputOption::VALUE_REQUIRED, 'Only runs tests from the specified group(s).')
42 8
            ->addOption('exclude-group', null, InputOption::VALUE_REQUIRED, 'Don\'t run tests from the specified group(s).')
43 8
            ->addOption('stop-on-failure', null, InputOption::VALUE_NONE, 'Don\'t start any more processes after a failure.')
44 8
            ->addOption('log-junit', null, InputOption::VALUE_REQUIRED, 'Log test execution in JUnit XML format to file.')
45 8
            ->addOption('colors', null, InputOption::VALUE_NONE, 'Displays a colored bar as a test result.')
46 8
            ->addOption('testsuite', null, InputOption::VALUE_OPTIONAL, 'Filter which testsuite to run')
47 8
            ->addArgument('path', InputArgument::OPTIONAL, 'The path to a directory or file containing tests. <comment>(default: current directory)</comment>')
48 8
            ->addOption('path', null, InputOption::VALUE_REQUIRED, 'An alias for the path argument.');
49 8
        $this->command = $command;
0 ignored issues
show
Documentation Bug introduced by
$command is of type object<Symfony\Component\Console\Command\Command>, but the property $command was declared to be of type object<ParaTest\Console\Commands\ParaTestCommand>. Are you sure that you always receive this specific sub-class here, or does it make sense to add an instanceof check?

Our type inference engine has found a suspicous assignment of a value to a property. This check raises an issue when a value that can be of a given class or a super-class is assigned to a property that is type hinted more strictly.

Either this assignment is in error or an instanceof check should be added for that assignment.

class Alien {}

class Dalek extends Alien {}

class Plot
{
    /** @var  Dalek */
    public $villain;
}

$alien = new Alien();
$plot = new Plot();
if ($alien instanceof Dalek) {
    $plot->villain = $alien;
}
Loading history...
50 8
    }
51
52
    /**
53
     * Executes the PHPUnit Runner. Will Display help if no config and no path
54
     * supplied.
55
     *
56
     * @param InputInterface  $input
57
     * @param OutputInterface $output
58
     *
59
     * @return int|mixed
60
     */
61
    public function execute(InputInterface $input, OutputInterface $output)
62
    {
63
        if (!$this->hasConfig($input) && !$this->hasPath($input)) {
64
            $this->displayHelp($input, $output);
65
        }
66
67
        if ($input->getOption('runner') === 'WrapperRunner') {
68
            $runner = new WrapperRunner($this->getRunnerOptions($input));
69
        } else {
70
            if ($input->getOption('runner') !== '') {
71
                // because we want to have to bootstrap script inherited before check/initialization
72
                $runnerOption = $this->getRunnerOptions($input);
73
                $runnerClass = $input->getOption('runner');
74
                if (null !== $runnerClass && class_exists($runnerClass)) {
75
                    $runner = new $runnerClass($runnerOption);
76
                }
77
            }
78
        }
79
80
        if (!isset($runner)) {
81
            $runner = new Runner($this->getRunnerOptions($input));
82
        }
83
84
        $runner->run();
85
86
        return $runner->getExitCode();
87
    }
88
89
    /**
90
     * Returns whether or not a test path has been supplied
91
     * via option or regular input.
92
     *
93
     * @param InputInterface $input
94
     *
95
     * @return bool
96
     */
97
    protected function hasPath(InputInterface $input)
98
    {
99
        $argument = $input->getArgument('path');
100
        $option = $input->getOption('path');
101
102
        return $argument || $option;
103
    }
104
105
    /**
106
     * Is there a PHPUnit xml configuration present.
107
     *
108
     * @param InputInterface $input
109
     *
110
     * @return bool
111
     */
112 4
    protected function hasConfig(InputInterface $input): bool
113
    {
114 4
        return false !== $this->getConfig($input);
115
    }
116
117
    /**
118
     * @param \Symfony\Component\Console\Input\InputInterface $input
119
     *
120
     * @return \ParaTest\Runners\PHPUnit\Configuration|bool
121
     */
122 4
    protected function getConfig(InputInterface $input)
123
    {
124 4
        $cwd = getcwd() . DIRECTORY_SEPARATOR;
125
126 4
        if ($input->getOption('configuration')) {
127
            $configFilename = $input->getOption('configuration');
128 4
        } elseif (file_exists($cwd . 'phpunit.xml.dist')) {
129 4
            $configFilename = $cwd . 'phpunit.xml.dist';
130
        } elseif (file_exists($cwd . 'phpunit.xml')) {
131
            $configFilename = $cwd . 'phpunit.xml';
132
        } else {
133
            return false;
134
        }
135
136 4
        return new Configuration($configFilename);
137
    }
138
139
    /**
140
     * @param \Symfony\Component\Console\Input\InputInterface $input
141
     *
142
     * @throws \RuntimeException
143
     *
144
     * @return array
145
     */
146 4
    public function getRunnerOptions(InputInterface $input): array
147
    {
148 4
        $path = $input->getArgument('path');
149 4
        $options = $this->getOptions($input);
150 4
        $bootstrap = $this->getBootstrapFile($input, $options);
151 4
        $this->requireBootstrap($bootstrap);
152
153 4
        if ($this->hasCoverage($options)) {
154 2
            $options['coverage-php'] = tempnam(sys_get_temp_dir(), 'paratest_');
155
        }
156
157 4
        if ($path) {
158 4
            $options = array_merge(['path' => $path], $options);
159
        }
160
161 4
        return $options;
162
    }
163
164
    /**
165
     * Require the bootstrap. If the file is specified, but does not exist
166
     * then an exception will be raised.
167
     *
168
     * @param $file
169
     *
170
     * @throws \RuntimeException
171
     */
172 5
    public function requireBootstrap(string $file)
173
    {
174 5
        if (!$file) {
175
            return;
176
        }
177
178 5
        if (!file_exists($file)) {
179
            $message = sprintf('Bootstrap specified but could not be found (%s)', $file);
180
            throw new \RuntimeException($message);
181
        }
182
183 5
        $this->scopedRequire($file);
184 5
    }
185
186
    /**
187
     * This function limits the scope of a required file
188
     * so that variables defined in it do not break
189
     * this object's configuration.
190
     *
191
     * @param mixed $file
192
     */
193 5
    protected function scopedRequire(string $file)
194
    {
195 5
        $cwd = getcwd();
196 5
        require_once $file;
197 5
        chdir($cwd);
198 5
    }
199
200
    /**
201
     * Return whether or not code coverage information should be collected.
202
     *
203
     * @param $options
204
     *
205
     * @return bool
206
     */
207 4
    protected function hasCoverage(array $options): bool
208
    {
209 4
        $isFileFormat = isset($options['coverage-html']) || isset($options['coverage-clover']);
210 4
        $isPHP = isset($options['coverage-php']);
211
212 4
        return $isFileFormat && !$isPHP;
213
    }
214
215
    /**
216
     * Fetch the path to the bootstrap file.
217
     *
218
     * @param InputInterface $input
219
     * @param array          $options
220
     *
221
     * @return string
222
     */
223 4
    protected function getBootstrapFile(InputInterface $input, array $options): string
224
    {
225 4
        if (isset($options['bootstrap'])) {
226
            return $options['bootstrap'];
227
        }
228
229 4
        if (!$this->hasConfig($input)) {
230
            return '';
231
        }
232
233 4
        $config = $this->getConfig($input);
234 4
        $bootstrap = $config->getBootstrap();
235
236 4
        return ($bootstrap) ? $config->getConfigDir() . $bootstrap : '';
237
    }
238
}
239