Thanks to visit codestin.com
Credit goes to github.com

Skip to content

Conversation

@mehamasum
Copy link
Contributor

@mehamasum mehamasum commented Feb 9, 2022

Fixes #31

(I know this repo has not been developed in long time but your feedback is appreciated. πŸ˜„ )

The first commit adds tests for #28, to make sure there is no regression.

The second and third commit respectively tests and fixes the problem described in #31.

Without the change in the third commit, the tests would get stuck in the infinite loop or cause Allowed Memory Exhausted error.

Testing instructions

Please run tests with a memory limit (i.e. vendor/bin/phpunit -d memory_limit=24M) so that tests are not stuck in a loop for a long time.

Apply this patch (to remove @runTestsInSeparateProcesses and add finish_spying)
Index: tests/StubTest.php
IDEA additional info:
Subsystem: com.intellij.openapi.diff.impl.patch.CharsetEP
<+>UTF-8
===================================================================
diff --git a/tests/StubTest.php b/tests/StubTest.php
--- a/tests/StubTest.php	
+++ b/tests/StubTest.php	
@@ -1,9 +1,11 @@
<?php

-/**
- * @runTestsInSeparateProcesses
- */
+
class StubTest extends PHPUnit_Framework_TestCase {
+	public function tearDown() {
+		\Spies\finish_spying();
+	}
+
  public function test_mock_function_returns_a_spy() {
  	$stub = \Spies\mock_function( 'test_stub' );
  	$this->assertTrue( $stub instanceof \Spies\Spy );
Index: tests/SpyTest.php
IDEA additional info:
Subsystem: com.intellij.openapi.diff.impl.patch.CharsetEP
<+>UTF-8
===================================================================
diff --git a/tests/SpyTest.php b/tests/SpyTest.php
--- a/tests/SpyTest.php	
+++ b/tests/SpyTest.php	
@@ -7,10 +7,12 @@
  return $val;
}

-/**
- * @runTestsInSeparateProcesses
- */
+
class SpyTest extends PHPUnit_Framework_TestCase {
+	public function tearDown() {
+		\Spies\finish_spying();
+	}
+
  public function test_spy_was_called_returns_true_if_called() {
  	$spy = new \Spies\Spy();
  	$spy();
Index: tests/MockObjectTest.php
IDEA additional info:
Subsystem: com.intellij.openapi.diff.impl.patch.CharsetEP
<+>UTF-8
===================================================================
diff --git a/tests/MockObjectTest.php b/tests/MockObjectTest.php
--- a/tests/MockObjectTest.php	
+++ b/tests/MockObjectTest.php	
@@ -14,10 +14,12 @@
  }
}

-/**
- * @runTestsInSeparateProcesses
- */
+
class MockObjectTest extends PHPUnit_Framework_TestCase {
+	public function tearDown() {
+		\Spies\finish_spying();
+	}
+
  public function test_mock_object_returns_mock_object() {
  	$mock = \Spies\mock_object();
  	$this->assertTrue( $mock instanceof \Spies\MockObject );

  • on master, comment out self::$global_functions = []; in src/Spies/GlobalSpies.php (one liner change from Forget the name of the global functions on restoreΒ #28) run the tests; some tests fail, so there could be come problems with finish_spying (indicates the problem that PR tries to fix)
  • on master, revert the one liner change and run tests again: tests get memory limit error, so the infinite loop is possibly introduced (indicates the problem this PR is trying to fix)
  • checkout first commit of this branch, run the tests: no changes, so both issues remain but there is test for first issue now
  • checkout second commit of this branch, run the tests: no changes, so both issues remain but there is test for both issues now
  • checkout third commit of this branch, run the tests: tests pass, so both issues should be resolved by now

Comment on lines -124 to -127
if ( isset( $value ) ) {
return self::filter_return_for( $value );
}
return \Patchwork\relay();
Copy link
Contributor Author

@mehamasum mehamasum Feb 9, 2022

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I am not entirely sure why we need to relay here.
I did not intend to change this part (this is not directly part of the fix) but without this, the call count of a stubbed method gets increased. For example:

1) ConsecutiveExecutionTest::test__undefined_function___can_be_restubbed
Failed asserting that 2 matches expected 1.

Copy link
Owner

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I'm having trouble finding the docs for this version of Patchwork to remember what this did exactly, but I assume the reason we relay here is so that a global function could be spied upon without needing to also mock it. Something like:

function getName() {
  return 'human';
}
//...
$spy = \Spies\get_spy_for( 'getName' );
$name = getName();
$this->assertSame( 'human', $name );
$this->assertTrue( $spy->was_called() );

If this is removed, won't that prevent the original global function from being called?

Although, I see that you have a test test__global_function__can_be_restubbed which covers this case, and it passes. How does that work?

Copy link
Owner

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Ok, I followed the code and it looks like when GlobalSpies::handle_call_for() is called, it eventually calls GlobalSpies::call_original_global_function() thanks to Spy->get_return_for(), which uses \Patchwork\restore() to call the function before recreating the mock with GlobalSpies::replace_global_function().

That explains why this works. Maybe that wasn't always the case which would explain the relay? Or maybe there's another edge case we're not considering here. 🀷

@mehamasum mehamasum marked this pull request as ready for review February 9, 2022 21:57
@mehamasum mehamasum marked this pull request as draft February 9, 2022 22:08
@mehamasum mehamasum force-pushed the fix/infinite-loop-on-restub-invoke branch from c01ea2d to cff9765 Compare February 10, 2022 15:43
@mehamasum mehamasum force-pushed the fix/infinite-loop-on-restub-invoke branch from cff9765 to 6a8bc03 Compare February 10, 2022 16:09
@mehamasum mehamasum marked this pull request as ready for review February 10, 2022 16:47
Copy link
Owner

@sirbrillig sirbrillig left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Looks fine to me! Thank you so much for adding tests.

Comment on lines -124 to -127
if ( isset( $value ) ) {
return self::filter_return_for( $value );
}
return \Patchwork\relay();
Copy link
Owner

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I'm having trouble finding the docs for this version of Patchwork to remember what this did exactly, but I assume the reason we relay here is so that a global function could be spied upon without needing to also mock it. Something like:

function getName() {
  return 'human';
}
//...
$spy = \Spies\get_spy_for( 'getName' );
$name = getName();
$this->assertSame( 'human', $name );
$this->assertTrue( $spy->was_called() );

If this is removed, won't that prevent the original global function from being called?

Although, I see that you have a test test__global_function__can_be_restubbed which covers this case, and it passes. How does that work?

Comment on lines -124 to -127
if ( isset( $value ) ) {
return self::filter_return_for( $value );
}
return \Patchwork\relay();
Copy link
Owner

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Ok, I followed the code and it looks like when GlobalSpies::handle_call_for() is called, it eventually calls GlobalSpies::call_original_global_function() thanks to Spy->get_return_for(), which uses \Patchwork\restore() to call the function before recreating the mock with GlobalSpies::replace_global_function().

That explains why this works. Maybe that wasn't always the case which would explain the relay? Or maybe there's another edge case we're not considering here. 🀷

@sirbrillig sirbrillig merged commit 2b19c09 into sirbrillig:master Feb 10, 2022
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

Potential infinite loop on re-stubbing undefined global function without any return value

2 participants