Merge pull request #1170 from pestphp/feat/presets

[3.x] Arch Presets
This commit is contained in:
Nuno Maduro
2024-06-11 22:54:12 +01:00
committed by GitHub
10 changed files with 431 additions and 1 deletions

View File

@ -0,0 +1,54 @@
<?php
declare(strict_types=1);
namespace Pest\ArchPresets;
use Pest\Arch\Contracts\ArchExpectation;
/**
* @internal
*/
abstract class AbstractPreset
{
/**
* Creates a new preset instance.
*
* @param array<int, string> $userNamespaces
* @param array<int, ArchExpectation> $expectations
*/
final public function __construct(// @phpstan-ignore-line
protected array $userNamespaces,
protected array $expectations = [],
) {
//
}
/**
* Executes the arch preset.
*
* @internal
*/
abstract public function execute(): void;
/**
* Ignores the given "targets" or "dependencies".
*
* @param array<int, string>|string $targetsOrDependencies
*/
final public function ignoring(array|string $targetsOrDependencies): void
{
$this->expectations = array_map(
fn (ArchExpectation $expectation): \Pest\Arch\Contracts\ArchExpectation => $expectation->ignoring($targetsOrDependencies),
$this->expectations,
);
}
/**
* Flushes the expectations.
*/
final public function flush(): void
{
$this->expectations = [];
}
}

94
src/ArchPresets/Base.php Normal file
View File

@ -0,0 +1,94 @@
<?php
declare(strict_types=1);
namespace Pest\ArchPresets;
/**
* @internal
*/
final class Base extends AbstractPreset
{
/**
* Executes the arch preset.
*/
public function execute(): void
{
$this->expectations[] = expect([
'debug_zval_dump',
'debug_backtrace',
'debug_print_backtrace',
'dd',
'ddd',
'dump',
'ray',
'die',
'goto',
'global',
'var_dump',
'phpinfo',
'echo',
'ereg',
'eregi',
'mysql_connect',
'mysql_pconnect',
'mysql_query',
'mysql_select_db',
'mysql_fetch_array',
'mysql_fetch_assoc',
'mysql_fetch_object',
'mysql_fetch_row',
'mysql_num_rows',
'mysql_affected_rows',
'mysql_free_result',
'mysql_insert_id',
'mysql_error',
'mysql_real_escape_string',
'print',
'print_r',
'var_export',
'xdebug_break',
'xdebug_call_class',
'xdebug_call_file',
'xdebug_call_int',
'xdebug_call_line',
'xdebug_code_coverage_started',
'xdebug_connect_to_client',
'xdebug_debug_zval',
'xdebug_debug_zval_stdout',
'xdebug_dump_superglobals',
'xdebug_get_code_coverage',
'xdebug_get_collected_errors',
'xdebug_get_function_count',
'xdebug_get_function_stack',
'xdebug_get_gc_run_count',
'xdebug_get_gc_total_collected_roots',
'xdebug_get_gcstats_filename',
'xdebug_get_headers',
'xdebug_get_monitored_functions',
'xdebug_get_profiler_filename',
'xdebug_get_stack_depth',
'xdebug_get_tracefile_name',
'xdebug_info',
'xdebug_is_debugger_active',
'xdebug_memory_usage',
'xdebug_notify',
'xdebug_peak_memory_usage',
'xdebug_print_function_stack',
'xdebug_set_filter',
'xdebug_start_code_coverage',
'xdebug_start_error_collection',
'xdebug_start_function_monitor',
'xdebug_start_gcstats',
'xdebug_start_trace',
'xdebug_stop_code_coverage',
'xdebug_stop_error_collection',
'xdebug_stop_function_monitor',
'xdebug_stop_gcstats',
'xdebug_stop_trace',
'xdebug_time_index',
'xdebug_var_dump',
'trap',
])->not->toBeUsed();
}
}

View File

@ -0,0 +1,61 @@
<?php
declare(strict_types=1);
namespace Pest\ArchPresets;
/**
* @internal
*/
final class Laravel extends AbstractPreset
{
/**
* Executes the arch preset.
*/
public function execute(): void
{
$this->expectations[] = expect([
'env',
'exit',
])->not->toBeUsed();
$this->expectations[] = expect('App\Http\Controllers')
->toHaveSuffix('Controller');
$this->expectations[] = expect('App\Http\Middleware')
->toHaveMethod('handle');
$this->expectations[] = expect('App\Models')
->not->toHaveSuffix('Model');
$this->expectations[] = expect('App\Http\Requests')
->toHaveSuffix('Request')
->toExtend('Illuminate\Foundation\Http\FormRequest')
->toHaveMethod('rules');
$this->expectations[] = expect('App\Console\Commands')
->toHaveSuffix('Command')
->toExtend('Illuminate\Console\Command')
->toHaveMethod('handle');
$this->expectations[] = expect('App\Exceptions')
->toImplement('Throwable');
$this->expectations[] = expect('App\Mail')
->toExtend('Illuminate\Mail\Mailable');
$this->expectations[] = expect('App\Jobs')
->toHaveMethod('handle');
$this->expectations[] = expect('App\Listeners')
->toHaveMethod('handle');
$this->expectations[] = expect('App\Notifications')
->toExtend('Illuminate\Notifications\Notification');
$this->expectations[] = expect('App\Providers')
->toHaveSuffix('ServiceProvider')
->toExtend('Illuminate\Support\ServiceProvider')
->not->toBeUsed();
}
}

