Options   A
last analyzed

Complexity

Total Complexity 31

Size/Duplication

Total Lines 318
Duplicated Lines 0 %

Coupling/Cohesion

Components 2
Dependencies 1

Test Coverage

Coverage 94.25%

Importance

Changes 0
Metric Value
wmc 31
lcom 2
cbo 1
dl 0
loc 318
ccs 82
cts 87
cp 0.9425
rs 9.8
c 0
b 0
f 0

11 Methods

Rating   Name   Duplication   Size   Complexity  
D __construct() 0 37 9
A __get() 0 4 1
A __isset() 0 4 1
A defaults() 0 16 1
A phpunit() 0 15 4
A vendorDir() 0 9 2
A filterOptions() 0 21 2
A getConfigurationPath() 0 8 2
A getDefaultConfigurationForPath() 0 17 4
A initAnnotations() 0 9 3
A isFile() 0 4 2
1
<?php
2
3
declare(strict_types=1);
4
5
namespace ParaTest\Runners\PHPUnit;
6
7
/**
8
 * Class Options.
9
 *
10
 * An object containing all configurable information used
11
 * to run PHPUnit via ParaTest
12
 */
13
class Options
14
{
15
    /**
16
     * The number of processes to run at a time.
17
     *
18
     * @var int
19
     */
20
    protected $processes;
21
22
    /**
23
     * The test path pointing to tests that will
24
     * be run.
25
     *
26
     * @var string
27
     */
28
    protected $path;
29
30
    /**
31
     * The path to the PHPUnit binary that will be run.
32
     *
33
     * @var string
34
     */
35
    protected $phpunit;
36
37
    /**
38
     * Determines whether or not ParaTest runs in
39
     * functional mode. If enabled, ParaTest will run
40
     * every test method in a separate process.
41
     *
42
     * @var string
43
     */
44
    protected $functional;
45
46
    /**
47
     * Prevents starting new tests after a test has failed.
48
     *
49
     * @var bool
50
     */
51
    protected $stopOnFailure;
52
53
    /**
54
     * A collection of post-processed option values. This is the collection
55
     * containing ParaTest specific options.
56
     *
57
     * @var array
58
     */
59
    protected $filtered;
60
61
    /**
62
     * @var string
63
     */
64
    protected $runner;
65
66
    /**
67
     * @var bool
68
     */
69
    protected $noTestTokens;
70
71
    /**
72
     * @var bool
73
     */
74
    protected $colors;
75
76
    /**
77
     * Filters which tests to run.
78
     *
79
     * @var string|null
80
     */
81
    protected $testsuite;
82
83
    /**
84
     * @var int|null
85
     */
86
    protected $maxBatchSize;
87
88
    /**
89
     * @var string
90
     */
91
    protected $filter;
92
93
    /**
94
     * @var string[]
95
     */
96
    protected $groups;
97
98
    /**
99
     * @var string[]
100
     */
101
    protected $excludeGroups;
102
103
    /**
104
     * A collection of option values directly corresponding
105
     * to certain annotations - i.e group.
106
     *
107
     * @var array
108
     */
109
    protected $annotations = [];
110
111 42
    public function __construct(array $opts = [])
112
    {
113 42
        foreach (self::defaults() as $opt => $value) {
114 42
            $opts[$opt] = $opts[$opt] ?? $value;
115
        }
116
117 42
        $this->processes = $opts['processes'];
118 42
        $this->path = $opts['path'];
119 42
        $this->phpunit = $opts['phpunit'];
120 42
        $this->functional = $opts['functional'];
121 42
        $this->stopOnFailure = $opts['stop-on-failure'];
122 42
        $this->runner = $opts['runner'];
123 42
        $this->noTestTokens = $opts['no-test-tokens'];
124 42
        $this->colors = $opts['colors'];
125 42
        $this->testsuite = $opts['testsuite'];
126 42
        $this->maxBatchSize = (int) $opts['max-batch-size'];
127 42
        $this->filter = $opts['filter'];
128
129
        // we need to register that options if they are blank but do not get them as
130
        // key with null value in $this->filtered as it will create problems for
131
        // phpunit command line generation (it will add them in command line with no value
132
        // and it's wrong because group and exclude-group options require value when passed
133
        // to phpunit)
134 42
        $this->groups = isset($opts['group']) && $opts['group'] !== ''
135 14
                      ? explode(',', $opts['group'])
136 30
                      : [];
137 42
        $this->excludeGroups = isset($opts['exclude-group']) && $opts['exclude-group'] !== ''
138
                             ? explode(',', $opts['exclude-group'])
139 42
                             : [];
140
141 42
        if (isset($opts['filter']) && strlen($opts['filter']) > 0 && !$this->functional) {
142
            throw new \RuntimeException('Option --filter is not implemented for non functional mode');
143
        }
144
145 42
        $this->filtered = $this->filterOptions($opts);
146 42
        $this->initAnnotations();
147 42
    }
148
149
    /**
150
     * Public read accessibility.
151
     *
152
     * @param string $var
153
     *
154
     * @return mixed
155
     */
156 40
    public function __get(string $var)
157
    {
158 40
        return $this->{$var};
159
    }
160
161
    /**
162
     * Public read accessibility
163
     * (e.g. to make empty($options->property) work as expected).
164
     *
165
     * @param string $var
166
     *
167
     * @return mixed
168
     */
169 23
    public function __isset(string $var): bool
170
    {
171 23
        return isset($this->{$var});
172
    }
173
174
    /**
175
     * Returns a collection of ParaTest's default
176
     * option values.
177
     *
178
     * @return array
179
     */
180 42
    protected static function defaults(): array
181
    {
182
        return [
183 42
            'processes' => 5,
184 42
            'path' => '',
185 42
            'phpunit' => static::phpunit(),
186
            'functional' => false,
187
            'stop-on-failure' => false,
188 42
            'runner' => 'Runner',
189
            'no-test-tokens' => false,
190
            'colors' => false,
191 42
            'testsuite' => '',
192 42
            'max-batch-size' => 0,
193
            'filter' => null,
194
        ];
195
    }
196
197
    /**
198
     * Get the path to phpunit
199
     * First checks if a Windows batch script is in the composer vendors directory.
200
     * Composer automatically handles creating a .bat file, so if on windows this should be the case.
201
     * Second look for the phpunit binary under nix
202
     * Defaults to phpunit on the users PATH.
203
     *
204
     * @return string $phpunit the path to phpunit
205
     */
206 42
    protected static function phpunit(): string
207
    {
208 42
        $vendor = static::vendorDir();
209
210 42
        $phpunit = $vendor . DIRECTORY_SEPARATOR . 'bin' . DIRECTORY_SEPARATOR . 'phpunit';
211 42
        $batch = $phpunit . '.bat';
212
213 42
        if (DIRECTORY_SEPARATOR === '\\' && file_exists($batch)) {
214
            return $phpunit . '.bat';
215 42
        } elseif (file_exists($phpunit)) {
216 42
            return $phpunit;
217
        }
218
219
        return 'phpunit';
220
    }
221
222
    /**
223
     * Get the path to the vendor directory
224
     * First assumes vendor directory is accessible from src (i.e development)
225
     * Second assumes vendor directory is accessible within src.
226
     */
227 42
    protected static function vendorDir(): string
228
    {
229 42
        $vendor = dirname(dirname(dirname(__DIR__))) . DIRECTORY_SEPARATOR . 'vendor';
230 42
        if (!file_exists($vendor)) {
231
            $vendor = dirname(dirname(dirname(dirname(dirname(__DIR__)))));
232
        }
233
234 42
        return $vendor;
235
    }
236
237
    /**
238
     * Filter options to distinguish between paratest
239
     * internal options and any other options.
240
     */
241 42
    protected function filterOptions(array $options): array
242
    {
243 42
        $filtered = array_diff_key($options, [
244 42
            'processes' => $this->processes,
245 42
            'path' => $this->path,
246 42
            'phpunit' => $this->phpunit,
247 42
            'functional' => $this->functional,
248 42
            'stop-on-failure' => $this->stopOnFailure,
249 42
            'runner' => $this->runner,
250 42
            'no-test-tokens' => $this->noTestTokens,
251 42
            'colors' => $this->colors,
252 42
            'testsuite' => $this->testsuite,
253 42
            'max-batch-size' => $this->maxBatchSize,
254 42
            'filter' => $this->filter,
255
        ]);
256 42
        if ($configuration = $this->getConfigurationPath($filtered)) {
257 42
            $filtered['configuration'] = new Configuration($configuration);
258
        }
259
260 42
        return $filtered;
261
    }
262
263
    /**
264
     * Take an array of filtered options and return a
265
     * configuration path.
266
     *
267
     * @param $filtered
268
     *
269
     * @return string|null
270
     */
271 42
    protected function getConfigurationPath(array $filtered)
272
    {
273 42
        if (isset($filtered['configuration'])) {
274 18
            return $this->getDefaultConfigurationForPath($filtered['configuration'], $filtered['configuration']);
275
        }
276
277 28
        return $this->getDefaultConfigurationForPath();
278
    }
279
280
    /**
281
     * Retrieve the default configuration given a path (directory or file).
282
     * This will search into the directory, if a directory is specified.
283
     *
284
     * @param string $path    The path to search into
285
     * @param string $default The default value to give back
286
     *
287
     * @return string|null
288
     */
289 42
    private function getDefaultConfigurationForPath(string $path = '.', string $default = null)
290
    {
291 42
        if ($this->isFile($path)) {
292 15
            return realpath($path);
293
        }
294
295 28
        $path = rtrim($path, DIRECTORY_SEPARATOR) . DIRECTORY_SEPARATOR;
296 28
        $suffixes = ['phpunit.xml', 'phpunit.xml.dist'];
297
298 28
        foreach ($suffixes as $suffix) {
299 28
            if ($this->isFile($path . $suffix)) {
300 28
                return realpath($path . $suffix);
301
            }
302
        }
303
304 4
        return $default;
305
    }
306
307
    /**
308
     * Load options that are represented by annotations
309
     * inside of tests i.e @group group1 = --group group1.
310
     */
311 42
    protected function initAnnotations()
312
    {
313 42
        $annotatedOptions = ['group'];
314 42
        foreach ($this->filtered as $key => $value) {
315 42
            if (array_search($key, $annotatedOptions, true) !== false) {
316 42
                $this->annotations[$key] = $value;
317
            }
318
        }
319 42
    }
320
321
    /**
322
     * @param $file
323
     *
324
     * @return bool
325
     */
326 42
    private function isFile(string $file): bool
327
    {
328 42
        return file_exists($file) && !is_dir($file);
329
    }
330
}
331