Completed
Push — master ( be9660...707803 )
by Georges
16s queued 13s
created

TestHelper   F

Complexity

Total Complexity 60

Size/Duplication

Total Lines 450
Duplicated Lines 0 %

Importance

Changes 8
Bugs 1 Features 0
Metric Value
eloc 184
c 8
b 1
f 0
dl 0
loc 450
rs 3.6
wmc 60

22 Methods

Rating   Name   Duplication   Size   Complexity  
A __construct() 0 15 1
A accessInaccessibleMember() 0 6 1
A printNoteText() 0 5 1
A printNewLine() 0 4 1
A printText() 0 12 3
A printFailText() 0 8 2
A runAsyncProcess() 0 6 2
A printPassText() 0 6 1
A terminateTest() 0 6 1
A resetExitCode() 0 5 1
A printDebugText() 0 5 1
B runCRUDTests() 0 84 9
A runSubProcess() 0 3 2
A exceptionHandler() 0 17 2
A printHeaders() 0 13 3
C errorHandler() 0 49 16
A printInfoText() 0 6 1
A getExitCode() 0 3 1
A debugEvents() 0 7 1
B isCli() 0 23 8
A printSkipText() 0 5 1
A isHHVM() 0 3 1

How to fix   Complexity   

Complex Class

Complex classes like TestHelper often do a lot of different things. To break such a class down, we need to identify a cohesive component within that class. A common approach to find such a component is to look for fields/methods that share the same prefixes, or suffixes.

Once you have determined the fields that belong together, you can apply the Extract Class refactoring. If the component makes sense as a sub-class, Extract Subclass is also a candidate, and is often faster.

While breaking up the class, it is a good idea to analyze how other classes use TestHelper, and based on these observations, apply Extract Interface, too.

1
<?php
2
3
/**
4
 *
5
 * This file is part of phpFastCache.
6
 *
7
 * @license MIT License (MIT)
8
 *
9
 * For full copyright and license information, please see the docs/CREDITS.txt file.
10
 *
11
 * @author Khoa Bui (khoaofgod)  <[email protected]> https://www.phpfastcache.com
12
 * @author Georges.L (Geolim4)  <[email protected]>
13
 *
14
 */
15
declare(strict_types=1);
16
17
namespace Phpfastcache\Helper;
18
19
use Phpfastcache\Api;
20
use Phpfastcache\Core\Pool\ExtendedCacheItemPoolInterface;
21
use Phpfastcache\Event\EventManagerInterface;
22
use Phpfastcache\Exceptions\PhpfastcacheDriverCheckException;
23
use Phpfastcache\Exceptions\PhpfastcacheIOException;
24
use Phpfastcache\Exceptions\PhpfastcacheLogicException;
25
use ReflectionClass;
26
use ReflectionException;
27
use Throwable;
28
29
30
/**
31
 * Class TestHelper
32
 * @package phpFastCache\Helper
33
 */
