summaryrefslogtreecommitdiffstatshomepage
diff options
context:
space:
mode:
authorJohn Blackbourn <johnbillion@git.wordpress.org>2025-01-29 18:17:34 +0000
committerJohn Blackbourn <johnbillion@git.wordpress.org>2025-01-29 18:17:34 +0000
commit9acdbb9d8db4eba90d623eefd5ad312cde140593 (patch)
tree0a5df57bf951e925b509ea84b74f54b4884c4796
parent4863a926efbf210f0773b190d06765a5c731dfc3 (diff)
downloadwordpress-9acdbb9d8db4eba90d623eefd5ad312cde140593.tar.gz
wordpress-9acdbb9d8db4eba90d623eefd5ad312cde140593.zip
Build/Test Tools: Add a retry mechanism for tests that perform external HTTP requests.
While the `skipTestOnTimeout()` method will catch a timeout and prevent it from causing a test to fail, other errors such as a failed DNS lookup or HTTPS handshake can still cause a test to unnecessarily fail. This introduces a simple retry mechanism that will hopefully further reduce the flakiness of tests that perform HTTP API requests. Fixes #62830 git-svn-id: https://develop.svn.wordpress.org/trunk@59729 602fd350-edb4-49c9-b593-d223f7449a82
-rw-r--r--tests/phpunit/includes/abstract-testcase.php115
-rw-r--r--tests/phpunit/tests/http/base.php69
-rw-r--r--tests/phpunit/tests/http/functions.php27
-rw-r--r--tests/phpunit/tests/readme.php3
4 files changed, 148 insertions, 66 deletions
diff --git a/tests/phpunit/includes/abstract-testcase.php b/tests/phpunit/includes/abstract-testcase.php
index 224929eb37..b6c82cc066 100644
--- a/tests/phpunit/includes/abstract-testcase.php
+++ b/tests/phpunit/includes/abstract-testcase.php
@@ -1694,4 +1694,119 @@ abstract class WP_UnitTestCase_Base extends PHPUnit_Adapter_TestCase {
touch( $file );
}
+
+ /**
+ * Wrapper for `wp_safe_remote_request()` that retries on error and skips the test on timeout.
+ *
+ * @param string $url URL to retrieve.
+ * @param array $args Optional. Request arguments. Default empty array.
+ * @return array|WP_Error The response or WP_Error on failure.
+ */
+ protected function wp_safe_remote_request( $url, $args = array() ) {
+ return self::retry_on_error( 'wp_safe_remote_request', $url, $args );
+ }
+
+ /**
+ * Wrapper for `wp_safe_remote_get()` that retries on error and skips the test on timeout.
+ *
+ * @param string $url URL to retrieve.
+ * @param array $args Optional. Request arguments. Default empty array.
+ * @return array|WP_Error The response or WP_Error on failure.
+ */
+ protected function wp_safe_remote_get( $url, $args = array() ) {
+ return self::retry_on_error( 'wp_safe_remote_get', $url, $args );
+ }
+
+ /**
+ * Wrapper for `wp_safe_remote_post()` that retries on error and skips the test on timeout.
+ *
+ * @param string $url URL to retrieve.
+ * @param array $args Optional. Request arguments. Default empty array.
+ * @return array|WP_Error The response or WP_Error on failure.
+ */
+ protected function wp_safe_remote_post( $url, $args = array() ) {
+ return self::retry_on_error( 'wp_safe_remote_post', $url, $args );
+ }
+
+ /**
+ * Wrapper for `wp_safe_remote_head()` that retries on error and skips the test on timeout.
+ *
+ * @param string $url URL to retrieve.
+ * @param array $args Optional. Request arguments. Default empty array.
+ * @return array|WP_Error The response or WP_Error on failure.
+ */
+ protected function wp_safe_remote_head( $url, $args = array() ) {
+ return self::retry_on_error( 'wp_safe_remote_head', $url, $args );
+ }
+
+ /**
+ * Wrapper for `wp_remote_request()` that retries on error and skips the test on timeout.
+ *
+ * @param string $url URL to retrieve.
+ * @param array $args Optional. Request arguments. Default empty array.
+ * @return array|WP_Error The response or WP_Error on failure.
+ */
+ protected function wp_remote_request( $url, $args = array() ) {
+ return self::retry_on_error( 'wp_remote_request', $url, $args );
+ }
+
+ /**
+ * Wrapper for `wp_remote_get()` that retries on error and skips the test on timeout.
+ *
+ * @param string $url URL to retrieve.
+ * @param array $args Optional. Request arguments. Default empty array.
+ * @return array|WP_Error The response or WP_Error on failure.
+ */
+ protected function wp_remote_get( $url, $args = array() ) {
+ return self::retry_on_error( 'wp_remote_get', $url, $args );
+ }
+
+ /**
+ * Wrapper for `wp_remote_post()` that retries on error and skips the test on timeout.
+ *
+ * @param string $url URL to retrieve.
+ * @param array $args Optional. Request arguments. Default empty array.
+ * @return array|WP_Error The response or WP_Error on failure.
+ */
+ protected function wp_remote_post( $url, $args = array() ) {
+ return self::retry_on_error( 'wp_remote_post', $url, $args );
+ }
+
+ /**
+ * Wrapper for `wp_remote_head()` that retries on error and skips the test on timeout.
+ *
+ * @param string $url URL to retrieve.
+ * @param array $args Optional. Request arguments. Default empty array.
+ * @return array|WP_Error The response or WP_Error on failure.
+ */
+ protected function wp_remote_head( $url, $args = array() ) {
+ return self::retry_on_error( 'wp_remote_head', $url, $args );
+ }
+
+ /**
+ * Retries an HTTP API request up to three times and skips the test on timeout.
+ *
+ * @param callable $callback The HTTP API request function to call.
+ * @param string $url URL to retrieve.
+ * @param array $args Request arguments.
+ * @return array|WP_Error The response or WP_Error on failure.
+ */
+ private function retry_on_error( callable $callback, $url, $args ) {
+ $attempts = 0;
+
+ while ( $attempts < 3 ) {
+ $result = call_user_func( $callback, $url, $args );
+
+ if ( ! is_wp_error( $result ) ) {
+ return $result;
+ }
+
+ ++$attempts;
+ sleep( 5 );
+ }
+
+ $this->skipTestOnTimeout( $result );
+
+ return $result;
+ }
}
diff --git a/tests/phpunit/tests/http/base.php b/tests/phpunit/tests/http/base.php
index 2e73b0de94..49e4672ad2 100644
--- a/tests/phpunit/tests/http/base.php
+++ b/tests/phpunit/tests/http/base.php
@@ -44,9 +44,8 @@ abstract class WP_HTTP_UnitTestCase extends WP_UnitTestCase {
*/
public function test_redirect_on_301() {
// 5 : 5 & 301.
- $res = wp_remote_request( $this->redirection_script . '?code=301&rt=' . 5, array( 'redirection' => 5 ) );
+ $res = $this->wp_remote_request( $this->redirection_script . '?code=301&rt=' . 5, array( 'redirection' => 5 ) );
- $this->skipTestOnTimeout( $res );
$this->assertNotWPError( $res );
$this->assertSame( 200, (int) $res['response']['code'] );
}
@@ -56,9 +55,8 @@ abstract class WP_HTTP_UnitTestCase extends WP_UnitTestCase {
*/
public function test_redirect_on_302() {
// 5 : 5 & 302.
- $res = wp_remote_request( $this->redirection_script . '?code=302&rt=' . 5, array( 'redirection' => 5 ) );
+ $res = $this->wp_remote_request( $this->redirection_script . '?code=302&rt=' . 5, array( 'redirection' => 5 ) );
- $this->skipTestOnTimeout( $res );
$this->assertNotWPError( $res );
$this->assertSame( 200, (int) $res['response']['code'] );
}
@@ -70,9 +68,8 @@ abstract class WP_HTTP_UnitTestCase extends WP_UnitTestCase {
*/
public function test_redirect_on_301_no_redirect() {
// 5 > 0 & 301.
- $res = wp_remote_request( $this->redirection_script . '?code=301&rt=' . 5, array( 'redirection' => 0 ) );
+ $res = $this->wp_remote_request( $this->redirection_script . '?code=301&rt=' . 5, array( 'redirection' => 0 ) );
- $this->skipTestOnTimeout( $res );
$this->assertNotWPError( $res );
$this->assertSame( 301, (int) $res['response']['code'] );
}
@@ -84,9 +81,8 @@ abstract class WP_HTTP_UnitTestCase extends WP_UnitTestCase {
*/
public function test_redirect_on_302_no_redirect() {
// 5 > 0 & 302.
- $res = wp_remote_request( $this->redirection_script . '?code=302&rt=' . 5, array( 'redirection' => 0 ) );
+ $res = $this->wp_remote_request( $this->redirection_script . '?code=302&rt=' . 5, array( 'redirection' => 0 ) );
- $this->skipTestOnTimeout( $res );
$this->assertNotWPError( $res );
$this->assertSame( 302, (int) $res['response']['code'] );
}
@@ -96,9 +92,8 @@ abstract class WP_HTTP_UnitTestCase extends WP_UnitTestCase {
*/
public function test_redirections_equal() {
// 5 - 5.
- $res = wp_remote_request( $this->redirection_script . '?rt=' . 5, array( 'redirection' => 5 ) );
+ $res = $this->wp_remote_request( $this->redirection_script . '?rt=' . 5, array( 'redirection' => 5 ) );
- $this->skipTestOnTimeout( $res );
$this->assertNotWPError( $res );
$this->assertSame( 200, (int) $res['response']['code'] );
}
@@ -108,9 +103,8 @@ abstract class WP_HTTP_UnitTestCase extends WP_UnitTestCase {
*/
public function test_no_head_redirections() {
// No redirections on HEAD request.
- $res = wp_remote_request( $this->redirection_script . '?code=302&rt=' . 1, array( 'method' => 'HEAD' ) );
+ $res = $this->wp_remote_request( $this->redirection_script . '?code=302&rt=' . 1, array( 'method' => 'HEAD' ) );
- $this->skipTestOnTimeout( $res );
$this->assertNotWPError( $res );
$this->assertSame( 302, (int) $res['response']['code'] );
}
@@ -122,7 +116,7 @@ abstract class WP_HTTP_UnitTestCase extends WP_UnitTestCase {
*/
public function test_redirect_on_head() {
// Redirections on HEAD request when Requested.
- $res = wp_remote_request(
+ $res = $this->wp_remote_request(
$this->redirection_script . '?rt=' . 5,
array(
'redirection' => 5,
@@ -130,7 +124,6 @@ abstract class WP_HTTP_UnitTestCase extends WP_UnitTestCase {
)
);
- $this->skipTestOnTimeout( $res );
$this->assertNotWPError( $res );
$this->assertSame( 200, (int) $res['response']['code'] );
}
@@ -140,9 +133,8 @@ abstract class WP_HTTP_UnitTestCase extends WP_UnitTestCase {
*/
public function test_redirections_greater() {
// 10 > 5.
- $res = wp_remote_request( $this->redirection_script . '?rt=' . 10, array( 'redirection' => 5 ) );
+ $res = $this->wp_remote_request( $this->redirection_script . '?rt=' . 10, array( 'redirection' => 5 ) );
- $this->skipTestOnTimeout( $res );
$this->assertWPError( $res );
}
@@ -151,9 +143,8 @@ abstract class WP_HTTP_UnitTestCase extends WP_UnitTestCase {
*/
public function test_redirections_greater_edgecase() {
// 6 > 5 (close edge case).
- $res = wp_remote_request( $this->redirection_script . '?rt=' . 6, array( 'redirection' => 5 ) );
+ $res = $this->wp_remote_request( $this->redirection_script . '?rt=' . 6, array( 'redirection' => 5 ) );
- $this->skipTestOnTimeout( $res );
$this->assertWPError( $res );
}
@@ -162,9 +153,8 @@ abstract class WP_HTTP_UnitTestCase extends WP_UnitTestCase {
*/
public function test_redirections_less_edgecase() {
// 4 < 5 (close edge case).
- $res = wp_remote_request( $this->redirection_script . '?rt=' . 4, array( 'redirection' => 5 ) );
+ $res = $this->wp_remote_request( $this->redirection_script . '?rt=' . 4, array( 'redirection' => 5 ) );
- $this->skipTestOnTimeout( $res );
$this->assertNotWPError( $res );
}
@@ -175,9 +165,8 @@ abstract class WP_HTTP_UnitTestCase extends WP_UnitTestCase {
*/
public function test_redirections_zero_redirections_specified() {
// 0 redirections asked for, should return the document?
- $res = wp_remote_request( $this->redirection_script . '?code=302&rt=' . 5, array( 'redirection' => 0 ) );
+ $res = $this->wp_remote_request( $this->redirection_script . '?code=302&rt=' . 5, array( 'redirection' => 0 ) );
- $this->skipTestOnTimeout( $res );
$this->assertNotWPError( $res );
$this->assertSame( 302, (int) $res['response']['code'] );
}
@@ -191,9 +180,8 @@ abstract class WP_HTTP_UnitTestCase extends WP_UnitTestCase {
*/
public function test_location_header_on_201() {
// Prints PASS on initial load, FAIL if the client follows the specified redirection.
- $res = wp_remote_request( $this->redirection_script . '?201-location=true' );
+ $res = $this->wp_remote_request( $this->redirection_script . '?201-location=true' );
- $this->skipTestOnTimeout( $res );
$this->assertNotWPError( $res );
$this->assertSame( 'PASS', $res['body'] );
}
@@ -210,7 +198,7 @@ abstract class WP_HTTP_UnitTestCase extends WP_UnitTestCase {
$url = 'http://api.wordpress.org/core/tests/1.0/redirection.php?201-location=1';
// Test 301 - POST to POST.
- $res = wp_remote_request(
+ $res = $this->wp_remote_request(
$url,
array(
'method' => 'PUT',
@@ -218,7 +206,6 @@ abstract class WP_HTTP_UnitTestCase extends WP_UnitTestCase {
)
);
- $this->skipTestOnTimeout( $res );
$this->assertNotWPError( $res );
$this->assertSame( 'PASS', wp_remote_retrieve_body( $res ) );
$this->assertNotEmpty( $res['headers']['location'] );
@@ -236,9 +223,8 @@ abstract class WP_HTTP_UnitTestCase extends WP_UnitTestCase {
'test2' => 0,
'test3' => '',
);
- $res = wp_remote_request( $this->redirection_script . '?header-check', array( 'headers' => $headers ) );
+ $res = $this->wp_remote_request( $this->redirection_script . '?header-check', array( 'headers' => $headers ) );
- $this->skipTestOnTimeout( $res );
$this->assertNotWPError( $res );
$headers = array();
@@ -267,7 +253,7 @@ abstract class WP_HTTP_UnitTestCase extends WP_UnitTestCase {
public function test_file_stream() {
$url = $this->file_stream_url;
$size = 153204;
- $res = wp_remote_request(
+ $res = $this->wp_remote_request(
$url,
array(
'stream' => true,
@@ -281,7 +267,6 @@ abstract class WP_HTTP_UnitTestCase extends WP_UnitTestCase {
unlink( $res['filename'] );
}
- $this->skipTestOnTimeout( $res );
$this->assertNotWPError( $res );
$this->assertSame( '', $res['body'] ); // The body should be empty.
$this->assertEquals( $size, $res['headers']['Content-Length'] ); // Check the headers are returned (and the size is the same).
@@ -297,7 +282,7 @@ abstract class WP_HTTP_UnitTestCase extends WP_UnitTestCase {
public function test_file_stream_limited_size() {
$url = $this->file_stream_url;
$size = 10000;
- $res = wp_remote_request(
+ $res = $this->wp_remote_request(
$url,
array(
'stream' => true,
@@ -312,7 +297,6 @@ abstract class WP_HTTP_UnitTestCase extends WP_UnitTestCase {
unlink( $res['filename'] );
}
- $this->skipTestOnTimeout( $res );
$this->assertNotWPError( $res );
$this->assertSame( $size, $filesize ); // Check that the file is written to disk correctly without any extra characters.
}
@@ -328,7 +312,7 @@ abstract class WP_HTTP_UnitTestCase extends WP_UnitTestCase {
$url = $this->file_stream_url;
$size = 10000;
- $res = wp_remote_request(
+ $res = $this->wp_remote_request(
$url,
array(
'timeout' => 30,
@@ -336,7 +320,6 @@ abstract class WP_HTTP_UnitTestCase extends WP_UnitTestCase {
)
);
- $this->skipTestOnTimeout( $res );
$this->assertNotWPError( $res );
$this->assertSame( $size, strlen( $res['body'] ) );
}
@@ -354,9 +337,8 @@ abstract class WP_HTTP_UnitTestCase extends WP_UnitTestCase {
public function test_post_redirect_to_method_300( $response_code, $method ) {
$url = 'http://api.wordpress.org/core/tests/1.0/redirection.php?post-redirect-to-method=1';
- $res = wp_remote_post( add_query_arg( 'response_code', $response_code, $url ), array( 'timeout' => 30 ) );
+ $res = $this->wp_remote_post( add_query_arg( 'response_code', $response_code, $url ), array( 'timeout' => 30 ) );
- $this->skipTestOnTimeout( $res );
$this->assertNotWPError( $res );
$this->assertSame( $method, wp_remote_retrieve_body( $res ) );
}
@@ -405,9 +387,8 @@ abstract class WP_HTTP_UnitTestCase extends WP_UnitTestCase {
'redirection' => 0,
);
- $res = wp_remote_get( $url, $args );
+ $res = $this->wp_remote_get( $url, $args );
- $this->skipTestOnTimeout( $res );
$this->assertNotWPError( $res );
$this->assertSame( 'PASS', wp_remote_retrieve_body( $res ) );
}
@@ -427,11 +408,10 @@ abstract class WP_HTTP_UnitTestCase extends WP_UnitTestCase {
add_filter( 'http_request_args', array( $this, 'filter_http_request_args' ) );
- $res = wp_remote_head( $url, $args );
+ $res = $this->wp_remote_head( $url, $args );
remove_filter( 'http_request_args', array( $this, 'filter_http_request_args' ) );
- $this->skipTestOnTimeout( $res );
$this->assertNotEmpty( $this->http_request_args['sslcertificates'] );
$this->assertNotWPError( $res );
}
@@ -447,9 +427,8 @@ abstract class WP_HTTP_UnitTestCase extends WP_UnitTestCase {
public function test_cookie_handling() {
$url = 'http://api.wordpress.org/core/tests/1.0/redirection.php?cookie-test=1';
- $res = wp_remote_get( $url );
+ $res = $this->wp_remote_get( $url );
- $this->skipTestOnTimeout( $res );
$this->assertNotWPError( $res );
$this->assertSame( 'PASS', wp_remote_retrieve_body( $res ) );
}
@@ -467,9 +446,8 @@ abstract class WP_HTTP_UnitTestCase extends WP_UnitTestCase {
$this->fail( 'This installation of PHP does not support SSL.' );
}
- $res = wp_remote_get( 'https://wordpress.org/' );
+ $res = $this->wp_remote_get( 'https://wordpress.org/' );
- $this->skipTestOnTimeout( $res );
$this->assertNotWPError( $res );
}
@@ -484,9 +462,8 @@ abstract class WP_HTTP_UnitTestCase extends WP_UnitTestCase {
$path = parse_url( $url, PHP_URL_PATH );
$url = str_replace( $path, '/' . $path, $url );
- $res = wp_remote_request( $url );
+ $res = $this->wp_remote_request( $url );
- $this->skipTestOnTimeout( $res );
$this->assertNotWPError( $res );
}
}
diff --git a/tests/phpunit/tests/http/functions.php b/tests/phpunit/tests/http/functions.php
index 9c3e54a8b8..96c6e4db2f 100644
--- a/tests/phpunit/tests/http/functions.php
+++ b/tests/phpunit/tests/http/functions.php
@@ -12,9 +12,8 @@ class Tests_HTTP_Functions extends WP_UnitTestCase {
public function test_head_request() {
// This URL gives a direct 200 response.
$url = 'https://s.w.org/screenshots/3.9/dashboard.png';
- $response = wp_remote_head( $url );
+ $response = $this->wp_remote_head( $url );
- $this->skipTestOnTimeout( $response );
$this->assertNotWPError( $response );
$headers = wp_remote_retrieve_headers( $response );
@@ -31,9 +30,8 @@ class Tests_HTTP_Functions extends WP_UnitTestCase {
public function test_head_redirect() {
// This URL will 301 redirect.
$url = 'https://wp.org/screenshots/3.9/dashboard.png';
- $response = wp_remote_head( $url );
+ $response = $this->wp_remote_head( $url );
- $this->skipTestOnTimeout( $response );
$this->assertNotWPError( $response );
$this->assertSame( 301, wp_remote_retrieve_response_code( $response ) );
}
@@ -43,9 +41,8 @@ class Tests_HTTP_Functions extends WP_UnitTestCase {
*/
public function test_head_404() {
$url = 'https://wordpress.org/screenshots/3.9/awefasdfawef.jpg';
- $response = wp_remote_head( $url );
+ $response = $this->wp_remote_head( $url );
- $this->skipTestOnTimeout( $response );
$this->assertNotWPError( $response );
$this->assertSame( 404, wp_remote_retrieve_response_code( $response ) );
}
@@ -58,9 +55,8 @@ class Tests_HTTP_Functions extends WP_UnitTestCase {
public function test_get_request() {
$url = 'https://s.w.org/screenshots/3.9/dashboard.png';
- $response = wp_remote_get( $url );
+ $response = $this->wp_remote_get( $url );
- $this->skipTestOnTimeout( $response );
$this->assertNotWPError( $response );
$headers = wp_remote_retrieve_headers( $response );
@@ -80,9 +76,8 @@ class Tests_HTTP_Functions extends WP_UnitTestCase {
// This will redirect to wordpress.org.
$url = 'https://wp.org/screenshots/3.9/dashboard.png';
- $response = wp_remote_get( $url );
+ $response = $this->wp_remote_get( $url );
- $this->skipTestOnTimeout( $response );
$this->assertNotWPError( $response );
$headers = wp_remote_retrieve_headers( $response );
@@ -101,9 +96,8 @@ class Tests_HTTP_Functions extends WP_UnitTestCase {
$url = 'https://wp.org/screenshots/3.9/dashboard.png';
// Pretend we've already redirected 5 times.
- $response = wp_remote_get( $url, array( 'redirection' => -1 ) );
+ $response = $this->wp_remote_get( $url, array( 'redirection' => -1 ) );
- $this->skipTestOnTimeout( $response );
$this->assertWPError( $response );
}
@@ -118,9 +112,8 @@ class Tests_HTTP_Functions extends WP_UnitTestCase {
public function test_get_response_cookies() {
$url = 'https://login.wordpress.org/wp-login.php';
- $response = wp_remote_head( $url );
+ $response = $this->wp_remote_head( $url );
- $this->skipTestOnTimeout( $response );
$this->assertNotWPError( $response );
$cookies = wp_remote_retrieve_cookies( $response );
@@ -152,7 +145,7 @@ class Tests_HTTP_Functions extends WP_UnitTestCase {
public function test_get_response_cookies_with_wp_http_cookie_object() {
$url = 'https://login.wordpress.org/wp-login.php';
- $response = wp_remote_get(
+ $response = $this->wp_remote_get(
$url,
array(
'cookies' => array(
@@ -166,7 +159,6 @@ class Tests_HTTP_Functions extends WP_UnitTestCase {
)
);
- $this->skipTestOnTimeout( $response );
$this->assertNotWPError( $response );
$cookies = wp_remote_retrieve_cookies( $response );
@@ -189,7 +181,7 @@ class Tests_HTTP_Functions extends WP_UnitTestCase {
public function test_get_response_cookies_with_name_value_array() {
$url = 'https://login.wordpress.org/wp-login.php';
- $response = wp_remote_get(
+ $response = $this->wp_remote_get(
$url,
array(
'cookies' => array(
@@ -198,7 +190,6 @@ class Tests_HTTP_Functions extends WP_UnitTestCase {
)
);
- $this->skipTestOnTimeout( $response );
$this->assertNotWPError( $response );
$cookies = wp_remote_retrieve_cookies( $response );
diff --git a/tests/phpunit/tests/readme.php b/tests/phpunit/tests/readme.php
index 04faecbb36..85ffffcdf6 100644
--- a/tests/phpunit/tests/readme.php
+++ b/tests/phpunit/tests/readme.php
@@ -91,9 +91,8 @@ class Tests_Readme extends WP_UnitTestCase {
* @return string The response body.
*/
public function get_response_body( $url ) {
- $response = wp_remote_get( $url );
+ $response = $this->wp_remote_get( $url );
- $this->skipTestOnTimeout( $response );
$this->assertNotWPError( $response );
$response_code = wp_remote_retrieve_response_code( $response );