Merge "Update docs/hooks.txt for ShowSearchHitTitle"
[mediawiki.git] / tests / phpunit / includes / jobqueue / JobQueueTest.php
blob7b34b59b152182ba1be3bbfd30f9457b1df41a58
1 <?php
3 /**
4 * @group JobQueue
5 * @group medium
6 * @group Database
7 */
8 class JobQueueTest extends MediaWikiTestCase {
9 protected $key;
10 protected $queueRand, $queueRandTTL, $queueFifo, $queueFifoTTL;
12 function __construct( $name = null, array $data = [], $dataName = '' ) {
13 parent::__construct( $name, $data, $dataName );
15 $this->tablesUsed[] = 'job';
18 protected function setUp() {
19 global $wgJobTypeConf;
20 parent::setUp();
22 if ( $this->getCliArg( 'use-jobqueue' ) ) {
23 $name = $this->getCliArg( 'use-jobqueue' );
24 if ( !isset( $wgJobTypeConf[$name] ) ) {
25 throw new MWException( "No \$wgJobTypeConf entry for '$name'." );
27 $baseConfig = $wgJobTypeConf[$name];
28 } else {
29 $baseConfig = [ 'class' => 'JobQueueDB' ];
31 $baseConfig['type'] = 'null';
32 $baseConfig['wiki'] = wfWikiID();
33 $variants = [
34 'queueRand' => [ 'order' => 'random', 'claimTTL' => 0 ],
35 'queueRandTTL' => [ 'order' => 'random', 'claimTTL' => 10 ],
36 'queueTimestamp' => [ 'order' => 'timestamp', 'claimTTL' => 0 ],
37 'queueTimestampTTL' => [ 'order' => 'timestamp', 'claimTTL' => 10 ],
38 'queueFifo' => [ 'order' => 'fifo', 'claimTTL' => 0 ],
39 'queueFifoTTL' => [ 'order' => 'fifo', 'claimTTL' => 10 ],
41 foreach ( $variants as $q => $settings ) {
42 try {
43 $this->$q = JobQueue::factory( $settings + $baseConfig );
44 } catch ( MWException $e ) {
45 // unsupported?
46 // @todo What if it was another error?
51 protected function tearDown() {
52 parent::tearDown();
53 foreach (
55 'queueRand', 'queueRandTTL', 'queueTimestamp', 'queueTimestampTTL',
56 'queueFifo', 'queueFifoTTL'
57 ] as $q
58 ) {
59 if ( $this->$q ) {
60 $this->$q->delete();
62 $this->$q = null;
66 /**
67 * @dataProvider provider_queueLists
68 * @covers JobQueue::getWiki
70 public function testGetWiki( $queue, $recycles, $desc ) {
71 $queue = $this->$queue;
72 if ( !$queue ) {
73 $this->markTestSkipped( $desc );
75 $this->assertEquals( wfWikiID(), $queue->getWiki(), "Proper wiki ID ($desc)" );
78 /**
79 * @dataProvider provider_queueLists
80 * @covers JobQueue::getType
82 public function testGetType( $queue, $recycles, $desc ) {
83 $queue = $this->$queue;
84 if ( !$queue ) {
85 $this->markTestSkipped( $desc );
87 $this->assertEquals( 'null', $queue->getType(), "Proper job type ($desc)" );
90 /**
91 * @dataProvider provider_queueLists
92 * @covers JobQueue
94 public function testBasicOperations( $queue, $recycles, $desc ) {
95 $queue = $this->$queue;
96 if ( !$queue ) {
97 $this->markTestSkipped( $desc );
100 $this->assertTrue( $queue->isEmpty(), "Queue is empty ($desc)" );
102 $queue->flushCaches();
103 $this->assertEquals( 0, $queue->getSize(), "Queue is empty ($desc)" );
104 $this->assertEquals( 0, $queue->getAcquiredCount(), "Queue is empty ($desc)" );
106 $this->assertNull( $queue->push( $this->newJob() ), "Push worked ($desc)" );
107 $this->assertNull( $queue->batchPush( [ $this->newJob() ] ), "Push worked ($desc)" );
109 $this->assertFalse( $queue->isEmpty(), "Queue is not empty ($desc)" );
111 $queue->flushCaches();
112 $this->assertEquals( 2, $queue->getSize(), "Queue size is correct ($desc)" );
113 $this->assertEquals( 0, $queue->getAcquiredCount(), "No jobs active ($desc)" );
114 $jobs = iterator_to_array( $queue->getAllQueuedJobs() );
115 $this->assertEquals( 2, count( $jobs ), "Queue iterator size is correct ($desc)" );
117 $job1 = $queue->pop();
118 $this->assertFalse( $queue->isEmpty(), "Queue is not empty ($desc)" );
120 $queue->flushCaches();
121 $this->assertEquals( 1, $queue->getSize(), "Queue size is correct ($desc)" );
123 $queue->flushCaches();
124 if ( $recycles ) {
125 $this->assertEquals( 1, $queue->getAcquiredCount(), "Active job count ($desc)" );
128 $job2 = $queue->pop();
129 $this->assertTrue( $queue->isEmpty(), "Queue is empty ($desc)" );
130 $this->assertEquals( 0, $queue->getSize(), "Queue is empty ($desc)" );
132 $queue->flushCaches();
133 if ( $recycles ) {
134 $this->assertEquals( 2, $queue->getAcquiredCount(), "Active job count ($desc)" );
137 $queue->ack( $job1 );
139 $queue->flushCaches();
140 if ( $recycles ) {
141 $this->assertEquals( 1, $queue->getAcquiredCount(), "Active job count ($desc)" );
144 $queue->ack( $job2 );
146 $queue->flushCaches();
147 $this->assertEquals( 0, $queue->getAcquiredCount(), "Active job count ($desc)" );
149 $this->assertNull( $queue->batchPush( [ $this->newJob(), $this->newJob() ] ),
150 "Push worked ($desc)" );
151 $this->assertFalse( $queue->isEmpty(), "Queue is not empty ($desc)" );
153 $queue->delete();
154 $queue->flushCaches();
155 $this->assertTrue( $queue->isEmpty(), "Queue is empty ($desc)" );
156 $this->assertEquals( 0, $queue->getSize(), "Queue is empty ($desc)" );
160 * @dataProvider provider_queueLists
161 * @covers JobQueue
163 public function testBasicDeduplication( $queue, $recycles, $desc ) {
164 $queue = $this->$queue;
165 if ( !$queue ) {
166 $this->markTestSkipped( $desc );
169 $this->assertTrue( $queue->isEmpty(), "Queue is empty ($desc)" );
171 $queue->flushCaches();
172 $this->assertEquals( 0, $queue->getSize(), "Queue is empty ($desc)" );
173 $this->assertEquals( 0, $queue->getAcquiredCount(), "Queue is empty ($desc)" );
175 $this->assertNull(
176 $queue->batchPush(
177 [ $this->newDedupedJob(), $this->newDedupedJob(), $this->newDedupedJob() ]
179 "Push worked ($desc)" );
181 $this->assertFalse( $queue->isEmpty(), "Queue is not empty ($desc)" );
183 $queue->flushCaches();
184 $this->assertEquals( 1, $queue->getSize(), "Queue size is correct ($desc)" );
185 $this->assertEquals( 0, $queue->getAcquiredCount(), "No jobs active ($desc)" );
187 $this->assertNull(
188 $queue->batchPush(
189 [ $this->newDedupedJob(), $this->newDedupedJob(), $this->newDedupedJob() ]
191 "Push worked ($desc)"
194 $this->assertFalse( $queue->isEmpty(), "Queue is not empty ($desc)" );
196 $queue->flushCaches();
197 $this->assertEquals( 1, $queue->getSize(), "Queue size is correct ($desc)" );
198 $this->assertEquals( 0, $queue->getAcquiredCount(), "No jobs active ($desc)" );
200 $job1 = $queue->pop();
201 $this->assertTrue( $queue->isEmpty(), "Queue is empty ($desc)" );
203 $queue->flushCaches();
204 $this->assertEquals( 0, $queue->getSize(), "Queue is empty ($desc)" );
205 if ( $recycles ) {
206 $this->assertEquals( 1, $queue->getAcquiredCount(), "Active job count ($desc)" );
209 $queue->ack( $job1 );
211 $queue->flushCaches();
212 $this->assertEquals( 0, $queue->getAcquiredCount(), "Active job count ($desc)" );
216 * @dataProvider provider_queueLists
217 * @covers JobQueue
219 public function testDeduplicationWhileClaimed( $queue, $recycles, $desc ) {
220 $queue = $this->$queue;
221 if ( !$queue ) {
222 $this->markTestSkipped( $desc );
225 $job = $this->newDedupedJob();
226 $queue->push( $job );
228 // De-duplication does not apply to already-claimed jobs
229 $j = $queue->pop();
230 $queue->push( $job );
231 $queue->ack( $j );
233 $j = $queue->pop();
234 // Make sure ack() of the twin did not delete the sibling data
235 $this->assertType( 'NullJob', $j );
239 * @dataProvider provider_queueLists
240 * @covers JobQueue
242 public function testRootDeduplication( $queue, $recycles, $desc ) {
243 $queue = $this->$queue;
244 if ( !$queue ) {
245 $this->markTestSkipped( $desc );
248 $this->assertTrue( $queue->isEmpty(), "Queue is empty ($desc)" );
250 $queue->flushCaches();
251 $this->assertEquals( 0, $queue->getSize(), "Queue is empty ($desc)" );
252 $this->assertEquals( 0, $queue->getAcquiredCount(), "Queue is empty ($desc)" );
254 $id = wfRandomString( 32 );
255 $root1 = Job::newRootJobParams( "nulljobspam:$id" ); // task ID/timestamp
256 for ( $i = 0; $i < 5; ++$i ) {
257 $this->assertNull( $queue->push( $this->newJob( 0, $root1 ) ), "Push worked ($desc)" );
259 $queue->deduplicateRootJob( $this->newJob( 0, $root1 ) );
261 $root2 = $root1;
262 # Add a second to UNIX epoch and format back to TS_MW
263 $root2_ts = strtotime( $root2['rootJobTimestamp'] );
264 $root2_ts++;
265 $root2['rootJobTimestamp'] = wfTimestamp( TS_MW, $root2_ts );
267 $this->assertNotEquals( $root1['rootJobTimestamp'], $root2['rootJobTimestamp'],
268 "Root job signatures have different timestamps." );
269 for ( $i = 0; $i < 5; ++$i ) {
270 $this->assertNull( $queue->push( $this->newJob( 0, $root2 ) ), "Push worked ($desc)" );
272 $queue->deduplicateRootJob( $this->newJob( 0, $root2 ) );
274 $this->assertFalse( $queue->isEmpty(), "Queue is not empty ($desc)" );
276 $queue->flushCaches();
277 $this->assertEquals( 10, $queue->getSize(), "Queue size is correct ($desc)" );
278 $this->assertEquals( 0, $queue->getAcquiredCount(), "No jobs active ($desc)" );
280 $dupcount = 0;
281 $jobs = [];
282 do {
283 $job = $queue->pop();
284 if ( $job ) {
285 $jobs[] = $job;
286 $queue->ack( $job );
288 if ( $job instanceof DuplicateJob ) {
289 ++$dupcount;
291 } while ( $job );
293 $this->assertEquals( 10, count( $jobs ), "Correct number of jobs popped ($desc)" );
294 $this->assertEquals( 5, $dupcount, "Correct number of duplicate jobs popped ($desc)" );
298 * @dataProvider provider_fifoQueueLists
299 * @covers JobQueue
301 public function testJobOrder( $queue, $recycles, $desc ) {
302 $queue = $this->$queue;
303 if ( !$queue ) {
304 $this->markTestSkipped( $desc );
307 $this->assertTrue( $queue->isEmpty(), "Queue is empty ($desc)" );
309 $queue->flushCaches();
310 $this->assertEquals( 0, $queue->getSize(), "Queue is empty ($desc)" );
311 $this->assertEquals( 0, $queue->getAcquiredCount(), "Queue is empty ($desc)" );
313 for ( $i = 0; $i < 10; ++$i ) {
314 $this->assertNull( $queue->push( $this->newJob( $i ) ), "Push worked ($desc)" );
317 for ( $i = 0; $i < 10; ++$i ) {
318 $job = $queue->pop();
319 $this->assertTrue( $job instanceof Job, "Jobs popped from queue ($desc)" );
320 $params = $job->getParams();
321 $this->assertEquals( $i, $params['i'], "Job popped from queue is FIFO ($desc)" );
322 $queue->ack( $job );
325 $this->assertFalse( $queue->pop(), "Queue is not empty ($desc)" );
327 $queue->flushCaches();
328 $this->assertEquals( 0, $queue->getSize(), "Queue is empty ($desc)" );
329 $this->assertEquals( 0, $queue->getAcquiredCount(), "No jobs active ($desc)" );
333 * @covers JobQueue
335 public function testQueueAggregateTable() {
336 $queue = $this->queueFifo;
337 if ( !$queue || !method_exists( $queue, 'getServerQueuesWithJobs' ) ) {
338 $this->markTestSkipped();
341 $this->assertNotContains(
342 [ $queue->getType(), $queue->getWiki() ],
343 $queue->getServerQueuesWithJobs(),
344 "Null queue not in listing"
347 $queue->push( $this->newJob( 0 ) );
349 $this->assertContains(
350 [ $queue->getType(), $queue->getWiki() ],
351 $queue->getServerQueuesWithJobs(),
352 "Null queue in listing"
356 public static function provider_queueLists() {
357 return [
358 [ 'queueRand', false, 'Random queue without ack()' ],
359 [ 'queueRandTTL', true, 'Random queue with ack()' ],
360 [ 'queueTimestamp', false, 'Time ordered queue without ack()' ],
361 [ 'queueTimestampTTL', true, 'Time ordered queue with ack()' ],
362 [ 'queueFifo', false, 'FIFO ordered queue without ack()' ],
363 [ 'queueFifoTTL', true, 'FIFO ordered queue with ack()' ]
367 public static function provider_fifoQueueLists() {
368 return [
369 [ 'queueFifo', false, 'Ordered queue without ack()' ],
370 [ 'queueFifoTTL', true, 'Ordered queue with ack()' ]
374 function newJob( $i = 0, $rootJob = [] ) {
375 return new NullJob( Title::newMainPage(),
376 [ 'lives' => 0, 'usleep' => 0, 'removeDuplicates' => 0, 'i' => $i ] + $rootJob );
379 function newDedupedJob( $i = 0, $rootJob = [] ) {
380 return new NullJob( Title::newMainPage(),
381 [ 'lives' => 0, 'usleep' => 0, 'removeDuplicates' => 1, 'i' => $i ] + $rootJob );