Miscellaneous code hacking fixes:
- Fix how CodeHackerTestHook::executeBeforeTest parses the test name, to account for warnings and tests with data sets. - CodeHackerTestHook now includes a executeAfterTest hook that disables the code hacker (needed to prevent it from inadvertently altering further tests). Also, clear_hacks is executed in executeBeforeTest for the same reason. - CodeHacker gets restore, clear_hacks and is_enabled methods to support the changes in CodeHackerTestHook. - FunctionsMockerHack fixed so that it doesn't modify strings that are class method definitions. - Added the WC_Unit_Test_Case::file_copy method, it must be used instead of the PHP built-in "copy" in tests, otherwise tests that run with the code hacker active will fail. This is something to investigate.
This commit is contained in:
parent
9a5b3b353d
commit
1a68abbc28
|
@ -1,6 +1,7 @@
|
|||
<?php
|
||||
|
||||
use PHPUnit\Runner\BeforeTestHook;
|
||||
use PHPUnit\Runner\AfterTestHook;
|
||||
|
||||
/**
|
||||
* Helper to use the CodeHacker class in PHPUnit.
|
||||
|
@ -44,19 +45,37 @@ use PHPUnit\Runner\BeforeTestHook;
|
|||
* Parameters specified after the class name will be passed to the class constructor.
|
||||
* Hacks defined as class annotations will be applied to all tests.
|
||||
*/
|
||||
final class CodeHackerTestHook implements BeforeTestHook {
|
||||
final class CodeHackerTestHook implements BeforeTestHook, AfterTestHook {
|
||||
|
||||
public function __construct() {
|
||||
include_once __DIR__ . '/code-hacker.php';
|
||||
}
|
||||
|
||||
public function executeAfterTest( string $test, float $time ): void {
|
||||
CodeHacker::restore();
|
||||
}
|
||||
|
||||
public function executeBeforeTest( string $test ): void {
|
||||
$parts = explode( '::', $test );
|
||||
/**
|
||||
* Possible formats of $test:
|
||||
* TestClass::TestMethod
|
||||
* TestClass::TestMethod with data set #...
|
||||
* Warning
|
||||
*/
|
||||
$parts = explode( '::', $test );
|
||||
if ( count( $parts ) < 2 ) {
|
||||
return;
|
||||
}
|
||||
$class_name = $parts[0];
|
||||
$method_name = $parts[1];
|
||||
$method_name = explode( ' ', $parts[1] )[0];
|
||||
|
||||
// Make code hacker class and individual hack classes visible
|
||||
include_once __DIR__ . '/code-hacker.php';
|
||||
foreach ( glob( __DIR__ . '/hacks/*.php' ) as $hack_class_file ) {
|
||||
include_once $hack_class_file;
|
||||
}
|
||||
|
||||
CodeHacker::clear_hacks();
|
||||
|
||||
$this->execute_before_methods( $class_name, $method_name );
|
||||
|
||||
$has_class_annotation_hacks = $this->add_hacks_from_annotations( new ReflectionClass( $class_name ) );
|
||||
|
|
|
@ -33,9 +33,29 @@ class CodeHacker {
|
|||
|
||||
private static $hacks = array();
|
||||
|
||||
private static $enabled = false;
|
||||
|
||||
public static function enable() {
|
||||
stream_wrapper_unregister( self::PROTOCOL );
|
||||
stream_wrapper_register( self::PROTOCOL, __CLASS__ );
|
||||
if ( ! self::$enabled ) {
|
||||
stream_wrapper_unregister( self::PROTOCOL );
|
||||
stream_wrapper_register( self::PROTOCOL, __CLASS__ );
|
||||
self::$enabled = true;
|
||||
}
|
||||
}
|
||||
|
||||
public static function restore() {
|
||||
if ( self::$enabled ) {
|
||||
stream_wrapper_restore( self::PROTOCOL );
|
||||
self::$enabled = false;
|
||||
}
|
||||
}
|
||||
|
||||
public static function clear_hacks() {
|
||||
self::$hacks = array();
|
||||
}
|
||||
|
||||
public static function is_enabled() {
|
||||
return self::$enabled;
|
||||
}
|
||||
|
||||
public static function add_hack( $hack ) {
|
||||
|
@ -98,6 +118,7 @@ class CodeHacker {
|
|||
|
||||
|
||||
public function stream_close() {
|
||||
// echo "***** CLOSE HANDLE: " . $this->handle . " \n";
|
||||
fclose( $this->handle );
|
||||
}
|
||||
|
||||
|
|
|
@ -38,6 +38,16 @@ abstract class CodeHack {
|
|||
return is_array( $token ) && $type === $token[0];
|
||||
}
|
||||
|
||||
/**
|
||||
* Return the type of a given token.
|
||||
*
|
||||
* @param mixed $token Token to check.
|
||||
* @return mixed|null Type of token (see https://www.php.net/manual/en/tokens.php), or null if it's a character.
|
||||
*/
|
||||
protected function token_type_of( $token ) {
|
||||
return is_array( $token ) ? $token[0] : null;
|
||||
}
|
||||
|
||||
/**
|
||||
* Converts a token to its string representation.
|
||||
*
|
||||
|
|
|
@ -14,6 +14,13 @@ require_once __DIR__ . '/code-hack.php';
|
|||
*/
|
||||
final class FunctionsMockerHack extends CodeHack {
|
||||
|
||||
private static $non_global_function_tokens = array(
|
||||
T_PAAMAYIM_NEKUDOTAYIM,
|
||||
T_DOUBLE_COLON,
|
||||
T_OBJECT_OPERATOR,
|
||||
T_FUNCTION,
|
||||
);
|
||||
|
||||
/**
|
||||
* FunctionsMockerHack constructor.
|
||||
*
|
||||
|
@ -35,16 +42,19 @@ final class FunctionsMockerHack extends CodeHack {
|
|||
}
|
||||
|
||||
public function hack( $code, $path ) {
|
||||
$tokens = $this->tokenize( $code );
|
||||
$code = '';
|
||||
$previous_token_is_object_operator = false;
|
||||
$tokens = $this->tokenize( $code );
|
||||
$code = '';
|
||||
$previous_token_is_non_global_function_qualifier = false;
|
||||
|
||||
foreach ( $tokens as $token ) {
|
||||
if ( $this->is_token_of_type( $token, T_STRING ) && ! $previous_token_is_object_operator && in_array( $token[1], $this->mocked_methods ) ) {
|
||||
$token_type = $this->token_type_of( $token );
|
||||
if ( T_WHITESPACE === $token_type ) {
|
||||
$code .= $this->token_to_string( $token );
|
||||
} elseif ( T_STRING === $token_type && ! $previous_token_is_non_global_function_qualifier && in_array( $token[1], $this->mocked_methods ) ) {
|
||||
$code .= "{$this->mock_class}::{$token[1]}";
|
||||
} else {
|
||||
$code .= $this->token_to_string( $token );
|
||||
$previous_token_is_object_operator = $this->is_token_of_type( $token, T_DOUBLE_COLON ) || $this->is_token_of_type( $token, T_OBJECT_OPERATOR );
|
||||
$code .= $this->token_to_string( $token );
|
||||
$previous_token_is_non_global_function_qualifier = in_array( $token_type, self::$non_global_function_tokens );
|
||||
}
|
||||
}
|
||||
|
||||
|
|
|
@ -99,4 +99,27 @@ class WC_Unit_Test_Case extends WP_HTTP_TestCase {
|
|||
$message = $message ? $message : "We're all doomed!";
|
||||
throw new Exception( $message, $code );
|
||||
}
|
||||
|
||||
|
||||
/**
|
||||
* Copies a file, temporarily disabling the code hacker.
|
||||
* Use this instead of "copy" in tests for compatibility with the code hacker.
|
||||
*
|
||||
* TODO: Investigate why invoking "copy" within a test with the code hacker active causes the test to fail.
|
||||
*
|
||||
* @param string $source Path to the source file.
|
||||
* @param string $dest The destination path.
|
||||
* @param resource $context [optional] A valid context resource created with stream_context_create.
|
||||
* @return bool true on success or false on failure.
|
||||
*/
|
||||
public function file_copy( $source, $dest, $context = null ) {
|
||||
if ( CodeHacker::is_enabled() ) {
|
||||
CodeHacker::restore();
|
||||
$result = copy( $source, $dest, $context );
|
||||
CodeHacker::enable();
|
||||
return $result;
|
||||
} else {
|
||||
return copy( $source, $dest, $context );
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
|
@ -147,7 +147,7 @@ class WC_Tests_Product_CSV_Importer extends WC_Unit_Test_Case {
|
|||
* @return void
|
||||
*/
|
||||
public function test_server_file() {
|
||||
copy( $this->csv_file, ABSPATH . '/sample.csv' );
|
||||
$this->file_copy( $this->csv_file, ABSPATH . '/sample.csv' );
|
||||
$_POST['file_url'] = 'sample.csv';
|
||||
$import_controller = new WC_Product_CSV_Importer_Controller();
|
||||
$this->assertEquals( ABSPATH . 'sample.csv', $import_controller->handle_upload() );
|
||||
|
@ -644,7 +644,7 @@ class WC_Tests_Product_CSV_Importer extends WC_Unit_Test_Case {
|
|||
if ( false !== strpos( $url, 'http://demo.woothemes.com' ) ) {
|
||||
|
||||
if ( ! empty( $request['filename'] ) ) {
|
||||
copy( WC_Unit_Tests_Bootstrap::instance()->tests_dir . '/data/Dr1Bczxq4q.png', $request['filename'] );
|
||||
$this->file_copy( WC_Unit_Tests_Bootstrap::instance()->tests_dir . '/data/Dr1Bczxq4q.png', $request['filename'] );
|
||||
}
|
||||
|
||||
$mocked_response = array(
|
||||
|
|
|
@ -126,7 +126,7 @@ class WC_Tests_MaxMind_Database extends WC_Unit_Test_Case {
|
|||
|
||||
if ( 'https://download.maxmind.com/app/geoip_download?edition_id=GeoLite2-Country&license_key=testing_license&suffix=tar.gz' === $url ) {
|
||||
// We need to copy the file to where the request is supposed to have streamed it.
|
||||
copy( WC_Unit_Tests_Bootstrap::instance()->tests_dir . '/data/GeoLite2-Country.tar.gz', $request['filename'] );
|
||||
$this->file_copy( WC_Unit_Tests_Bootstrap::instance()->tests_dir . '/data/GeoLite2-Country.tar.gz', $request['filename'] );
|
||||
|
||||
$mocked_response = array(
|
||||
'response' => array( 'code' => 200 ),
|
||||
|
|
|
@ -236,14 +236,14 @@ class WC_Tests_API_Functions extends WC_Unit_Test_Case {
|
|||
} elseif ( 'http://somedomain.com/invalid-image-2.png' === $url ) {
|
||||
// image with an unsupported mime type.
|
||||
// we need to manually copy the file as we are mocking the request. without this an empty file is created.
|
||||
copy( WC_Unit_Tests_Bootstrap::instance()->tests_dir . '/data/file.txt', $request['filename'] );
|
||||
$this->file_copy( WC_Unit_Tests_Bootstrap::instance()->tests_dir . '/data/file.txt', $request['filename'] );
|
||||
|
||||
$mocked_response = array(
|
||||
'response' => array( 'code' => 200 ),
|
||||
);
|
||||
} elseif ( 'http://somedomain.com/' . $this->file_name === $url ) {
|
||||
// we need to manually copy the file as we are mocking the request. without this an empty file is created.
|
||||
copy( WC_Unit_Tests_Bootstrap::instance()->tests_dir . '/data/Dr1Bczxq4q.png', $request['filename'] );
|
||||
$this->file_copy( WC_Unit_Tests_Bootstrap::instance()->tests_dir . '/data/Dr1Bczxq4q.png', $request['filename'] );
|
||||
|
||||
$mocked_response = array(
|
||||
'response' => array( 'code' => 200 ),
|
||||
|
|
Loading…
Reference in New Issue