Add phpUnit testing for Amazon Incentives class
This commit is contained in:
26
vendor/dg/bypass-finals/composer.json
vendored
Normal file
26
vendor/dg/bypass-finals/composer.json
vendored
Normal file
@@ -0,0 +1,26 @@
|
||||
{
|
||||
"name": "dg/bypass-finals",
|
||||
"description": "Removes final keyword from source code on-the-fly and allows mocking of final methods and classes",
|
||||
"keywords": ["testing", "unit", "phpunit", "mocking", "finals"],
|
||||
"license": ["BSD-3-Clause", "GPL-2.0", "GPL-3.0"],
|
||||
"authors": [
|
||||
{
|
||||
"name": "David Grudl",
|
||||
"homepage": "https://davidgrudl.com"
|
||||
}
|
||||
],
|
||||
"require": {
|
||||
"php": ">=7.1"
|
||||
},
|
||||
"require-dev": {
|
||||
"nette/tester": "^2.3",
|
||||
"phpstan/phpstan": "^0.12"
|
||||
},
|
||||
"autoload": {
|
||||
"classmap": ["src/"]
|
||||
},
|
||||
"scripts": {
|
||||
"phpstan": "phpstan analyse",
|
||||
"tester": "tester tests -s"
|
||||
}
|
||||
}
|
||||
49
vendor/dg/bypass-finals/license.md
vendored
Normal file
49
vendor/dg/bypass-finals/license.md
vendored
Normal file
@@ -0,0 +1,49 @@
|
||||
Licenses
|
||||
========
|
||||
|
||||
Good news! You may use Nette Tester under the terms of either
|
||||
the New BSD License or the GNU General Public License (GPL) version 2 or 3.
|
||||
|
||||
|
||||
|
||||
New BSD License
|
||||
---------------
|
||||
|
||||
Copyright (c) 2004, 2013 David Grudl (https://davidgrudl.com)
|
||||
All rights reserved.
|
||||
|
||||
Redistribution and use in source and binary forms, with or without modification,
|
||||
are permitted provided that the following conditions are met:
|
||||
|
||||
* Redistributions of source code must retain the above copyright notice,
|
||||
this list of conditions and the following disclaimer.
|
||||
|
||||
* Redistributions in binary form must reproduce the above copyright notice,
|
||||
this list of conditions and the following disclaimer in the documentation
|
||||
and/or other materials provided with the distribution.
|
||||
|
||||
* Neither the name of "Nette Tester" nor the names of its contributors
|
||||
may be used to endorse or promote products derived from this software
|
||||
without specific prior written permission.
|
||||
|
||||
This software is provided by the copyright holders and contributors "as is" and
|
||||
any express or implied warranties, including, but not limited to, the implied
|
||||
warranties of merchantability and fitness for a particular purpose are
|
||||
disclaimed. In no event shall the copyright owner or contributors be liable for
|
||||
any direct, indirect, incidental, special, exemplary, or consequential damages
|
||||
(including, but not limited to, procurement of substitute goods or services;
|
||||
loss of use, data, or profits; or business interruption) however caused and on
|
||||
any theory of liability, whether in contract, strict liability, or tort
|
||||
(including negligence or otherwise) arising in any way out of the use of this
|
||||
software, even if advised of the possibility of such damage.
|
||||
|
||||
|
||||
|
||||
GNU General Public License
|
||||
--------------------------
|
||||
|
||||
GPL licenses are very very long, so instead of including them here we offer
|
||||
you URLs with full text:
|
||||
|
||||
- [GPL version 2](http://www.gnu.org/licenses/gpl-2.0.html)
|
||||
- [GPL version 3](http://www.gnu.org/licenses/gpl-3.0.html)
|
||||
64
vendor/dg/bypass-finals/readme.md
vendored
Normal file
64
vendor/dg/bypass-finals/readme.md
vendored
Normal file
@@ -0,0 +1,64 @@
|
||||
Bypass Finals
|
||||
=============
|
||||
|
||||
[](https://packagist.org/packages/dg/bypass-finals)
|
||||
[](https://github.com/dg/bypass-finals/actions)
|
||||
[](https://github.com/dg/bypass-finals/releases)
|
||||
[](https://github.com/dg/bypass-finals/blob/master/license.md)
|
||||
|
||||
|
||||
Introduction
|
||||
------------
|
||||
|
||||
Removes final keywords from source code on-the-fly and allows mocking of final methods and classes.
|
||||
It can be used together with any test tool such as PHPUnit, Mockery or [Nette Tester](https://tester.nette.org).
|
||||
|
||||
|
||||
Installation
|
||||
------------
|
||||
|
||||
The recommended way to install is through Composer:
|
||||
|
||||
```
|
||||
composer require dg/bypass-finals --dev
|
||||
```
|
||||
|
||||
It requires PHP version 7.1 (or 5.6 in case of release 1.1) and supports PHP up to 8.1.
|
||||
|
||||
|
||||
Usage
|
||||
-----
|
||||
|
||||
Simply call this:
|
||||
|
||||
```php
|
||||
DG\BypassFinals::enable();
|
||||
```
|
||||
|
||||
You need to enable it before the classes you want to remove the final are loaded. So call it as soon as possible,
|
||||
preferably right after `vendor/autoload.php` is loaded.
|
||||
|
||||
Note that final internal PHP classes like `Closure` cannot be mocked.
|
||||
|
||||
You can choose to only bypass finals in specific files or directories:
|
||||
|
||||
```php
|
||||
DG\BypassFinals::setWhitelist([
|
||||
'*/Nette/*',
|
||||
]);
|
||||
```
|
||||
|
||||
This gives you finer control and can solve issues with certain frameworks and libraries.
|
||||
|
||||
You can try to increase performance by using the cache (the directory must exist):
|
||||
|
||||
```php
|
||||
DG\BypassFinals::$cacheDir = __DIR__ . '/tmp';
|
||||
```
|
||||
|
||||
Support Project
|
||||
---------------
|
||||
|
||||
Do you like BypassFinals?
|
||||
|
||||
[](https://nette.org/make-donation?to=bypass-finals)
|
||||
312
vendor/dg/bypass-finals/src/BypassFinals.php
vendored
Normal file
312
vendor/dg/bypass-finals/src/BypassFinals.php
vendored
Normal file
@@ -0,0 +1,312 @@
|
||||
<?php
|
||||
|
||||
declare(strict_types=1);
|
||||
|
||||
namespace DG;
|
||||
|
||||
|
||||
/**
|
||||
* Removes keyword final from source codes.
|
||||
*/
|
||||
class BypassFinals
|
||||
{
|
||||
private const PROTOCOL = 'file';
|
||||
|
||||
/** @var resource|null */
|
||||
public $context;
|
||||
|
||||
/** @var ?string */
|
||||
public static $cacheDir;
|
||||
|
||||
/** @var resource|null */
|
||||
private $handle;
|
||||
|
||||
/** @var array */
|
||||
private static $pathWhitelist = ['*'];
|
||||
|
||||
/** @var ?object */
|
||||
private static $prevWrapper;
|
||||
|
||||
|
||||
public static function enable(): void
|
||||
{
|
||||
$meta = stream_get_meta_data(fopen(__FILE__, 'r'));
|
||||
self::$prevWrapper = $meta['wrapper_data'] ?? null;
|
||||
stream_wrapper_unregister(self::PROTOCOL);
|
||||
stream_wrapper_register(self::PROTOCOL, self::class);
|
||||
}
|
||||
|
||||
|
||||
public static function setWhitelist(array $whitelist): void
|
||||
{
|
||||
foreach ($whitelist as &$mask) {
|
||||
$mask = strtr($mask, '\\', '/');
|
||||
}
|
||||
|
||||
self::$pathWhitelist = $whitelist;
|
||||
}
|
||||
|
||||
|
||||
public function dir_closedir(): void
|
||||
{
|
||||
closedir($this->handle);
|
||||
}
|
||||
|
||||
|
||||
public function dir_opendir(string $path, int $options): bool
|
||||
{
|
||||
$this->handle = $this->context
|
||||
? $this->native('opendir', $path, $this->context)
|
||||
: $this->native('opendir', $path);
|
||||
return (bool) $this->handle;
|
||||
}
|
||||
|
||||
|
||||
public function dir_readdir()
|
||||
{
|
||||
return readdir($this->handle);
|
||||
}
|
||||
|
||||
|
||||
public function dir_rewinddir(): bool
|
||||
{
|
||||
return (bool) rewinddir($this->handle);
|
||||
}
|
||||
|
||||
|
||||
public function mkdir(string $path, int $mode, int $options): bool
|
||||
{
|
||||
$recursive = (bool) ($options & STREAM_MKDIR_RECURSIVE);
|
||||
return $this->context
|
||||
? $this->native('mkdir', $path, $mode, $recursive, $this->context)
|
||||
: $this->native('mkdir', $path, $mode, $recursive);
|
||||
}
|
||||
|
||||
|
||||
public function rename(string $pathFrom, string $pathTo): bool
|
||||
{
|
||||
return $this->context
|
||||
? $this->native('rename', $pathFrom, $pathTo, $this->context)
|
||||
: $this->native('rename', $pathFrom, $pathTo);
|
||||
}
|
||||
|
||||
|
||||
public function rmdir(string $path, int $options): bool
|
||||
{
|
||||
return $this->context
|
||||
? $this->native('rmdir', $path, $this->context)
|
||||
: $this->native('rmdir', $path);
|
||||
}
|
||||
|
||||
|
||||
public function stream_cast(int $castAs)
|
||||
{
|
||||
return $this->handle;
|
||||
}
|
||||
|
||||
|
||||
public function stream_close(): void
|
||||
{
|
||||
fclose($this->handle);
|
||||
}
|
||||
|
||||
|
||||
public function stream_eof(): bool
|
||||
{
|
||||
return feof($this->handle);
|
||||
}
|
||||
|
||||
|
||||
public function stream_flush(): bool
|
||||
{
|
||||
return fflush($this->handle);
|
||||
}
|
||||
|
||||
|
||||
public function stream_lock(int $operation): bool
|
||||
{
|
||||
return $operation
|
||||
? flock($this->handle, $operation)
|
||||
: true;
|
||||
}
|
||||
|
||||
|
||||
public function stream_metadata(string $path, int $option, $value): bool
|
||||
{
|
||||
switch ($option) {
|
||||
case STREAM_META_TOUCH:
|
||||
return $this->native('touch', $path, $value[0] ?? time(), $value[1] ?? time());
|
||||
case STREAM_META_OWNER_NAME:
|
||||
case STREAM_META_OWNER:
|
||||
return $this->native('chown', $path, $value);
|
||||
case STREAM_META_GROUP_NAME:
|
||||
case STREAM_META_GROUP:
|
||||
return $this->native('chgrp', $path, $value);
|
||||
case STREAM_META_ACCESS:
|
||||
return $this->native('chmod', $path, $value);
|
||||
}
|
||||
|
||||
return false;
|
||||
}
|
||||
|
||||
|
||||
public function stream_open(string $path, string $mode, int $options, ?string &$openedPath): bool
|
||||
{
|
||||
$usePath = (bool) ($options & STREAM_USE_PATH);
|
||||
if ($mode === 'rb' && pathinfo($path, PATHINFO_EXTENSION) === 'php' && self::isPathInWhiteList($path)) {
|
||||
if (self::$prevWrapper) {
|
||||
$content = null;
|
||||
self::$prevWrapper->stream_open($path, $mode, $options, $openedPath);
|
||||
while (!self::$prevWrapper->stream_eof()) {
|
||||
$content .= self::$prevWrapper->stream_read(8192);
|
||||
}
|
||||
|
||||
self::$prevWrapper->stream_close();
|
||||
} else {
|
||||
$content = $this->native('file_get_contents', $path, $usePath, $this->context);
|
||||
}
|
||||
if (!is_string($content)) {
|
||||
return false;
|
||||
}
|
||||
$modified = self::cachedRemoveFinals($content);
|
||||
if ($modified !== $content) {
|
||||
$this->handle = tmpfile();
|
||||
$this->native('fwrite', $this->handle, $modified);
|
||||
$this->native('fseek', $this->handle, 0);
|
||||
return true;
|
||||
}
|
||||
}
|
||||
$this->handle = $this->context
|
||||
? $this->native('fopen', $path, $mode, $usePath, $this->context)
|
||||
: $this->native('fopen', $path, $mode, $usePath);
|
||||
return (bool) $this->handle;
|
||||
}
|
||||
|
||||
|
||||
public function stream_read(int $count)
|
||||
{
|
||||
return fread($this->handle, $count);
|
||||
}
|
||||
|
||||
|
||||
public function stream_seek(int $offset, int $whence = SEEK_SET): bool
|
||||
{
|
||||
return fseek($this->handle, $offset, $whence) === 0;
|
||||
}
|
||||
|
||||
|
||||
public function stream_set_option(int $option, int $arg1, ?int $arg2): bool
|
||||
{
|
||||
return false;
|
||||
}
|
||||
|
||||
|
||||
public function stream_stat()
|
||||
{
|
||||
return fstat($this->handle);
|
||||
}
|
||||
|
||||
|
||||
public function stream_tell()
|
||||
{
|
||||
return ftell($this->handle);
|
||||
}
|
||||
|
||||
|
||||
public function stream_truncate(int $newSize): bool
|
||||
{
|
||||
return ftruncate($this->handle, $newSize);
|
||||
}
|
||||
|
||||
|
||||
public function stream_write(string $data)
|
||||
{
|
||||
return fwrite($this->handle, $data);
|
||||
}
|
||||
|
||||
|
||||
public function unlink(string $path): bool
|
||||
{
|
||||
return $this->native('unlink', $path);
|
||||
}
|
||||
|
||||
|
||||
public function url_stat(string $path, int $flags)
|
||||
{
|
||||
$func = $flags & STREAM_URL_STAT_LINK ? 'lstat' : 'stat';
|
||||
return $flags & STREAM_URL_STAT_QUIET
|
||||
? @$this->native($func, $path)
|
||||
: $this->native($func, $path);
|
||||
}
|
||||
|
||||
|
||||
private function native(string $func)
|
||||
{
|
||||
stream_wrapper_restore(self::PROTOCOL);
|
||||
try {
|
||||
return $func(...array_slice(func_get_args(), 1));
|
||||
} finally {
|
||||
stream_wrapper_unregister(self::PROTOCOL);
|
||||
stream_wrapper_register(self::PROTOCOL, self::class);
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
public static function cachedRemoveFinals(string $code): string
|
||||
{
|
||||
if (stripos($code, 'final') === false) {
|
||||
// do nothing
|
||||
|
||||
} elseif (self::$cacheDir) {
|
||||
$hash = sha1($code);
|
||||
if ($file = @fopen(self::$cacheDir . '/' . $hash, 'r')) { // @ may not exist
|
||||
flock($file, LOCK_SH);
|
||||
if ($res = stream_get_contents($file)) {
|
||||
return $res;
|
||||
}
|
||||
}
|
||||
$code = self::removeFinals($code);
|
||||
if ($file = @fopen(self::$cacheDir . '/' . $hash, 'x')) { // @ may exist
|
||||
flock($file, LOCK_EX);
|
||||
fwrite($file, $code);
|
||||
}
|
||||
|
||||
} else {
|
||||
$code = self::removeFinals($code);
|
||||
}
|
||||
|
||||
return $code;
|
||||
}
|
||||
|
||||
|
||||
public static function removeFinals(string $code): string
|
||||
{
|
||||
try {
|
||||
$tokens = token_get_all($code, TOKEN_PARSE);
|
||||
} catch (\ParseError $e) {
|
||||
return $code;
|
||||
}
|
||||
|
||||
$code = '';
|
||||
foreach ($tokens as $token) {
|
||||
$code .= is_array($token)
|
||||
? ($token[0] === T_FINAL ? '' : $token[1])
|
||||
: $token;
|
||||
}
|
||||
|
||||
return $code;
|
||||
}
|
||||
|
||||
|
||||
private static function isPathInWhiteList(string $path): bool
|
||||
{
|
||||
$path = strtr($path, '\\', '/');
|
||||
foreach (self::$pathWhitelist as $mask) {
|
||||
if (fnmatch($mask, $path)) {
|
||||
return true;
|
||||
}
|
||||
}
|
||||
|
||||
return false;
|
||||
}
|
||||
}
|
||||
Reference in New Issue
Block a user