Completed
Push — develop ( 2faf16...be2583 )
by ANTHONIUS
15s queued 11s
created

Session::xdebugPatch()   B

Complexity

Conditions 7
Paths 5

Size

Total Lines 30
Code Lines 19

Duplication

Lines 0
Ratio 0 %

Code Coverage

Tests 0
CRAP Score 56

Importance

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