| @@ -8,16 +8,21 @@ | ||
| 8 | 8 | class JobsRunnerTest extends TestCase |
| 9 | 9 | { |
| 10 | 10 | private const ASYNC_ACTION = 'sync_basalam_run_jobs_async'; |
| 11 | 11 | private const DISPATCH_LOCK = 'sync_basalam_jobs_runner_async_dispatch_lock'; |
| 12 | + private const IDLE_PROBE_LOCK = 'sync_basalam_jobs_runner_idle_probe_lock'; | |
| 12 | 13 | |
| 13 | 14 | private $originalRequest; |
| 14 | 15 | private $originalCookie; |
| 16 | + private $originalWpdb; | |
| 17 | + private $hadOriginalWpdb; | |
| 15 | 18 | |
| 16 | 19 | protected function setUp(): void |
| 17 | 20 | { |
| 18 | 21 | $this->originalRequest = $_REQUEST; |
| 19 | 22 | $this->originalCookie = $_COOKIE; |
| 23 | + $this->hadOriginalWpdb = array_key_exists('wpdb', $GLOBALS); | |
| 24 | + $this->originalWpdb = $GLOBALS['wpdb'] ?? null; | |
| 20 | 25 | |
| 21 | 26 | $_REQUEST = []; |
| 22 | 27 | $_COOKIE = []; |
| 23 | 28 | |
| @@ -23,8 +28,9 @@ | ||
| 23 | 28 | |
| 24 | 29 | $GLOBALS['sync_basalam_jobs_runner_test_state'] = [ |
| 25 | 30 | 'actions' => [], |
| 26 | 31 | 'did_actions' => [], |
| 32 | + 'current_filter' => '', | |
| 27 | 33 | 'doing_ajax' => false, |
| 28 | 34 | 'transients' => [], |
| 29 | 35 | 'transient_reads' => [], |
| 30 | 36 | 'transient_writes' => [], |
| @@ -38,8 +44,14 @@ | ||
| 38 | 44 | { |
| 39 | 45 | $_REQUEST = $this->originalRequest; |
| 40 | 46 | $_COOKIE = $this->originalCookie; |
| 41 | 47 | |
| 48 | + if ($this->hadOriginalWpdb) { | |
| 49 | + $GLOBALS['wpdb'] = $this->originalWpdb; | |
| 50 | + } else { | |
| 51 | + unset($GLOBALS['wpdb']); | |
| 52 | + } | |
| 53 | + | |
| 42 | 54 | unset($GLOBALS['sync_basalam_jobs_runner_test_state']); |
| 43 | 55 | } |
| 44 | 56 | |
| 45 | 57 | public function testRegistersDispatcherOnShutdownAndNeverOnInit(): void |
| @@ -48,9 +60,9 @@ | ||
| 48 | 60 | $actions = $this->state()['actions']; |
| 49 | 61 | |
| 50 | 62 | self::assertArrayHasKey('shutdown', $actions); |
| 51 | 63 | self::assertSame([$runner, 'maybeDispatchAsyncRequest'], $actions['shutdown'][0]['callback']); |
| 52 | - self::assertSame(PHP_INT_MAX, $actions['shutdown'][0]['priority']); | |
| 64 | + self::assertSame(2, $actions['shutdown'][0]['priority']); | |
| 53 | 65 | self::assertSame(1, $actions['shutdown'][0]['accepted_args']); |
| 54 | 66 | self::assertArrayNotHasKey('init', $actions); |
| 55 | 67 | |
| 56 | 68 | self::assertSame( |
| @@ -64,9 +76,9 @@ | ||
| 64 | 76 | $actions['wp_ajax_nopriv_' . self::ASYNC_ACTION][0]['callback'] |
| 65 | 77 | ); |
| 66 | 78 | } |
| 67 | 79 | |
| 68 | - public function testDispatchLeaseIsWrittenBeforeQueueProbeAndDispatching(): void | |
| 80 | + public function testDispatchLeaseIsWrittenOnlyWhenQueueHasWork(): void | |
| 69 | 81 | { |
| 70 | 82 | $jobManager = new FakeJobManager([true]); |
| 71 | 83 | $runner = $this->newRunner($jobManager); |
| 72 | 84 | |
| @@ -73,9 +85,9 @@ | ||
| 73 | 85 | $_COOKIE = ['wordpress_test_cookie' => 'cookie-value']; |
| 74 | 86 | $runner->maybeDispatchAsyncRequest(); |
| 75 | 87 | |
| 76 | 88 | self::assertSame( |
| 77 | - ['get_transient', 'set_transient', 'has_pending_jobs', 'remote_post'], | |
| 89 | + ['get_transient', 'get_transient', 'has_pending_jobs', 'get_transient', 'set_transient', 'remote_post'], | |
| 78 | 90 | $this->state()['events'] |
| 79 | 91 | ); |
| 80 | 92 | self::assertSame([120], $jobManager->timeouts); |
| 81 | 93 | self::assertSame( |
| @@ -99,22 +111,47 @@ | ||
| 99 | 111 | self::assertSame('test-nonce-for-' . self::ASYNC_ACTION, $requests[0]['args']['body']['nonce']); |
| 100 | 112 | self::assertSame($_COOKIE, $requests[0]['args']['cookies']); |
| 101 | 113 | } |
| 102 | 114 | |
| 103 | - public function testEmptyQueueStillReservesDispatchLease(): void | |
| 115 | + public function testShutdownRepairsDatabaseBeforeUsingQueueApis(): void | |
| 104 | 116 | { |
| 117 | + $GLOBALS['sync_basalam_jobs_runner_test_state']['current_filter'] = 'shutdown'; | |
| 118 | + $GLOBALS['wpdb'] = new FakeWpdb(); | |
| 119 | + | |
| 105 | 120 | $jobManager = new FakeJobManager([false]); |
| 106 | 121 | $runner = $this->newRunner($jobManager); |
| 107 | 122 | |
| 108 | 123 | $runner->maybeDispatchAsyncRequest(); |
| 109 | 124 | |
| 110 | - self::assertSame(['get_transient', 'set_transient', 'has_pending_jobs'], $this->state()['events']); | |
| 125 | + self::assertSame( | |
| 126 | + [ | |
| 127 | + 'fastcgi_finish_request', | |
| 128 | + 'db_flush', | |
| 129 | + 'db_check_connection', | |
| 130 | + 'get_transient', | |
| 131 | + 'get_transient', | |
| 132 | + 'has_pending_jobs', | |
| 133 | + 'set_transient', | |
| 134 | + ], | |
| 135 | + $this->state()['events'] | |
| 136 | + ); | |
| 137 | + self::assertSame([false], $GLOBALS['wpdb']->allowBailValues); | |
| 138 | + } | |
| 139 | + | |
| 140 | + public function testEmptyQueueReservesOnlyShortIdleProbeLease(): void | |
| 141 | + { | |
| 142 | + $jobManager = new FakeJobManager([false]); | |
| 143 | + $runner = $this->newRunner($jobManager); | |
| 144 | + | |
| 145 | + $runner->maybeDispatchAsyncRequest(); | |
| 146 | + | |
| 147 | + self::assertSame(['get_transient', 'get_transient', 'has_pending_jobs', 'set_transient'], $this->state()['events']); | |
| 111 | 148 | self::assertSame([120], $jobManager->timeouts); |
| 112 | 149 | self::assertSame( |
| 113 | 150 | [[ |
| 114 | - 'name' => self::DISPATCH_LOCK, | |
| 151 | + 'name' => self::IDLE_PROBE_LOCK, | |
| 115 | 152 | 'value' => 1, |
| 116 | - 'expiration' => 25, | |
| 153 | + 'expiration' => 5, | |
| 117 | 154 | ]], |
| 118 | 155 | $this->state()['transient_writes'] |
| 119 | 156 | ); |
| 120 | 157 | self::assertSame([], $this->state()['remote_requests']); |
| @@ -119,23 +156,22 @@ | ||
| 119 | 156 | ); |
| 120 | 157 | self::assertSame([], $this->state()['remote_requests']); |
| 121 | 158 | } |
| 122 | 159 | |
| 123 | - public function testEmptyQueueLeasePreventsASecondProbeInTheSameRequest(): void | |
| 160 | + public function testNewJobBypassesIdleProbeLeaseAndDispatchesImmediately(): void | |
| 124 | 161 | { |
| 125 | 162 | $jobManager = new FakeJobManager([false, true]); |
| 126 | 163 | $runner = $this->newRunner($jobManager); |
| 127 | 164 | |
| 128 | - // The shutdown probe reserves the lease even when no work is found. | |
| 129 | 165 | $runner->maybeDispatchAsyncRequest(); |
| 130 | 166 | self::assertCount(1, $this->state()['transient_writes']); |
| 131 | 167 | |
| 132 | - // A second callback in the same request must observe that lease. | |
| 168 | + $GLOBALS['sync_basalam_jobs_runner_test_state']['current_filter'] = 'sync_basalam_job_created'; | |
| 133 | 169 | $runner->maybeDispatchAsyncRequest(); |
| 134 | 170 | |
| 135 | - self::assertSame([120], $jobManager->timeouts); | |
| 136 | - self::assertCount(1, $this->state()['transient_writes']); | |
| 137 | - self::assertCount(0, $this->state()['remote_requests']); | |
| 171 | + self::assertSame([120, 120], $jobManager->timeouts); | |
| 172 | + self::assertSame(self::DISPATCH_LOCK, $this->state()['transient_writes'][1]['name']); | |
| 173 | + self::assertCount(1, $this->state()['remote_requests']); | |
| 138 | 174 | } |
| 139 | 175 | |
| 140 | 176 | public function testExistingLockSkipsQueueCheckAndDispatch(): void |
| 141 | 177 | { |
| @@ -144,9 +180,9 @@ | ||
| 144 | 180 | $runner = $this->newRunner($jobManager); |
| 145 | 181 | |
| 146 | 182 | $runner->maybeDispatchAsyncRequest(); |
| 147 | 183 | |
| 148 | - self::assertSame(['get_transient'], $this->state()['events']); | |
| 184 | + self::assertSame(['get_transient', 'get_transient'], $this->state()['events']); | |
| 149 | 185 | self::assertSame([], $jobManager->timeouts); |
| 150 | 186 | self::assertSame([], $this->state()['transient_writes']); |
| 151 | 187 | self::assertSame([], $this->state()['remote_requests']); |
| 152 | 188 | } |
| @@ -253,8 +289,26 @@ | ||
| 253 | 289 | $GLOBALS['sync_basalam_jobs_runner_test_state']['events'][] = 'has_pending_jobs'; |
| 254 | 290 | $this->timeouts[] = $timeout; |
| 255 | 291 | |
| 256 | 292 | return (bool) array_shift($this->pendingResults); |
| 293 | + } | |
| 294 | +} | |
| 295 | + | |
| 296 | +class FakeWpdb | |
| 297 | +{ | |
| 298 | + public $allowBailValues = []; | |
| 299 | + | |
| 300 | + public function flush(): void | |
| 301 | + { | |
| 302 | + $GLOBALS['sync_basalam_jobs_runner_test_state']['events'][] = 'db_flush'; | |
| 303 | + } | |
| 304 | + | |
| 305 | + public function check_connection($allowBail = true): bool | |
| 306 | + { | |
| 307 | + $GLOBALS['sync_basalam_jobs_runner_test_state']['events'][] = 'db_check_connection'; | |
| 308 | + $this->allowBailValues[] = $allowBail; | |
| 309 | + | |
| 310 | + return true; | |
| 257 | 311 | } |
| 258 | 312 | } |
| 259 | 313 | |
| 260 | 314 | class FakeHttpBlockService |