34
class TestHelper
35
{
36
    /**
37
     * @var string
38
     */
39
    protected $testName;
40
41
    /**
42
     * @var int
43
     */
44
    protected $exitCode = 0;
45
46
    /**
47
     * @var int
48
     */
49
    protected $timestamp;
50
51
    /**
52
     * @var \League\CLImate\CLImate
53
     */
54
    protected $climate;
55
56
    /**
57
     * TestHelper constructor.
58
     *
59
     * @param string $testName
60
     * @throws PhpfastcacheIOException
61
     * @throws PhpfastcacheLogicException
62
     */
63
    public function __construct(string $testName)
64
    {
65
        $this->timestamp = microtime(true);
66
        $this->testName = $testName;
67
        $this->climate = new \League\CLImate\CLImate;
68
        $this->climate->forceAnsiOn();
69
70
        /**
71
         * Catch all uncaught exception
72
         * to our own exception handler
73
         */
74
        set_exception_handler([$this, 'exceptionHandler']);
75
        set_error_handler([$this, 'errorHandler']);
76
77
        $this->printHeaders();
78
    }
79
80
    /**
81
     * @throws PhpfastcacheIOException
82
     * @throws PhpfastcacheLogicException
83
     */
84
    public function printHeaders()
85
    {
86
        if (!$this->isCli() && !headers_sent()) {
87
            header('Content-Type: text/plain, true');
88
        }
89
90
        $loadedExtensions = get_loaded_extensions();
91
        natcasesort($loadedExtensions);
92
        $this->printText('[PhpFastCache CORE v' . Api::getPhpFastCacheVersion() . Api::getPhpFastCacheGitHeadHash() . ']', true);
93
        $this->printText('[PhpFastCache API v' . Api::getVersion() . ']', true);
94
        $this->printText('[PHP v' . PHP_VERSION . ' with: ' . implode(', ', $loadedExtensions) . ']', true);
95
        $this->printText("[Begin Test: '{$this->testName}']");
96
        $this->printText('---');
97
    }
98
99
    /**
100
     * @see https://stackoverflow.com/questions/933367/php-how-to-best-determine-if-the-current-invocation-is-from-cli-or-web-server
101
     * @return bool
102
     */
103
    public function isCli(): bool
104
    {
105
        if (defined('STDIN')) {
106
            return true;
107
        }
108
109
        if (php_sapi_name() === 'cli') {
110
            return true;
111
        }
112
113
        if (array_key_exists('SHELL', $_ENV)) {
114
            return true;
115
        }
116
117
        if (empty($_SERVER['REMOTE_ADDR']) && !isset($_SERVER['HTTP_USER_AGENT']) && count($_SERVER['argv']) > 0) {
118
            return true;
119
        }
120
121
        if (!array_key_exists('REQUEST_METHOD', $_SERVER)) {
122
            return true;
123
        }
124
125
        return false;
126
    }
127
128
    /**
129
     * @param string $string
130
     * @param bool $strtoupper
131
     * @param string $prefix
132
     * @return $this
133
     */
134
    public function printText(string $string, bool $strtoupper = false, string $prefix = ''): self
135
    {
136
        if ($prefix) {
137
            $string = "[{$prefix}] {$string}";
138
        }
139
        if (!$strtoupper) {
140
            $this->climate->out(trim($string));
141
        } else {
142
            $this->climate->out(strtoupper(trim($string)));
143
        }
144
145
        return $this;
146
    }
147
148
    /**
149
     * @return int
150
     */
151
    public function getExitCode(): int
152
    {
153
        return $this->exitCode;
154
    }
155
156
    /**
157
     * @return $this
158
     */
159
    public function resetExitCode(): self
160
    {
161
        $this->exitCode = 0;
162
163
        return $this;
164
    }
165
166
    /**
167
     * @param string $string
168
     * @return $this
169
     */
170
    public function printNoteText(string $string): self
171
    {
172
        $this->printText($string, false, '<blue>NOTE</blue>');
173
174
        return $this;
175
    }
176
177
    /**
178
     * @param int $count
179
     * @return $this
180
     */
181
    public function printNewLine(int $count = 1): self
182
    {
183
        $this->climate->out(str_repeat(PHP_EOL, $count));
184
        return $this;
185
    }
186
187
    /**
188
     * @param string $file
189
     * @param string $ext
190
     */
191
    public function runSubProcess(string $file, string $ext = '.php')
192
    {
193
        $this->runAsyncProcess(($this->isHHVM() ? 'hhvm ' : 'php ') . getcwd() . DIRECTORY_SEPARATOR . 'subprocess' . DIRECTORY_SEPARATOR . $file . '.subprocess' . $ext);
194
    }
195
196
    /**
197
     * @param string $cmd
198
     */
199
    public function runAsyncProcess(string $cmd)
200
    {
201
        if (substr(php_uname(), 0, 7) === 'Windows') {
202
            pclose(popen('start /B ' . $cmd, 'r'));
0 ignored issues
show
Bug introduced by
It seems like popen('start /B ' . $cmd, 'r') can also be of type false; however, parameter $handle of pclose() does only seem to accept resource, maybe add an additional type check? ( Ignorable by Annotation )

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

202
            pclose(/** @scrutinizer ignore-type */ popen('start /B ' . $cmd, 'r'));
Loading history...
203
        } else {
204
            exec($cmd . ' > /dev/null &');
205
        }
206
    }
207
208
    /**
209
     * @return bool
210
     */
211
    public function isHHVM(): bool
212
    {
213
        return defined('HHVM_VERSION');
214
    }
215
216
    /**
217
     * @param $obj
218
     * @param $prop
219
     * @return mixed
220
     * @throws ReflectionException
221
     */
222
    public function accessInaccessibleMember($obj, $prop)
223
    {
224
        $reflection = new ReflectionClass($obj);
225
        $property = $reflection->getProperty($prop);
226
        $property->setAccessible(true);
227
        return $property->getValue($obj);
228
    }
229
230
    /**
231
     * @param Throwable $exception
232
     */
233
    public function exceptionHandler(Throwable $exception)
234
    {
235
        if ($exception instanceof PhpfastcacheDriverCheckException) {
236
            $this->printSkipText('A driver could not be initialized due to missing requirement: ' . $exception->getMessage());
237
            $this->exitCode = 0;
238
        } else {
239
            $this->printFailText(
240
                sprintf(
241
                    'Uncaught exception "%s" in "%s" line %d with message: "%s"',
242
                    get_class($exception),
243
                    $exception->getFile(),
244
                    $exception->getLine(),
245
                    $exception->getMessage()
246
                )
247
            );
248
        }
249
        $this->terminateTest();
250
    }
251
252
    /**
253
     * @param string $string
254
     * @return $this
255
     */
256
    public function printSkipText(string $string): self
257
    {
258
        $this->printText($string, false, '<yellow>SKIP</yellow>');
259
260
        return $this;
261
    }
262
263
    /**
264
     * @param string $string
265
     * @param bool $failsTest
266
     * @return $this
267
     */
268
    public function printFailText(string $string, bool $failsTest = true): self
269
    {
270
        $this->printText($string, false, '<red>FAIL</red>');
271
        if ($failsTest) {
272
            $this->exitCode = 1;
273
        }
274
275
        return $this;
276
    }
277
278
    /**
279
     * @return void
280
     */
281
    public function terminateTest()
282
    {
283
        $execTime = round(microtime(true) - $this->timestamp, 3);
284
285
        $this->printText('<yellow>Test duration: </yellow><light_green>' . $execTime . 's</light_green>');
286
        exit($this->exitCode);
0 ignored issues
show
Best Practice introduced by
Using exit here is not recommended.

In general, usage of exit should be done with care and only when running in a scripting context like a CLI script.

Loading history...
287
    }
288
289
    /**
290
     * @param int $errno
291
     * @param string $errstr
292
     * @param string $errfile
293
     * @param int $errline
294
     */
295
    public function errorHandler(int $errno, string $errstr, string $errfile, int $errline)
296
    {
297
        $errorType = '';
298
299
        switch ($errno) {
300
            case E_PARSE:
301
            case E_ERROR:
302
            case E_CORE_ERROR:
303
            case E_COMPILE_ERROR:
304
            case E_USER_ERROR:
305
                $errorType = '[FATAL ERROR]';
306
                break;
307
            case E_WARNING:
308
            case E_USER_WARNING:
309
            case E_COMPILE_WARNING:
310
            case E_RECOVERABLE_ERROR:
311
                $errorType = '[WARNING]';
312
                break;
313
            case E_NOTICE:
314
            case E_USER_NOTICE:
315
                $errorType = '[NOTICE]';
316
                break;
317
            case E_STRICT:
318
                $errorType = '[STRICT]';
319
                break;
320
            case E_DEPRECATED:
321
            case E_USER_DEPRECATED:
322
                $errorType = '[DEPRECATED]';
323
                break;
324
            default:
325
                break;
326
        }
327
328
        if ($errorType === '[FATAL ERROR]') {
329
            $this->printFailText(
330
                sprintf(
331
                    "<red>A critical error has been caught: \"%s\" in %s line %d</red>",
332
                    "$errorType $errstr",
333
                    $errfile,
334
                    $errline
335
                )
336
            );
337
        } else {
338
            $this->printDebugText(
339
                sprintf(
340
                    "<yellow>A non-critical error has been caught: \"%s\" in %s line %d</yellow>",
341
                    "$errorType $errstr",
342
                    $errfile,
343
                    $errline
344
                )
345
            );
346
        }
347
    }
348
349
    /**
350
     * @param string $string
351
     * @return $this
352
     */
353
    public function printDebugText(string $string): self
354
    {
355
        $this->printText($string, false, "\e[35mDEBUG\e[0m");
356
357
        return $this;
358
    }
359
360
    /**
361
     * @param EventManagerInterface $eventManager
362
     */
363
    public function debugEvents(EventManagerInterface $eventManager)
364
    {
365
        $eventManager->onEveryEvents(
366
            function (string $eventName) {
367
                $this->printDebugText("Triggered event '{$eventName}'");
368
            },
369
            'debugCallback'
370
        );
371
    }
372
373
    /**
374
     * @param ExtendedCacheItemPoolInterface $pool
375
     */
376
    public function runCRUDTests(ExtendedCacheItemPoolInterface $pool)
377
    {
378
        $this->printInfoText('Running CRUD tests on the following backend: ' . get_class($pool));
379
        $pool->clear();
380
381
        $cacheKey = 'cache_key_' . bin2hex(random_bytes(8) . '_' . random_int(100, 999));
382
        $cacheValue = 'cache_data_' . random_int(1000, 999999);
383
        $cacheTag = 'cache_tag_' . bin2hex(random_bytes(8) . '_' . random_int(100, 999));
384
        $cacheItem = $pool->getItem($cacheKey);
385
        $this->printInfoText('Using cache key: ' . $cacheKey);
386
387
        $cacheItem->set($cacheValue)
388
            ->expiresAfter(600)
389
            ->addTag($cacheTag);
390
391
        if ($pool->save($cacheItem)) {
392
            $this->printPassText('The pool successfully saved an item.');
393
        } else {
394
            $this->printFailText('The pool failed to save an item.');
395
            return;
396
        }
397
398
        /***
399
         * Detach the items to force "re-pull" from the backend
400
         */
401
        $pool->detachAllItems();
402
403
        $this->printInfoText('Re-fetching item by its tag...');
404
        $cacheItems = $pool->getItemsByTag($cacheTag);
405
406
        if (isset($cacheItems[$cacheKey]) && $cacheItems[$cacheKey]->getKey() === $cacheKey) {
407
            $this->printPassText('The pool successfully retrieved the cache item.');
408
        } else {
409
            $this->printFailText('The pool failed to retrieve the cache item.');
410
            return;
411
        }
412
        $cacheItem = $cacheItems[$cacheKey];
413
414
        if ($cacheItem->get() === $cacheValue) {
415
            $this->printPassText('The pool successfully retrieved the expected value.');
416
        } else {
417
            $this->printFailText('The pool failed to retrieve the expected value.');
418
            return;
419
        }
420
421
        $this->printInfoText('Updating the cache item by appending some chars...');
422
        $cacheItem->append('_appended');
423
        $cacheValue .= '_appended';
424
        $pool->saveDeferred($cacheItem);
425
        $this->printInfoText('Deferred item is being committed...');
426
        if ($pool->commit()) {
427
            $this->printPassText('The pool successfully committed deferred cache item.');
428
        } else {
429
            $this->printFailText('The pool failed to commit deferred cache item.');
430
        }
431
432
        /***
433
         * Detach the items to force "re-pull" from the backend
434
         */
435
        $pool->detachAllItems();
436
        unset($cacheItem);
437
        $cacheItem = $pool->getItem($cacheKey);
438
439
        if ($cacheItem->get() === $cacheValue) {
440
            $this->printPassText('The pool successfully retrieved the expected new value.');
441
        } else {
442
            $this->printFailText('The pool failed to retrieve the expected new value.');
443
            return;
444
        }
445
446
447
        if ($pool->deleteItem($cacheKey)) {
448
            $this->printPassText('The pool successfully deleted the cache item.');
449
        } else {
450
            $this->printFailText('The pool failed to delete the cache item.');
451
        }
452
453
        if ($pool->clear()) {
454
            $this->printPassText('The pool successfully cleared.');
455
        } else {
456
            $this->printFailText('The cluster failed to clear.');
457
        }
458
459
        $this->printInfoText(sprintf('I/O stats: %d HIT, %s MISS, %d WRITE', $pool->getIO()->getReadHit(), $pool->getIO()->getReadMiss(), $pool->getIO()->getWriteHit()));
460
    }
461
462
    /**
463
     * @param string printFailText
0 ignored issues
show
Bug introduced by
The type Phpfastcache\Helper\printFailText 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...
464
     * @return $this
465
     */
466
    public function printInfoText(string $string): self
467
    {
468
        $this->printText($string, false, "\e[34mINFO\e[0m");
469
470
471
        return $this;
472
    }
473
474
    /**
475
     * @param string $string
476
     * @return $this
477
     */
478
    public function printPassText(string $string): self
479
    {
480
        $this->printText($string, false, "\e[32mPASS\e[0m");
481
482
483
        return $this;
484
    }
485
}