Skip to content

Commit

Permalink
PHPUnit: Fix slow tests (#1087)
Browse files Browse the repository at this point in the history
* Tests: Fix slow tests

Takes tests execution from 7.8s to ~3s.

* Fix phpcs

* Try comma separation

* Remove type hint

* Don't listen by default for as long as we support 7.0

* Mock response instead of bypassing it entirely

* More specific error message

* Use existing fixtures
  • Loading branch information
obenland authored Dec 18, 2024
1 parent 9011ffc commit 6a7de00
Show file tree
Hide file tree
Showing 5 changed files with 312 additions and 26 deletions.
5 changes: 5 additions & 0 deletions phpunit.xml.dist
Original file line number Diff line number Diff line change
Expand Up @@ -11,4 +11,9 @@
<directory prefix="class-test-" suffix=".php">./tests</directory>
</testsuite>
</testsuites>
<!--
<listeners>
<listener class="Activitypub\Tests\Activitypub_Testcase_Timer" file="tests/class-activitypub-testcase-timer.php" />
</listeners>
-->
</phpunit>
63 changes: 63 additions & 0 deletions tests/bootstrap.php
Original file line number Diff line number Diff line change
Expand Up @@ -12,6 +12,7 @@
\define( 'WP_SITEURL', 'http://example.org' );
\define( 'WP_HOME', 'http://example.org' );

\define( 'AP_TESTS_DIR', __DIR__ );
$_tests_dir = \getenv( 'WP_TESTS_DIR' );

if ( ! $_tests_dir ) {
Expand All @@ -38,6 +39,68 @@ function _manually_load_plugin() {
}
\tests_add_filter( 'muplugins_loaded', '_manually_load_plugin' );

/**
* Disable HTTP requests.
*
* @param mixed $response The value to return instead of making a HTTP request.
* @param array $args Request arguments.
* @param string $url The request URL.
* @return mixed|false|WP_Error
*/
function http_disable_request( $response, $args, $url ) {
if ( false !== $response ) {
// Another filter has already overridden this request.
return $response;
}

/**
* Allow HTTP requests to be made.
*
* @param bool $allow Whether to allow the HTTP request.
* @param array $args Request arguments.
* @param string $url The request URL.
*/
if ( apply_filters( 'tests_allow_http_request', false, $args, $url ) ) {
// This request has been specifically permitted.
return false;
}

$backtrace = array_reverse( debug_backtrace() ); // phpcs:ignore WordPress.PHP.DevelopmentFunctions.error_log_debug_backtrace,PHPCompatibility.FunctionUse.ArgumentFunctionsReportCurrentValue.NeedsInspection
$trace_str = '';
foreach ( $backtrace as $frame ) {
if (
( isset( $frame['file'] ) && strpos( $frame['file'], 'phpunit.php' ) !== false ) ||
( isset( $frame['file'] ) && strpos( $frame['file'], 'wp-includes/http.php' ) !== false ) ||
( isset( $frame['file'] ) && strpos( $frame['file'], 'wp-includes/class-wp-hook.php' ) !== false ) ||
( isset( $frame['function'] ) && __FUNCTION__ === $frame['function'] ) ||
( isset( $frame['function'] ) && 'apply_filters' === $frame['function'] )
) {
continue;
}

if ( $trace_str ) {
$trace_str .= ', ';
}

if ( ! empty( $frame['file'] ) && ! empty( $frame['line'] ) ) {
$trace_str .= basename( $frame['file'] ) . ':' . $frame['line'];
if ( ! empty( $frame['function'] ) ) {
$trace_str .= ' ';
}
}

if ( ! empty( $frame['function'] ) ) {
if ( ! empty( $frame['class'] ) ) {
$trace_str .= $frame['class'] . '::';
}
$trace_str .= $frame['function'] . '()';
}
}

return new WP_Error( 'cancelled', 'Live HTTP request cancelled by bootstrap.php' );
}
\tests_add_filter( 'pre_http_request', 'http_disable_request', 99, 3 );

// Start up the WP testing environment.
require $_tests_dir . '/includes/bootstrap.php';
require __DIR__ . '/class-activitypub-testcase-cache-http.php';
Expand Down
101 changes: 101 additions & 0 deletions tests/class-activitypub-testcase-timer.php
Original file line number Diff line number Diff line change
@@ -0,0 +1,101 @@
<?php
/**
* Test Timer Listener for PHPUnit.
*
* phpcs:disable WordPress.Security.EscapeOutput.OutputNotEscaped,PHPCompatibility.FunctionDeclarations.NewReturnTypeDeclarations.voidFound
*
* @package Activitypub
*/

namespace Activitypub\Tests;

use PHPUnit\Framework\TestListener;
use PHPUnit\Framework\TestListenerDefaultImplementation;
use PHPUnit\Framework\Test;
use PHPUnit\Framework\TestSuite;

/**
* Activitypub Testcase Timer class.
*/
class Activitypub_Testcase_Timer implements TestListener {
use TestListenerDefaultImplementation;

/**
* Store test start times.
*
* @var array
*/
private $test_start_times = array();

/**
* Store slow tests.
*
* @var array
*/
private $slow_tests = array();

/**
* Threshold for slow tests in seconds.
*
* @var float
*/
private $slow_threshold = 0.2; // 200ms

/**
* A test started.
*
* @param Test $test The test case.
*/
public function startTest( Test $test ): void {
$this->test_start_times[ $test->getName() ] = microtime( true );
}

/**
* A test ended.
*
* @param Test $test The test case.
* @param float $time Time taken.
*/
public function endTest( Test $test, $time ): void { // phpcs:ignore VariableAnalysis.CodeAnalysis.VariableAnalysis.UnusedVariable
$test_name = $test->getName();
if ( ! isset( $this->test_start_times[ $test_name ] ) ) {
return;
}

$duration = microtime( true ) - $this->test_start_times[ $test_name ];
if ( $duration >= $this->slow_threshold ) {
$this->slow_tests[] = array(
'name' => sprintf( '%s::%s', get_class( $test ), $test_name ),
'duration' => $duration,
);
}

unset( $this->test_start_times[ $test_name ] );
}

/**
* A test suite ended.
*
* @param TestSuite $suite The test suite.
*/
public function endTestSuite( TestSuite $suite ): void {
if ( $suite->getName() === 'ActivityPub' && ! empty( $this->slow_tests ) ) {
usort(
$this->slow_tests,
function ( $a, $b ) {
return $b['duration'] <=> $a['duration'];
}
);

echo "\n\nSlow Tests (>= {$this->slow_threshold}s):\n";
foreach ( $this->slow_tests as $test ) {
printf(
" \033[33m%.3fs\033[0m %s\n",
$test['duration'],
$test['name']
);
}
echo "\n";
}
}
}
67 changes: 58 additions & 9 deletions tests/includes/class-test-mention.php
Original file line number Diff line number Diff line change
Expand Up @@ -29,6 +29,24 @@ class Test_Mention extends \WP_UnitTestCase {
),
);

/**
* Set up the test case.
*/
public function set_up() {
parent::set_up();
add_filter( 'pre_get_remote_metadata_by_actor', array( get_called_class(), 'pre_get_remote_metadata_by_actor' ), 10, 2 );
add_filter( 'pre_http_request', array( $this, 'pre_http_request' ), 10, 3 );
}

/**
* Tear down the test case.
*/
public function tear_down() {
remove_filter( 'pre_get_remote_metadata_by_actor', array( get_called_class(), 'pre_get_remote_metadata_by_actor' ) );
remove_filter( 'pre_http_request', array( $this, 'pre_http_request' ) );
parent::tear_down();
}

/**
* Test the content.
*
Expand All @@ -39,11 +57,7 @@ class Test_Mention extends \WP_UnitTestCase {
* @param string $content_with_mention The content with mention.
*/
public function test_the_content( $content, $content_with_mention ) {
add_filter( 'pre_get_remote_metadata_by_actor', array( get_called_class(), 'pre_get_remote_metadata_by_actor' ), 10, 2 );
$content = Mention::the_content( $content );
remove_filter( 'pre_get_remote_metadata_by_actor', array( get_called_class(), 'pre_get_remote_metadata_by_actor' ) );

$this->assertEquals( $content_with_mention, $content );
$this->assertEquals( $content_with_mention, Mention::the_content( $content ) );
}

/**
Expand Down Expand Up @@ -77,17 +91,52 @@ public function the_content_provider() {
}

/**
* Filter for get_remote_metadata_by_actor.
* Mock HTTP requests.
*
* @param false|array|\WP_Error $response HTTP response.
* @param array $parsed_args HTTP request arguments.
* @param string $url The request URL.
* @return array|false|\WP_Error
*/
public function pre_http_request( $response, $parsed_args, $url ) {
// Mock responses for remote users.
if ( 'https://notiz.blog/.well-known/webfinger?resource=acct%3Apfefferle%40notiz.blog' === $url ) {
// phpcs:ignore WordPress.WP.AlternativeFunctions.file_get_contents_file_get_contents
return json_decode( file_get_contents( AP_TESTS_DIR . '/fixtures/notiz-blog-well-known-webfinger.json' ), true );
}

if ( 'https://lemmy.ml/.well-known/webfinger?resource=acct%3Apfefferle%40lemmy.ml' === $url ) {
// phpcs:ignore WordPress.WP.AlternativeFunctions.file_get_contents_file_get_contents
return json_decode( file_get_contents( AP_TESTS_DIR . '/fixtures/lemmy-ml-well-known-webfinger.json' ), true );
}

if ( 'https://notiz.blog/author/matthias-pfefferle/' === $url ) {
// phpcs:ignore WordPress.WP.AlternativeFunctions.file_get_contents_file_get_contents
return json_decode( file_get_contents( AP_TESTS_DIR . '/fixtures/notiz-blog-author-matthias-pfefferle.json' ), true );
}

if ( 'https://lemmy.ml/u/pfefferle' === $url ) {
// phpcs:ignore WordPress.WP.AlternativeFunctions.file_get_contents_file_get_contents
return json_decode( file_get_contents( AP_TESTS_DIR . '/fixtures/lemmy-ml-u-pfefferle.json' ), true );
}

return $response;
}

/**
* Filters remote metadata by actor.
*
* @param string $pre The pre.
* @param string $actor The actor.
* @return array
* @param array|string $pre The pre-filtered value.
* @param string $actor The actor.
* @return array|string
*/
public static function pre_get_remote_metadata_by_actor( $pre, $actor ) {
$actor = ltrim( $actor, '@' );

if ( isset( self::$users[ $actor ] ) ) {
return self::$users[ $actor ];
}

return $pre;
}
}
Loading

0 comments on commit 6a7de00

Please sign in to comment.