View File

@ -0,0 +1,41 @@
<?php
declare(strict_types=1);
namespace Pest\ArchPresets;
/**
* @internal
*/
final class Security extends AbstractPreset
{
/**
* Executes the arch preset.
*/
public function execute(): void
{
$this->expectations[] = expect([
'md5',
'sha1',
'uniqid',
'rand',
'mt_rand',
'tempnam',
'str_shuffle',
'shuffle',
'array_rand',
'eval',
'exec',
'shell_exec',
'system',
'passthru',
'create_function',
'unserialize',
'extract',
'parse_str',
'mb_parse_str',
'dl',
'assert',
])->not->toBeUsed();
}
}

View File

@ -0,0 +1,27 @@
<?php
declare(strict_types=1);
namespace Pest\ArchPresets;
/**
* @internal
*/
final class Strict extends AbstractPreset
{
/**
* Executes the arch preset.
*/
public function execute(): void
{
foreach ($this->userNamespaces as $namespace) {
$this->expectations[] = expect([
'sleep',
'usleep',
])->not->toBeUsed();
$this->expectations[] = expect($namespace)
->toUseStrictTypes();
}
}
}

View File

@ -0,0 +1,12 @@
<?php
declare(strict_types=1);
namespace Pest\Contracts;
/**
* @internal
*/
interface ArchPreset
{
}

View File

@ -12,6 +12,7 @@ use Pest\Factories\TestCaseMethodFactory;
use Pest\Mutate\Decorators\TestCallDecorator as MutationTestCallDecorator;
use Pest\PendingCalls\Concerns\Describable;
use Pest\Plugins\Only;
use Pest\Preset;
use Pest\Support\Backtrace;
use Pest\Support\Exporter;
use Pest\Support\HigherOrderCallables;
@ -521,4 +522,12 @@ final class TestCall
$testCase->attributes = array_merge($testCase->attributes, $this->testCaseFactoryAttributes);
}
}
/**
* Uses the given preset on the test.
*/
public function preset(): Preset
{
return new Preset($this);
}
}

108
src/Preset.php Normal file
View File

@ -0,0 +1,108 @@
<?php
declare(strict_types=1);
namespace Pest;
use Pest\Arch\Support\Composer;
use Pest\ArchPresets\AbstractPreset;
use Pest\ArchPresets\Base;
use Pest\ArchPresets\Laravel;
use Pest\ArchPresets\Security;
use Pest\ArchPresets\Strict;
use Pest\PendingCalls\TestCall;
use stdClass;
/**
* @internal
*/
final class Preset
{
/**
* The application / package base namespaces.
*
* @var ?array<int, string>
*/
private static ?array $baseNamespaces = null;
/**
* Creates a new preset instance.
*/
public function __construct(private readonly TestCall $testCall)
{
//
}
/**
* Uses the Pest base preset and returns the test call instance.
*/
public function base(): Base
{
return $this->executePreset(new Base($this->baseNamespaces()));
}
/**
* Uses the Pest laravel preset and returns the test call instance.
*/
public function laravel(): Laravel
{
return $this->executePreset(new Laravel($this->baseNamespaces()));
}
/**
* Uses the Pest strict preset and returns the test call instance.
*/
public function strict(): Strict
{
return $this->executePreset(new Strict($this->baseNamespaces()));
}
/**
* Uses the Pest security preset and returns the test call instance.
*/
public function security(): AbstractPreset
{
return $this->executePreset(new Security($this->baseNamespaces()));
}
/**
* Executes the given preset.
*
* @template TPreset of AbstractPreset
*
* @param TPreset $preset
* @return TPreset
*/
private function executePreset(AbstractPreset $preset): AbstractPreset
{
if ((fn (): ?string => $this->description)->call($this->testCall) === null) {
$description = strtolower((new \ReflectionClass($preset))->getShortName());
(fn (): string => $this->description = sprintf('arch "%s" preset', $description))->call($this->testCall);
}
$this->baseNamespaces();
$preset->execute();
$this->testCall->testCaseMethod->closure = (function () use ($preset): void {
$preset->flush();
})->bindTo(new stdClass);
return $preset;
}
/**
* Get the base namespaces for the application / package.
*
* @return array<int, string>
*/
private function baseNamespaces(): array
{
if (self::$baseNamespaces === null) {
self::$baseNamespaces = Composer::userNamespaces();
}
return self::$baseNamespaces;
}
}

View File

@ -1,5 +1,7 @@
PASS Tests\Arch
✓ arch "base" preset
✓ arch "strict" preset
✓ globals
✓ dependencies
✓ contracts

View File

@ -2,6 +2,26 @@
use Pest\Expectation;
arch()->preset()->base()->ignoring([
Expectation::class,
'debug_backtrace',
'var_export',
'xdebug_info',
]);
arch()->preset()->strict()->ignoring([
'usleep',
]);
arch()->preset()->security()->ignoring([
'eval',
'str_shuffle',
'exec',
'unserialize',
'extract',
'assert',
]);
arch('globals')
->expect(['dd', 'dump', 'ray', 'die', 'var_dump', 'sleep'])
->not->toBeUsed()
@ -30,4 +50,6 @@ arch('contracts')
'NunoMaduro\Collision\Contracts',
'Pest\Factories\TestCaseMethodFactory',
'Symfony\Component\Console',
'Pest\Arch\Contracts',
'Pest\PendingCalls',
])->toBeInterfaces();