xref: /dokuwiki/_test/tests/Search/Index/LockTest.php (revision 057ab5a057a76df652cfe4176512a550ca9937f6)
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