8 class JobQueueTest
extends MediaWikiTestCase
{
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;
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];
29 $baseConfig = [ 'class' => 'JobQueueDB' ];
31 $baseConfig['type'] = 'null';
32 $baseConfig['wiki'] = wfWikiID();
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 ) {
43 $this->$q = JobQueue
::factory( $settings +
$baseConfig );
44 } catch ( MWException
$e ) {
46 // @todo What if it was another error?
51 protected function tearDown() {
55 'queueRand', 'queueRandTTL', 'queueTimestamp', 'queueTimestampTTL',
56 'queueFifo', 'queueFifoTTL'
67 * @dataProvider provider_queueLists
68 * @covers JobQueue::getWiki
70 public function testGetWiki( $queue, $recycles, $desc ) {
71 $queue = $this->$queue;
73 $this->markTestSkipped( $desc );
75 $this->assertEquals( wfWikiID(), $queue->getWiki(), "Proper wiki ID ($desc)" );
79 * @dataProvider provider_queueLists
80 * @covers JobQueue::getType
82 public function testGetType( $queue, $recycles, $desc ) {
83 $queue = $this->$queue;
85 $this->markTestSkipped( $desc );
87 $this->assertEquals( 'null', $queue->getType(), "Proper job type ($desc)" );
91 * @dataProvider provider_queueLists
94 public function testBasicOperations( $queue, $recycles, $desc ) {
95 $queue = $this->$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();
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();
134 $this->assertEquals( 2, $queue->getAcquiredCount(), "Active job count ($desc)" );
137 $queue->ack( $job1 );
139 $queue->flushCaches();
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)" );
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
163 public function testBasicDeduplication( $queue, $recycles, $desc ) {
164 $queue = $this->$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)" );
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)" );
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)" );
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
219 public function testDeduplicationWhileClaimed( $queue, $recycles, $desc ) {
220 $queue = $this->$queue;
222 $this->markTestSkipped( $desc );
225 $job = $this->newDedupedJob();
226 $queue->push( $job );
228 // De-duplication does not apply to already-claimed jobs
230 $queue->push( $job );
234 // Make sure ack() of the twin did not delete the sibling data
235 $this->assertType( 'NullJob', $j );
239 * @dataProvider provider_queueLists
242 public function testRootDeduplication( $queue, $recycles, $desc ) {
243 $queue = $this->$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 ) );
262 # Add a second to UNIX epoch and format back to TS_MW
263 $root2_ts = strtotime( $root2['rootJobTimestamp'] );
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)" );
283 $job = $queue->pop();
288 if ( $job instanceof DuplicateJob
) {
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
301 public function testJobOrder( $queue, $recycles, $desc ) {
302 $queue = $this->$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)" );
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)" );
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() {
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() {
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 );