Passed
Push — develop ( 734f70...83757e )
by ANTHONIUS
03:38
created

src/Bridge/CodeCoverage/Session/Session.php (2 issues)

1
<?php
2
3
/*
4
 * This file is part of the doyo/behat-code-coverage 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 Symfony\Component\Cache\Adapter\FilesystemAdapter;
22
23
abstract class Session implements \Serializable, SessionInterface
24
{
25
    const CACHE_KEY = 'session';
26
27
    /**
28
     * @var string|null
29
     */
30
    protected $name;
31
32
    /**
33
     * @var TestCase
34
     */
35
    protected $testCase;
36
37
    /**
38
     * @var FilesystemAdapter|null
39
     */
40
    protected $adapter;
41
42
    /**
43
     * @var array
44
     */
45
    protected $data = [];
46
47
    /**
48
     * @var ProcessorInterface
49
     */
50
    protected $processor;
51
52
    /**
53
     * @var \Exception[]
54
     */
55
    protected $exceptions = [];
56
57
    /**
58
     * @var bool
59
     */
60
    protected $hasStarted = false;
61
62
    /**
63
     * Code coverage for this session.
64
     *
65
     * @var CodeCoverage
66
     */
67
    protected $codeCoverage;
68
69
    protected $patchXdebug = true;
70
71 29
    public function __construct($name, $patchXdebug = true)
72
    {
73 29
        $dir               = sys_get_temp_dir().'/doyo/behat-coverage-extension';
74 29
        $adapter           = new FilesystemAdapter($name, 0, $dir);
75 29
        $this->adapter     = $adapter;
76 29
        $this->name        = $name;
77 29
        $this->patchXdebug = $patchXdebug;
78 29
        $this->refresh();
79 6
    }
80
81
    /**
82
     * {@inheritdoc}
83
     */
84 10
    public function reset()
85
    {
86 10
        $this->testCase   = null;
87 10
        $this->exceptions = [];
88 10
        $this->processor->clear();
89
90 10
        $this->save();
91 5
    }
92
93
    /**
94
     * {@inheritdoc}
95
     */
96 19
    public function serialize()
97
    {
98
        $data = [
99 19
            $this->name,
100 19
            $this->testCase,
101 19
            $this->exceptions,
102 19
            $this->processor,
103 19
            $this->patchXdebug,
104
        ];
105
106 19
        return serialize($data);
107
    }
108
109
    /**
110
     * {@inheritdoc}
111
     */
112 22
    public function unserialize($serialized)
113
    {
114
        list(
115 22
            $this->name,
116 22
            $this->testCase,
117 22
            $this->exceptions,
118 22
            $this->processor,
119 22
            $this->patchXdebug
120 22
        ) = unserialize($serialized);
121 4
    }
122
123
    /**
124
     * {@inheritdoc}
125
     */
126 32
    public function refresh()
127
    {
128 32
        $adapter = $this->adapter;
129 32
        $cached  = $adapter->getItem(static::CACHE_KEY)->get();
0 ignored issues
show
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

129
        $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...
130
131 32
        if ($cached instanceof self) {
132 21
            $this->name        = $cached->getName();
133 21
            $this->testCase    = $cached->getTestCase();
134 21
            $this->exceptions  = $cached->getExceptions();
135 21
            $this->processor   = $cached->getProcessor();
136 21
            $this->patchXdebug = $cached->getPatchXdebug();
137
        }
138 6
    }
139
140 1
    public function setPatchXdebug(bool $flag)
141
    {
142 1
        $this->patchXdebug = $flag;
143
    }
144
145
    /**
146
     * @return bool
147
     */
148 22
    public function getPatchXdebug(): bool
149
    {
150 22
        return $this->patchXdebug;
151
    }
152
153
    /**
154
     * @return string|null
155
     */
156 23
    public function getName()
157
    {
158 23
        return $this->name;
159
    }
160
161
    /**
162
     * @return TestCase
163
     */
164 23
    public function getTestCase()
165
    {
166 23
        return $this->testCase;
167
    }
168
169
    /**
170
     * {@inheritdoc}
171
     */
172 17
    public function setTestCase(TestCase $testCase = null)
173
    {
174 17
        $this->testCase = $testCase;
175
176 17
        return $this;
177
    }
178
179
    /**
180
     * @return FilesystemAdapter|null
181
     */
182 1
    public function getAdapter(): FilesystemAdapter
183
    {
184 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...
185
    }
186
187
    /**
188
     * @param FilesystemAdapter|null $adapter
189
     */
190 1
    public function setAdapter(FilesystemAdapter $adapter)
191
    {
192 1
        $this->adapter = $adapter;
193
    }
194
195
    /**
196
     * @param ProcessorInterface $processor
197
     */
