Passed
Pull Request — develop (#10)
by ANTHONIUS
03:55
created

Session::getCodeCoverageOptions()   A

Complexity

Conditions 1
Paths 1

Size

Total Lines 3
Code Lines 1

Duplication

Lines 0
Ratio 0 %

Code Coverage

Tests 0
CRAP Score 2

Importance

Changes 0
Metric Value
cc 1
eloc 1
nc 1
nop 0
dl 0
loc 3
ccs 0
cts 2
cp 0
crap 2
rs 10
c 0
b 0
f 0
1
<?php
2
3
/*
4
 * This file is part of the doyo/behat-coverage-extension project.
5
 *
6
 * (c) Anthonius Munthi <[email protected]>
7
 *
8
 * For the full copyright and license information, please view the LICENSE
9
 * file that was distributed with this source code.
10
 */
11
12
declare(strict_types=1);
13
14
namespace Doyo\Behat\Coverage\Bridge\CodeCoverage\Session;
15
16
use Doyo\Behat\Coverage\Bridge\CodeCoverage\Exception\SessionException;
17
use Doyo\Behat\Coverage\Bridge\CodeCoverage\Processor;
18
use Doyo\Behat\Coverage\Bridge\CodeCoverage\ProcessorInterface;
19
use Doyo\Behat\Coverage\Bridge\CodeCoverage\TestCase;
20
use SebastianBergmann\CodeCoverage\CodeCoverage;
21
use SebastianBergmann\CodeCoverage\Filter;
22
use Symfony\Component\Cache\Adapter\FilesystemAdapter;
23
24
abstract class Session implements \Serializable, SessionInterface
25
{
26
    const CACHE_KEY = 'session';
27
28
    /**
29
     * @var string|null
30
     */
31
    protected $name;
32
33
    /**
34
     * @var TestCase
35
     */
36
    protected $testCase;
37
38
    /**
39
     * @var FilesystemAdapter|null
40
     */
41
    protected $adapter;
42
43
    /**
44
     * @var array
45
     */
46
    protected $data = [];
47
48
    /**
49
     * @var ProcessorInterface
50
     */
51
    protected $processor;
52
53
    /**
54
     * @var \Exception[]
55
     */
56
    protected $exceptions = [];
57
58
    /**
59
     * @var bool
60
     */
61
    protected $hasStarted = false;
62
63
    /**
64
     * Code coverage for this session.
65
     *
66
     * @var CodeCoverage
67
     */
68
    protected $codeCoverage;
69
70
    protected $patchXdebug = true;
71
72 29
    public function __construct($name, $patchXdebug = true)
73
    {
74 29
        $dir               = sys_get_temp_dir().'/doyo/behat-coverage-extension';
75 29
        $adapter           = new FilesystemAdapter($name, 0, $dir);
76 29
        $this->adapter     = $adapter;
77 29
        $this->name        = $name;
78 29
        $this->patchXdebug = $patchXdebug;
79 29
        $this->refresh();
80 6
    }
81
82
    /**
83
     * {@inheritdoc}
84
     */
85 10
    public function reset()
86
    {
87 10
        $this->testCase   = null;
88 10
        $this->exceptions = [];
89 10
        $this->processor->clear();
90
91 10
        $this->save();
92 5
    }
93
94
    /**
95
     * {@inheritdoc}
96
     */
97 19
    public function serialize()
98
    {
99
        $data = [
100 19
            $this->name,
101 19
            $this->testCase,
102 19
            $this->exceptions,
103 19
            $this->processor,
104 19
            $this->patchXdebug
105
        ];
106
107 19
        return serialize($data);
108
    }
109
110
    /**
111
     * {@inheritdoc}
112
     */
113 22
    public function unserialize($serialized)
114
    {
115
        list(
116 22
            $this->name,
117 22
            $this->testCase,
118 22
            $this->exceptions,
119 22
            $this->processor,
120 22
            $this->patchXdebug
121 22
        ) = unserialize($serialized);
122 4
    }
123
124
    /**
125
     * {@inheritdoc}
126
     */
127 32
    public function refresh()
128
    {
129 32
        $adapter = $this->adapter;
130 32
        $cached  = $adapter->getItem(static::CACHE_KEY)->get();
0 ignored issues
show
Bug introduced by
The method getItem() does not exist on null. ( Ignorable by Annotation )

If this is a false-positive, you can also ignore this issue in your code via the ignore-call  annotation

130
        $cached  = $adapter->/** @scrutinizer ignore-call */ getItem(static::CACHE_KEY)->get();

This check looks for calls to methods that do not seem to exist on a given type. It looks for the method on the type itself as well as in inherited classes or implemented interfaces.

This is most likely a typographical error or the method has been renamed.

Loading history...
131
132 32
        if ($cached instanceof self) {
133 21
            $this->name = $cached->getName();
134 21
            $this->testCase = $cached->getTestCase();
135 21
            $this->exceptions = $cached->getExceptions();
136 21
            $this->processor = $cached->getProcessor();
137 21
            $this->patchXdebug = $cached->getPatchXdebug();
138
        }
139 6
    }
140
141 1
    public function setPatchXdebug(bool $flag)
142
    {
143 1
        $this->patchXdebug = $flag;
144
    }
145
146
    /**
147
     * @return bool
148
     */
149 22
    public function getPatchXdebug(): bool
150
    {
151 22
        return $this->patchXdebug;
152
    }
153
154
    /**
155
     * @return string|null
156
     */
157 23
    public function getName()
158
    {
159 23
        return $this->name;
160
    }
161
162
    /**
163
     * @return TestCase
164
     */
165 23
    public function getTestCase()
166
    {
167 23
        return $this->testCase;
168
    }
169
170
    /**
171
     * {@inheritdoc}
172
     */
173 17
    public function setTestCase(TestCase $testCase = null)
174
    {
175 17
        $this->testCase = $testCase;
176
177 17
        return $this;
178
    }
179
180
    /**
181
     * @return FilesystemAdapter|null
182
     */
183 1
    public function getAdapter(): FilesystemAdapter
184
    {
185 1
        return $this->adapter;
0 ignored issues
show
Bug Best Practice introduced by
The expression return $this->adapter could return the type null which is incompatible with the type-hinted return Symfony\Component\Cache\Adapter\FilesystemAdapter. Consider adding an additional type-check to rule them out.
Loading history...
186
    }
187
188
    /**
189
     * @param FilesystemAdapter|null $adapter
190
     *
191
     * @return Cache
0 ignored issues
show
Bug introduced by
The type Doyo\Behat\Coverage\Brid...eCoverage\Session\Cache was not found. Maybe you did not declare it correctly or list all dependencies?

The issue could also be caused by a filter entry in the build configuration. If the path has been excluded in your configuration, e.g. excluded_paths: ["lib/*"], you can move it to the dependency path list as follows:

filter:
    dependency_paths: ["lib/*"]

For further information see https://scrutinizer-ci.com/docs/tools/php/php-scrutinizer/#list-dependency-paths

Loading history...
192
     */
193 1
    public function setAdapter(FilesystemAdapter $adapter): self
194
    {
195 1
        $this->adapter = $adapter;
196
197 1
        return $this;
0 ignored issues
show
Bug Best Practice introduced by
The expression return $this returns the type Doyo\Behat\Coverage\Brid...overage\Session\Session which is incompatible with the documented return type Doyo\Behat\Coverage\Brid...eCoverage\Session\Cache.
Loading history...
198
    }
199
200
    /**
201
     * @param ProcessorInterface $processor
202
     */
203 26
    public function setProcessor(ProcessorInterface $processor = null)
204
    {
205 26
        $this->processor = $processor;
206 5
    }
207
208
    /**
209
     * @return ProcessorInterface|null
210
     */
211 22
    public function getProcessor()
212
    {
213 22
        return $this->processor;
214
    }
215
216
    /**
217
     * {@inheritdoc}
218
     */
219 8
    public function hasExceptions()
220
    {
221 8
        return \count($this->exceptions) > 0;
222
    }
223
224
    /**
225
     * {@inheritdoc}
226
     */
227 22
    public function getExceptions()
228
    {
229 22
        return $this->exceptions;
230
    }
231
232
    /**
233
     * @param \Exception $e
234
     */
235 4
    public function addException(\Exception $e)
236
    {
237 4
        $id = md5($e->getMessage());
238 4
        $this->exceptions[$id] = $e;
239
    }
240
241
    /**
242
     * @codeCoverageIgnore
243
     */
244
    public function xdebugPatch()
245
    {
246
        if(!$this->patchXdebug){
247
            return;
248
        }
249
250
        $filter = $this->getProcessor()->getCodeCoverageFilter();
251
        $options = $filter->getWhitelistedFiles();
252
        $filterKey = 'whitelistedFiles';
253
254
        if (
255
            !\extension_loaded('xdebug')
256
            || !\function_exists('xdebug_set_filter')
257
            || !isset($options[$filterKey])
258
        ) {
259
            return;
260
        }
261
262
        $dirs = [];
263
        foreach ($options[$filterKey] as $fileName => $status) {
264
            $dir = \dirname($fileName);
265
            if (!\in_array($dir, $dirs, true)) {
266
                $dirs[] = $dir;
267
            }
268
        }
269
270
        xdebug_set_filter(
271
            XDEBUG_FILTER_CODE_COVERAGE,
272
            XDEBUG_PATH_WHITELIST,
273
            $dirs
274
        );
275
    }
276
277
    /**
278
     * {@inheritdoc}
279
     */
280 14
    final public function start($driver = null)
281
    {
282 8
        if (null === $this->testCase) {
283 1
            return;
284
        }
285
        try {
286 7
            $this->hasStarted = false;
287 7
            $this->codeCoverage = $this->createCodeCoverage($driver);
288 5
            $this->xdebugPatch();
289 5
            $this->codeCoverage->start($this->testCase->getName());
290 14
            $this->hasStarted = true;
291 3
        } catch (\Exception $e) {
292 3
            $message = sprintf(
293 3
                "Can not start code coverage with error message:\n%s",
294 3
                $e->getMessage()
295
            );
296 3
            $exception = new SessionException($message);
297 3
            throw $exception;
298
        }
299 7
    }
300
301 11
    public function stop()
302
    {
303
        try {
304 11
            $codeCoverage = $this->codeCoverage;
305 11
            $processor    = $this->processor;
306
307 11
            $codeCoverage->stop();
308
309 2
            $processor->merge($codeCoverage);
310 3
        } catch (\Exception $e) {
311 3
            throw new SessionException(
312 3
                sprintf(
313 3
                    'Failed to stop coverage for session %s: %s',
314 3
                    $this->name,
315 3
                    $e->getMessage()
316
                )
317
            );
318
        }
319 1
        $this->hasStarted = false;
320
    }
321
322
    /**
323
     * {@inheritdoc}
324
     */
325 17
    public function save()
326
    {
327 17
        $adapter = $this->adapter;
328 17
        $item    = $adapter->getItem(static::CACHE_KEY);
329
330 17
        $item->set($this);
331 17
        $adapter->save($item);
332 5
    }
333
334 9
    public function shutdown()
335
    {
336 9
        if ($this->hasStarted && null !== $this->processor) {
337
            try {
338 9
                $this->stop();
339 2
            } catch (\Exception $e) {
340 2
                $this->addException(new SessionException($e->getMessage()));
341
            }
342
        }
343 2
        $this->hasStarted   = false;
344 2
        $this->save();
345
    }
346
347
    /**
348
     * @param mixed|null $driver
349
     *
350
     * @return CodeCoverage
351
     */
352 7
    protected function createCodeCoverage($driver): CodeCoverage
353
    {
354 7
        $filter   = $this->processor->getCodeCoverageFilter();
355 5
        $options  = $this->processor->getCodeCoverageOptions();
356 5
        $coverage = new CodeCoverage($driver, $filter);
357 5
        foreach ($options as $method => $option) {
358 1
            if(method_exists($coverage, $method)){
359 1
                $method = 'set'.ucfirst($method);
360 1
                \call_user_func_array([$coverage, $method], [$option]);
361
            }
362
        }
363
364 5
        return $coverage;
365
    }
366
}
367