Completed
Push — master ( 4ffa9b...a4fcf1 )
by Julian
12s
created

Options::__construct()   D

Complexity

Conditions 9
Paths 64

Size

Total Lines 37
Code Lines 22

Duplication

Lines 0
Ratio 0 %

Code Coverage

Tests 23
CRAP Score 9.0414

Importance

Changes 0
Metric Value
c 0
b 0
f 0
dl 0
loc 37
ccs 23
cts 25
cp 0.92
rs 4.909
cc 9
eloc 22
nc 64
nop 1
crap 9.0414
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($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 = $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($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($var)
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()
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()
207
    {
208 42
        $vendor = static::vendorDir();
209 42
        $phpunit = $vendor . DIRECTORY_SEPARATOR . 'bin' . DIRECTORY_SEPARATOR . 'phpunit';
210 42
        $batch = $phpunit . '.bat';
211
212 42
        if (file_exists($batch)) {
213
            return $batch;
214
        }
215
216 42
        if (file_exists($phpunit)) {
217 42
            return $phpunit;
218
        }
219
220
        return 'phpunit';
221
    }
222
223
    /**
224
     * Get the path to the vendor directory
225
     * First assumes vendor directory is accessible from src (i.e development)
226
     * Second assumes vendor directory is accessible within src.
227
     */
228 42
    protected static function vendorDir()
229
    {
230 42
        $vendor = dirname(dirname(dirname(__DIR__))) . DIRECTORY_SEPARATOR . 'vendor';
231 42
        if (!file_exists($vendor)) {
232
            $vendor = dirname(dirname(dirname(dirname(dirname(__DIR__)))));
233
        }
234
235 42
        return $vendor;
236
    }
237
238
    /**
239
     * Filter options to distinguish between paratest
240
     * internal options and any other options.
241
     */
242 42
    protected function filterOptions(array $options): array
243
    {
244 42
        $filtered = array_diff_key($options, [
245 42
            'processes' => $this->processes,
246 42
            'path' => $this->path,
247 42
            'phpunit' => $this->phpunit,
248 42
            'functional' => $this->functional,
249 42
            'stop-on-failure' => $this->stopOnFailure,
250 42
            'runner' => $this->runner,
251 42
            'no-test-tokens' => $this->noTestTokens,
252 42
            'colors' => $this->colors,
253 42
            'testsuite' => $this->testsuite,
254 42
            'max-batch-size' => $this->maxBatchSize,
255 42
            'filter' => $this->filter,
256
        ]);
257 42
        if ($configuration = $this->getConfigurationPath($filtered)) {
258 42
            $filtered['configuration'] = new Configuration($configuration);
259
        }
260
261 42
        return $filtered;
262
    }
263
264
    /**
265
     * Take an array of filtered options and return a
266
     * configuration path.
267
     *
268
     * @param $filtered
269
     *
270
     * @return string|null
271
     */
272 42
    protected function getConfigurationPath($filtered)
273
    {
274 42
        if (isset($filtered['configuration'])) {
275 18
            return $this->getDefaultConfigurationForPath($filtered['configuration'], $filtered['configuration']);
276
        }
277
278 28
        return $this->getDefaultConfigurationForPath();
279
    }
280
281
    /**
282
     * Retrieve the default configuration given a path (directory or file).
283
     * This will search into the directory, if a directory is specified.
284
     *
285
     * @param string $path    The path to search into
286
     * @param string $default The default value to give back
287
     *
288
     * @return string|null
289
     */
290 42
    private function getDefaultConfigurationForPath($path = '.', $default = null)
291
    {
292 42
        if ($this->isFile($path)) {
293 15
            return realpath($path);
294
        }
295
296 28
        $path = rtrim($path, DIRECTORY_SEPARATOR) . DIRECTORY_SEPARATOR;
297 28
        $suffixes = ['phpunit.xml', 'phpunit.xml.dist'];
298
299 28
        foreach ($suffixes as $suffix) {
300 28
            if ($this->isFile($path . $suffix)) {
301 28
                return realpath($path . $suffix);
302
            }
303
        }
304
305 4
        return $default;
306
    }
307
308
    /**
309
     * Load options that are represented by annotations
310
     * inside of tests i.e @group group1 = --group group1.
311
     */
312 42
    protected function initAnnotations()
313
    {
314 42
        $annotatedOptions = ['group'];
315 42
        foreach ($this->filtered as $key => $value) {
316 42
            if (array_search($key, $annotatedOptions, true) !== false) {
317 42
                $this->annotations[$key] = $value;
318
            }
319
        }
320 42
    }
321
322
    /**
323
     * @param $file
324
     *
325
     * @return bool
326
     */
327 42
    private function isFile($file)
328
    {
329 42
        return file_exists($file) && !is_dir($file);
330
    }
331
}
332