A great example unit test for a PHP process that needs to fork to try concurrency locking
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
<?php | |
/** | |
* When lockMutex() happens, we aquire a semaphore | |
* no other processes have access while this is locked | |
* | |
* This unit test forks into 2 processes and verifies locking | |
*/ | |
// Test that locking actually makes me wait if someone else has the lock. | |
public function testForkLockMutex() { | |
// tempFile used for communication between parent and child. | |
$tempFile = tmpfile(); | |
// Split into two.. | |
$pid = pcntl_fork(); | |
if ($pid == -1) { | |
$this->assertEqual('shit went down. [failed to fork]', 'no shit went down'); | |
return; | |
} | |
if ($pid) { | |
// I am parent. | |
// Wait 5000 microseconds / 5 milliseconds. Give child time to grab lock. | |
usleep(5000); | |
$timeBeforeLock = microtime(true); | |
// Get lock. | |
$this->assertTrue($this->Model->lockMutex(1)); | |
$timeAfterLock = microtime(true); | |
$this->assertTrue($this->Model->unlockMutex(1)); | |
// How much time elapsed? | |
$timeElapsed = $timeAfterLock - $timeBeforeLock; | |
// Amount of time elapsed should be (150ms - 5ms + x + y +/- z), where | |
// x is the fork time, y is the overhead time from unlockMutex/assert, and z is | |
// the "fudge factor" from OS scheduling... | |
// Allow range [140, 200] ms | |
$this->assertGreaterThanOrEqual(0.140, $timeElapsed); | |
$this->assertLessThanOrEqual( 0.200, $timeElapsed); | |
// Make sure Child passed tests and delete tempFile | |
fseek($tempFile, 0); | |
$messageFromChild = fread($tempFile, 1024); | |
$this->assertEqual($messageFromChild, 'pass'); | |
fclose($tempFile); | |
} else { | |
// I am child. | |
// Get lock. | |
$lockStatus = $this->Model->lockMutex(1); | |
// Hold lock for 150 milliseconds / 150000 microseconds, then unlock | |
usleep(150000); | |
$unlockStatus = $this->Model->unlockMutex(1); | |
// Write test status to temp file, so parent can read it. | |
if ($lockStatus === true && $unlockStatus === true) { | |
fwrite($tempFile, "pass"); | |
} else { | |
fwrite($tempFile, "fail"); | |
} | |
// Kill self with a "kill" command. | |
// If we used exit or die, then CakeTestCase | |
// would take over and start to tear down all of the | |
// fixtures! Our parent still needs it. | |
posix_kill(posix_getpid(), SIGTERM); | |
} | |
} |
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment