1<?php 2 3namespace dokuwiki\test\Search\Index; 4 5use dokuwiki\Search\Exception\IndexLockException; 6use dokuwiki\Search\Index\Lock; 7 8class LockTest extends \DokuWikiTest 9{ 10 protected function tearDown(): void 11 { 12 Lock::releaseAll(); 13 // restore the default wait timeout in case a test lowered it 14 self::setInaccessibleProperty(new Lock(), 'waitTimeout', 3); 15 parent::tearDown(); 16 } 17 18 public function testAcquireAndRelease() 19 { 20 Lock::acquire('test_lock'); 21 22 // lock directory should exist 23 global $conf; 24 $this->assertDirectoryExists($conf['lockdir'] . '/test_lock.index'); 25 26 Lock::release('test_lock'); 27 28 // lock directory should be removed 29 $this->assertDirectoryDoesNotExist($conf['lockdir'] . '/test_lock.index'); 30 } 31 32 public function testReferenceCounting() 33 { 34 global $conf; 35 $dir = $conf['lockdir'] . '/refcount.index'; 36 37 Lock::acquire('refcount'); 38 Lock::acquire('refcount'); 39 $this->assertDirectoryExists($dir); 40 41 // first release only decrements, lock stays 42 Lock::release('refcount'); 43 $this->assertDirectoryExists($dir); 44 45 // second release removes the lock 46 Lock::release('refcount'); 47 $this->assertDirectoryDoesNotExist($dir); 48 } 49 50 public function testReleaseUnheldLockIsNoop() 51 { 52 // should not throw or error 53 Lock::release('never_acquired'); 54 $this->assertTrue(true); 55 } 56 57 public function testAcquireFailsWhenAlreadyLockedByAnotherProcess() 58 { 59 global $conf; 60 $dir = $conf['lockdir'] . '/foreign.index'; 61 62 // simulate a foreign lock by creating the directory directly 63 mkdir($dir); 64 // set mtime to now so it's not stale 65 touch($dir); 66 67 // don't wait for the foreign lock in the test 68 self::setInaccessibleProperty(new Lock(), 'waitTimeout', 0); 69 70 $this->expectException(IndexLockException::class); 71 Lock::acquire('foreign'); 72 } 73 74 public function testAcquireAppliesConfiguredDirectoryPermissions() 75 { 76 if (strtoupper(substr(PHP_OS, 0, 3)) === 'WIN') { 77 $this->markTestSkipped('Permission checks skipped on Windows'); 78 } 79 80 global $conf; 81 $dir = $conf['lockdir'] . '/perm.index'; 82 83 $oldumask = umask(0); 84 $conf['dperm'] = 0707; 85 try { 86 Lock::acquire('perm'); 87 88 clearstatcache(); 89 $this->assertSame(0707, fileperms($dir) & 0777); 90 } finally { 91 Lock::release('perm'); 92 umask($oldumask); 93 } 94 } 95 96 public function testStaleLockIsOverridden() 97 { 98 global $conf; 99 $dir = $conf['lockdir'] . '/stale.index'; 100 101 // simulate a stale lock (older than 5 minutes) 102 mkdir($dir); 103 touch($dir, time() - 400); 104 105 // should succeed by removing the stale lock 106 Lock::acquire('stale'); 107 $this->assertDirectoryExists($dir); 108 109 Lock::release('stale'); 110 } 111 112 public function testReleaseAll() 113 { 114 global $conf; 115 116 Lock::acquire('all_a'); 117 Lock::acquire('all_b'); 118 Lock::acquire('all_a'); // refcount 2 119 120 Lock::releaseAll(); 121 122 $this->assertDirectoryDoesNotExist($conf['lockdir'] . '/all_a.index'); 123 $this->assertDirectoryDoesNotExist($conf['lockdir'] . '/all_b.index'); 124 125 // releasing after releaseAll should be safe 126 Lock::release('all_a'); 127 } 128 129 public function testMultipleIndependentLocks() 130 { 131 global $conf; 132 133 Lock::acquire('ind_a'); 134 Lock::acquire('ind_b'); 135 136 $this->assertDirectoryExists($conf['lockdir'] . '/ind_a.index'); 137 $this->assertDirectoryExists($conf['lockdir'] . '/ind_b.index'); 138 139 Lock::release('ind_a'); 140 $this->assertDirectoryDoesNotExist($conf['lockdir'] . '/ind_a.index'); 141 $this->assertDirectoryExists($conf['lockdir'] . '/ind_b.index'); 142 143 Lock::release('ind_b'); 144 $this->assertDirectoryDoesNotExist($conf['lockdir'] . '/ind_b.index'); 145 } 146 147 public function testAcquireCreatesLockInsideConfiguredLockDirectory() 148 { 149 global $conf; 150 151 $dir = $conf['lockdir'] . '/page.index'; 152 $wrongDir = dirname($conf['lockdir']) . '/page.index'; 153 154 Lock::acquire('page'); 155 156 $this->assertDirectoryExists($dir); 157 $this->assertDirectoryDoesNotExist($wrongDir); 158 159 Lock::release('page'); 160 } 161} 162