198 26
    public function setProcessor(ProcessorInterface $processor = null)
199
    {
200 26
        $this->processor = $processor;
201 5
    }
202
203
    /**
204
     * @return ProcessorInterface|null
205
     */
206 22
    public function getProcessor()
207
    {
208 22
        return $this->processor;
209
    }
210
211
    /**
212
     * {@inheritdoc}
213
     */
214 8
    public function hasExceptions()
215
    {
216 8
        return \count($this->exceptions) > 0;
217
    }
218
219
    /**
220
     * {@inheritdoc}
221
     */
222 22
    public function getExceptions()
223
    {
224 22
        return $this->exceptions;
225
    }
226
227
    /**
228
     * @param \Exception $e
229
     */
230 4
    public function addException(\Exception $e)
231
    {
232 4
        $id                    = md5($e->getMessage());
233 4
        $this->exceptions[$id] = $e;
234
    }
235
236
    /**
237
     * @codeCoverageIgnore
238
     */
239
    public function xdebugPatch()
240
    {
241
        if (!$this->patchXdebug) {
242
            return;
243
        }
244
245
        $filter    = $this->getProcessor()->getCodeCoverageFilter();
246
        $options   = $filter->getWhitelistedFiles();
247
        $filterKey = 'whitelistedFiles';
248
249
        if (
250
            !\extension_loaded('xdebug')
251
            || !\function_exists('xdebug_set_filter')
252
            || !isset($options[$filterKey])
253
        ) {
254
            return;
255
        }
256
257
        $dirs = [];
258
        foreach ($options[$filterKey] as $fileName => $status) {
259
            $dir = \dirname($fileName);
260
            if (!\in_array($dir, $dirs, true)) {
261
                $dirs[] = $dir;
262
            }
263
        }
264
265
        xdebug_set_filter(
266
            XDEBUG_FILTER_CODE_COVERAGE,
267
            XDEBUG_PATH_WHITELIST,
268
            $dirs
269
        );
270
    }
271
272
    /**
273
     * {@inheritdoc}
274
     */
275 14
    final public function start($driver = null)
276
    {
277 8
        if (null === $this->testCase) {
278 1
            return;
279
        }
280
        try {
281 7
            $this->hasStarted   = false;
282 7
            $this->codeCoverage = $this->createCodeCoverage($driver);
283 5
            $this->xdebugPatch();
284 5
            $this->codeCoverage->start($this->testCase->getName());
285 14
            $this->hasStarted = true;
286 3
        } catch (\Exception $e) {
287 3
            $message = sprintf(
288 3
                "Can not start code coverage with error message:\n%s",
289 3
                $e->getMessage()
290
            );
291 3
            $exception = new SessionException($message);
292 3
            throw $exception;
293
        }
294 7
    }
295
296 11
    public function stop()
297
    {
298
        try {
299 11
            $codeCoverage = $this->codeCoverage;
300 11
            $processor    = $this->processor;
301
302 11
            $codeCoverage->stop();
303
304 2
            $processor->merge($codeCoverage);
305 3
        } catch (\Exception $e) {
306 3
            throw new SessionException(
307 3
                sprintf(
308 3
                    'Failed to stop coverage for session %s: %s',
309 3
                    $this->name,
310 3
                    $e->getMessage()
311
                )
312
            );
313
        }
314 1
        $this->hasStarted = false;
315
    }
316
317
    /**
318
     * {@inheritdoc}
319
     */
320 17
    public function save()
321
    {
322 17
        $adapter = $this->adapter;
323 17
        $item    = $adapter->getItem(static::CACHE_KEY);
324
325 17
        $item->set($this);
326 17
        $adapter->save($item);
327 5
    }
328
329 9
    public function shutdown()
330
    {
331 9
        if ($this->hasStarted && null !== $this->processor) {
332
            try {
333 9
                $this->stop();
334 2
            } catch (\Exception $e) {
335 2
                $this->addException(new SessionException($e->getMessage()));
336
            }
337
        }
338 2
        $this->hasStarted   = false;
339 2
        $this->save();
340
    }
341
342
    /**
343
     * @param mixed|null $driver
344
     *
345
     * @return CodeCoverage
346
     */
347 7
    protected function createCodeCoverage($driver): CodeCoverage
348
    {
349 7
        $filter   = $this->processor->getCodeCoverageFilter();
350 5
        $options  = $this->processor->getCodeCoverageOptions();
351 5
        $coverage = new CodeCoverage($driver, $filter);
352 5
        foreach ($options as $method => $option) {
353 1
            if (method_exists($coverage, $method)) {
354 1
                $method = 'set'.ucfirst($method);
355 1
                \call_user_func_array([$coverage, $method], [$option]);
356
            }
357
        }
358
359 5
        return $coverage;
360
    }
361
}
362