Skip to content

Commit 8721f61

Browse files
authored
Improving incorrect usage/deprecated calls early on (#845)
* WIP * Contd * Contd * Wrapping up for today * WIP * Adding before/after to collection * Adding lazy collection * Wrapping up * Wrapping up * Drop lazy * Lint fixes * Wrapping up * Testing CI * Lint fix * Testing CI * Lint fix * Wrapping up early * Wrapping up incorrect usage changes * Remove comment * Improvements to attributes * Remove trace method * Remove testing * Remove old * Linting fixes * Adding early deprecation support
1 parent 7365f0e commit 8721f61

22 files changed

+646
-134
lines changed

CHANGELOG.md

Lines changed: 14 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -5,6 +5,20 @@ All notable changes to this project will be documented in this file.
55
The format is based on [Keep a Changelog](https://keepachangelog.com/en/1.0.0/),
66
and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0.html).
77

8+
## Unreleased
9+
10+
### Added
11+
12+
- Added better messaging for early `_doing_it_wrong()` calls during testing
13+
bootstrap.
14+
15+
### Changed
16+
17+
- Tests that call `$this->ignoreIncorrectUsage()` or use
18+
`Ignore_Incorrect_Usage` will no longer make any assertions. Only using the
19+
`$this->setExpectedIncorrectUsage()` method or the `Expected_Incorrect_Usage`
20+
attribute will make assertions and pass the test.
21+
822
## v1.14.1
923

1024
### Added

composer.json

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -20,6 +20,7 @@
2020
"doctrine/inflector": "^2.0.8",
2121
"dragonmantank/cron-expression": "^3.3.3",
2222
"fakerphp/faker": "^1.24",
23+
"filp/whoops": "^2.18.1",
2324
"laravel/serializable-closure": "^1.3.1 || ^2.0",
2425
"league/commonmark": "^2.7.0",
2526
"league/flysystem": "^3.29",

phpcs.xml

Lines changed: 7 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -32,6 +32,8 @@
3232
<exclude name="WordPress.PHP.YodaConditions.NotYoda" />
3333
<exclude name="WordPress.Files.FileName.NotHyphenatedLowercase" />
3434
<exclude name="WordPress.Files.FileName.InvalidClassFileName" />
35+
<exclude name="WordPressVIPMinimum.Functions.StripTags.StripTagsOneParameter" />
36+
<exclude name="WordPressVIPMinimum.Performance.FetchingRemoteData" />
3537
</rule>
3638

3739
<rule ref="Generic.Arrays.DisallowLongArraySyntax" />
@@ -75,4 +77,9 @@
7577
<rule ref="Generic.CodeAnalysis.UselessOverridingMethod.Found">
7678
<exclude-pattern>src/mantle/framework/resources/</exclude-pattern>
7779
</rule>
80+
81+
<!-- Ignore all WordPres naming conventions for PSR-4 code. -->
82+
<rule ref="WordPress.NamingConventions">
83+
<exclude-pattern>src/mantle/testing/*</exclude-pattern>
84+
</rule>
7885
</ruleset>

src/mantle/console/concerns/trait-interacts-with-io.php

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -393,7 +393,7 @@ public function success( string $string, $verbosity = null ): void {
393393
* @param int|string|null $verbosity
394394
*/
395395
public function alert( string $string, $verbosity = null ): void {
396-
$length = Str::length( strip_tags( $string ) ) + 12; // phpcs:ignore WordPressVIPMinimum.Functions.StripTags.StripTagsOneParameter
396+
$length = Str::length( strip_tags( $string ) ) + 12;
397397

398398
$this->comment( str_repeat( '*', $length ), $verbosity );
399399
$this->comment( '* ' . $string . ' *', $verbosity );
Lines changed: 136 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,136 @@
1+
<?php
2+
/**
3+
* EarlyDeprecationsHandler class file
4+
*
5+
* @package Mantle
6+
*/
7+
8+
declare(strict_types=1);
9+
10+
namespace Mantle\Testing;
11+
12+
use Closure;
13+
use Spatie\Backtrace\Backtrace;
14+
use Spatie\Backtrace\Frame;
15+
16+
use function Mantle\Support\Helpers\collect;
17+
18+
/**
19+
* Handler for early deprecation calls early in the testing lifecycle
20+
*
21+
* @see \Mantle\Testing\Concerns\Deprecations
22+
*
23+
* @internal
24+
*/
25+
final class EarlyDeprecationsHandler {
26+
/**
27+
* Hook priority for deprecation handlers.
28+
*
29+
* This priority is cleared fully when unregistering the hooks. The priority
30+
* is assumed to not be commonly used by other handlers.
31+
*
32+
* @var int
33+
*/
34+
public const HOOK_PRIORITY = 57;
35+
36+
/**
37+
* Register the hooks for the class.
38+
*/
39+
public static function register(): void {
40+
foreach ( TestCase::DEPRECATION_TYPES as $type ) {
41+
tests_add_filter(
42+
"deprecated_{$type}_run",
43+
self::create_deprecated_run_callback( $type, self::deprecated_run( ... ) ),
44+
self::HOOK_PRIORITY,
45+
99,
46+
);
47+
48+
tests_add_filter(
49+
"deprecated_{$type}_trigger_error",
50+
'__return_false',
51+
self::HOOK_PRIORITY,
52+
);
53+
}
54+
55+
// Filter for _deprecated_file() which doesn't follow the same pattern.
56+
tests_add_filter(
57+
'deprecated_file_included',
58+
self::create_deprecated_run_callback( 'file', self::deprecated_run( ... ) ),
59+
self::HOOK_PRIORITY,
60+
199,
61+
);
62+
}
63+
64+
/**
65+
* Deregister the hooks for the class.
66+
*/
67+
public static function unregister(): void {
68+
foreach ( TestCase::DEPRECATION_TYPES as $type ) {
69+
remove_all_actions( "deprecated_{$type}_run", self::HOOK_PRIORITY );
70+
remove_filter( "deprecated_{$type}_trigger_error", '__return_false', self::HOOK_PRIORITY );
71+
}
72+
73+
// Filters for _deprecated_file() which doesn't follow the same pattern.
74+
remove_all_actions( 'deprecated_file_included', self::HOOK_PRIORITY );
75+
}
76+
77+
/**
78+
* Handle a deprecated call.
79+
*
80+
* @param string $type The type of deprecation (function, argument, hook, etc).
81+
* @param string $name The name of the deprecated argument/function/hook/etc.
82+
* @param string|null $message An optional message.
83+
*/
84+
public static function deprecated_run( string $type, string $name, ?string $message = null ): void {
85+
if ( empty( $message ) ) {
86+
$message = "Deprecation notice for {$type} '{$name}'.";
87+
}
88+
89+
$writer = new TraceWriter(
90+
title: "Unexpected {$type} deprecation notice in test bootstrap",
91+
description: $message,
92+
frames: collect( Backtrace::create()->frames() )
93+
->skip_until( fn ( Frame $frame ) => in_array( $frame->method, TestCase::get_deprecation_methods(), true ) )
94+
->slice( 1 )
95+
->values()
96+
->all(),
97+
prefix: 'Early Deprecation',
98+
);
99+
100+
$writer->write();
101+
}
102+
103+
/**
104+
* Create a callback for deprecated method calls.
105+
*
106+
* This method will resolve the varying argument structures of the different
107+
* deprecation types in WordPress and call the provided callback with a
108+
* consistent set of parameters.
109+
*
110+
* @param string $type The type of deprecation (function, argument, hook, etc).
111+
* @param Closure $callback The callback to run on deprecation.
112+
*
113+
* @phpstan-param Closure(string $type, string $name, ?string $message): void $callback
114+
*/
115+
public static function create_deprecated_run_callback( string $type, Closure $callback ): callable {
116+
return function ( ...$args ) use ( $type, $callback ): void {
117+
// Because WordPress is WordPress, the position of the message parameter
118+
// varies based on the type of deprecation.
119+
$message = match ( $type ) {
120+
'function' => $args[3] ?? null,
121+
'constructor' => $args[2] ?? null,
122+
'class' => $args[2] ?? null,
123+
'file' => $args[3] ?? null,
124+
'argument' => $args[2] ?? null,
125+
'hook' => $args[3] ?? null,
126+
default => end( $args ) ?: null, // Fallback to the last argument.
127+
};
128+
129+
$callback(
130+
type: $type,
131+
name: $args[0],
132+
message: $message,
133+
);
134+
};
135+
}
136+
}
Lines changed: 67 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,67 @@
1+
<?php
2+
/**
3+
* EarlyIncorrectUsageHandler class file
4+
*
5+
* @package Mantle
6+
*/
7+
8+
declare(strict_types=1);
9+
10+
namespace Mantle\Testing;
11+
12+
use Spatie\Backtrace\Backtrace;
13+
use Spatie\Backtrace\Frame;
14+
15+
use function Mantle\Support\Helpers\collect;
16+
17+
/**
18+
* Handler for early _doing_it_wrong() calls early in the testing lifecycle
19+
* (during the bootstrap process).
20+
*
21+
* Within the context of a test, the \Mantle\Testing\Concerns\Incorrect_Usage
22+
* trait handles _doing_it_wrong() calls appropriately. However, if
23+
* _doing_it_wrong() is called before the test begins (for example, during the
24+
* bootstrap process), those calls are routed to this handler instead.
25+
*
26+
* @see \Mantle\Testing\Concerns\Incorrect_Usage
27+
*
28+
* @internal
29+
*/
30+
final class EarlyIncorrectUsageHandler {
31+
/**
32+
* Register the hooks for the class.
33+
*/
34+
public static function register(): void {
35+
tests_add_filter( 'doing_it_wrong_run', [ self::class, 'handle_doing_it_wrong_run' ], 10, 2 );
36+
tests_add_filter( 'doing_it_wrong_trigger_error', '__return_false', 9 );
37+
}
38+
39+
/**
40+
* Deregister the hooks for the class.
41+
*/
42+
public static function unregister(): void {
43+
remove_action( 'doing_it_wrong_run', [ self::class, 'handle_doing_it_wrong_run' ] );
44+
remove_filter( 'doing_it_wrong_trigger_error', '__return_false', 9 );
45+
}
46+
47+
/**
48+
* Handle a _doing_it_wrong() call.
49+
*
50+
* @param string $function The function called incorrectly.
51+
* @param string $message The message for the incorrect usage.
52+
*/
53+
public static function handle_doing_it_wrong_run( string $function, string $message ): void {
54+
$writer = new TraceWriter(
55+
title: 'Unexpected incorrect usage call in test bootstrap',
56+
description: $message,
57+
frames: collect( Backtrace::create()->frames() )
58+
->skip_until( fn ( Frame $frame ) => $frame->method === '_doing_it_wrong' )
59+
->slice( 1 )
60+
->values()
61+
->all(),
62+
prefix: 'Early Incorrect Usage',
63+
);
64+
65+
$writer->write();
66+
}
67+
}

0 commit comments

Comments
 (0)