FlockHandlerTest.php 4.1 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124
  1. <?php
  2. namespace Test\Michel\Lock;
  3. use Michel\UniTester\TestCase;
  4. use Michel\Lock\Handler\FlockHandler;
  5. class FlockHandlerTest extends TestCase
  6. {
  7. private string $testDir;
  8. protected function setUp(): void
  9. {
  10. $this->testDir = sys_get_temp_dir() . DIRECTORY_SEPARATOR . 'flock_test_' . uniqid();
  11. if (!is_dir($this->testDir)) {
  12. mkdir($this->testDir);
  13. }
  14. }
  15. protected function tearDown(): void
  16. {
  17. if (is_dir($this->testDir)) {
  18. $files = glob($this->testDir . '/*');
  19. foreach ($files as $file) {
  20. unlink($file);
  21. }
  22. rmdir($this->testDir);
  23. }
  24. }
  25. protected function execute(): void
  26. {
  27. $this->testLockFile();
  28. $this->testUnlockReleasesLock();
  29. $this->testLockNonBlockingFailsIfLocked();
  30. $this->testLockBlockingWaits();
  31. }
  32. private function testLockFile()
  33. {
  34. $handler = new FlockHandler($this->testDir);
  35. $key = 'test_lock_file';
  36. $bool = $handler->lock($key);
  37. $this->assertTrue($bool, 'FlockHandler::lock should return true on success');
  38. $expectedFile = $this->testDir . DIRECTORY_SEPARATOR . 'michel_lock_' . $key . '.lock';
  39. $this->assertFileExists($expectedFile, 'FlockHandler::lock should create a lock file');
  40. $ready = $handler->lock($key);
  41. $this->assertFalse($ready, 'FlockHandler::lock should return false if already locked');
  42. $handler->unlock($key);
  43. $ready = $handler->lock($key);
  44. $this->assertTrue($ready, 'FlockHandler::lock should return true after unlocking');
  45. }
  46. private function testUnlockReleasesLock()
  47. {
  48. $handler = new FlockHandler($this->testDir);
  49. $key = 'test_unlock';
  50. $handler->lock($key);
  51. $handler->unlock($key);
  52. $expectedFile = $this->testDir . DIRECTORY_SEPARATOR . 'michel_lock_' . $key . '.lock';
  53. $this->assertFileExists($expectedFile, 'FlockHandler::unlock should not delete the file');
  54. }
  55. private function testLockNonBlockingFailsIfLocked()
  56. {
  57. $key = 'concurrent_lock';
  58. $lockFile = $this->testDir . DIRECTORY_SEPARATOR . 'michel_lock_' . $key . '.lock';
  59. $tempScript = sys_get_temp_dir() . '/holder.php';
  60. file_put_contents($tempScript, '<?php $f=fopen($argv[1],"c"); flock($f,LOCK_EX); sleep(2);');
  61. $cmd = sprintf('php %s %s', escapeshellarg($tempScript), escapeshellarg($lockFile));
  62. $process = proc_open($cmd, [], $pipes);
  63. usleep(200000);
  64. $handler = new FlockHandler($this->testDir);
  65. $start = microtime(true);
  66. $result = $handler->lock($key, false); // Non-blocking
  67. $end = microtime(true);
  68. $this->assertFalse($result, 'Lock should fail if already locked by another process');
  69. $this->assertTrue(($end - $start) < 1, 'Non-blocking lock should return immediately');
  70. proc_terminate($process);
  71. proc_close($process);
  72. unlink($tempScript);
  73. }
  74. private function testLockBlockingWaits()
  75. {
  76. $key = 'blocking_lock';
  77. $lockFile = $this->testDir . DIRECTORY_SEPARATOR . 'michel_lock_' . $key . '.lock';
  78. $tempScript = sys_get_temp_dir() . '/holder.php';
  79. file_put_contents($tempScript, '<?php $f=fopen($argv[1],"c"); flock($f,LOCK_EX); sleep(2);');
  80. $cmd = sprintf('php %s %s', escapeshellarg($tempScript), escapeshellarg($lockFile));
  81. $process = proc_open($cmd, [], $pipes);
  82. usleep(200000);
  83. $handler = new FlockHandler($this->testDir);
  84. $start = microtime(true);
  85. $result = $handler->lock($key, true); // Blocking
  86. $end = microtime(true);
  87. $elapsed = $end - $start;
  88. $this->assertTrue($result, 'Blocking lock should eventually succeed');
  89. $this->assertTrue($elapsed >= 1.5, 'Blocking lock should wait for lock release (approx 1.5s remaining)');
  90. proc_terminate($process);
  91. proc_close($process);
  92. unlink($tempScript);
  93. }
  94. }