From 6453c48c8fb1c9ebdb0b067d8bf8f9402c44805c Mon Sep 17 00:00:00 2001 From: Steve Grunwell Date: Sat, 31 Oct 2020 16:19:00 -0400 Subject: [PATCH] To avoid users from having to deal with conflict resolution, do away with the Runkit trait in favor of more static methods on the Runkit support class --- composer.json | 2 +- phpstan.neon.dist | 4 ++ src/Concerns/Runkit.php | 79 --------------------- src/Constants.php | 12 ++-- src/Functions.php | 16 +++-- src/Support/Runkit.php | 64 +++++++++++++++++ tests/Concerns/RunkitTest.php | 98 -------------------------- tests/ConstantsTest.php | 18 +++-- tests/FunctionsTest.php | 54 +++++++------- tests/Support/RunkitTest.php | 59 ++++++++++++++++ tests/{Support => stubs}/functions.php | 2 +- 11 files changed, 188 insertions(+), 220 deletions(-) delete mode 100644 src/Concerns/Runkit.php delete mode 100644 tests/Concerns/RunkitTest.php create mode 100644 tests/Support/RunkitTest.php rename tests/{Support => stubs}/functions.php (90%) diff --git a/composer.json b/composer.json index de4e063..c0f3d02 100644 --- a/composer.json +++ b/composer.json @@ -44,7 +44,7 @@ "Tests\\": "tests/" }, "files": [ - "tests/Support/functions.php" + "tests/stubs/functions.php" ] }, "config": { diff --git a/phpstan.neon.dist b/phpstan.neon.dist index a4cb97c..670eeba 100644 --- a/phpstan.neon.dist +++ b/phpstan.neon.dist @@ -24,3 +24,7 @@ parameters: paths: - tests/FixtureTest.php - tests/FunctionsTest.php + + - + message: '#Call to an undefined static method \S+#' + path: tests/Support/RunkitTest.php diff --git a/src/Concerns/Runkit.php b/src/Concerns/Runkit.php deleted file mode 100644 index 3c89908..0000000 --- a/src/Concerns/Runkit.php +++ /dev/null @@ -1,79 +0,0 @@ -isRunkitAvailable()) { - return; - } - - throw new SkippedTestError($message ?: 'This test requires Runkit, skipping.'); - } - - /** - * Determine whether or not Runkit is available in the current environment. - * - * @return bool - */ - protected function isRunkitAvailable() - { - return function_exists('runkit7_constant_redefine') - || function_exists('runkit_constant_redefine'); - } - - /** - * Get the current runkit namespace. - * - * If the property is currently empty, one will be created. - * - * @return string The namespace (with trailing backslash) where we're moving functions, - * constants, etc. during tests. - */ - protected function getRunkitNamespace() - { - if (empty($this->runkitNamespace)) { - $this->runkitNamespace = uniqid(__NAMESPACE__ . '\\runkit_') . '\\'; - } - - return $this->runkitNamespace; - } - - /** - * Namespace the given reference. - * - * @param string $var The item to be moved into the temporary test namespace. - * - * @return string The newly-namespaced item. - */ - protected function runkitNamespace($var) - { - // Strip leading backslashes. - if (0 === mb_strpos($var, '\\')) { - $var = mb_substr($var, 1); - } - - return $this->getRunkitNamespace() . $var; - } -} diff --git a/src/Constants.php b/src/Constants.php index 00ecab3..20a099e 100644 --- a/src/Constants.php +++ b/src/Constants.php @@ -7,8 +7,6 @@ trait Constants { - use Concerns\Runkit; - /** * All constants being handled by this trait. * @@ -43,6 +41,8 @@ protected function restoreConstants() unset($this->constants['created'][$key]); } + + Runkit::reset(); } /** @@ -59,7 +59,9 @@ protected function restoreConstants() */ protected function setConstant($name, $value = null) { - $this->requiresRunkit('setConstant() requires Runkit be available, skipping.'); + if (! Runkit::isAvailable()) { + $this->markTestSkipped('setConstant() requires Runkit be available, skipping.'); + } if (defined($name)) { if (! isset($this->constants['updated'][$name])) { @@ -96,7 +98,9 @@ protected function deleteConstant($name) return $this; } - $this->requiresRunkit('deleteConstant() requires Runkit be available, skipping.'); + if (! Runkit::isAvailable()) { + $this->markTestSkipped('deleteConstant() requires Runkit be available, skipping.'); + } if (! isset($this->constants[$name])) { $this->constants['updated'][$name] = constant($name); diff --git a/src/Functions.php b/src/Functions.php index 22cf953..37a053a 100644 --- a/src/Functions.php +++ b/src/Functions.php @@ -8,8 +8,6 @@ trait Functions { - use Concerns\Runkit; - /** * All functions being handled by this trait. * @@ -41,6 +39,8 @@ protected function restoreFunctions() array_map([Runkit::class, 'function_remove'], $this->functions['defined']); $this->functions['defined'] = []; + + Runkit::reset(); } /** @@ -64,7 +64,9 @@ protected function defineFunction($name, \Closure $closure) )); } - $this->requiresRunkit('defineFunction() requires Runkit be available, skipping.'); + if (! Runkit::isAvailable()) { + $this->markTestSkipped('defineFunction() requires Runkit be available, skipping.'); + } if (! Runkit::function_add($name, $closure)) { throw new RunkitException(sprintf('Unable to define function %1$s().', $name)); @@ -91,11 +93,13 @@ protected function redefineFunction($name, \Closure $closure) return $this->defineFunction($name, $closure); } - $this->requiresRunkit('redefineFunction() requires Runkit be available, skipping.'); + if (! Runkit::isAvailable()) { + $this->markTestSkipped('redefineFunction() requires Runkit be available, skipping.'); + } // Back up the original version of the function. if (! isset($this->functions['redefined'][$name])) { - $namespaced = $this->runkitNamespace($name); + $namespaced = Runkit::makeNamespaced($name); if (! Runkit::function_rename($name, $namespaced)) { throw new RunkitException(sprintf('Unable to back up %1$s(), aborting.', $name)); @@ -126,7 +130,7 @@ protected function deleteFunction($name) return $this; } - $namespaced = $this->runkitNamespace($name); + $namespaced = Runkit::makeNamespaced($name); if (! Runkit::function_rename($name, $namespaced)) { throw new RunkitException(sprintf('Unable to back up %1$s(), aborting.', $name)); diff --git a/src/Support/Runkit.php b/src/Support/Runkit.php index 48a7146..eb3fa69 100644 --- a/src/Support/Runkit.php +++ b/src/Support/Runkit.php @@ -32,6 +32,13 @@ */ class Runkit { + /** + * A namespace used to move things out of the way for the duration of a test. + * + * @var string + */ + private static $namespace; + /** * Dynamically alias methods to the underlying Runkit functions. * @@ -57,4 +64,61 @@ public static function __callStatic($name, array $args = []) $name )); } + + /** + * Determine whether or not Runkit is available in the current environment. + * + * @return bool + */ + public static function isAvailable() + { + return function_exists('runkit7_constant_redefine') + || function_exists('runkit_constant_redefine'); + } + + /** + * Get the current runkit namespace. + * + * If the property is currently empty, one will be created. + * + * @return string The namespace (with trailing backslash) where we're moving functions, + * constants, etc. during tests. + */ + public static function getNamespace() + { + if (empty(self::$namespace)) { + self::$namespace = uniqid(__NAMESPACE__ . '\\runkit_') . '\\'; + } + + return self::$namespace; + } + + /** + * Namespace the given reference. + * + * @param string $var The item to be moved into the temporary test namespace. + * + * @return string The newly-namespaced item. + */ + public static function makeNamespaced($var) + { + // Strip leading backslashes. + if (0 === mb_strpos($var, '\\')) { + $var = mb_substr($var, 1); + } + + return self::getNamespace() . $var; + } + + /** + * Reset static properties. + * + * This is helpful to run before tests in case self::$namespace gets polluted. + * + * @return void + */ + public static function reset() + { + self::$namespace = ''; + } } diff --git a/tests/Concerns/RunkitTest.php b/tests/Concerns/RunkitTest.php deleted file mode 100644 index 120f449..0000000 --- a/tests/Concerns/RunkitTest.php +++ /dev/null @@ -1,98 +0,0 @@ -instance = $this->getMockForTrait(Runkit::class, [], '', true, true, true, [ - 'isRunkitAvailable', - ]); - } - - /** - * @test - */ - public function it_should_permit_tests_to_run_if_runkit_is_available() - { - $this->instance->expects($this->once()) - ->method('isRunkitAvailable') - ->willReturn(true); - - $method = new \ReflectionMethod($this->instance, 'requiresRunkit'); - $method->setAccessible(true); - - $this->assertNull($method->invoke($this->instance)); - } - - /** - * @test - */ - public function it_should_skip_tests_that_require_runkit_if_it_is_unavailable() - { - $this->instance->expects($this->once()) - ->method('isRunkitAvailable') - ->willReturn(false); - - $method = new \ReflectionMethod($this->instance, 'requiresRunkit'); - $method->setAccessible(true); - - // Older versions of PHPUnit will actually try to mark this as skipped. - try { - $method->invoke($this->instance); - } catch (SkippedTestError $e) { - $this->assertInstanceOf(SkippedTestError::class, $e); - return; - } - - $this->fail('Did not catch the expected SkippedTestError.'); - } - - /** - * @test - */ - public function it_should_be_able_to_namespace_values() - { - $namespace = new \ReflectionMethod($this->instance, 'getRunkitNamespace'); - $namespace->setAccessible(true); - $namespace = $namespace->invoke($this->instance); - - $method = new \ReflectionMethod($this->instance, 'runkitNamespace'); - $method->setAccessible(true); - - $this->assertSame( - $namespace . 'some_global_function', - $method->invoke($this->instance, 'some_global_function'), - 'The global namespace should be eligible.' - ); - $this->assertSame( - $namespace . 'Some\\Namespaced\\function_to_move', - $method->invoke($this->instance, 'Some\\Namespaced\\function_to_move'), - 'Namespaces should be preserved.' - ); - $this->assertSame( - $namespace . 'Some\\Namespaced\\function_with_leading_slashes', - $method->invoke($this->instance, '\\Some\\Namespaced\\function_with_leading_slashes'), - 'Leading slashes should be stripped.' - ); - } -} diff --git a/tests/ConstantsTest.php b/tests/ConstantsTest.php index 11d02ab..e3599d2 100644 --- a/tests/ConstantsTest.php +++ b/tests/ConstantsTest.php @@ -3,10 +3,10 @@ namespace Tests; use AssertWell\PHPUnitGlobalState\Exceptions\RedefineException; +use AssertWell\PHPUnitGlobalState\Support\Runkit; use PHPUnit\Framework\SkippedTestError; /** - * @covers AssertWell\PHPUnitGlobalState\Concerns\Runkit * @covers AssertWell\PHPUnitGlobalState\Constants * * @group Constants @@ -31,7 +31,9 @@ public static function defineConstants() */ public function setConstant_should_be_able_to_handle_newly_defined_constants() { - $this->requiresRunkit('This test depends on runkit being available.'); + if (! Runkit::isAvailable()) { + $this->markTestSkipped('This test depends on runkit being available.'); + } $this->assertFalse(defined('SOME_CONSTANT')); @@ -48,7 +50,9 @@ public function setConstant_should_be_able_to_handle_newly_defined_constants() */ public function setConstant_should_be_able_to_redefine_existing_constants() { - $this->requiresRunkit('This test depends on runkit being available.'); + if (! Runkit::isAvailable()) { + $this->markTestSkipped('This test depends on runkit being available.'); + } $this->setConstant('EXISTING_CONSTANT', 'some other value'); $this->assertSame('some other value', constant('EXISTING_CONSTANT')); @@ -67,7 +71,9 @@ public function setConstant_should_be_able_to_redefine_existing_constants() */ public function setConstant_should_throw_an_exception_if_it_cannot_redefine_a_constant() { - $this->requiresRunkit('This test depends on runkit being available.'); + if (! Runkit::isAvailable()) { + $this->markTestSkipped('This test depends on runkit being available.'); + } $this->expectException(RedefineException::class); $this->setConstant('EXISTING_CONSTANT', (object) ['some' => 'object']); @@ -85,7 +91,9 @@ public function setConstant_should_throw_an_exception_if_it_cannot_redefine_a_co */ public function deleteConstant_should_remove_an_existing_constant() { - $this->requiresRunkit('This test depends on runkit being available.'); + if (! Runkit::isAvailable()) { + $this->markTestSkipped('This test depends on runkit being available.'); + } $this->deleteConstant('DELETE_THIS_CONSTANT'); $this->assertFalse(defined('DELETE_THIS_CONSTANT')); diff --git a/tests/FunctionsTest.php b/tests/FunctionsTest.php index 6cb34cb..3aac327 100644 --- a/tests/FunctionsTest.php +++ b/tests/FunctionsTest.php @@ -3,11 +3,11 @@ namespace Tests; use AssertWell\PHPUnitGlobalState\Exceptions\FunctionExistsException; +use AssertWell\PHPUnitGlobalState\Support\Runkit; -use function Tests\Support\sum_three_numbers; +use function Tests\Stubs\sum_three_numbers; /** - * @covers AssertWell\PHPUnitGlobalState\Concerns\Runkit * @covers AssertWell\PHPUnitGlobalState\Functions * * @group Functions @@ -19,7 +19,9 @@ class FunctionsTest extends TestCase */ protected function verifyRunkitIsAvailable() { - $this->requiresRunkit('This test depends on runkit being available.'); + if (! Runkit::isAvailable()) { + $this->markTestSkipped('This test depends on runkit being available.'); + } } /** @@ -46,17 +48,17 @@ public function defineFunction_should_be_able_to_define_a_new_function() */ public function defineFunction_should_throw_a_warning_if_the_function_already_exists() { - $this->assertTrue(function_exists('Tests\Support\sum_three_numbers')); - $signature = (string) (new \ReflectionFunction('Tests\Support\sum_three_numbers')); + $this->assertTrue(function_exists('Tests\\Stubs\\sum_three_numbers')); + $signature = (string) (new \ReflectionFunction('Tests\\Stubs\\sum_three_numbers')); $this->expectException(FunctionExistsException::class); - $this->defineFunction('Tests\Support\sum_three_numbers', function ($return) { + $this->defineFunction('Tests\\Stubs\\sum_three_numbers', function ($return) { return $return; }); $this->assertSame( $signature, - (string) (new \ReflectionFunction('Tests\Support\sum_three_numbers')), + (string) (new \ReflectionFunction('Tests\\Stubs\\sum_three_numbers')), 'The original function should have been left untouched.' ); } @@ -67,20 +69,20 @@ public function defineFunction_should_throw_a_warning_if_the_function_already_ex */ public function redefineFunction_should_be_able_to_redefine_existing_functions() { - $this->assertTrue(function_exists('Tests\Support\sum_three_numbers')); - $signature = (string) (new \ReflectionFunction('Tests\Support\sum_three_numbers')); + $this->assertTrue(function_exists('Tests\\Stubs\\sum_three_numbers')); + $signature = (string) (new \ReflectionFunction('Tests\\Stubs\\sum_three_numbers')); - $this->redefineFunction('Tests\Support\sum_three_numbers', function () { + $this->redefineFunction('Tests\\Stubs\\sum_three_numbers', function () { return 123; }); $this->assertSame(123, sum_three_numbers(1, 2, 3)); $this->restoreFunctions(); - $this->assertTrue(function_exists('Tests\Support\sum_three_numbers')); + $this->assertTrue(function_exists('Tests\\Stubs\\sum_three_numbers')); $this->assertSame( $signature, - (string) (new \ReflectionFunction('Tests\Support\sum_three_numbers')), + (string) (new \ReflectionFunction('Tests\\Stubs\\sum_three_numbers')), 'The original function definition should have been restored.' ); } @@ -114,16 +116,16 @@ function_exists('my_test_function'), */ public function redefineFunction_should_be_able_to_redefine_existing_functions_multiple_times() { - $this->assertTrue(function_exists('Tests\Support\sum_three_numbers')); - $signature = (string) (new \ReflectionFunction('Tests\Support\sum_three_numbers')); + $this->assertTrue(function_exists('Tests\\Stubs\\sum_three_numbers')); + $signature = (string) (new \ReflectionFunction('Tests\\Stubs\\sum_three_numbers')); - $this->redefineFunction('Tests\Support\sum_three_numbers', function () { + $this->redefineFunction('Tests\\Stubs\\sum_three_numbers', function () { return 'first'; }); - $this->redefineFunction('Tests\Support\sum_three_numbers', function () { + $this->redefineFunction('Tests\\Stubs\\sum_three_numbers', function () { return 'second'; }); - $this->redefineFunction('Tests\Support\sum_three_numbers', function () { + $this->redefineFunction('Tests\\Stubs\\sum_three_numbers', function () { return 'third'; }); @@ -136,7 +138,7 @@ public function redefineFunction_should_be_able_to_redefine_existing_functions_m $this->restoreFunctions(); $this->assertSame( $signature, - (string) (new \ReflectionFunction('Tests\Support\sum_three_numbers')), + (string) (new \ReflectionFunction('Tests\\Stubs\\sum_three_numbers')), 'The original function definition should have been restored.' ); } @@ -167,19 +169,19 @@ public function redefineFunction_should_define_functions_if_they_do_not_exist() public function deleteFunction_should_be_able_to_delete_functions() { $this->assertTrue( - function_exists('Tests\Support\sum_three_numbers'), + function_exists('Tests\\Stubs\\sum_three_numbers'), 'Test is predicated on this function existing.' ); - $this->deleteFunction('Tests\Support\sum_three_numbers'); + $this->deleteFunction('Tests\\Stubs\\sum_three_numbers'); $this->assertFalse( - function_exists('Tests\Support\sum_three_numbers'), + function_exists('Tests\\Stubs\\sum_three_numbers'), 'The function should have been deleted.' ); $this->restoreFunctions(); $this->assertTrue( - function_exists('Tests\Support\sum_three_numbers'), + function_exists('Tests\\Stubs\\sum_three_numbers'), 'The function should have been restored.' ); } @@ -191,19 +193,19 @@ function_exists('Tests\Support\sum_three_numbers'), public function deleteFunction_should_do_nothing_if_the_function_does_not_exist() { $this->assertFalse( - function_exists('Tests\Support\sum_three_numbers_again'), + function_exists('Tests\\Stubs\\sum_three_numbers_again'), 'Test is predicated on this function NOT existing.' ); - $this->deleteFunction('Tests\Support\sum_three_numbers_again'); + $this->deleteFunction('Tests\\Stubs\\sum_three_numbers_again'); $this->assertFalse( - function_exists('Tests\Support\sum_three_numbers_again'), + function_exists('Tests\\Stubs\\sum_three_numbers_again'), 'Deleting a non-existent function should not do anything.' ); $this->restoreFunctions(); $this->assertFalse( - function_exists('Tests\Support\sum_three_numbers_again'), + function_exists('Tests\\Stubs\\sum_three_numbers_again'), 'Nothing should be restored as there was nothing to begin with.' ); } diff --git a/tests/Support/RunkitTest.php b/tests/Support/RunkitTest.php new file mode 100644 index 0000000..b9474cf --- /dev/null +++ b/tests/Support/RunkitTest.php @@ -0,0 +1,59 @@ +expectException(\BadFunctionCallException::class); + + Runkit::a_function_that_does_not_exist(); + } + + /** + * @test + */ + public function getNamespace_should_return_the_same_value_on_subsequent_calls() + { + $namespace = Runkit::getNamespace(); + + $this->assertSame(Runkit::getNamespace(), $namespace); + } + + /** + * @test + */ + public function makeNamespaced_should_return_the_given_reference_with_a_prefixed_namespace() + { + $namespace = Runkit::getNamespace(); + + $this->assertSame( + $namespace . 'some_global_function', + Runkit::makeNamespaced('some_global_function'), + 'The global namespace should be eligible.' + ); + $this->assertSame( + $namespace . 'Some\\Namespaced\\function_to_move', + Runkit::makeNamespaced('Some\\Namespaced\\function_to_move'), + 'Namespaces should be preserved.' + ); + $this->assertSame( + $namespace . 'Some\\Namespaced\\function_with_leading_slashes', + Runkit::makeNamespaced('\\Some\\Namespaced\\function_with_leading_slashes'), + 'Leading slashes should be stripped.' + ); + } +} diff --git a/tests/Support/functions.php b/tests/stubs/functions.php similarity index 90% rename from tests/Support/functions.php rename to tests/stubs/functions.php index 201485a..a735ce6 100644 --- a/tests/Support/functions.php +++ b/tests/stubs/functions.php @@ -4,7 +4,7 @@ * Dummy functions for the sake of testing. */ -namespace Tests\Support; +namespace Tests\Stubs; /** * Return the sum of three numbers.